diff --git a/src/agents/openclaw-tools.ts b/src/agents/openclaw-tools.ts index eed12b72d..83590d3bf 100644 --- a/src/agents/openclaw-tools.ts +++ b/src/agents/openclaw-tools.ts @@ -1,12 +1,12 @@ import type { OpenClawConfig } from "../config/config.js"; -import type { GatewayMessageChannel } from "../utils/message-channel.js"; -import type { SandboxFsBridge } from "./sandbox/fs-bridge.js"; -import type { AnyAgentTool } from "./tools/common.js"; import { resolvePluginTools } from "../plugins/tools.js"; +import type { GatewayMessageChannel } from "../utils/message-channel.js"; import { resolveSessionAgentId } from "./agent-scope.js"; +import type { SandboxFsBridge } from "./sandbox/fs-bridge.js"; import { createAgentsListTool } from "./tools/agents-list-tool.js"; import { createBrowserTool } from "./tools/browser-tool.js"; import { createCanvasTool } from "./tools/canvas-tool.js"; +import type { AnyAgentTool } from "./tools/common.js"; import { createCronTool } from "./tools/cron-tool.js"; import { createGatewayTool } from "./tools/gateway-tool.js"; import { createImageTool } from "./tools/image-tool.js"; diff --git a/src/agents/session-transcript-repair.e2e.test.ts b/src/agents/session-transcript-repair.e2e.test.ts index f03d9f6e0..b87eed2ec 100644 --- a/src/agents/session-transcript-repair.e2e.test.ts +++ b/src/agents/session-transcript-repair.e2e.test.ts @@ -24,7 +24,7 @@ describe("sanitizeToolUseResultPairing", () => { content: [{ type: "text", text: "ok" }], isError: false, }, - ] satisfies AgentMessage[]; + ] as unknown as AgentMessage[]; const out = sanitizeToolUseResultPairing(input); expect(out[0]?.role).toBe("assistant"); @@ -56,7 +56,7 @@ describe("sanitizeToolUseResultPairing", () => { isError: false, }, { role: "user", content: "ok" }, - ] satisfies AgentMessage[]; + ] as unknown as AgentMessage[]; const out = sanitizeToolUseResultPairing(input); expect(out.filter((m) => m.role === "toolResult")).toHaveLength(1); @@ -83,7 +83,7 @@ describe("sanitizeToolUseResultPairing", () => { content: [{ type: "text", text: "second (duplicate)" }], isError: false, }, - ] satisfies AgentMessage[]; + ] as unknown as AgentMessage[]; const out = sanitizeToolUseResultPairing(input); const results = out.filter((m) => m.role === "toolResult") as Array<{ @@ -107,7 +107,7 @@ describe("sanitizeToolUseResultPairing", () => { role: "assistant", content: [{ type: "text", text: "ok" }], }, - ] satisfies AgentMessage[]; + ] as unknown as AgentMessage[]; const out = sanitizeToolUseResultPairing(input); expect(out.some((m) => m.role === "toolResult")).toBe(false); @@ -125,7 +125,7 @@ describe("sanitizeToolUseResultPairing", () => { stopReason: "error", }, { role: "user", content: "something went wrong" }, - ] as AgentMessage[]; + ] as unknown as AgentMessage[]; const result = repairToolUseResultPairing(input); @@ -147,7 +147,7 @@ describe("sanitizeToolUseResultPairing", () => { stopReason: "aborted", }, { role: "user", content: "retrying after abort" }, - ] as AgentMessage[]; + ] as unknown as AgentMessage[]; const result = repairToolUseResultPairing(input); @@ -168,7 +168,7 @@ describe("sanitizeToolUseResultPairing", () => { stopReason: "toolUse", }, { role: "user", content: "user message" }, - ] as AgentMessage[]; + ] as unknown as AgentMessage[]; const result = repairToolUseResultPairing(input); @@ -195,7 +195,7 @@ describe("sanitizeToolUseResultPairing", () => { isError: false, }, { role: "user", content: "retrying" }, - ] as AgentMessage[]; + ] as unknown as AgentMessage[]; const result = repairToolUseResultPairing(input); @@ -211,20 +211,20 @@ describe("sanitizeToolUseResultPairing", () => { describe("sanitizeToolCallInputs", () => { it("drops tool calls missing input or arguments", () => { - const input: AgentMessage[] = [ + const input = [ { role: "assistant", content: [{ type: "toolCall", id: "call_1", name: "read" }], }, { role: "user", content: "hello" }, - ]; + ] as unknown as AgentMessage[]; const out = sanitizeToolCallInputs(input); expect(out.map((m) => m.role)).toEqual(["user"]); }); it("drops tool calls with missing or blank name/id", () => { - const input: AgentMessage[] = [ + const input = [ { role: "assistant", content: [ @@ -234,7 +234,7 @@ describe("sanitizeToolCallInputs", () => { { type: "functionCall", id: "", name: "exec", arguments: {} }, ], }, - ]; + ] as unknown as AgentMessage[]; const out = sanitizeToolCallInputs(input); const assistant = out[0] as Extract; @@ -250,7 +250,7 @@ describe("sanitizeToolCallInputs", () => { }); it("keeps valid tool calls and preserves text blocks", () => { - const input: AgentMessage[] = [ + const input = [ { role: "assistant", content: [ @@ -259,7 +259,7 @@ describe("sanitizeToolCallInputs", () => { { type: "toolCall", id: "call_drop", name: "read" }, ], }, - ]; + ] as unknown as AgentMessage[]; const out = sanitizeToolCallInputs(input); const assistant = out[0] as Extract; diff --git a/src/agents/subagent-announce.format.e2e.test.ts b/src/agents/subagent-announce.format.e2e.test.ts index 39592e7e7..e77c7c81d 100644 --- a/src/agents/subagent-announce.format.e2e.test.ts +++ b/src/agents/subagent-announce.format.e2e.test.ts @@ -1,9 +1,17 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js"; -const agentSpy = vi.fn(async () => ({ runId: "run-main", status: "ok" })); -const sessionsDeleteSpy = vi.fn(); -const readLatestAssistantReplyMock = vi.fn(async () => "raw subagent reply"); +type AgentCallRequest = { method?: string; params?: Record }; +type RequesterResolution = { + requesterSessionKey: string; + requesterOrigin?: Record; +} | null; + +const agentSpy = vi.fn(async (_req: AgentCallRequest) => ({ runId: "run-main", status: "ok" })); +const sessionsDeleteSpy = vi.fn((_req: AgentCallRequest) => undefined); +const readLatestAssistantReplyMock = vi.fn( + async (_sessionKey?: string): Promise => "raw subagent reply", +); const embeddedRunMock = { isEmbeddedPiRunActive: vi.fn(() => false), isEmbeddedPiRunStreaming: vi.fn(() => false), @@ -12,8 +20,8 @@ const embeddedRunMock = { }; const subagentRegistryMock = { isSubagentSessionRunActive: vi.fn(() => true), - countActiveDescendantRuns: vi.fn(() => 0), - resolveRequesterForChildSession: vi.fn(() => null), + countActiveDescendantRuns: vi.fn((_sessionKey: string) => 0), + resolveRequesterForChildSession: vi.fn((_sessionKey: string): RequesterResolution => null), }; let sessionStore: Record> = {}; let configOverride: ReturnType<(typeof import("../config/config.js"))["loadConfig"]> = { diff --git a/src/agents/tool-call-id.e2e.test.ts b/src/agents/tool-call-id.e2e.test.ts index d8bbb9a6f..30ae90fe1 100644 --- a/src/agents/tool-call-id.e2e.test.ts +++ b/src/agents/tool-call-id.e2e.test.ts @@ -26,7 +26,7 @@ const buildDuplicateIdCollisionInput = () => toolName: "read", content: [{ type: "text", text: "two" }], }, - ] satisfies AgentMessage[]; + ] as unknown as AgentMessage[]; function expectCollisionIdsRemainDistinct( out: AgentMessage[], @@ -62,7 +62,7 @@ describe("sanitizeToolCallIdsForCloudCodeAssist", () => { toolName: "read", content: [{ type: "text", text: "ok" }], }, - ] satisfies AgentMessage[]; + ] as unknown as AgentMessage[]; const out = sanitizeToolCallIdsForCloudCodeAssist(input); expect(out).toBe(input); @@ -80,7 +80,7 @@ describe("sanitizeToolCallIdsForCloudCodeAssist", () => { toolName: "read", content: [{ type: "text", text: "ok" }], }, - ] satisfies AgentMessage[]; + ] as unknown as AgentMessage[]; const out = sanitizeToolCallIdsForCloudCodeAssist(input); expect(out).not.toBe(input); @@ -126,7 +126,7 @@ describe("sanitizeToolCallIdsForCloudCodeAssist", () => { toolName: "read", content: [{ type: "text", text: "two" }], }, - ] satisfies AgentMessage[]; + ] as unknown as AgentMessage[]; const out = sanitizeToolCallIdsForCloudCodeAssist(input); const assistant = out[0] as Extract; @@ -168,7 +168,7 @@ describe("sanitizeToolCallIdsForCloudCodeAssist", () => { toolName: "login", content: [{ type: "text", text: "ok" }], }, - ] satisfies AgentMessage[]; + ] as unknown as AgentMessage[]; const out = sanitizeToolCallIdsForCloudCodeAssist(input, "strict"); expect(out).not.toBe(input); @@ -217,7 +217,7 @@ describe("sanitizeToolCallIdsForCloudCodeAssist", () => { toolName: "read", content: [{ type: "text", text: "two" }], }, - ] satisfies AgentMessage[]; + ] as unknown as AgentMessage[]; const out = sanitizeToolCallIdsForCloudCodeAssist(input, "strict9"); expect(out).not.toBe(input); diff --git a/src/agents/tools/browser-tool.e2e.test.ts b/src/agents/tools/browser-tool.e2e.test.ts index bd9748148..b47da5694 100644 --- a/src/agents/tools/browser-tool.e2e.test.ts +++ b/src/agents/tools/browser-tool.e2e.test.ts @@ -1,27 +1,31 @@ import { afterEach, describe, expect, it, vi } from "vitest"; const browserClientMocks = vi.hoisted(() => ({ - browserCloseTab: vi.fn(async () => ({})), - browserFocusTab: vi.fn(async () => ({})), - browserOpenTab: vi.fn(async () => ({})), - browserProfiles: vi.fn(async () => []), - browserSnapshot: vi.fn(async () => ({ - ok: true, - format: "ai", - targetId: "t1", - url: "https://example.com", - snapshot: "ok", - })), - browserStart: vi.fn(async () => ({})), - browserStatus: vi.fn(async () => ({ + browserCloseTab: vi.fn(async (..._args: unknown[]) => ({})), + browserFocusTab: vi.fn(async (..._args: unknown[]) => ({})), + browserOpenTab: vi.fn(async (..._args: unknown[]) => ({})), + browserProfiles: vi.fn( + async (..._args: unknown[]): Promise>> => [], + ), + browserSnapshot: vi.fn( + async (..._args: unknown[]): Promise> => ({ + ok: true, + format: "ai", + targetId: "t1", + url: "https://example.com", + snapshot: "ok", + }), + ), + browserStart: vi.fn(async (..._args: unknown[]) => ({})), + browserStatus: vi.fn(async (..._args: unknown[]) => ({ ok: true, running: true, pid: 1, cdpPort: 18792, cdpUrl: "http://127.0.0.1:18792", })), - browserStop: vi.fn(async () => ({})), - browserTabs: vi.fn(async () => []), + browserStop: vi.fn(async (..._args: unknown[]) => ({})), + browserTabs: vi.fn(async (..._args: unknown[]): Promise>> => []), })); vi.mock("../../browser/client.js", () => browserClientMocks); @@ -55,7 +59,7 @@ const browserConfigMocks = vi.hoisted(() => ({ vi.mock("../../browser/config.js", () => browserConfigMocks); const nodesUtilsMocks = vi.hoisted(() => ({ - listNodes: vi.fn(async () => []), + listNodes: vi.fn(async (..._args: unknown[]): Promise>> => []), })); vi.mock("./nodes-utils.js", async () => { const actual = await vi.importActual("./nodes-utils.js"); @@ -101,7 +105,7 @@ describe("browser tool snapshot maxChars", () => { it("applies the default ai snapshot limit", async () => { const tool = createBrowserTool(); - await tool.execute?.(null, { action: "snapshot", snapshotFormat: "ai" }); + await tool.execute?.("call-1", { action: "snapshot", snapshotFormat: "ai" }); expect(browserClientMocks.browserSnapshot).toHaveBeenCalledWith( undefined, @@ -115,7 +119,7 @@ describe("browser tool snapshot maxChars", () => { it("respects an explicit maxChars override", async () => { const tool = createBrowserTool(); const override = 2_000; - await tool.execute?.(null, { + await tool.execute?.("call-1", { action: "snapshot", snapshotFormat: "ai", maxChars: override, @@ -131,27 +135,29 @@ describe("browser tool snapshot maxChars", () => { it("skips the default when maxChars is explicitly zero", async () => { const tool = createBrowserTool(); - await tool.execute?.(null, { + await tool.execute?.("call-1", { action: "snapshot", snapshotFormat: "ai", maxChars: 0, }); expect(browserClientMocks.browserSnapshot).toHaveBeenCalled(); - const [, opts] = browserClientMocks.browserSnapshot.mock.calls.at(-1) ?? []; + const opts = browserClientMocks.browserSnapshot.mock.calls.at(-1)?.[1] as + | { maxChars?: number } + | undefined; expect(Object.hasOwn(opts ?? {}, "maxChars")).toBe(false); }); it("lists profiles", async () => { const tool = createBrowserTool(); - await tool.execute?.(null, { action: "profiles" }); + await tool.execute?.("call-1", { action: "profiles" }); expect(browserClientMocks.browserProfiles).toHaveBeenCalledWith(undefined); }); it("passes refs mode through to browser snapshot", async () => { const tool = createBrowserTool(); - await tool.execute?.(null, { action: "snapshot", snapshotFormat: "ai", refs: "aria" }); + await tool.execute?.("call-1", { action: "snapshot", snapshotFormat: "ai", refs: "aria" }); expect(browserClientMocks.browserSnapshot).toHaveBeenCalledWith( undefined, @@ -167,7 +173,7 @@ describe("browser tool snapshot maxChars", () => { browser: { snapshotDefaults: { mode: "efficient" } }, }); const tool = createBrowserTool(); - await tool.execute?.(null, { action: "snapshot", snapshotFormat: "ai" }); + await tool.execute?.("call-1", { action: "snapshot", snapshotFormat: "ai" }); expect(browserClientMocks.browserSnapshot).toHaveBeenCalledWith( undefined, @@ -182,16 +188,18 @@ describe("browser tool snapshot maxChars", () => { browser: { snapshotDefaults: { mode: "efficient" } }, }); const tool = createBrowserTool(); - await tool.execute?.(null, { action: "snapshot", snapshotFormat: "aria" }); + await tool.execute?.("call-1", { action: "snapshot", snapshotFormat: "aria" }); expect(browserClientMocks.browserSnapshot).toHaveBeenCalled(); - const [, opts] = browserClientMocks.browserSnapshot.mock.calls.at(-1) ?? []; + const opts = browserClientMocks.browserSnapshot.mock.calls.at(-1)?.[1] as + | { mode?: string } + | undefined; expect(opts?.mode).toBeUndefined(); }); it("defaults to host when using profile=chrome (even in sandboxed sessions)", async () => { const tool = createBrowserTool({ sandboxBridgeUrl: "http://127.0.0.1:9999" }); - await tool.execute?.(null, { action: "snapshot", profile: "chrome", snapshotFormat: "ai" }); + await tool.execute?.("call-1", { action: "snapshot", profile: "chrome", snapshotFormat: "ai" }); expect(browserClientMocks.browserSnapshot).toHaveBeenCalledWith( undefined, @@ -212,7 +220,7 @@ describe("browser tool snapshot maxChars", () => { }, ]); const tool = createBrowserTool(); - await tool.execute?.(null, { action: "status", target: "node" }); + await tool.execute?.("call-1", { action: "status", target: "node" }); expect(gatewayMocks.callGatewayTool).toHaveBeenCalledWith( "node.invoke", @@ -236,7 +244,7 @@ describe("browser tool snapshot maxChars", () => { }, ]); const tool = createBrowserTool({ sandboxBridgeUrl: "http://127.0.0.1:9999" }); - await tool.execute?.(null, { action: "status" }); + await tool.execute?.("call-1", { action: "status" }); expect(browserClientMocks.browserStatus).toHaveBeenCalledWith( "http://127.0.0.1:9999", @@ -256,7 +264,7 @@ describe("browser tool snapshot maxChars", () => { }, ]); const tool = createBrowserTool(); - await tool.execute?.(null, { action: "status", profile: "chrome" }); + await tool.execute?.("call-1", { action: "status", profile: "chrome" }); expect(browserClientMocks.browserStatus).toHaveBeenCalledWith( undefined, @@ -292,7 +300,7 @@ describe("browser tool snapshot labels", () => { imagePath: "/tmp/snap.png", }); - const result = await tool.execute?.(null, { + const result = await tool.execute?.("call-1", { action: "snapshot", snapshotFormat: "ai", labels: true, @@ -335,7 +343,7 @@ describe("browser tool external content wrapping", () => { }); const tool = createBrowserTool(); - const result = await tool.execute?.(null, { action: "snapshot", snapshotFormat: "aria" }); + const result = await tool.execute?.("call-1", { action: "snapshot", snapshotFormat: "aria" }); expect(result?.content?.[0]).toMatchObject({ type: "text", text: expect.stringContaining("<<>>"), @@ -369,7 +377,7 @@ describe("browser tool external content wrapping", () => { ]); const tool = createBrowserTool(); - const result = await tool.execute?.(null, { action: "tabs" }); + const result = await tool.execute?.("call-1", { action: "tabs" }); expect(result?.content?.[0]).toMatchObject({ type: "text", text: expect.stringContaining("<<>>"), @@ -402,7 +410,7 @@ describe("browser tool external content wrapping", () => { }); const tool = createBrowserTool(); - const result = await tool.execute?.(null, { action: "console" }); + const result = await tool.execute?.("call-1", { action: "console" }); expect(result?.content?.[0]).toMatchObject({ type: "text", text: expect.stringContaining("<<>>"), diff --git a/src/agents/tools/discord-actions.e2e.test.ts b/src/agents/tools/discord-actions.e2e.test.ts index b7ed18685..e266135cb 100644 --- a/src/agents/tools/discord-actions.e2e.test.ts +++ b/src/agents/tools/discord-actions.e2e.test.ts @@ -43,35 +43,35 @@ const kickMemberDiscord = vi.fn(async () => ({})); const banMemberDiscord = vi.fn(async () => ({})); vi.mock("../../discord/send.js", () => ({ - banMemberDiscord: (...args: unknown[]) => banMemberDiscord(...args), - createChannelDiscord: (...args: unknown[]) => createChannelDiscord(...args), - createThreadDiscord: (...args: unknown[]) => createThreadDiscord(...args), - deleteChannelDiscord: (...args: unknown[]) => deleteChannelDiscord(...args), - deleteMessageDiscord: (...args: unknown[]) => deleteMessageDiscord(...args), - editChannelDiscord: (...args: unknown[]) => editChannelDiscord(...args), - editMessageDiscord: (...args: unknown[]) => editMessageDiscord(...args), - fetchMessageDiscord: (...args: unknown[]) => fetchMessageDiscord(...args), - fetchChannelPermissionsDiscord: (...args: unknown[]) => fetchChannelPermissionsDiscord(...args), - fetchReactionsDiscord: (...args: unknown[]) => fetchReactionsDiscord(...args), - kickMemberDiscord: (...args: unknown[]) => kickMemberDiscord(...args), - listGuildChannelsDiscord: (...args: unknown[]) => listGuildChannelsDiscord(...args), - listPinsDiscord: (...args: unknown[]) => listPinsDiscord(...args), - listThreadsDiscord: (...args: unknown[]) => listThreadsDiscord(...args), - moveChannelDiscord: (...args: unknown[]) => moveChannelDiscord(...args), - pinMessageDiscord: (...args: unknown[]) => pinMessageDiscord(...args), - reactMessageDiscord: (...args: unknown[]) => reactMessageDiscord(...args), - readMessagesDiscord: (...args: unknown[]) => readMessagesDiscord(...args), - removeChannelPermissionDiscord: (...args: unknown[]) => removeChannelPermissionDiscord(...args), - removeOwnReactionsDiscord: (...args: unknown[]) => removeOwnReactionsDiscord(...args), - removeReactionDiscord: (...args: unknown[]) => removeReactionDiscord(...args), - searchMessagesDiscord: (...args: unknown[]) => searchMessagesDiscord(...args), - sendMessageDiscord: (...args: unknown[]) => sendMessageDiscord(...args), - sendVoiceMessageDiscord: (...args: unknown[]) => sendVoiceMessageDiscord(...args), - sendPollDiscord: (...args: unknown[]) => sendPollDiscord(...args), - sendStickerDiscord: (...args: unknown[]) => sendStickerDiscord(...args), - setChannelPermissionDiscord: (...args: unknown[]) => setChannelPermissionDiscord(...args), - timeoutMemberDiscord: (...args: unknown[]) => timeoutMemberDiscord(...args), - unpinMessageDiscord: (...args: unknown[]) => unpinMessageDiscord(...args), + banMemberDiscord, + createChannelDiscord, + createThreadDiscord, + deleteChannelDiscord, + deleteMessageDiscord, + editChannelDiscord, + editMessageDiscord, + fetchMessageDiscord, + fetchChannelPermissionsDiscord, + fetchReactionsDiscord, + kickMemberDiscord, + listGuildChannelsDiscord, + listPinsDiscord, + listThreadsDiscord, + moveChannelDiscord, + pinMessageDiscord, + reactMessageDiscord, + readMessagesDiscord, + removeChannelPermissionDiscord, + removeOwnReactionsDiscord, + removeReactionDiscord, + searchMessagesDiscord, + sendMessageDiscord, + sendVoiceMessageDiscord, + sendPollDiscord, + sendStickerDiscord, + setChannelPermissionDiscord, + timeoutMemberDiscord, + unpinMessageDiscord, })); const enableAllActions = () => true; @@ -165,7 +165,9 @@ describe("handleDiscordMessagingAction", () => { }); it("adds normalized timestamps to readMessages payloads", async () => { - readMessagesDiscord.mockResolvedValueOnce([{ id: "1", timestamp: "2026-01-15T10:00:00.000Z" }]); + readMessagesDiscord.mockResolvedValueOnce([ + { id: "1", timestamp: "2026-01-15T10:00:00.000Z" }, + ] as never); const result = await handleDiscordMessagingAction( "readMessages", diff --git a/src/agents/tools/slack-actions.e2e.test.ts b/src/agents/tools/slack-actions.e2e.test.ts index a123365cc..7c3d6effb 100644 --- a/src/agents/tools/slack-actions.e2e.test.ts +++ b/src/agents/tools/slack-actions.e2e.test.ts @@ -2,34 +2,34 @@ import { describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../../config/config.js"; import { handleSlackAction } from "./slack-actions.js"; -const deleteSlackMessage = vi.fn(async () => ({})); -const editSlackMessage = vi.fn(async () => ({})); -const getSlackMemberInfo = vi.fn(async () => ({})); -const listSlackEmojis = vi.fn(async () => ({})); -const listSlackPins = vi.fn(async () => ({})); -const listSlackReactions = vi.fn(async () => ({})); -const pinSlackMessage = vi.fn(async () => ({})); -const reactSlackMessage = vi.fn(async () => ({})); -const readSlackMessages = vi.fn(async () => ({})); -const removeOwnSlackReactions = vi.fn(async () => ["thumbsup"]); -const removeSlackReaction = vi.fn(async () => ({})); -const sendSlackMessage = vi.fn(async () => ({})); -const unpinSlackMessage = vi.fn(async () => ({})); +const deleteSlackMessage = vi.fn(async (..._args: unknown[]) => ({})); +const editSlackMessage = vi.fn(async (..._args: unknown[]) => ({})); +const getSlackMemberInfo = vi.fn(async (..._args: unknown[]) => ({})); +const listSlackEmojis = vi.fn(async (..._args: unknown[]) => ({})); +const listSlackPins = vi.fn(async (..._args: unknown[]) => ({})); +const listSlackReactions = vi.fn(async (..._args: unknown[]) => ({})); +const pinSlackMessage = vi.fn(async (..._args: unknown[]) => ({})); +const reactSlackMessage = vi.fn(async (..._args: unknown[]) => ({})); +const readSlackMessages = vi.fn(async (..._args: unknown[]) => ({})); +const removeOwnSlackReactions = vi.fn(async (..._args: unknown[]) => ["thumbsup"]); +const removeSlackReaction = vi.fn(async (..._args: unknown[]) => ({})); +const sendSlackMessage = vi.fn(async (..._args: unknown[]) => ({})); +const unpinSlackMessage = vi.fn(async (..._args: unknown[]) => ({})); vi.mock("../../slack/actions.js", () => ({ - deleteSlackMessage: (...args: unknown[]) => deleteSlackMessage(...args), - editSlackMessage: (...args: unknown[]) => editSlackMessage(...args), - getSlackMemberInfo: (...args: unknown[]) => getSlackMemberInfo(...args), - listSlackEmojis: (...args: unknown[]) => listSlackEmojis(...args), - listSlackPins: (...args: unknown[]) => listSlackPins(...args), - listSlackReactions: (...args: unknown[]) => listSlackReactions(...args), - pinSlackMessage: (...args: unknown[]) => pinSlackMessage(...args), - reactSlackMessage: (...args: unknown[]) => reactSlackMessage(...args), - readSlackMessages: (...args: unknown[]) => readSlackMessages(...args), - removeOwnSlackReactions: (...args: unknown[]) => removeOwnSlackReactions(...args), - removeSlackReaction: (...args: unknown[]) => removeSlackReaction(...args), - sendSlackMessage: (...args: unknown[]) => sendSlackMessage(...args), - unpinSlackMessage: (...args: unknown[]) => unpinSlackMessage(...args), + deleteSlackMessage, + editSlackMessage, + getSlackMemberInfo, + listSlackEmojis, + listSlackPins, + listSlackReactions, + pinSlackMessage, + reactSlackMessage, + readSlackMessages, + removeOwnSlackReactions, + removeSlackReaction, + sendSlackMessage, + unpinSlackMessage, })); describe("handleSlackAction", () => { @@ -521,7 +521,7 @@ describe("handleSlackAction", () => { cfg, ); - const [, opts] = readSlackMessages.mock.calls[0] ?? []; + const opts = readSlackMessages.mock.calls[0]?.[1] as { threadId?: string } | undefined; expect(opts?.threadId).toBe("12345.6789"); }); @@ -551,7 +551,7 @@ describe("handleSlackAction", () => { readSlackMessages.mockClear(); readSlackMessages.mockResolvedValueOnce({ messages: [], hasMore: false }); await handleSlackAction({ action: "readMessages", channelId: "C1" }, cfg); - const [, opts] = readSlackMessages.mock.calls[0] ?? []; + const opts = readSlackMessages.mock.calls[0]?.[1] as { token?: string } | undefined; expect(opts?.token).toBe("xoxp-1"); }); @@ -562,7 +562,7 @@ describe("handleSlackAction", () => { readSlackMessages.mockClear(); readSlackMessages.mockResolvedValueOnce({ messages: [], hasMore: false }); await handleSlackAction({ action: "readMessages", channelId: "C1" }, cfg); - const [, opts] = readSlackMessages.mock.calls[0] ?? []; + const opts = readSlackMessages.mock.calls[0]?.[1] as { token?: string } | undefined; expect(opts?.token).toBeUndefined(); }); @@ -572,7 +572,7 @@ describe("handleSlackAction", () => { } as OpenClawConfig; sendSlackMessage.mockClear(); await handleSlackAction({ action: "sendMessage", to: "channel:C1", content: "Hello" }, cfg); - const [, , opts] = sendSlackMessage.mock.calls[0] ?? []; + const opts = sendSlackMessage.mock.calls[0]?.[2] as { token?: string } | undefined; expect(opts?.token).toBeUndefined(); }); @@ -584,7 +584,7 @@ describe("handleSlackAction", () => { } as OpenClawConfig; sendSlackMessage.mockClear(); await handleSlackAction({ action: "sendMessage", to: "channel:C1", content: "Hello" }, cfg); - const [, , opts] = sendSlackMessage.mock.calls[0] ?? []; + const opts = sendSlackMessage.mock.calls[0]?.[2] as { token?: string } | undefined; expect(opts?.token).toBe("xoxp-1"); }); diff --git a/src/agents/tools/web-tools.enabled-defaults.e2e.test.ts b/src/agents/tools/web-tools.enabled-defaults.e2e.test.ts index a581798b4..ff160d580 100644 --- a/src/agents/tools/web-tools.enabled-defaults.e2e.test.ts +++ b/src/agents/tools/web-tools.enabled-defaults.e2e.test.ts @@ -2,13 +2,12 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createWebFetchTool, createWebSearchTool } from "./web-tools.js"; function installMockFetch(payload: unknown) { - const mockFetch = vi.fn(() => + const mockFetch = vi.fn((_input?: unknown, _init?: unknown) => Promise.resolve({ ok: true, json: () => Promise.resolve(payload), } as Response), ); - // @ts-expect-error mock fetch global.fetch = mockFetch; return mockFetch; } @@ -55,7 +54,7 @@ async function executePerplexitySearch( const mockFetch = installPerplexitySuccessFetch(); const tool = createPerplexitySearchTool(options?.perplexityConfig); await tool?.execute?.( - 1, + "call-1", options?.freshness ? { query, freshness: options.freshness } : { query }, ); return mockFetch; @@ -90,7 +89,6 @@ describe("web_search country and language parameters", () => { afterEach(() => { vi.unstubAllEnvs(); - // @ts-expect-error global fetch cleanup global.fetch = priorFetch; }); @@ -105,7 +103,7 @@ describe("web_search country and language parameters", () => { const mockFetch = installMockFetch({ web: { results: [] } }); const tool = createWebSearchTool({ config: undefined, sandboxed: true }); expect(tool).not.toBeNull(); - await tool?.execute?.(1, { query: "test", ...params }); + await tool?.execute?.("call-1", { query: "test", ...params }); expect(mockFetch).toHaveBeenCalled(); return new URL(mockFetch.mock.calls[0][0] as string); } @@ -123,7 +121,7 @@ describe("web_search country and language parameters", () => { it("rejects invalid freshness values", async () => { const mockFetch = installMockFetch({ web: { results: [] } }); const tool = createWebSearchTool({ config: undefined, sandboxed: true }); - const result = await tool?.execute?.(1, { query: "test", freshness: "yesterday" }); + const result = await tool?.execute?.("call-1", { query: "test", freshness: "yesterday" }); expect(mockFetch).not.toHaveBeenCalled(); expect(result?.details).toMatchObject({ error: "invalid_freshness" }); @@ -135,7 +133,6 @@ describe("web_search perplexity baseUrl defaults", () => { afterEach(() => { vi.unstubAllEnvs(); - // @ts-expect-error global fetch cleanup global.fetch = priorFetch; }); @@ -213,7 +210,6 @@ describe("web_search external content wrapping", () => { afterEach(() => { vi.unstubAllEnvs(); - // @ts-expect-error global fetch cleanup global.fetch = priorFetch; }); @@ -236,11 +232,10 @@ describe("web_search external content wrapping", () => { }), } as Response), ); - // @ts-expect-error mock fetch global.fetch = mockFetch; const tool = createWebSearchTool({ config: undefined, sandboxed: true }); - const result = await tool?.execute?.(1, { query: "test" }); + const result = await tool?.execute?.("call-1", { query: "test" }); const details = result?.details as { externalContent?: { untrusted?: boolean; source?: string; wrapped?: boolean }; results?: Array<{ description?: string }>; @@ -275,11 +270,10 @@ describe("web_search external content wrapping", () => { }), } as Response), ); - // @ts-expect-error mock fetch global.fetch = mockFetch; const tool = createWebSearchTool({ config: undefined, sandboxed: true }); - const result = await tool?.execute?.(1, { query: "unique-test-url-not-wrapped" }); + const result = await tool?.execute?.("call-1", { query: "unique-test-url-not-wrapped" }); const details = result?.details as { results?: Array<{ url?: string }> }; // URL should NOT be wrapped - kept raw for tool chaining (e.g., web_fetch) @@ -306,11 +300,10 @@ describe("web_search external content wrapping", () => { }), } as Response), ); - // @ts-expect-error mock fetch global.fetch = mockFetch; const tool = createWebSearchTool({ config: undefined, sandboxed: true }); - const result = await tool?.execute?.(1, { query: "unique-test-site-name-wrapping" }); + const result = await tool?.execute?.("call-1", { query: "unique-test-site-name-wrapping" }); const details = result?.details as { results?: Array<{ siteName?: string }> }; expect(details.results?.[0]?.siteName).toBe("example.com"); @@ -337,11 +330,12 @@ describe("web_search external content wrapping", () => { }), } as Response), ); - // @ts-expect-error mock fetch global.fetch = mockFetch; const tool = createWebSearchTool({ config: undefined, sandboxed: true }); - const result = await tool?.execute?.(1, { query: "unique-test-brave-published-wrapping" }); + const result = await tool?.execute?.("call-1", { + query: "unique-test-brave-published-wrapping", + }); const details = result?.details as { results?: Array<{ published?: string }> }; expect(details.results?.[0]?.published).toBe("2 days ago"); @@ -360,14 +354,13 @@ describe("web_search external content wrapping", () => { }), } as Response), ); - // @ts-expect-error mock fetch global.fetch = mockFetch; const tool = createWebSearchTool({ config: { tools: { web: { search: { provider: "perplexity" } } } }, sandboxed: true, }); - const result = await tool?.execute?.(1, { query: "test" }); + const result = await tool?.execute?.("call-1", { query: "test" }); const details = result?.details as { content?: string }; expect(details.content).toContain("<<>>"); @@ -377,7 +370,7 @@ describe("web_search external content wrapping", () => { it("does not wrap Perplexity citations (raw for tool chaining)", async () => { vi.stubEnv("PERPLEXITY_API_KEY", "pplx-test"); const citation = "https://example.com/some-article"; - const mockFetch = vi.fn(() => + const mockFetch = vi.fn((_input?: unknown, _init?: unknown) => Promise.resolve({ ok: true, json: () => @@ -387,14 +380,15 @@ describe("web_search external content wrapping", () => { }), } as Response), ); - // @ts-expect-error mock fetch global.fetch = mockFetch; const tool = createWebSearchTool({ config: { tools: { web: { search: { provider: "perplexity" } } } }, sandboxed: true, }); - const result = await tool?.execute?.(1, { query: "unique-test-perplexity-citations-raw" }); + const result = await tool?.execute?.("call-1", { + query: "unique-test-perplexity-citations-raw", + }); const details = result?.details as { citations?: string[] }; // Citations are URLs - should NOT be wrapped for tool chaining