Skip to content

fix: preserve local chat continuation across provider switches - #3005

Open
130040167 wants to merge 1 commit into
BigPizzaV3:mainfrom
130040167:codex/native-provider-resume-20261109
Open

130040167 wants to merge 1 commit into
BigPizzaV3:mainfrom
130040167:codex/native-provider-resume-20261109

Conversation

@130040167

@130040167 130040167 commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

问题

在 API、官方登录或混合模式之间切换后,本地会话的索引和已加载连接可能仍保留旧的 provider 身份,导致原生 provider 校验阻止旧会话续接。

修改

  • 在本地恢复和发送入口准备目标会话:备份实际变更,以 CAS 更新索引,并在会话空闲时卸载旧连接、按目标 provider 恢复。保留会话 ID、历史、模型和推理档位。
  • 已运行的会话不强制卸载;若它仍连接旧供应商,显示可能继续使用旧连接的提示并允许当前操作继续。提示不代表切换已经完成;会话空闲后的后续准备才会尝试恢复。
  • 临时会话直接交给原生处理,不读取轮次列表,也不尝试卸载、迁移或恢复;若可确认它仍使用旧供应商,则提示旧连接风险。
  • 聚合连接信息暂不可用且尚未更改会话时,提示可能仍使用旧供应商并放行原生操作;若此前准备已更改会话,仍返回错误。
  • 保留 fix(app): 恢复 Pure API 旧线程时保留模型与推理档位 #2742 的纯 API 模型与推理档位恢复逻辑,供应商同步开启或关闭时都保留未加载会话的设置;已加载且供应商一致的会话不被旧磁盘快照覆盖。
  • 不需要准备时提前放行;只读预检失败时降级为原生请求。使用显式标记和有界等待协调内部恢复、受管连接及上下文刷新,准备已经修改会话后发生的错误仍会返回。
  • 会话准备备份最多保留最近 100 份可识别的备份,清理失败会报告。

供应商同步未开启时不改写会话索引。使用 ATTACH 事务处理分库更新的普通错误回滚;SQLite WAL 模式下跨库提交不保证掉电时整体原子性,最小字段备份保留恢复依据。开启供应商同步时,OpenAI 会话身份的恢复可能额外读取一次会话状态。

验证

验收范围

真实官方登录与纯 API、混合模式之间的双向续接、令牌过期刷新及重新登录后的旧会话恢复仍需隔离验收。真实加密推理或压缩历史跨上游续接尚未验证,不能据现有模拟测试认定兼容。

@130040167 130040167 closed this Nov 9, 2026
@130040167 130040167 reopened this Nov 9, 2026
@BigPizzaV3

Copy link
Copy Markdown
Owner

感谢!三平台 CI 通过,验证范围和未验证项写得很清楚。这个 PR 改的是所有用户的会话恢复入口,按你在描述里列的,真实官方登录 ⇄ 纯 API ⇄ 混合模式的双向续接、令牌过期刷新、带加密推理/压缩历史的跨上游续接,需要我们用真实官方账号在隔离环境里验一遍再合。验收完会在这里给结论,发现问题会附上复现条件。

@BigPizzaV3

Copy link
Copy Markdown
Owner

main 刚合入了 #2990(上下文刷新,同样改了 30-service-tier.js / 70-model-catalog.js / 91-extension-rpc.js / 91-extension-runtime.js 等)和 #3003,这里出现了冲突,麻烦 rebase 到最新 main,并重新跑一次 node scripts/assemble-renderer-inject.mjs 确认产物一致。真实官方登录的隔离验收仍在我们这边,rebase 后继续。

@130040167
130040167 force-pushed the codex/native-provider-resume-20261109 branch 2 times, most recently from 1022c1b to 5c4052f Compare November 11, 2026 04:04
@BigPizzaV3

Copy link
Copy Markdown
Owner

