feat: mirror boss messages via app server

This commit is contained in:
AI Bot
2026-06-03 15:00:25 +08:00
parent 0c3437a36f
commit dbdaab8d0f
7 changed files with 121 additions and 1 deletions

View File

@@ -171,6 +171,7 @@
- 当前已补 Codex App Server 受控线程改名:`POST /api/v1/projects/[projectId]/rename``mode=thread` 且绑定真实 `codexThreadRef` 时,会在本地 Boss 改名后创建 `intentCategory=thread_rename` 任务,`local-agent` 直接调用 `thread/name/set`;该链路不启动普通 turn不把 thread 原始字段写回 APP只提示“已同步 Codex 线程名称”。设备离线、并发冲突或 App Server 不可用不会回滚 Boss 本地改名。 - 当前已补 Codex App Server 受控线程改名:`POST /api/v1/projects/[projectId]/rename``mode=thread` 且绑定真实 `codexThreadRef` 时,会在本地 Boss 改名后创建 `intentCategory=thread_rename` 任务,`local-agent` 直接调用 `thread/name/set`;该链路不启动普通 turn不把 thread 原始字段写回 APP只提示“已同步 Codex 线程名称”。设备离线、并发冲突或 App Server 不可用不会回滚 Boss 本地改名。
- 当前已补 Codex App Server 受控线程 Git 元数据同步:`POST /api/v1/projects/[projectId]/thread-metadata` 会创建 `intentCategory=thread_metadata_sync` 任务,`local-agent` 直接调用 `thread/metadata/update`;当前只允许同步 `gitInfo.sha / branch / originUrl`,不会启动普通 turn也不允许写入任意 metadata。 - 当前已补 Codex App Server 受控线程 Git 元数据同步:`POST /api/v1/projects/[projectId]/thread-metadata` 会创建 `intentCategory=thread_metadata_sync` 任务,`local-agent` 直接调用 `thread/metadata/update`;当前只允许同步 `gitInfo.sha / branch / originUrl`,不会启动普通 turn也不允许写入任意 metadata。
- 当前已补 Codex App Server 受控线程分叉:`POST /api/v1/projects/[projectId]/thread-fork` 会创建 `intentCategory=thread_fork` 任务,`local-agent` 直接调用 `thread/fork`;当前不允许远程覆盖 model、sandbox、instructions 或 config也不会把 path、cwd、turns、instructionSources 写回 APP。新线程进入 Boss 会话列表仍依赖 thread discovery / 导入链路。 - 当前已补 Codex App Server 受控线程分叉:`POST /api/v1/projects/[projectId]/thread-fork` 会创建 `intentCategory=thread_fork` 任务,`local-agent` 直接调用 `thread/fork`;当前不允许远程覆盖 model、sandbox、instructions 或 config也不会把 path、cwd、turns、instructionSources 写回 APP。新线程进入 Boss 会话列表仍依赖 thread discovery / 导入链路。
- 当前已补 Codex App Server 版 Boss 用户消息镜像:普通单线程 `conversation_reply` 任务携带 `mirrorBossUserMessageToCodexDesktop=true` 时,`local-agent/codex-app-server-runner.mjs` 会在 `thread/resume` 后、`turn/start` 前调用 `thread/inject_items`,把 Boss APP 用户原文作为 `role=user` 的 Responses item 写入目标 Codex 线程模型可见历史;任务结果只回传 `threadHistorySync.threadId / injectedItemCount / source`,不回传消息 ID、内部 prompt 或用户原文。CLI rollout 镜像仍保留为 App Server 不可用前的 fallback 链路。
- 当前 boss-agent 已支持 Mac OTA`local-agent/boss-agent-ota-runner.mjs` 默认开启,每 5 分钟检查服务端最新包;状态页可手动检查或下载并安装,安装时保留原绑定配置,只更新版本号和本机 runtime 路径。最新验证版本为 `20260516221619`,已在 MacBook Air `macbook-air` 上确认 OTA 下载校验、暂存、覆盖安装后不会误切到默认 `config.cloud.json`。正式分发脚本已预留 Developer ID 公证路径:`BOSS_AGENT_NOTARIZE=1` 配合 notary profile 或 Apple ID 凭据。 - 当前 boss-agent 已支持 Mac OTA`local-agent/boss-agent-ota-runner.mjs` 默认开启,每 5 分钟检查服务端最新包;状态页可手动检查或下载并安装,安装时保留原绑定配置,只更新版本号和本机 runtime 路径。最新验证版本为 `20260516221619`,已在 MacBook Air `macbook-air` 上确认 OTA 下载校验、暂存、覆盖安装后不会误切到默认 `config.cloud.json`。正式分发脚本已预留 Developer ID 公证路径:`BOSS_AGENT_NOTARIZE=1` 配合 notary profile 或 Apple ID 凭据。
- 当前量产治理已补设备撤权和任务可靠性底座:`revoke_device` 会清空设备 token、标记离线并阻断 heartbeat / 任务认领 / Skill 同步 / 日志上报 / boss-agent OTA`MasterAgentTask` claim 会记录 attempt 和 lease运行中任务可按租约重试超过上限转 `timed_out`,用户或管理员可通过 cancel 接口转 `canceled` 且迟到 complete 不覆盖终态。 - 当前量产治理已补设备撤权和任务可靠性底座:`revoke_device` 会清空设备 token、标记离线并阻断 heartbeat / 任务认领 / Skill 同步 / 日志上报 / boss-agent OTA`MasterAgentTask` claim 会记录 attempt 和 lease运行中任务可按租约重试超过上限转 `timed_out`,用户或管理员可通过 cancel 接口转 `canceled` 且迟到 complete 不覆盖终态。
- 当前群聊 `dispatch_execution` 完成回写已补幂等,重复完成不会再向群聊重复追加结果 - 当前群聊 `dispatch_execution` 完成回写已补幂等,重复完成不会再向群聊重复追加结果

