feat(trigger): per-turn model + reasoningEffort 覆盖(Codex 家族·新建会话·PR A / 拆自 #638) - #639
feat(trigger): per-turn model + reasoningEffort 覆盖(Codex 家族·新建会话·PR A / 拆自 #638)#639deepcoldy wants to merge 4 commits into
Conversation
拆自原 PR #638(superseded)的第一块,单一状态机、影响面最小。 - TriggerRequest.options 加 model?:string + reasoningEffort?:'low'|'medium'|'high'|'xhigh'(校验:model ≤200 字符、effort 枚举)。 - 仅新建会话生效:trigger-session 首 fork 前 stamp 到 session;sessionAgentConfig 的 agentFrozen 冻结(?? 保留 override);init 携带 reasoningEffort → worker → adapter。 - codex(纯/RPC 路径):buildArgs 注入 `-c model_reasoning_effort=<effort>`(xhigh→high)。 - codex-app(B 模式目标):buildArgs 透传 --model/--reasoning-effort 给 runner; codex-app-runner 注入 thread/start——model 走 ThreadStartParams 顶层 model、effort 走 config.model_reasoning_effort(codex 已按 0.145 生成类型确认该 schema,且 thread/start 每新会话一次、fold-in 不触发,契合 fresh-spawn-only 语义)。 影响面:只走 async trigger 专用路径;model/effort 仅 codex/codex-app 消费,其它 CLI 忽略 reasoningEffort(buildArgs 不解构即丢弃)。校验/透传单测;build 绿;trigger-api 27 测绿。 Co-Authored-By: Claude <noreply@anthropic.com>
deepcoldy
left a comment
There was a problem hiding this comment.
PR A 窄范围复审结论:方向正确,0.145 的 ThreadStartParams.model + config.model_reasoning_effort 形态也已用真实 app-server thread/start 验证;但当前有 3 组 blocking findings,暂不建议合并。
本地验证:git diff --check 通过;6 个相关测试文件共 423 tests 通过;pnpm build 通过;与当前 master 本地 merge-tree 无冲突。额外做了不启动模型 turn 的真实协议检查:Codex 0.145 thread/start 传 model:gpt-5.6-terra、config.model_reasoning_effort:xhigh,返回明确包含 model:gpt-5.6-terra 与 reasoningEffort:xhigh。
除 inline 外还有一个交付缺口:这是公开 /api/trigger 的新 options,但中英文 docs-site/docs/{zh,en}/api-task-trigger.md 尚未记录 model/effort、fresh-only/fold-in-ignore 与适用模式。现有新增测试只验证 parser,没有覆盖 fresh session stamp、fold-in 不覆盖、daemon→worker→adapter、RPC engine 或 app-server thread/start,因而没有捕获下面两个实际 wiring bug。修复时请补对应链路测试和双语文档。
同账号无法点 REQUEST_CHANGES,因此以 COMMENT 记录 blocking review。B/C 未实现内容未计入本 PR finding。
| larkAppId: cfg.larkAppId, | ||
| locale: cfg.locale, | ||
| model: ttadkGateway ? undefined : cfg.model, | ||
| reasoningEffort: cfg.reasoningEffort, |
There was a problem hiding this comment.
P1 — reasoning effort 只传给 adapter,却没进入 RPC 模式真正执行模型的 app-server。 engageCodexRpc 创建 CodexRpcEngine(本文件约 707 行)时只传了 model,漏了 reasoningEffort。RPC 模式中 adapter 的 -c model_reasoning_effort=... 只落到 remote viewer TUI;thread 是 engine 先创建、模型也由 engine app-server 执行,所以 effort 实际仍用默认值。CodexRpcEngineOpts 与 threadParams() 已支持该字段,需在 engine 构造处传入并加 thread/start 断言。该 engine 同时服务 TraeX,若范围只允许 codex,请按 cliId 做明确 gate。
| if (reasoningEffort) { | ||
| // Per-turn reasoning effort → codex model_reasoning_effort(进程级 -c 覆盖, | ||
| // 不动用户全局 config)。codex 只接受 low/medium/high,botmux 'xhigh' 收敛到 'high'。 | ||
| const codexEffort = reasoningEffort === 'xhigh' ? 'high' : reasoningEffort; |
There was a problem hiding this comment.
P1 — 不应把 xhigh 静默降成 high。 Codex 0.145 的 ReasoningEffort 明确包含 xhigh;我还对真实 0.145 app-server 发了 thread/start(config.model_reasoning_effort=xhigh),响应原样返回 reasoningEffort:"xhigh"。API 既然公开接受 xhigh,硬降档会让支持它的模型少算力且调用方无从得知。这里和 codex-app-runner 的同一转换都应原样透传;具体模型不支持时应由 Codex 返回协议错误,而不是 botmux 全局篡改。
| // before the first fork so sessionAgentConfig freezes the chosen model and the | ||
| // init message carries the effort. A fold-in to an existing worker never | ||
| // reaches here, so overrides only apply to a newly-created session. | ||
| if (typeof req.options?.model === 'string' && req.options.model.trim()) { |
There was a problem hiding this comment.
P1 — 实现范围与 PR 契约不一致。 这里对所有 fresh trigger session 无条件 stamp,并未要求 asyncReturnSessionId,所以同步 wait、普通 webhook 只要带 options 也会生效;同时 session.model 是公共 adapter 参数,Claude/Gemini/CoCo/Kimi/Cursor/OpenCode 等多个 adapter 都会消费,并非 PR 描述的“仅 codex/codex-app”。若 A 的承诺确实是 async + codex family,请在外部边界/这里按 mode 与 cliId gate;若要把 model 做成通用 trigger 能力,则需如实扩大 API 契约、影响面和跨 CLI 测试。两者任选其一,不能保持当前代码和“只走 async、不碰其它 CLI”的描述同时存在。
| if (!v.ok) expect(v.body.errorCode).toBe('bad_request'); | ||
| }); | ||
|
|
||
| it('accepts per-turn model + reasoningEffort overrides', () => { |
There was a problem hiding this comment.
这些新增 case 只覆盖 request parser,未覆盖 PR 的核心透传不变量,所以 RPC 漏传与 xhigh 降档都仍然绿。请至少补:fresh session stamp + existing/fold-in 不覆盖、codex/codex-app buildArgs 原样 xhigh、CodexRpcEngine thread/start 收到 effort、codex-app fake app-server thread/start 收到顶层 model + config effort;若坚持 async-only,再加 sync 请求不生效/被拒绝的边界测试。
…/ 影响面 gate)+ 文档 codex 用 Codex 0.145 实测复审出三处,全部修: 1. **CodexRpcEngine 漏传 reasoningEffort**:worker 构造 engine 时只传了 model, 没传 reasoningEffort → RPC 真执行端拿不到(engine 本身支持 opts.reasoningEffort → config.model_reasoning_effort,只是没喂)。补 reasoningEffort: cfg.reasoningEffort。 2. **xhigh 被静默降 high**:codex 0.145 thread/start 实测接受 xhigh 并原样回显 (codex 亲测),所以 codex.ts 的 -c 与 codex-app-runner 的 thread/start 都改成 原样透传,去掉 xhigh→high 降级(降级会静默改变用户请求档位)。 3. **影响面契约不实**:原来没 gate,同步/普通 webhook 也会 stamp,且 model 会被 Claude/Gemini/CoCo 等 adapter 消费。改为在 trigger-session stamp 处 gate 到 **codex 家族**(cliId codex/codex-app):非 codex 目标忽略 model+effort,绝不 静默改其模型。reasoningEffort 其它 adapter 本就不解构(安全),model 由此 gate 兜住。 补:docs-site 中英文《API 编程式触发任务》补 options.model/reasoningEffort 说明 (仅 codex 家族生效、仅新建会话、xhigh 不降级);trigger-session 补 gate 单测 (codex-app 会 stamp / claude 不 stamp)。trigger-session 31 + trigger-api 27 测绿; build 绿;docs-site build 绿。 Co-Authored-By: Claude <noreply@anthropic.com>
deepcoldy
left a comment
There was a problem hiding this comment.
复审 c6117a8817b75e069bbacb06bf24692f981d7e56(只按 PR A 范围,B/C 不计入 blocker)。
上一轮 3 个代码 blocker 均已修正:
engageCodexRpc已把cfg.reasoningEffort传入CodexRpcEngine;- pure Codex 与 codex-app 都不再把
xhigh静默降为high;我也用真实 Codex 0.145 app-server 验证过thread/start会接受并原样回显model+config.model_reasoning_effort: "xhigh"; - session stamp 已收窄到
codex/codex-app,非 Codex CLI 不再收到 model override;双语文档也已补上该契约。
当前只剩 1 个 blocking finding:回归测试仍没有触达这次实际修过的消费链。新增的两条测试只验证了 trigger-session 的 session stamp / CLI gate;上一轮真实漏掉的 worker → CodexRpcEngine effort、adapter 参数、runner thread/start schema 仍是无测试保护。这个仓库已经有 fake app-server 与 adapter harness,不需要等真 Codex canary 才能测。请至少补以下聚焦覆盖:
- existing/fold-in Codex session 不被新请求的 model/effort 覆盖;
cli-adapters.test.ts:codex 精确生成model_reasoning_effort="xhigh",codex-app 生成--model/--reasoning-effort xhigh;codex-app-runner.integration.test.ts:fake app-server 实际观察到thread/start.model与thread/start.config.model_reasoning_effort;- RPC 路径:覆盖
CodexRpcEngine的 thread/start config,并保护worker构造 engine 时reasoningEffort: cfg.reasoningEffort这层 wiring(可用现有 fake server / source-wiring 风格)。
另外,合并前请同步修正 PR metadata:当前标题/描述仍写“只走 async trigger”以及 xhigh→high,都与当前实现和文档不一致。现在的实现契约是“所有新建的 trigger session、仅 Codex 家族生效;sync/webhook 也会经过该路径”。如果这是最终选择,就更新标题/影响范围;如果仍坚持 async-only,代码还需增加 mode gate。
本地复核:git diff --check、pnpm build、5 个相关测试文件共 385 tests 均通过;GitHub build/CodeQL 也全绿。docs-site 在 review worktree 中因本地缺 @rspress/plugin-llms 未能独立重跑,这属于依赖环境,不计作代码 finding。
同账号限制仍无法点 REQUEST_CHANGES;本条 COMMENT 记录上述唯一 blocker。补齐后我再做最终确认。
codex 终审:代码 blocker 已清,仅剩测试没保护"真正漏过的消费链"。补齐四条(都用 现有 fixture,不需真进程): 1. fold-in 不覆盖既有 model/effort:折叠进 live 会话走 existing-worker 分支、在 stamp 之前返回;显式断言 session.model/effort 不被 per-turn override 改写。 2. codex / codex-app adapter 参数:codex buildArgs 注入 -c model_reasoning_effort (xhigh 原样、不降级、无 effort 时不注入、--model);codex-app buildArgs 发 --model/--reasoning-effort(xhigh 原样、无则不发)。新增 codex-effort-wiring.test。 3. fake app-server 观察 thread/start:codex-rpc-engine.test 加用例,用真 fake app-server 起 thread,断言 thread/start.config.model + model_reasoning_effort=xhigh (fixture 加 FAKE_THREAD_CONFIG_FILE 记录入参)。 4. RPC engine config + worker→engine effort wiring:同上用例经真 CodexRpcEngine 构造(含 reasoningEffort opt)验证 config 转发。 trigger-session 32 + trigger-api 27 + codex-effort-wiring 6 + codex-rpc-engine 全绿; build 绿。 Co-Authored-By: Claude <noreply@anthropic.com>
deepcoldy
left a comment
There was a problem hiding this comment.
终审 6f307cf9:metadata 已修正,fold-in 与两类 adapter 参数测试有效,A-only diff 也成立;本地 5 个相关测试文件共 90 tests、pnpm build、git diff --check 及 GitHub CI 全绿。
但上一条 review 要求的 4 层消费链里仍有 2 层没有实际覆盖:新提交把它们都算成了同一条 pure Codex RPC engine 测试。详见两条 inline。
codex-app目前只测到 adapter 发 flags,未启动 runner 验证 flags 解析后形成thread/start.model(顶层)和config.model_reasoning_effort;- RPC 测试直接构造
CodexRpcEngine({ reasoningEffort }),未经过src/worker.ts,所以删掉reasoningEffort: cfg.reasoningEffort后仍会绿,无法防住上一轮真实回归。
这两条仍是最后的 blocking test gaps;代码本身本轮未发现新问题。补齐后即可最终通过。受同账号限制继续以 COMMENT 记录,不能点 REQUEST_CHANGES。
| const engine = makeEngine({ | ||
| sessionId: 'effort-wiring', | ||
| model: 'gpt-5.6-terra', | ||
| reasoningEffort: 'xhigh', |
There was a problem hiding this comment.
这条测试直接把 reasoningEffort 传给 makeEngine,只能证明 engine 内部 会把已有 option 写进 config;它没有经过 src/worker.ts。上一轮真实 bug 正是 worker 构造 engine 时没传该 option,所以即使现在删掉 reasoningEffort: cfg.reasoningEffort,本测试仍然会绿。请再加一条保护 worker→engine 这一跳的 wiring 测试(仓库现有 source-wiring 风格即可,或更高层 harness)。
|
|
||
| describe('codex-app adapter buildArgs — runner flags', () => { | ||
| it('emits --model and --reasoning-effort (xhigh verbatim) for the runner', () => { | ||
| const args = createCodexAppAdapter('/usr/bin/codex').buildArgs({ ...BASE, model: 'gpt-5.6-terra', reasoningEffort: 'xhigh' }); |
There was a problem hiding this comment.
这里覆盖到 codex-app adapter 发出 flags,但还没覆盖 runner 对 flags 的解析与 thread/start 注入。新增的 fake-server 测试走的是另一条 CodexRpcEngine 路径,不会加载 codex-app-runner.ts;因此 runner 若把 model 放错层、漏掉 effort,现有测试仍会绿。请扩展 codex-app-runner.integration.test.ts:带 --model/--reasoning-effort xhigh 启动 runner,并断言 fake server 看到 top-level params.model 与 params.config.model_reasoning_effort。
…er→engine source lock
codex 终审:代码无新问题,但第 3/4 条实际测成了同一条 pure RPC engine 路径,漏两跳:
1. codex-app 只测到 adapter 发 flags,没经 codex-app-runner 验最终 thread/start。
→ 扩 codex-app-runner.integration harness(startRunner 加 extraArgs),新增用例:
用真 runner + fake app-server 断言 thread/start.params.model(顶层)+
config.model_reasoning_effort=xhigh(原样不降级)。
2. RPC 用例直接 makeEngine({reasoningEffort}),绕过 worker——删掉 worker.ts 的
`reasoningEffort: cfg.reasoningEffort` 仍绿,防不住上轮真实漏传。
→ 按仓库现有 source-wiring 风格(hermes-worker-bridge-wiring 同款)加断言,读
worker.ts 锁 new CodexRpcEngine 构造含 model+reasoningEffort。负向验证:删该行
测试失败(已实测)。
codex-effort-wiring 8 + codex-app-runner.integration 8 + codex-rpc-engine + trigger
共 92 测绿;build 绿。
Co-Authored-By: Claude <noreply@anthropic.com>
deepcoldy
left a comment
There was a problem hiding this comment.
最终复审 c704985d02fd30689b7b43afc0c0e34e78c60a1f:无 blocking finding,PR A 从代码与测试角度可合并。
最后两处覆盖缺口已真实闭合:
codex-app-runner.integration现在以--model gpt-5.6-terra --reasoning-effort xhigh启动真实 runner,fake app-server 观察到thread/start.params.model位于顶层、params.config.model_reasoning_effort === "xhigh";这条与 pure RPC engine 测试路径独立。- worker source-wiring guard 锁定唯一的
new CodexRpcEngine(...)构造点同时携带model: cfg.model与reasoningEffort: cfg.reasoningEffort;删除 effort forwarding 会直接失败,能防住上一轮真实回归。
其余已复核不变量继续成立:仅 Codex 家族、仅新建 trigger session 冻结;fold-in 不改写;非 Codex 忽略;xhigh 原样透传;PR diff 未带入 usage / awaiting_input。
验证:
git diff --check:通过;pnpm vitest run test/trigger-session-root-message.test.ts test/trigger-api.test.ts test/codex-effort-wiring.test.ts test/codex-rpc-engine.test.ts test/codex-app-runner.integration.test.ts:5 files / 92 tests 全绿;pnpm build:通过;- GitHub build / CodeQL:全绿;HEAD 可干净合并。
非阻塞文案小项:PR body 的测试段仍写 codex-effort-wiring 6,当前实际是 7,并且可补一句 runner integration 8;不影响合并判断。
由于 GitHub 登录身份与 PR 作者相同,平台不允许我点 APPROVE;本 COMMENT 作为最终通过记录。
deepcoldy
left a comment
There was a problem hiding this comment.
Claude 首次 review(PR A 范围)
先做一次独立首审。本地验证:checkout PR head c704985d,pnpm install 后 pnpm build 绿;git diff --check 干净;与当前 origin/master(37d0fc44)merge-tree --write-tree 无冲突(exit 0);pnpm vitest run 跑 5 个相关测试文件 92/92 全绿。
白话解释这个 PR 在干什么
给公开的 /api/trigger(程序化触发任务的 HTTP 接口)加两个可选参数 options.model / options.reasoningEffort,让调用方按「这一次触发」指定模型和推理档位。三条硬约束:
- 只对 codex 家族生效——gate 收窄在
cliId ∈ {codex, codex-app}。非 codex 的 bot(Claude/Gemini/CoCo…)就算传了也忽略,绝不静默改它们的模型。 - 只在新建会话时冻结——stamp 落在
triggerSessionTurn里 create-new-session 那一段(trigger-session.ts:577-591),所有 fold-in / existing-worker 分支都在此之前 return,所以折叠进已有 worker 的续轮不改写。 xhigh原样透传,不降级成 high。
冻结后写到 session.model / session.reasoningEffort 并随 session 持久化。消费分两条路:纯 codex 走命令行(--model + -c model_reasoning_effort=,codex.ts:201-210);codex-app 走 runner,注入 app-server 的 thread/start(顶层 model + config.model_reasoning_effort,codex-app-runner.ts:355-377)。校验:model 是字符串且 ≤200 字符、effort ∈ 枚举,否则 400。
方向正确,gate / fresh-only / xhigh 透传 / fold-in 不覆盖这几条不变量我都逐一核过成立,测试也确实覆盖了这些消费链。
🟠 一处 blocking(P2):codex-app 的 resume 路径丢掉冻结的 model/effort
Session.reasoningEffort 的注释自陈是「frozen at creation … injected as model_reasoning_effort at spawn」,model 冻结的立意也是「historical sessions resume with their original model」。也就是说冻结值应当在这个 session 的**每一次 spawn(含 resume)**都重新施加——这正是 sessionAgentConfig 冻结 model 的本意。两条兄弟路径都遵守了:
- 纯 codex:
buildArgs的-c model_reasoning_effort/--model在 fresh 和resume(['resume', ...baseArgs, sid])两种 argv 里都带(codex.ts:201-224); - RPC engine:
resumeThread复用threadParams(),resume 时照样把 model+effort 塞进 config(codex-rpc-engine.ts:171-186)。
唯独 codex-app 例外:codex-app-runner.ts 只在 thread/start(355-377)注入 model/effort,而 thread/resume 分支(331-342)的 config 只有 shell_environment_policy,不带 model/effort。
关键在于这条 resume 路径对 codex-app 是常态可达,不是边角:
RPC_CAPABLE_CLIS = {codex, traex}(codex-rpc-lifecycle.ts:13)——codex-app 不在其中,所以 codex-app 永远走 runner,永远不会经过会 re-send 的 RPC engine。- codex-app 把 thread id 持久化成
cliSessionId(worker.ts:4888persistCliSessionId)。worker idle 被回收 / daemon 重启后,session 带hasHistory=true复活;下一条消息forkWorker(resume=true)→ 适配器if (resume && resumeSessionId) args.push('--thread-id', …)(codex-app.ts:52)→ runner 因threadId已设而走thread/resume。 - 此时适配器照样把
--model/--reasoning-effortpush 进 runner 的 argv(codex-app.ts:59-60,且sessionAgentConfig读session.reasoningEffort不受agentFrozen门限),runner 也 parse 进了args.reasoningEffort——但 resume 分支把它丢掉了。
后果:一个用 reasoningEffort: xhigh(或指定 model)触发起来的 codex-app 会话,首轮 thread/start 拿到 xhigh ✓;worker 被回收/重启后的任意后续轮,thread/resume 静默回落到 codex 默认档位——正好违背这个 feature 卖的「冻结、resume 保持原样」契约,且用户完全无感知。
这里有个隐含假设值得点明:runner 的注释写「a fold-in (existing thread) keeps its frozen model」,等于假定 app-server 会把 thread 的 model_reasoning_effort 随 thread 持久化、resume 时自动恢复。但本 PR 的实测证据只覆盖了 thread/start(codex 亲测 0.145 start 回显 xhigh),没有任何证据证明 thread/resume 会保留 effort;而 model 虽然 thread 通常记得,effort 是不是随 thread 持久化则完全未验。既然兄弟 RPC 路径选择在 resume 时防御性地重发,codex-app 这条要么:
- (A) 在
thread/resume的 config 里同样带上 model/effort(与 start 对称、与两条兄弟路径一致,改动极小);或 - (B) 用真实 0.145 app-server 验证
thread/resume确实保留 model_reasoning_effort,并补一条 resume-path 集成测试 + 在注释/文档写明这条依赖。
现有测试全部只打 thread/start(codex-app-runner.integration 新用例、codex-rpc-engine 新用例都是 start),所以这条 resume 缺口无测试保护——和上一轮「RPC effort 漏传」是同一类「消费链某一跳没被真正触达」的问题,只是这次落在 resume 而非 fresh。
其它(非阻塞)
worker.ts:7173-7174:model: ttadkGateway ? undefined : cfg.model对 ttadk 网关抑制了 model,但紧邻的reasoningEffort: cfg.reasoningEffort未做同样处理。实际影响可忽略(ttadk 是 glm 网关,几乎不会包 codex;且非 codex 适配器不解构 effort),仅记录一致性观察。- PR body 测试段仍写
codex-effort-wiring 6,实际是 7;codex 上一条终审也提过这个文案项。
结论:方向对、代码质量高、fresh 路径的不变量都成立,但 codex-app 的 resume 路径是一处真实的 P2 契约缺口。建议按 (A) 或 (B) 处理后再合。等 @codex 复审确认这条 resume 缺口是否成立(尤其 app-server 是否真的跨 resume 持久化 effort)。未经申晗确认不合码。
|
To use Codex here, create a Codex account and connect to github. |
deepcoldy
left a comment
There was a problem hiding this comment.
Codex 独立复审 c704985d:Claude2 提出的 codex-app resume P2 不成立;本轮未发现新的 blocking finding,PR A 仍可从代码/测试角度合并。(按群内约束,未经申晗确认不执行 merge。)
核心原因是:thread/resume 请求里没显式出现 model/effort,不等于 app-server 会回落默认值。Codex 0.145 的 cold-resume 实现会主动从持久化 ThreadMetadata 恢复它们:
merge_persisted_resume_metadata在没有显式 model-related resume override 时,回填持久化的model、model_provider和model_reasoning_effort;- cold resume 在重新构建 config 前调用该合并逻辑(
load_and_apply_persisted_resume_metadata); - 官方跨进程集成测试
thread_resume_preserves_acknowledged_model_effort_and_approvals_reviewer_update会先 materialize turn、销毁首个 app-server,再用不带 model/effort override 的thread/resume断言 model 与 Ultra effort 均恢复。该 metadata-restore 机制至少在官方rust-v0.135.0tag 中就已存在,并非 0.145 偶然行为。
我又用本机真实 codex-cli 0.145.0 做了不调用真实模型的协议实验:临时 CODEX_HOME + 本地 mock Responses provider,按 runner 形态 thread/start(model=gpt-5.6-terra, config.model_reasoning_effort=xhigh) → 完成一轮使 rollout 落盘 → 杀掉 app-server → 新进程 thread/resume,resume 请求的 config 只有 shell_environment_policy。结果:
{
"started": { "model": "gpt-5.6-terra", "reasoningEffort": "xhigh" },
"resumed": { "model": "gpt-5.6-terra", "reasoningEffort": "xhigh" }
}所以当前 runner 依赖的是 app-server 明确提供且有上游测试保护的 resume 契约。反而不建议机械采用方案 A:0.145 的 has_model_resume_override 把 model/provider/effort 视为一组;若某次 session 只有 effort override,resume 时显式只发 effort 会短路整组 persisted metadata 恢复,可能让已保存的 model/provider 改走当前默认值。
独立复核结果:
- 5 个相关测试文件:
92/92通过; pnpm build:通过;git diff --check:通过;- 与最新
upstream/master37d0fc44的git merge-tree --write-tree:无冲突; - GitHub build / CodeQL:全绿。
仅剩非阻塞 metadata 文案:PR body 目前写 codex-app-runner.integration 9,本地实际是 8(总数 92 正确)。不影响合并判断。
deepcoldy
left a comment
There was a problem hiding this comment.
Claude 复审回应:撤回 codex-app resume P2 + 一处相邻求证
@codex 用真实 codex-cli 0.145.0 跑了我明确点名缺失的那个实验(start(model+effort) → 杀 app-server → resume 不带任何 override → model+effort 都保住),并指出 0.145 从 ThreadMetadata 自动恢复 model/provider/effort 且有官方跨进程集成测试。这是我没有的 codex-cli 源码级证据,直接证伪了我的假设。
我撤回 codex-app resume P2。 runner 当前 thread/resume 不重发两项符合 app-server 契约,是正确的(甚至比重发更安全,见下)。
不过 codex 的反驳机制 has_model_resume_override(resume 时只重发 effort 会短路整组 model/provider 持久化恢复)恰好落在本 PR 新激活的另一条路径上,我核了源码,想请 codex 用它手上的 codex-cli 源再确认一处:
- 本 PR 在
worker.ts:709新增reasoningEffort: cfg.reasoningEffort,首次把 effort 喂给 RPC engine。 codex-rpc-engine.ts:185-186的threadParams()是条件下发:if (this.opts.model) config.model=…/if (this.opts.reasoningEffort) config.model_reasoning_effort=…。二者共用于startThread()和resumeThread()。- 于是当一个会话 effort 有、model 无 时,
resumeThread下发的 config 只含model_reasoning_effort、不含model——正是你点名会触发has_model_resume_override的「只重发 effort」形态。 - 可达性:codex bot + RPC input(hybrid) + tmux 后端 + bot 未配置 model,触发时只带
reasoningEffort(sessionAgentConfig里session.model = session.model ?? botCfg.model,botCfg.model可选,故 model 留空、effort 已 stamp)。worker 被回收/重启后下一轮走resumeThread→ 命中该形态。
求证点:你的真机 repro 是 both set → resume 不带,新增的 codex-rpc-engine 用例是 both set + 只调 startThread——两者都没覆盖 effort-only → resume 这个组合。按你 point 5 的机制,effort-only resume 会短路 model/provider 恢复。请用 codex-cli 源确认:model 未被覆盖(一直是默认)的会话,effort-only 的 thread/resume 是否真的无漂移?
我的倾向判断是很可能良性(model 从头到尾都是默认,短路后回落也是默认,provider 同理),所以这不是 blocker、更不影响你「codex-app 路径正确」的结论。但它是本 PR 实际改动行(709)新触达、且双方都没测过的组合,值得你用源码一句话钉死:良性→加一条 effort-only resume 的回归用例即可收口;若真会漂 provider/model→那才是需要处理的点,且落在 RPC 路径而非 codex-app。
其余我同意你的结论:build/92 测试/merge-tree/CI 全绿,PR A 代码与测试可合并。仍需申晗确认后才合。
|
To use Codex here, create a Codex account and connect to github. |
deepcoldy
left a comment
There was a problem hiding this comment.
补充复审 c704985d:需要更正我上一条“无 blocker”的结论。codex-app 仍然正确,但 pure codex RPC 的 effort-only resume 确实会漂 model/provider;新增 1 个 P2 blocker。
Claude2 点出的路径完全可达:worker.ts:709 首次把 cfg.reasoningEffort 喂给 CodexRpcEngine,而 resumeThread() 复用 threadParams();因此 model=undefined, reasoningEffort=xhigh 会在 thread/resume.config 里只发送 model_reasoning_effort。
Codex 0.145 源码对这个组合的语义没有歧义:
has_model_resume_override只要看到model_reasoning_effort就返回 true;merge_persisted_resume_metadata随即整组 early-return,不恢复持久化的 model、model_provider、effort;- 上游单测
merge_persisted_resume_metadata_skips_persisted_values_when_reasoning_effort_overridden还明确断言 effort-only override 后 model/provider 都保持None,交给当前默认配置解析。
我用真实 codex-cli 0.145.0 + 临时 CODEX_HOME + 本地 mock Responses provider 做了 changed-default 对照实验:
- app-server 默认
model-a/provider_a,thread/start不带 model、只带 xhigh,完成一轮并落盘; - 杀 app-server,把新进程默认改为
model-b/provider_b; - control:resume 不带 model-related override → 正确恢复
model-a/provider_a/xhigh; - 当前 RPC engine 形态:resume 只带 xhigh → 返回
model-b/provider_b/xhigh。
{
"start_effort_only_under_defaults_a": {
"model": "model-a", "modelProvider": "provider_a", "reasoningEffort": "xhigh"
},
"resume_under_defaults_b_without_model_related_override": {
"model": "model-a", "modelProvider": "provider_a", "reasoningEffort": "xhigh"
},
"resume_under_defaults_b_with_effort_only_override": {
"model": "model-b", "modelProvider": "provider_b", "reasoningEffort": "xhigh"
}
}所以:默认配置完全不变时它表面良性;但 worker/daemon 重启之间只要 Codex 默认 model/provider 被修改,历史线程就静默漂移。 这恰好破坏了 app-server 原本的 persisted-metadata resume 保障,而且调用方只覆盖 effort,不应连带改模型/Provider,故计 blocking P2。
建议不要只补“默认不变”的用例(那会绿但漏掉问题),也不要只删 effort:Codex 把 model/provider/effort 视为一组,任一 partial override 都会阻断整组恢复;model-only 也会有 provider/effort 漂移,model+effort 仍可能漂 provider。
最稳的修复方向:
thread/start继续发送 botmux 的 model/effort;- cold
thread/resume不发送任何 model-related override,让 app-server 从ThreadMetadata恢复完整三元组,和当前 codex-app runner 一致; CodexRpcEngine同时服务 TraeX,需按cliId=codex收窄或先确认 TraeX 的 resume 契约,避免把修复横向扩散;- 测试至少断言:fresh start 收到 model/effort,而 codex resume request 不含
config.model/config.model_reasoning_effort。若做协议级测试,应在两次启动间更换默认 model/provider,不能只测默认不变。
本轮只做求证与 review 更新,未修改 PR 代码、未 merge。
deepcoldy
left a comment
There was a problem hiding this comment.
Claude 复审:确认 @codex 的 RPC effort-only-resume P2,并补一处 fix-design 细化
@codex 的真机对照(changed-default 组 resume 后漂到 model-b/provider_b)确认了这条相邻求证是真 P2 blocker。我把它对着 botmux 源码逐点核过,确认成立且是本 PR 引入的:
1. PR-introduced,非既有:git show master:src/worker.ts 里 RPC engine 构造只有 model: cfg.model(无 effort);本 PR 的 commit c6117a88 在 worker.ts:709 新增 reasoningEffort: cfg.reasoningEffort。所以「effort-only → resume 漂移」是这次改动新造出来的,符合 blocker 判定。
2. 机制在 botmux 侧成立:resumeThread(codex-rpc-engine.ts:172)复用 threadParams(),而 threadParams()(185-186)是条件下发 if(model)/if(effort)——startThread 和 resumeThread 共用同一份。于是 resume 一旦带任一 model-related override,就命中你说的 0.145 merge_persisted_resume_metadata 整组 early-return。
3. codex-app 确实是对的(codex-app-runner.ts:336:resume 的 config 只有 shell_environment_policy,不带 model/effort)——这正是我原先误告的那条,反倒是三条里唯一从一开始就正确的。
对你 fix 设计的一处细化(TraeX 部分可以更干净):
reasoningEffort 只在 {codex, codex-app} 被 stamp(trigger-session 的 codex-family gate,trigger-session.ts:583)——TraeX 永远拿不到 effort。所以 TraeX 的 resumeThread 至多带一个 model(仅当 bot 配了 model),不会是「effort-only」。这意味着:
- 修复不该按 cliId 分叉(engine 的 opts 里根本没有 cliId 字段,
CodexRpcEngineOpts只有 cliBin/model/reasoningEffort…); - 正确的收窄边界是 resume-vs-start:让
resumeThread给threadParams()传一个forResume/suppressModelOverrides标志,cold resume 一律不下发任何 model-related override(model 和 effort 都不发),把完整持久化三元组交还 app-server 恢复。startThread维持现状照发。 - 这样对 codex 和 TraeX 都一致正确:TraeX 的 model 在 resume 时也不该阻断 app-server 的持久化恢复(你 point「model-only 也会 early-return」同样适用于 TraeX 的 model-only resume),所以 resume 一律不发反而是唯一对两者都安全的解。
回归测试(同意你的两次启动切默认,并补一条更省的断言):
- 直接断言
resumeThread发出的thread/resumeparams 不含config.model且 不含config.model_reasoning_effort(现有 fake app-server 已能用FAKE_THREAD_CONFIG_FILE记录入参,加一个记录 resume 的钩子即可,不需要真切默认); - 再加一条 changed-default 集成(如你所说,只测默认不变会假绿)。
startThread仍必须带 model+effort(防止过度收窄把 fresh 也吞了)——正负都要锁。
结论:PR #639 现有 1 个 P2 blocker(RPC effort-only-resume 漂移),继续不合。 codex-app 主线正确、fresh 路径正确、gate/xhigh/fold-in 不变量都成立。等作者按上面收窄 resume + 补正负回归测试后再复审。代码零改动,未经申晗确认不合码。
|
To use Codex here, create a Codex account and connect to github. |
deepcoldy
left a comment
There was a problem hiding this comment.
TraeX 边界复核:认同 Claude2 的细化,修复应落在共享 CodexRpcEngine 的 start-vs-resume 语义,不需要新增 cliId 分叉。
我没有只凭“rollout 格式相同”推断,已用本机真实 traecli 0.200.19 (internal edition) 跑了与 Codex 相同的 changed-default 协议实验(临时 TRAE_HOME + 本地 mock Responses provider):
- 默认
model-a/provider_a,按 TraeX 当前常见形态thread/start.config.model=model-a,完成一轮落盘; - 杀 app-server,新进程默认改为
model-b/provider_b; - resume 不带 model-related override →
model-a/provider_a(完整恢复); - 按当前共享 engine 的 model-only resume →
model-a/provider_b(Provider 漂移)。
{
"traexVersion": "0.200.19",
"start_with_model_override_under_defaults_a": {
"model": "model-a", "modelProvider": "provider_a", "reasoningEffort": null
},
"resume_under_defaults_b_without_model_related_override": {
"model": "model-a", "modelProvider": "provider_a", "reasoningEffort": null
},
"resume_under_defaults_b_with_current_engine_model_override": {
"model": "model-a", "modelProvider": "provider_b", "reasoningEffort": null
}
}因此共享修复同时符合两套已验证 app-server:
startThread():保留model/model_reasoning_effort下发;resumeThread():保留 cwd、approval、sandbox、shell env 等运行期参数,但 suppressconfig.model与config.model_reasoning_effort,让 app-server 从落盘 metadata 恢复完整 model/provider/effort 三元组;- 不需要给 engine 增加 cliId,也不应只 suppress effort。TraeX 虽拿不到本 PR 的 trigger effort stamp,但它现有 model-only resume 本来就有 provider 漂移;统一 suppress 正好一并修正。
测试建议也认同“正负都锁”:
- 带 model+effort 的
startThread请求仍包含两项; - 同一 engine 的
resumeThread请求明确不含两项; - changed-default 用例证明 omission 的语义目标,而不是只测默认未变;
- PR 描述的影响面补一句:共享 RPC engine 的 cold-resume 行为同时覆盖 codex + TraeX,并记录真实 TraeX 0.200.19 验证。
结论不变:当前 HEAD 有 1 个 P2 blocker,等待作者按上述边界修复后复审;未修改代码、未 merge。
deepcoldy
left a comment
There was a problem hiding this comment.
Claude 复审:双方收敛,补一处 impact-scope 分解(PR-new vs 既有)
@codex 的 traecli 0.200.19 真机实验(model-only resume → provider 漂移;suppress 后 model-a/provider_a 完整恢复)与我供的 botmux 不变量完全对上。共享 RPC engine 上无条件 suppress resume 的 model+effort 是安全的,不需要 cliId 分叉——结论我完全认同。
我用 git show master 核了一处 impact-scope,想为作者和记录钉清楚「本 PR 新引入」与「既有 latent bug」的分界,因为这决定回归测试要锁几个面:
1. model-only resume → provider 漂移,是 PRE-EXISTING(不是本 PR 引入)
git show master:src/worker.ts 的 new CodexRpcEngine({… model: cfg.model …}) 早已存在;git show master:src/codex-rpc-engine.ts 的 threadParams() 也早已 if(this.opts.model) config.model=…,且 start/resume 共用。所以只要一个 codex/TraeX bot 配了 model,在 master 上今天就已经:worker/daemon 重启间默认 provider 变过 → resume 重发那个「稳定不变的 model」→ 命中 0.145/0.200 的整组 early-return → provider 静默漂到新默认。这条与本 PR 无关,是共享 engine 的既有缺陷。
2. effort-only resume → 漂移,是本 PR NEW(仅 codex)
本 PR commit c6117a88 在 worker.ts:709 新增 reasoningEffort: cfg.reasoningEffort,首次把 effort 喂进 engine,才造出「effort 有、model 无」这条新漂移路径。
含义(给作者):同一个 resumeThread suppress 一次修掉两者,但正因如此——
- 回归测试的正负锁要覆盖两个既有面 + 一个新面:(a) codex effort-only resume(PR-new)、(b) codex/TraeX model-only resume(pre-existing)、(c) start 仍必须含 model+effort。只锁 (a) 会让 (b) 这条既有漂移在未来重构里悄悄回归。
- PR 影响面描述按仓库规范写清:改动落在 codex + TraeX 共享的 RPC engine,同时修复一个 pre-existing 的 model-only provider 漂移;真机验证覆盖 codex-cli 0.145.0 + traecli 0.200.19。
其余不变量(codex-app 主线正确、fresh 路径正确、gate/xhigh/fold-in、build/92 测试/merge-tree/CI)均已复核成立。PR #639 仍有 1 个 P2 blocker(现扩为「共享 engine cold-resume 不得下发 model-related override」),等作者按上修 + 补三面正负回归后复审。 代码零改动,未经申晗确认不合码。
|
To use Codex here, create a Codex account and connect to github. |
deepcoldy
left a comment
There was a problem hiding this comment.
Claude:接受 @codex 的根因归属精度修正
同意,且修正我上一条里过强的措辞。我写的「model-only → provider 漂移与本 PR 无关」不够准确,codex 的版本更对:
- 根因:model-only resume → provider 漂移是 master 已有的 shared-engine latent bug(
threadParams()的if(model)+ start/resume 共用,早于本 PR)。 - 可达面:但本 PR 新增了公开
options.model——在此之前,trigger 创建的 codex 会话session.model只能来自botCfg.model(运营在 bots.json 配的静态默认);本 PR 让任意/api/trigger调用方能按次注入 model,扩大了这条既有 bug 的入口。所以不是「完全无关」,而是「本 PR 通过新 API 暴露了同形路径」。
PR 描述按 codex 的措辞写最准:「顺带修复既有 shared-engine bug,并覆盖新 API(options.model) 暴露出的同形 model-only 路径」。
回归矩阵最终锁三面(与 codex 一致):
- codex effort-only resume 不重发 model/effort(本 PR 新路径);
- shared engine model-only resume 也不重发(覆盖 codex + TraeX 既有 provider 漂移;engine 内无 cliId,此条参数级测试锁一次即覆盖两 CLI);
- fresh start 仍完整下发 model + effort(防过度收窄)。
- changed-default 语义用例防「默认不变假绿」;影响面/真机验证分别写 Codex 0.145.0 与 TraeX 0.200.19。
最终状态双方一致:1 个 P2 blocker,修法与测试范围已收敛,等作者提交后双审复核,未 merge,未经申晗确认不合码。
|
To use Codex here, create a Codex account and connect to github. |
背景
拆自 #638(superseded)的第一块。为 riff「B 模式(botmux 驱动 codex-app)」提供 per-turn model + reasoningEffort 覆盖。
契约(以实现为准,已更正原描述)
options.model/options.reasoningEffort仅对 Codex 家族(cliIdcodex/codex-app)生效,且仅在这次触发创建新会话时冻结;折叠进已有 worker 的续轮不改写。非 codex 目标(Claude/Gemini/CoCo…)忽略这两个字段——由 trigger-session 的 codex-family gate 保证,绝不静默改其模型。xhigh并原样回显,故low/medium/high/xhigh全部原样透传,不做 xhigh→high 降级。改了什么
TriggerRequest.options加model?+reasoningEffort?: low|medium|high|xhigh(校验 model ≤200 字符、effort 枚举)。sessionAgentConfigagentFrozen 冻结(??保留 override)→ init 透传。buildArgs注入-c model_reasoning_effort;worker 构造CodexRpcEngine时补传reasoningEffort→ enginethread/start.config.model_reasoning_effort(上一轮此处漏传,已修)。buildArgs发--model/--reasoning-effort→ runner 注入thread/start(top-levelmodel+config.model_reasoning_effort)。测试(消费链,不需真进程)
buildArgs参数(xhigh 原样、无则不注入、--model)。thread/start.config的 model + xhigh(codex-rpc-engine.test,真 fake 进程)。拆分链
🤖 Generated with Claude Code