Skip to content

feat(desktop): 模型图片输入能力本地声明与运行期对账 - #4856

Open
zyjisdog wants to merge 21 commits into
makecindy:mainfrom
zyjisdog:fix/model-capacity-undeclared
Open

zyjisdog wants to merge 21 commits into
makecindy:mainfrom
zyjisdog:fix/model-capacity-undeclared

Conversation

@zyjisdog

@zyjisdog zyjisdog commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

这次改了什么

摘要

让用户能在设置 → 模型 → 高级设置里,为任意一个模型声明上游目录没说清、但实际成立的模型能力,声明只落在本机 model-catalog-overrides.json,不写回 Server、不改连接配置。

背景是一条真实的用户困境:目录(Server 正本 → 客户端目录)里某些模型的能力位是错的或缺失的。典型实报——space-bunny-free 被声明为「不支持思考」且六档思考档全为 null,而厂商模型卡明确它是强制推理模型(reasoning_effort: none 无效,档位 max/xhigh/high/medium/low)。目录这么写,Pi 客户端就没有任何 thinking 通道可用,模型的推理文本只能随 content 通道返回、被 Cindy 当作普通 assistant 正文渲染,用户看到的是「思考被写进正文」,而不是可折叠的思考块。

这个 PR 把 override 写入面从「只有读」补成「可声明」,并在两条能力上真正落地:图片输入能力(多模态)与思考档位。

变更类型

  • feat 新功能
  • fix 缺陷修复
  • refactor / perf 重构或性能优化
  • docs / test / chore 文档、测试或工程维护
  • 其他:

范围

  • 关联 Issue / 需求:模型能力目录错报 / 缺失时,用户缺少自行声明的入口
  • 本 PR 包含:
    • override 写入面(此前 upstream 上只有 readModelCatalogOverrides):新增 store 写入函数、IPC handler、preload 桥、抽屉 UI 控件区;非法组合在 IPC 边界就拒,不让 sanitize 事后把整条判无效、用户看着「保存了但没生效」
    • 多模态(图片输入)能力声明:supportsImageInput 三态(跟随 / 显式支持 / 显式不支持);运行期对账按声明决定是否拒收图片
    • 思考档位声明:efforts + defaultEffort,三态 + 逐档勾选;Pi 侧无档位时记 warn 而非静默照发
    • 顺带修正高级抽屉两处上下文显示问题(非新增上下文能力,是既有能力的显示与时序修正):
      「上游最大上下文」不再拿 200K 展示兜底冒充上游窗口(按 contextWindowMax → 已验证 contextWindow → 未声明 三级来源,第 3 条必须排除「自定义模型缺元数据时的 200K 兜底」——那个常量会让用户以为容量只有 200K,而运行期窗口其实取模型级上下文上限);上下文上限提交在途时不再闪回旧值(ctxCommitting 收口)
  • 明确不包含:
    • 上下文窗口上限的 override 能力本身不在本 PR 新增范围:它在 upstream/main 已存在,本 PR 未改动其写入逻辑;本 PR 只是让写入面成为可复用的通用通道,使「思考档位」能以同一套机制落地。需要区分的是:本 PR 仍然包含上文那条上下文显示与提交时序的修正(既有能力的修 bug,不是新增能力)
    • 不改 Server 正本(上游元数据修正应由 Server 侧 PR 处理)
    • 不开通用 patch 写入口(会让任意目录字段可从 renderer 改写,超出当前需求)
  • 用户可见变化:设置 → 模型 → 高级设置新增「图片输入能力」与「思考」两行声明控件;多档勾选、跟随供应商、失败提示带可执行指引;另修正高级抽屉「上游最大上下文」的取值来源与上下文上限提交在途的闪回
  • 是否存在 breaking change:无

UI 变化

  • 引用的设计规范:docs/design-rules/DESIGN.md §4 Component Stylings(Buttons):声明控件沿用同抽屉既有 dropdown 触发按钮的形态——同一控件内「状态图标 + 文案 + Chevron」的组合、rounded-full 胶囊外形、hover 走 --surface-hover,颜色全部走语义 token,Light/Dark 两套都不写死;disabled 状态沿用同抽屉协议控件既有约定(禁用但不改 opacity),未新增视觉语言。docs/design-rules/DESIGN.md §11 Voice & Content:文案沿用既有 override 控件的「跟随供应商 / 已声明」措辞与 §11.1「动宾结构、结果点名对象」的要求,失败提示按 §11.1「错误 = 发生了什么 + 下一步」给出可执行原因(errorReason 透出),五语言经 i18n key 落地。docs/design-rules/DESIGN.md §14.2 Focus Management:多选下拉勾选时 preventDefault 阻止 Radix 自动关闭,便于连续勾多档,键盘路径沿用 Radix 既有行为,未改 Tab 遍历。

怎么验证的

自动验证

pnpm --filter desktop run typecheck
结果:通过(0 error)

pnpm test:unit:related
结果:EXIT=0,8 个工作区全 PASS(含 desktop 全量 unit)

pnpm check:dco
结果:8 commits signed off

新增/更新测试:
- model-catalog-override-store.test.ts(图片输入声明面,12 例)
- modelCatalogThinkingOverride.test.ts(思考档位声明面,7 例:三态、只动自己字段、
  perAgent 残留遮蔽清理、按 agent 合成 base+perAgent、多引擎分叉检出、非法组合拒收)
