@spzhongwin/skill-logger-plugin 1.0.14 → 1.0.16

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.
Files changed (57) hide show
  1. package/dist/index.js +80 -285
  2. package/openclaw.plugin.json +50 -50
  3. package/package.json +34 -37
  4. package/src/active-skills.test.ts +32 -32
  5. package/src/active-skills.ts +77 -77
  6. package/src/config-sync.test.ts +165 -165
  7. package/src/config-sync.ts +544 -544
  8. package/src/expert-skill-layout.test.ts +196 -0
  9. package/src/expert-skill-layout.ts +233 -0
  10. package/src/global.d.ts +4 -0
  11. package/src/hooks.test.ts +228 -251
  12. package/src/hooks.ts +494 -517
  13. package/src/http.ts +61 -61
  14. package/src/identity.ts +88 -64
  15. package/src/index.test.ts +53 -53
  16. package/src/index.ts +218 -226
  17. package/src/integration.test.ts +119 -119
  18. package/src/matcher.test.ts +170 -170
  19. package/src/matcher.ts +393 -393
  20. package/src/paths.test.ts +57 -57
  21. package/src/paths.ts +84 -84
  22. package/src/reporter.test.ts +139 -139
  23. package/src/reporter.ts +303 -298
  24. package/src/sample-config.json +72 -72
  25. package/src/semver.test.ts +33 -23
  26. package/src/semver.ts +65 -60
  27. package/src/skill-version.ts +53 -53
  28. package/src/types.ts +202 -198
  29. package/src/updater.test.ts +431 -325
  30. package/src/updater.ts +584 -549
  31. package/src/ws-client.test.ts +158 -128
  32. package/src/ws-client.ts +805 -769
  33. package/test-ws.ts +17 -17
  34. package/tsconfig.json +18 -14
  35. package/dist/active-skills.js +0 -67
  36. package/dist/active-skills.test.js +0 -29
  37. package/dist/config-sync.js +0 -439
  38. package/dist/config-sync.test.js +0 -145
  39. package/dist/hooks.js +0 -337
  40. package/dist/hooks.test.js +0 -123
  41. package/dist/http.js +0 -54
  42. package/dist/identity.js +0 -56
  43. package/dist/index.test.js +0 -39
  44. package/dist/integration.test.js +0 -102
  45. package/dist/matcher.js +0 -362
  46. package/dist/matcher.test.js +0 -139
  47. package/dist/paths.js +0 -62
  48. package/dist/paths.test.js +0 -49
  49. package/dist/reporter.js +0 -267
  50. package/dist/reporter.test.js +0 -128
  51. package/dist/semver.js +0 -64
  52. package/dist/semver.test.js +0 -21
  53. package/dist/skill-version.js +0 -23
  54. package/dist/types.js +0 -9
  55. package/dist/updater.js +0 -352
  56. package/dist/updater.test.js +0 -212
  57. package/dist/ws-client.js +0 -484
