import test from "node:test"; import assert from "node:assert/strict"; import { normalizeRemoteExecutionResultForTesting } from "../src/lib/execution/remote-runtime-adapter.ts"; test("RemoteRuntimeAdapter 会把 local-agent 回写标准化成统一结果", () => { const normalized = normalizeRemoteExecutionResultForTesting({ status: "completed", dispatchExecutionId: "dx-1", targetProjectId: "project-1", targetThreadId: "thread-1", rawThreadReply: " 链路正常 ", replyBody: " 主 Agent 汇总:链路正常 ", }); assert.equal(normalized.status, "completed"); assert.equal(normalized.dispatchExecutionId, "dx-1"); assert.equal(normalized.targetProjectId, "project-1"); assert.equal(normalized.targetThreadId, "thread-1"); assert.equal(normalized.targetUrl, undefined); assert.equal(normalized.targetApp, undefined); assert.equal(normalized.rawThreadReply, "链路正常"); assert.equal(normalized.replyBody, "主 Agent 汇总:链路正常"); }); test("RemoteRuntimeAdapter 会保留 browser/desktop 控制结果的目标信息", () => { const browser = normalizeRemoteExecutionResultForTesting({ status: "completed", replyBody: "浏览器控制已完成", targetUrl: " https://example.com/dashboard ", }); const desktop = normalizeRemoteExecutionResultForTesting({ status: "completed", replyBody: "桌面控制已完成", targetApp: " 微信 ", }); assert.equal(browser.targetUrl, "https://example.com/dashboard"); assert.equal(browser.targetApp, undefined); assert.equal(desktop.targetUrl, undefined); assert.equal(desktop.targetApp, "微信"); }); test("RemoteRuntimeAdapter 会忽略空白字段并保留失败状态", () => { const normalized = normalizeRemoteExecutionResultForTesting({ status: "failed", dispatchExecutionId: " ", targetProjectId: " project-2 ", targetThreadId: "", rawThreadReply: " ", errorMessage: " MODEL_CALL_FAILED ", }); assert.equal(normalized.status, "failed"); assert.equal(normalized.dispatchExecutionId, undefined); assert.equal(normalized.targetProjectId, "project-2"); assert.equal(normalized.targetThreadId, undefined); assert.equal(normalized.rawThreadReply, undefined); assert.equal(normalized.errorMessage, "MODEL_CALL_FAILED"); }); test("RemoteRuntimeAdapter 会把线程环境脏回复改写成失败", () => { const normalized = normalizeRemoteExecutionResultForTesting({ status: "completed", replyBody: "我不能直接把当前会话环境从只读改回可写。cwd 我可以在命令里指向 /Users/kris/code/gptpluscontrol,但真正卡住的是只读权限。", }); assert.equal(normalized.status, "failed"); assert.equal(normalized.replyBody, undefined); assert.equal(normalized.rawThreadReply, undefined); assert.match(normalized.errorMessage ?? "", /THREAD_ENVIRONMENT_INVALID/); }); test("RemoteRuntimeAdapter 会把 Codex CLI 启动日志泄漏改写成内部执行失败", () => { const normalized = normalizeRemoteExecutionResultForTesting({ status: "failed", errorMessage: [ "OpenAI Codex v0.114.0 (research preview)", "--------", "workdir: /Users/kris/code/boss", "model: gpt-5.4", "provider: openai", "approval: never", "sandbox: workspace-write [workdir, /tmp, $TMPDIR, /Users/kris/.codex/memories]", "session id: 019da4e5-9b1d-7dc1-8aa5-a74a74b6b021", "--------", "user", "同步完成记得要和我说,以后也是这样。", "mcp: chrome-devtools starting", ].join("\n"), }); assert.equal(normalized.status, "failed"); assert.equal(normalized.replyBody, undefined); assert.equal(normalized.rawThreadReply, undefined); assert.equal(normalized.errorMessage, "MASTER_CODEX_NODE_OUTPUT_LEAKED"); }); test("RemoteRuntimeAdapter 会把执行提示词片段泄漏改写成内部执行失败", () => { const normalized = normalizeRemoteExecutionResultForTesting({ status: "failed", errorMessage: [ "管理员全局主提示词:", "你是 Boss 控制台的主 Agent。", "回复风格:像专业职业经理人,先给结论,再给推进动作。", "", "用户私有主提示词:", "默认中文回复。", "", "当前对话附加提示词:", "同步项目目标和版本记录后记得告诉我。", "", "当前消息:", "同步完成记得要和我说,以后也是这样。", ].join("\n"), }); assert.equal(normalized.status, "failed"); assert.equal(normalized.replyBody, undefined); assert.equal(normalized.rawThreadReply, undefined); assert.equal(normalized.errorMessage, "MASTER_CODEX_NODE_OUTPUT_LEAKED"); }); test("RemoteRuntimeAdapter 不会误杀包含路径和 sandbox 描述的有效线程回复", () => { const normalized = normalizeRemoteExecutionResultForTesting({ status: "completed", replyBody: "已经把配置写到 /Users/kris/code/gptpluscontrol/.env.local,接下来如果线上仍受 sandbox 限制,我们再切到服务器验证。", }); assert.equal(normalized.status, "completed"); assert.match(normalized.replyBody ?? "", /gptpluscontrol/); });