PR #41 Codex Provider 审查

feat(daemon): add codex app-server provider · +5319 · 25 files · 2026-06-01

Codex 特有语义大面积泄漏到公共抽象层。 PR 在实现 Codex provider 的同时修改了统一接口类型、编排层和路由层,把 Codex wire format 概念塞进了所有 provider 共用的公共合约,穿透了 Phase 2 的三层隔离设计。
6
Major 接口偏移
2
Major 内部缺陷
5
Minor

接口层 — Codex 概念外溢到公共类型

MajorAgentInput 加了 Codex skill 概念
公共输入类型新增 kind: 'text' | 'skill'skillName。"skill" 是 Codex wire format,Claude Code / ACP 不这么用。更严重的是 service 层也被污染了——编排层直接构造 { kind: 'skill' }
// service.ts 编排层不应该知道 Codex 的 skill 概念 return { kind: 'skill', skillName, text: `Inject skill: ${skillName}` }
skill.inject 定义为 execution: 'prompt-expansion',让 runner 的 runControl 内部处理就行。
MajorApprovalResponse 加了 Codex 审批字段
新增 updatedInput(Codex 用户提问回答)和 updatedPermissions(Codex 权限授予)。两个字段只有 Codex adapter 的 protocol.ts 消费。provider-specific 的审批 payload 应放一个 opaque 字段,不暴露 Codex 的字段名。
MajorproviderTurnId 加在事件公共基类上
Codex 的 turn ≠ Eyrie 的 run。providerTurnId 被加在 AgentProviderEventBase(每个事件都带),但只有 Codex runner 内部读它做 turn→run 映射。映射是 adapter 的事,不应穿透到公共事件类型。
MajorSink 签名 + Runner 接口加了 options.runId
PR #44 已设计 SessionSink.setCurrentRun(runId)——service 注入 runId,runner 不碰。PR #41 无视已有设计,改了 emitstartTurnrunControl 三个接口签名让 runner 管 runId 再传回 sink。
// 已有设计:service 注入,runner 不知道 runId sink.setCurrentRun(run.id) runner.startTurn(input) // PR #41 改成 U-turn:runId 从 service 绕一圈又回来 runner.startTurn(input, { runId }) → sink.emit(event, { runId })
Major路由层硬编码 Codex 命令 schema
routes/agents.ts 里变量名叫 codexControlCommandSchema,9 个 Codex 命令名和参数写在路由层。同时 codexProviderConfigSchema 也在路由层。加新 provider 或 Codex 新增命令都要改路由层,且和 codex/index.ts 的命令定义重复——两处要同步。

根因链

没对齐
SessionSink 设计
runner
自己管 runId
事件上带
providerTurnId
改 sink 签名
传回 runId

解决根源后 3 个 Major 自然消失。AgentInput / ApprovalResponse / 路由硬编码是独立问题。

Codex 内部实现

MajorresolveSkillPath 同步 I/O 阻塞 daemon
existsSync 解析 skill 路径。daemon 单线程,同步 I/O 阻塞所有 session 的事件处理和 HTTP 请求。
MajorhandleExit 先 evict 再 emit 致事件丢失
进程退出时先通知 service 删 runner 引用,再 emit run.completed。emit 失败被 .catch 吞掉,run 停在 running 直到 daemon 重启回收。应先 emit 再 evict。
MinorrespondToApproval 不校验 optionId
传入不存在的 optionId 时,Codex 收到 accept、Eyrie 记录 deny——两侧状态矛盾。
Minor权限审批默认全授权 / 未知消息静默丢弃 / 工具函数重复
用户点 accept 默认授予全部请求权限无注释说明;Codex 新增事件类型只有 debug 日志;isRecord 三个文件各写一遍;turnRunIds 进程崩溃时不清理。
审查基于 Phase 2 统一接口 spec · base 点 b1be059:types.ts · PR #44 SessionSink 设计