- providerHandlers.test.ts(写入 / 读回 / 拒写 / 边界校验,共 200 例)
- ModelAdvancedDrawer.test.tsx(抽屉声明控件,共 55 例)
- useModelCatalogImageInput.test.ts(hook 并发与回读纪律)
- pi-provider-routing.test.ts(运行期对账,Pi 侧)

手工验证

  • 图片能力(设置 → 模型 → 高级设置 → 能力区):目录未声明图片能力时,声明「支持」→ 该模型在 Pi 侧可发图;声明「不支持」→ Pi 客户端拒收图片(请求不出网),ErrorBanner 给出可执行指引。已覆盖自动化测试。
  • 思考档位:对 space-bunny-free 这类目录声明「零档位」但实际会推理的模型,声明其真实档位(max/xhigh/high/medium/low)→ 该模型在 Pi 侧能开真正的 thinking 通道,推理回到可折叠思考块,不再混进正文。
  • 平台:Windows(desktop main + renderer);Pi 运行期对账在真实 Pi + fake gateway 下覆盖。

未执行的验证

  • 未在真实 Pi 子进程 + 真实 space-bunny 网关上做端到端实拍(需真实账号额度与解锁环境)。目录错报的判定依据是厂商公开模型卡与 Pi 运行时 models.json 的实测(reasoning: false + 六档全 null)。

风险

风险分类

  • 无已知风险
  • SQLite / migration
  • system prompt
  • 协议兼容
  • 权限 / 安全 / 用户数据
  • 存量插件兼容(批准状态 / 指纹 / manifest 校验 / 安装布局 / 包格式)
  • 原生层 / fingerprint / OTA
  • 跨平台差异
  • 其他:override 是本机最高优先级写入,若不加门会让运行期能力偏离服务端 / 管理员下发的配置

影响与回滚

  • 权限 / 安全(本 PR 的关键门):override 是本机最高优先级,不加这道门就能让运行期能力偏离管理员下发的企业配置。因此写入面对两类来源拒写:

    • xd(Cindy AI Gateway,能力由服务端目录控制)→ INVALID_PARAMS
    • 组织托管供应商(能力由管理员下发)→ PERMISSION_DENIED,与价格 override 同一位置、同一错误码

    抽屉控件也一并 disabled(按钮禁用挡不住受信 renderer 直调,IPC 侧拒写是真正的那道门)。

  • 多引擎 id 双写:桥接投影两端 id 不同(如 openai 行 codex 用 gpt-5.6-sol、pi 用 chatgpt/gpt-5.6-sol),override 按 provider:model 精确匹配。只写主展示引擎的 id,运行期真正消费该能力的 Pi 读不到、UI 却显示「已声明」。因此写入面一次写该行全部引擎 id;各引擎键分叉时读回报 diverged,UI 允许「重选当前项」写回一致。

  • 恢复继承:用户把「显式不支持」改回「跟随供应商」后 override 被删、声明读回 undefined,与「目录自己标了 text-only」在读回看来相同。为此运行期新增「活动目录最终结论」钩子(readModelCatalogImageCapability),以最终 input 判定「该恢复支持」;拿不到结论则保持现状不翻转(不猜)。

  • 影响范围:仅模型能力元数据(图片输入 / 思考档位)的本机声明;不改会话数据、不改凭证、不改连接配置。

  • 回滚 / 降级方式:删除 model-catalog-overrides.json 中对应条目即回到「跟随目录」;整文件删除回到「无覆盖」。本 PR 全部逻辑在客户端,回滚无服务端残留。

提交前检查

  • 已 review 完整 diff
  • 每个 commit 都带 DCO 签名(git commit -s,见 DCO)
  • UI 改动已在「UI 变化」注明引用的设计规范章节
  • 未提交凭证、令牌或授权文件
  • 已核对受影响的文档(设计台账 design-inventory 已重跑生成器并保持最新)
  • 已确认测试结果或说明未执行原因

目录未声明 supportsImageInput 的自定义模型会在客户端被 Pi 拒收图片,且报错指引的开关并不存在:\n- 设置 → 模型 → 高级设置新增「图片输入能力」三态声明(跟随供应商/支持/不支持),\n  写本机目录 override(preset 连接仍跟随官方目录),新开专用 IPC(不进 device-link allowlist);\n- Pi 运行期能力对账:会话启动后目录才声明能力时,切模与发送带图消息两处都会\n  就地刷新会话快照(热写 models.json + switch_session 重载),补上即放行,补不上仍\n  fail-closed;全分支 debug 日志可定位。\n- 顺带修正高级抽屉「上游最大上下文」的展示来源(未验证的 200K 展示兜底不再冒充\n  上游窗口)与上下文上限提交在途时闪回旧值的问题。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
@zyjisdog
zyjisdog requested a review from a team as a code owner September 22, 2026 07:22
@greptile-apps

greptile-apps Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium risk] Adds local model capability overrides for image input and thinking tiers.

当前 PR 未发现仍需阻止合并的新问题,看来可以安全合并。

Summary

本 PR 为桌面端增加模型图片输入与思考档位的本地声明能力,并让活动 Pi 会话在运行期重新对账目录能力;同时修正高级设置中的上下文容量展示和提交状态时序。

  • 新增受限的本地 override 存储、IPC、preload 与 renderer hooks。
  • 设置抽屉提供图片输入三态和思考档位多选声明,并处理多引擎模型 ID 分叉。
  • Pi 在切模、同路由同步及图片发送前刷新能力快照,避免宿主与子进程状态分叉。
  • 写入边界拒绝 XD 网关和组织托管供应商,并保留 owner 隔离与目录成员校验。
  • 本轮未发现需要新增评论的问题。
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  UI["高级设置能力控件"] --> Hook["图片 / 思考 Hook"]
  Hook --> IPC["Preload 与受信 IPC"]
  IPC --> Guard["目标、权限、Owner 与目录成员校验"]
  Guard --> Store["model-catalog-overrides.json"]
  Store --> Catalog["刷新活动模型目录"]
  Catalog --> Pi["Pi 会话能力对账"]
  Pi --> Runtime["models.json / 活动思考档位快照"]
