Skip to content

feat(desktop): 任务级上下文窗口档位 - #4598

Open
zyjisdog wants to merge 30 commits into
makecindy:mainfrom
zyjisdog:feat/session-context-window
Open

zyjisdog wants to merge 30 commits into
makecindy:mainfrom
zyjisdog:feat/session-context-window

Conversation

@zyjisdog

@zyjisdog zyjisdog commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

feat(desktop): 任务级上下文窗口档位
---HEAD: feat/session-context-window

这次改了什么

摘要

给单个任务加了「工作上下文窗口档位」:用户可以在任务里选择这个任务最多用多大的上下文窗口(tokens),与设置页的自动压缩阈值百分比正交——档位决定分母/容量,百分比决定触发点。

动机有两类:

  1. 分档计费模型:窗口档位直接决定落在哪条价带(如 Qwen3.7 Plus 的 ≤256K / >256K 两档单价差 3 倍)。用户按任务选择档位 = 直接选成本。
  2. 不分档计费模型:每轮输入都按上下文 tokens 计价,把任务压到一个更小的运行窗口能实打实降低平均每轮成本(也更少踩长上下文的质量衰减)。

关联 Issue:#3464(「按工种/角色预设上下文档位」,维护者分析结论:产品缺口是任务级策略与入口,contextWindow 必须继续来自目录,不能由 session 数据库改写)。本 PR 落的是任务级入口本身,且从不超过目录声明的 contextWindowMax。

菜单能给到什么范围(统一口径;此前正文这里有自相矛盾的两处表述,已按实现改正):

  • 向下与向上都可以:档位基准是该路由的有效上限 = min(目录物理上限 contextWindowMax, 用户在同路由设过的模型级「上下文上限」);比例档取基准的 25% / 50% / 100%,所以当有效上限高于模型默认窗口时,菜单里会出现比默认窗口更大的档位(算术示例:基准 1.05M、默认 272K → 默认 / 50% · 525K / 100% · 1.05M 三档)。
  • 上限只有一道:任何档位都 ≤ 有效上限,绝不越过目录声明的 contextWindowMax;contextWindowMax 原始字段不进 renderer 投影(不包含项见下),但 main / 被控端解析出的有效上限会随 maker:get-context-window-bounds 下发给 chip,作为档位基准。
  • 唯一「只能收紧」的场合:device-link 远程任务拿不到被控端权威边界时(老被控端 / 拉取失败),控制端只用本地已知默认值当上限,卡片里明示「更大的窗口需要到被控设备本机调整」。
  • 想「这条路由默认就别跑满 / 干脆锁更小」→ 设置 → 模型 → 高级 → 「上下文上限」,它同时是档位基准。

变更类型

  • feat 新功能

范围

  • 关联 Issue / 需求:希望支持按工种/角色预设上下文档位,缓解长文档审阅超限 #3464
  • 本 PR 包含:
    • 任务预算存 main 侧偏好文件(owner 隔离 userData 下的 session-context-budget-prefs.json,形如 { budgets: { "<sessionId>": tokens } },null / 缺条目 = 跟随模型默认),读写集中在 session-context-budget-store.ts;不新增数据库迁移、不改 schema、不写会话行 —— DB 模式停在官方 0113;
    • 为什么不是会话列:任务预算最初写成 sessions.context_window_budget + 新迁移。那会改写迁移链身份:库一旦记上本分支独有的迁移,任何不带该迁移的包(例如官方包)启动时会以 applied migration runtime identity changed at seq ... 拒绝启动,反向亦然 —— 同一份用户库无法在官方包与本分支包之间切换。任务级窗口是纯客户端偏好,不该换来不可逆的库状态;pnpm --filter desktop db:validate 现在报 0000..0113、no schema drift;
    • 有效窗口解析 min(任务预算 ?? 模型级上限, 模型级上限, 目录 contextWindowMax);
    • 三引擎下发(Claude Code / Pi / Codex)与切模时按目标路由重新收敛;
    • 保存后应用到活实例(空闲关 handle 冷重建 / 回合中登记 pending);
    • renderer 的档位 chip(任务底栏,紧邻上下文圆环)+ 价带展示(每条档位标出该档的输入单价)+ 五语言文案 + 测试;
    • device-link 远程任务的档位命令面:新 channel maker:set-context-window-budget(进 allowlist),控制端经隧道让被控端写入自己的偏好文件并应用;老被控端回 CHANNEL_NOT_ALLOWED → 提示「该设备版本不支持」。
  • 明确不包含(后续工作):
    • 移动端 / IM / 定时任务的选择入口:这些任务跟随模型默认;IM 渠道级默认可后续参考 ImDefaultSettingsSection 加;
    • 目录 contextWindowMax 原始字段的 renderer 投影(第 4 轮 review 时已撤掉端到端透传):renderer 不直接读目录字段,档位基准一律走 main / 被控端解析出的有效上限。这不等于「只能调小」——有效上限高于默认窗口时菜单就会给出更大的档位(见上面的统一口径);
    • 低于任务基线的动态地板(先靠 CC 自带 thrashing 检测 + host rollover + chip 的缩档提示兜底)。
    • 新任务「第一条消息前」的选择入口(已知边界):新任务入口 /cc-agent/new 是 transient draft(没有后端 session,
      ChatInput 的 sessionId 为 undefined),而任务级窗口是按会话存的,所以第一条消息按模型默认窗口跑;
      任务创建后底栏 chip 立即可调,改动从下一条消息生效(与卡片文案一致)。想「这条路由默认就别跑满」走
      设置 → 模型 → 高级 → 「上下文上限」(全局,同时是档位表的基准)。草稿期选择(把预算存进草稿、createSession
      时落库)是独立增量,单独一版做。
  • 用户可见变化:任务底栏新增「上下文窗口」档位 chip(本机 / SSH 远程 / device-link 远程任务都可见);下拉里每条档位标出该档的输入单价。
  • 是否存在 breaking change:无。未自定义的任务完全跟随原判定(老任务零行为变化),context_window / context_window_runtime 快照口径不变。

语义与生效时机(如实说明)

  • 档位选择不快照默认值:选「模型默认」上报 null,目录默认变化能继续跟随(符合 configuration-and-overrides.md 的 override 纪律)。
  • 档位只改分母:选 300K 后仍按设置页的 90% 在 ~270K 触发压缩。
  • 生效时机:空闲任务立即关 handle、下一条消息冷重建生效;回合进行中登记 pending,回合结束后生效。UI 文案如实写明,不做「立即生效」的假承诺。
  • 优先关系:任务预算不得越过用户在同一路由上设过的模型级「上下文上限」(两者取更紧者);模型级上限被改紧时,既有任务预算会让位。

UI 变化

  • 引用的设计规范:docs/design-rules/DESIGN.md §2(颜色一律走语义 token:chip 用 --msg-tool-card-chevron / --text-secondary / --warning-fg,与货币 chip、「用量明细」卡同源)、§4(复用标准 Popover 原语——与「用量明细」卡同一个浮层,不新造几何)、§5(chip 是按钮 → 胶囊,不新造外层卡片)、§10(Light/Dark 都由 token 覆盖,无单模式补丁)、§11(微文案:说明生效时机与「档位 ≠ 压缩目标」)。
  • 档位规则(最终形态):菜单给的是「模型默认档 + 占模型有效上限(min(目录物理上限, 该路由模型级上限))25%/50%/100% 的比例档」,最多三档,超出时优先保留大比例;比例档值按 round(基准 × 百分比) 精确换算(1,048,576 的 50% 就是 524,288,不贴 256K/512K 网格);低于 MIN_PERCENT_TIER_TOKENS(200K)或与默认档重合的不出现;100% 档只在有效上限高于默认窗口时出现(保证能开满窗口:基准 1.05M 而默认 272K 时,三档是 默认 / 50% · 525K / 100% · 1.05M;这条方向由 keeps the 100% tier so a lowered default can still reach the physical max 与 chip 的 offers the route max tier so a session can raise the window again 两个用例守着);显示为 25% · 250K(K 段一位小数、M 段最多两位,落库仍是精确整数);每一行都是「百分比 · 绝对值」(含默认档与旧值补的当前档:默认档 = 100% · 1.05M 模型默认,Codex 形态默认档 = 26% · 272K 模型默认),不再有「默认档不带百分比」的例外。
  • 触发器与底栏的视觉契约(多轮实机打磨后定稿):chip 上只有「标记 + 当前档位百分比」,当前窗口的绝对值进 aria-label(triggerLabel)与卡片行。标记用 ↕(U+2195)而不是括号/图标 —— 括号语义不明确。字号 text-11、字重/颜色与货币 chip 的 ¥ 同一 token(--msg-tool-card-chevron)并同样 opacity-60;text-11 是按墨迹选的:12px 下 ↕ 墨迹 12px(上缘 11/下缘 1)比 ¥(9/0)和圆环 1%(9/1)高 2px,11px 下 ≈10.1/0.9,三者的墨迹高度与视觉中心都在亚像素级。
  • 交互与「用量明细」卡完全一致:指针悬浮展开(延迟 300ms / 离开宽限 200ms,常量在 lib/statusBarCards.ts,与 TodaySpendChip 同值),不需要点击;键盘仍可 Enter/Esc,且键盘路径保留 focus 指示(指针路径关闭时不还焦点,避免 Chrome 判成 :focus-visible 出现蓝圈)。卡片改用与 ¥ 卡同一个 Popover 原语(Radix 菜单展开会抢焦点,做不了悬浮卡),档位行是 role="radio" + aria-checked。
  • 唯一窗体:新增 renderer/lib/statusBarCards.ts 协调器,两张底栏卡片互相注册 —— 一张展开时另一张立刻收起(实测四个方向都只有一张卡)。选中档位后卡片不自动关(勾选看得见),鼠标移开才收。
  • 菜单里所有次要文字(说明行、「模型默认」与单价注解)统一 --text-secondary(即「用量明细」卡里说明文字的同一档灰,实测 rgb(111,111,111));字号/字重也与该卡同套:标题 12/500、说明 12/400、档位行 13/500、注解 12/400。chipLabel / percentBasis 两个词条已从 5 语言删除。
  • 菜单行形态统一为「百分比 · 绝对值」(默认档 = 100% · 1.05M 模型默认),百分比与绝对值同色同节点,不再出现「默认档绝对值在前 / 比例档百分比单独上色」的两种行。
  • 截图(Dark 主题,CN dev 沙箱;图片随本分支提交在 .github/pr-assets/):
    • 底栏:¥ ↕ 100% ◯ 1% —— 标记与货币符号同色同透明度、与圆环并列同基线
    • 悬浮卡(指针悬停 ↕ 自动展开,无需点击):档位行 25% · 262.1K / 50% · 524.3K / ⦿100% · 1.05M 模型默认

