fix(canvas): 本地 Codex 会话 - 修复新会话首次发送失败 (#24)
This commit is contained in:
86
canvas-agent/test/codex-thread.test.ts
Normal file
86
canvas-agent/test/codex-thread.test.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { resolveCodexThread, type CodexThreadClient } from "../src/codex-thread.js";
|
||||
|
||||
const cwd = "C:\\canvas-workspace";
|
||||
|
||||
test("当前进程刚创建的会话直接开始首轮", async () => {
|
||||
const client = fakeClient();
|
||||
|
||||
const threadId = await resolveCodexThread(client.value, { threadId: "thread-new", cwd }, "thread-new");
|
||||
|
||||
assert.equal(threadId, "thread-new");
|
||||
assert.deepEqual(client.calls, []);
|
||||
});
|
||||
|
||||
test("已有会话仍校验工作空间并恢复", async () => {
|
||||
const client = fakeClient({
|
||||
readThread: async () => ({ thread: { id: "thread-old", cwd } }),
|
||||
resumeThread: async () => ({ id: "thread-old", cwd }),
|
||||
});
|
||||
|
||||
const threadId = await resolveCodexThread(client.value, { threadId: "thread-old", cwd });
|
||||
|
||||
assert.equal(threadId, "thread-old");
|
||||
assert.deepEqual(client.calls, ["read:thread-old", "resume:thread-old"]);
|
||||
});
|
||||
|
||||
test("未落盘的空会话自动替换", async () => {
|
||||
const client = fakeClient({
|
||||
readThread: async () => ({ thread: { id: "thread-stale", cwd } }),
|
||||
resumeThread: async () => {
|
||||
throw new Error("no rollout found for thread id thread-stale");
|
||||
},
|
||||
startThread: async () => ({ id: "thread-replacement", cwd }),
|
||||
});
|
||||
|
||||
const threadId = await resolveCodexThread(client.value, { threadId: "thread-stale", cwd });
|
||||
|
||||
assert.equal(threadId, "thread-replacement");
|
||||
assert.deepEqual(client.calls, ["read:thread-stale", "resume:thread-stale", "start"]);
|
||||
});
|
||||
|
||||
test("读取阶段发现未 materialize 的空会话也会替换", async () => {
|
||||
const client = fakeClient({
|
||||
readThread: async () => {
|
||||
throw new Error("thread thread-stale is not materialized yet");
|
||||
},
|
||||
startThread: async () => ({ id: "thread-replacement", cwd }),
|
||||
});
|
||||
|
||||
const threadId = await resolveCodexThread(client.value, { threadId: "thread-stale", cwd });
|
||||
|
||||
assert.equal(threadId, "thread-replacement");
|
||||
assert.deepEqual(client.calls, ["read:thread-stale", "start"]);
|
||||
});
|
||||
|
||||
test("非空会话错误不会被静默替换", async () => {
|
||||
const client = fakeClient({
|
||||
readThread: async () => {
|
||||
throw new Error("permission denied");
|
||||
},
|
||||
});
|
||||
|
||||
await assert.rejects(() => resolveCodexThread(client.value, { threadId: "thread-old", cwd }), /permission denied/);
|
||||
assert.deepEqual(client.calls, ["read:thread-old"]);
|
||||
});
|
||||
|
||||
function fakeClient(overrides: Partial<CodexThreadClient> = {}) {
|
||||
const calls: string[] = [];
|
||||
const value: CodexThreadClient = {
|
||||
readThread: async (threadId) => {
|
||||
calls.push(`read:${threadId}`);
|
||||
return await (overrides.readThread?.(threadId, false) || Promise.resolve({ thread: { id: threadId, cwd } }));
|
||||
},
|
||||
resumeThread: async (threadId, workspace) => {
|
||||
calls.push(`resume:${threadId}`);
|
||||
return await (overrides.resumeThread?.(threadId, workspace) || Promise.resolve({ id: threadId, cwd: workspace }));
|
||||
},
|
||||
startThread: async (workspace) => {
|
||||
calls.push("start");
|
||||
return await (overrides.startThread?.(workspace) || Promise.resolve({ id: "thread-started", cwd: workspace }));
|
||||
},
|
||||
};
|
||||
return { calls, value };
|
||||
}
|
||||
Reference in New Issue
Block a user