View File

@@ -134,6 +134,7 @@
- App Server heartbeat discovery 现在支持 `skills/extraRoots/set`:配置 `codexAppServerSkillExtraRoots` 或环境变量 `BOSS_CODEX_APP_SERVER_SKILL_EXTRA_ROOTS`runner 会先把共享 Skill 根下发给 App Server再刷新 `skills/list`,并写入 `capabilities.codexAppServer.metadata.skillExtraRootsSummary`。该字段用于 APP/后台展示企业共享 Skill 根是否已下发只保留数量、basename 和状态不保存根目录绝对路径、Skill 文件路径或配置原文。 - App Server heartbeat discovery 现在支持 `skills/extraRoots/set`:配置 `codexAppServerSkillExtraRoots` 或环境变量 `BOSS_CODEX_APP_SERVER_SKILL_EXTRA_ROOTS`runner 会先把共享 Skill 根下发给 App Server再刷新 `skills/list`,并写入 `capabilities.codexAppServer.metadata.skillExtraRootsSummary`。该字段用于 APP/后台展示企业共享 Skill 根是否已下发只保留数量、basename 和状态不保存根目录绝对路径、Skill 文件路径或配置原文。
- App Server heartbeat discovery 现在支持 `hooks/list`,写入 `capabilities.codexAppServer.metadata.hookSummary`。该字段用于 APP/后台展示本机 Codex hook 治理状态;只保留 workspace 数、hook 数、启用数、受管 / 可信 / 修改 / 未信任计数、warning / error 计数和事件 / handler 类型,不保存 hook key、command、sourcePath、statusMessage、hash、error message 或本地路径。 - App Server heartbeat discovery 现在支持 `hooks/list`,写入 `capabilities.codexAppServer.metadata.hookSummary`。该字段用于 APP/后台展示本机 Codex hook 治理状态;只保留 workspace 数、hook 数、启用数、受管 / 可信 / 修改 / 未信任计数、warning / error 计数和事件 / handler 类型,不保存 hook key、command、sourcePath、statusMessage、hash、error message 或本地路径。
- 当前 Codex App Server runner 已新增第一版 Boss Inter-Thread Broker任务携带 `intentCategory=thread_collaboration``sourceCodexThreadRef``targetCodexThreadRef` 时,会先 `thread/read` 源线程,再通过 `thread/inject_items` 向目标线程注入受控摘要,最后 `turn/start` 目标线程;服务端入口是 `POST /api/v1/projects/[projectId]/thread-collaboration`,负责权限、源/目标线程校验和任务排队。这不是假设官方线程 P2P而是 Boss 自己做线程协作编排。 - 当前 Codex App Server runner 已新增第一版 Boss Inter-Thread Broker任务携带 `intentCategory=thread_collaboration``sourceCodexThreadRef``targetCodexThreadRef` 时,会先 `thread/read` 源线程,再通过 `thread/inject_items` 向目标线程注入受控摘要,最后 `turn/start` 目标线程;服务端入口是 `POST /api/v1/projects/[projectId]/thread-collaboration`,负责权限、源/目标线程校验和任务排队。这不是假设官方线程 P2P而是 Boss 自己做线程协作编排。
- 当前 Codex App Server runner 已新增 Boss 用户消息镜像:普通 `conversation_reply` 任务携带 `mirrorBossUserMessageToCodexDesktop=true``sourceMessageBody` 和目标 `codexThreadRef` 时,会先 `thread/resume`,再 `thread/inject_items` 写入 `role=user` 的 Boss APP 用户原文,最后 `turn/start`;该链路用于让 APP 发起的对话进入 Codex Desktop 同一线程历史。执行结果只保存 `threadHistorySync` 安全摘要,不保存 App Server 原始 item、消息 ID、用户原文、系统提示词或内部调度字段。
- 当前 Codex App Server runner 已新增受控线程回滚:任务携带 `intentCategory=thread_rollback`、目标 `codexThreadRef``rollbackNumTurns` 时,会调用 `thread/rollback` 回滚目标线程最近 N 轮,不会启动新 turn也不会把 App Server 返回的 thread/turn/items 写回 APP。服务端入口是 `POST /api/v1/projects/[projectId]/thread-rollback`,只保存回滚轮数、原因和执行摘要;边界是只回滚 Codex 线程历史,不自动还原本地文件变更。 - 当前 Codex App Server runner 已新增受控线程回滚:任务携带 `intentCategory=thread_rollback`、目标 `codexThreadRef``rollbackNumTurns` 时,会调用 `thread/rollback` 回滚目标线程最近 N 轮,不会启动新 turn也不会把 App Server 返回的 thread/turn/items 写回 APP。服务端入口是 `POST /api/v1/projects/[projectId]/thread-rollback`,只保存回滚轮数、原因和执行摘要;边界是只回滚 Codex 线程历史,不自动还原本地文件变更。
- 当前 Codex App Server runner 已新增受控线程压缩:任务携带 `intentCategory=thread_compact` 和目标 `codexThreadRef` 时,会调用 `thread/compact/start` 发起上下文压缩,不会启动普通 turn也不会把 contextCompaction item 的原始字段写回 APP。服务端入口是 `POST /api/v1/projects/[projectId]/thread-compact`,只保存压缩原因和执行摘要;边界是只压缩 Codex 线程上下文,不代表代码修改、文件恢复或版本发布完成。 - 当前 Codex App Server runner 已新增受控线程压缩:任务携带 `intentCategory=thread_compact` 和目标 `codexThreadRef` 时,会调用 `thread/compact/start` 发起上下文压缩,不会启动普通 turn也不会把 contextCompaction item 的原始字段写回 APP。服务端入口是 `POST /api/v1/projects/[projectId]/thread-compact`,只保存压缩原因和执行摘要;边界是只压缩 Codex 线程上下文,不代表代码修改、文件恢复或版本发布完成。
- 当前 Codex App Server runner 已新增受控线程归档 / 恢复:任务携带 `intentCategory=thread_archive|thread_unarchive`、目标 `codexThreadRef``threadLifecycleAction` 时,会直接调用 `thread/archive``thread/unarchive`,不会先 resume 已归档线程,也不会启动普通 turn。服务端入口是 `POST /api/v1/projects/[projectId]/thread-archive`,只保存生命周期动作、原因和执行摘要;边界是只改变 Codex 线程生命周期状态,不代表代码修改、文件恢复或版本发布完成。 - 当前 Codex App Server runner 已新增受控线程归档 / 恢复:任务携带 `intentCategory=thread_archive|thread_unarchive`、目标 `codexThreadRef``threadLifecycleAction` 时,会直接调用 `thread/archive``thread/unarchive`,不会先 resume 已归档线程,也不会启动普通 turn。服务端入口是 `POST /api/v1/projects/[projectId]/thread-archive`,只保存生命周期动作、原因和执行摘要;边界是只改变 Codex 线程生命周期状态,不代表代码修改、文件恢复或版本发布完成。