底栏

悬浮卡

Review 轮次修复(对抗性 review 第 1 轮,4 个 P1)

本 PR 在本地经过一轮对抗性 review(模型:z-ai/glm-5.3-flash,范围 = 单 commit vs upstream/main)。四条 P1 均已复核为真并修复:

  1. 回合中改档位会静默吞掉用户刚选的模型:预算 applier 原本无条件以「当前 live 模型」调 applyRuntimeSetModelChange,而 PendingCredentialSwitchService.register 是整体替换 —— 用户先切模型 B、再调档位,pending 就从 B 变成 A(且无任何提示)。修复:与目录刷新共用 hasPendingRuntimeSelection 守卫(跨引擎切换意图 / 显式路由变更 / 凭证切换),有待定选择时只落库不动 pending;pending 自己的收口重建会经 prepareStartOptions 读到最新预算。
  2. 带预算的任务热切到收敛窗口更小的模型时,CC 子进程 env 停在旧窗口:引擎侧 requiresModelSwitchRebuild 在预算模式下对任意目标模型都返回「预算 == live env」,探测不到失配;宿主侧窗口保护闸也只按用量判危,用量不高时不会拦。修复:宿主在切模时比较目标路由收敛后窗口与当前窗口,不同即强制重建(不依赖引擎自检)。
  3. 未核实路由上的任务预算可以越过目录物理上限:resolveConfiguredContextWindow 的 unverified 回退分支直接用预算值,而目录里真实存在「contextWindowVerified=false + contextWindowMax 有值」的形态(发现流程只拿到 max_context_window 时)。修复:任务预算即使未核实也先按目录声明的上限夹紧(模型级上限保留既有「路由配错时用户可强行解开」的逃生口语义);同时修掉启动时非法显式预算透传给引擎的漏斗缺口。
  4. UI 无法表达「在目录上限内调大」:buildContextWindowBudgetOptions 的上限档分支在 renderer 侧拿不到 contextWindowMax(能力投影里没有),等于死代码。修复:contextWindowMax 进 availableModels 展示投影(catalog-to-descriptors → maker-core ModelDescriptor → renderer ModelDefinition),chip 用目录上限推导上限档;标注为纯展示元数据,运行期收敛仍按会话实际路由。

同时补测:未核实路由夹紧、contextWindowBudgetChangesEffectiveWindow(applier 的「是否真的变了」判定,原先无覆盖)。

Review 轮次修复(对抗性 review 第 2 轮,6 个 P1)

第 2 轮换模型(meta/muse-spark-1.3-contributor,provider commandcode)独立重审,结论仍是「不建议按当前 commit 推送」,报 6 条 P1。处置如下:

发现 判定 处置
chip 档位 useMemo 缺 maxWindow 依赖 → 目录上限变化后上限档永久过期 成立 deps 补全(改为 [budget, effectiveDefaultWindow, maxWindow, modelLimit, ceiling])
上限档取自 availableModels 扁平表 → 同 id 跨 provider 时可能显示另一条路由的上限 成立(这是本轮引入的) 撤掉扁平表透传,上限改由路由级解析 resolveRouteContextWindowMax(providers, agent, providerId, model)(要求唯一候选,歧义时只允许收紧),并给「歧义 → 不出现上限档」补测
chip 不读模型级上限 → 档位表与生效值系统性偏大 成立 chip 接入 useModelContextLimit(route),档位表与「有效默认档」都按 min(目录默认, 目录上限, 模型级上限) 收敛;补测
预算 pending 守卫漏掉 axis-only defer → 回合中改 effort 后再改档位会抢跑重建 不成立(轴变更在 sessionRuntimeControl 的独立 store,收口边界两套状态同时生效,登记重建 pending 不覆盖轴意图) 保留原行为,代码注释写明两套 store 的边界
pending 被取消/超车后预算无人补做 → 活实例永久停在旧窗口 成立(改档位时若什么都不做) 跳过前先沿用 pending 的目标再补 forceSessionRebuild(凭证 pending),收口关会话 → 下一条消息按 DB 新预算重建;仅轴/runtime-control pending 落到原有登记路径,保证收口一定重建
Pi 无 live 上报时 target !== verified 恒真 → 同窗切模也被强制重建 成立 Pi 未上报时用目录侧收敛值做比较基准(新增 catalogCurrentWindowForBudget),避免不必要的破坏性重建

第 2 轮同时确认了第 1 轮 4 处修复中 2 处完全成立、2 处部分成立(部分成立的部分即上表的第 2/3 行,已按上表处置)。

Review 轮次修复(对抗性 review 第 3 轮,7 个 P1)

第 3 轮仍用 meta/muse-spark-1.3-contributor 独立重审(commit 29eeefa8d),报 7 条 P1、0 条 P0。处置:

发现 判定 处置
远程任务 chip 用本机目录与本机模型级上限算档位,写却落在被控端 成立 远程(deviceId)不再使用本机 providers 解析路由:默认值退回被控端能力缓存、不给上限档、不读本机模型级上限(只允许收紧),并补测
Pi/Codex 的「路由默认档」恒回落扁平表(resolveSessionContextWindow 对 pi/codex 恒 null) 成立 新增 resolveRouteContextWindowBounds(路由唯一候选 → {providerId, defaultWindow, maxWindow},不借用 pi/codex 的运行时快照口径),chip 的默认档与上限档都用它,补 Pi 用例
providerId 缺失时 chip 放弃模型级上限,与 host 收敛分叉 成立 由 resolveRouteContextWindowBounds 回传路由解出的 providerId,据此读同一路由的模型级上限,补测
预算 applier 的 pending 分支在 withSendToSessionLock 之外先读后写,可冲掉期间新选的模型 成立 整个分支搬进锁内,pending 目标在锁内重读;有效窗口比对仍先在锁外做(纯读)
跨引擎 agentSwitchPending 直接 return → 切换被取消时预算永不生效 成立 取消该 return:跨引擎/仅轴/runtime-control pending 一律落到「以当前 live 路由登记 + forceSessionRebuild」的同一条路径,保证收口一定重建
maker:set-context-window-budget 无范围校验:非法值静默清预算或静默夹紧 成立 handler 内按 [MIN_CONTEXT_WINDOW_BUDGET, MAX_CONTEXT_WINDOW_BUDGET] 整数值校验,越界抛 INVALID_PARAMS
切模 forceSessionRebuild 判据在「当前窗不可知」时恒为真 → 无辜同窗切模也破坏性重建 成立 引入 budgetCompareWindow = verified ?? 有效窗口 ?? 当前路由收敛值;三者都不可知时不强制重建(fail-safe)
「撤掉扁平表透传」描述与代码不符(上一轮 git checkout 恢复的是已含改动的 HEAD,实际没撤) 成立 真正删除 ModelDescriptor/ModelDefinition/capabilities/catalog-to-descriptors/useAgentCapabilities 上的 contextWindowMax 透传(抽屉用的是 CatalogModel 原生字段,不受影响)

Review 轮次修复(对抗性 review 第 4 轮,6 个 P1)

第 4 轮(meta/muse-spark-1.3-contributor,对 commit a20aee2f1)报 6 条 P1、0 条 P0。处置:

发现 判定 处置
跨引擎 pending + 凭证 pending 同时存在时,改档位会用 live 路由覆盖用户已登记的模型(第 3 轮删掉 return 引入的回归) 成立 有凭证 pending 时一律原样保留其 model/provider 只补 forceSessionRebuild(不再按 agentSwitchPending 分叉);无凭证 pending 时才用 live 路由登记
同一字段两套校验:隧道严格抛错、本地 sessions:update 静默 normalize 成立 updateSessionInDb 入口加同口径校验(null 或 [1000, 1e8] 整数,越界 INVALID_PARAMS),两条入口行为一致;补 2 例测试
切模 fail-safe 窄路径:当前窗口不可知 + 目标更小 → 热切不重建,CC env 可能停在过大窗口(第 3 轮引入的回归) 成立 改回 fail-closed:只要不能证明「当前窗口 == 目标窗口」就重建(比较基准已尽力补齐,不可知即视为可能不同)
连点/双窗口并发改档位 → live 重建顺序可与落库顺序倒挂 成立 applier 在锁内重读 DB 当前预算作为 next(有效窗口比对也移到锁内),让 live 与落库终值对齐;chip 加同步在飞守卫(useRef)避免同帧连发两次写
远程任务只能收紧,但 menuHint 写「调大适合长文档」→ 误导 成立 新增五语言 remoteTightenOnly 文案并在远程任务菜单里展示,如实说明「只能调小」;补断言(注:后续版本补上被控端边界只读查询后,这条限制只在拿不到边界时生效,见下方「远程任务」一节)
未核实路由上 chip 默认档(目录声明值)与 host 生效值(null 预算 → 引擎自取默认)分叉 不采纳(记为已知限制) 该分叉只影响「模型默认档」的显示:显式档位会被 host 精确兑现(且永不越过声明上限),而「默认档」沿用产品既有的目录窗口展示约定(模型选择器/抽屉同源)。彻底修需要把 verified 溯源下发给 renderer 并新增五语言文案,属独立 UX 改动

Review 轮次修复(对抗性 review 第 5 轮,5 个 P1)

第 5 轮(meta/muse-spark-1.3-contributor,对 commit c44b4661a)报 5 条 P1、0 条 P0。处置:

发现 判定 处置
「远程只允许收紧」只有 UI 文案、无被控端强制 不按建议加限制 被控端 host 本来就会按被控端目录夹紧(与本地同一套收敛),直接调大不会越界;加「隧道拒绝放大」会凭空禁止用户在远程任务上放大窗口,与本地行为分裂。改为把文案改成描述这里给出的档位范围(「更大的窗口需要到被控设备本机调整」),不再声称设备侧策略
chip 的 useRef 在飞守卫会吞掉合法连点(第 4 轮引入) 成立 撤掉该守卫:在飞时菜单项本就是 disabled(可见禁用而非静默丢弃);跨窗口并发由 main 侧会话锁 + 终值比对收敛。测试同步撤掉
锁内重读 latestBudget 但沿用 stale previous → 同一终值连续重建两次 成立 加会话级「已按该值重建过」去重(会话被删/关掉即清理),同一终值只重建一次;补幂等日志
无凭证 pending、但有跨引擎/显式路由 pending 时,仍登记一条旧引擎凭证 pending 保留登记(取舍已写进注释) 该登记写入的路由就等于 live 路由,最坏多一次关会话/重建,不会复活旧路由;换来「跨引擎切换被取消时预算仍生效」这个更强保证 —— 第 3 轮 review 正是在这一点上要求不要 return
persistSessionFields(镜像回流)/sessionCreateToRow/fork 与新校验三口径分裂,可静默清预算 成立 镜像路径改为显式归一化 + 非法值跳过该字段并 warn(不再静默清 null);fork 继承前归一化(与 updateSessionInDb 同口径)

远程任务:按需向被控端拉取窗口边界(可放大)

上一版把远程档位收紧成「只出不大于被控端默认值的档」,原因是控制端拿不到被控端该路由的
物理上限与模型级上限(本地目录可能属于另一条路由)。这版补上事实来源:

  • 新增只读隧道 channel maker:get-context-window-bounds(进 allowlist,无 sender 依赖、
    不写任何存储):被控端按会话实际路由解析 {providerId, defaultWindow, maxWindow, modelLimit}
    —— 与运行期收敛同一套口径(同一份目录 + 同一份模型级上限 store,含隐式来源解析)。
  • 控制端 chip 在远程任务上按需拉一次(按 设备+会话+模型 缓存,提交成功后失效),拿到边界才
    放出「更大」的档位;拿不到(老版本 → CHANNEL_NOT_ALLOWED,或拉取失败)退回只允许收紧
    并如实说明,绝不拿本机目录的值冒充被控端。
  • 隧道命令本身允许放大,被控端 host 仍按被控端目录夹紧,因此「显示 == 生效 == 不越界」。

第 6 轮 review 修复(4 个 P1)

  • applier 的路由快照搬到锁内:锁外读到的是加锁前的路由,若期间用户切了模,有效窗口判定会按旧路由算成「没变」而漏掉必需重建 —— 现在锁内重读会话行后再判定。
  • 合并进 pending 的分支不再先记去重:那条路径只是登记意图,真正的重建在收口时;pending 被取消/超车时什么都没做,先记去重会把后续同值通知全挡掉,活实例永久停在旧窗口。
  • 档位表统一读 main 的权威边界:新增本地读口 maker:get-context-window-bounds(与远程同一 handler/口径,preload 暴露 getSessionContextWindowBounds),chip 本地/远程都读它 —— 跨 provider 同 id 时 renderer 解不出来源、给不出模型级上限,会给出 main 一定夹掉的档;现在这类分叉连同前几轮记的「已知限制」一起消失。
  • 远程/本机边界缓存加 TTL + 开菜单刷新:被控端改了模型级上限后不再永久陈旧(60s TTL,提交成功与每次打开菜单都会刷新)。

第 6 轮 P2 的收敛(非阻塞项,能低成本修的都修了)

  • 会话切换时 chip 不再闪现上一个会话的档位:档位按 (设备, 会话, 模型) 在渲染期取缓存值派生,不必等 effect。
  • 只读边界查询改认内存态 provider 覆盖(getSessionProvider ?? row.providerId),deferred 切模期间不再按旧路由回答。
  • 会话删除/取消切换时一并清掉预算去重表(原先只在「活实例消失」分支清,删会话场景条目常驻内存)。
  • 补测:共享边界的形状收敛(畸形/缺字段/非数/负数一律按「未知」)、远程收到畸形边界时退回「只允许收紧」。
  • 记录不动:远程档位旁的单价仍取控制端本机报价(两端区域/订阅不同时口径可能不同,窗口本身不受影响);远程拿不到边界时已存的大预算仍作为「当前档」出现在选项里(单选组需要匹配项,选中它等于无变化、不写库)。

第 7 轮 review 结论与未修项(P2,非阻塞,已记录)

第 7 轮(meta/muse-spark-1.3-contributor,对 commit 42dd47ebd)结论:0 个 P0、0 个 P1,可按现状推送/开 PR;另报 5 条 P2(按仓库口径不作为阻塞项),其中 4 条已在本轮改前顺手收敛(渲染期换锚、内存 provider 覆盖、会话语义清理、共享形状收敛测试),剩余记录如下:

  • 预算去重表清理不完整:appliedContextWindowBudgets 只在「取消切换 / 活实例消失」时清;删会话/归档不经过那两条路径 → 条目常驻到进程结束(UUID 主键,有界、不丢失数据)。另有一条窄分叉:预算应用后关掉会话 → 关闭期间改预算(applier 因无活实例 early-return,表不更新)→ 重开后再改回旧值会被去重表挡住一次,直到下次改动自愈。属「下次重建自愈」类,不阻塞。
  • chip 边界缓存 key 不含 agentKind:同会话跨引擎切换且 model 字符串相同时会短暂复用旧引擎的上限;打开菜单即重拉自愈,只影响展示,落库与生效由 main 按实际路由收敛。
  • 每次开菜单都强制重拉:TTL 因此只对「挂载后不开菜单」的后台期有效 → 远程下每次开菜单多一次隧道 invoke(正确性无碍,弱网下是可感知的多余请求)。
  • 读路径不归一化已存脏值:手改 DB 的浮点预算会在选项里补成一个「存不进去的幽灵档」(点它被 INVALID_PARAMS 拒绝并 toast 失败);运行时不受影响(tighterBudget 内会 round),仅脏数据可达。
  • activeKeyRef.current = key 写在渲染期:本例有两重守卫(cancelled + key 比对),无用户可见错误,但属 React 反模式,后续加逻辑易踩。
  • 另记录两条判定为可接受的展示口径差异:远程档位旁单价取控制端本机报价(窗口本身不受影响);远程拿不到边界时已存大预算仍作为「当前档」出现(单选组需匹配项,选中它等于无变化、不写库)。

dev 实机验证发现的问题与修复(第 8 轮,用户实测报障)

  • 现象:同一引擎内换模型是延迟切换(发送边界才落库),期间「模型选择器已指向目标模型、sessions.model 还是旧模型」。原 maker:get-context-window-bounds 只带 sessionId,main/被控端按会话行(旧路由)回答 —— 262K 的千问切到 1M 的 deepseek 后,chip 与档位表都停在 262.1K,且该答案会按目标模型作为键进入 60s 缓存,切模落库后仍显示旧值。
  • 修复:边界查询改为携带界面上正在显示的路由({ agent, providerId, model },renderer 与 device-link 远程同一形状),main/被控端按该路由解析(与运行期收敛同口径);未携带或畸形时整份退回「按会话行回答」(老调用方/老被控端行为不变);缓存键补 providerId(同一模型跨来源的上限可能不同)。新增 normalizeSessionContextWindowBoundsRoute 与「按显示路由回答」「跨来源重键」两组回归用例。
  • 实机复核(CN dev 沙箱,真实 UI,仅切模型不发消息):1cab679d 会话切到千问 3.8 27B → chip 立刻 262.1K、菜单只剩「262.1K 模型默认」;切回 DeepSeek V4.1 Flash → 立刻回 1M + 25%/50%/默认三档。main 侧 A/B 直测:不带路由 → 1,000,000(会话行),带 Qwen/Qwen3.8-27B 路由 → 262,144,畸形路由 → 退回会话行。