Loading

Reviews (15) · Last reviewed commit: "Merge remote-tracking branch 'upstream/m..."

Comment thread packages/maker-core/src/agents/pi/index.ts Outdated
Comment thread apps/desktop/src/main/maker-ipc/providerHandlers.ts Outdated
Comment thread apps/desktop/src/renderer/components/settings/ModelAdvancedDrawer.tsx Outdated
Comment thread apps/desktop/src/renderer/components/settings/ModelAdvancedDrawer.tsx Outdated
对抗审核与 PR review 的合并修复:\n- 对账里的 switch_session 必须重放当前路由 + get_state 读回(否则会话内切过模的会话会被\n  静默拽回启动路由:同路由 no-op / 发图 / steer 都不再打 set_model);整段对账进会话串行链,\n  避免与心跳 setModel、compact 交错。\n- 对账改为双向(先对账再信任快照):声明从「支持」改成「不支持」时旧快照不再放行图片。\n- 回合运行中不重建子会话(会打断在跑的回合),只对齐宿主快照并置 pending,空闲时补重载。\n- 声明覆盖该行全部引擎 id(桥接两端 id 不同:openai 行 codex=gpt-5.6-sol、pi=chatgpt/gpt-5.6-sol),\n  此前只写主展示引擎的 id,运行期读不到、UI 却显示已声明。\n- SET 拒绝网关(xd)目标;目录成员校验移入写队列(与价格/上下文上限同一顺序);\n  value=null 清陈旧 override 不再要求成员校验。\n- store:按 agent 算有效声明、清掉遮蔽 base 的 perAgent 残留;手改文件里会被 sanitize\n  丢弃的 patches 条目改为显式拒绝写入(不静默删除)。\n- hook:写在途时忽略广播触发的回读(旧值会顶掉写入回声)、加 owner 代次守卫;\n  抽屉:超限告警按 max→已验证窗口同一来源、提交代次化(旧提交不再清新状态)。\n- 对账失败文案本地化(新增 PI_IMAGE_CAPABILITY_REFRESH_FAILED)并补 5 语;\n  PI_CATALOG_RELOAD_UNCONFIRMED 文案不再假定「切换模型」场景。\n- 新增测试:路由重放、反向对账、多引擎 id 写入、perAgent 残留、sanitize 丢条目拒绝、\n  写在途广播、owner 守卫、抽屉目标覆盖 pi 侧 id、已验证窗口超限告警。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
@zyjisdog

Copy link
Copy Markdown
Contributor Author

@greptileai review

Comment thread packages/maker-core/src/agents/pi/index.ts
Comment thread apps/desktop/src/main/maker-ipc/providerHandlers.ts
Comment thread apps/desktop/src/renderer/hooks/useModelCatalogImageInput.ts Outdated
第二轮对抗审核的修复:\n- 路由重放改为「先 get_state 读回、读回已等于当前路由就不发 set_model」:spawn 能靠 custom\n  model id 跑在 Pi 自带目录没有的路由上,对这种路由重发 set_model 会被 Pi 拒,而重放失败\n  会 terminate 整个任务 —— 只改一个能力声明就杀掉正在跑的任务。读回确实不同才重放,且\n  重放被拒/读回不匹配都是 fail-closed 终止(子进程确实跑在错路由上,静默发错模型更糟)。\n- 宿主快照已改、子进程还没加载(回合在跑,重载被延后)时不再放行图片:此前会放行给一个\n  仍按旧清单判定的子进程,用户看到的是子进程的报错/无图回答。改为抛可重试的\n  PI_IMAGE_CAPABILITY_REFRESH_FAILED。\n- 会话快照里没有该模型行(启动后才新增的 BYOM/目录行)时显式跳过:写盘是空操作却会置\n  pending、触发有终止风险的重载,而准入门读的快照始终没变(声明永远不生效)。\n- 新增的 PI_IMAGE_CAPABILITY_REFRESH_FAILED 接进 ErrorBanner(此前只在模型切换 toast 路径\n  本地化,对话里显示的是中文原文),两种码不再互相误判。\n- store 的「拒绝丢掉手改条目」改用独立错误类型,IPC 映射成 PRECONDITION_FAILED 并把指引\n  带给用户(不再退化成笼统的保存失败)。\n- hook 写在途时跳过回读只对同一 target 生效:否则写 A 行后立刻打开 B 行,B 的回读被跳过、\n  界面永远停在 loading(显示成「跟随供应商」且点击无效)。\n- 新增测试:spawn-only 路由不重放/不终止、重放被拒终止、非 no-op 切模落在目标模型、\n  新增行的跳过、两码互不误判、跨 target 回读不被吞、loss guard 的错误类型与 IPC 码。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
Greptile 第三批意见的处置:\n- 同一行各引擎的声明可能不一致(手工改文件、或旧版只写主展示引擎键的存量数据):读回\n  此前取「第一个非空」,于是 UI 显示主展示引擎的值、运行期 Pi 读另一键 —— 显示“支持”\n  而 Pi 仍拒收图片。现在展示值优先取运行期真正消费该能力的引擎(Pi)那一侧,并在不一致\n  时回 diverged。\n- 抽屉的同值 no-op 会在分叉时把「重选当前项」挡掉,用户无法从 UI 修复分叉。diverged 时\n  放行写入,把各引擎键写回一致;未分叉时仍然跳过空写入(不引入多余的全量目录刷新)。\n- 新增测试:分叉时展示 Pi 侧值 + diverged、各键一致时不标分叉、diverged 时同值可写入\n  修复、未分叉时同值仍为 no-op。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>

