Skip to content

fix(protected): match tool names case-insensitively across clients - #484

Merged
ranxianglei merged 1 commit into
masterfrom
2026-09-30_case-insensitive-tool-match
Oct 2, 2026
Merged

ranxianglei merged 1 commit into
masterfrom
2026-09-30_case-insensitive-tool-match

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Fixes the case-sensitivity defect reported in billion-context#1725.

Problem

matchToolPattern compared tool names with === (exact) and startsWith (glob prefix) — both case-sensitive. Client hosts disagree on tool-name casing: opencode sends read, Claude/ZCode send Read. Because every knob built on this matcher shares the single comparator, a user's pattern silently stopped matching on any client whose casing differed from the pattern:

  • recent-zone exclusion (NEVER_PRESERVE_RECENT_TOOLS is lowercase [decompress, search_context, read, bash]) never fired for Read/Bash, so those large results stayed pinned in the protected recent zone instead of being immediately compressible;
  • preserveRecentTools:["read"] was a silent no-op on such clients (it subtracted an already-non-matching entry);
  • same latent gap in protectedTools, protectedLatestTools, and absorb/crush excludeTools.

Fix

Lowercase both sides before comparing in matchToolPattern. One change fixes all six knobs uniformly across clients.

Monotonic / no regression: case-insensitivity only adds matches — any pair that matched under exact comparison still matches (a===b ⇒ a.toLowerCase()===b.toLowerCase()). No prior protection or exclusion is lost. The lone theoretical edge (a client registering two distinct tools differing only by case) over-matches in the conservative direction (protect more / crush less), not destructively. Wire tool names are untouched (internal comparison only), so message-id/ref numbering, rendered tag format, token snapshots, nudge cadence, and prefix-cache stability are all unaffected.

Contract note

This touches the protected-tool-filtering / soft-recent-zone semantics (§7.3 kernel contract) — flagged for human review. Per repo rules this needs ≥2 agent reviews before merge; merge is human-only. billion-context will bump its acp-kernel pin in a follow-up once this version is published to npm.

Pre-flight

  • npm run typecheck — clean
  • npm test — 1207/1207 pass
  • npm run build — ok
  • npm run format:check — clean
  • 5 new regression tests in tests/protect-recent.test.ts; verified they fail on the pre-fix code and pass here.

@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-09-30_case-insensitive-tool-match (2cc21fa)

Option A — Install from npm PR tag (recommended)

In your adapter project:

npm install acp-kernel@pr-484

Each push to this PR publishes a new version under the pr-484 npm tag.

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf acp-kernel-pr484.tgz
npm install ./package

This comment is automatically updated on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷

Review #1(agent)—— 结论:方向正确、验证充分,可合并(待第 2 个独立 agent review + 人工合并)。

查重