怎么验证的

自动验证

  • pnpm test:unit:related:全绿(含新增的 statusBarCards 7 例与 chip 键盘导航 3 例;最近一次 desktop 420s / mobile / device-link / maker-core / lizi-mcps / orca-workflow / test:runner 全 PASS)(apps/desktop full、packages/lizi-mcps full、packages/maker-core related、packages/orca-workflow related)。
  • pnpm --filter desktop run typecheck、pnpm --filter @cindy/maker-core run build(tsc --noEmit):通过。
  • pnpm --filter desktop db:validate + test:migration-replay:通过(0000..0113、journal/snapshot 对齐、no schema drift;drizzle 目录 / schema.ts / mapper.ts 与上游逐字节一致)。
  • 新增测试:sessionContextWindowBudget(档位推导/取值收敛)、sessionContextBudgetStore(偏好文件读写、外部编辑生效、越界/非整数拒绝、fork 复制、任务删除后清理)、modelContextSettings.sessionBudget(预算 vs 模型级上限 vs 目录上限,含未核实路由夹紧与有效窗口变化判定)、contextWindowBudgetChip(31 例:按显示路由查边界、跨来源缓存重键、路由级上限档、模型级上限压档、跨 provider 歧义时不给上限档、Pi 走路由锚点、远程只允许收紧 + 远程文案、providerId 缺失时仍读到模型级上限、提交值、价带、缩档提示、隧道降级与失败回显)、sessionsUpdate(预算字段两条入口同口径:越界/非整数拒绝,null 与合法值写入偏好)、resolveSessionContextWindowBounds(被控端边界的隐式来源解析与不可用情形)、device-link allowlist(只读查询放行、通用写入口不放开)、chip 用例(本地读 main 权威边界并按其夹紧档位、远程拿到边界→上限档出现、拿不到→只收紧 + 说明)。

手工验证

  • dev 实机已验证(CN 沙箱 session-context-window-7f7ffc):chip 渲染与档位集合、切模时档位表跟随显示路由、选档写入偏好文件并由 maker:get-context-window-bounds 回带、下一条消息后 context_window / context_window_runtime 收敛、远程边界查询参数形状。
  • CI 首轮红了两处,已定位并修复(commit 92b55311d):
    • Linux/Windows unit tests (1/2) 的 companion 步骤(botCanonicalSession.test.ts 等 localDb 用例不在 unit tier,本地 test:unit:related 的 vitest related 选不中它们)挂在 no such column: "context_window_budget" —— 根因是 4 个手写 CREATE TABLE sessions fixture 没跟着新列同步(botCanonicalSession / sessionAutoTitlePersist / orcaTeamStore / drizzleProxy),当时补了 context_window_budget INTEGER 并逐个跑通(247 + 17 + 11 + 4 例)。最新一版把存储换成偏好文件后,这些 fixture 与 schema.ts 已整体回到上游,本地不再有任何 DB 侧改动。
    • 其余 7 个同样报错的 localDb/主进程用例与本改动无关(fixture 缺的是基准版就有的 list_preview 等列,以及 worktree 锁 / dbClient.tx 的 mock 问题),这些文件不在本 PR 的 diff 里,未改动。
  • Greptile 首轮 3 条意见的处置:P1(远程 channel 缺会话级授权)判定为本 PR 引入之外——远程命令面是设备级信任,maker:set-permission-mode / maker:set-extra-dirs / maker:send 等既有 channel 同一套门禁且能力更强,仓库里也不存在"未向控制端开放的任务"模型,已在对应 thread 逐条说明;2 条 P2(同类卡片无法互斥 / 单选组缺键盘导航)成立并已修,见上。

未执行的验证

  • Claude Code / Codex 真机的端到端(本机 dev 沙箱只跑通 Pi);device-link 双端实测(只跑了 allowlist / 协议单测、chip 的隧道调用单测与单端 main handler 直测);Light/Dark 实机目检。
  • 沙箱网关 429 ExceededBudget(账号预算为 0,与本改动无关)导致一次 Pi 端到端调用未能闭环,已如实记录。

风险

风险分类

  • 无已知风险
  • SQLite / migration(本版已无 schema / 迁移改动)
  • system prompt
  • 协议兼容(device-link allowlist 单向新增 channel)
  • 权限 / 安全 / 用户数据
  • 存量插件兼容(批准状态 / 指纹 / manifest 校验 / 安装布局 / 包格式)
  • 原生层 / fingerprint / OTA
  • 跨平台差异
  • 其他:任务运行时行为(工作窗口变小会提前触发压缩)

影响与回滚

  • 影响面:只有显式选过档位的任务受影响(偏好文件里有条目);未选过的任务逐字节保持原判定。

  • 回滚:git revert 即可;偏好文件里的条目无人读取,选过档位的任务退回「跟随模型默认」。数据库完全不受影响(无 schema 改动、无迁移记录),因此新老包可以自由来回切换。

  • 偏好文件:路径走 owner 隔离 userData(ownerScopedUserDataPath),与「单模型上下文上限」同一套写入约定(临时文件 + 原子 rename);写入失败会向上抛错、不回滚成静默成功。

  • 系统提示词 / 协议 / 原生层:不涉及。

  • 跨平台:不涉及平台相关分支。

  • 跨端协议:新增 maker:set-context-window-budget 到 device-link allowlist(单向新增,老控制端不认识该 channel 不会调)。老被控端不认识新 channel 时回 CHANNEL_NOT_ALLOWED,控制端提示版本不支持并保留原档位;无 schema 变更、无重连/握手变化。

  • 行为风险:把档位压到低于任务基线(system prompt + MCP 工具 schema)会频繁压缩;这与「把 pct 调到很低」同源,靠 CC 自带 thrashing 检测、host rollover 与 chip 的缩档提示兜底,动态地板作为后续工作。

  • Codex 特例:带显式档位的任务会走 Codex 的 custom-context host 路径(与用户设过「上下文上限」的行为一致),不影响不带档位的任务。

用户可以在**单个任务**上选择工作上下文窗口(tokens),与全局的自动压缩阈值
百分比正交:档位是分母/容量,百分比是触发点。分档计费的模型据此落在不同价带
(档位旁直接标出该档输入单价),不分档计费的模型通过压小运行期上下文降低每轮
输入成本。

- 新增 sessions.context_window_budget(nullable)。NULL = 跟随模型路由默认,
  老任务零行为变化;不触碰 context_window / context_window_runtime 快照口径。
- 有效窗口 = min(任务预算 ?? 模型级上限, 模型级上限, 目录 contextWindowMax),
  由 resolveConfiguredContextWindow 解析;任务预算不得越过目录物理上限。
- 下发:prepareStartOptions 统一收敛后经 StartSessionOptions.contextWindowBudget
  传给三引擎(CC 的 AUTO_COMPACT_WINDOW、Pi 的 working window/reserveTokens、
  Codex 的 model_context_window);切模时按目标路由重新收敛;fork 继承(继承前归一化)。
- 生效:空闲任务关 handle、下一条消息冷重建;回合中登记 pending、回合结束生效;
  并发改档位以 DB 终值为准(锁内重读会话路由 + 预算值 + 同值去重)。
- renderer:任务底栏的「上下文窗口」档位 chip。档位固定三档 250K / 500K / 1M,
  选中路由默认档上报 null,不把当前默认值快照进任务。
- 档位锚点按**路由**解析(唯一候选;歧义时只允许收紧),Pi/Codex 不回落扁平表;
  同路由的模型级上限会成为档位表上界。
- device-link 远程任务:写 channel maker:set-context-window-budget(进 allowlist,
  带范围校验,与本地入口同口径);控制端经同一个只读 handler/channel 读被控端该路由的
  窗口边界(按需拉取 + 60s TTL + 开菜单刷新),据此在远程任务上也能选择更大的档位;
  拿不到(老被控端 CHANNEL_NOT_ALLOWED / 失败 / 畸形返回)时退回「只允许收紧」并如实说明。

多轮对抗性 review 共处置 34 项发现(29 修、5 记为取舍/已记录不阻塞),明细见 PR 描述。
用户验收反馈:菜单文案收短、档位固定为 250K/500K/1M 三档。

验证:pnpm test:unit:related 全绿(apps/desktop、apps/mobile、packages/device-link、
packages/lizi-mcps、packages/maker-core、packages/orca-workflow);desktop /
@cindy/maker-core / @cindy/device-link typecheck;pnpm --filter desktop db:validate
与 test:migration-replay。

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

greptile-apps Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium risk] Adds task-level context window budget controls to the desktop app.

当前实现未发现仍需阻止合并的正确性、安全性或规则合规问题,可以合并。

Summary