View File

@@ -146,6 +146,7 @@ UI 参考:
- `local-agent/codex-app-server-runner.mjs` 已把 App Server `windowsSandbox/setupCompleted` 归一成 `executionProgress.windowsSandbox`;服务端进度路由和 Android 原生进度卡已支持展示,测试覆盖 setup error 中的 token、本地 Windows 路径和 sourcePath 不外泄 - `local-agent/codex-app-server-runner.mjs` 已把 App Server `windowsSandbox/setupCompleted` 归一成 `executionProgress.windowsSandbox`;服务端进度路由和 Android 原生进度卡已支持展示,测试覆盖 setup error 中的 token、本地 Windows 路径和 sourcePath 不外泄
- 新增实时进度入口 `POST /api/v1/master-agent/tasks/[taskId]/progress`,设备端可在任务执行中持续刷新同一张 `execution_progress` 卡;`local-agent` 的 App Server runner 已在收到协议进度事件时调用该接口complete 仍携带最终进度作为兜底 - 新增实时进度入口 `POST /api/v1/master-agent/tasks/[taskId]/progress`,设备端可在任务执行中持续刷新同一张 `execution_progress` 卡;`local-agent` 的 App Server runner 已在收到协议进度事件时调用该接口complete 仍携带最终进度作为兜底
- 新增服务端线程协作入口 `POST /api/v1/projects/[projectId]/thread-collaboration`,由 Boss 校验源/目标项目权限并创建 `intentCategory=thread_collaboration``conversation_reply` 任务;设备端继续通过 App Server runner 执行 `thread/read -> thread/inject_items -> turn/start`,避免把“线程互通”误做成无监管 P2P - 新增服务端线程协作入口 `POST /api/v1/projects/[projectId]/thread-collaboration`,由 Boss 校验源/目标项目权限并创建 `intentCategory=thread_collaboration``conversation_reply` 任务;设备端继续通过 App Server runner 执行 `thread/read -> thread/inject_items -> turn/start`,避免把“线程互通”误做成无监管 P2P
- 新增 Boss APP 用户消息镜像到 Codex App Server 线程历史:普通 `conversation_reply` 任务已复用 `mirrorBossUserMessageToCodexDesktop` 标记;设备端通过 App Server runner 执行 `thread/resume -> thread/inject_items(user message) -> turn/start`,让 APP 发起的用户输入进入 Codex Desktop 同一线程的模型可见历史。执行结果只返回 `threadHistorySync` 安全摘要,不回写消息 ID、用户原文、内部 prompt 或 App Server 原始 item。
- 新增活跃 turn 干预:任务携带 `targetCodexTurnId` / `targetTurnId`App Server runner 会调用 `turn/steer`,并把 `turnControl=steer``turnId` 写回执行结果;没有活跃 turn id 时仍使用 `turn/start` - 新增活跃 turn 干预:任务携带 `targetCodexTurnId` / `targetTurnId`App Server runner 会调用 `turn/steer`,并把 `turnControl=steer``turnId` 写回执行结果;没有活跃 turn id 时仍使用 `turn/start`
- 新增活跃 turn 中断:用户或管理员通过任务取消接口把任务转为 `canceled` 后,设备端会轮询 `GET /api/v1/master-agent/tasks/[taskId]/control-state`;如果当前任务已经启动 App Server turnrunner 会在同一个 JSON-RPC 连接上调用 `turn/interrupt`,并把返回的 `interrupted` 处理成干净取消,不再把用户主动取消误判成 runtime failure - 新增活跃 turn 中断:用户或管理员通过任务取消接口把任务转为 `canceled` 后,设备端会轮询 `GET /api/v1/master-agent/tasks/[taskId]/control-state`;如果当前任务已经启动 App Server turnrunner 会在同一个 JSON-RPC 连接上调用 `turn/interrupt`,并把返回的 `interrupted` 处理成干净取消,不再把用户主动取消误判成 runtime failure
- 新增受控线程回滚:服务端入口 `POST /api/v1/projects/[projectId]/thread-rollback` 会创建 `intentCategory=thread_rollback` 任务;设备端通过 App Server 调用 `thread/rollback`,只返回“已回滚最近 N 轮”的用户可见摘要,不启动新 turn不保存 App Server 返回的 thread/turn/items。该能力只回滚 Codex 线程历史,不自动还原本地文件变更,后续如需文件级撤回必须另走 Git / 文件快照恢复链路。 - 新增受控线程回滚:服务端入口 `POST /api/v1/projects/[projectId]/thread-rollback` 会创建 `intentCategory=thread_rollback` 任务;设备端通过 App Server 调用 `thread/rollback`,只返回“已回滚最近 N 轮”的用户可见摘要,不启动新 turn不保存 App Server 返回的 thread/turn/items。该能力只回滚 Codex 线程历史,不自动还原本地文件变更,后续如需文件级撤回必须另走 Git / 文件快照恢复链路。