open/closed issue 与 PR 均检索(关键词 case-insensitive / casing / tool name matching):无同类在先工作,本 PR 是该根因的首个修复,来源标记(billion-context#1725)合规。

① 原 issue 方向审

billion-context#1725 的根因分析(floor 2/6)定位准确:全部工具名旋钮共用单一比较器 matchToolPattern,其两个分支(=== 与 glob startsWith)都大小写敏感。owner 在 #1725 floor 4 拍板「直接提交pr修复吧」,即 Phase 1;floor 6 已回答「read 是否默认保护」= 不改默认。本 PR 精确落地了已批准的 Phase 1,修的是根本原因(比较器),不是症状(某条配置)。diff 确认未动任何默认值(NEVER_PRESERVE_RECENT_TOOLS 内容不变、read 仍默认可压缩)。✅

② 回归审查(既有行为清点)

触碰路径:src/protected.ts 的 matchToolPattern 及其全部调用点。逐项判定:

既有行为 结论
精确名匹配语义 保持且单调增宽:a===b ⇒ lower(a)===lower(b),原匹配对无一丢失
尾 * 前缀 glob 语义 保持且单调增宽:JS toLowerCase 逐码元独立作用,前缀关系必保留
六个旋钮是否真被一处改动全覆盖 逐一 grep 核实:src/protected.ts:69(preserveRecentTools 减法)、:72(recent-zone 排除)、:110(protectedTools)、:185(protectedLatestTools)、src/absorb.ts:98、src/crush.ts:1720、src/ccr.ts:396(excludeTools)——全走该比较器,无遗漏旋钮
有意变更(已披露) 大写变体现在命中小写模式:Read/Bash 结果默认排除出最近区(正是本次修复);preserveRecentTools:["READ"] 能划掉内置 read;protected/exclude 类过匹配方向保守(更保护/更少 crush)
wire 工具名字节、message-id/ref 编号、渲染 tag、token 快照、nudge 节奏、prefix-cache 稳态字节 不受影响(纯内部比较,diff 不触任何渲染/编号路径)
ALWAYS_PROTECTED_TOOLS 硬保护、isToolProtected 回调 未动,保持原样
默认值 全部未动

未发现无意回归。

③ 代码审查

最小 diff(函数体 4 行 + 注释),实现正确;注释写明了动机、单调性论证与来源 issue,符合「注释只用于必要处」规范。无 as any/@ts-ignore,零依赖保持。toLowerCase() 为 locale-independent ASCII 安全路径,工具名场景无 Unicode 陷阱。

④ 测试有效性审查

5 条新测试有牙:正反断言齐全(Grep≠read、write_file∉read*),含端到端 computeProtectedRefs 用例。非恒真、非 mock 自嗨。本地独立复验(不只信 CI):把 master 的旧版 protected.ts 换回跑同一文件 → 恰为这 5 条新测试失败、19 条旧测试全过;换回 PR 版本 → 24/24 全过。证明它们真实钉住了 bug 与修复。

⑤ diff 整洁度

2 文件,+96/-4,每一行都与本 PR 目的相关;无版本行、无锁文件、无空行重排。干净。

CI 与合并状态

4 项检查全绿(test node24 / test node22 / pr-validation / build-artifact,commit 4689f50);mergeable_state: clean,base 即当前 master(5d7b538),无落后。

契约提示

本 PR 触及 §7.3 契约 #6(protected-tool filtering / soft-recent-zone 语义)——human-gated,按 §6 需 ≥2 个独立 agent review,本条为第 1 个(通过);合并仅限人工。

Review 中发现的新问题(已立项)

内核里还有一批针对 ACP 自身注入的协议工具名(compress/acp_rule 等)的硬编码大小写敏感比较,不走 matchToolPattern(src/protected.ts:105、tool-pairs.ts:29、rebuild.ts:74、wire/compress-detect.ts:61、hide-consumed.ts:182/195/217/235、prune.ts:309、absorb.ts:80-81、ccr.ts:390)。触发条件与 #1725 不同(需要宿主改写 ACP 注入工具名,目前无实证),风险面也不同(wire 层牵涉无损往返契约),故未并入本 PR,已单列:acp-kernel#486(带来源标记,列全所有点位,给了 A/B 两案供你拍板)。


一句话中文摘要:修复了工具名匹配大小写敏感导致跨客户端(read vs Read)六个配置旋钮静默失效的问题(统一比较器两侧小写化,单调增宽、零默认值变更);我本地独立复验了 5 条回归测试旧码必挂、新码全过,CI 四项全绿,diff 干净——从代码与行为看可以合并,按规矩还需第 2 个独立 agent review 后由你人工合并。

@ranxianglei

Copy link
Copy Markdown
Owner Author

Review #2(independent agent)—— 结论:同意 Review #1,可合并(人工)。以下为独立复验,非复读。

独立复验(全部亲手跑过)

  • 全量:PR head 4689f50 上 npm test 1207/1207、typecheck 干净。
  • 测试有牙(新代码↔旧代码互换):把 base 5d7b538 的旧 protected.ts 换回、只跑 tests/protect-recent.test.ts → 恰好 5 条新测试全红、19 条旧测试全绿;换回 PR 版 → 24/24。证明回归测试真实钉住 bug 与修复,不是恒真。
  • 调用点普查:grep 全仓 matchToolPattern → 8 个真实调用点:src/protected.ts:69(preserveRecentTools 减法)、:72(never-preserve)、:110(protectedTools)、:185(protectedLatestTools)、src/absorb.ts:98、src/crush.ts:1720、src/ccr.ts:396(excludeTools);src/index.ts:363 是 re-export。六个旋钮确实共用唯一比较器,无第七处遗漏。

正确性论证(自行推演,与 PR 一致)

  • 单调增宽:a===b ⇒ lower(a)===lower(b);前缀关系在逐码元 toLowerCase 下必保留(无整段重排)。旧行为无一丢失。
  • toLowerCase()(无参)是 locale 无关的 —— 土耳其 İ 陷阱属于 toLocaleLowerCase,工具名场景无 Unicode 风险。
  • 过匹配边缘(两工具仅大小写之差)方向保守:protect 更多 / crush 更少,PR body 已披露。
  • diff 只动比较器与测试:wire 工具名字节、渲染 tag、ref 编号、token 快照、nudge 节奏、prefix-cache 稳态均不受影响(核对过 touched paths)。
  • 默认收益即 #1725 症状本身:NEVER_PRESERVE_RECENT_TOOLS(小写 read/bash)现在能命中 Claude/ZCode 的 Read/Bash,大结果不再钉死在保护区。

范围纪律

ACP 自身注入工具名(compress/acp_rule)的硬编码大小写敏感比较(protected.ts:10、tool-pairs.ts:29、rebuild.ts:74、wire/compress-detect.ts:61、hide-consumed.ts:182/195/217/235、prune.ts:309)不并入本 PR 是对的 —— 触发面不同(需宿主改写 bili 注入的工具名,无实证)、风险面不同(wire 往返契约),已在 #486 单列。本 PR 只修用户配置旋钮,边界干净。

状态提示

契约

触及 §7.3 契约 #6(protected-tool filtering / soft-recent-zone)——human-gated。本条为第 2 个独立 agent review(通过)。合并仅限人工。

一句话:修复正确、单调、零默认值变更;测试经旧码互换取证;调用点无遗漏;范围边界干净 —— 从代码与行为角度可合,等你按流程人工合并。

Client hosts disagree on tool-name casing (opencode sends read, Claude/ZCode send Read). matchToolPattern compared with === / startsWith (case-sensitive), so every knob built on it -- protectedTools, protectedLatestTools, recent-zone exclusion, preserveRecentTools, absorb/crush excludeTools -- silently stopped matching on differently-cased clients.

Fix: lowercase both sides before comparing. Monotonic change: only adds matches, never removes one that held under exact matching, so no prior protection/exclusion is lost. The lone theoretical edge (two distinct tools differing only by case) over-matches in the conservative direction.

Regression tests fail on the old code and pass here. Fixes billion-context#1725.

Pre-flight: typecheck clean; full suite 1207/1207; build ok; prettier clean.
@ranxianglei
ranxianglei force-pushed the 2026-09-30_case-insensitive-tool-match branch from 4689f50 to 2cc21fa Compare October 2, 2026 01:55
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷

收到 Review #2 —— ≥2 独立 agent review 的要求现已满足(两条均通过),且「behind」提示我已核实并处理完毕,不是留给你合并前手动做的:

behind 状态:已核实 + 已 rebase

当前状态(刚查)

  • head:2cc21fa,mergeable_state: **clean**(不再 behind)
  • CI 4 项在新 head 上全部 success(pr-validation / test node24 / test node22 / build-artifact)

剩余步骤(均为人工/后续流程)

  1. 人工合并(agent 无权合并):fix(protected): match tool names case-insensitively across clients #484
  2. 合并后走 release 分支发版(v0.0.100,CI 自动 publish 到 npm)
  3. 上 npm 后 billion-context 侧 bump acp-kernel pin + 回归测试(Fixes #1725 收尾)——我在 #1725 线程跟进
  4. 遗留加固点已在 acp-kernel#486 单列(ACP 内部协议工具名硬编码比较),等你 A/B 拍板,不阻塞本 PR

一句话:两个独立 review 均通过、behind 已 rebase 修复且新 head CI 四项全绿、mergeable_state clean —— 万事俱备,只等你人工合并。

@ranxianglei
ranxianglei merged commit 76dfc92 into master Oct 2, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant