- qwen_delegate MCP tool: src/qwen-delegate.ts (testable core, subprocess spawn/timeout/parse) + src/qwen-delegate-server.ts (thin MCP stdio wiring) - test/qwen-delegate.test.ts (node:test, mocked spawn) - docs/agents/* + CLAUDE.md from /setup-matt-pocock-skills (Gitea issue tracker via tea CLI, default triage labels, single-context domain docs) - research/qwen-mcp-delegation.md, corrected after confirming qwen-code runs natively on Windows (no WSL) against a local OpenAI-compatible proxy Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRnb5Gqdu7gVTQrwAqFdfJ
74 lines
2.4 KiB
TypeScript
74 lines
2.4 KiB
TypeScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { EventEmitter } from "node:events";
|
|
import { delegateToQwen } from "../src/qwen-delegate.ts";
|
|
|
|
// Fake child_process.spawn: returns an EventEmitter with stdout/stderr sub-emitters and a
|
|
// kill() spy, driven manually by each test via the returned `child` handle.
|
|
function fakeSpawn() {
|
|
const child: any = new EventEmitter();
|
|
child.stdout = new EventEmitter();
|
|
child.stderr = new EventEmitter();
|
|
child.killed = false;
|
|
child.kill = () => {
|
|
child.killed = true;
|
|
};
|
|
const spawnFn = () => child;
|
|
return { spawnFn, child };
|
|
}
|
|
|
|
test("resolves ok with trimmed stdout on exit code 0", async () => {
|
|
const { spawnFn, child } = fakeSpawn();
|
|
const promise = delegateToQwen("hi", { spawnFn });
|
|
|
|
child.stdout.emit("data", " OK\n");
|
|
child.emit("close", 0);
|
|
|
|
assert.deepEqual(await promise, { ok: true, output: "OK" });
|
|
});
|
|
|
|
test("resolves not-ok with stderr detail on non-zero exit", async () => {
|
|
const { spawnFn, child } = fakeSpawn();
|
|
const promise = delegateToQwen("hi", { spawnFn });
|
|
|
|
child.stderr.emit("data", "API Key fehlt.\n");
|
|
child.emit("close", 1);
|
|
|
|
const result = await promise;
|
|
assert.equal(result.ok, false);
|
|
assert.match(result.output, /exited with code 1/);
|
|
assert.match(result.output, /API Key fehlt/);
|
|
});
|
|
|
|
test("resolves not-ok when spawn itself fails (e.g. qwen not on PATH)", async () => {
|
|
const { spawnFn, child } = fakeSpawn();
|
|
const promise = delegateToQwen("hi", { spawnFn });
|
|
|
|
child.emit("error", new Error("ENOENT"));
|
|
|
|
const result = await promise;
|
|
assert.equal(result.ok, false);
|
|
assert.match(result.output, /failed to start qwen/);
|
|
});
|
|
|
|
test("times out and kills the child if it never exits", async () => {
|
|
const { spawnFn, child } = fakeSpawn();
|
|
const promise = delegateToQwen("hi", { spawnFn, timeoutMs: 10 });
|
|
|
|
const result = await promise;
|
|
assert.equal(result.ok, false);
|
|
assert.match(result.output, /timed out/);
|
|
assert.equal(child.killed, true);
|
|
});
|
|
|
|
test("ignores stderr warnings when the run still succeeds", async () => {
|
|
const { spawnFn, child } = fakeSpawn();
|
|
const promise = delegateToQwen("hi", { spawnFn });
|
|
|
|
child.stderr.emit("data", "Warning: MCP server(s) failed to start: omniroute-search\n");
|
|
child.stdout.emit("data", "OK\n");
|
|
child.emit("close", 0);
|
|
|
|
assert.deepEqual(await promise, { ok: true, output: "OK" });
|
|
});
|