本 PR 为桌面端任务增加独立的上下文窗口档位,并让预算在本机、device-link 远程任务、模型切换、运行时重建、任务恢复和 fork 场景中保持一致。

  • 任务预算保存在 owner 隔离的 main 侧偏好文件中,不改变会话数据库 schema 或迁移身份。
  • main 侧统一按任务预算、模型级上限和目录物理上限解析有效窗口,并将结果传给 Claude Code、Pi 与 Codex。
  • 档位修改会按任务运行状态立即冷重建或延迟到当前回合结束后生效。
  • renderer 新增上下文窗口 chip、价带信息、键盘导航及本地/远程权威边界查询。
  • device-link 增加受 allowlist 管理的边界读取与预算更新通道,并为旧端或查询失败提供保守降级。
  • 补充了预算存储、路由收敛、fork、恢复、模型切换、远程兼容和 UI 交互测试。
Diagram
sequenceDiagram
    participant U as 用户
    participant R as Renderer 档位卡
    participant M as 本机或被控端 Main
    participant S as 任务预算偏好文件
    participant H as Maker Host
    participant A as Agent Runtime

    U->>R: 打开档位卡
    R->>M: 查询当前显示路由的权威窗口边界
    M->>S: 读取任务预算
    M->>M: 按目录上限与模型级上限收敛
    M-->>R: 默认窗口、有效上限、当前预算
    U->>R: 选择档位
    R->>M: 写入任务预算
    M->>S: 原子保存 owner 隔离偏好
    alt 任务空闲
        M->>H: 关闭旧 handle
        H->>A: 下一次发送时按新预算冷重建
    else 回合进行中
        M->>H: 登记 pending rebuild
        H->>A: 回合结束后按新预算重建
    end
Loading

Reviews (23) · Last reviewed commit: "Merge upstream/main into feat/session-co..."

Comment thread apps/desktop/src/main/maker-ipc/register.ts
Comment thread apps/desktop/src/renderer/lib/statusBarCards.ts Outdated
@MagicLizi

Copy link
Copy Markdown
Contributor

本 PR 命中维护者确认门(产品 / UI、规则文档、架构),已开讨论 issue,请维护者在本 PR 上 Approve 放行;需要修改请 Request Changes。

讨论 issue:#4601

@MagicLizi MagicLizi added awaiting-discussion 等待维护者讨论(review-pr) touches:core 改动碰到架构核心路径(review-pr 自动维护,仅展示) touches:large-diff 改动量较大(review-pr 自动维护,仅展示) touches:product-ui 改动碰到产品 / UI 面(review-pr 自动维护,仅展示) touches:redline 改动碰到红线路径(updater / DB schema 等,review-pr 自动维护,仅展示) touches:rules 改动碰到规则 / 规范文档(review-pr 自动维护,仅展示) labels Sep 17, 2026
- 4 个手写 CREATE TABLE sessions fixture 少了 `context_window_budget`
  (CI companion 步骤 botCanonicalSession.test.ts 198 例因此挂掉);
- statusBarCards 改为按**实例**登记(分屏/Orca 同时挂载两份底栏时,
  按种类登记会互相顶掉关闭回调,两张档位卡能同时亮着);注销后的句柄
  不再有协调权限;
- 档位行补 roving tabindex + 方向键/Home/End 焦点移动(只移焦点不提交,
  避免扫过一遍档位连写多次库),并补对应单测。

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

Copy link
Copy Markdown
Contributor Author

@greptileai review — 请基于当前 head 92b55311d 再评审一轮。

上一轮 3 条意见的处置(逐条已在对应 thread 回复):

  1. P1「远程调用绕过会话授权」(register.ts)→ 判定为本 PR 范围之外,不改:远程命令面是设备级信任(同账号 + 被控端显式开启远程控制 + 未撤销 + allowlist 四道门),allowlist 里既有 maker:set-permission-mode(可放宽审批)、maker:set-extra-dirs / maker:set-writable-dirs、maker:send / maker:steer 等会话变更型 channel,门禁与本 PR 新增的两条完全相同,而能力更强;仓库里也不存在「未向控制端开放的任务」这一模型(maker:get-session-tree / maker:list-active 本就放行)。要做会话级授权是整个 181 条 channel 的口径变更,已说明愿按维护者裁决另开一版。若你认为这两条在多出的具体路径上越权(例如绕过 assertReviewSettingsUnlocked),请指出路径。
  2. P2「同类卡片无法互斥」→ 已修:协调器改为按实例登记(句柄 + token),并加注销守卫;分屏 / Orca 同时挂载两份底栏时,后展开的那张会关掉先展开的。新增 apps/desktop/src/renderer/lib/__tests__/statusBarCards.test.ts 7 例。
  3. P2「单选组缺少键盘导航」→ 已修:档位行补 roving tabindex(组内单一 Tab 停靠点,落在当前选中档)+ ArrowUp/Down/Left/Right/Home/End 移焦点(键盘处理挂在浮层上,因为 Radix 把初始焦点给内容容器而非行)。方向键只移焦点不提交(每次提交都会落库并可能触发活实例重建,扫一遍档位不该连写多次库;与换 Popover 前的 Radix 菜单行为一致),Enter/Space/点击才提交。chip 用例 22 → 31。

本轮其它变化: CI 首轮红的根因是 4 个手写 CREATE TABLE sessions fixture 未同步新列(localDb 用例不在 unit tier,本地 vitest related 选不中),已修并逐个跑通;当前 CI 全绿、DCO 过、MERGEABLE。PR 描述也统一了「菜单能否选到高于模型默认的档位」的表述(可上可下,上限只有一道;唯一只能收紧的场合是远程拿不到被控端权威边界)。

@zyjisdog

Copy link
Copy Markdown
Contributor Author

@codex review

同步上游 main(d1fd9c91e),解决与任务级上下文窗口档位的冲突:

- 迁移编号冲突:上游已占用 0108/0109,本功能的
  `0108_loose_puppet_master.sql` 顺延为 `0110_green_unus.sql`,
  并重生成 0110 快照与 journal 条目(`db:validate` 校验通过、
  迁移回放 10/10 通过)。
- 其余冲突按「上游结构 + 本功能 hunk」合并:本分支相对上游 main
  的差异仍恰好只有本功能的 51 个文件。

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

Copy link
Copy Markdown
Contributor Author

关于本轮 Windows unit tests (2/2) 的红灯:基础设施 flake,与本 PR 无关,重跑已触发。

证据(该 job 日志):

  • Test Files 1366 passed (1366)、Tests 19537 passed | 46 skipped (19583) —— 用例全过;
  • 唯一的错误是 vitest 自身的 worker 通道超时:Error: [vitest-worker]: Timeout calling "onTaskUpdate",vitest 把它记成 Errors 1 error,scripts/test-workspaces.mjs 归类为 TEST_ASSERTION_FAILED;
  • 同一次 run 里 Linux unit tests (1/2)、Linux unit tests (2/2)、Windows unit tests (1/2)、verify、verify-checks、Desktop Git integration 全过。

本机全量 desktop 单测上还观察到两个同类上游 flake(两轮各挂 1 / 2 例,且两轮互不相同),均为上游 main 的既有测试、git diff d1fd9c91e 为空、单独跑通过:

  • pi-package-store-security.test.ts > keeps the maximum package roster projection bounded(重负载下投影断言失败);
  • piEnvironment.test.ts 两例 fetch failed → Error: bad port(随机端口撞上 undici 禁用端口名单)。

这两处不属于本 PR 的改动范围,本 PR 未改动上游测试。

@zyjisdog zyjisdog closed this Sep 17, 2026
@zyjisdog zyjisdog reopened this Sep 17, 2026
zyjisdog added a commit to zyjisdog/cindy that referenced this pull request Sep 19, 2026
Upstream-PR: makecindy#4598
Upstream-PR-Head: 6e5c18d
Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
zyjisdog added a commit to zyjisdog/cindy that referenced this pull request Sep 19, 2026
本地构建曾把 makecindy#4598 的「任务级上下文窗口档位」迁移落成 0108_loose_puppet_master.sql;
之后该分支合入上游时,主干已在 0108 放了 bot 私聊迁移(0108_lowly_scarlet_witch.sql),
同一 schema 意图被重编号到 0110_green_unus.sql。于是「用旧包升级过、又换新包」的库会在
prepareMigrationRuntimeManifest 处以
`applied migration runtime identity changed at seq 108 (0108_loose_puppet_master.sql)`
失败关闭——app 起不来,而且重装官方版也救不回:判定依据是 userData 里的数据库与
migration-runtime manifest,不属于安装包。

修法(只认登记在册、内容指纹完全一致的重编号,其余仍失败关闭):
- migrationRunner: 新增 KNOWN_RENUMBERED_APPLIED_MIGRATIONS 登记表与
  resolveCanonicalAppliedIdentity;已落库旧身份能解析到 canonical 时重写 manifest,
  并把它腾出的旧 seq 一并销账;「canonical 未执行」的缺条目检查只查到已落库前缀为止。
- 0108 重放守卫:重编号血统的 bot_direct_messages 缺 sender_name/recipient_name 时,
  在该迁移事务内把表收敛回 0108 执行后的等价结构(列序/索引对齐,先自查无外部外键引用)。
- migrate: up-to-date 与 replay 两条启动路径都调用 reconcileRenumberedAppliedSchema,
  保证「旧 checkout 血统」的 schema 收敛不会被 pending 为空短路掉。