View File

@@ -263,6 +263,7 @@ cd /Users/kris/code/boss
- 当前已绑定真实 `codexThreadRef` 的普通单线程聊天,会在 `local-agent` 执行 `codex exec resume` 前,先把 Boss 用户消息镜像写入对应 Codex Desktop rollout这样 APP 发起的消息也能进入桌面版同一线程历史,并按 `sourceMessageId` 去重。rollout 定位优先使用 `state_5.sqlite`,状态库不可用或索引缺失时回退扫描 `~/.codex/sessions`;写入后会尽量刷新 `threads.updated_at / updated_at_ms / has_user_event`,再通过 `codex://threads/{threadId}` 深链提示桌面版打开目标线程 - 当前已绑定真实 `codexThreadRef` 的普通单线程聊天,会在 `local-agent` 执行 `codex exec resume` 前,先把 Boss 用户消息镜像写入对应 Codex Desktop rollout这样 APP 发起的消息也能进入桌面版同一线程历史,并按 `sourceMessageId` 去重。rollout 定位优先使用 `state_5.sqlite`,状态库不可用或索引缺失时回退扫描 `~/.codex/sessions`;写入后会尽量刷新 `threads.updated_at / updated_at_ms / has_user_event`,再通过 `codex://threads/{threadId}` 深链提示桌面版打开目标线程
- 当前 `local-agent` 已新增 `Codex App Server` providerboss-agent 默认配置 `codexAppServerEnabled=true``conversation_reply / dispatch_execution` 会先通过 `codex app-server` 的 stdio JSON-RPC 恢复或创建线程,也可配置 `codexAppServerTransport=ws + codexAppServerUrl=ws://127.0.0.1:<port>``codexAppServerTransport=unix + codexAppServerUrl=unix:///absolute/path.sock` 连接同机长驻 App Server长驻连接可通过 `codexAppServerAuthTokenFile``BOSS_CODEX_APP_SERVER_AUTH_TOKEN_FILE` 提供 bearer token。随后 runner 下发 `turn/start` 并收集流式 agent 回复;如果单个 JSON-RPC 请求返回 `-32001 / retry later`runner 会先做指数退避重试;如果任务携带 `targetCodexTurnId`,会改用 `turn/steer` 干预活跃 turn如果 App Server 在 turn 启动前失败,默认允许回退到 `codex exec resume`,如果 turn 已经启动则不再回退,避免同一轮用户消息被重复执行。桌面控制另有 `codexComputerUseEnabled=true`,默认先走 Codex Computer Use再回退 CUA Driver。 - 当前 `local-agent` 已新增 `Codex App Server` providerboss-agent 默认配置 `codexAppServerEnabled=true``conversation_reply / dispatch_execution` 会先通过 `codex app-server` 的 stdio JSON-RPC 恢复或创建线程,也可配置 `codexAppServerTransport=ws + codexAppServerUrl=ws://127.0.0.1:<port>``codexAppServerTransport=unix + codexAppServerUrl=unix:///absolute/path.sock` 连接同机长驻 App Server长驻连接可通过 `codexAppServerAuthTokenFile``BOSS_CODEX_APP_SERVER_AUTH_TOKEN_FILE` 提供 bearer token。随后 runner 下发 `turn/start` 并收集流式 agent 回复;如果单个 JSON-RPC 请求返回 `-32001 / retry later`runner 会先做指数退避重试;如果任务携带 `targetCodexTurnId`,会改用 `turn/steer` 干预活跃 turn如果 App Server 在 turn 启动前失败,默认允许回退到 `codex exec resume`,如果 turn 已经启动则不再回退,避免同一轮用户消息被重复执行。桌面控制另有 `codexComputerUseEnabled=true`,默认先走 Codex Computer Use再回退 CUA Driver。
- 当前已新增 Boss 自有 Inter-Thread Broker 第一版:服务端入口 `POST /api/v1/projects/[projectId]/thread-collaboration` 会创建带源/目标 Codex 线程引用的协作任务App Server runner 执行 `thread/read(source) -> thread/inject_items(target) -> turn/start(target)`,用于让一个线程的结论受控进入另一个线程,不依赖官方任意线程 P2P 互聊能力 - 当前已新增 Boss 自有 Inter-Thread Broker 第一版:服务端入口 `POST /api/v1/projects/[projectId]/thread-collaboration` 会创建带源/目标 Codex 线程引用的协作任务App Server runner 执行 `thread/read(source) -> thread/inject_items(target) -> turn/start(target)`,用于让一个线程的结论受控进入另一个线程,不依赖官方任意线程 P2P 互聊能力
- 当前已新增 App Server 版 Boss 用户消息镜像:普通 `conversation_reply` 任务携带 `mirrorBossUserMessageToCodexDesktop=true`App Server runner 会在 `thread/resume` 后、`turn/start` 前调用 `thread/inject_items`,把 Boss APP 用户原文写成目标 Codex 线程的 `role=user` Responses item任务结果只回写 `threadHistorySync.threadId / injectedItemCount / source`,不回写消息 ID、用户原文、内部调度 prompt 或系统约束。CLI rollout 写入仍作为 App Server 不可用前的兼容兜底。
- 当前已新增 Codex App Server 受控线程回滚:服务端入口 `POST /api/v1/projects/[projectId]/thread-rollback` 会创建 `intentCategory=thread_rollback` 任务App Server runner 执行 `thread/rollback(target, numTurns)`,只回写“已回滚最近 N 轮”的用户可见摘要,不启动新 turn不保存 App Server 返回的 thread/turn/items。该能力只回滚 Codex 线程历史,不自动还原本地文件变更。 - 当前已新增 Codex App Server 受控线程回滚:服务端入口 `POST /api/v1/projects/[projectId]/thread-rollback` 会创建 `intentCategory=thread_rollback` 任务App Server runner 执行 `thread/rollback(target, numTurns)`,只回写“已回滚最近 N 轮”的用户可见摘要,不启动新 turn不保存 App Server 返回的 thread/turn/items。该能力只回滚 Codex 线程历史,不自动还原本地文件变更。
- 当前已新增 Codex App Server 受控线程压缩:服务端入口 `POST /api/v1/projects/[projectId]/thread-compact` 会创建 `intentCategory=thread_compact` 任务App Server runner 执行 `thread/compact/start(target)`,只回写“已发起上下文压缩”的用户可见摘要,不启动普通 turn不保存 contextCompaction item 原始字段。该能力只压缩 Codex 线程上下文,不代表代码修改、文件恢复或版本发布完成。 - 当前已新增 Codex App Server 受控线程压缩:服务端入口 `POST /api/v1/projects/[projectId]/thread-compact` 会创建 `intentCategory=thread_compact` 任务App Server runner 执行 `thread/compact/start(target)`,只回写“已发起上下文压缩”的用户可见摘要,不启动普通 turn不保存 contextCompaction item 原始字段。该能力只压缩 Codex 线程上下文,不代表代码修改、文件恢复或版本发布完成。
- 当前已新增 Codex App Server 受控线程归档 / 恢复:服务端入口 `POST /api/v1/projects/[projectId]/thread-archive` 会创建 `intentCategory=thread_archive|thread_unarchive` 任务App Server runner 直接执行 `thread/archive(target)``thread/unarchive(target)`,不先 resume 已归档线程,不启动普通 turn不保存 App Server 返回的 thread 原始字段。该能力只改变 Codex 线程生命周期状态,不代表代码修改、文件恢复或版本发布完成。 - 当前已新增 Codex App Server 受控线程归档 / 恢复:服务端入口 `POST /api/v1/projects/[projectId]/thread-archive` 会创建 `intentCategory=thread_archive|thread_unarchive` 任务App Server runner 直接执行 `thread/archive(target)``thread/unarchive(target)`,不先 resume 已归档线程,不启动普通 turn不保存 App Server 返回的 thread 原始字段。该能力只改变 Codex 线程生命周期状态,不代表代码修改、文件恢复或版本发布完成。

