Skip to content

默认保护工具大小写问题失效 #1725

Closed

Description

@ranxianglei
Image

另外需要注意不通的客户端名字可能还不一样 讨论统一解决方案

Activity

  1. ranxianglei commented on Sep 30, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

    [bot] 🏷

    已按源码逐行核实——这个「失效」是真实 bug,根因在 acp-kernel 的一个比较函数上。先给结论,再给统一方案。

    一、根因(已在源码层确认)

    acp-kernel 里所有工具名匹配都走同一个比较器 matchToolPattern(dist/index.js):

    function matchToolPattern(toolName, pattern) {
      if (pattern.endsWith("*")) return toolName.startsWith(pattern.slice(0, -1)); // 前缀,区分大小写
      return toolName === pattern;                                                  // 全等,区分大小写
    }

    两个分支都大小写敏感。它同时支撑全部工具名旋钮:protectedTools / protectedLatestTools / neverPreserveRecentTools(最近区排除)/ preserveRecentTools(正向豁免),以及 absorb、ccr 的 excludeTools(同样调这个函数)。

    内置默认排除表是小写 ["decompress","search_context","read","bash"](kernel NEVER_PRESERVE_RECENT_TOOLS)。bili 侧对工具名零归一化(我全局查过,所有 toLowerCase 都在处理 host/header/env,没有一个碰工具名),wire 上的名字原样进 kernel。于是:

    opencode(发小写 read) ZCode(发大写 Read)
    默认表里的 read 命中 → read 结果立即可压缩 永不命中("Read" !== "read")→ Read 结果默认就受保护
    配了 preserveRecentTools:["read"] 把 read 从表里划掉 → 行为改变 划掉一条本来就没命中的空规则 → 零变化

    这就是你截图那张对照表的机制,我在源码层验证成立。你机器上「参数没效果」不是参数坏了,而是大小写对不上让那条规则在你这成了死条款。

    二、统一方案(建议,均不新增用户配置面)

    问题分两层:① 纯大小写差异(本次报的;主流客户端 opencode / Claude / ZCode 基本都属于这类);② 完全不同的名字(你说的「名字可能还不一样」,主要出现在 codex 系这种工具词汇表不同的 harness)。

    Phase 1 —— 根因修复:把 matchToolPattern 改成大小写不敏感(两侧统一 casefold 后比较)。

    • 一处改动,上述所有旋钮、所有客户端同时受益——这就是「统一」的最小充要形态。
    • 零新配置项;内置默认表自动跟着变一致(opencode 小写、Claude/ZCode 大写都能命中 read/bash)。
    • 唯一要披露的行为变化:模式匹配今后不区分大小写。极端情况若某客户端真有两个只差大小写的不同工具会被一并命中——实践几乎不存在,方向上也偏「更保护」。
    • ⚠️ 归属:该函数在 acp-kernel,属跨仓变更(AGENTS.md 规定跨仓走人工)——需先发 kernel 新版 → npm 可装 → 本仓再 bump pin + 补回归测试。

    Phase 2 ——(可选,待数据)完全异名的规范化别名表:把已知跨客户端同义名归一到 canonical token 后再匹配(如 shell/exec/run_command → bash)。

    三、需要你拍板的两点

    1. Phase 1(大小写不敏感)现在立项推进吗? 跨仓 + 行为语义变更,按规矩得你点头我才动手。
    2. Phase 2 是否纳入本期? 若纳入,请点名你观察到的「完全异名」的客户端和具体工具名,我据此设计内置映射;否则本期只做 Phase 1。

    (查重:open/closed issue 与 devlog 均无同类记录,是新问题。)

    一句话:bug 属实,根子在 acp-kernel 那个大小写敏感的 matchToolPattern;最干净的统一解是先把它改成大小写不敏感(一处修好全部旋钮+默认表),完全异名的别名表等你给清单再说。

  2. ranxianglei commented on Sep 30, 2026

    @ranxianglei
    OwnerAuthor

    直接提交pr修复吧 需要讨论如果改成大小写敏感会带来问题吗

    另外read工具是不是应该默认保护

  3. ranxianglei commented on Sep 30, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

    [bot] 🏷

    好,开工。先把两个讨论点讲清楚,再说 PR 怎么落。

    一、改成大小写不敏感会带来问题吗

    核心安全性质是单调性:大小写不敏感只会「多匹配」,绝不「少匹配」——若旧逻辑 a === b 成立,则 a.toLowerCase() === b.toLowerCase() 必成立。所以原来被保护的仍被保护、原来被排除的仍被排除,唯一差异是一些「只差大小写、过去匹配不上」的组合现在开始命中。

    所有旋钮共用同一个 matchToolPattern,一处改全部生效:

    旋钮 变化方向 评估
    neverPreserveRecentTools(最近区排除,本 bug) Read 现在命中 read → 立即可压缩 ✅ 正是想要的修复
    protectedTools MyTool 模式也命中 mytool ⚠️ 方向「更保护」;实践不存在只差大小写的两个不同工具,可忽略
    preserveRecentTools(正向豁免) ["READ"] 也能划掉内置 read 条目 ✅ 让这条配置跨客户端真正生效
    absorb / crush 的 excludeTools 不同大小写名也被排除在 crush/absorb 外 ✅ 偏保守(少 crush),无破坏性

    唯一理论风险:某客户端真注册了两个只差大小写的不同工具(如同时有 Read 与 read)——一个模式会把它俩都命中。实际客户端几乎不会这么做;即便发生,过匹配方向也是「更保护 / crush 更少」,非破坏性。可接受。

    不受影响:wire 上的工具名字节一个都不改(只改内部比较,不动保真);message-id/ref 编号、渲染 tag 格式、token 快照、nudge 节奏全不变;prefix-cache 稳态字节不变(只改「哪些工具结果够格进最近区排除」这个决策,不改任何渲染输出)。

    结论:单调增宽,无回归面,唯一边界情形方向保守,可以安全落地。

    二、read 工具是否应默认保护

    先厘清「默认」。当前设计里 read 在 NEVER_PRESERVE_RECENT_TOOLS(内核常量注释写明理由:file/image contents — the largest common source of context bloat),即 read 结果默认被排除出受保护最近区 = 立即可压缩。这是刻意设计,不是 bug。

    我的建议:维持现状(read 默认可压缩,不默认保护),理由是不对称成本:

    • read 返回文件正文,是最大上下文膨胀源;
    • 但它最便宜可再生——大不了重新读一遍磁盘,代价只是一次重读;
    • 反之把一堆 read 钉死在最近区 = 持续膨胀,正好抵消这个特性的目的;
    • 对比 search_context / 命令输出,read 的可再生性最高,所以默认排它最划算。

    若你确实想让 read 默认受保护,那是产品级默认值变更(AGENTS.md 规定默认值变更必须人拍板),我可以把 read 从内置表移除来实现;但我不建议全局这么干——真有需要的客户端用 preserveRecentTools:["read"] 按客户端开即可,而这个逃生口本次修复后也能跨客户端正常工作了。

    所以 #3 我倾向不改,除非你有具体场景坚持要默认保护,告诉我我再单列。

    三、PR 怎么落(跨仓)

    根因函数 matchToolPattern 在 acp-kernel;bili 侧只是透传配置 + 在 src/decompress-shared.ts:442 调它一次。因此:

    1. acp-kernel:改 matchToolPattern 为两侧 toLowerCase() 后比较 + 补回归测试(Read 命中 read;computeProtectedRefs 不再把新鲜 Read 结果算进保护区)。按内核规矩,这属 §7.3 契约(protected-tool / recent-zone 语义)+ 跨仓,需 ≥2 agent review + 人工合并,且内核要先发版。
    2. billion-context:等 acp-kernel 新版上 npm 后,bump pin + 构建 + 回归测试,另开 PR(Fixes #1725)。

    我现在就去改 acp-kernel 并开 PR,稍后把链接贴回这里。#3 若要改默认,说一声我一并处理。

  4. ranxianglei commented on Sep 30, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

    [bot] 🏷

    PR 已提交(内核侧,根因所在):

    ranxianglei/acp-kernel#484 — fix(protected): match tool names case-insensitively across clients

    改法就是之前说的 Phase 1:把 matchToolPattern 两侧 toLowerCase() 后比较。一处改动让全部六个旋钮(protectedTools / protectedLatestTools / 最近区排除 / preserveRecentTools / absorb、crush 的 excludeTools)跨客户端统一生效。

    已验证(不是「应该能跑」):

    • 5 条新回归测试(tests/protect-recent.test.ts)——我把源码临时还原成旧版跑了一遍,这 5 条全部失败,再套上修复后全过,证明它们真能抓住这个 bug;
    • typecheck 干净;全量测试 1207/1207;build 成功;prettier 干净;
    • diff 只有两个文件(src/protected.ts + 测试),无版本改动、无无关改动。

    关于 #3(read 是否默认保护):我没动默认值。 本次只修比较器,read 仍按原设计被排除出最近区(默认可压缩)。若你要改成默认保护 read,那是产品级默认值变更,告诉我我单开一个改动。

    接下来的跨仓流程(需要你推进的部分):

    1. acp-kernel#484 → 按内核规矩需 ≥2 agent review + 人工合并(它属 §7.3 protected/recent-zone 契约);
    2. 合并后发 acp-kernel 新版本(CI 自动 publish 到 npm);
    3. 我看到新版上 npm 后,在 billion-context bump pin + 构建 + 回归测试,另开 PR(Fixes #1725)——这步等内核发版才能绿,所以现在先停在这里。

    一句话:根因修复已进 acp-kernel#484 并验证通过(单调增宽、无回归面),read 默认行为未动;等你 review/合并并发版后我就把 billion-context 的 pin 提上来收尾。

  5. ranxianglei commented on Sep 30, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

    [bot] 🏷

    补一条最新状态(刚重新核过):

    • acp-kernel npm latest 仍是 0.0.98;PR release v0.1.75 #484 仍 open、未合并(mergeable_state: clean)。npm 上只有 CI 的 PR 预览包(0.0.98-pr.484.340)——billion-context 的精确 pin 不能指向 -pr. 预发布版,所以 billion-context 提 pin 这步仍被卡住,现在做只会指向未发布版本、开出红 PR,不提前。

    小结:

    下一步(需要你推进):#484 过 ≥2 agent review + 人工合并 → CI 发版上 npm → 我再 bump pin + 补 bili 侧回归测试 + 开 Fixes #1725 的 PR 收尾。

  6. added a commit that references this issue on Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingp2

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions