@zhushanwen/pi-unified-hooks 0.2.4 → 0.2.5
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-unified-hooks",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Unified hooks extension - collect scattered hooks in one place for easy maintenance",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
],
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@zhushanwen/pi-extension-logger": "0.
|
|
22
|
+
"@zhushanwen/pi-extension-logger": "0.3.0"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"@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
|
-
|
|
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("
|
|
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
|
-
//
|
|
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
|
|
65
|
+
it("does nothing on isError:false (no appendEntry)", async () => {
|
|
77
66
|
const pi = createMockPi();
|
|
78
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
112
|
-
expect(
|
|
113
|
-
"
|
|
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(
|
|
117
|
-
"
|
|
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
|
-
|
|
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(
|
|
139
|
-
"
|
|
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
|
-
|
|
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(
|
|
164
|
-
expect(
|
|
165
|
-
expect(
|
|
166
|
-
expect(
|
|
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
|
-
//
|
|
90
|
-
//
|
|
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
|
}
|