feat(cli): add codex app-server backend (protocol + profile)#2216
Conversation
实现 docs/rfc/codex-backend.md Phase 1:OpenAI Codex CLI 作为第三个 backend, 走 `codex app-server` JSON-RPC 2.0 over stdio。 实现: - internal/cli/protocol_codex.go — CodexProtocol 实现 cli.Protocol: initialize+initialized+thread/start 握手、turn/start(UserInput[])、 turn/interrupt、item/agentMessage/delta 流式累积、turn/completed 边界 flush assistant 帧 + result、thread/tokenUsage/updated → metadata、 */requestApproval 反向请求自动 allow。模式对齐 ACPProtocol。 - internal/cli/backend/profile_codex.go — codexProfile(),注册到 RegisterDefaults (注册≠默认开启,仍需 cli.backends 显式配置)。 - internal/cli/detect.go — knownBackends 增 codex 行。 可行性验证(codex 0.141.0 实测,docs/rfc/codex-backend-validation.md): - app-server 协议完整跑通,RFC §2.4 事件流 + method 名命中。 - 纠正 RFC §7:codex 原生支持 Amazon Bedrock(内置 amazon-bedrock provider)。 实测 bedrock-mantle/v1/responses + gpt-oss-120b 连通;两个约束:内置 provider 路径 bug(需自定义 provider 指 /v1)、gpt-oss responses 拒 codex namespace 工具(agentic 受限,纯对话/function-calling 可用)。 测试:protocol_codex_test.go 表驱动覆盖握手/turn/interrupt/approval/ tokenUsage/未知method/错误响应;profile 注册断言更新为 3 backend。 go build/vet/test -race ./internal/cli/... 全绿。
- internal/upstream/caps_test.go: derivedCaps() 现含 codex 的 RequiredNodeCaps "codex-app-server",更新断言为 ["acp","codex-app-server"] (修复 CI test/test-macos FAILURE:TestDerivedCaps_FromDefaultRegistry)。 - internal/node/caps.go: knownServerCaps 增 "codex-app-server",与 kiro 的 "acp" 对齐,消除 codex 子节点注册时的 spurious "unknown capabilities" WARN。 - docs/rfc: 二次实测纠正 —— Bedrock + gpt-5.5(us-east-1/2,内置 amazon-bedrock provider)支持 codex 完整 shell agentic(codex exec + app-server 实测通过), 是首选 Bedrock 部署路径;§7.1 约束 2(namespace 工具被拒)仅适用于 gpt-oss。 完整性审计(vs kiro 端到端接线):profile 注册 / detect / 节点 cap 派生+远程 门控 / config 校验 / doctor / dashboard chip+tag+feature+cost API / 本地 wrapper 全部经 backend.Profile 注册表泛型驱动,codex 零额外编辑生效。codexjsonl 历史 source 为 phase1+(当前 NoopHistorySource 优雅降级,不 panic)。 go build/vet/test ./internal/... ./cmd/... 全绿。
补齐 codex backend 唯一的结构性抽象缺口:历史面板回填。codex 之前是 NoopHistorySource(优雅降级,历史会话显示空),现与 claude/kiro 对等。 - internal/history/codexjsonl/source.go — history.Source 实现,读 ~/.codex/sessions/YYYY/MM/DD/rollout-<iso>-<threadId>.jsonl。codex 的 rollout 格式比 kiro 更干净:event_msg 行(user_message/agent_message) 自带 ISO-8601 时间戳和已拼接纯文本,无需借时间戳。WalkDir 按 `-<threadId>.jsonl` 后缀匹配日期分桶树;镜像 kirojsonl 的 path-traversal 防护 / 16MiB 上限 / ctx 取消 / 逐行容错降级。init() 注册 codex factory。 - HistoryWiring.CodexSessionsDir 字段贯通:cli/history.go + RouterConfig + router_core/lifecycle(codexSessionsDir) + main.go(~/.codex/sessions) + wireup/history_backends.go blank-import。 - 测试:source_test.go(glob/event_msg 映射/ISO 时间戳/limit/before 过滤/ path-traversal/缺文件/降级/坏行跳过/factory)+ 实测 codex 0.141 真实 rollout 文件解析正确(user prompt + agent reply 时间戳准确)。 go build/vet/test ./internal/... ./cmd/... 全绿。 说明:askuser / passthrough / embedded_context 三个 Feature 标志仍为 phase1 false——codex 协议有对应原语(item/tool/requestUserInput / turn/steer / mention UserInput),但需各自实现底层管线后再翻 true,否则 dashboard 会显示点了不工作的控件。属 RFC §10 Phase 2 范围。
check-router-fields lint 要求每个 Router 字段声明 `// 读写:` 访问集; codexSessionsDir 与 kiroSessionsDir 同访问模式(core init / lifecycle attachHistorySource / discovery)。修复 lint-router CI 失败。
|
对抗性 review(独立 reviewer + 对抗验证,已在 PR 分支 HEAD 🔴 [major] post-handshake codex
|
三个 claude 专属 Feature 在 codex backend 的可行性评估,含 codex 0.141 live 探针结论: - embedded_context: EASY/MODERATE — @path 经 shell 工具读取(非原生内联), 倾向结构化 mention UserInput;推荐第一做(~0.5-1d)。 - passthrough: MODERATE — 实测确认 clientUserMessageId 逐字 round-trip 为 item.clientId + turn/steer mid-turn 成功,slot-matching 适配器可行(非重设计); 第二做(~3-4d)。 - askuser: HARD — 阻塞式 RPC 重引入 pending/TTL 表,且 requestUserInput 请求/响应 schema 未验证;推迟,先 live 捕获。 codex 与 kiro Feature map 逐位相同,三个 false 是所有非-replay 后端的 共同 Phase-2 空缺,非 codex 独有。
…lowed ErrCodexRPC) [#2216] CodexProtocol.ReadEvent returns (nil, true, %w ErrCodexRPC) for a deferred turn/start ERROR response on the main readLoop, intending to close the turn — but handleShimStdout only recognised ErrACPRPC, so errors.Is(codexErr, ErrACPRPC) was false and the error fell into the 'skip unparseable event' branch. No synthetic result event was dispatched, the turn never closed, and the codex session hung in state=running — exactly the failure mode the ReadEvent comment claims to prevent. Triggerable because turn/start is sent fire-and-forget via WriteMessage, so any error reply (-32001 overload, gpt-oss tool rejection) lands on this path. Fix: extract rpcErrorTurnEnd(err) which recognises BOTH ErrACPRPC and ErrCodexRPC and returns the backend tag for the synthesized result. The readLoop error branch calls it instead of an inline ACP-only errors.Is, so a new backend only needs to register its sentinel in one place. Regression test rpc_error_turn_end_test.go covers both sentinels, the wrapped+bare forms, a non-RPC parse error (must NOT synthesize), and a guard asserting ErrCodexRPC does not satisfy errors.Is(ErrACPRPC) — the exact distinction the old code missed. Full internal/cli green. Found via adversarial review of this PR.
|
✅ 已修复并推送(commit 修复:抽取包级纯函数 回归测试 完整
|
概述
实现
docs/rfc/codex-backend.mdPhase 1:把 OpenAI Codex CLI 作为 naozhi 第三个 CLI backend(继 claude / kiro 之后),走codex app-server的 JSON-RPC 2.0 over stdio 长连接协议。先做了 Phase 0 可行性验证(codex-cli 0.141.0 实测),把 RFC 从 Draft v1 升到 v2 并纠正了一处关键过时论断。
可行性验证结论(详见
docs/rfc/codex-backend-validation.md)initialize → initialized → thread/start → turn/start → turn/started → item/* → turn/completed,RFC §2.4 事件流与 method 名经generate-json-schema+ 实测全部命中。amazon-bedrockprovider(openai/codex PR #18744)。实测bedrock-mantle.us-west-2.api.aws/v1/responses+gpt-oss-120b连通。/openai/v1/responses,gpt-oss 只在/v1/responses服务 → 需自定义 provider;② Bedrock gpt-oss responses 拒绝 codex 内置 agentic 工具的namespace变体(只认function/mcp)→ Bedrock 路径仅支持纯对话/function-calling,完整 shell-agentic 需 OpenAI 凭据 + gpt-5.x-codex。改动
新增
internal/cli/protocol_codex.go—CodexProtocol实现cli.Protocol(BuildArgs / Init 握手 / WriteMessage(turn/start, UserInput[]) / WriteInterrupt(turn/interrupt) / ReadEvent 翻译层 / HandleEvent 自动授权 / Capabilities)。模式对齐ACPProtocol(atomic.Pointer threadID、readUntilResponse goroutine+timeout+shim-deadline-pulse、mu-guarded textBuf、turn 边界 flush assistant 帧 + result 元数据二段式)。internal/cli/protocol_codex_test.go— 表驱动单测(握手/turn/interrupt/approval/tokenUsage/未知 method/错误响应/partial-text-on-failure)。internal/cli/backend/profile_codex.go—codexProfile()(ID=codex, Tag=cdx, ChipColor=OpenAI 绿, CostUnit=tokens, HistoryDir=~/.codex/sessions/, RequiredNodeCaps=[codex-app-server])。docs/rfc/codex-backend-validation.md— Phase 0 实测报告。修改
internal/cli/backend/profile.go—RegisterDefaults增codexProfile()。注册 ≠ 默认开启:EnabledBackends仍由cli.backends配置驱动,默认只有 claude。internal/cli/detect.go—knownBackends增 codex 行(满足 mirror 契约测试)。internal/cli/backend/profile_test.go— 默认 backend 数 2→3 + codex 字段断言。docs/rfc/codex-backend.md/README.md— 实测回写(input 形态、token usage 位置、反向请求名、Bedrock 小节、状态升 v2)。Code Review
经 go-reviewer 审查,修复 2 个 CRITICAL(turn 边界缺 assistant 帧→dashboard 无气泡;failed turn 丢弃 partial text)+ 2 个 HIGH(WriteInterrupt 缺 cancel metric;post-handshake 错误响应被静默吞)+ 1 个 MEDIUM(id round-trip 改用 strconv.Atoi 对齐 ACP、处理负数 id)。
测试计划
go build ./...go vet ./internal/cli/...gofmt -l(无 diff)go test -race ./internal/cli/(含新 codex 表驱动测试)go test ./internal/{server,discovery,config,session,metrics}/(无回归)后续(不在本 PR)
internal/history/codexjsonl/历史 source。turn/steer→/urgent。🤖 Generated with Claude Code