@zhushanwen/pi-permission 1.6.4 → 1.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhushanwen/pi-permission",
3
- "version": "1.6.4",
3
+ "version": "1.6.6",
4
4
  "description": "Pi permission extension — four permission modes (yolo/auto/approve/strict) with three-layer pipeline (AST + rules + AI classifier).",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -50,6 +50,7 @@
50
50
  "dependencies": {
51
51
  "tree-sitter-bash": "^0.25.1",
52
52
  "web-tree-sitter": "^0.26.11",
53
+ "@zhushanwen/extension-protocol": "0.24.0",
53
54
  "@zhushanwen/pi-ext-guards": "0.5.0",
54
55
  "@zhushanwen/pi-extension-logger": "0.6.2",
55
56
  "@zhushanwen/pi-llm-shared": "0.12.0"
@@ -11,6 +11,9 @@ import {
11
11
  renderApprovalView,
12
12
  requestUserApproval,
13
13
  } from "../approval.js";
14
+ // 对拍符号:runtime event-adapter 判定键与本扩展 title 构造引用同一常量(值锁定在
15
+ // extension-protocol markers.test.ts),单源化后双端文案漂移在编译期即报错
16
+ import { PERMISSION_APPROVAL_TITLE } from "@zhushanwen/extension-protocol";
14
17
  import type { ApprovalRequest } from "../pipeline.js";
15
18
  import type { ClassifierResult, ToolInvocationContext, UserDecision } from "../types.js";
16
19
 
@@ -176,7 +179,7 @@ describe("requestUserApproval: rpc 模式", () => {
176
179
  const lines = capturedTitle.split("\n");
177
180
  // RPC title 5→6 行:补 ` reasoning: <AI 理由>` 行(与 TUI 字段集同源,M6 漂移修复)
178
181
  expect(lines).toHaveLength(6);
179
- expect(lines[0]).toBe("[pi-permission] Approval required");
182
+ expect(lines[0]).toBe(PERMISSION_APPROVAL_TITLE);
180
183
  expect(lines[1]).toBe("Tool: bash");
181
184
  expect(lines[2]).toBe("Command: rm -rf /tmp");
182
185
  expect(lines[3]).toBe("Reason: no allow rule");
@@ -14,6 +14,9 @@
14
14
  * 注册,D3「旧名不保留别名」),不对其迁移后结论作断言;
15
15
  * 5b. 反向混合集(wildcard deny 在前、精确 allow 在后)——与分支5互为镜像,
16
16
  * 区分度弱,承重位置锚 = 精确条目仍在 wildcard 之前。
17
+ * 6. subagents → subagent_batch(2026-10-23 批量工具改名入表)——单目标展开:
18
+ * 展开正确 + 字段保留 + 幂等 + 保序位置锚 + 真实 matcher 结论等价四维度合并
19
+ * 锁定(单元素展开无次序承重,四维度一用例足够)。
17
20
  *
18
21
  * 匹配器行为注释:matchNonBashTool 的 tool 字段经 wildcardToRegExp 全锚定匹配
19
22
  * (精确名 = 精确匹配,'*' = 匹配全部);pattern 对 path 匹配(path 缺省时对
@@ -34,7 +37,7 @@ function userRule(overrides: Partial<Rule> & { id: string; tool: string }): Rule
34
37
  };
35
38
  }
36
39
 
37
- describe("旧工具名精确规则迁移(D3)— 表驱动六用例(五分支 + 分支5b 反向混合集)", () => {
40
+ describe("旧工具名精确规则迁移(D3)— 表驱动七用例(六分支 + 分支5b 反向混合集)", () => {
38
41
  it("分支1:精确旧名展开(subagent 三条 + workflow 两条,展开顺序承重,其余字段保留)", () => {
39
42
  const rules: Rule[] = [
40
43
  userRule({ id: "user-1", tool: "subagent", pattern: "*", action: "deny", description: "no subagent" }),
@@ -196,4 +199,36 @@ describe("旧工具名精确规则迁移(D3)— 表驱动六用例(五分
196
199
  expect(matchNonBashTool("bash", "/x", migrated).action).toBe(matchNonBashTool("bash", "/x", mixed).action);
197
200
  expect(matchNonBashTool("bash", "/x", mixed).action).toBe("deny");
198
201
  });
202
+
203
+ it("分支6:subagents → subagent_batch(2026-10-23 批量工具改名入表)— 展开 + 幂等 + 保序 + matcher 结论等价", () => {
204
+ // 展开正确 + 字段保留:单目标展开(一条旧名规则 → 一条 subagent_batch)
205
+ const rules: Rule[] = [
206
+ userRule({ id: "user-1", tool: "subagents", pattern: "*", action: "deny", description: "no batch" }),
207
+ userRule({ id: "user-2", tool: "*", pattern: "*", action: "allow" }),
208
+ ];
209
+ const migrated = migrateLegacyToolRules(rules);
210
+
211
+ // 保序位置锚:展开条目原位替换,wildcard 仍在后(单目标无展开次序承重)
212
+ expect(migrated).toHaveLength(2);
213
+ expect(migrated.map((r) => r.tool)).toEqual(["subagent_batch", "*"]);
214
+ // pattern/action/source/description 保留(只换 tool 名与 id)
215
+ expect(migrated[0]).toMatchObject({ pattern: "*", action: "deny", source: "user", description: "no batch" });
216
+ // 新 id(D3 契约②):不克隆原 id,形态 user-N
217
+ expect(migrated[0]?.id).not.toBe("user-1");
218
+ expect(migrated[0]?.id).toMatch(/^user-\d+$/);
219
+ // 纯函数:原数组不改写
220
+ expect(rules).toHaveLength(2);
221
+ expect(rules[0]?.tool).toBe("subagents");
222
+
223
+ // 幂等:新名不在旧名集合,再迁移深度相等
224
+ expect(migrateLegacyToolRules(migrated)).toEqual(migrated);
225
+
226
+ // 真实 matcher 结论等价:场景同分支5(尾部 wildcard allow 覆盖精确 deny)——
227
+ // 若实装错误地把展开条目追加到数组尾部,subagent_batch deny 落到 wildcard
228
+ // allow 之后 → 结论翻成 deny ≠ 迁移前 allow,本断言即红灯。
229
+ const beforeSubagents = matchNonBashTool("subagents", "/x", rules).action;
230
+ const afterBatch = matchNonBashTool("subagent_batch", "/x", migrated).action;
231
+ expect(beforeSubagents).toBe("allow");
232
+ expect(afterBatch).toBe(beforeSubagents);
233
+ });
199
234
  });
package/src/approval.ts CHANGED
@@ -22,6 +22,10 @@
22
22
 
23
23
  import { type Component, matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
24
24
 
25
+ // 审批 select title 首行判定键单源:runtime event-adapter 按此串精确匹配落
26
+ // permission: true 键(侧栏等待可见性),双端 import 同一符号防文案漂移静默失联。
27
+ import { PERMISSION_APPROVAL_TITLE } from "@zhushanwen/extension-protocol";
28
+
25
29
  import type { ApprovalRequest } from "./pipeline.js";
26
30
  import type { ToolInvocationContext, UserDecision } from "./types.js";
27
31
 
@@ -223,7 +227,7 @@ interface ApprovalFieldSet {
223
227
  /** 构造审批卡字段集(title/tool/command/reason + 可选 AI 四元组含 reasoning)。 */
224
228
  function buildApprovalFieldSet(req: ApprovalRequest): ApprovalFieldSet {
225
229
  return {
226
- title: "[pi-permission] Approval required",
230
+ title: PERMISSION_APPROVAL_TITLE,
227
231
  tool: req.toolName,
228
232
  // 空字符串与 undefined 同义(两外壳的 Command 行都只在非空时渲染)
229
233
  command: req.command !== undefined && req.command.length > 0 ? req.command : undefined,
@@ -5,6 +5,7 @@
5
5
  * subagent_start/subagent_manage/subagent_message 与 workflow_run/workflow_control,
6
6
  * 旧名不保留别名(探针 PB-fork)。用户权限规则按工具名匹配(`Rule.tool`)并持久化
7
7
  * 落盘(config.ts saveConfig),旧名精确规则随改名失配——deny 静默退化成 ask/AI 分类。
8
+ * 批量工具 `subagents` → `subagent_batch` 改名(2026-10-23)同机制入表。
8
9
  *
9
10
  * 迁移语义(D3 两条契约):
10
11
  * ① 展开落位与保序——精确匹配旧名的用户规则**原地替换**为其新名展开(展开顺序
@@ -38,6 +39,7 @@ import { makeNextIdCounter } from "../rule-templates.js";
38
39
  const LEGACY_TOOL_NAME_EXPANSION: Readonly<Record<string, readonly string[]>> = {
39
40
  subagent: ["subagent_start", "subagent_manage", "subagent_message"],
40
41
  workflow: ["workflow_run", "workflow_control"],
42
+ subagents: ["subagent_batch"],
41
43
  };
42
44
 
43
45
  /**