feat: add native Qwen Code hook integration
This commit is contained in:
1 parent
3999ed0f44
commit
d59b0101b1
12 files changed
+1637
No files matched your search
@@ -0,0 +1,188 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { spawn } from "node:child_process";
|
||||
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { parse } from "jsonc-parser";
|
||||
|
||||
const pluginModuleUrl = pathToFileURL(
|
||||
path.resolve("dist/plugins/qwen.js"),
|
||||
).href;
|
||||
|
||||
export const registryModuleUrl = pathToFileURL(
|
||||
path.resolve("dist/plugins/registry.js"),
|
||||
).href;
|
||||
export const cliPath = path.resolve("dist/cli.js");
|
||||
export const expectedShell =
|
||||
process.platform === "win32" ? "powershell" : "bash";
|
||||
|
||||
export function runNode(args, env, input = "", cwd) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(process.execPath, args, {
|
||||
cwd,
|
||||
env,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
child.stdout.on("data", (chunk) => {
|
||||
stdout += chunk;
|
||||
});
|
||||
child.stderr.on("data", (chunk) => {
|
||||
stderr += chunk;
|
||||
});
|
||||
child.once("error", reject);
|
||||
child.once("close", (code) => resolve({ code, stdout, stderr }));
|
||||
child.stdin.end(input);
|
||||
});
|
||||
}
|
||||
|
||||
export function childEnv(fixture, overrides = {}) {
|
||||
const env = {
|
||||
...process.env,
|
||||
HOME: fixture.homeDir,
|
||||
USERPROFILE: fixture.homeDir,
|
||||
...overrides,
|
||||
};
|
||||
if (!Object.hasOwn(overrides, "QWEN_HOME")) delete env.QWEN_HOME;
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
if (value === undefined) delete env[key];
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
export function runPlugin(fixture, method, argument, overrides = {}) {
|
||||
const script = `
|
||||
import { qwenPlugin } from ${JSON.stringify(pluginModuleUrl)};
|
||||
const argument = JSON.parse(process.env.ELYDORA_TEST_ARGUMENT);
|
||||
const result = await qwenPlugin[process.env.ELYDORA_TEST_METHOD](argument);
|
||||
if (result !== undefined) console.log(JSON.stringify(result));
|
||||
`;
|
||||
return runNode(
|
||||
["--input-type=module", "--eval", script],
|
||||
childEnv(fixture, {
|
||||
...overrides,
|
||||
ELYDORA_TEST_ARGUMENT: JSON.stringify(argument),
|
||||
ELYDORA_TEST_METHOD: method,
|
||||
}),
|
||||
"",
|
||||
fixture.workspaceDir,
|
||||
);
|
||||
}
|
||||
|
||||
function quoteShell(value) {
|
||||
return process.platform === "win32"
|
||||
? `'${value.replaceAll("'", "''")}'`
|
||||
: `'${value.replaceAll("'", `'"'"'`)}'`;
|
||||
}
|
||||
|
||||
export function generatedCommand(scriptPath) {
|
||||
const invocation = `${quoteShell(process.execPath)} ${quoteShell(scriptPath)}`;
|
||||
return process.platform === "win32"
|
||||
? `& ${invocation}; exit $LASTEXITCODE`
|
||||
: invocation;
|
||||
}
|
||||
|
||||
export function runHook(handler, input) {
|
||||
const command = process.platform === "win32" ? "powershell" : "bash";
|
||||
const args =
|
||||
process.platform === "win32"
|
||||
? ["-NoProfile", "-NonInteractive", "-Command", handler.command]
|
||||
: ["-c", handler.command];
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(command, args, { stdio: ["pipe", "pipe", "pipe"] });
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
child.stdout.on("data", (chunk) => {
|
||||
stdout += chunk;
|
||||
});
|
||||
child.stderr.on("data", (chunk) => {
|
||||
stderr += chunk;
|
||||
});
|
||||
child.once("error", reject);
|
||||
child.once("close", (code) => resolve({ code, stdout, stderr }));
|
||||
child.stdin.end(input);
|
||||
});
|
||||
}
|
||||
|
||||
export function parseSettings(raw) {
|
||||
const errors = [];
|
||||
const value = parse(raw, errors, {
|
||||
allowTrailingComma: false,
|
||||
disallowComments: false,
|
||||
});
|
||||
assert.deepEqual(errors, []);
|
||||
return value;
|
||||
}
|
||||
|
||||
export function managedHandler(settings, event, scriptPath) {
|
||||
const command = generatedCommand(scriptPath);
|
||||
for (const group of settings.hooks?.[event] ?? []) {
|
||||
const handler = group.hooks.find(
|
||||
(candidate) => candidate.command === command,
|
||||
);
|
||||
if (handler) return handler;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function createFixture({
|
||||
existingSettings,
|
||||
guardSource,
|
||||
hookSource,
|
||||
} = {}) {
|
||||
const rootDir = await mkdtemp(path.join(os.tmpdir(), "elydora-qwen-"));
|
||||
const homeDir = path.join(rootDir, "home with spaces and 'quote");
|
||||
const workspaceDir = path.join(homeDir, "workspace");
|
||||
const agentDir = path.join(homeDir, ".elydora", "agent-1");
|
||||
const configPath = path.join(homeDir, ".qwen", "settings.json");
|
||||
const guardScriptPath = path.join(agentDir, "guard.js");
|
||||
const hookScriptPath = path.join(agentDir, "hook.js");
|
||||
await mkdir(workspaceDir, { recursive: true });
|
||||
await mkdir(agentDir, { recursive: true });
|
||||
await writeFile(
|
||||
guardScriptPath,
|
||||
guardSource ??
|
||||
"process.stderr.write('Agent is frozen by Elydora.'); process.exit(2);\n",
|
||||
);
|
||||
await writeFile(hookScriptPath, hookSource ?? "process.exit(0);\n");
|
||||
await writeFile(
|
||||
path.join(agentDir, "config.json"),
|
||||
JSON.stringify({ agent_id: "agent-1", agent_name: "qwen" }),
|
||||
);
|
||||
if (existingSettings !== undefined) {
|
||||
await mkdir(path.dirname(configPath), { recursive: true });
|
||||
await writeFile(
|
||||
configPath,
|
||||
typeof existingSettings === "string"
|
||||
? existingSettings
|
||||
: `${JSON.stringify(existingSettings, null, 2)}\n`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
agentDir,
|
||||
configPath,
|
||||
guardScriptPath,
|
||||
homeDir,
|
||||
hookScriptPath,
|
||||
rootDir,
|
||||
workspaceDir,
|
||||
async install(overrides = {}) {
|
||||
return runPlugin(
|
||||
this,
|
||||
"install",
|
||||
{
|
||||
agentName: "qwen",
|
||||
agentId: "agent-1",
|
||||
guardScriptPath,
|
||||
hookScriptPath,
|
||||
},
|
||||
overrides,
|
||||
);
|
||||
},
|
||||
async close() {
|
||||
await rm(rootDir, { recursive: true, force: true });
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user