感谢这么完整的实现!ATTACH + IMMEDIATE + CAS 回滚这条路径我们看过了,没问题。合入前还有几处需要处理:

  1. (高)这个 PR 撤掉了已合入的 fix(app): 恢复 Pure API 旧线程时保留模型与推理档位 #2742 修复。 30-service-tier.js 里 Pure API 恢复时保留模型和推理档位的逻辑被整段删掉,verifyCodexPureApiResumeResult 成了空函数,thread-model-preservation.test.ts 的回归测试也被删除。没开供应商同步的纯 API 用户会重新遇到这个问题。请恢复这部分逻辑和测试,或者明确由同步机制接管并补上等价的测试。
  2. (中高) 内部恢复是按参数形状识别的(4 个键、config === null,见 91-extension-rpc.js 约 362 行)。aggregate 模式下 config 被补上之后识别会失效,这次恢复会去等同一把会话准备锁,可能一直挂住。建议改用显式标记(WeakSet 或 symbol),并给 plan.before 和准备锁加超时。
  3. (中) aggregate 模式下开着同步时,JS 侧没有跳过 codex_plus_managed,Rust 端(provider_sync.rs 约 654 行)会直接报错,用户的恢复被拒。
  4. (低) 每次改写都会留一个备份 JSON,从不清理。

另外说明:CI 里 Windows / Linux 的失败是 main 本身已有的问题,与本 PR 无关。

@BigPizzaV3

Copy link
Copy Markdown
Owner

感谢更新!对照 07:59 的四点逐条看过了,并在 45c6bded 上跑了相关 JS 测试(324 项)和 provider_sync Rust 测试(78 项),都通过。

  • 第 2 点(显式标记 + plan.before / 准备锁超时)、第 3 点(JS 侧跳过 codex_plus_managed)、第 4 点(备份保留 100 份、清理失败只告警)都已处理,测试也很到位,谢谢。
  • 第 1 点只处理了一半。我们把 main 上原版的 thread-model-preservation.test.ts 放到这个 PR 上跑,有 3 项失败,暴露出两个问题:
    1. 已加载会话会被旧快照覆盖。 main 上会话已加载(idle/active)且供应商一致时会直接放行;现在 30-service-tier.js:207 只在供应商不一致时拦截,一致的情况会继续注入磁盘上的模型和档位。实测:会话已加载、供应商为 custom、磁盘快照是 older-model 时,请求被改成 model: "older-model"。用户在会话里改了模型、切走再回来,选择会被回退。请恢复这里的 return params,并补回 "keeps loaded matching-provider settings instead of restoring an older disk snapshot" 这条测试。
    2. 开着供应商同步的纯 API 用户仍会遇到 fix(app): 恢复 Pure API 旧线程时保留模型与推理档位 #2742。 前面的供应商改写还是会补上 modelProvider,同时 model 为 null,这正是 fix(app): 恢复 Pure API 旧线程时保留模型与推理档位 #2742 的触发条件;新测试里 restoreSnapshot = !enabled && ... 也把「开同步时不恢复」写成了预期。请让开同步时也保留已保存的模型和档位,或者补一条测试证明这种情况下原生会自己恢复。
  • 新问题:每次 turn/start 前都会先做 thread/read + config/read,没有中继配置、没开同步也一样(RPC 路径上只要有激活的中继配置就会做),而且 thread/read 失败会让这条消息直接发送失败。麻烦在不需要准备时(没开同步、也不涉及受管连接)提前返回,读取失败时降级为放行,不要拒绝。
  • 小建议:main 上原有的降级和超时类用例(读取超时与迟到快照、刷新失败降级、thread/read 失败降级、官方模式不探测)在新版测试里少了,能补回来或说明由哪些新测试覆盖就更好了。另外开着同步时「OpenAI 会话身份」下的恢复会额外读取一次会话状态,和 fix(app): 恢复 Pure API 旧线程时保留模型与推理档位 #2742「不额外读取」的约定不同,可以接受,但请在 PR 描述里写明。

改完我们继续做真实官方账号的隔离验收。辛苦了!

@BigPizzaV3

Copy link
Copy Markdown
Owner

感谢更新!这轮四点逐条核过了:已加载且供应商一致时放行并补回了对应测试;开同步时纯 API 恢复也保留模型和档位;turn/start 不需要准备时提前返回,读取失败降级放行;降级和超时类用例也都有覆盖。全部 JS 测试(1971 项)和 provider_sync Rust 测试都通过,谢谢!