package/src/hooks.test.ts CHANGED
@@ -1,251 +1,228 @@
1
- import { describe, it } from "node:test";
2
- import assert from "node:assert/strict";
3
- import { Hooks } from "./hooks.ts";
4
- import { ActiveSkills } from "./active-skills.ts";
5
- import { buildIndex } from "./matcher.ts";
6
- import type { Reporter } from "./reporter.ts";
7
- import type { ConfigSync } from "./config-sync.ts";
8
- import type { SkillEvent, SkillStandardConfig } from "./types.ts";
9
-
10
- const sampleConfigs: SkillStandardConfig[] = [
11
- {
12
- skillName: "model-usage",
13
- version: "1.0.0",
14
- functions: [
15
- { id: "usage_current", name: "当前用量", match: { type: "script", script: "scripts/model_usage.py", argRules: [{ flag: "--mode", value: "current" }] } },
16
- ],
17
- },
18
- ];
19
-
20
- const tick = () => new Promise((r) => setImmediate(r));
21
-
22
- function setup() {
23
- const captured: SkillEvent[] = [];
24
- const reporter = { appendEvent: async (e: SkillEvent) => void captured.push(e) } as unknown as Reporter;
25
- const index = buildIndex(sampleConfigs);
26
- const lazy: string[] = [];
27
- const configSync = {
28
- getIndex: () => index,
29
- lazyCheck: async (name: string) => void lazy.push(name),
30
- resolveSkillName: () => undefined,
31
- getVersion: () => undefined,
32
- } as unknown as ConfigSync;
33
- const activeSkills = new ActiveSkills();
34
- const hooks = new Hooks(reporter, configSync, activeSkills, () => ({}));
35
- return { hooks, captured, lazy, activeSkills };
36
- }
37
-
38
- describe("Hooks 端到端串联", () => {
39
- it("read SKILL.md → skill_trigger + 标记激活 + 懒拉", async () => {
40
- const { hooks, captured, lazy, activeSkills } = setup();
41
- hooks.onBeforeToolCall(
42
- { toolName: "read", params: { path: "/e/skills/model-usage/SKILL.md" } },
43
- { sessionId: "s1", agentId: "main" }
44
- );
45
- await tick();
46
- assert.equal(captured.length, 1);
47
- assert.equal(captured[0].event_type, "skill_trigger");
48
- assert.equal(captured[0].skill_name, "model-usage");
49
- assert.equal(captured[0].invoke_mode, "inline");
50
- assert.deepEqual(lazy, ["model-usage"]);
51
- assert.ok(activeSkills.getActive("s1").has("model-usage"));
52
- });
53
-
54
- it("skill 工具直调(command-dispatch: tool)→ skill_trigger(tool)", async () => {
55
- const { hooks, captured, activeSkills } = setup();
56
- hooks.onBeforeToolCall({ toolName: "skill", params: { skill: "model-usage" } }, { sessionId: "s1" });
57
- await tick();
58
- assert.equal(captured.length, 1);
59
- assert.equal(captured[0].event_type, "skill_trigger");
60
- assert.equal(captured[0].invoke_mode, "tool");
61
- assert.ok(activeSkills.getActive("s1").has("model-usage"));
62
- });
63
-
64
- it("skill 工具直调有 toolCallId 时 after 报错 → 补记 function_call(error)", async () => {
65
- const { hooks, captured } = setup();
66
- hooks.onBeforeToolCall(
67
- { toolName: "skill", params: { skill: "model-usage" }, toolCallId: "skill-t1" },
68
- { sessionId: "s1" }
69
- );
70
- hooks.onAfterToolCall({ toolCallId: "skill-t1", errorMessage: "skill failed", elapsedMs: 9 });
71
- await tick();
72
-
73
- assert.equal(captured.length, 2);
74
- assert.equal(captured[0].event_type, "skill_trigger");
75
- const call = captured[1];
76
- assert.equal(call.event_type, "function_call");
77
- assert.equal(call.skill_name, "model-usage");
78
- assert.equal(call.invoke_tool, "skill");
79
- assert.equal(call.status, "error");
80
- assert.equal(call.error_message, "skill failed");
81
- assert.equal(call.duration_ms, 9);
82
- });
83
-
84
- it("after 未到达:flushAllPending 补记 unknown", async () => {
85
- const { hooks, captured } = setup();
86
- hooks.onBeforeToolCall(
87
- { toolName: "exec", params: { command: "python /e/skills/model-usage/scripts/model_usage.py --mode current" }, toolCallId: "t9" },
88
- { sessionId: "s1" }
89
- );
90
- await tick();
91
- assert.equal(captured.length, 0);
92
- await hooks.flushAllPending();
93
- await tick();
94
- assert.equal(captured.length, 1);
95
- assert.equal(captured[0].status, "unknown");
96
- assert.equal(captured[0].function_id, "usage_current");
97
- });
98
-
99
- it("session_end 补记该 session 残留 pending 并清激活", async () => {
100
- const { hooks, captured, activeSkills } = setup();
101
- hooks.onBeforeToolCall({ toolName: "read", params: { path: "/e/skills/model-usage/SKILL.md" } }, { sessionId: "s1" });
102
- hooks.onBeforeToolCall(
103
- { toolName: "exec", params: { command: "python /e/skills/model-usage/scripts/model_usage.py --mode current" }, toolCallId: "t10" },
104
- { sessionId: "s1" }
105
- );
106
- await tick();
107
- captured.length = 0;
108
- hooks.onSessionEnd({ sessionId: "s1" });
109
- await tick();
110
- assert.equal(captured.filter((e) => e.status === "unknown").length, 1);
111
- assert.deepEqual([...activeSkills.getActive("s1")], []);
112
- });
113
-
114
- it("exec 命中 → 暂存,after 成功 → function_call(success)", async () => {
115
- const { hooks, captured } = setup();
116
- hooks.onBeforeToolCall(
117
- { toolName: "exec", params: { command: "python /e/skills/model-usage/scripts/model_usage.py --mode current" }, toolCallId: "t1" },
118
- { sessionId: "s1" }
119
- );
120
- await tick();
121
- assert.equal(captured.length, 0); // 等 after
122
-
123
- hooks.onAfterToolCall({ toolCallId: "t1", durationMs: 42 });
124
- await tick();
125
- assert.equal(captured.length, 1);
126
- const ev = captured[0];
127
- assert.equal(ev.event_type, "function_call");
128
- assert.equal(ev.function_id, "usage_current");
129
- assert.equal(ev.match_type, "script");
130
- assert.equal(ev.status, "success");
131
- assert.equal(ev.duration_ms, 42);
132
- assert.equal(ev.args?.mode, "current");
133
- });
134
-
135
- it("after 带 error → function_call(error)", async () => {
136
- const { hooks, captured } = setup();
137
- hooks.onBeforeToolCall(
138
- { toolName: "exec", params: { command: "python /e/skills/model-usage/scripts/model_usage.py --mode current" }, toolCallId: "t2" },
139
- { sessionId: "s1" }
140
- );
141
- await tick();
142
- hooks.onAfterToolCall({ toolCallId: "t2", error: "boom" });
143
- await tick();
144
- assert.equal(captured[0].status, "error");
145
- assert.equal(captured[0].error_message, "boom");
146
- });
147
-
148
- it("after 的结构化错误与失败状态也会归为 error", async () => {
149
- const { hooks, captured } = setup();
150
- hooks.onBeforeToolCall(
151
- { toolName: "exec", params: { command: "python /e/skills/model-usage/scripts/model_usage.py --mode current" }, toolCallId: "t4" },
152
- { sessionId: "s1" }
153
- );
154
- await tick();
155
- hooks.onAfterToolCall({ toolCallId: "t4", status: "failed", message: "process failed", duration_ms: 7 });
156
- await tick();
157
- assert.equal(captured[0].status, "error");
158
- assert.equal(captured[0].error_message, "process failed");
159
- assert.equal(captured[0].duration_ms, 7);
160
- });
161
-
162
- it("after 的嵌套 result 失败信号会提取 stderr", async () => {
163
- const { hooks, captured } = setup();
164
- hooks.onBeforeToolCall(
165
- { toolName: "exec", params: { command: "python /e/skills/model-usage/scripts/model_usage.py --mode current" }, toolCallId: "t6" },
166
- { sessionId: "s1" }
167
- );
168
- await tick();
169
- hooks.onAfterToolCall({ toolCallId: "t6", result: { ok: false, stderr: "stderr failed" } });
170
- await tick();
171
- assert.equal(captured[0].status, "error");
172
- assert.equal(captured[0].error_message, "stderr failed");
173
- });
174
-
175
- it("未匹配 exec 但会话中只有一个激活 skill → 等 after 补记状态和错误", async () => {
176
- const { hooks, captured } = setup();
177
- hooks.onBeforeToolCall({ toolName: "read", params: { path: "/e/skills/model-usage/SKILL.md" } }, { sessionId: "s1" });
178
- await tick();
179
- captured.length = 0;
180
-
181
- hooks.onBeforeToolCall(
182
- { toolName: "exec", params: { command: "custom command --bad" }, toolCallId: "t5" },
183
- { sessionId: "s1" }
184
- );
185
- await tick();
186
- assert.equal(captured.length, 0);
187
-
188
- hooks.onAfterToolCall({ toolCallId: "t5", error: { message: "custom failed" }, durationMs: 11 });
189
- await tick();
190
- assert.equal(captured.length, 1);
191
- assert.equal(captured[0].event_type, "function_call");
192
- assert.equal(captured[0].skill_name, "model-usage");
193
- assert.equal(captured[0].invoke_tool, "exec");
194
- assert.equal(captured[0].command, "custom command --bad");
195
- assert.equal(captured[0].status, "error");
196
- assert.equal(captured[0].error_message, "custom failed");
197
- assert.equal(captured[0].duration_ms, 11);
198
- });
199
-
200
- it("report_skill_error 手动上报会记录 error 事件并携带会话 appKey", async () => {
201
- const { hooks, captured } = setup();
202
- hooks.onMessageReceived({ content: "appKey: abcdef123456" }, { sessionId: "s1" });
203
- hooks.onManualErrorRecord(
204
- {
205
- skill_name: "model-usage",
206
- tool_name: "usage_current",
207
- error_message: "manual failure",
208
- input_args: "--mode current",
209
- },
210
- { sessionId: "s1", agentId: "main", runId: "r1" }
211
- );
212
- await tick();
213
- assert.equal(captured.length, 1);
214
- assert.equal(captured[0].event_type, "function_call");
215
- assert.equal(captured[0].skill_name, "model-usage");
216
- assert.equal(captured[0].invoke_tool, "report_skill_error");
217
- assert.equal(captured[0].status, "error");
218
- assert.equal(captured[0].error_message, "manual failure");
219
- assert.equal(captured[0].app_key, "abcdef123456");
220
- assert.deepEqual(captured[0].args, { raw_args: "--mode current" });
221
- });
222
-
223
- it("未命中且默认配置 → 不记录", async () => {
224
- const { hooks, captured } = setup();
225
- hooks.onBeforeToolCall({ toolName: "exec", params: { command: "ls -la" }, toolCallId: "t3" }, { sessionId: "s1" });
226
- hooks.onAfterToolCall({ toolCallId: "t3" });
227
- await tick();
228
- assert.equal(captured.length, 0);
229
- });
230
- });
231
-
232
- describe("插件模块图可加载", () => {
233
- it("import index.ts 并 register 注册全部生命周期 hook", async () => {
234
- const mod = await import("./index.ts");
235
- const registered: string[] = [];
236
- mod.default.register({ on: (name: string) => registered.push(name) });
237
- assert.deepEqual(
238
- registered.sort(),
239
- [
240
- "after_tool_call",
241
- "before_install",
242
- "before_prompt_build",
243
- "before_tool_call",
244
- "gateway_start",
245
- "gateway_stop",
246
- "message_received",
247
- "session_end",
248
- ]
249
- );
250
- });
251
- });
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { Hooks } from "./hooks.ts";
4
+ import { ActiveSkills } from "./active-skills.ts";
5
+ import { buildIndex } from "./matcher.ts";
6
+ import type { Reporter } from "./reporter.ts";
7
+ import type { ConfigSync } from "./config-sync.ts";
8
+ import type { SkillEvent, SkillStandardConfig } from "./types.ts";
9
+
10
+ const sampleConfigs: SkillStandardConfig[] = [
11
+ {
12
+ skillName: "model-usage",
13
+ version: "1.0.0",
14
+ functions: [
15
+ { id: "usage_current", name: "当前用量", match: { type: "script", script: "scripts/model_usage.py", argRules: [{ flag: "--mode", value: "current" }] } },
16
+ ],
17
+ },
18
+ ];
19
+
20
+ const tick = () => new Promise((r) => setImmediate(r));
21
+
22
+ function setup() {
23
+ const captured: SkillEvent[] = [];
24
+ const reporter = { appendEvent: async (e: SkillEvent) => void captured.push(e) } as unknown as Reporter;
25
+ const index = buildIndex(sampleConfigs);
26
+ const lazy: string[] = [];
27
+ const configSync = {
28
+ getIndex: () => index,
29
+ lazyCheck: async (name: string) => void lazy.push(name),
30
+ resolveSkillName: () => undefined,
31
+ getVersion: () => undefined,
32
+ } as unknown as ConfigSync;
33
+ const activeSkills = new ActiveSkills();
34
+ const hooks = new Hooks(reporter, configSync, activeSkills, () => ({}));
35
+ return { hooks, captured, lazy, activeSkills };
36
+ }
37
+
38
+ describe("Hooks 端到端串联", () => {
39
+ it("read SKILL.md → skill_trigger + 标记激活 + 懒拉", async () => {
40
+ const { hooks, captured, lazy, activeSkills } = setup();
41
+ hooks.onBeforeToolCall(
42
+ { toolName: "read", params: { path: "/e/skills/model-usage/SKILL.md" } },
43
+ { sessionId: "s1", agentId: "main" }
44
+ );
45
+ await tick();
46
+ assert.equal(captured.length, 1);
47
+ assert.equal(captured[0].event_type, "skill_trigger");
48
+ assert.equal(captured[0].skill_name, "model-usage");
49
+ assert.equal(captured[0].invoke_mode, "inline");
50
+ assert.deepEqual(lazy, ["model-usage"]);
51
+ assert.ok(activeSkills.getActive("s1").has("model-usage"));
52
+ });
53
+
54
+ it("skill 工具直调(command-dispatch: tool)→ skill_trigger(tool)", async () => {
55
+ const { hooks, captured, activeSkills } = setup();
56
+ hooks.onBeforeToolCall({ toolName: "skill", params: { skill: "model-usage" } }, { sessionId: "s1" });
57
+ await tick();
58
+ assert.equal(captured.length, 1);
59
+ assert.equal(captured[0].event_type, "skill_trigger");
60
+ assert.equal(captured[0].invoke_mode, "tool");
61
+ assert.ok(activeSkills.getActive("s1").has("model-usage"));
62
+ });
63
+
64
+ it("skill 工具直调有 toolCallId 时 after 报错 → 补记 function_call(error)", async () => {
65
+ const { hooks, captured } = setup();
66
+ hooks.onBeforeToolCall(
67
+ { toolName: "skill", params: { skill: "model-usage" }, toolCallId: "skill-t1" },
68
+ { sessionId: "s1" }
69
+ );
70
+ hooks.onAfterToolCall({ toolCallId: "skill-t1", errorMessage: "skill failed", elapsedMs: 9 });
71
+ await tick();
72
+
73
+ assert.equal(captured.length, 2);
74
+ assert.equal(captured[0].event_type, "skill_trigger");
75
+ const call = captured[1];
76
+ assert.equal(call.event_type, "function_call");
77
+ assert.equal(call.skill_name, "model-usage");
78
+ assert.equal(call.invoke_tool, "skill");
79
+ assert.equal(call.status, "error");
80
+ assert.equal(call.error_message, "skill failed");
81
+ assert.equal(call.duration_ms, 9);
82
+ });
83
+
84
+ it("after 未到达:flushAllPending 补记 unknown", async () => {
85
+ const { hooks, captured } = setup();
86
+ hooks.onBeforeToolCall(
87
+ { toolName: "exec", params: { command: "python /e/skills/model-usage/scripts/model_usage.py --mode current" }, toolCallId: "t9" },
88
+ { sessionId: "s1" }
89
+ );
90
+ await tick();
91
+ assert.equal(captured.length, 0);
92
+ await hooks.flushAllPending();
93
+ await tick();
94
+ assert.equal(captured.length, 1);
95
+ assert.equal(captured[0].status, "unknown");
96
+ assert.equal(captured[0].function_id, "usage_current");
97
+ });
98
+
99
+ it("session_end 补记该 session 残留 pending 并清激活", async () => {
100
+ const { hooks, captured, activeSkills } = setup();
101
+ hooks.onBeforeToolCall({ toolName: "read", params: { path: "/e/skills/model-usage/SKILL.md" } }, { sessionId: "s1" });
102
+ hooks.onBeforeToolCall(
103
+ { toolName: "exec", params: { command: "python /e/skills/model-usage/scripts/model_usage.py --mode current" }, toolCallId: "t10" },
104
+ { sessionId: "s1" }
105
+ );
106
+ await tick();
107
+ captured.length = 0;
108
+ hooks.onSessionEnd({ sessionId: "s1" });
109
+ await tick();
110
+ assert.equal(captured.filter((e) => e.status === "unknown").length, 1);
111
+ assert.deepEqual([...activeSkills.getActive("s1")], []);
112
+ });
113
+
114
+ it("exec 命中 → 暂存,after 成功 → function_call(success)", async () => {
115
+ const { hooks, captured } = setup();
116
+ hooks.onBeforeToolCall(
117
+ { toolName: "exec", params: { command: "python /e/skills/model-usage/scripts/model_usage.py --mode current" }, toolCallId: "t1" },
118
+ { sessionId: "s1" }
119
+ );
120
+ await tick();
121
+ assert.equal(captured.length, 0); // 等 after
122
+
123
+ hooks.onAfterToolCall({ toolCallId: "t1", durationMs: 42 });
124
+ await tick();
125
+ assert.equal(captured.length, 1);
126
+ const ev = captured[0];
127
+ assert.equal(ev.event_type, "function_call");
128
+ assert.equal(ev.function_id, "usage_current");
129
+ assert.equal(ev.match_type, "script");
130
+ assert.equal(ev.status, "success");
131
+ assert.equal(ev.duration_ms, 42);
132
+ assert.equal(ev.args?.mode, "current");
133
+ });
134
+
135
+ it("after 带 error → function_call(error)", async () => {
136
+ const { hooks, captured } = setup();
137
+ hooks.onBeforeToolCall(
138
+ { toolName: "exec", params: { command: "python /e/skills/model-usage/scripts/model_usage.py --mode current" }, toolCallId: "t2" },
139
+ { sessionId: "s1" }
140
+ );
141
+ await tick();
142
+ hooks.onAfterToolCall({ toolCallId: "t2", error: "boom" });
143
+ await tick();
144
+ assert.equal(captured[0].status, "error");
145
+ assert.equal(captured[0].error_message, "boom");
146
+ });
147
+
148
+ it("after 的结构化错误与失败状态也会归为 error", async () => {
149
+ const { hooks, captured } = setup();
150
+ hooks.onBeforeToolCall(
151
+ { toolName: "exec", params: { command: "python /e/skills/model-usage/scripts/model_usage.py --mode current" }, toolCallId: "t4" },
152
+ { sessionId: "s1" }
153
+ );
154
+ await tick();
155
+ hooks.onAfterToolCall({ toolCallId: "t4", status: "failed", message: "process failed", duration_ms: 7 });
156
+ await tick();
157
+ assert.equal(captured[0].status, "error");
158
+ assert.equal(captured[0].error_message, "process failed");
159
+ assert.equal(captured[0].duration_ms, 7);
160
+ });
161
+
162
+ it("after 的嵌套 result 失败信号会提取 stderr", async () => {
163
+ const { hooks, captured } = setup();
164
+ hooks.onBeforeToolCall(
165
+ { toolName: "exec", params: { command: "python /e/skills/model-usage/scripts/model_usage.py --mode current" }, toolCallId: "t6" },
166
+ { sessionId: "s1" }
167
+ );
168
+ await tick();
169
+ hooks.onAfterToolCall({ toolCallId: "t6", result: { ok: false, stderr: "stderr failed" } });
170
+ await tick();
171
+ assert.equal(captured[0].status, "error");
172
+ assert.equal(captured[0].error_message, "stderr failed");
173
+ });
174
+
175
+ it("未匹配 exec 但会话中只有一个激活 skill → 等 after 补记状态和错误", async () => {
176
+ const { hooks, captured } = setup();
177
+ hooks.onBeforeToolCall({ toolName: "read", params: { path: "/e/skills/model-usage/SKILL.md" } }, { sessionId: "s1" });
178
+ await tick();
179
+ captured.length = 0;
180
+
181
+ hooks.onBeforeToolCall(
182
+ { toolName: "exec", params: { command: "custom command --bad" }, toolCallId: "t5" },
183
+ { sessionId: "s1" }
184
+ );
185
+ await tick();
186
+ assert.equal(captured.length, 0);
187
+
188
+ hooks.onAfterToolCall({ toolCallId: "t5", error: { message: "custom failed" }, durationMs: 11 });
189
+ await tick();
190
+ assert.equal(captured.length, 1);
191
+ assert.equal(captured[0].event_type, "function_call");
192
+ assert.equal(captured[0].skill_name, "model-usage");
193
+ assert.equal(captured[0].invoke_tool, "exec");
194
+ assert.equal(captured[0].command, "custom command --bad");
195
+ assert.equal(captured[0].status, "error");
196
+ assert.equal(captured[0].error_message, "custom failed");
197
+ assert.equal(captured[0].duration_ms, 11);
198
+ });
199
+
200
+ it("未命中且默认配置 不记录", async () => {
201
+ const { hooks, captured } = setup();
202
+ hooks.onBeforeToolCall({ toolName: "exec", params: { command: "ls -la" }, toolCallId: "t3" }, { sessionId: "s1" });
203
+ hooks.onAfterToolCall({ toolCallId: "t3" });
204
+ await tick();
205
+ assert.equal(captured.length, 0);
206
+ });
207
+ });
208
+
209
+ describe("插件模块图可加载", () => {
210
+ it("import index.ts register 注册全部生命周期 hook", async () => {
211
+ const mod = await import("./index.ts");
212
+ const registered: string[] = [];
213
+ mod.default.register({ on: (name: string) => registered.push(name) });
214
+ assert.deepEqual(
215
+ registered.sort(),
216
+ [
217
+ "after_tool_call",
218
+ "before_install",
219
+ "before_prompt_build",
220
+ "before_tool_call",
221
+ "gateway_start",
222
+ "gateway_stop",
223
+ "message_received",
224
+ "session_end",
225
+ ]
226
+ );
227
+ });
228
+ });