@zhushanwen/pi-unified-hooks 0.2.4 → 0.2.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/README.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # unified-hooks
2
2
 
3
+ > **[DEPRECATED] 本包已废弃,被 [`@zhushanwen/pi-base-tool-enhance`](https://www.npmjs.com/package/@zhushanwen/pi-base-tool-enhance) 取代,不再维护。**
4
+ >
5
+ > 能力承接落点:
6
+ >
7
+ > | 原 hook | 承接方式 |
8
+ > |---------|---------|
9
+ > | `test-timeout-guard` | base-tool-enhance 可配置 force-test 白名单:测试类命令**自动转后台执行**并返回 task_id(不再 block 打回补 timeout 参数) |
10
+ > | `network-timeout-guard` | 正则不迁入(这类命令时长不定且结果常被立即需要);挂死保护由模型显式 timeout + base-tool-enhance 可配置前台默认超时承接 |
11
+ > | `tool-error-handler` | base-tool-enhance 的 tool_error 审计(customType 保持 `unified-hooks:tool-error`,历史 entry 不断链) |
12
+ >
13
+ > 迁移指引:`pi uninstall npm:@zhushanwen/pi-unified-hooks` 后 `pi install npm:@zhushanwen/pi-base-tool-enhance`。两者同时安装会对 bash 产生双重拦截,务必先卸载本包。
14
+
3
15
  统一 hooks 管理器 — 将散落的 hooks 收集到一个扩展中统一维护,每个 hook 可独立启用/禁用。
4
16
 
5
17
  ## 功能
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@zhushanwen/pi-unified-hooks",
3
- "version": "0.2.4",
4
- "description": "Unified hooks extension - collect scattered hooks in one place for easy maintenance",
3
+ "version": "0.2.6",
4
+ "description": "[DEPRECATED] Unified hooks extension - collect scattered hooks in one place for easy maintenance",
5
+ "deprecated": "Superseded by @zhushanwen/pi-base-tool-enhance — install that instead; test-command guarding, tool-error audit and configurable timeouts are all covered.",
5
6
  "main": "index.ts",
6
7
  "type": "module",
7
8
  "xyz-agent": {
@@ -19,7 +20,7 @@
19
20
  ],
20
21
  "license": "MIT",
21
22
  "dependencies": {
22
- "@zhushanwen/pi-extension-logger": "0.2.2"
23
+ "@zhushanwen/pi-extension-logger": "0.3.0"
23
24
  },
24
25
  "peerDependencies": {
25
26
  "@earendil-works/pi-coding-agent": "^0.84.1"
@@ -3,20 +3,19 @@ import { describe, expect, it, vi } from "vitest";
3
3
 
4
4
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
5
5
 
6
- // Mock 共享 logger,让 logger.warn 可被 spy
6
+ import { setupToolErrorHandler } from "../hooks/tool-error-handler.ts";
7
+
8
+ // 回归防护:源码已删泛化 logger.warn 双写(D5),若被重新引入测试必须红。
9
+ // mock 掉共享 logger,使 loggerMock.warn 可被断言未被调。
7
10
  const { loggerMock } = vi.hoisted(() => ({
8
- loggerMock: {
9
- debug: vi.fn(),
10
- warn: vi.fn(),
11
- error: vi.fn(),
12
- },
11
+ loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn() },
13
12
  }));
14
13
  vi.mock("@zhushanwen/pi-extension-logger", () => ({
15
14
  getLogger: () => loggerMock,
15
+ createLogger: () => loggerMock,
16
+ setPiHandle: vi.fn(),
16
17
  }));
17
18
 
18
- import { setupToolErrorHandler } from "../hooks/tool-error-handler.ts";
19
-
20
19
  // --- helper types ---
21
20
  interface MockPi {
22
21
  on: ReturnType<typeof vi.fn>;
@@ -40,9 +39,8 @@ describe("setupToolErrorHandler", () => {
40
39
  expect(pi.on).toHaveBeenCalledWith("tool_execution_end", expect.any(Function));
41
40
  });
42
41
 
43
- it("logs via logger.warn and appendEntry with dedicated customType on isError:true", async () => {
42
+ it("appendEntry with dedicated customType on isError:true", async () => {
44
43
  const pi = createMockPi();
45
- loggerMock.warn.mockClear();
46
44
  pi.appendEntry.mockClear();
47
45
 
48
46
  setupToolErrorHandler(pi as unknown as ExtensionAPI);
@@ -50,19 +48,10 @@ describe("setupToolErrorHandler", () => {
50
48
 
51
49
  await handler({ isError: true, toolName: "read", toolCallId: "call-42" });
52
50
 
53
- // logger.warn 被调一次(内部走泛化 appendEntry customType
54
- expect(loggerMock.warn).toHaveBeenCalledTimes(1);
55
- expect(loggerMock.warn).toHaveBeenCalledWith(
56
- "[unified-hooks] read error (callId=call-42)",
57
- expect.objectContaining({
58
- toolName: "read",
59
- toolCallId: "call-42",
60
- errorText: null,
61
- }),
62
- );
63
- // 额外 appendEntry 用专属 customType "unified-hooks:tool-error",
64
- // 保留按 entry type 过滤 tool 错误的埋点契约
51
+ // 契约载体 = 专属 customType "unified-hooks:tool-error"
65
52
  expect(pi.appendEntry).toHaveBeenCalledTimes(1);
53
+ // 回归防护(D5):泛化 logger.warn 双写不得回归
54
+ expect(loggerMock.warn).not.toHaveBeenCalled();
66
55
  expect(pi.appendEntry).toHaveBeenCalledWith(
67
56
  "unified-hooks:tool-error",
68
57
  expect.objectContaining({
@@ -73,16 +62,16 @@ describe("setupToolErrorHandler", () => {
73
62
  );
74
63
  });
75
64
 
76
- it("does nothing on isError:false (no logger.warn)", async () => {
65
+ it("does nothing on isError:false (no appendEntry)", async () => {
77
66
  const pi = createMockPi();
78
- loggerMock.warn.mockClear();
67
+ pi.appendEntry.mockClear();
79
68
 
80
69
  setupToolErrorHandler(pi as unknown as ExtensionAPI);
81
70
  const handler = pi.on.mock.calls[0]![1] as (event: unknown) => Promise<void>;
82
71
 
83
72
  await handler({ isError: false, toolName: "bash", toolCallId: "call-99" });
84
73
 
85
- expect(loggerMock.warn).not.toHaveBeenCalled();
74
+ expect(pi.appendEntry).not.toHaveBeenCalled();
86
75
  });
87
76
 
88
77
  // --- edge cases ---
@@ -97,7 +86,7 @@ describe("setupToolErrorHandler", () => {
97
86
 
98
87
  it("handles concurrent error events independently", async () => {
99
88
  const pi = createMockPi();
100
- loggerMock.warn.mockClear();
89
+ pi.appendEntry.mockClear();
101
90
 
102
91
  setupToolErrorHandler(pi as unknown as ExtensionAPI);
103
92
  const handler = pi.on.mock.calls[0]![1] as (event: unknown) => Promise<void>;
@@ -108,13 +97,13 @@ describe("setupToolErrorHandler", () => {
108
97
  handler({ isError: false, toolName: "edit", toolCallId: "e3" }),
109
98
  ]);
110
99
 
111
- expect(loggerMock.warn).toHaveBeenCalledTimes(2);
112
- expect(loggerMock.warn).toHaveBeenCalledWith(
113
- "[unified-hooks] read error (callId=e1)",
100
+ expect(pi.appendEntry).toHaveBeenCalledTimes(2);
101
+ expect(pi.appendEntry).toHaveBeenCalledWith(
102
+ "unified-hooks:tool-error",
114
103
  expect.objectContaining({ toolName: "read", toolCallId: "e1" }),
115
104
  );
116
- expect(loggerMock.warn).toHaveBeenCalledWith(
117
- "[unified-hooks] bash error (callId=e2)",
105
+ expect(pi.appendEntry).toHaveBeenCalledWith(
106
+ "unified-hooks:tool-error",
118
107
  expect.objectContaining({ toolName: "bash", toolCallId: "e2" }),
119
108
  );
120
109
  });
@@ -123,7 +112,7 @@ describe("setupToolErrorHandler", () => {
123
112
 
124
113
  it("从 result.content[0].text 提取错误文本(如 'hub disposed')", async () => {
125
114
  const pi = createMockPi();
126
- loggerMock.warn.mockClear();
115
+ pi.appendEntry.mockClear();
127
116
 
128
117
  setupToolErrorHandler(pi as unknown as ExtensionAPI);
129
118
  const handler = pi.on.mock.calls[0]![1] as (event: unknown) => Promise<void>;
@@ -135,8 +124,8 @@ describe("setupToolErrorHandler", () => {
135
124
  result: { content: [{ type: "text", text: "hub disposed" }] },
136
125
  });
137
126
 
138
- expect(loggerMock.warn).toHaveBeenCalledWith(
139
- "[unified-hooks] subagent error (callId=call-disposed)",
127
+ expect(pi.appendEntry).toHaveBeenCalledWith(
128
+ "unified-hooks:tool-error",
140
129
  expect.objectContaining({
141
130
  toolName: "subagent",
142
131
  toolCallId: "call-disposed",
@@ -147,7 +136,7 @@ describe("setupToolErrorHandler", () => {
147
136
 
148
137
  it("result 缺失或无 content 时降级到无详情(不崩)", async () => {
149
138
  const pi = createMockPi();
150
- loggerMock.warn.mockClear();
139
+ pi.appendEntry.mockClear();
151
140
 
152
141
  setupToolErrorHandler(pi as unknown as ExtensionAPI);
153
142
  const handler = pi.on.mock.calls[0]![1] as (event: unknown) => Promise<void>;
@@ -160,9 +149,9 @@ describe("setupToolErrorHandler", () => {
160
149
  await handler({ isError: true, toolName: "bash", toolCallId: "x3", result: "oops" });
161
150
 
162
151
  // 三次都降级为无详情
163
- expect(loggerMock.warn.mock.calls[0]![0]).toBe("[unified-hooks] bash error (callId=x1)");
164
- expect(loggerMock.warn.mock.calls[0]![1]).toHaveProperty("errorText", null);
165
- expect(loggerMock.warn.mock.calls[1]![1]).toHaveProperty("errorText", null);
166
- expect(loggerMock.warn.mock.calls[2]![1]).toHaveProperty("errorText", null);
152
+ expect(pi.appendEntry).toHaveBeenCalledTimes(3);
153
+ expect(pi.appendEntry.mock.calls[0]![1]).toHaveProperty("errorText", null);
154
+ expect(pi.appendEntry.mock.calls[1]![1]).toHaveProperty("errorText", null);
155
+ expect(pi.appendEntry.mock.calls[2]![1]).toHaveProperty("errorText", null);
167
156
  });
168
157
  });
@@ -11,9 +11,6 @@
11
11
  */
12
12
 
13
13
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
14
- import { getLogger } from "@zhushanwen/pi-extension-logger";
15
-
16
- const logger = getLogger("unified-hooks");
17
14
 
18
15
  /**
19
16
  * Subset of `ToolExecutionEndEvent` fields used by this hook.
@@ -86,16 +83,10 @@ export function setupToolErrorHandler(pi: ExtensionAPI): void {
86
83
  // SDK 事件无 errorMessage 字段,只能从这里捞;拿不到也不阻断(降级到无详情)。
87
84
  const errorText = extractErrorText(e.result);
88
85
 
89
- // appendEntry 持久化到 session entries,供事后排查(无 UI、不泄漏)。
90
- // errorText 一起存上——事后排查能看到真实原因(如 "hub disposed")。
86
+ // 契约载体 = 专属 customType `unified-hooks:tool-error`(含 timestamp/toolName/toolCallId/errorText 全量字段)。
87
+ // 泛化 `unified-hooks:log` 冗余已删(无独有信息,全仓无消费方)。
91
88
  // 不调 ctx.ui.notify——tool error 已在对话流里(pi 原生 tool result),
92
89
  // notify 会重复显示且措辞("bash error")误导。
93
- //
94
- // 注意:除了 logger.warn(内部走泛化 `unified-hooks:log` customType),
95
- // 这里额外调一次 `pi.appendEntry("unified-hooks:tool-error", ...)`。
96
- // 原因:logger 内部的 appendEntry 用的是泛化 customType,无法区分 entry
97
- // 是否为 tool 错误;保留专属 entry type 让事后按 customType 过滤 tool
98
- // 错误的脚本/dashboard 仍可工作(埋点契约)。
99
90
  const entry = {
100
91
  timestamp: Date.now(),
101
92
  toolName: e.toolName,
@@ -103,6 +94,5 @@ export function setupToolErrorHandler(pi: ExtensionAPI): void {
103
94
  errorText: errorText ?? null,
104
95
  };
105
96
  pi.appendEntry("unified-hooks:tool-error", entry);
106
- logger.warn(`[unified-hooks] ${e.toolName} error (callId=${e.toolCallId})`, entry);
107
97
  });
108
98
  }