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 { runHermesCommandForTesting } from "../src/lib/execution/backends/hermes-runner.ts"; async function createTempScript(source: string) { const dir = await mkdtemp(join(tmpdir(), "hermes-runner-")); const scriptPath = join(dir, "hermes-script.mjs"); await writeFile(scriptPath, source, "utf8"); return { dir, scriptPath }; } test("Hermes runner 会按固定 chat -q -Q 形态执行并提取正文", async () => { const workspace = await mkdtemp(join(tmpdir(), "hermes-runner-cwd-")); const expectedWorkspace = await realpath(workspace); const { scriptPath } = await createTempScript(` process.stdout.write(JSON.stringify({ argv: process.argv.slice(2), cwd: process.cwd(), envSource: process.env.HERMES_SESSION_SOURCE || "" }) + "\\n"); process.stdout.write("Hermes smoke completed\\n\\n"); process.stdout.write("session_id: hermes-session-123\\n"); `); const result = await runHermesCommandForTesting({ config: { enabled: true, command: process.execPath, args: [scriptPath], cwd: workspace, timeoutMs: 1000, defaultModel: "gpt-5.4", toolsets: ["web", "terminal"], skills: ["boss-dev"], sourceTag: "tool", }, payload: { executionPrompt: "请输出链路正常", model: "gpt-5.5", }, }); assert.equal(result.status, "completed"); if (result.status !== "completed") { assert.fail("expected completed"); } const lines = result.output.split("\n"); const metadata = JSON.parse(lines[0] ?? "{}") as { argv: string[]; cwd: string; envSource: string; }; assert.deepEqual(metadata.argv, [ "chat", "-q", "请输出链路正常", "-Q", "--source", "tool", "-m", "gpt-5.5", "-t", "web,terminal", "-s", "boss-dev", ]); assert.equal(metadata.cwd, expectedWorkspace); assert.equal(metadata.envSource, ""); assert.equal(lines.at(-1), "Hermes smoke completed"); assert.equal(result.sessionId, "hermes-session-123"); }); test("Hermes runner 会把非零退出码映射成 stderr 或退出码错误", async () => { const { scriptPath } = await createTempScript(` process.stderr.write("hermes crashed"); process.exit(2); `); const result = await runHermesCommandForTesting({ config: { enabled: true, command: process.execPath, args: [scriptPath], timeoutMs: 1000, sourceTag: "tool", }, payload: { executionPrompt: "anything", }, }); assert.equal(result.status, "failed"); if (result.status !== "failed") { assert.fail("expected failed"); } assert.match(result.error, /hermes crashed/); }); test("Hermes runner 在输出只有 session_id 时会视为失败", async () => { const { scriptPath } = await createTempScript(` process.stdout.write("session_id: hermes-session-123\\n"); `); const result = await runHermesCommandForTesting({ config: { enabled: true, command: process.execPath, args: [scriptPath], timeoutMs: 1000, sourceTag: "tool", }, payload: { executionPrompt: "anything", }, }); assert.equal(result.status, "failed"); if (result.status !== "failed") { assert.fail("expected failed"); } assert.match(result.error, /EMPTY_HERMES_RESPONSE/); }); test("Hermes runner 超时后返回 HERMES_TIMEOUT", async () => { const { scriptPath } = await createTempScript(` setTimeout(() => { process.stdout.write("late response\\n"); }, 500); `); const result = await runHermesCommandForTesting({ config: { enabled: true, command: process.execPath, args: [scriptPath], timeoutMs: 50, sourceTag: "tool", }, payload: { executionPrompt: "slow", }, }); assert.equal(result.status, "failed"); if (result.status !== "failed") { assert.fail("expected failed"); } assert.match(result.error, /HERMES_TIMEOUT/); });