@zyjisdog zyjisdog left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

第二轮独立对抗审核(对上一批修复本身)

审核对象:44c6a3fbe(第一轮 review 的修复批次)。结论:修完 P0 再合,已全部处理并推送 c98098750。

P0(已修)

路由重放会对 spawn-only 路由发 set_model,失败即终止整个任务。 本文件自己写明「spawn 能靠 custom model id 跑在 Pi 自带目录没有的路由上,重复 set_model 反而 fail-closed」(switchModel 开头同路由 no-op 不变量),而上一批的路由重放是无条件 set_model + 失败 terminate —— 用户在 SuperGrok 这类路由上改一次「图片输入能力」,下一次同步点就会把正在跑的任务杀掉。修法:switch_session 后先 get_state 读回,读回已等于当前路由就不发 set_model(这正是 spawn 路由这一类);读回确实不同才重放。重放被拒/读回不匹配仍 fail-closed 终止(子进程确实跑在错路由上,静默发错模型比终止更糟)。

P1(已修)

  1. 回合延后期间把图片放行给旧清单的子进程:延后只对齐宿主快照,而准入门读的就是这份快照 → 立即放行;子进程 models.json 还是旧值。改为 pending 且本次需要该能力时抛可重试的 PI_IMAGE_CAPABILITY_REFRESH_FAILED(不再假装已生效)。
  2. currentSpec === undefined(启动后才新增的模型行)假装对账成功:写盘是空操作,却照样置 pending + 触发重载,而快照始终没变。改为显式跳过 + warn。
  3. 新错误码不会在对话里本地化:ErrorBanner 只特判了 PI_IMAGE_INPUT_UNSUPPORTED,新码走兜底显示中文原文。已补同型分支(isPiImageCapabilityRefreshFailedError)并加「两码互不误判」测试。

P2(已修 / 已知)

  • 已修:hook 写在途跳过回读改为只对同一 target 生效(否则写 A 行后立刻打开 B 行,B 卡在 loading 且点击无效);store 的 loss guard 改用独立错误类型 → IPC 映射 PRECONDITION_FAILED 并把「先修正该文件」的指引带给用户。
  • 已知并接受:读路径「取第一个非空」对存量单键条目(只写了一个引擎 id)仍可能 UI 显示已声明而运行期拒收。该状态只可能由本未合分支的旧写法产生,外发数据不存在;多引擎写入已保证新写入不再产生该状态。
  • 测试缺口:回合运行中的延后分支缺少自动化用例 —— 该测试文件的 harness 没有事件注入通道(ctx.isStreaming 由 translator 事件驱动,rpc-client 被整体 mock),补测需要扩 harness。已用 P1-1 的分支守卫(pending + supportsNow → 抛错)覆盖逻辑,但未做端到端断言。

验证

pnpm test:unit:related EXIT=0(desktop/maker-core 全 PASS);maker-core 与 desktop typecheck 0 错;check:i18n-glossary ✅;check:design-inventory ✅。新增测试 7 条:spawn-only 路由不重放/不终止、重放被拒终止、非 no-op 切模落在目标模型、两码互不误判、跨 target 回读不被吞、loss guard 错误类型与 IPC 码。

…undeclared

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
@zyjisdog

Copy link
Copy Markdown
Contributor Author

