
























@@ -393,6 +393,12 @@ const expectOkInvokeResponse = async (res: Response) => {
393393return body as { ok: boolean; result?: Record<string, unknown> };
394394};
395395396+const firstHookCallArg = () => {
397+const call = hookMocks.runBeforeToolCallHook.mock.calls[0];
398+expect(call).toBeDefined();
399+return call?.[0] as RunBeforeToolCallHookArgs;
400+};
401+396402const invokeToolsRpc = async (params: Record<string, unknown>, scopes = ["operator.write"]) => {
397403const respond = vi.fn();
398404await toolsInvokeHandlers["tools.invoke"]({
@@ -442,17 +448,14 @@ describe("POST /tools/invoke", () => {
442448expect(body).toHaveProperty("result");
443449expect(lastCreateOpenClawToolsContext?.allowMediaInvokeCommands).toBe(true);
444450expect(lastCreateOpenClawToolsContext?.disablePluginTools).toBe(true);
445-expect(hookMocks.runBeforeToolCallHook).toHaveBeenCalledWith(
446-expect.objectContaining({
447-toolName: "agents_list",
448-ctx: expect.objectContaining({
449-agentId: "main",
450-config: cfg,
451-sessionKey: "agent:main:main",
452-loopDetection: { warnAt: 3 },
453-}),
454-}),
455-);
451+const hookArg = firstHookCallArg();
452+expect(hookArg.toolName).toBe("agents_list");
453+const hookCtx = hookArg.ctx;
454+expect(hookCtx).toBeDefined();
455+expect(hookCtx?.agentId).toBe("main");
456+expect(hookCtx?.config).toBe(cfg);
457+expect(hookCtx?.sessionKey).toBe("agent:main:main");
458+expect(hookCtx?.loopDetection).toEqual({ warnAt: 3 });
456459});
457460458461it("opts direct gateway tool invocation into gateway subagent binding", async () => {
@@ -489,10 +492,9 @@ describe("POST /tools/invoke", () => {
489492});
490493491494const body = await expectOkInvokeResponse(res);
492-expect(body.result).toMatchObject({ ok: true, permissionFlow: true });
493-expect(lastCreateOpenClawToolsContext?.pluginToolAllowlist).toEqual(
494-expect.arrayContaining(["plugin_doctor"]),
495-);
495+expect(body.result?.ok).toBe(true);
496+expect(body.result?.permissionFlow).toBe(true);
497+expect(lastCreateOpenClawToolsContext?.pluginToolAllowlist).toContain("plugin_doctor");
496498});
497499498500it("uses tools.alsoAllow for optional plugin discovery without loading every plugin tool", async () => {
@@ -508,10 +510,9 @@ describe("POST /tools/invoke", () => {
508510});
509511510512const body = await expectOkInvokeResponse(res);
511-expect(body.result).toMatchObject({ ok: true, permissionFlow: true });
512-expect(lastCreateOpenClawToolsContext?.pluginToolAllowlist).toEqual(
513-expect.arrayContaining(["plugin_doctor"]),
514-);
513+expect(body.result?.ok).toBe(true);
514+expect(body.result?.permissionFlow).toBe(true);
515+expect(lastCreateOpenClawToolsContext?.pluginToolAllowlist).toContain("plugin_doctor");
515516expect(lastCreateOpenClawToolsContext?.pluginToolAllowlist).not.toContain("*");
516517});
517518@@ -529,13 +530,10 @@ describe("POST /tools/invoke", () => {
529530});
530531531532expect(res.status).toBe(403);
532-await expect(res.json()).resolves.toMatchObject({
533-ok: false,
534-error: {
535-type: "tool_call_blocked",
536-message: "blocked by test hook",
537-},
538-});
533+const body = await res.json();
534+expect(body.ok).toBe(false);
535+expect(body.error?.type).toBe("tool_call_blocked");
536+expect(body.error?.message).toBe("blocked by test hook");
539537});
540538541539it("accepts shared-secret bearer auth on the HTTP tools surface", async () => {
@@ -587,7 +585,7 @@ describe("POST /tools/invoke", () => {
587585});
588586589587const body = await expectOkInvokeResponse(res);
590-expect(body.result).toMatchObject({ ok: true });
588+expect(body.result?.ok).toBe(true);
591589});
592590593591it("supports tools.alsoAllow in profile and implicit modes", async () => {
@@ -866,13 +864,10 @@ describe("POST /tools/invoke", () => {
866864});
867865868866expect(res.status).toBe(403);
869-await expect(res.json()).resolves.toMatchObject({
870-ok: false,
871-error: {
872-type: "forbidden",
873-message: "missing scope: operator.write",
874-},
875-});
867+const body = await res.json();
868+expect(body.ok).toBe(false);
869+expect(body.error?.type).toBe("forbidden");
870+expect(body.error?.message).toBe("missing scope: operator.write");
876871});
877872878873it("treats shared-secret bearer auth as full operator access on /tools/invoke", async () => {
@@ -982,25 +977,20 @@ describe("tools.invoke Gateway RPC", () => {
982977});
983978984979expect(call?.[0]).toBe(true);
985-expect(call?.[1]).toMatchObject({
986-ok: true,
987-toolName: "agents_list",
988-output: { ok: true, result: [] },
989-source: "core",
990-});
980+expect(call?.[1]?.ok).toBe(true);
981+expect(call?.[1]?.toolName).toBe("agents_list");
982+expect(call?.[1]?.output).toEqual({ ok: true, result: [] });
983+expect((call?.[1] as { source?: unknown } | undefined)?.source).toBe("core");
991984expect(lastCreateOpenClawToolsContext?.allowGatewaySubagentBinding).toBe(true);
992-expect(hookMocks.runBeforeToolCallHook).toHaveBeenCalledWith(
993-expect.objectContaining({
994-approvalMode: "report",
995-toolName: "agents_list",
996-toolCallId: "rpc-rpc-tool-test",
997-ctx: expect.objectContaining({
998-agentId: "main",
999-config: cfg,
1000-sessionKey: "agent:main:main",
1001-}),
1002-}),
1003-);
985+const hookArg = firstHookCallArg();
986+expect(hookArg.approvalMode).toBe("report");
987+expect(hookArg.toolName).toBe("agents_list");
988+expect(hookArg.toolCallId).toBe("rpc-rpc-tool-test");
989+const hookCtx = hookArg.ctx;
990+expect(hookCtx).toBeDefined();
991+expect(hookCtx?.agentId).toBe("main");
992+expect(hookCtx?.config).toBe(cfg);
993+expect(hookCtx?.sessionKey).toBe("agent:main:main");
1004994});
10059951006996it("returns typed approval-needed refusal when the policy hook blocks", async () => {
@@ -1020,15 +1010,12 @@ describe("tools.invoke Gateway RPC", () => {
10201010});
1021101110221012expect(call?.[0]).toBe(true);
1023-expect(call?.[1]).toMatchObject({
1024-ok: false,
1025-toolName: "tools_invoke_test",
1026-requiresApproval: true,
1027-error: {
1028-code: "requires_approval",
1029-message: "Plugin approval required",
1030-},
1031-});
1013+expect(call?.[1]?.ok).toBe(false);
1014+expect(call?.[1]?.toolName).toBe("tools_invoke_test");
1015+expect((call?.[1] as { requiresApproval?: unknown } | undefined)?.requiresApproval).toBe(true);
1016+const error = call?.[1]?.error as { code?: string; message?: string } | undefined;
1017+expect(error?.code).toBe("requires_approval");
1018+expect(error?.message).toBe("Plugin approval required");
10321019});
1033102010341021it("rejects mismatched session and agent scope", async () => {
@@ -1048,23 +1035,19 @@ describe("tools.invoke Gateway RPC", () => {
10481035});
1049103610501037expect(call?.[0]).toBe(true);
1051-expect(call?.[1]).toMatchObject({
1052-ok: false,
1053-toolName: "agents_list",
1054-error: {
1055-code: "validation_error",
1056-message: 'agent id "other" does not match session agent "main"',
1057-},
1058-});
1038+expect(call?.[1]?.ok).toBe(false);
1039+expect(call?.[1]?.toolName).toBe("agents_list");
1040+const error = call?.[1]?.error as { code?: string; message?: string } | undefined;
1041+expect(error?.code).toBe("validation_error");
1042+expect(error?.message).toBe('agent id "other" does not match session agent "main"');
10591043});
1060104410611045it("rejects malformed params at the RPC boundary", async () => {
10621046const call = await invokeToolsRpc({ name: "" });
1063104710641048expect(call?.[0]).toBe(false);
1065-expect(call?.[2]).toMatchObject({
1066-code: "INVALID_REQUEST",
1067-message: expect.stringContaining("invalid tools.invoke params"),
1068-});
1049+const error = call?.[2] as { code?: string; message?: string } | undefined;
1050+expect(error?.code).toBe("INVALID_REQUEST");
1051+expect(error?.message).toContain("invalid tools.invoke params");
10691052});
10701053});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。