测试:migrationCompatibility / migrationReplay 新增重编号修复与重放守卫用例;
四个引用 migrationRunner/migrate 的测试文件 39 通过,tsc 通过。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
(cherry picked from commit 194299a)
(cherry picked from commit d697139)
zyjisdog added a commit to zyjisdog/cindy that referenced this pull request Sep 19, 2026
Upstream-PR: makecindy#4598
Upstream-PR-Head: 6e5c18d
Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
zyjisdog added a commit to zyjisdog/cindy that referenced this pull request Sep 19, 2026
本地构建曾把 makecindy#4598 的「任务级上下文窗口档位」迁移落成 0108_loose_puppet_master.sql;
之后该分支合入上游时,主干已在 0108 放了 bot 私聊迁移(0108_lowly_scarlet_witch.sql),
同一 schema 意图被重编号到 0110_green_unus.sql。于是「用旧包升级过、又换新包」的库会在
prepareMigrationRuntimeManifest 处以
`applied migration runtime identity changed at seq 108 (0108_loose_puppet_master.sql)`
失败关闭——app 起不来,而且重装官方版也救不回:判定依据是 userData 里的数据库与
migration-runtime manifest,不属于安装包。

修法(只认登记在册、内容指纹完全一致的重编号,其余仍失败关闭):
- migrationRunner: 新增 KNOWN_RENUMBERED_APPLIED_MIGRATIONS 登记表与
  resolveCanonicalAppliedIdentity;已落库旧身份能解析到 canonical 时重写 manifest,
  并把它腾出的旧 seq 一并销账;「canonical 未执行」的缺条目检查只查到已落库前缀为止。
- 0108 重放守卫:重编号血统的 bot_direct_messages 缺 sender_name/recipient_name 时,
  在该迁移事务内把表收敛回 0108 执行后的等价结构(列序/索引对齐,先自查无外部外键引用)。
- migrate: up-to-date 与 replay 两条启动路径都调用 reconcileRenumberedAppliedSchema,
  保证「旧 checkout 血统」的 schema 收敛不会被 pending 为空短路掉。

测试:migrationCompatibility / migrationReplay 新增重编号修复与重放守卫用例;
四个引用 migrationRunner/migrate 的测试文件 39 通过,tsc 通过。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
(cherry picked from commit 194299a)
(cherry picked from commit d697139)
同步上游 main(3e238a8fa,132 个提交)。

本轮唯一真冲突在 `apps/desktop/src/main/localDb/mapper.ts`:两侧都在
`sessionCreateToRow` 同一区域加字段/类型成员,rerere 复用了上一轮的解法,
逐行核对确认该文件相对上游只多出本功能的 12 行、没有丢上游内容。

合并结果 = 上游 main + 本功能原样保留(逐项核验):

- 相对上游 main 的差异仍恰好是 51 文件 / +9077-43;
- 23 个「两侧都改过」的文件,其新增行与删除行集合与合并前逐行一致;
- 上游未占用新迁移号(本功能仍是 0110),`db:validate` 通过、
  migration replay 10/10;
- desktop typecheck 通过;本功能 11 个测试文件 516 例、device-link
  allowlist 49 例全过;`pnpm test:runner` 全绿;`check:i18n` 五语言
  10042 key 一致。

本机重门禁(`pnpm test:unit:related`)两轮都因共享测试锁被其它 worktree
占满而排队超时(exit 75,测试未跑),已用上述定向用例与 test:runner
覆盖,并由 CI 的全量分片做最终判定。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
@zyjisdog zyjisdog closed this Sep 19, 2026
@zyjisdog zyjisdog reopened this Sep 19, 2026
同步上游 main(773631f91)。上游已发布到 0113(v0.1.88),其中
`0110_abandoned_scarlet_witch` 占用了本功能此前顺延到的 0110 号。

- 迁移按主干最新链用 drizzle-kit 重新生成:`0114_blue_harrier.sql`,并按仓库
  规则改成「SQL 置空 + 同名 companion 用 PRAGMA 守卫」的幂等形态,使列已存在时
  重放为 no-op;新增回放用例覆盖该守卫。
- 其余冲突仍按「上游结构 + 本功能 hunk」合并:相对上游 main 的差异只有本功能
  自己(53 文件 / +9252-43,含生成的 snapshot 与 journal 条目)。
- 校验:`db:validate`(0000..0114、journal aligned、no schema drift)、migration
  replay 12/12、desktop typecheck、本功能 11 个测试文件 516 例、device-link
  allowlist 49 例、`pnpm test:runner`(本机需把 rustc 放进 PATH)。
- 本机重门禁(`pnpm test:unit:related`)在上一版合并态已通过(EXIT 0);本次
  合并态由 CI 全量分片做最终判定。

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

Copy link
Copy Markdown
Contributor Author

补充说明这次同步主干带来的迁移调整(不改功能语义,只改迁移身份与幂等形态):

1. 迁移号 0108 → 0110 → 最终 0114

上游在 v0.1.88 已发布 0110_abandoned_scarlet_witch / 0111 / 0112 / 0113,占掉了本功能此前顺延到的 0110 号。现在按 docs/dev-rules/database-and-migrations.md 的要求,以主干最新迁移链用 drizzle-kit 重新生成(没有手改文件名、journal 或 snapshot):本次生成 0114_blue_harrier.sql,journal 与 meta/0114_snapshot.json 均为 drizzle-kit 产物。

2. 迁移改成自带守卫的幂等形态

同一份规则要求「新迁移必须自带守卫」(SQLite 的 ALTER TABLE ADD COLUMN 没有 IF NOT EXISTS,直接写进 SQL 无法重放;仓库里 0075 / 0076 是既有范式)。所以 0114_blue_harrier.sql 只留 SELECT 1;,实际加列放在同名 companion drizzle/scripts/0114_blue_harrier.ts,用 PRAGMA table_info('sessions') 判断列是否已存在:

function run(db) {
  if (!hasColumn(db, 'sessions', 'context_window_budget')) {
    db.exec('ALTER TABLE `sessions` ADD `context_window_budget` integer');
  }
}

这一条不只是形式:任何已经跑过本分支早期构建的库都已经有这一列(此前那份迁移是无守卫的裸 ALTER),没有守卫的话重放会直接报 duplicate column 并阻断启动。新增回放用例 replays the guarded 0114 companion over an existing column as a no-op 覆盖这个路径。

3. 验证(head f80c5505)

  • pnpm --filter desktop db:validate:0000..0114 连续、journal 对齐、无 schema drift、companion 为 CommonJS;
  • pnpm --filter desktop test:migration-replay:12/12(含上面的守卫用例);
  • pnpm --filter desktop typecheck:通过;
  • 本功能 11 个测试文件 516 例、packages/device-link allowlist 49 例:全过;
  • pnpm test:runner:全绿(本机需要把 rustc 放进 PATH,属环境前置);
  • 相对上游 main 的差异仍只有本功能自己(53 文件 / +9252-43,含生成的 snapshot)。

@zyjisdog zyjisdog closed this Sep 21, 2026
@zyjisdog zyjisdog reopened this Sep 21, 2026
@zyjisdog

Copy link
Copy Markdown
Contributor Author

@greptileai review — 本轮两条:9fc2591b5 冷 Pi 预检按任务预算算目标窗口;3c361a32f 未核实路由的兜底默认不再当物理上限(档位不再出现 >100%)。

用户远程端实测(2026-09-22):自定义连接未声明窗口 + 模型级上限 1M 时,档位卡只剩
「20% · 200K」与「100% · 1M 模型默认」两行,25%/50% 缺失。

上一轮只把行内百分比的分母改成 tierBase(max(申报上限, 默认档)),档位表却仍按
effectiveMaxWindow 生成:被控端申报的上限(老 main 会把未核实路由的兜底默认 200K 当
上限发过来)低于它自己解析出的默认档 1M 时,基准被抬到 1M,而 25%/50% 按 200K 生成
又低于 200K 地板被过滤 —— 显示与可选档位分叉。

改为新端直接拿 tierBase 当档位上限(显示与档位同源);老被控端维持原口径:只允许收紧,
基准就是那个保守上限,不拿默认档去抬(避免重新引入 800K 档那类 P1)。

用例:远程形态固化为一条回归(20%/25%/50%/100% 四行),并保留老被控端不出现 800K
的断言。验证:tsc 通过;相关 6 文件 111 例全过;pnpm test:unit:related 全绿
(apps/desktop unit 488.6s PASS)。

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

Copy link
Copy Markdown
Contributor Author

@greptileai review

bb35729 把档位表也换成 tierBase(它含 max(…, 默认档) 的抬升),于是目录**声明了**
contextWindowMax=200K、模型级逃生上限 1M 的未核实路由会给出 250K/500K 档;但 main 对
**显式**预算无条件按声明上限夹一次,这两档运行期只会得到 200K —— 窗口与输入价带一起
说谎。默认档写 null 走模型级逃生口、真的生效在 1M,那条不抬上限就成立。

改为两条口径各归各位:
- 显式比例档的上限 = min(申报上限, 模型级上限),即 main 对显式预算会夹的那个数;
- 行内百分比分母 = max(该上限, 默认档),默认档高于上限时它自己那行仍落在 100%;
- 老被控端维持「只允许收紧」,不拿默认档去抬。

两条 P1 同时成立:不再出现运行期达不到的档位,也不再出现 500% · 1M 模型默认。

