Conversation
Codex Desktop 只在回合最后一条 Agent 消息带 phase: "final_answer" 时, 才会把执行过程折叠到 "Worked for …" 之后。多数 Harness 适配器不标 phase, 这些回合完成后过程无法收起。 新增 inferredFinalAnswer():回合成功结束、Desktop 可见的最后一个条目是 非空 agentMessage、且适配器未给出 phase 时,将其视为 final_answer; 适配器显式给出的 phase 始终优先,标 commentary 可退出推断。 - 实时推送:在 turn/completed 之前重发该消息的 item/completed。Desktop 处理 turn/completed 时不读取 turn.items,而 item/completed 按 id 替换条目。 - 历史回放:projectHistoricalTurn 按投影后可见条目顺序应用同一规则, 重新打开的旧会话同样生效。 - 更新 HostAgentMessageItem.phase 的契约注释,补充定向测试,并同步 deepseek-harness 会话测试中新增的补发消息。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
新增 docs/architecture/turn-activity-folding.md,说明 Codex Desktop 的折叠 条件、Protocol Core 的推断规则与两条投影路径、已知限制,以及与 Kiro CLI、 Qoder、Kimi Code 现有显式阶段标注的重复关系;在 docs/index.md 登记入口。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: BytePioneer-AI/codex-host/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (7)
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review. 📝 SummarySummary by CodeRabbit
Walkthrough新增最终答复阶段推断逻辑,并将其用于 Codex UI 的实时和历史回合投影。文档说明推断条件、历史回合处理方式及折叠限制。测试覆盖推断成功和不推断的情况。 Changes最终答复阶段与投影
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Feature Sequence Diagram(s)sequenceDiagram
participant CodexUiProjector
participant inferredFinalAnswer
participant CodexUIWire
CodexUiProjector->>inferredFinalAnswer: 传入最后一条可见条目和回合完成状态
inferredFinalAnswer-->>CodexUiProjector: 返回带 final_answer 的消息或 null
CodexUiProjector->>CodexUIWire: 更新实时条目并重放完成通知
Suggested reviewers: Merge Risk: ⚪ Minimal · up to The final-answer projection is mergeable after normal checks; no actionable issue remains from this review. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to Completed turns may now fold earlier commands and tool activity behind the final reply, including historical turns without a recorded outcome. The activity remains expandable, and the change does not appear to grant new execution or permission authority. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
原生历史缺少终止证据时,Adapter 按规范把回合结果记为 unknown(例如 Claude Code 的会话记录不保存 SDK Result,其历史回合一律为 unknown),但 projectHistoricalTurn 仍把这类回合显示为 completed。推断条件此前只认 succeeded,导致 Claude Code、Hermes、Cursor CLI 等的旧对话无法折叠。 历史回合改为按 historicalStatus(...) === "completed" 判断,与 Desktop 显示 的回合状态一致;failed 与 cancelled 仍不推断,回合结果本身不变。实时回合 只有 succeeded / failed / cancelled,行为不变。 - 用 6 个真实 Claude Code 会话(60 轮)验证:修复前 0 轮、修复后 60 轮 投影出 final_answer。 - 测试拆分为取消/失败不推断(补充 failed)与 unknown 历史回合推断两例。 - 文档补充 unknown 的来源分类与相应的已知限制。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary
问题
Codex Desktop 在回合完成后,会把最终回复之前的推理、命令和工具调用折叠到"Worked for …"后面。但它只在回合最后一条 Agent 消息带
phase: "final_answer"时才允许折叠。依据 Codex Desktop 26.924.2738.0 前端:local-conversation-turn中的hasFinalAssistantStarted依赖phase === "final_answer",conversation-blocks只在该值为真、回合未取消、且存在可折叠条目时开启折叠。目前只有 Kiro CLI、Qoder / Qoder CN、Kimi Code 的适配器会标这个 phase。Claude Code、DeepSeek Harness、OpenCode、Grok、Cursor、Pi、OMP、Hermes、Antigravity、CodeBuddy、WorkBuddy 都不标,所以这些 Harness 的回合完成后,过程一直展开。
方案
在 Protocol Core 的投影层统一推断,所有 Harness 一次生效:
packages/protocol-core/src/final-answer-phase.ts。同时满足以下条件时,把 Desktop 可见的最后一条agentMessage投影为final_answer:succeeded/unknown(即historicalStatus(...)为completed);failed、cancelled回合不推断。commentary即可退出推断。CodexTurnProjector在turn/completed之前,重发该消息的item/completed。原因是 Desktop 处理turn/completed时只读状态、错误和耗时,不读turn.items;而item/completed会按 id 替换已有条目。projector 对文件变更汇总项已经在用同样的补发方式。projectHistoricalTurn按投影后的可见条目顺序应用同一规则。HostAgentMessageItem.phase的契约注释,写明"不标时由宿主推断,标commentary可退出"。docs/architecture/turn-activity-folding.md,并登记到docs/index.md。"可见条目"按 Desktop 实际收到的顺序计算。Todo 工具、无法解析的文件写入工具和空 Reasoning 不计入。同一回合的文件变更合并为一个汇总条目,位置在首次改文件处。实时推送和历史回放两条路径的计算结果一致。
为什么放在 Protocol Core,而不是逐个改适配器
host-runtime/src/delegation-snapshot.ts)在消息没有 phase 时,同样按"最后一条 Agent 消息"推断最终回复。修正:历史回合结果为
unknown时同样推断(commitce067aea)第一版只认历史结果为
succeeded的回合。实测发现这会让 Claude Code 的旧对话全部无法折叠:~/.claude/projects/*.jsonl)不保存 SDK 的result消息,而实时判断成功依赖其中的subtype、is_error、terminal_reason(claude-code/src/native-message.ts:457-486)。按openspec/specs/claude-code-text-session/spec.md中 "Native history omits complete Result evidence" 的要求,历史回合结果必须保持unknown,也不能凭stop_reason推断成功。projectHistoricalTurn一直把unknown显示为completed,只有推断条件比 Desktop 更严格。修正后,历史回合改用
historicalStatus(snapshot.outcome) === "completed"判断,与 Desktop 显示的回合状态一致。适配器的回合结果不变,所以不违反上述规范。实时回合只有succeeded/failed/cancelled,行为不变。影响范围
succeeded(包括unknown)时,它们会把结尾消息显式标为commentary,所以也不受这次修正影响。Qoder 在会话关闭路径上有一处不带 phase 的消息(qoder-sdk-transport.ts:1772),那时回合以失败结束,也不满足推断条件。final_answer。这类回合如果有推理条目,完成后会折叠,和 Kiro、Qoder、原生 Codex 的行为一致。projectHistoricalTurn重新投影;Desktop 也不在磁盘上缓存会话。所以升级后旧会话同样会折叠。历史回合结果的来源分三类(逐个读过各适配器的判定代码):unknown:Claude Code(claude-history.ts:163-184)、Hermes(gateway-history.ts:80-83、hermes-session.ts:428-435)、Cursor CLI(projection.ts:266-269)。这些 Harness 的旧对话依赖这次修正才能折叠。succeeded,缺证据时才是unknown:Grok、CodeBuddy / WorkBuddy、Kiro CLI、Kimi Code、Qoder、Pi、OMP、OpenCode、Antigravity(从 codexhost 自己保存的附属记录读取实时结果,记录缺失时生成的unknown回合没有条目)。unknown:DeepSeek Harness。final_answer和委派快照原有的推断结果一致,codexhost thread read的结果不变。已知限制
unknown"的 Harness,这类回合更可能是异常结束的,现在同样按已完成推断:最后一段文字会被当作最终回复,过程仍可展开查看。与 Kiro CLI、Qoder、Kimi Code 现有实现的重复
这三个 Harness 在本 PR 之前已经自行标注
final_answer,规则和本 PR 的推断基本等价。本 PR 不修改它们:显式 phase 优先,推断对它们不生效,行为保持不变。建议本 PR 合入、实机稳定后,另开 PR 删除下列标注
final_answer的逻辑,统一由 Protocol Core 推断。 失败或取消时标commentary的分支要保留:委派快照依赖它,才不会把失败回合的部分输出当作最终结果。以下行号基于main@4052cf49:Kiro CLI
packages/adapters/kiro-cli/src/turn-output.ts:169:finish()在回合成功时以final_answer结束仍在输出的消息。只删除成功分支,保留失败时的commentary。packages/adapters/kiro-cli/src/history.ts:360-367、:400-401:用lastContent选出最后一个"工具调用或非空回复";是回复且回合成功时标final_answer。packages/adapters/kiro-cli/src/kiro-adapter.ts:1414:斜杠命令结果消息固定标final_answer。Qoder(Qoder CN 通过
packages/adapters/qoder-cn/src/plugin.ts复用同一实现)packages/adapters/qoder/src/qoder-sdk-transport.ts:562-570、:582-584:本条消息不含tool_use的文本块,直接标final_answer。这条规则和推断不一致,可能把回合中途的消息标成最终回复。packages/adapters/qoder/src/qoder-sdk-transport.ts:859:result成功时以final_answer结束仍在输出的消息。只删除成功分支,保留取消或失败时的commentary。packages/adapters/qoder/src/qoder-history.ts:232-238、:247-248:最后一条 assistant 消息(字符串内容)且回合成功时,标final_answer。packages/adapters/qoder/src/qoder-history.ts:256-261、:283-290:最后一条 assistant 消息中,所在消息不含tool_use的文本块且回合成功时,标final_answer。Kimi Code
packages/adapters/kimi-code/src/kimi-session.ts:560、:625、:820:用hasHadToolCallsInTurn跟踪本轮是否有工具调用;回合结束时只在有工具调用时标final_answer。这条规则和推断不一致:没有工具调用的回合不标。packages/adapters/kimi-code/src/history.ts:538:历史回放只在maxToolOrder >= 0时标final_answer。中间消息标
commentary的代码(Kiroturn-output.ts:102/126/134,Qoderqoder-sdk-transport.ts:522/705,Kimikimi-session.ts:619/628/647/732/754、history.ts:465)不影响 Desktop 主会话界面的折叠:主界面传的commentaryDisplay是all。保留也没有副作用,可以由适配器维护者决定是否一并清理。另外,Kiro CLI 与 Qoder 在历史结果为
unknown时会把结尾消息显式标为commentary(kiro-cli/src/history.ts:400-401、qoder-history.ts:247-248、:283-290),所以这类回合不会折叠。删除上述逻辑时如果改为不标 phase,这类回合会转由 Protocol Core 按已完成推断。Related issues
无。相关的 #410(委派快照识别
final_answer)和本 PR 互相独立,可以分别合入。Test plan
定向测试:
npx vitest run --config tests/vitest.config.js packages/protocol-core,13 个文件、135 个用例全部通过。新增的packages/protocol-core/test/final-answer-phase.test.ts覆盖了:item/completed;unknown时推断,且回合状态仍为completed。反向确认:
null后,实时和历史两个正向用例失败;恢复后通过。succeeded后,unknown用例失败;恢复后通过。真实数据验证:用 Claude Code 适配器自己的
readClaudeTranscript和mapClaudeSnapshot读取 6 个真实 Claude Code 会话(共 60 轮,全部为unknown,每轮最后一个可见条目都是回复),再交给projectHistoricalTurn投影。修正前 0 轮、修正后 60 轮投影出final_answer。DeepSeek Harness 会话测试
streams v0.1.5 attempts...收集了 projector 的全部item/completed,现在多出一条补发的最终回复。断言已按新行为更新,这也覆盖了 DeepSeek Harness 的真实事件序列。全量 TypeScript 测试:
npm run build:typescript后执行npx vitest run --config tests/vitest.config.js。结果为 380 个文件通过、14 个跳过;4679 个用例通过、37 个跳过、4 个失败。这 4 个在干净的main@4052cf49上、在同一环境里同样失败,与本 PR 无关:claude-code/test/sdk-transport.test.ts的两个 root 安全用例;workbuddy/test/history-derivation.test.ts的preserves the native failure when bridge cleanup also fails;tests/release/host-bundle.test.mjs。此前一次全量运行中,
host-runtime/test/harness-plugin-loader.test.ts的gives later plugins a full timeout...出现过负载下的超时,单独运行 3 次都通过,本次全量运行也通过。npx tsc -p tests/tsconfig.json --noEmit、npm run lint(eslint .及tools/check-boundaries.mjs)通过;改动文件的prettier --check通过。实机验证(Windows,Codex Desktop 26.924.2738.0,从本分支源码启动):
unknown修正。SSH 远程工作区的 Claude Code 线程由被控机器上的 codexhost 0.10.2 投影,所以没有覆盖到;Claude Code 旧对话的效果目前只有上面的真实数据验证。未执行:Rust 测试(本 PR 没有改 Rust 代码)。
🤖 Generated with Claude Code