fix(worker-pool): 模型不再冻结进会话,每次启动跟随 bot 配置 - #773
Conversation
全量测试结果(本机 macOS,node 24)两个失败都与本改动无关,逐个核过:
|
deepcoldy
left a comment
There was a problem hiding this comment.
复审结论:Request Changes。当前改动覆盖了 daemon refork/restore,但仍有 3 条可达的模型状态转换没有闭合:
src/core/worker-pool.ts:950:活 worker 的/restart/ dashboard restart / CLI crash auto-restart 不会重新解析 model。daemon 的 restart IPC 只携带最新 env,worker 随后用旧lastInitConfigrespawn,因此同 CLI 修改 model 后,物理重启仍继续用旧模型。这与“每次启动(含 resume)按当前配置解析”的核心目标直接冲突。src/daemon.ts:4583:Codex App 完成通知接管只清理session.model,没有清理新迁移到内存态的spawnModelOverride。解析规则 1 无条件优先,导致 trigger 的一次性 model 覆盖泄漏到接管后的 codex-app 启动;改动前该值位于session.model,会被这里正确清掉。src/core/session-model.ts:51:PR 后创建的会话不再写session.model,所以 CLI mismatch 时的规则 3 对新会话恒为 undefined。该路径不只来自手改配置:/botconfig set cli与配置卡通过applyConfigField热更新bot.config.cliId,但没有 dashboard PUT 的closeCliMismatchedSessionsForBotsweep;旧会话冷停/崩溃后 refork 会丢掉原显式 model,多个依赖启动参数的适配器会退回 CLI 默认。
建议补齐:
- restart IPC 增加 model 的三态刷新通道(或等效地在每次 worker 内 respawn 前重新解析),覆盖所有 restart 生产者和合并分支;
- notifier takeover 明确清除
spawnModelOverride; - 对齐所有 CLI 热切入口的 mismatch sweep,或继续维护一个只用于 mismatch 兜底的历史 model 记录;并为上述状态转换补行为测试。
本地验证:
pnpm build✅pnpm exec vitest run test/session-launch-model.test.ts:8/8 ✅pnpm exec vitest run test/session-lifecycle-start.test.ts:77/77 ✅pnpm exec vitest run test/codex-notifier-adopt-race.test.ts:20/20 ✅pnpm exec vitest run test/codex-notifier-adoption-wiring.test.ts:1/1 ✅
现有测试全绿,但没有覆盖以上三条状态转换。
|
三条都已修,推在 1|活 worker 的物理重启不重新解析模型 —— 已修确认成立,而且不止 修法直接复用仓库里 per-bot
2|Codex App 接管没清
|
deepcoldy
left a comment
There was a problem hiding this comment.
结论:仍需修改。73a32a8a 已补上普通 live-worker restart 的 model 携带、notifier 接管清理 override,但还有三条可达缺口:
-
live-worker respawn 没有同步 rule 3 的历史记录。
sessionAgentConfig()只在 daemon refork 时回写session.model;四个 restart 生产者只把最新 model 发给 worker,worker 也只更新lastInitConfig.model。因此会话先以 A 启动,bot 改成 B 后 live/restart确实启动 B,但持久记录仍是 A。随后通过/botconfig set cli/ 配置卡热切 CLI(这些路径没有 mismatch sweep),下一次/restart的 CLI-mismatch 规则会从旧记录取回 A,刚切到 B 的旧 CLI 会话又退回 A。新增测试只断言了 IPC 的model: B,没有断言成功 respawn 后历史记录与 B 收敛。请补完整状态转换及 A→B live restart→切 CLI→仍保持 B 的回归测试;一次性spawnModelOverride仍不得落盘。 -
crash-loop park 后的消息触发重试仍读旧 model。 第四次 crash 后 worker 被保留在
crashDiagnosticStopped;用户此时修改 bot model,再发下一条消息,worker.ts直接用{...lastInitConfig, resume:true}调spawnCli,daemon 没有 restart IPC 可携最新 model。这条是现有的正式恢复路径,也违反“每次 spawn 跟随 live 配置”。需要给该 retry 路径补刷新通道与测试。 -
fork 丢失了新定义下必需的 mismatch 历史。 当前
forkSession明确不复制session.model。当源会话的冻结 CLI 已与 bot 当前 CLI 不同,源会话靠 rule 3 保留其历史 model;child 继承旧cliId却没有历史 model,首个 fork spawn 会落到 undefined/default。新 resolver 在 CLI 匹配时始终让 live bot model 优先,因此复制这条历史记录不会复活旧冻结 bug;反而是 mismatch fork 所必需。请补“bot 热切 CLI 后 fork 旧会话仍沿用其最后模型”的测试。
另外,当前 head 与 origin/master@725d7e47e 的 merge-tree 有 6 个冲突,且不全是字段并集:
- restart IPC 要保留 master 的
reason和本 PR 的model;dashboard operator 发送者需同时带reason:'operator'+ 最新 model,crash sender同理保留reason:'cli_crash'。 trigger-session.ts必须把本 PR 的 in-memoryspawnModelOverride语义移植进 master 新增的原子 first-owner claim,不能退回持久化session.model。fork-session冲突需保留 master 的cliRuntime深拷贝;在上述第 3 点修正后,同时保留历史 model 与 runtime override 的正确分层。
Codex App 同 CLI 的通知接管边界本身不是问题:runner 的 thread/resume 明确不发送 model,恢复线程持久化的 model/provider/effort;只有 resume 失败转 fresh thread/start 时才使用 bot 的 model,这与同 CLI 配置一致。
本地验证:pnpm build 通过;restart-live-worker-model 14、session-lifecycle-start 78、crash-loop-diagnostic 3、restart-worker-null-reattach 10、codex-notifier-adopt-race 20、fork-session 18 均隔离通过。上述缺口是现有测试未覆盖的状态序列。
会话创建时会把 bot 的 model 冻结进 session 记录,此后每次 resume 都显式 `--model <冻结值>` 启动,导致 dashboard 里配置的模型对**存量长会话永久失效**: 冻结值只是「建会话那一刻继承来的默认」,却压过了之后人为做出的显式配置, 优先级正好反了。 改为:model 在**每次 spawn(含 resume / 物理重启)时按 live bot 配置解析**,不再 进冻结集合。`cliId` / `cliRuntime` / `cliPathOverride` / `wrapperCli` 的冻结**保持 不变**——那几个被中途换掉会真丢能力(`ttadk codex` wrapper 掉成裸 codex 会丢网关), 而 model 是人主动配的、本就该生效。也不做数据迁移:存量记录留在原地、语义改为 「上次实际启动用的模型」。 解析优先级集中在新增的 `resolveSessionLaunchModel()`(core/session-model.ts): 1. `DaemonSession.spawnModelOverride` — 显式的 per-trigger 覆盖(trigger API `options.model`,仅 codex 家族),**只驻内存**; 2. live bot 配置,**仅当会话冻结的 cliId 与 bot 当前 cliId 一致**——被钉在别的 CLI 上的会话(bot 后来换了 CLI、或 Codex App 线程接管把 cliId 钉成 codex-app)不能 被塞进属于另一个 CLI 的 model 串; 3. 会话记录的「上次实际启动用的模型」,仅兜底上面那种 CLI 不匹配的情况。 (`/botconfig set cli` 与配置卡热切 cliId 时没有 dashboard PUT 那条 closeCliMismatchedSessionsForBot 清扫,这类会话确实能活到下一次 refork。) 覆盖全部启动路径: - daemon refork / restore:`sessionAgentConfig` 解析并回写记录; - 活 worker 的物理重启(`/restart`、dashboard 重启按钮、dashboard cwd-move、 CLI 崩溃 auto-restart):restart IPC 捎带最新 model,沿用 per-bot env 已有的 三分态(字符串=用它 / null=当前不该传 / undefined=取不到就保持快照),worker 在 **合并守卫之前**覆盖 `lastInitConfig.model`。 trigger API 的 `options.model` 顺带修正为名副其实的 per-turn:以前写进持久字段 `session.model`,一次性调用会变成永久覆盖(文档写的是「仅新建会话生效」),现在落在 内存态 `spawnModelOverride`;Codex App 线程接管会显式清掉它。 `options.reasoningEffort` 行为不变(仍随会话持久化)。 影响面 - 会话类型:普通话题会话、chat 会话、trigger/HTTP 会话、fork 子会话、restore 冷恢复都走同一个 `sessionAgentConfig`;adopt 只观察不 spawn,不受影响。 - 跨 CLI:所有适配器统一从 init 消息拿 model,规则同一条;ttadk wrapper 的 `-m` 取值同步改成 live 配置(关闭卡里给出的 resume 命令原本会退化成 ttadk 内置默认 模型,而不是 bot 配的那个)。 - 展示面:关闭卡 resume 命令、本地终端打开命令都改用同一解析函数,与实际启动一致。 验证 - `pnpm build` / `tsc --noEmit` 通过。 - 新增 `test/session-launch-model.test.ts`(8 例)锁优先级; `test/restart-live-worker-model.test.ts`(14 例)锁 restart 三分态、四个生产者 wiring、worker 侧 merge 位置与 null 语义、接管清覆盖; `session-lifecycle-start` / `closed-session-card` / `fork-session` / `trigger-session-root-message` 各有新增或改写用例。 - 变异验证:加回冻结行 / 解析改回 `session.model ?? botCfg.model` / trigger 不写 内存字段 / 关闭卡改回读 `session.model` / restart 不带 model / worker 不 merge / 接管不清覆盖 / 去掉启动记录回写,八种变异分别让对应用例转红。
复审第二轮指出三条仍未闭合的状态转换,逐条修:
1. **live restart 只刷了启动参数、没刷记录。** 四个 restart 生产者把最新 model 发给
worker,但 `session.model`(rule 3 的兜底记录)只在 daemon refork 那条路回写:
会话以 A 启动 → 改成 B → live `/restart` 起了 B,记录仍是 A → 之后热切 CLI(那些
没有 mismatch 清扫的入口)→ rule 3 把 A 取回来,B 又丢了。
把回写抽成 `recordLaunchModel()`,`sessionAgentConfig` 与 respawn 解析共用;
`latestModelForRestart` 更名 `latestModelForRespawn`(它现在服务两类 respawn),
解析时一并收敛记录。
2. **crash-loop park 后由消息触发的恢复重启读旧快照。** 第四次崩溃后 worker 停在
`crashDiagnosticStopped`,下一条消息直接 `{...lastInitConfig, resume:true}` 起 CLI,
这条路没有 restart IPC 可携带新值。给 `message` IPC 加同一套三分态 model 刷新,
worker 在消息处理开头覆盖快照(在 park 重试分支之前);对已在跑的 CLI 无影响。
3. **fork 反而需要复制那条记录。** 上一版刻意不复制 `session.model` 是错的:源会话
若已 CLI 不匹配、靠 rule 3 保住模型,子会话继承旧 `cliId` 却没有记录,首个 fork
spawn 会掉到 CLI 默认。复制不会复活旧冻结 bug——CLI 匹配时 live 配置永远优先。
同时 rebase 到上游 master `725d7e47`,手工解 6 个文件的冲突(非字段并集处):
restart IPC 同时保留上游的 `reason: 'operator' | 'cli_crash'` 与本 PR 的 `model`;
`trigger-session` 把内存态 `spawnModelOverride` 移植进上游新增的原子 first-owner
claim(不退回持久化 `session.model`);`fork-session` 保留上游的 `cliRuntime` 深拷贝
并与本次的记录复制分层。
验证
- `pnpm build` / `tsc --noEmit` 通过。
- `restart-live-worker-model` 扩到 20 例,新增:respawn 回写记录、一次性覆盖不入记录、
以及完整状态序列「A → live restart 到 B → 热切 CLI → 仍是 B」;另加 crash-park
重试通道的三条(worker 侧 merge 语义、merge 在重试分支之前、两个 message IPC
生产者都捎带)。
- `fork-session` 把「不复制」那条反写为「复制记录,CLI 不匹配的 fork 仍用自己的模型」。
- 变异验证:respawn 不回写记录 / message 不带 model / worker 不 merge / fork 不复制
记录,四种变异分别让对应用例转红。
73a32a8 to
883eda8
Compare
|
三条都已修,并已 rebase 到 1|live restart 没有同步 rule 3 的历史记录 —— 已修确认成立。把回写抽成 补了你要求的完整状态序列测试:A 启动 → 改成 B → live 2|crash-loop park 后的消息触发重试 —— 已修确认成立, 给 一个实现细节值得说明:第一版我无条件在每条 message 上带 model,被三个既有测试判红( 行为测试落在 3|fork 需要复制 mismatch 历史 —— 已修确认成立,恢复 冲突解法(按你点的三处)
另外谢谢澄清 Codex App 同 CLI 接管那条( 验证
3 个失败文件全部在干净的
分支 head: |
问题
会话创建时会把 bot 的
model冻结进 session 记录(sessionAgentConfig),此后每次 resume 都显式传--model <冻结值>。结果是 dashboard 里配置的模型对存量长会话永久失效:ds.session.model = ds.session.model ?? botCfg.model,也就是「建会话那一刻继承来的 bot 默认」;session.model的入口是 trigger API 的options.model,且被isCodexFamily门限制,对 claude / gemini / coco 等根本不会写;同一个 bot 上还存在语义分叉:
reasoningEffort没有 botCfg 回填,只认显式来源;model是「继承也钉死」。两个「运行时档位」两套规矩。改动
model退出冻结集合,改为每次 spawn(含 resume)按 live bot 配置解析。cliId/cliRuntime/cliPathOverride/wrapperCli的冻结保持不变——那几个被中途换掉会真丢能力(ttadk codexwrapper 掉成裸codex会丢网关),而 model 是人主动配的、本就该生效。解析规则集中在新增的
resolveSessionLaunchModel()(src/core/session-model.ts):DaemonSession.spawnModelOverride—— 显式 per-trigger 覆盖(trigger APIoptions.model,仅 codex 家族),只驻内存;cliId与 bot 当前cliId一致——被钉在别的 CLI 上的会话(bot 后来换了 CLI;或 Codex App 线程接管把cliId钉成codex-app)不能被塞进属于另一个 CLI 的 model 串;model记录,仅兜底上面那种 CLI 不匹配的情况。配套:
session.model记录留在原地、不再被读(Session.model标@deprecated,只作历史记录 + 上述第 3 条兜底)。没有需要回滚的写操作。options.model修正为名副其实的 per-turn:以前写进持久字段session.model,一次性调用会变成永久覆盖(文档写的是「仅新建会话生效」);现在落在内存态spawnModelOverride,daemon 重启后不复活。options.reasoningEffort行为不变(仍随会话持久化)。glm-5.1),而不是 bot 配的那个。bots-json(zh/en)说明model每次启动解析、改动对存量会话生效;api-task-trigger(zh/en)说明options.model只驻内存、不落盘。影响面
sessionAgentConfig,规则统一;adopt 只观察不 spawn,不受影响。-m同步改成 live 配置。daemon.ts):cliId被钉成codex-app,与通知 Bot 的cliId不一致 → 走规则 3、且遗留记录已清空 → 不会继承通知 Bot 面向别的 CLI 的 model,与改动前行为一致。model被清空,存量会话不再传--model,由 CLI 自行解析(claude --resume会恢复 transcript 里记录的模型)。这与「没有显式配置就沿用会话自己的历史」一致。验证
pnpm build通过。test/session-launch-model.test.ts(8 例)锁优先级三档 + 缺 botCfg 兜底。test/session-lifecycle-start.test.ts新增两例:同 CLI 的冻结会话 resume 时用当前 bot model(并确认遗留记录未被改写)、显式 per-trigger 覆盖仍然优先;原有「冻结会话不随 bot 配置漂移」的用例改为覆盖 bot 换了 CLI 的情形,仍然绿。test/closed-session-card.test.ts新增一例:冻结会话的 ttadk resume 命令里-m用 live 配置(用非 ttadk 默认值的模型名,避免与内置默认撞车而失去判别力)。test/fork-session.test.ts新增两例:不把遗留冻结值复制到子会话行;显式 per-trigger 覆盖随运行时会话到子会话。test/trigger-session-root-message.test.ts改为断言覆盖落在spawnModelOverride、且session.model保持为空。session.model ?? botCfg.model、(c) trigger 不写spawnModelOverride、(d) 关闭卡改回读session.model,四种变异各让对应用例转红,无一漏网。pnpm test:见下方评论贴出的结果。