用例:未声明上限(用户实测形态,含已存 200K 任务档)→ 20%/25%/50%/100% 四行;
声明上限 200K + 模型级 1M → 只有 20%/100%,断言不出现 250000/500000;老被控端
不出现 800K、无上限只显绝对值等既有用例保持。

验证:tsc 通过;chip 42 例全过;pnpm test:unit:related 全绿(apps/desktop unit
568.0s PASS;首轮 piEnvironment 一条为本机环境抖动,单跑 23/23 通过)。

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

Copy link
Copy Markdown
Contributor Author

@greptileai review

用户实测:底栏左边「本任务 ¥…」卡悬浮展开后点击不会收起,本卡却会 —— 两张卡交互
不一致。

与 TodaySpendChip 同款做法:onClick 里 preventDefault 阻止 Radix 把点击解释成开关,
展开/收起只由 hover、焦点与 outside-click 驱动;点击回到「保持展开」,来源按
指针/键盘区分(指针路径继续不抢焦点,鼠标用户不会看到蓝框)。

用例:悬浮展开后点击 → 卡片与档位行仍在,且不会排出下一次关闭。
验证:tsc 通过;chip 43 例全过;pnpm test:unit:related 全绿(apps/desktop unit
610.7s PASS)。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
用户实测:Tab 到达底栏用量卡后再按多少次 Tab 都到不了本卡。根因是触发器用了原生
`disabled`(调用点 disabled={readOnly || !session})—— 原生禁用会把元素整个移出
Tab 序列,而左边用量卡没有这道门。

改为不用原生 disabled:
- `aria-disabled` 暴露状态,元素保持可聚焦、可 hover、可展开 —— 只读任务也能看一眼
  当前档位与价带;
- 「不可改」由档位行自己 disabled 拦住(写入路径不受影响),并在卡内补一句
  `readOnlyHint`(五语言)说明原因,不再让行默默变灰;
- 外观改为按 isDisabled 条件加 `cursor-default opacity-50`(原来的 `disabled:` 变体
  在没有原生 disabled 后已失效)。

用例:只读任务下 trigger.disabled === false / aria-disabled === true、可展开、档位行
全部禁用、卡内有只读说明。
验证:tsc 通过;chip 44 例全过;check:i18n 通过(10527 key 五语言一致);
pnpm test:unit:related 全绿(apps/desktop unit 545.3s PASS)。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
用户实测:Tab 到左边用量卡后按多少次都到不了右边的任务窗口卡。CDP 实测定位到两个叠加原因:

1. 两张底栏卡片的浮层都 portal 到 body 末尾 —— 文档内卡片之后再没有下一个可 Tab
   节点,焦点一进卡就离开了底栏(表观「卡住」)。改为把浮层挂到触发器**紧跟其后**的
   宿主节点(`ui/popover.tsx` 新增可选 `portalContainer`,缺省仍是 body),DOM 顺序
   = 视觉顺序:底栏实测可 Tab 顺序为 用量触发器 → 用量卡 → 任务窗口触发器 → 圆环。
2. 用量卡关闭路径无条件 `restoreQuotaPopoverFocus()` 把焦点抢回触发器:focus-outside
   触发的关闭比浏览器的 Tab 目标更早执行,正好把 Tab 要去的那枚 chip 顶掉,于是焦点在
   trigger 与卡片之间反复弹。给归还加保护:焦点已交给卡片外**仍然存在**的控件时不抢回;
   焦点在卡片里(Escape)或卡片卸载中焦点即将掉到 body(切换计费形态)时照旧归还。

同时保留既有键盘语义:键盘聚焦仍进卡(卡片滚动区 / 看板按钮可达),点击/悬停行为不变。

用例:本卡新增「浮层必须挂在触发器之后、不在 body 上」的结构不变量;用量卡既有 4 组
焦点用例全绿(未改变语义)。
验证:tsc 通过;4 个底栏单测文件 166 例全过(本卡 45 例);pnpm test:unit:related 全绿
(apps/desktop unit 470.3s PASS)。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
@zyjisdog zyjisdog closed this Sep 24, 2026
@zyjisdog zyjisdog reopened this Sep 24, 2026
@zyjisdog

Copy link
Copy Markdown
Contributor Author

@greptileai review

用户实测(附定位):Tab 能到 1(工作目录)、2(用量卡),卡片段落展开后 Tab 永远
出不去。CDP 焦点仿真 + 真实 Tab 复现:焦点进卡后永远环回卡内 —— 根因是 Radix
PopoverContentImpl 给 FocusScope 写死 `loop: true`(loop 在卡内没有可 Tab 边缘时把
Tab 还给自身,我们的卡在最后一档亦然),Tab 的默认穿越动作被环回逻辑吃掉、
且无 preventDefault 可循。

Radix 不透传 loop,改为在它之前接管两个 Tab 边缘(新增 lib/focusTraversal.ts):
- Tab 在卡内**最后一个**可 Tab 元素上 → 焦点交给卡外 DOM 序的下一个(右边那枚 chip);
- Shift+Tab 在卡内**第一个**可 Tab 元素上 → 交给卡外上一个(回到触发器方向);
- 卡内其余 Tab/方向键行为不变。接线:两张底栏卡片(任务窗口卡 + 用量卡)的
  onKeyDownCapture,先于 Radix 的 bubble handler,接管即 preventDefault + stopPropagation。

可 Tab 集合的可见性判据与 Radix FocusScope getTabbableCandidates 同源(checkVisibility
缺失时按 visibility/display祖先链判定),避免与 Radix 行为分叉。

验证:官方确认卡片段落展开后 Tab 可继续(CDP 真实 Tab:用量卡 → 任务窗口卡 → 圆环);
tsc 通过;新增 focusTraversal 4 例;chip 45 例与用量卡 48 例全过;
pnpm test:unit:related 13 workspace 全绿(apps/desktop unit 740.1s PASS)。

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

Copy link
Copy Markdown
Contributor Author

底栏卡片键盘可达性收口(本轮四条交互修复)

用户目测阶段的四条交互问题,全部收到并修复,累计 5 个提交:

  1. 625c1fd02 悬停展开后点击不再收起:与底栏用量卡(TodaySpendChip)同一语义 —— 展开收起只由 hover/焦点/outside click 驱动,onClick 里 preventDefault 阻止 Radix 当开关;指针路径不抢焦点、键盘路径保持原有.enter 卡语义。
  2. 146652b4a 只读任务不再被 Tab 跳过:触发器不再用原生 disabled(那会把元素整个移出 Tab 序列,用户实测 Tab 到用量卡后就永远回不到本卡),改 aria-disabled;落档能力由档位行自己禁用,卡内补 readOnlyHint(五语言)。
  3. 78c42d9c3 浮层不再挂在 body 末尾:浮层 portal 到「触发器紧跟其后」的宿主(ui/popover.tsx 新增可选 portalContainer,缺省 = body),DOM 顺序 = 视觉顺序;同时给用量卡的焦点归还真加保护(关闭路径不再把已经交给下一控件的焦点抢回触发器 —— 实测那是 Tab「弹回」的直接原因)。
  4. c58ffd156 解开 Radix loop: true 对 Tab 的困锁:Radix 给 FocusScope 写死 loop: true,焦点进卡后 Tab 永远环回卡内(用户实测:卡片段落展开后停在 2)。新增 lib/focusTraversal.ts,在 Radix 之前接管两个 Tab 边缘 —— 最后一个边缘的 Tab 交出卡外、第一个边缘的 Shift+Tab 回到触发器方向;卡内其余 Tab/方向键行为不变。两张底栏卡片同时接线。

实测(CDP 焦点仿真 + 真实 Tab 按键):用量卡展开后 Tab 依次是 → 任务上下文窗口 → 圆环 → 继续。与用户确认修复生效。

验证:tsc 通过;focusTraversal 4 例 + 底栏 4 个测试文件全过;pnpm test:unit:related 全绿(apps/desktop unit 740.1s PASS)。

@zyjisdog

Copy link
Copy Markdown
Contributor Author

@greptileai review — 本轮追加四处底栏交互修复(点击不收卡 / 只读可达性 / portal 顺序 / loop 焦点困锁),见上一条评论。

zyjisdog added a commit to zyjisdog/cindy that referenced this pull request Sep 27, 2026
Upstream-PR: makecindy#4598
Upstream-PR-Head: c58ffd1
Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
zyjisdog added a commit to zyjisdog/cindy that referenced this pull request Sep 28, 2026
Upstream-PR: makecindy#4598
Upstream-PR-Head: c58ffd1
Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
zyjisdog added a commit to zyjisdog/cindy that referenced this pull request Sep 29, 2026
Upstream-PR: makecindy#4598
Upstream-PR-Head: c58ffd1
Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
用户实测:Orca worker 的 tester 任务窗口预算调到 50% 后,上下文跑到 110% 也不自动压缩。

取证(安装版 0.1.93 + 磁盘 Pi 实例):
- 偏好文件里该会话的 500000 一直在,会话列 context_window=500000,档位卡按 50% 显示;
- 但 Pi 实例 settings.json 的 compaction.reserveTokens 是 50000(= 无预算 + pct95 的
  公式),阈值退回满窗 950K ⇒ 预算窗口 500K 内永远不会触发;
- 用生产同一支函数复现:resolveConfiguredContextWindow(500K)=500000、reserveTokens
  应为 550000(阈值 450000)⇒ 收敛与换算都没问题,**预算没进到引擎进程**。

