R26 集成批 2:文案 locale 独占门禁 + 两段式压缩落盘 + MCP M0/M1/M1b + 插件错误路径中和 + .spill 排除快照 - #26
Merged
Merged
Conversation
新增 scripts/dev/check-ui-i18n.ts:用 TypeScript 编译器 API 遍历 apps/*/src 与 packages/*/src,把「含汉字的字符串/模板字面量出现在非词典文件里」判为违规。词典白名单 只认 i18n*.ts / locale*.ts / locales/ 目录 / *-copy.ts,并设 MINIMUM_SOURCES=480 与 MINIMUM_LOCALE_OWNER_SOURCES=8 两条失效保险丝,防扫描范围塌成「零违规通过」。 存量 5732 处一次迁不完,故上棘轮基线 ui-i18n-baseline.json:按「文件 + 归一化片段 → 次数」记录(不按行号,重排代码不假红),基线内不报错、新增即 exit 1、基线里已消失的条目 提示删除;--write-baseline 可整体重录,--files 供 pre-commit 只扫暂存文件。真的不是界面 文案的中文串(模型提示词等)用行内 ui-i18n-allow 豁免,与既有 term-allow 同一套约定。 check-copy-terms.ts 从手写的 5 个词典文件扩到 260 个:共用 check-ui-i18n 的文件发现逻辑 (全部词典文件 + 基线里仍含文案的文件),判据也从「整行含中文」升级成 AST 字面量,注释与 正则里的中文不再误伤。扩覆盖暴露的 25 处存量命中同样进棘轮基线,改词属于产品决策不在本批。 形状借鉴 deepseek-harness(MIT)的 scripts/verify-client-ui-i18n.ts,来源与本地改造记在文件头。 接线:根 package.json 加 audit:ui-i18n(并入 lint 链)与 test:scripts(scripts 不是 workspace 包,pnpm -r 覆盖不到,故自带 tsconfig 与 node --test);lefthook pre-commit 加 staged-only 一步;verify.yml workspace job 加 pnpm test:scripts 一步。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
禁词门扩到 260 个文件后 pre-commit 要跑 5.45 秒,超出 commit 时预算。给 check-copy-terms.ts 补 --files 模式(与 check-ui-i18n.ts 同形,免掉全量 glob: 暂存文件里只有词典或两份基线里记着的文件才需要扫),lefthook 两个文案门都改成 staged-only,全量仍由 pnpm lint 与 CI 兜底。顺手把 relativizeToRoot 提成共用导出。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
把 apps/desktop-webview/src/workbench 全树的 `zh ? "中文" : "English"` 内联三元搬进
每个模块自己的 locales.ts(workbench/、army/、chat/、drive/、editor/、files/、inbox/、
kanban/、proposal/、schedule/、settings/、timeline/ 各一份)。形状照 deepseek-harness
的 per-package locales.ts:中文对象是 key 集事实源,英文对象 `satisfies
Record<keyof typeof zh, string>` 做编译期对齐——少键多键都编译不过。
渲染出来的文案一个字没改:搬家是纯表达式替换(三元换成词典查表),1698 个桌面端测试与
typecheck 全绿。词典函数第一参数收 `WorkHubLocale | boolean` 是过渡口子——这一层的渲染
函数历史上大量以 `zh: boolean` 传语言,改签名是另一件事,不和搬家混在一批。
棘轮基线随之收缩:5732 → 5041 处(-691)。剩下的多是带插值的模板字面量三元
(`zh ? \`共 ${n} 条\` : ...`),需要参数化词条,不是机械搬家能覆盖的。
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
同一次机械搬家扩到 apps/desktop-webview/src/spotlight(controller、labels、ask-cuu、
14 个 view)与 src 顶层(登录、连接屏、重绑、Cuu 运行时等):新增 spotlight/locales.ts、
spotlight/views/locales.ts、src/locales.ts 三份词典,形状与工作台那批一致。
渲染文案一个字未改,1698 个桌面端测试 + typecheck 全绿。桌面端存量 2086 → 706(-66%);
全仓棘轮 5041 → 4352。禁词门基线里 3 条随文案搬进新词典,同步重录(文案本身没动)。
剩下的 706 处不是「三元」形状,机械搬家覆盖不到:
- command-palette.ts(96)用 `{ "zh-CN": …, en: … }` 记录式双语表;
- labels.ts / schedule 状态表用 `{ zh, en }` 映射;
- cuu-preferences / pet-surface / cuu-qa-scenarios 是无条件中文串;
- 带插值的模板三元(`zh ? \`共 ${n} 条\` : …`)要参数化词条。
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
第一段(免费剪枝):压缩触发时先把历史里保留窗口之外、超过单条预算的 tool_result 剪成 head + 中英双语标记 + tail,重算上下文压力;够了就不发那次摘要请求(摘要走 独立计费的 context_compact 路由)。剪的是内容不是消息,tool_use/tool_result 配对 结构一个字都不动。 第二段(spill):超过单条上下文预算的结果先写进 <workdir>/.spill/<步号>-<工具>.txt, 模型看到的截断文本末尾附中英各一句定位提示,可以自己用 read_file 把原文读回来。 没有 workdir 或落盘失败时退回既有的纯截断话术。 行为变更:有 workdir 时超大工具结果的截断话术改成「截断正文 + .spill 定位提示」; 剪枝排在压缩次数耗尽的升级判定之前——能靠不花钱的剪枝撑过去就不叫醒人。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
新增 packages/contracts/src/domain/mcp.ts:transport 单值 literal(阶段 0 只 stdio)、status 三值枚举、trust_level 管理员断言上限(read_only/external_effect, 默认 external_effect,指挥者拍板与阶段 0 合并交付)、7 项静态体检 id 枚举与报告 结构、管理员/桌面完整 VM 与 web 只读投影(结构性不含 command/args/env/ secret_refs/cwd)。挂 barrel 导出并配 9 条契约测试。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
新增迁移 packages/db/migrations/0073_mcp_servers.sql:workspace 围栏、 server_name 工作区内唯一(构成模型可见工具名)、transport 结构性只允许 stdio、status/trust_level/timeout/tool_count 四条 CHECK、阶段 1 的 url/auth_header_* 四列现在建但被 CHECK 锁死成建了不能用;journal 收于 idx:73(when 严格递增)。 schema/core.ts 补 mcpServers 表定义并入 workHubTables(图计数 81→82); 新增 repositories/mcp-servers.ts(全部原语 workspace 围栏;setEnabled(true) 不冒充已验证连接,落 connect_failed 等真实试连接回填)。 schema.test.ts 更新 journal 尾部断言、新增 0073 迁移/schema 同步测试块; 新增 mcp-servers-repository.test.ts(query-recorder 风格,10 条)。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
记录 R26 M0 的决定与取舍:为何不复用 plugins 表、trust_level 合并进阶段 0 交付的拍板依据、transport 阶段 0 结构性只认 stdio 且 HTTP 相关列现在建但 锁死、setEnabled(true) 不冒充已验证连接等设计取舍,供 M1-M3 后续工包引用。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
同一次机械搬家扩到 packages/ui 与 apps/web:
- packages/ui/src/gold-path/locales.ts(354 条)——route-components.ts 366 处 +
render.ts 13 处,是全仓最大的一块内联文案;
- packages/ui/src/locales.ts、packages/ui/src/workitem/locales.ts;
- apps/web/src/locales.ts(234 条)——browser.ts 181 处 + routes.ts 19 处 + 9 个模块。
新词典与既有的 gold-path/i18n.ts 并存而不是往那 652 行里继续堆:新代码走 per-package 小
locales.ts(中文 key 集事实源 + 英文 satisfies 对齐),这是 B5 立项时定的方向。
渲染文案一个字未改。验证:pnpm -r typecheck 全绿;@workhub/ui 261、@workhub/web 168、
@workhub/cuu 50、@workhub/desktop-webview 1698、@workhub/api 2105(2 跳过)全通过。
棘轮 4352 → 3692。
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
labels.ts 的 9 张 `["中文", "English"]` 元组对照表搬进 spotlight/locales.ts,拆成 zh/en
两张表:元组读不出「哪个是中文」,也没法让编译器盯两侧对称;拆开后英文表 satisfies
`{ [Group]: Record<keyof zh[Group], string> }`,少一个枚举取值或多一个都编译不过。
labels.ts 只剩「查不到怎么办」的决定(多数原样渲染裸值,agentStepPhaseLabel 兜底成「步骤」,
与迁移前逐一对应)与各枚举的具名入口。文案逐字未改,1698 个桌面端测试与 typecheck 全绿。
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
剪枝的计划、判定与全部模型可见文本只有一份实现(loop/context-pruning.ts), 两套引擎各写一层薄投影:wire 的 tool_result 块 / pi 的 toolResult 文本块。 投影口径逐条对齐(纯字符串内容、text/thinking 正文、工具名加参数 JSON 长度), 否则同一段历史会算出不同压力、在「剪枝够不够」上分叉。 落盘与截断在 loop2 侧放在 execute 返回给 pi 的那一刻——与 loop.ts 把结果写进 messages 的那一刻是同一个「首次进入模型可见历史」的时点,wire 文本因此逐字节相同。 完整 ToolResult 仍挂在 details 上,step.toolResults / trace 不受影响。 剪枝在两边都排在「压缩次数耗尽」判定之后:loop2 的耗尽判定在 shouldStopAfterTurn, 那里拿不到即将发出的历史,双引擎同步优先于多救一次。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
packages/tools 新增 sanitizeModelFacingText:围栏标签中和(覆盖 loop.ts FENCE_TAG_NAMES 的快照拷贝 + candidate_N)、tail/head-tail 两种截断风格、 可选控制字符清理。为 R25 M-MCP 客户端设计 4.2「共享一小块」铺路—— packages/plugin-host 与后续 packages/mcp-client(M1)共用同一份判断, 不再各包各写一份、口径慢慢漂移。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
同源缺口(R25 M-MCP 客户端设计 5.2):插件工具描述符文案早有 sanitizePluginText 中和+上限,但插件工具*执行结果*(translate.ts 的 renderToolContent)此前完全没有 ——不像描述符,结果常被工人原样抄进 outputs/ 与自述,会被 packages/agent 的 fenced()/collectOutputExcerpts 装进围栏,一段字面 </outputs> 就能提前闭合围栏冒充指令; 且没有长度上限,插件想返回多少字节就进多少。 - to-tool-spec.ts 的 sanitizePluginText 改成调用 packages/tools 的 sanitizeModelFacingText(tail 截断、不开围栏中和),行为逐字不变:43 条既有 plugin-host 测试与 pnpm gen:expected 均无变化。 - translate.ts 的 renderToolContent 拆成原始拼接(renderToolContentRaw)+ 唯一导出、 唯一返回口的外壳,在这一处统一做围栏标签中和 + 32KB 上限(PLUGIN_RESULT_MAX_CHARS, 对齐设计文档给 MCP content[] 定的档位),render 抛错/非 text 占位/循环引用回退等 既有分支同样受保护。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
记录 sanitizeModelFacingText 的落点选择、围栏标签清单单一事实源的已知负债 (packages/tools 不能依赖 packages/agent,本工包文件白名单也不含 packages/agent, 只能维护一份快照拷贝并双向注释指向对方)、M1(packages/mcp-client)后续应该 怎么接这个共享点,以及发现但按设计范围未修的相邻缺口 (plugin-host-client.ts 里插件 execute() 抛错时的 message 不经 renderToolContent)。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
MCP 客户端接入的纯翻译包,与 packages/plugin-host 平级:MCP 服务器本来就是独立进程, 第三方代码从不进我们的模块图,不需要再套一层自己的宿主。 names.ts 只管一条不变式:两台服务器的两个不同工具,公开名永不坍缩。跨服务器重名是常态 (调查里 1470 台服务器出现 775 个重名工具),一旦坍缩,模型以为在调 A 的 search、 实际打到 B 上。做法是 `mcp__<服务器>__<工具>`,非法字符压下划线;**只有改名有损时** (字符被压过或超了 64 字符)才追加 12 位 SHA-256 指纹,干净且够短的名字原样保留。 指纹喂的是原始输入而不是压缩后的串,否则压完同形的两个名字会得到同一个指纹。 反方向刻意不提供:原始工具名只在 tools/call 上用,公开名从不反解——反解需要「压缩可逆」, 而这件事本来就不可逆,留一个假装能反解的函数早晚会有人拿去拼一次真实调用。 另附两件给上层用的纯函数:名字预算(设置页实时预览,把人推向短名)与工具 id 分词 (人工保留门按词判高风险类,而服务器名也参与分词——一台叫 finance 的服务器, 它的每个工具都会被归到财务类,这件事得在添加服务器的界面上先说明白)。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
三条纪律:非 text 块留占位不静默丢(模型得知道有东西没带过来,否则会对着半份结果 编一个完整结论);过围栏中和;32KB 上限带截断标记。 中和这一层的理由要说准:工具结果本身进的是 tool_result 消息、不在围栏里,但它常被工人 原样抄进 outputs/ 与自述,而那两条确实要进评审围栏。一台服务器只要在结果里回一行字面的 `</outputs>`,就能在第二段提前闭合围栏、把后文送到围栏外冒充指令。中和成本 O(len), 堵的是这条二段式逃逸。 截断有两个不是讲究、是必须的细节:给标记预留位置,让「结果 ≤ 上限」这句话成立; 不切开代理对,孤立代理对在 JSON 序列化时会变成替换字符、个别 HTTP 客户端直接报编码错, 把一次本来成功的调用变成失败。 围栏标签表是从 packages/agent 的 loop.ts 复制来的(本包不依赖 agent),因此配了一条 对着 loop.ts 源码核对的漂移守卫:那边加了新标签而这边没跟上,测试会红。 这份复制与 plugin-host 的 sanitizePluginText 一起,是 M1b 的合并点。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
翻出来的 ToolSpec 走既有注册表通道,于是自动继承 canUse 双检、快照门、人工保留拦截 与审批链——服务器只提供能力实现,授权判断全在 API 进程这一侧。 读写分级按拍板落成一张真值表:最终 sideEffect = 管理员断言 AND 服务器自述 (readOnlyHint 为真且 destructiveHint 不为真才算只读),任何缺省都取最高风险。 两条道理要一起说才成立:不分级的话,external_effect 会让人工保留门对每一次调用都开升级, 而 MCP 生态里占大头的恰恰是只读检索;敢分级则是因为 MCP 规范自己写明 annotations 是 不可信提示,所以服务器只能在管理员划的线以内降风险,永远不能自己抬权限。 其余映射:description 去控制字符 + 4000 上限,缺省回落成一句说得清来源的话; inputSchema 直通(删 $schema、type 缺省补 object),序列化超 32KB 或含取不到的 $ref 则丢弃该工具——截断一份 JSON Schema 只会产出无效 schema,让整轮请求 400; minScope 拼成 mcp:<服务器>:<read|external_effect>,服务器名先压过一遍, 否则一个含 `*` 或 `:` 的名字能伪造出 mcp:*:read 绕开封禁规则。 整份清单级别的问题(raw 名重复、公开名坍缩)让整次翻译失败,不留半套。 qa/fixtures/ 是给 M5/M6 复用的常量夹具:两个工具正好把真值表跑成两端各一条, 外加一份把每条拒绝规则各命中一次的边界夹具。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
env:白名单不是黑名单。基座是插件宿主那六个键,其余一律不给。参考实现用的是黑名单 (过滤掉像凭据的键名、其余父环境全透传),这条已经被否决过——漏一个 MY_COMPANY_PAT 就全给出去了,测试里专门钉了这一条。 凭据不落库:服务器配置里的 env 过凭据形状黑名单(复用 plugin-host 那一份,不留副本), 真要给凭据就填指针不是值,API 进程 spawn 时从自己的 process.env 取。被引用的服务端变量 必须带 WORKHUB_MCP_SECRET_ 前缀——这条不立,引用式密钥就成了一个绕过整个白名单读任意 环境变量的原语(管理员填一个指向 COOKIE_SECRET 的引用就能把它递给第三方服务器); 也不能改用凭据形状黑名单来兜,因为 WORKHUB_MCP_SECRET_GITHUB 自己就命中 SECRET。 引用不存在时 fail-closed,不拿空串起进程——起来了只会在第一次调用时报一个远端 401,更难查。 precheck:不执行任何东西,命令存在性由调用方查好传进来。npx / pnpm dlx / yarn dlx / bunx / uvx 一类「现下现跑」的启动器按拍板拦掉,错误文案教用户先在本机装好、再填装好之后 的路径——放行会让仓库的两条既有红线(安装期脚本、sandbox 的 npm exec 禁令)对同一类风险 给出相反答案。 体检项是 8 条不是设计稿表里的 7 条:多出来的 secret_ref_scope 就是上面那条前缀规则。 另附 mcpPrecheckErrorCode,把每条 block 映射到稳定错误码,省得展示层去解析英文诊断。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
真值表覆盖:谁被剪(保留窗口之外且超预算)、保留窗口至少 1 条、幂等(靠标记识别 而不是长度,剪完的文本天然略超预算)、配对结构不动(消息条数/角色/tool_use_id/ is_error 全查)、压力重算口径、落盘文件名与总字节上限、无 workdir 分支。 计费可观测两条:剪枝够用 → 摘要请求数为 0 且不占压缩配额;剪枝不够 → 恰好一次。 golden 新增 6 份(既有 expected 一个字节未变):两种截断话术、spill 定位提示、 两种剪枝正文、以及一段固定历史的剪枝真值表 JSON。gen:expected 二次运行无变化。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
记下四件在代码里看不出来的事:为什么判据是「含汉字的字面量」而不是上游那套文案属性识别、 为什么上棘轮基线而不是全量豁免、为什么禁词门的 25 处存量记账而不是就地改词,以及立项时 说的「53 个 key 不对称」经逐文件核实并不存在(四份词典早就是编译期强制对称)。 附剩余 3648 条按形状的普查表与「后来者怎么继续搬」的操作步骤。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
index.ts 只导出纯模块——照 plugin-host 刻意不 re-export ./host.js 的先例,把 SDK 关在 apps/api 侧那一个文件里:SDK 还在快速迭代,破坏性改版就只砸在那一个文件上,本包契约不动。 Note 里写清三条在设计稿之外收紧的判断(密钥引用前缀、$ref 只放行同文档片段、 minScope 里的服务器名要压过),以及 M1b 的两处合并点与给 M0/M3/M7 的硬信息。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
# Conflicts: # .github/workflows/verify.yml
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
三条场景走 B6 留下的 runBoth harness:落盘后的截断文本与定位提示逐字相同、 剪枝够用时两边都零摘要请求且剪掉的字符数相同、剪枝不够时两边都恰好一次。 剪枝事件补 context_chars 字段并纳入事件投影——两侧上下文投影口径一旦漂移, 等价性检查立刻红(已用两次 mutation 验证:截断预算差 1、otherChars 差 1 都被抓到)。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
collectOutputExcerpts / 交付物清单 / 变动行数统计都只走 workdir/outputs, .spill/ 在 workdir 根下因此天然不被采集;这条用例把「天然」变成有门禁的事实。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…快照与内容哈希,revert 也不删它 - SnapshotTakeInput / RevertSnapshotInput 增 excludeDirs(顶层目录名精确匹配,含分隔符的条目直接拒绝) - listFiles 顶层跳过,takeFileSnapshot 与 hashWorkdir 共用;revert 整树换入后把当前的排除目录原样搬回 - agent-run-snapshots 与审计路由的 revert 传 [SPILL_DIR_NAME],不重复字面量 - 测试:.spill 不进快照目录、.spill 内容变化不改 contentSha256(CORE-04 去重仍生效)、revert 保留 .spill、不带参数行为不变 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
R26 集成批 2:文案 locale 独占门禁(B5)、两段式压缩与落盘(B10)、MCP 客户端 M0/M1/M1b、插件错误路径中和、
.spill/排除快照。基线为 PR #25 的头(含真机修复)。一、B5 文案 locale 独占 + AST 门禁(r26/u-ui-i18n-gate,7 commits)
pnpm audit:ui-i18n(scripts/dev/check-ui-i18n.ts,TypeScript 编译器 API):含汉字的字符串/模板字面量出现在非词典文件里即违规;允许文件i18n*.ts/locale*.ts/locales/**/*-copy.ts;两条失效保险丝(MINIMUM_SOURCES/MINIMUM_LOCALE_OWNER_SOURCES);行内ui-i18n-allow豁免(模型可见文本等)。scripts/dev/ui-i18n-baseline.json(按文件 + 归一化片段记,不按行号):基线内不报、新增即红、已消失提示删除。真实存量 5732 处(立项写的 261 处只数了一种写法),本批迁了 36%:桌面端 2086→662(-68%),route-components.ts892→526,19 份 per-modulelocales.ts(中文 key 集 + 英文satisfies)。渲染文案一字未改,全靠既有测试与 golden 兜底。check-copy-terms.ts从手写 5 个文件扩到 258 个文案文件(与新门共用发现逻辑,判据升级为 AST 字面量),25 处存量进copy-terms-baseline.json。audit:ui-i18n进 lint 链;test:scripts进 workspace job;两个文案门 pre-commit 只扫暂存文件(整条链 2.8–5.1 秒)。二、B10 两段式压缩 + 落盘(r26/w-two-stage-compaction,6 commits)
ceil(总条数 × 0.16))之外、超单条预算(2000 字符)的tool_result剪成头 75% + 双语标记 + 尾 15%,重算压力够了就不发摘要请求;配对守卫结构性成立(只改槽位字符串)。<workdir>/.spill/<四位步号>-<工具名>.txt(单 run 16 MiB 上限),截断文本末尾附定位提示,模型可用read_file取回;无 workdir/写失败退回纯截断话术。equivalence用例逐条 deepEqual;agent_run.compacting事件summary_kind增"pruned"。context-pruning.*;mutation 验证两处均能红。三、
.spill/排除出快照(07d1084f)SnapshotTakeInput/RevertSnapshotInput增excludeDirs(顶层目录名精确匹配,含分隔符的条目直接拒绝);listFiles顶层跳过,takeFileSnapshot与hashWorkdir共用(.spill内容变化不改contentSha256,CORE-04 去重仍生效);revert 整树换入后把当前的排除目录原样搬回。[SPILL_DIR_NAME];audit 与 api 两侧各一条测试钉住。四、MCP 客户端 M0 / M1 / M1b
packages/contracts/src/domain/mcp.ts(transport 阶段 0 只认 stdio、状态枚举、trust_level管理员断言列默认external_effect、体检项 8 条、McpServerVm/ 只读投影结构性不含命令与密钥引用);迁移0073_mcp_servers(journal idx 73)+ Drizzle 表 + 仓储 8 个原语全部工作区围栏 + 测试。packages/mcp-client(零 IO、零依赖):命名不变式(mcp__<server>__<tool>,有损时挂 12 位指纹,不提供反解)、结果 → 模型可见文本(中和 + 上限)、逐字段映射与读写分级真值表(管理员断言 AND 服务器自述,服务器只能在上限内降风险,缺省取最高风险)、env 白名单 + 引用式密钥(必须指向WORKHUB_MCP_SECRET_前缀,否则就是绕过白名单读任意变量的原语)、静态体检(含npx/pnpm dlx/uvx类现下现跑启动器一律拦)。101 测试。packages/tools新增sanitizeModelFacingText(围栏中和 + 上限 + 控制字符清理);插件工具 description 中和改为薄封装(行为逐字不变,gen:expected 零 diff);插件工具结果路径接中和 + 32KB 上限(设计稿 5.2 两处同源缺口)。plugin-host-client.ts的callToolcatch,fddccbeb):插件execute()抛出的 message 跨 RPC 边界后不再原样拼进errorToolResult;测试证明字面</outputs>被中和、40000 字符被封顶。secret_ref_scope与 M1 的 8 条对齐。expected 基线 diff 摘要(
pnpm gen:expected幂等,git status为空)packages/agent/expected/context-pruning.*,既有 expected 零改动。验证
pnpm -r typecheck全 Done;audit 7 / agent 313 / api 2113 / tools 71 / mcp-client 101 / plugin-host 48 / db 510 / contracts 194 / ui 261 / web 168 / desktop-webview 1698 / cuu 50 / scripts 12 全过。qa:plugin-smoke通过;cargo fmt / clippy 绿。🤖 Generated with Claude Code