feat: add claw backend adapter

This commit is contained in:
kris
2026-04-03 01:36:29 +08:00
parent 8daaea01fd
commit 39b576cc42
23 changed files with 1212 additions and 23 deletions

180
tests/claw-runner.test.ts Normal file
View File

@@ -0,0 +1,180 @@
import assert from "node:assert/strict";
import { mkdtemp, realpath, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { runClawCommandForTesting } from "../src/lib/execution/backends/claw-runner.ts";
async function createTempScript(source: string) {
const dir = await mkdtemp(join(tmpdir(), "claw-runner-"));
const scriptPath = join(dir, "claw-script.mjs");
await writeFile(scriptPath, source, "utf8");
return { dir, scriptPath };
}
test("Claw runner 把 completed JSON 响应映射成 completed并把 payload 写入 stdin", async () => {
const workspace = await mkdtemp(join(tmpdir(), "claw-runner-cwd-"));
const expectedWorkspace = await realpath(workspace);
const { scriptPath } = await createTempScript(`
import { readFile } from "node:fs/promises";
let stdin = "";
process.stdin.setEncoding("utf8");
for await (const chunk of process.stdin) {
stdin += chunk;
}
const payload = JSON.parse(stdin);
process.stdout.write(JSON.stringify({
status: "completed",
output: JSON.stringify({
body: payload.body,
cwd: process.cwd(),
command: payload.command,
}),
}));
`);
const result = await runClawCommandForTesting({
config: {
enabled: true,
command: process.execPath,
args: [scriptPath],
cwd: workspace,
timeoutMs: 1000,
},
payload: {
body: "继续执行",
command: "claw",
},
});
assert.equal(result.status, "completed");
if (result.status !== "completed") {
assert.fail("expected completed");
}
const output = JSON.parse(result.output) as {
body: string;
cwd: string;
command: string;
};
assert.equal(output.body, "继续执行");
assert.equal(output.cwd, expectedWorkspace);
assert.equal(output.command, "claw");
});
test("Claw runner 把 failed JSON 响应映射成 failed", async () => {
const { scriptPath } = await createTempScript(`
let stdin = "";
process.stdin.setEncoding("utf8");
for await (const chunk of process.stdin) {
stdin += chunk;
}
const payload = JSON.parse(stdin);
process.stdout.write(JSON.stringify({
status: "failed",
error: "bad-request:" + payload.body,
}));
`);
const result = await runClawCommandForTesting({
config: {
enabled: true,
command: process.execPath,
args: [scriptPath],
timeoutMs: 1000,
},
payload: {
body: "格式不对",
},
});
assert.equal(result.status, "failed");
if (result.status !== "failed") {
assert.fail("expected failed");
}
assert.equal(result.error, "bad-request:格式不对");
});
test("Claw runner 把无效 JSON 响应映射成 INVALID_CLAW_RESPONSE", async () => {
const { scriptPath } = await createTempScript(`
process.stdout.write("not-json");
`);
const result = await runClawCommandForTesting({
config: {
enabled: true,
command: process.execPath,
args: [scriptPath],
timeoutMs: 1000,
},
payload: {
body: "anything",
},
});
assert.equal(result.status, "failed");
if (result.status !== "failed") {
assert.fail("expected failed");
}
assert.match(result.error, /INVALID_CLAW_RESPONSE/);
});
test("Claw runner 把非零退出码映射成 stderr 或退出码错误", async () => {
const { scriptPath } = await createTempScript(`
process.stderr.write("claw crashed");
process.exit(2);
`);
const result = await runClawCommandForTesting({
config: {
enabled: true,
command: process.execPath,
args: [scriptPath],
timeoutMs: 1000,
},
payload: {
body: "anything",
},
});
assert.equal(result.status, "failed");
if (result.status !== "failed") {
assert.fail("expected failed");
}
assert.match(result.error, /claw crashed/);
});
test("Claw runner 超时后返回 CLAW_TIMEOUT", async () => {
const { scriptPath } = await createTempScript(`
setTimeout(() => {
process.stdout.write(JSON.stringify({ status: "completed", output: "late" }));
}, 500);
`);
const result = await runClawCommandForTesting({
config: {
enabled: true,
command: process.execPath,
args: [scriptPath],
timeoutMs: 50,
},
payload: {
body: "slow",
},
});
assert.equal(result.status, "failed");
if (result.status !== "failed") {
assert.fail("expected failed");
}
assert.match(result.error, /CLAW_TIMEOUT/);
});