8.8 KiB
Codex Desktop Thread Sync Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 让 Boss App 发往单线程 Codex 会话的用户消息,在继续现有 conversation_reply -> codex exec resume 主链前,同步镜像进本机 Codex Desktop 的同一个线程历史。
Architecture: 服务端继续以 Boss 项目账本为主真相,但给普通单线程 conversation_reply 任务补齐 sourceMessage* 元数据和显式镜像开关。local-agent 在执行 codex exec resume 前,按 targetCodexThreadRef 解析目标 rollout 文件,先做一次本地 user_message append + 去重,再继续原有执行链。rollout 定位优先使用 state_5.sqlite,若本机 Codex CLI/Desktop 版本导致状态库不可用,则回退扫描 ~/.codex/sessions;rollout 写入后尽量刷新 threads.updated_at / updated_at_ms / has_user_event,但不依赖 GUI 自动化。
Tech Stack: Next.js App Router, TypeScript, Node.js, sqlite, local-agent Node runtime, tsx --test, Node test runner
Task 1: 给 conversation task 补齐 Desktop 镜像元数据
Files:
-
Modify:
src/lib/boss-data.ts -
Modify:
src/lib/boss-master-agent.ts -
Modify:
src/app/api/v1/projects/[projectId]/messages/route.ts -
Test:
tests/single-thread-message-execution.test.ts -
Step 1: 写失败测试,要求普通单线程消息返回的任务带 sourceMessage 元数据
assert.equal(task?.sourceMessageId, message.id);
assert.equal(task?.sourceMessageBody, "请同步一下当前阻塞情况");
assert.equal(task?.sourceMessageSentAt, message.sentAt);
assert.equal(task?.mirrorBossUserMessageToCodexDesktop, true);
- Step 2: 运行测试确认失败
Run: npx tsx --test tests/single-thread-message-execution.test.ts
Expected: FAIL,提示 sourceMessageBody/sourceMessageSentAt/mirrorBossUserMessageToCodexDesktop 不存在或断言失败。
- Step 3: 写最小实现
在 MasterAgentTask 和状态序列化/反序列化里补字段:
sourceMessageId?: string;
sourceMessageBody?: string;
sourceMessageSentAt?: string;
mirrorBossUserMessageToCodexDesktop?: boolean;
在 queueThreadConversationReplyTask 中透传:
sourceMessageId: params.sourceMessageId,
sourceMessageBody: params.sourceMessageBody,
sourceMessageSentAt: params.sourceMessageSentAt,
mirrorBossUserMessageToCodexDesktop:
params.relayViaMasterAgent ? undefined : true,
在消息 route 调用时补:
const queuedTask = await queueThreadConversationReplyTask({
projectId,
requestMessageId: message.id,
requestText: message.body,
requestedBy: session.displayName || session.account,
requestedByAccount: session.account,
sourceMessageId: message.id,
sourceMessageBody: message.body,
sourceMessageSentAt: message.sentAt,
});
- Step 4: 运行测试确认通过
Run: npx tsx --test tests/single-thread-message-execution.test.ts
Expected: PASS
Task 2: 新增 rollout writer,并确保重复任务不重复写 Desktop 线程
Files:
-
Create:
local-agent/codex-thread-rollout-writer.mjs -
Test:
tests/local-agent-codex-rollout-writer.test.mjs -
Step 1: 写失败测试,约束 writer 会写入 user_message 且按 sourceMessageId 去重
test("appendBossUserMessageToCodexThreadRollout writes one user_message event and dedupes by source message id", async () => {
const first = await appendBossUserMessageToCodexThreadRollout({ ... });
const second = await appendBossUserMessageToCodexThreadRollout({ ... });
assert.equal(first.status, "written");
assert.equal(second.status, "duplicate");
});
- Step 2: 运行测试确认失败
Run: node --test tests/local-agent-codex-rollout-writer.test.mjs
Expected: FAIL,提示模块不存在或导出函数不存在。
- Step 3: 写最小实现
实现 writer 逻辑:
export async function appendBossUserMessageToCodexThreadRollout(params) {
const rolloutPath = await resolveThreadRolloutPath(params);
const duplicate = await hasBossSourceMessageInRolloutTail(rolloutPath, params.sourceMessageId);
if (duplicate) return { status: "duplicate", rolloutPath };
const responseItem = JSON.stringify({
timestamp: params.sentAt,
type: "response_item",
payload: {
type: "message",
role: "user",
content: [{ type: "input_text", text: params.message }],
},
});
const event = JSON.stringify({
timestamp: params.sentAt,
type: "event_msg",
payload: {
type: "user_message",
message: params.message,
images: [],
local_images: [],
text_elements: [],
metadata: {
bossSourceMessageId: params.sourceMessageId,
bossMirroredFrom: "boss-app",
},
},
});
await appendFile(rolloutPath, `${responseItem}\n${event}\n`, "utf8");
return { status: "written", rolloutPath };
}
- Step 4: 运行测试确认通过
Run: node --test tests/local-agent-codex-rollout-writer.test.mjs
Expected: PASS
Task 3: 在 local-agent 执行 resume 前追加 Desktop 线程镜像
Files:
-
Modify:
local-agent/codex-task-runner.mjs -
Modify:
tests/local-agent-codex-task-runner.test.mjs -
Step 1: 写失败测试,要求 prepare 阶段保留镜像计划,且 relay task 不启用
assert.deepEqual(result.execution.desktopMirror, {
enabled: true,
sourceMessageId: "msg-1",
sourceMessageBody: "请继续推进",
});
- Step 2: 运行测试确认失败
Run: node --test tests/local-agent-codex-task-runner.test.mjs
Expected: FAIL,提示 desktopMirror 不存在。
- Step 3: 写最小实现
在 buildCodexTaskExecution 返回值中增加:
desktopMirror: shouldMirrorBossUserMessageToDesktop(task)
? {
enabled: true,
sourceMessageId: task.sourceMessageId,
sourceMessageBody: task.sourceMessageBody,
sourceMessageSentAt: task.sourceMessageSentAt,
targetThreadRef,
}
: { enabled: false }
其中 shouldMirrorBossUserMessageToDesktop(task) 需要保证:
-
task.taskType === "conversation_reply" -
task.mirrorBossUserMessageToCodexDesktop === true -
task.relayViaMasterAgent !== true -
targetThreadRef/sourceMessageId/sourceMessageBody全部存在 -
Step 4: 运行测试确认通过
Run: node --test tests/local-agent-codex-task-runner.test.mjs
Expected: PASS
Task 4: 在实际任务执行前调用 rollout writer,并保持现有 resume/complete 主链不回归
Files:
-
Modify:
local-agent/server.mjs -
Modify:
tests/local-agent-codex-task-runner.test.mjs -
Modify:
tests/single-thread-message-execution.test.ts -
Step 1: 写失败测试,要求 server 在 spawn codex 前先执行 rollout 镜像
assert.equal(writerCalls.length, 1);
assert.equal(writerCalls[0].sourceMessageId, "msg-1");
assert.equal(writerCalls[0].message, "请继续推进");
- Step 2: 运行测试确认失败
Run: node --test tests/local-agent-codex-task-runner.test.mjs
Expected: FAIL,提示 writer 未被调用。
- Step 3: 写最小实现
在 runMasterAgentTask 的 spawn("codex", ...) 前增加:
if (codexExecution.desktopMirror?.enabled) {
await appendBossUserMessageToCodexThreadRollout({
stateDbPath: config.codexStateDbPath,
targetThreadRef: codexExecution.desktopMirror.targetThreadRef,
sourceMessageId: codexExecution.desktopMirror.sourceMessageId,
message: codexExecution.desktopMirror.sourceMessageBody,
sentAt: codexExecution.desktopMirror.sourceMessageSentAt ?? new Date().toISOString(),
});
}
如果镜像失败:
-
不吞掉错误
-
直接按任务失败返回,让链路保持 fail-closed
-
Step 4: 运行定向测试确认通过
Run:
node --test tests/local-agent-codex-rollout-writer.test.mjs
node --test tests/local-agent-codex-task-runner.test.mjs
npx tsx --test tests/single-thread-message-execution.test.ts
Expected: PASS
Task 5: 回归验证与文档同步
Files:
-
Modify:
README.md(如需补一句运行时说明) -
Modify:
docs/architecture/current_runtime_and_deploy_status_cn.md -
Modify:
docs/architecture/api_and_service_inventory_cn.md -
Step 1: 跑仓库要求的基线验证
Run:
npm run lint
npm run build
Expected: PASS
- Step 2: 补文档
在运行时/服务清单文档补一句:
-
Boss 普通线程单聊现在会在 local-agent 执行
codex exec resume前,把 Boss 用户消息镜像进目标 Codex Desktop 线程 rollout -
该能力仅针对已绑定
codexThreadRef的单线程会话 -
Step 3: 完成最终自检
检查:
- 没有把主 Agent 会话或 takeover relay 错写进 Desktop 子线程
- 没有重复写 rollout
- 现有 heartbeat 读取 recent desktop replies 仍可工作