Initial commit: qwen delegation MCP server, tracker setup, research
- 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
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
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" });
|
||||
});
|
||||
Reference in New Issue
Block a user