Files
boss/docs/superpowers/plans/2026-04-21-codex-desktop-thread-sync.md

275 lines
8.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`
- [x] **Step 1: 写失败测试,要求普通单线程消息返回的任务带 sourceMessage 元数据**
```ts
assert.equal(task?.sourceMessageId, message.id);
assert.equal(task?.sourceMessageBody, "请同步一下当前阻塞情况");
assert.equal(task?.sourceMessageSentAt, message.sentAt);
assert.equal(task?.mirrorBossUserMessageToCodexDesktop, true);
```
- [x] **Step 2: 运行测试确认失败**
Run: `npx tsx --test tests/single-thread-message-execution.test.ts`
Expected: FAIL提示 `sourceMessageBody/sourceMessageSentAt/mirrorBossUserMessageToCodexDesktop` 不存在或断言失败。
- [x] **Step 3: 写最小实现**
`MasterAgentTask` 和状态序列化/反序列化里补字段:
```ts
sourceMessageId?: string;
sourceMessageBody?: string;
sourceMessageSentAt?: string;
mirrorBossUserMessageToCodexDesktop?: boolean;
```
`queueThreadConversationReplyTask` 中透传:
```ts
sourceMessageId: params.sourceMessageId,
sourceMessageBody: params.sourceMessageBody,
sourceMessageSentAt: params.sourceMessageSentAt,
mirrorBossUserMessageToCodexDesktop:
params.relayViaMasterAgent ? undefined : true,
```
在消息 route 调用时补:
```ts
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,
});
```
- [x] **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`
- [x] **Step 1: 写失败测试,约束 writer 会写入 user_message 且按 sourceMessageId 去重**
```js
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");
});
```
- [x] **Step 2: 运行测试确认失败**
Run: `node --test tests/local-agent-codex-rollout-writer.test.mjs`
Expected: FAIL提示模块不存在或导出函数不存在。
- [x] **Step 3: 写最小实现**
实现 writer 逻辑:
```js
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 };
}
```
- [x] **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`
- [x] **Step 1: 写失败测试,要求 prepare 阶段保留镜像计划,且 relay task 不启用**
```js
assert.deepEqual(result.execution.desktopMirror, {
enabled: true,
sourceMessageId: "msg-1",
sourceMessageBody: "请继续推进",
});
```
- [x] **Step 2: 运行测试确认失败**
Run: `node --test tests/local-agent-codex-task-runner.test.mjs`
Expected: FAIL提示 `desktopMirror` 不存在。
- [x] **Step 3: 写最小实现**
`buildCodexTaskExecution` 返回值中增加:
```js
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` 全部存在
- [x] **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`
- [x] **Step 1: 写失败测试,要求 server 在 spawn codex 前先执行 rollout 镜像**
```js
assert.equal(writerCalls.length, 1);
assert.equal(writerCalls[0].sourceMessageId, "msg-1");
assert.equal(writerCalls[0].message, "请继续推进");
```
- [x] **Step 2: 运行测试确认失败**
Run: `node --test tests/local-agent-codex-task-runner.test.mjs`
Expected: FAIL提示 writer 未被调用。
- [x] **Step 3: 写最小实现**
`runMasterAgentTask``spawn("codex", ...)` 前增加:
```js
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
- [x] **Step 4: 运行定向测试确认通过**
Run:
```bash
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`
- [x] **Step 1: 跑仓库要求的基线验证**
Run:
```bash
npm run lint
npm run build
```
Expected: PASS
- [x] **Step 2: 补文档**
在运行时/服务清单文档补一句:
- Boss 普通线程单聊现在会在 local-agent 执行 `codex exec resume` 前,把 Boss 用户消息镜像进目标 Codex Desktop 线程 rollout
- 该能力仅针对已绑定 `codexThreadRef` 的单线程会话
- [x] **Step 3: 完成最终自检**
检查:
- 没有把主 Agent 会话或 takeover relay 错写进 Desktop 子线程
- 没有重复写 rollout
- 现有 heartbeat 读取 recent desktop replies 仍可工作