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

8.8 KiB
Raw Permalink Blame History

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/sessionsrollout 写入后尽量刷新 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: 写最小实现

runMasterAgentTaskspawn("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 仍可工作