






















@@ -24,6 +24,41 @@ vi.mock("./tools/gateway.js", () => ({
2424const mockGetGlobalHookRunner = vi.mocked(getGlobalHookRunner);
2525const mockCallGatewayTool = vi.mocked(callGatewayTool);
262627+function requireRecord(value: unknown, label: string): Record<string, unknown> {
28+if (!value || typeof value !== "object" || Array.isArray(value)) {
29+throw new Error(`expected ${label}`);
30+}
31+return value as Record<string, unknown>;
32+}
33+34+function requireApprovalRequestCall(label: string): {
35+timeoutParams: Record<string, unknown>;
36+request: Record<string, unknown>;
37+options: Record<string, unknown>;
38+} {
39+const call = mockCallGatewayTool.mock.calls[0];
40+if (!call) {
41+throw new Error(`expected ${label}`);
42+}
43+expect(call[0]).toBe("plugin.approval.request");
44+return {
45+timeoutParams: requireRecord(call[1], `${label} timeout params`),
46+request: requireRecord(call[2], `${label} request`),
47+options: requireRecord(call[3], `${label} options`),
48+};
49+}
50+51+function requireBeforeToolCall(
52+mock: ReturnType<typeof vi.fn<HookRunner["runBeforeToolCall"]>>,
53+label: string,
54+): Parameters<HookRunner["runBeforeToolCall"]> {
55+const call = mock.mock.calls[0];
56+if (!call) {
57+throw new Error(`expected ${label}`);
58+}
59+return call;
60+}
61+2762describe("runBeforeToolCallHook — embedded mode approvals", () => {
2863let hookRunner: Pick<HookRunner, "hasHooks" | "runBeforeToolCall">;
2964let runBeforeToolCallMock: ReturnType<typeof vi.fn<HookRunner["runBeforeToolCall"]>>;
@@ -75,9 +110,23 @@ describe("runBeforeToolCallHook — embedded mode approvals", () => {
75110});
76111expect(mockCallGatewayTool).toHaveBeenCalledWith(
77112"plugin.approval.request",
78-expect.any(Object),
79-expect.any(Object),
80-expect.any(Object),
113+{
114+timeoutMs: 130_000,
115+},
116+{
117+agentId: undefined,
118+allowedDecisions: undefined,
119+description: "Test approval request",
120+pluginId: "test-plugin",
121+sessionKey: undefined,
122+severity: "info",
123+timeoutMs: 120_000,
124+title: "Needs approval",
125+toolCallId: "call-1",
126+toolName: "exec",
127+twoPhase: true,
128+},
129+{ expectFinal: false },
81130);
82131expect(onResolution).toHaveBeenCalledTimes(1);
83132expect(onResolution).toHaveBeenCalledWith(PluginApprovalResolutions.CANCELLED);
@@ -133,12 +182,17 @@ describe("runBeforeToolCallHook — embedded mode approvals", () => {
133182});
134183135184expect(result.blocked).toBe(true);
136-expect(mockCallGatewayTool).toHaveBeenCalledWith(
137-"plugin.approval.request",
138-expect.any(Object),
139-expect.any(Object),
140-expect.any(Object),
141-);
185+const approvalCall = requireApprovalRequestCall("non-embedded approval request");
186+expect(approvalCall.timeoutParams.timeoutMs).toBe(15_000);
187+expect(approvalCall.request.pluginId).toBe("test-plugin");
188+expect(approvalCall.request.title).toBe("Needs approval");
189+expect(approvalCall.request.description).toBe("Test approval request");
190+expect(approvalCall.request.severity).toBe("info");
191+expect(approvalCall.request.toolName).toBe("exec");
192+expect(approvalCall.request.toolCallId).toBe("call-2");
193+expect(approvalCall.request.timeoutMs).toBe(5_000);
194+expect(approvalCall.request.twoPhase).toBe(true);
195+expect(approvalCall.options.expectFinal).toBe(false);
142196});
143197144198it("preserves hook params override after an approval allow decision", async () => {
@@ -210,15 +264,17 @@ describe("runBeforeToolCallHook — embedded mode approvals", () => {
210264});
211265212266expect(result).toEqual({ blocked: false, params: { command: "deploy" } });
213-expect(mockCallGatewayTool).toHaveBeenCalledWith(
214-"plugin.approval.request",
215-expect.any(Object),
216-expect.objectContaining({
217-pluginId: "trusted-policy",
218-title: "Policy approval",
219-}),
220-{ expectFinal: false },
221-);
267+const approvalCall = requireApprovalRequestCall("trusted policy approval request");
268+expect(approvalCall.timeoutParams.timeoutMs).toBe(130_000);
269+expect(approvalCall.request.pluginId).toBe("trusted-policy");
270+expect(approvalCall.request.title).toBe("Policy approval");
271+expect(approvalCall.request.description).toBe("Policy requested approval");
272+expect(approvalCall.request.toolName).toBe("exec");
273+expect(approvalCall.request.toolCallId).toBe("call-policy");
274+expect(approvalCall.request.agentId).toBe("main");
275+expect(approvalCall.request.sessionKey).toBe("main");
276+expect(approvalCall.request.twoPhase).toBe(true);
277+expect(approvalCall.options.expectFinal).toBe(false);
222278expect(runBeforeToolCallMock).not.toHaveBeenCalled();
223279});
224280@@ -247,12 +303,14 @@ describe("runBeforeToolCallHook — embedded mode approvals", () => {
247303});
248304249305expect(result).toEqual({ blocked: false, params: { command: "patched" } });
250-expect(runBeforeToolCallMock).toHaveBeenCalledWith(
251-expect.objectContaining({
252-params: { command: "patched" },
253-}),
254-expect.any(Object),
306+const [hookParams, hookContext] = requireBeforeToolCall(
307+runBeforeToolCallMock,
308+"before_tool_call invocation",
255309);
310+expect(hookParams.params).toEqual({ command: "patched" });
311+expect(hookParams.toolName).toBe("exec");
312+expect(hookParams.toolCallId).toBe("call-policy-params");
313+expect(typeof hookContext).toBe("object");
256314});
257315258316it("keeps original params after an approval allow decision without overrides", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。