Conversation
目录未声明 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>
|
对抗审核与 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>
|
@greptileai review |
第二轮对抗审核的修复:\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
left a comment
There was a problem hiding this comment.
第二轮独立对抗审核(对上一批修复本身)
审核对象: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(已修)
- 回合延后期间把图片放行给旧清单的子进程:延后只对齐宿主快照,而准入门读的就是这份快照 → 立即放行;子进程 models.json 还是旧值。改为 pending 且本次需要该能力时抛可重试的
PI_IMAGE_CAPABILITY_REFRESH_FAILED(不再假装已生效)。 currentSpec === undefined(启动后才新增的模型行)假装对账成功:写盘是空操作,却照样置 pending + 触发重载,而快照始终没变。改为显式跳过 + warn。- 新错误码不会在对话里本地化:
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>
|
已并入 upstream/main(45 commits,含 #4854「未知图片能力默认按支持处理」), 冲突点的处置(
验证: |
|
@greptileai review |
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>
|
@greptileai review |
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>
CI 红的两条根因(已定位,一条已修,一条在 upstream/main 上)① 已修:
|
|
这条 PR 需要维护者确认后才能继续自动审查和合并。 当前拦截是产品 / UI:设置高级抽屉新增「图片输入」三态声明(跟随供应商 / 支持 / 不支持),图片失败文案也变了。架构面上新增两条本机 IPC,并在切模、发图时按目录声明热重载 Pi 会话。 请维护者在 PR 上 Approve;需要作者修改时请 Request Changes。讨论:#4889 |
该行由 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)
Upstream-PR: makecindy#4856 Upstream-PR-Head: 72935c6 Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
该行由 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)
Upstream-PR: makecindy#4856 Upstream-PR-Head: 72935c6 Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
该行由 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
|
@greptileai review |
处置合并 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>
|
@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>
处置 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>
|
@greptileai review
|
处置 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>
|
@greptileai review |
…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>
|
@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>
|
@greptileai review |
Upstream-PR: makecindy#4856 Upstream-PR-Head: 6d0465d Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
该行由 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>
Upstream-PR: makecindy#4856 Upstream-PR-Head: 10ebb7d Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
该行由 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)
这次改了什么
摘要
让用户能在设置 → 模型 → 高级设置里,为任意一个模型声明上游目录没说清、但实际成立的模型能力,声明只落在本机
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文档、测试或工程维护范围
readModelCatalogOverrides):新增 store 写入函数、IPC handler、preload 桥、抽屉 UI 控件区;非法组合在 IPC 边界就拒,不让 sanitize 事后把整条判无效、用户看着「保存了但没生效」supportsImageInput三态(跟随 / 显式支持 / 显式不支持);运行期对账按声明决定是否拒收图片efforts+defaultEffort,三态 + 逐档勾选;Pi 侧无档位时记 warn 而非静默照发「上游最大上下文」不再拿 200K 展示兜底冒充上游窗口(按
contextWindowMax→ 已验证contextWindow→ 未声明 三级来源,第 3 条必须排除「自定义模型缺元数据时的 200K 兜底」——那个常量会让用户以为容量只有 200K,而运行期窗口其实取模型级上下文上限);上下文上限提交在途时不再闪回旧值(ctxCommitting收口)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 遍历。怎么验证的
自动验证
手工验证
space-bunny-free这类目录声明「零档位」但实际会推理的模型,声明其真实档位(max/xhigh/high/medium/low)→ 该模型在 Pi 侧能开真正的 thinking 通道,推理回到可折叠思考块,不再混进正文。未执行的验证
models.json的实测(reasoning: false+ 六档全null)。风险
风险分类
影响与回滚
权限 / 安全(本 PR 的关键门):override 是本机最高优先级,不加这道门就能让运行期能力偏离管理员下发的企业配置。因此写入面对两类来源拒写:
xd(Cindy AI Gateway,能力由服务端目录控制)→INVALID_PARAMSPERMISSION_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 全部逻辑在客户端,回滚无服务端残留。提交前检查
git commit -s,见 DCO)design-inventory已重跑生成器并保持最新)