View File

@@ -86,6 +86,17 @@ function isThreadForkTask(task) {
return task?.intentCategory === "thread_fork" || task?.taskType === "thread_fork"; return task?.intentCategory === "thread_fork" || task?.taskType === "thread_fork";
} }
function shouldMirrorBossUserMessageToCodexThread(task) {
return (
task?.mirrorBossUserMessageToCodexDesktop === true ||
task?.syncUserMessageToCodexThread === true
);
}
function resolveBossUserMessageText(task) {
return trimToDefined(task?.sourceMessageBody) || trimToDefined(task?.requestText);
}
function resolveThreadRenameName(task) { function resolveThreadRenameName(task) {
return trimToDefined(task?.threadRenameName || task?.threadName || task?.name); return trimToDefined(task?.threadRenameName || task?.threadName || task?.name);
} }
@@ -2896,6 +2907,50 @@ async function maybeInjectInterThreadContext({ request, task, targetThreadId })
}; };
} }
function buildBossUserMessageInjectionItem({ task, text }) {
return {
type: "message",
role: "user",
content: [
{
type: "input_text",
text,
},
],
metadata: {
boss_source: "boss_app",
boss_message_kind: "user_message",
...(trimToDefined(task?.sourceMessageSentAt)
? { boss_source_sent_at: trimToDefined(task.sourceMessageSentAt) }
: {}),
},
};
}
async function maybeInjectBossUserMessage({ request, task, targetThreadId, targetTurnId, hasExistingThreadRef }) {
if (
!targetThreadId ||
targetTurnId ||
!hasExistingThreadRef ||
!shouldMirrorBossUserMessageToCodexThread(task)
) {
return undefined;
}
const text = resolveBossUserMessageText(task);
if (!text) {
return undefined;
}
await request("thread/inject_items", {
threadId: targetThreadId,
items: [buildBossUserMessageInjectionItem({ task, text })],
});
return {
threadId: targetThreadId,
injectedItemCount: 1,
source: "boss_user_message",
};
}
export async function executeCodexAppServerTask(runnerConfig, task) { export async function executeCodexAppServerTask(runnerConfig, task) {
if (!shouldUseCodexAppServerTaskRunner(runnerConfig, task)) { if (!shouldUseCodexAppServerTaskRunner(runnerConfig, task)) {
return createFailure("CODEX_APP_SERVER_DISABLED", { canFallbackToCli: true }); return createFailure("CODEX_APP_SERVER_DISABLED", { canFallbackToCli: true });
@@ -3385,6 +3440,13 @@ export async function executeCodexAppServerTask(runnerConfig, task) {
task, task,
targetThreadId: threadId, targetThreadId: threadId,
}); });
const threadHistorySync = await maybeInjectBossUserMessage({
request,
task,
targetThreadId: threadId,
targetTurnId: targetTurnRef,
hasExistingThreadRef: Boolean(targetThreadRef),
});
const turnControl = targetTurnRef ? "steer" : "start"; const turnControl = targetTurnRef ? "steer" : "start";
const turnResult = targetTurnRef const turnResult = targetTurnRef
@@ -3420,6 +3482,7 @@ export async function executeCodexAppServerTask(runnerConfig, task) {
transport: runnerConfig.transport, transport: runnerConfig.transport,
executionProgress: progressCollector.snapshot(), executionProgress: progressCollector.snapshot(),
interThreadBroker, interThreadBroker,
threadHistorySync,
canFallbackToCli: false, canFallbackToCli: false,
}; };
} }
@@ -3433,6 +3496,7 @@ export async function executeCodexAppServerTask(runnerConfig, task) {
transport: runnerConfig.transport, transport: runnerConfig.transport,
executionProgress: progressCollector.snapshot(), executionProgress: progressCollector.snapshot(),
interThreadBroker, interThreadBroker,
threadHistorySync,
canFallbackToCli: false, canFallbackToCli: false,
}; };
} catch (error) { } catch (error) {

View File

@@ -796,6 +796,12 @@ rl.on("line", (line) => {
return; return;
} }
const text = message.params?.input?.find?.((item) => item?.type === "text")?.text ?? ""; const text = message.params?.input?.find?.((item) => item?.type === "text")?.text ?? "";
const injectedUserMessageText =
process.env.BOSS_CODEX_APP_SERVER_FIXTURE_ECHO_INJECTED_USER_MESSAGE === "1"
? injectedItems
.flatMap((item) => item?.content ?? [])
.find((content) => content?.type === "input_text")?.text
: "";
send({ send({
id: message.id, id: message.id,
result: { result: {
@@ -1654,7 +1660,9 @@ rl.on("line", (line) => {
threadId: message.params?.threadId, threadId: message.params?.threadId,
turnId: "turn-fixture", turnId: "turn-fixture",
delta: delta:
process.env.BOSS_CODEX_APP_SERVER_FIXTURE_INTER_THREAD === "1" injectedUserMessageText
? `INJECTED_USER_MESSAGE:${injectedUserMessageText}`
: process.env.BOSS_CODEX_APP_SERVER_FIXTURE_INTER_THREAD === "1"
? `INTER_THREAD_INJECTED:${JSON.stringify(injectedItems)}` ? `INTER_THREAD_INJECTED:${JSON.stringify(injectedItems)}`
: `APP_SERVER_REPLY:${text}`, : `APP_SERVER_REPLY:${text}`,
}, },

View File

@@ -508,6 +508,50 @@ test("codex app-server runner resumes a thread and collects streamed agent text"
assert.equal(result.transport, "stdio"); assert.equal(result.transport, "stdio");
}); });
test("codex app-server runner injects Boss user message into the target Codex thread before starting a turn", async () => {
const previous = process.env.BOSS_CODEX_APP_SERVER_FIXTURE_ECHO_INJECTED_USER_MESSAGE;
process.env.BOSS_CODEX_APP_SERVER_FIXTURE_ECHO_INJECTED_USER_MESSAGE = "1";
try {
const runnerConfig = getCodexAppServerRunnerConfig(process.env, {
codexAppServerEnabled: true,
codexAppServerCommand: process.execPath,
codexAppServerArgs: ["tests/fixtures/codex-app-server-runtime.mjs"],
codexAppServerWorkdir: repoRoot,
codexAppServerTimeoutMs: 5000,
masterAgentModel: "gpt-5.4",
});
const task = {
taskId: "task-app-server-sync-user-message",
taskType: "conversation_reply",
targetCodexThreadRef: "019d-app-server-thread",
targetCodexFolderRef: repoRoot,
executionPrompt: "请修复登录页跳转问题",
mirrorBossUserMessageToCodexDesktop: true,
userMessageId: "boss-msg-1",
sourceMessageId: "boss-msg-1",
sourceMessageBody: "请修复登录页跳转问题",
userDisplayName: "你",
};
const result = await executeCodexAppServerTask(runnerConfig, task);
assert.equal(result.status, "completed");
assert.equal(result.threadId, "019d-app-server-thread");
assert.equal(result.turnControl, "start");
assert.equal(result.threadHistorySync?.threadId, "019d-app-server-thread");
assert.equal(result.threadHistorySync?.injectedItemCount, 1);
assert.equal(result.threadHistorySync?.source, "boss_user_message");
assert.match(result.replyBody, /INJECTED_USER_MESSAGE:请修复登录页跳转问题/);
assert.doesNotMatch(JSON.stringify(result), /boss-msg-1/);
} finally {
if (previous === undefined) {
delete process.env.BOSS_CODEX_APP_SERVER_FIXTURE_ECHO_INJECTED_USER_MESSAGE;
} else {
process.env.BOSS_CODEX_APP_SERVER_FIXTURE_ECHO_INJECTED_USER_MESSAGE = previous;
}
}
});
test("codex app-server runner converts protocol progress events into Boss execution progress", async () => { test("codex app-server runner converts protocol progress events into Boss execution progress", async () => {
const previous = process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_PROGRESS; const previous = process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_PROGRESS;
process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_PROGRESS = "1"; process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_PROGRESS = "1";