断链点:resumeOrcaWorkerSessionIfMissing 自己拼 opts 且不带 contextWindowBudget。
它经 bootstrapSession → maker.createSession,理论上会被 prepareStartOptions 兜住,
但这条路径自己拼 opts、不依赖那条钩子;引擎进程重启后预算即静默丢失(UI 仍显示 50%)。

修法:
- 把预算收敛抽成 applySessionContextWindowBudgetToCreateOpts(model-context-settings),
  prepareStartOptions 与 worker 唤醒路径共用同一口径,不许分叉;
- worker 唤醒在 bootstrapSession 之前显式调用它;
- 未定型路由(无 model)时不透传未收敛的值。

用例:共享函数 4 例(读存档位/显式值收敛/未自定义不留字段/无 model 不透传,写盘隔离到
临时目录)+ worker 唤醒路径源码契约(预算注入必须在 bootstrapSession 之前)。
验证:tsc 通过;相关 62 例全过;pnpm test:unit:related 全绿(apps/desktop unit 546.4s PASS)。

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

Copy link
Copy Markdown
Contributor Author

用户实测报障修复:任务窗口预算在 worker 唤醒后失效(跑到 100% 以上也不压缩)

用户实测:Orca worker(tester)把任务级上下文窗口调到 50% 后,上下文跑到 110% 仍不自动压缩。

取证(安装版 0.1.93 + 磁盘 Pi 实例)

观测 值 含义
偏好文件 session-context-budget-prefs.json 该会话 500000 预算一直在 ✓
会话列 context_window 500000 宿主投影按 50% 算 ✓(所以卡上显示 103%/110%)
Pi 实例 settings.json 的 compaction.reserveTokens 50000 = 无预算 + pct95 的公式,阈值退回满窗 950K ✗
应有值(有预算时) 550000 → 阈值 450K 从未在盘上出现过

用生产同一支函数复现(真实目录事实:opencode-go / space-bunny-free,contextWindow=1,000,000、无 contextWindowMax、未核实):

resolveConfiguredContextWindow(500K) = 500000        ✅ 收敛正常
有预算 → reserveTokens 550000 → 阈值 450000        ✅ 换算正常
无预算 → reserveTokens 100000 → 阈值 900000

⇒ 收敛与换算都没问题,预算没进到引擎进程。

断链点

resumeOrcaWorkerSessionIfMissing(register.ts:7332)自己拼 opts 且不带 contextWindowBudget。它经 bootstrapSession → maker.createSession,本应由 prepareStartOptions 钩子兜住,但这条路径自己拼 opts、不依赖那条钩子;引擎进程重启后预算即静默丢失,而 UI 仍按 50% 显示(宿主投影照算)—— 于是形成"看得见、不生效"。

顺带澄清另一条时间线:09-30 13:26 那次"给 tester 改预算"实际落在了 reviewer(tester 当天没有任何预算写入),reviewer 因此被重建、阈值下移,所以它压缩正常;tester 是重启后预算丢失的那一个。

修法

  • 把预算收敛抽成 applySessionContextWindowBudgetToCreateOpts(放进 model-context-settings.ts),prepareStartOptions 与 worker 唤醒路径共用同一口径,不许分叉
  • worker 唤醒在 bootstrapSession 之前显式调用它
  • 未定型路由(无 model)时不透传未收敛的值(宁可不带预算,也不让引擎按"已收敛"信任一个没算过的数字)

顺带核过其它启动入口:worker 首次创建(orcaWorkerCreationService)、goal 恢复(goal-host/sessionRestore)都经 maker.createSession ⇒ 已被钩子覆盖 ✓,本次只补 worker 唤醒这一条。

用例与验证

  • 共享函数 4 例(读存档位 / 显式值收敛到目录上限 / 未自定义不留字段 / 无 model 不透传,写盘隔离到临时目录,不碰真实 userData)
  • worker 唤醒路径源码契约:预算注入必须在 bootstrapSession 之前
  • tsc 通过;相关 62 例全过;pnpm test:unit:related 全绿(apps/desktop unit 546.4s PASS)

上游前进 485 个提交后的例行同步。三处冲突由 rerere 重放(register.ts /
runtimeSetModel.ts / packages/maker-core/src/agents/pi/index.ts),已按符号级核对确认
任务级窗口预算的读取与下发链路完整保留;上游新增迁移 0115-0120 编号与本 PR 无冲突,
未改动任何既有迁移编号。

pnpm --filter desktop run typecheck: PASS

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

zyjisdog commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

@greptileai review

单个冲突:worker 唤醒路径的预算注入(我方)与上游新增的 assertCurrent 调用,取并集
—— 保持上游的新顺序(assertCurrent 在 ensureRemoteReadyForSessionStart 之前),预算
注入仍在 bootstrapSession 之前。

顺带修掉一个由合并暴露的真实问题:SET_CONTEXT_WINDOW_BUDGET / GET_CONTEXT_WINDOW_BOUNDS
两个 handler 落在 pluginWriteAccess.test.ts 的源码切片区间内(该测试按字面量把
SET_PERMISSION_MODE → SET_PLAN_MODE → EXPORT_SESSION_HTML 三段源码切出来在沙箱执行),
沙箱没有 normalizeSessionContextWindowBoundsRoute,报 ReferenceError 并连带打挂 6 条
Plan 权限用例。把两个 handler 整块移到 SET_PERMISSION_MODE 之前,使三个切片区间各自
只含自己的 handler。

验证:tsc 通过;25 个 register.ts 源码切片类测试文件 1667 例全绿;pnpm test:unit:related
全绿(apps/desktop unit 571.4s PASS,0 失败)。

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

zyjisdog commented Oct 1, 2026

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#4598
Upstream-PR-Head: 397d3e8
Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
zyjisdog added a commit to zyjisdog/cindy that referenced this pull request Oct 1, 2026
…断结构)

union 把 PR 侧预检塞进 remoteHostId/sdkSessionId 嵌套 if,与 HEAD 侧 if/else{rehydrate} 重复保留并丢掉闭合括号。改为对齐 local/v0.1.96 的已交付结构:预检声明在 runtimeAgentKind==='pi' 公共位置,三段式判定取代 HEAD 的 if/else 分支,保留 makecindy#4598 的 sessionContextWindowBudget 口径。另修上游新增测试 harness 缺的 statDirectory mock 与 makerTransport mock 的 canStopAgentTask 重复键。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
(cherry picked from commit af0ddbe6fbddeaf608bdbfe60dcf9c0e0f7161ed)
四处冲突,全部按并集/语义合并:
- maker-host/index.ts(import):上游新增 resolveDesktopModelEfforts,与我的预算相关 import
  合并为一条(并集去重)。
- model-context-settings.ts:上游在同区域新增 resolveDesktopModelEfforts,与我的预算
  函数族并存;补上被并集切掉的闭合括号。
- claude-code/index.ts(2 处):上游给 sdkEffortForModel 加了 providerId 参数、给
  setModel 加了 effort 选项;与我的 contextWindowBudget 声明/参数合并,两边语义都保留。
- pi/index.ts:上游删除了 projectedReserve 预演块,取我方(含预算优先的
  nextWorkingContextWindow)。

顺带:合并带进上游新依赖 @noble/curves(apps/mobile),本地 pnpm install 补齐。

门禁取证(两轮 RELATED_EXIT=1 的唯一失败均为 pi-package-store-security.test.ts):
- 纯净上游 worktree 单跑该文件:194 例全绿
- 本分支单跑该文件连续 3 次:均 194 例全绿
- 仅在门禁 45000+ 用例并发下偶发,且每轮失败的是不同用例 ⇒ 并发时序抖动,
  非本 PR 回归;其余 55 个 workspace 全绿。
tsc(desktop + maker-core)通过;预算链路六项与上游新语义逐项核在位。

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#4598
Upstream-PR-Head: 884c63f
Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
zyjisdog added a commit to zyjisdog/cindy that referenced this pull request Oct 5, 2026
…断结构)

union 把 PR 侧预检塞进 remoteHostId/sdkSessionId 嵌套 if,与 HEAD 侧 if/else{rehydrate} 重复保留并丢掉闭合括号。改为对齐 local/v0.1.96 的已交付结构:预检声明在 runtimeAgentKind==='pi' 公共位置,三段式判定取代 HEAD 的 if/else 分支,保留 makecindy#4598 的 sessionContextWindowBudget 口径。另修上游新增测试 harness 缺的 statDirectory mock 与 makerTransport mock 的 canStopAgentTask 重复键。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
(cherry picked from commit af0ddbe6fbddeaf608bdbfe60dcf9c0e0f7161ed)
zyjisdog added a commit to zyjisdog/cindy that referenced this pull request Oct 5, 2026
该测试切源码 + new Function 只截 resumeOrcaWorkerSessionIfMissing,而 makecindy#4598 让该函数依赖 applySessionContextWindowBudgetToCreateOpts/getActiveCatalog,isOrcaWorkerSessionResumable 是同文件兄弟函数也未随之截取 → ReferenceError。补进 bindings(状态读取复用测试自己的 readStatus 以保留 post-bootstrap 复核语义)。

Signed-off-by: Zhang Yunjin <zhangyunjin@zju.edu.cn>
(cherry picked from commit cbc37924199f7cd9223f2d99f2b6f6f4881b43ed)
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