还有两处请再看一下:

  1. 30-service-tier.js:211 现在 status === "active" 时不论供应商一律放行。main 上运行中的会话如果还连着旧供应商(比如 openai)会报「此会话仍使用旧供应商连接」,现在会静默走旧供应商。main 原版的 "rejects confirmed loaded old-provider sessions and only diagnoses restored setting differences" 在这个 PR 上失败。麻烦 active 也比对一下 modelProvider,不一致继续报错;如果是有意放宽,请在描述里说明原因并补测试。
  2. 30-service-tier.js:407 在受管连接下发送前会调用 thread/turns/list,没有排除临时会话。侧聊(ephemeral)调用会直接报 "ephemeral threads do not support thread/turns/list",消息发不出去——这就是你报的 [Bug]: 本地开发构建的侧边聊天发送时调用临时线程不支持的历史分页 #3051。我们用 provider-aggregate-review.test.ts 的夹具给 thread/read 加上 ephemeral: true 后复现了:聚合模式、会话供应商为 custom 时,turn/start 被拒。麻烦在 thread/read 之后遇到 read.thread.ephemeral === true 就直接放行(不翻页、不卸载恢复),并补一条用例。

改完我们继续做真实官方账号的隔离验收,辛苦了!

@130040167

Copy link
Copy Markdown
Contributor Author

针对这一轮的两点,已在 ccce1df9 上复核:

  1. 活动会话现在也会比对 modelProvider。如果确认仍连着旧供应商,会提示本次操作可能继续沿用旧连接,并交给原生处理;不会强制卸载正在运行的会话,也不会把放行当成切换成功。这是有意放宽原先的报错行为,优先保住当前会话;会话空闲后,后续操作仍可尝试重新接入。已补回归测试,覆盖旧供应商提示、活动会话不卸载及供应商一致时不覆盖当前设置。
  2. 聚合模式读到 ephemeral: true 时直接放行,跳过轮次分页、卸载、迁移和恢复;同步开启和关闭都加了回归用例。

本次重跑供应商准备和模型保留定向测试,72 项通过;注入产物与分片一致,当前提交的 Windows、Linux、macOS CI 均通过。真实官方登录与纯 API/混合模式双向续接、令牌刷新,以及真实加密推理或压缩历史的跨上游续接,仍待隔离验收。

@BigPizzaV3

Copy link
Copy Markdown
Owner

感谢!这一轮两点都核对过了:活动会话连着旧供应商时改为提示并交给原生处理,这个有意放宽的原因写清楚了,也补了回归;聚合模式下 ephemeral 线程直接放行(这也就是 #3051、#3062 侧聊报错的那条路径),同步开关两种情况都有用例。供应商相关的前端测试(194 项)本地通过,注入产物与分片一致。

现在剩两件事:

  1. 需要 rebase 到最新 main。 刚合入的 fix: 相同上下文配置切换额度账号时避免多余线程重载 #3070 也升了 codexAppServerModelRequestPatchVersion(37 → 38),和这里在 00-prelude.js、renderer-inject.js、manifest.json、tests/all/cdp_bridge.rs 冲突。rebase 后请把版本再往上升一位、重新组装注入产物。
  2. 真实官方登录的隔离验收还在我们这边,rebase 后进行,结论会在这里回复。

@130040167
130040167 force-pushed the codex/native-provider-resume-20261109 branch 2 times, most recently from 0754c00 to 278d59c Compare November 12, 2026 13:55
@130040167

Copy link
Copy Markdown
Contributor Author

已按这轮意见变基到 9b22eebd,当前提交 278d59c3。请求包装版本从本 PR 的 44 升到 45,重新组装了 43 个注入分片,产物一致。

完整前端 2,034 项通过、2 项跳过,供应商 Rust 用例 77 项及注入契约 2 项通过。当前提交的三平台 CI 均通过。真实官方登录、令牌刷新及真实加密/压缩历史续接仍留待你们隔离验收。

@BigPizzaV3

BigPizzaV3 commented Nov 12, 2026 •

Copy link
Copy Markdown
Owner

感谢这一轮的修改!合入前还有两件事:

  1. 需要 rebase 到最新 main。 fix(gateway): 重新接入后恢复已有会话 #3046 刚合入,它和本 PR 改了同一批文件,现在有 9 处冲突:00-prelude.js(两边都升了 codexAppServerModelRequestPatchVersion)、70-model-catalog.js、91-extension-rpc.js、renderer-inject.js、manifest.json,以及 renderer-inject.test.ts、runtime-context-rpc.test.ts、session-connection.test.ts、tests/all/cdp_bridge.rs。麻烦 rebase 到 3d41192e7 或之后,请求包装版本在 fix(gateway): 重新接入后恢复已有会话 #3046 的基础上再往上升一位,重新组装注入产物。合并时请特别留意两边都改过的发送前准备链路(prepareCodexGatewayReconnect 和本 PR 的供应商准备)在 proto.sendRequest 里的先后顺序。
  2. 请在 rebase 后的版本上做一次真实官方登录的隔离验收,并把结果贴在这里。 用独立的 CODEX_HOME 和 user-data-dir,不影响自己正在用的环境。至少覆盖:
    • 官方登录 ⇄ 纯 API、官方登录 ⇄ 混合模式,双向切换后旧会话都能续接;
    • 令牌过期刷新后、重新登录后,旧会话能恢复;
    • 带加密推理或压缩历史的会话跨上游续接(如果实际会报错,请如实写明报错和处理方式)。
      每项写清楚用的 Codex 版本、操作步骤和结果;没能验证的项也请注明。

这两项完成、三平台 CI 通过后我们再合入。辛苦了!

@130040167
130040167 force-pushed the codex/native-provider-resume-20261109 branch from 278d59c to 510b8aa Compare November 12, 2026 16:53
@130040167

130040167 commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor Author

已变基到包含 #3046 的 main(3d506558),PR 当前提交为 510b8aa1。发送包装版本升至 46,RPC 为 19,已重新组装注入产物。发送前先处理供应商身份准备,再检查网关连接代际;相关回归覆盖了两种恢复同时需要时的协调,避免重复卸载。此次没有混入独立的网关目录或模型选择器修复。

本地定向前端 181 项通过,覆盖 #3051、#3062 的侧聊首发、#3070 的原生账号选择,以及 #3038 的模型目录刷新回归;网关路由 Rust 47 项通过,覆盖停用路由组、移除供应商和未知模型映射。Vite 构建、Windows debug 构建和注入产物一致性检查通过。

#3072 补充验收:用当前源码和 Codex Desktop Windows 26.1103.4239.0,独立 CODEX_HOME、用户数据目录和本地 SSE 上游。发送走原生窗口和真实本地网关,回复由本地上游提供:

  • 旧会话从直连重新接入网关后继续发送:同一个线程 ID、保留历史、模型和 medium 推理档位,每轮正文只发一次。
  • 只切换同名模型自动组的路由目标,连接代际不变:在途回复在 A 完成,同一旧会话的下一轮去 B,无需重启或再次接入。
  • 注入「代际查询失败」对照:仍有效的连接继续完成原生发送,恢复查询后同一会话继续。没有模拟整个 helper 退出,也没有用已失效的连接验证。
  • 新增模型:保存后已出现在磁盘上的 Agent 目录,但这次隔离多开里的桥接目录和原生选单没有该条目,旧/新会话的新模型发送及停用/删除后的行为未验收通过。这与上面的路由热切换是两个阶段,留在 [Bug]: 网关新增供应商后重新接入仍无法调用,重启后新旧会话表现不一致 #3072 单独跟踪;[Bug]: 网关变更并重新启动 Codex 后,模型选择器退回全部展开 #3073 的重启后折叠状态不在本 PR 验收范围。

真实官方登录仍待验收:没有在本次隔离实例使用官方测试账号,未验证官方登录 ⇄ 纯 API、官方登录 ⇄ 混合模式的双向旧会话续接,也未验证真实令牌刷新、重新登录和跨上游加密推理/压缩历史。不会用本地 SSE 结果替代这些项目。

510b8aa1 的三平台 PR CI 已全部通过,PR 无合并冲突。此结果不是发布包验收,真实官方登录仍待复测。

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants