makecindy / makecindy/cindy

bug: Pi 已有任务跨来源切换供应商被 PRECONDITION_FAILED 拒绝(#3601 守卫封掉 #3705 的重建路径)

Open
#4,486 2 comments 0 reactions 0 assignees View on GitHub
bug pi
Dominant language
TypeScript
Stars
2.7k
Forks
401
Avg merge
21h 48m
Merged PRs (30d)
776

Description

### 问题描述 / What happened

在 **Pi 环境**的已有任务里,把供应商切到不同来源(例如 Cindy 网关 → xAI / Anthropic 订阅或 BYOM,反向同理),会被直接拒绝且选择不生效:

```
[PRECONDITION_FAILED] Pi target route requires an unsupported runtime replacement; runtime selection was not changed
```

期望:切换成功。按 #3705 的设计,这种跨 proxy 身份(活进程的 `x-cindy-pi-provider-id` 会变化)的切换应当“关闭空闲 Pi 进程、下一次发送按新来源懒创建”;目前桌面选择器链路在空闲、turn 中、冷会话三条路径上都会撞同一个断言。

不受影响的情况(可正常切换):同一 proxy 身份内的来源(默认 / `xd` / `cindy` 三个网关照之间),以及两个独立 OpenAI 订阅账号之间(#4331 的例外)。

### 复现步骤 / Steps to reproduce

1. 新建/打开一个使用 Pi harness 的任务,模型来自 Cindy 网关(`xd` / `cindy` / 默认)。
2. 发送一条消息,确保该任务有活着的 Pi runtime。
3. 打开模型选择器,切到另一个来源的模型(例:xAI 订阅、Anthropic 直连、BYOM 本地端点)。
4. 观察:不生效并报上面的错误。

补充路径同样复现:

- **冷会话**(本地会话,重启应用后没发过消息):主进程会先为该会话拉起 Pi runtime 做窗口核实,随后照样撞断言。
- **任务正在跑 turn**:先提示“任务结束后生效”,turn 结束结算时仍会失败。

### 定位与证据(本地核对 main @ 87367262b)

调用链:

- 目标来源跨 proxy 身份 → `shouldCloseSessionForCredentialSwitch` 返回 `true`:`apps/desktop/src/main/maker-host/codex-credential-switch.ts:273-295`;身份归一规则见同文件 249-263(`xd` / `cindy` / 空 → `null`,其它 provider 用自身 id;只有两个 OpenAI 订阅 provider 之间是例外)。
- `applyRuntimeSetModelChange` 在真正 close 之前调用 `assertSessionCloseSupported?.()`,且位于 busy/延迟判定之前:`apps/desktop/src/main/maker-ipc/runtimeSetModel.ts:266-278`。
- 桌面选择器链路为该断言注入了恒抛实现:`apps/desktop/src/main/maker-ipc/register.ts:16311-16319`,错误文本即上条。
- 冷会话不绕过:`register.ts:15935-15965` 会先 `rehydrateColdPiRuntimeForWindowVerification`(内部 `bootstrapSession` 拉起 Pi)再进入 apply。
- turn 中:`register.ts:15936-15938` 先 `deferLockedSelection()`(登记为 route-explicit deferred),turn 结束结算(`register.ts:10963` 起)仍回到同一个 `handleSetModel`,结果相同。

为什么判断这是回归而非有意限制:

- #3705(`62861c898`,2026-09-01 合入,"fix(pi): rebuild live session when proxy provider identity changes")实现了“空闲关闭 + 下次懒创建”,并有回归用例:`apps/desktop/src/main/maker-ipc/__tests__/runtimeSetModel.test.ts:1151`、`:1236`(xai ↔ openai ↔ xd 双向 close + lazy-create)。
- #3601(`1fbcdeac5`,2026-09-02 合入,"fix(desktop): guard switches into smaller model windows")引入该断言;`git merge-base --is-ancestor 62861c898 1fbcdeac5` 成立,即它是在 #3705 之上合入并顺手封死了 #3705 的路径。断言本意是“切小窗口必须在活进程上做 `get_state` 复核”,但 `assertSessionCloseSupported` 是 close 总闸,覆盖范围过大。
- 同一操作在其它入口是通的:IM 卡片选模型走 `apps/desktop/src/main/im/shared/cardActionHandler.ts:386`,没有传该断言;`PendingCredentialSwitchService` 的收口也自己 close。桌面选择器与其它入口行为不一致。
- 后续 #4331(`b06538372`)专门为“独立 OpenAI 换号”开了热切例外,方向显然是让 Pi 换来源尽量可用,剩下的阻塞正是这个断言。

另注:`runtimeSetModel.test.ts:1195` 目前有用例锁着“跨凭证 replacement 必须拒绝”,与 #3705 的能力直接冲突,修复时需要一并调整语义。

### 期望修复方向(供参考)

1. `applyRuntimeSetModelChange` 在因 route replacement 关闭会话成功时,向调用方返回“已重建”标记(或独立 status)。
2. `register.ts` 的 Pi 分支据此跳过活进程 `get_state` 复核(16348 起),落库目标 route + 目录核实窗口,交由下一次发送按新来源懒创建。
3. 保留 #3601 的红线:目标窗口核实缩小且当前上下文有压力时继续 fail-closed,或改走既有 `prepareModelWindowSwitch`(bounded handoff + `context_rebuild`;该路径会置 `modelWindowRebuilt=true`,本身就跳过活进程复核)。
4. 回归覆盖:Pi 跨身份切换在 idle / busy-settle / cold 三条路径上都不再抛错;缩窗 + 压力场景仍被保护。

如果维护者认为“选择器链路不允许 Pi runtime replacement”是有意设计,也请明确说明,因为目前它与 #3705、IM 入口的行为互相矛盾,且错误信息没有可行动的文案。

### 环境 / Environment

- Cindy 版本或 commit: Desktop 0.1.79(本地构建覆盖安装);代码核对 main @ 87367262b(2026-09-15)
- 平台与版本: Windows 11 专业版 10.0.26200
- 安装方式: 本地构建产物覆盖安装

### 日志与截图 / Logs & screenshots

主进程日志在点击切换后会出现该 `PRECONDITION_FAILED`;同时不会出现“关闭会话并发布新 route”的日志(`runtimeSetModel.ts` 的 `set-model: closed live session for route rebuild`),可作为“未发生重建”的对照证据。粘贴前请自行脱敏。

Contributor guide

Open the contributing guide

Research direction

Start with apps/desktop/src/main/maker-ipc/runtimeSetModel.ts and the Pi branches in apps/desktop/src/main/maker-ipc/register.ts, then compare the close behavior in codex-credential-switch.ts and the IM card handler. Run the relevant runtimeSetModel.test.ts cases, including lines 1151, 1195, and 1236, and cover idle, busy-settle, and cold-session switches while preserving the model-window safety case.

Written by the indexing model from the issue text.

Assessment

Tech stack
electron, typescript
Domain
desktop
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.