已并入 upstream/main(45 commits,含 #4854「未知图片能力默认按支持处理」),51ee475ed,冲突已解、PR 恢复 MERGEABLE。

冲突点的处置(packages/maker-core/src/agents/pi/index.ts,双方改的是同一段 assertImageInputSupported):

  • 采用 upstream fix(pi): 未声明图片能力时默认允许图片输入 #4854 的语义:目录未声明 input 时按支持图片算((nativeModel.input ?? ["text","image"]).includes("image")),不再一律拒收 —— 与全仓其它判定口径一致。
  • 我的双向对账(先 refresh 再判、runExclusivePiRpc 补链、延后期间 pending 拒收)整段保留。
  • 关键点:对账的「快照基线」口径必须跟着改 —— 否则对一个没写 input 的模型声明 false 时,基线(旧写法 false)与目标值相同会被判成「无变化」,声明永远落不下去。现在基线同样用 ?? ["text","image"](hadImage),并加了注释说明两边必须同口径。

验证:pnpm test:unit:related EXIT=0;maker-core / desktop typecheck 0 错;pi-provider-routing 98 条全过(我的 7 条 + upstream #4854 的 3 条);check:i18n-glossary ✅;check:design-inventory ✅(surface 数从 50 → 51,upstream 新增)。

@zyjisdog

Copy link
Copy Markdown
Contributor Author

@greptileai review

Comment thread apps/desktop/src/renderer/hooks/useModelCatalogImageInput.ts
Comment thread apps/desktop/src/main/maker-ipc/providerHandlers.ts
Greptile 第四批(两条 P2)的处置:\n- 写 A 未完成 → 切 B → 切回 A:A 的首次读取当时被跳过,旧写入回声又被 generation 守卫\n  丢弃,界面会永远停在 loading 占位(显示成「跟随供应商」且点击无效)。现在写入落定时若\n  回声丢失、且用户又回到同一行,补一次读取(用户停在其他行时不补 —— 那一行 key 不同,\n  本就没被跳过)。\n- store 拒绝写入时给出「先修正 model-catalog-overrides.json」的指引,但 hook 只回 false,\n  抽屉只显示通用「保存失败」——用户反复失败却看不到唯一的修复方式。现在 hook 暴露\n  errorReason(去掉 [CODE] 机器码前缀),抽屉用带 {{message}} 的文案提示;无原因时仍是\n  原通用文案。5 语同步新增 saveFailedWithReason。\n- 新增测试:切走再切回补读(不补读则停在 loading)、errorReason 去前缀、抽屉带原因提示。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
@zyjisdog

Copy link
Copy Markdown
Contributor Author

@greptileai review

@zyjisdog zyjisdog closed this Sep 22, 2026
@zyjisdog zyjisdog reopened this Sep 22, 2026
upstream 新增的设置面板文件(makecindy#4813 Cindy Make 等)让 desktop.settings 的裸圆角计数 717 → 718;\n合并后 GENERATED 区块与源码不同步会让 scripts/__tests__/design-inventory.test.mjs 的\n「CLI --check 在当前台账上通过」失败(Windows/Linux 两个分片都红)。按设计门禁要求\n重算台账(只改这一个统计单元格,人工区保留)。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
@zyjisdog

Copy link
Copy Markdown
Contributor Author

CI 红的两条根因(已定位,一条已修,一条在 upstream/main 上)

① 已修:design-inventory 台账落后(我的分支落后 upstream)

c864c6204:并入 upstream/main 后,upstream 新增的设置面板文件(#4813 Cindy Make 等)让 desktop.settings 的裸圆角计数 717 → 718,scripts/__tests__/design-inventory.test.mjs 的「CLI --check 在当前台账上通过」因此在 Windows/Linux 两个分片都红。按门禁要求重算台账(只改这一个统计单元格,人工区保留)。

顺带说明这条为什么只在 CI 出现、本地是绿的:CI 跑的是 PR 与当前 upstream/main 的合并结果,而本地 worktree 还停在我上一次合并的基点(6210df89d)。upstream 在这之后又走了 8 个提交。

② 未修(不是本 PR 引入):cindyMakeInlineEntry.test.ts 在 upstream/main 上就是红的

src/renderer/__tests__/cindyMakeInlineEntry.test.ts > Cindy Make composer presentation
  > keeps question, plan and permission prompts ahead of the first-execution input lock
AssertionError: expected -1 to be greater than 222686

这是 source-contract 断言:测试在 CCAgentSessionView.tsx 里找字面量 ") : cindyMakeRecoveryId && session ? (",而 #4813 把该处改成了先起一层 ) : (、再由 topSlot={cindyMakeRecoveryId && session ? ( 承载,字面量已不存在(indexOf 返回 -1)。

证据(都在 upstream/main 上验证,与本分支无关):

  • git show upstream/main:.../CCAgentSessionView.tsx | grep 只命中 topSlot={cindyMakeRecoveryId && session ? (,没有 ) : 前缀那一版;
  • git show upstream/main:.../cindyMakeInlineEntry.test.ts 仍然断言带前缀的字面量;
  • upstream/main 自己的 client-ci 在 7edd27be4 上也因同一条测试在 Linux/Windows 两个分片失败(run 35732083639)。

本 PR 没有触碰任何 cindyMake* 文件(git diff --name-only upstream/main...HEAD | grep -i cindyMake 为空)。

另外两条观察(非本 PR 问题)

  • packages/anthropic-compat-proxy/src/oversized-attachments.test.ts 在 Windows 分片偶发失败,单跑 18/18 通过 —— 高负载下的时序假失败。
  • 本地 pnpm test:unit:related 偶发 cindySubagentRunner.test.ts 同类假失败,单跑必过。

②需要 upstream 侧修(改一行断言字面量)或由维护者决定是否接受这条红;本 PR 侧不再夹带无关改动。

@MagicLizi

Copy link
Copy Markdown
Contributor

这条 PR 需要维护者确认后才能继续自动审查和合并。

当前拦截是产品 / UI:设置高级抽屉新增「图片输入」三态声明(跟随供应商 / 支持 / 不支持),图片失败文案也变了。架构面上新增两条本机 IPC,并在切模、发图时按目录声明热重载 Pi 会话。design-inventory.md 只把生成计数从 717 改成 718,不改变规则含义。

请维护者在 PR 上 Approve;需要作者修改时请 Request Changes。讨论:#4889

@MagicLizi MagicLizi added awaiting-discussion 等待维护者讨论(review-pr) touches:core 改动碰到架构核心路径(review-pr 自动维护,仅展示) touches:large-diff 改动量较大(review-pr 自动维护,仅展示) touches:product-ui 改动碰到产品 / UI 面(review-pr 自动维护,仅展示) touches:rules 改动碰到规则 / 规范文档(review-pr 自动维护,仅展示) labels Sep 22, 2026
zyjisdog added a commit to zyjisdog/cindy that referenced this pull request Sep 27, 2026
该行由 PR 侧(旧台账)带入:makecindy#4856 的 conflictPolicy 取 PR 侧后,门的 design:inventory 重新生成 GENERATED 区,人工区里的 mobile.automations 已无对应 ID → check:design-inventory 报孤儿行并 exit 1。上游台账里没有这一行(surface 已下架),删除即对齐。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
(cherry picked from commit 11993b24461d0e0e2579bcc44a9a9214a3844e30)
zyjisdog added a commit to zyjisdog/cindy that referenced this pull request Sep 28, 2026
Upstream-PR: makecindy#4856
Upstream-PR-Head: 72935c6
Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
zyjisdog added a commit to zyjisdog/cindy that referenced this pull request Sep 28, 2026
该行由 PR 侧(旧台账)带入:makecindy#4856 的 conflictPolicy 取 PR 侧后,门的 design:inventory 重新生成 GENERATED 区,人工区里的 mobile.automations 已无对应 ID → check:design-inventory 报孤儿行并 exit 1。上游台账里没有这一行(surface 已下架),删除即对齐。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
(cherry picked from commit 11993b24461d0e0e2579bcc44a9a9214a3844e30)
zyjisdog added a commit to zyjisdog/cindy that referenced this pull request Sep 29, 2026
Upstream-PR: makecindy#4856
Upstream-PR-Head: 72935c6
Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
zyjisdog added a commit to zyjisdog/cindy that referenced this pull request Sep 29, 2026
该行由 PR 侧(旧台账)带入:makecindy#4856 的 conflictPolicy 取 PR 侧后,门的 design:inventory 重新生成 GENERATED 区,人工区里的 mobile.automations 已无对应 ID → check:design-inventory 报孤儿行并 exit 1。上游台账里没有这一行(surface 已下架),删除即对齐。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
(cherry picked from commit 11993b24461d0e0e2579bcc44a9a9214a3844e30)
…undeclared

# Conflicts:
#	apps/desktop/src/main/maker-ipc/register.ts
#	docs/design-rules/design-inventory.md
@zyjisdog

Copy link
Copy Markdown
Contributor Author

@greptileai review

Comment thread packages/maker-core/src/agents/pi/index.ts Outdated
Comment thread apps/desktop/src/main/maker-ipc/providerHandlers.ts
处置合并 upstream 后的两条 P1 review:\n- 安全:图片能力写入口只拒 xd,漏了组织托管供应商。override 是本机最高优先级,不加这道门\n  就能让运行期能力偏离管理员下发的配置。现在与价格 override 同一位置、同一 PERMISSION_DENIED\n  口径拒写,抽屉控件一并 disabled(与 xd 网关同一约定;按钮禁用挡不住受信 renderer 直调,\n  IPC 侧的拒写是真正的那道门)。\n- 恢复继承不生效:用户把「显式不支持」改回「跟随供应商」后 override 被删、声明读回\n  undefined,对账因 fresh===undefined 直接早退,旧会话快照停在纯文本;而上游 makecindy#4854 的语义是\n  「未声明=支持」,于是旧会话一直错误拒收图片,只能重开任务。\n  难点:这种状态与「目录自己标了 input:['text']」在 readModelImageInput 看来完全一样(都是\n  undefined),快照自身又已被上一轮对账改成纯文本,二者无法区分。因此新增 deps 钩子\n  readModelCatalogImageCapability —— 活动目录的**最终结论**(未声明落到默认支持,口径与\n  resolvePiNativeProviders 的 input 映射一致),对账在目录未声明时据此决定目标值;拿不到结论\n  则保持现状不翻转(不猜)。\n- 测试:新增「先声明不支持 → 改回跟随供应商 → 旧会话恢复支持图片」、组织托管供应商拒写、\n  抽屉控件 disabled 共三条;既有 112 条 pi 对账与 249 条 desktop 相关测试全过。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
@zyjisdog

Copy link
Copy Markdown
Contributor Author

@greptileai review

与 makecindy#4856 的图片输入能力声明同构:设置 → 模型 → 高级设置里让用户自己声明该模型
支持哪些思考档位,落到本机 model-catalog-overrides.json 的模型级 base patch
(efforts + defaultEffort),不写回 Server、不改连接配置。

为什么需要:目录里的档位决定 Pi 能不能开真正的 thinking 通道
(piThinkingLevels:reasoning !== true → 零档位)。上游把会推理的模型声明成
「不支持思考」时,Pi 没有通道可用,模型推理只能随 content 返回、被 Cindy 当
普通 assistant 正文渲染(实报:协同 worker 切到 space-bunny-free 后正文变成
推理叙述)。此前没有任何入口能声明这件事。

- 写入面按精确 (providerId, modelId) 键;agent 只用于读回合成 base+perAgent
  与目录成员校验 —— 与图片输入 override 同一形状,不是本功能多出来的维度。
- 多引擎 id 双写:桥接投影两端 id 不同(如 chatgpt/ 前缀),只写主展示引擎的
  id 会让声明对运行期无效、UI 却显示已声明。
- 非法档位与不自洽组合(默认档不在集合内、空集合带默认档)在 IPC 边界就拒,
  不让 sanitize 事后把整条判无效、用户看着「保存了但没生效」。
- 写盘后重注 override + 刷新目录 + 广播 PROVIDER_CHANGED,renderer 据此刷新。
- 拒写口径与图片输入 override 对齐:xd(服务端目录控制)与组织托管供应商
  (管理员下发)本机不可覆盖,IPC 侧 INVALID_PARAMS / PERMISSION_DENIED 兜底,
  抽屉控件一并 disabled。
- Pi 侧防御:目标模型在启动快照里没有任何思考档位时,setEffort / setThinkingEnabled
  记 warn 而不是静默照发,让这类目录错报能在日志里定位,而不是只能翻库看消息构成。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
@zyjisdog zyjisdog closed this Sep 30, 2026
@zyjisdog zyjisdog reopened this Sep 30, 2026
Comment thread apps/desktop/src/main/maker-ipc/providerHandlers.ts Outdated
Comment thread apps/desktop/src/renderer/components/settings/ModelAdvancedDrawer.tsx Outdated
Comment thread packages/maker-core/src/agents/pi/index.ts
Comment thread apps/desktop/src/main/maker-ipc/providerHandlers.ts Outdated
Comment thread apps/desktop/src/renderer/components/settings/ModelAdvancedDrawer.tsx Outdated
Comment thread apps/desktop/src/main/maker-ipc/providerHandlers.ts
处置 Greptile 对思考档位声明的 5 条意见:

- 显式「不支持思考」存不下来:IPC 无条件拒空数组,而底层 sanitize 明确支持
  efforts: [] 配 defaultEffort: null。抽屉第三态因此永远保存失败。空数组改为合法,
  另补「空集合不得带默认档」的边界用例。
- 默认档被静默改成排序第一档:写入侧拿不到既有默认档,只能猜 tiers[0](升序恒为
  low),会把厂商声明的默认(如 max)改掉且用户无从修正。读回视图补 defaultEffort,
  写入沿用读回的既有默认(仍在新集合内才沿用),没有就留 null 交运行期按目录回退。
- 多引擎读回失真:Pi 侧「跟随供应商」时会回落到别的引擎的声明,把 Codex 的值说成
  Pi 的实际状态;档位数组还用引用比较,每次读盘都是新数组 → 两侧声明完全一致也永远
  判成 diverged,进而触发无效落盘与整目录刷新。改为只认 Pi 侧、按内容比较。
- 声明后现有会话不生效:activeEffortSnapshot 是 startSession 一次性解析的,Pi 的
  set_model 不重读 models.json,活着的子进程仍是零档位快照,thinking 通道开不出来、
  只有重开会话才生效。新增 readModelCatalogThinkingTiers 钩子,在切模点(含同路由
  no-op 早退之前,与图片能力对账同一位置)对齐档位集合;拿不到结论不猜。
- 含 ultra 的声明被静默删除:候选列表漏了 ultra(它是合法 Effort,IPC 与 Pi 映射都
  接受),用户勾选其它档位时按固定列表重建会丢掉它,且界面无入口恢复。补入候选。

未执行的验证:全量门禁下 CustomProviderDialogAccessibility 的 hover/tooltip 用例
偶发失败一次(tooltip 角色在负载下的时序敏感);单独连续复跑 3 次均 51/51 通过,
非本改动引入的逻辑回归。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
@zyjisdog

Copy link
Copy Markdown
Contributor Author

@greptileai review

acda94ee4 已处置本轮 5 条意见(显式「不支持思考」三态可存 / 默认档不再被猜成排序第一档 / 多引擎读回只认 Pi 侧且按内容比 / 声明后切模即对齐活会话档位快照 / ultra 补入候选),逐条回复并已 resolve,麻烦对新 head 再审一轮,辛苦!

Comment thread apps/desktop/src/main/maker-host/pi-host.ts Outdated
Comment thread packages/maker-core/src/agents/pi/index.ts Outdated
处置 Greptile 对上一轮修复的三条意见:

- 关闭声明不生效:host 侧把目录里的 `efforts: []` 折成 undefined,于是「用户显式
  声明该模型不支持思考」在对账层又变成「拿不到结论」→ 保留旧的非空快照,当前会话
  仍按启动档位发 set_thinking_level。空数组是有效结论,只有「模型不在目录里」才
  返回 undefined。
- 失败切模污染快照:对账原本在切模入口就改写 activeEffortSnapshot,而入口之后还有
  目录成员校验 / effort 校验 / set_model RPC。任一步失败时子进程仍停在旧模型,快照
  却已是失败目标的档位,之后的思考开关与 effort 校验会按错误模型执行。改为「入口只
  算不落定」:同路由 no-op(不会失败)与切模成功路径才落定,失败完全不动。
- ultra 未翻译:候选列表补进了 ultra,但五份语言文件没有 thinkingTier.ultra 键,
  菜单会直接显示原始 i18n key。补齐 en/zh-CN/zh-TW/ja/ko。

守护用例的有效性已验证:把「入口即落定」的缺陷注回去,该用例转红(exit=1),
还原后转绿(exit=0)——它真的能抓住这个回归,而不是只跑个形式。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
@zyjisdog

Copy link
Copy Markdown
Contributor Author

@greptileai review

Comment thread packages/maker-core/src/agents/pi/index.ts
…thinking

上游重写了 Pi 切模的落定区(preview 预检 + 压缩预留 set_compaction_reserve_tokens
+ get_state 复核 verifiedWindow + effectiveProviderId),与本 PR 的思考档位对账
落在同一段。合并要点:

- 对账仍留在「同路由 no-op 之前」(preview 校验之后、任何会抛错的校验之前)算,
  落定只发生在两处:上游的 routeUnchanged 早退分支与切模成功路径;失败完全不动快照。
- 旧版「读 set_model 响应 + switch_session 重载」的窗口复核整段让位给上游新版:
  上游已把 set_model 响应降为不可信,复核必须以子进程自报的 get_state 为准。
- 随上游删除已无引用的 xAI 专用热刷新(needsXaiCatalogReload / refreshLiveXaiCatalog),
  由通用 refreshLiveCatalog 取代。
- 守护用例的错误断言改为 /set_thinking_level refused/:上游把措辞改成了
  「is not in Pi's applied model catalog」,断言「拒绝」这个事实而不锁死措辞。

验证:typecheck 干净;Pi 侧 122/122;desktop 相关面全过;pnpm test:unit:related
EXIT=0(17 个工作区全 PASS)。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
处置 Greptile 的一条 P1:用户为目标模型声明新思考档位后,随即带着新 effort 切模,
预检仍用会话启动时冻结的 `nextEffortSnapshot`(`resolveAppliedEffortSnapshot` 的
结果),新声明的档位不在其中 → RPC 发出前就被判「不可用」,切模失败。

改为:预检用对账读到的目录结论(`pendingThinkingTiers`),没有对账结论时才回落到
启动快照。预检只是校验,活动快照的落定仍只在两处成功路径(routeUnchanged 早退、
切模成功),失败路径不动 —— 不破坏「入口只算不落定」的纪律。

守护用例 `preflights a freshly declared effort tier against the catalog, not the
frozen startup snapshot` 已做变异验证:把预检改回启动快照后该用例转红(exit=1),
还原后转绿(exit=0)。

验证:typecheck 干净;Pi 侧 123/123;pnpm test:unit:related EXIT=0。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
@zyjisdog

Copy link
Copy Markdown
Contributor Author

@greptileai review

…thinking

上游重构了高级抽屉(canEditProtocol 提前、路由窗口简化为 routeWindow、
上下文提交去掉在途锁),与本 PR 的能力声明/上下文修正落在同一段。合并要点:

- 上游重构一律取上游(canEditProtocol / routeWindow / commitCtxDraft 结构)。
- 「上游最大上下文」这一行保留本 PR 的 declaredWindow 三级来源判定
  (contextWindowMax → 已验证 contextWindow → 未声明):它排除「自定义模型缺元数据的
  200K 展示兜底」,避免把兜底值印成上游下发窗口(实测报障:圆环 1.0M、这里 200K)。
  routeWindow 是路由展示用的乐观值,两者用途不同,不可互换。
- 提交在途锁接回 commitCtxDraft:取上游结构时不能连带丢掉 ctxCommitting,否则
  displayedLimit 会在 promise resolve 前闪回一帧旧值。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
@zyjisdog

Copy link
Copy Markdown
Contributor Author

@greptileai review

zyjisdog added a commit to zyjisdog/cindy that referenced this pull request Oct 1, 2026
Upstream-PR: makecindy#4856
Upstream-PR-Head: 6d0465d
Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
zyjisdog added a commit to zyjisdog/cindy that referenced this pull request Oct 1, 2026
该行由 PR 侧(旧台账)带入:makecindy#4856 的 conflictPolicy 取 PR 侧后,门的 design:inventory 重新生成 GENERATED 区,人工区里的 mobile.automations 已无对应 ID → check:design-inventory 报孤儿行并 exit 1。上游台账里没有这一行(surface 已下架),删除即对齐。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
(cherry picked from commit 11993b24461d0e0e2579bcc44a9a9214a3844e30)
…thinking

上游在这 306 个提交里重构了 Pi 的运行时目录状态:新增 liveRuntimeSettings 作为单一
真源、queryNativeRuntime(inspect/refresh) 接管压缩预留、no-op 判定收敛为
routeUnchanged + preview.action。与本 PR 的图片能力对账/思考档位对账落在同一段。

合并要点:
- 删除本 PR 上一轮合并遗留的 projectedReserve 预检:上游已把压缩预留的重算与比对
  交给 queryNativeRuntime,保留会造成两套口径重复。
- no-op 落定点跟随上游的新判定(不再比较 oldReserve/targetReserve),保留本 PR 的
  思考档位落定:同路由 no-op 不发 set_model、不会失败,是安全落定点。
- restoreNativeCatalog / restoreNativeCatalogOrTerminate 保留:上游已删除同名物,
  但本 PR 的图片能力回滚(rollbackBaseline → 回滚旧快照并终止)在用。
- 思考档位对账仍在「同路由 no-op 之前」只算不落定;预检用对账结论。

新增依赖(@noble/curves)来自上游 mobile 改动,已 pnpm install。

验证:typecheck 干净;Pi 侧 127/127;desktop 相关面 266/266。
全量门禁有 4 条与本改动无关的失败(bot-import/60s、mirrorCachePurgeQueue/30s、
pi-package-store-security、pi-startsession-cleanup),逐个单跑均全过
(299/299、152/152),属本机全量并行下的负载偶发。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
zyjisdog added a commit to zyjisdog/cindy that referenced this pull request Oct 5, 2026
Upstream-PR: makecindy#4856
Upstream-PR-Head: 10ebb7d
Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
zyjisdog added a commit to zyjisdog/cindy that referenced this pull request Oct 5, 2026
该行由 PR 侧(旧台账)带入:makecindy#4856 的 conflictPolicy 取 PR 侧后,门的 design:inventory 重新生成 GENERATED 区,人工区里的 mobile.automations 已无对应 ID → check:design-inventory 报孤儿行并 exit 1。上游台账里没有这一行(surface 已下架),删除即对齐。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
(cherry picked from commit 11993b24461d0e0e2579bcc44a9a9214a3844e30)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting-discussion 等待维护者讨论(review-pr) touches:core 改动碰到架构核心路径(review-pr 自动维护,仅展示) touches:large-diff 改动量较大(review-pr 自动维护,仅展示) touches:product-ui 改动碰到产品 / UI 面(review-pr 自动维护,仅展示) touches:rules 改动碰到规则 / 规范文档(review-pr 自动维护,仅展示)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants