mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
chore: Fix types in tests 31/N.
This commit is contained in:
@@ -1,14 +1,15 @@
|
||||
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { toToolDefinitions } from "./pi-tool-definition-adapter.js";
|
||||
|
||||
const hookMocks = vi.hoisted(() => ({
|
||||
runner: {
|
||||
hasHooks: vi.fn(() => false),
|
||||
hasHooks: vi.fn((_: string) => false),
|
||||
runAfterToolCall: vi.fn(async () => {}),
|
||||
},
|
||||
isToolWrappedWithBeforeToolCallHook: vi.fn(() => false),
|
||||
consumeAdjustedParamsForToolCall: vi.fn(() => undefined),
|
||||
consumeAdjustedParamsForToolCall: vi.fn((_: string) => undefined as unknown),
|
||||
runBeforeToolCallHook: vi.fn(async ({ params }: { params: unknown }) => ({
|
||||
blocked: false,
|
||||
params,
|
||||
@@ -30,18 +31,28 @@ function createReadTool() {
|
||||
name: "read",
|
||||
label: "Read",
|
||||
description: "reads",
|
||||
parameters: {},
|
||||
parameters: Type.Object({}),
|
||||
execute: vi.fn(async () => ({ content: [], details: { ok: true } })),
|
||||
} satisfies AgentTool<unknown, unknown>;
|
||||
} satisfies AgentTool;
|
||||
}
|
||||
|
||||
type ToolExecute = ReturnType<typeof toToolDefinitions>[number]["execute"];
|
||||
const extensionContext = {} as Parameters<ToolExecute>[3];
|
||||
|
||||
function enableAfterToolCallHook() {
|
||||
hookMocks.runner.hasHooks.mockImplementation((name: string) => name === "after_tool_call");
|
||||
}
|
||||
|
||||
async function executeReadTool(callId: string) {
|
||||
const defs = toToolDefinitions([createReadTool()]);
|
||||
return await defs[0].execute(callId, { path: "/tmp/file" }, undefined, undefined);
|
||||
const args: Parameters<(typeof defs)[number]["execute"]> = [
|
||||
callId,
|
||||
{ path: "/tmp/file" },
|
||||
undefined,
|
||||
extensionContext,
|
||||
undefined,
|
||||
];
|
||||
return await defs[0].execute(...args);
|
||||
}
|
||||
|
||||
function expectReadAfterToolCallPayload(result: Awaited<ReturnType<typeof executeReadTool>>) {
|
||||
@@ -87,7 +98,7 @@ describe("pi tool definition adapter after_tool_call", () => {
|
||||
it("uses wrapped-tool adjusted params for after_tool_call payload", async () => {
|
||||
enableAfterToolCallHook();
|
||||
hookMocks.isToolWrappedWithBeforeToolCallHook.mockReturnValue(true);
|
||||
hookMocks.consumeAdjustedParamsForToolCall.mockReturnValue({ mode: "safe" });
|
||||
hookMocks.consumeAdjustedParamsForToolCall.mockReturnValue({ mode: "safe" } as unknown);
|
||||
const result = await executeReadTool("call-ok-wrapped");
|
||||
|
||||
expect(result.details).toMatchObject({ ok: true });
|
||||
@@ -101,14 +112,21 @@ describe("pi tool definition adapter after_tool_call", () => {
|
||||
name: "bash",
|
||||
label: "Bash",
|
||||
description: "throws",
|
||||
parameters: {},
|
||||
parameters: Type.Object({}),
|
||||
execute: vi.fn(async () => {
|
||||
throw new Error("boom");
|
||||
}),
|
||||
} satisfies AgentTool<unknown, unknown>;
|
||||
} satisfies AgentTool;
|
||||
|
||||
const defs = toToolDefinitions([tool]);
|
||||
const result = await defs[0].execute("call-err", { cmd: "ls" }, undefined, undefined);
|
||||
const args: Parameters<(typeof defs)[number]["execute"]> = [
|
||||
"call-err",
|
||||
{ cmd: "ls" },
|
||||
undefined,
|
||||
extensionContext,
|
||||
undefined,
|
||||
];
|
||||
const result = await defs[0].execute(...args);
|
||||
|
||||
expect(result.details).toMatchObject({
|
||||
status: "error",
|
||||
|
||||
@@ -1,21 +1,32 @@
|
||||
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { toToolDefinitions } from "./pi-tool-definition-adapter.js";
|
||||
|
||||
type ToolExecute = ReturnType<typeof toToolDefinitions>[number]["execute"];
|
||||
const extensionContext = {} as Parameters<ToolExecute>[3];
|
||||
|
||||
describe("pi tool definition adapter", () => {
|
||||
it("wraps tool errors into a tool result", async () => {
|
||||
const tool = {
|
||||
name: "boom",
|
||||
label: "Boom",
|
||||
description: "throws",
|
||||
parameters: {},
|
||||
parameters: Type.Object({}),
|
||||
execute: async () => {
|
||||
throw new Error("nope");
|
||||
},
|
||||
} satisfies AgentTool<unknown, unknown>;
|
||||
} satisfies AgentTool;
|
||||
|
||||
const defs = toToolDefinitions([tool]);
|
||||
const result = await defs[0].execute("call1", {}, undefined, undefined);
|
||||
const args: Parameters<(typeof defs)[number]["execute"]> = [
|
||||
"call1",
|
||||
{},
|
||||
undefined,
|
||||
extensionContext,
|
||||
undefined,
|
||||
];
|
||||
const result = await defs[0].execute(...args);
|
||||
|
||||
expect(result.details).toMatchObject({
|
||||
status: "error",
|
||||
@@ -30,14 +41,21 @@ describe("pi tool definition adapter", () => {
|
||||
name: "bash",
|
||||
label: "Bash",
|
||||
description: "throws",
|
||||
parameters: {},
|
||||
parameters: Type.Object({}),
|
||||
execute: async () => {
|
||||
throw new Error("nope");
|
||||
},
|
||||
} satisfies AgentTool<unknown, unknown>;
|
||||
} satisfies AgentTool;
|
||||
|
||||
const defs = toToolDefinitions([tool]);
|
||||
const result = await defs[0].execute("call2", {}, undefined, undefined);
|
||||
const args: Parameters<(typeof defs)[number]["execute"]> = [
|
||||
"call2",
|
||||
{},
|
||||
undefined,
|
||||
extensionContext,
|
||||
undefined,
|
||||
];
|
||||
const result = await defs[0].execute(...args);
|
||||
|
||||
expect(result.details).toMatchObject({
|
||||
status: "error",
|
||||
|
||||
@@ -32,11 +32,17 @@ describe("before_tool_call hook integration", () => {
|
||||
agentId: "main",
|
||||
sessionKey: "main",
|
||||
});
|
||||
const extensionContext = {} as Parameters<typeof tool.execute>[3];
|
||||
|
||||
await tool.execute("call-1", { path: "/tmp/file" }, undefined, undefined);
|
||||
await tool.execute("call-1", { path: "/tmp/file" }, undefined, extensionContext);
|
||||
|
||||
expect(hookRunner.runBeforeToolCall).not.toHaveBeenCalled();
|
||||
expect(execute).toHaveBeenCalledWith("call-1", { path: "/tmp/file" }, undefined, undefined);
|
||||
expect(execute).toHaveBeenCalledWith(
|
||||
"call-1",
|
||||
{ path: "/tmp/file" },
|
||||
undefined,
|
||||
extensionContext,
|
||||
);
|
||||
});
|
||||
|
||||
it("allows hook to modify parameters", async () => {
|
||||
@@ -45,14 +51,15 @@ describe("before_tool_call hook integration", () => {
|
||||
const execute = vi.fn().mockResolvedValue({ content: [], details: { ok: true } });
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
const tool = wrapToolWithBeforeToolCallHook({ name: "exec", execute } as any);
|
||||
const extensionContext = {} as Parameters<typeof tool.execute>[3];
|
||||
|
||||
await tool.execute("call-2", { cmd: "ls" }, undefined, undefined);
|
||||
await tool.execute("call-2", { cmd: "ls" }, undefined, extensionContext);
|
||||
|
||||
expect(execute).toHaveBeenCalledWith(
|
||||
"call-2",
|
||||
{ cmd: "ls", mode: "safe" },
|
||||
undefined,
|
||||
undefined,
|
||||
extensionContext,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -65,10 +72,11 @@ describe("before_tool_call hook integration", () => {
|
||||
const execute = vi.fn().mockResolvedValue({ content: [], details: { ok: true } });
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
const tool = wrapToolWithBeforeToolCallHook({ name: "exec", execute } as any);
|
||||
const extensionContext = {} as Parameters<typeof tool.execute>[3];
|
||||
|
||||
await expect(tool.execute("call-3", { cmd: "rm -rf /" }, undefined, undefined)).rejects.toThrow(
|
||||
"blocked",
|
||||
);
|
||||
await expect(
|
||||
tool.execute("call-3", { cmd: "rm -rf /" }, undefined, extensionContext),
|
||||
).rejects.toThrow("blocked");
|
||||
expect(execute).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -78,10 +86,16 @@ describe("before_tool_call hook integration", () => {
|
||||
const execute = vi.fn().mockResolvedValue({ content: [], details: { ok: true } });
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
const tool = wrapToolWithBeforeToolCallHook({ name: "read", execute } as any);
|
||||
const extensionContext = {} as Parameters<typeof tool.execute>[3];
|
||||
|
||||
await tool.execute("call-4", { path: "/tmp/file" }, undefined, undefined);
|
||||
await tool.execute("call-4", { path: "/tmp/file" }, undefined, extensionContext);
|
||||
|
||||
expect(execute).toHaveBeenCalledWith("call-4", { path: "/tmp/file" }, undefined, undefined);
|
||||
expect(execute).toHaveBeenCalledWith(
|
||||
"call-4",
|
||||
{ path: "/tmp/file" },
|
||||
undefined,
|
||||
extensionContext,
|
||||
);
|
||||
});
|
||||
|
||||
it("normalizes non-object params for hook contract", async () => {
|
||||
@@ -93,8 +107,9 @@ describe("before_tool_call hook integration", () => {
|
||||
agentId: "main",
|
||||
sessionKey: "main",
|
||||
});
|
||||
const extensionContext = {} as Parameters<typeof tool.execute>[3];
|
||||
|
||||
await tool.execute("call-5", "not-an-object", undefined, undefined);
|
||||
await tool.execute("call-5", "not-an-object", undefined, extensionContext);
|
||||
|
||||
expect(hookRunner.runBeforeToolCall).toHaveBeenCalledWith(
|
||||
{
|
||||
@@ -136,14 +151,16 @@ describe("before_tool_call hook deduplication (#15502)", () => {
|
||||
sessionKey: "main",
|
||||
});
|
||||
const [def] = toToolDefinitions([wrapped]);
|
||||
const extensionContext = {} as Parameters<typeof def.execute>[3];
|
||||
|
||||
await def.execute(
|
||||
const args: Parameters<typeof def.execute> = [
|
||||
"call-dedup",
|
||||
{ url: "https://example.com" },
|
||||
undefined,
|
||||
extensionContext,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
];
|
||||
await def.execute(...args);
|
||||
|
||||
expect(hookRunner.runBeforeToolCall).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -183,8 +200,16 @@ describe("before_tool_call hook integration for client tools", () => {
|
||||
onClientToolCall,
|
||||
{ agentId: "main", sessionKey: "main" },
|
||||
);
|
||||
const extensionContext = {} as Parameters<typeof tool.execute>[3];
|
||||
|
||||
await tool.execute("client-call-1", { value: "ok" }, undefined, undefined, undefined);
|
||||
const args: Parameters<typeof tool.execute> = [
|
||||
"client-call-1",
|
||||
{ value: "ok" },
|
||||
undefined,
|
||||
extensionContext,
|
||||
undefined,
|
||||
];
|
||||
await tool.execute(...args);
|
||||
|
||||
expect(onClientToolCall).toHaveBeenCalledWith("client_tool", {
|
||||
value: "ok",
|
||||
|
||||
@@ -51,7 +51,7 @@ async function seedZipDownloadResponse() {
|
||||
zip.file("hello.txt", "hi");
|
||||
const buffer = await zip.generateAsync({ type: "nodebuffer" });
|
||||
fetchWithSsrFGuardMock.mockResolvedValue({
|
||||
response: new Response(buffer, { status: 200 }),
|
||||
response: new Response(new Uint8Array(buffer), { status: 200 }),
|
||||
release: async () => undefined,
|
||||
});
|
||||
}
|
||||
@@ -107,7 +107,7 @@ describe("installSkill download extraction safety", () => {
|
||||
const buffer = await zip.generateAsync({ type: "nodebuffer" });
|
||||
|
||||
fetchWithSsrFGuardMock.mockResolvedValue({
|
||||
response: new Response(buffer, { status: 200 }),
|
||||
response: new Response(new Uint8Array(buffer), { status: 200 }),
|
||||
release: async () => undefined,
|
||||
});
|
||||
|
||||
@@ -150,7 +150,7 @@ describe("installSkill download extraction safety", () => {
|
||||
|
||||
const buffer = await fs.readFile(archivePath);
|
||||
fetchWithSsrFGuardMock.mockResolvedValue({
|
||||
response: new Response(buffer, { status: 200 }),
|
||||
response: new Response(new Uint8Array(buffer), { status: 200 }),
|
||||
release: async () => undefined,
|
||||
});
|
||||
|
||||
@@ -182,7 +182,7 @@ describe("installSkill download extraction safety", () => {
|
||||
zip.file("package/hello.txt", "hi");
|
||||
const buffer = await zip.generateAsync({ type: "nodebuffer" });
|
||||
fetchWithSsrFGuardMock.mockResolvedValue({
|
||||
response: new Response(buffer, { status: 200 }),
|
||||
response: new Response(new Uint8Array(buffer), { status: 200 }),
|
||||
release: async () => undefined,
|
||||
});
|
||||
|
||||
@@ -215,7 +215,7 @@ describe("installSkill download extraction safety", () => {
|
||||
zip.file("hello.txt", "hi");
|
||||
const buffer = await zip.generateAsync({ type: "nodebuffer" });
|
||||
fetchWithSsrFGuardMock.mockResolvedValue({
|
||||
response: new Response(buffer, { status: 200 }),
|
||||
response: new Response(new Uint8Array(buffer), { status: 200 }),
|
||||
release: async () => undefined,
|
||||
});
|
||||
|
||||
|
||||
@@ -19,8 +19,11 @@ describe("ensureSkillsWatcher", () => {
|
||||
mod.ensureSkillsWatcher({ workspaceDir: "/tmp/workspace" });
|
||||
|
||||
expect(watchMock).toHaveBeenCalledTimes(1);
|
||||
const targets = watchMock.mock.calls[0]?.[0] as string[];
|
||||
const opts = watchMock.mock.calls[0]?.[1] as { ignored?: unknown };
|
||||
const firstCall = (
|
||||
watchMock.mock.calls as unknown as Array<[string[], { ignored?: unknown }]>
|
||||
)[0];
|
||||
const targets = firstCall?.[0] ?? [];
|
||||
const opts = firstCall?.[1] ?? {};
|
||||
|
||||
expect(opts.ignored).toBe(mod.DEFAULT_SKILLS_WATCH_IGNORED);
|
||||
const posix = (p: string) => p.replaceAll("\\", "/");
|
||||
|
||||
@@ -130,7 +130,12 @@ describe("subagent registry persistence", () => {
|
||||
cleanup: string;
|
||||
label?: string;
|
||||
};
|
||||
const first = announceSpy.mock.calls[0]?.[0] as unknown as AnnounceParams;
|
||||
const first = (announceSpy.mock.calls as unknown as Array<[unknown]>)[0]?.[0] as
|
||||
| AnnounceParams
|
||||
| undefined;
|
||||
if (!first) {
|
||||
throw new Error("expected announce call");
|
||||
}
|
||||
expect(first.childSessionKey).toBe("agent:main:subagent:test");
|
||||
expect(first.requesterOrigin?.channel).toBe("whatsapp");
|
||||
expect(first.requesterOrigin?.accountId).toBe("acct-main");
|
||||
@@ -167,7 +172,7 @@ describe("subagent registry persistence", () => {
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
// announce should NOT be called since cleanupHandled was true
|
||||
const calls = announceSpy.mock.calls.map((call) => call[0]);
|
||||
const calls = (announceSpy.mock.calls as unknown as Array<[unknown]>).map((call) => call[0]);
|
||||
const match = calls.find(
|
||||
(params) =>
|
||||
(params as { childSessionKey?: string }).childSessionKey === "agent:main:subagent:two",
|
||||
|
||||
@@ -28,7 +28,7 @@ vi.mock("../config/config.js", () => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
const announceSpy = vi.fn(async () => true);
|
||||
const announceSpy = vi.fn(async (_params: unknown) => true);
|
||||
vi.mock("./subagent-announce.js", () => ({
|
||||
runSubagentAnnounceFlow: announceSpy,
|
||||
}));
|
||||
@@ -101,7 +101,7 @@ describe("subagent registry steer restarts", () => {
|
||||
await flushAnnounce();
|
||||
expect(announceSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
const announce = announceSpy.mock.calls[0]?.[0] as { childRunId?: string };
|
||||
const announce = (announceSpy.mock.calls[0]?.[0] ?? {}) as { childRunId?: string };
|
||||
expect(announce.childRunId).toBe("run-new");
|
||||
});
|
||||
|
||||
@@ -161,7 +161,7 @@ describe("subagent registry steer restarts", () => {
|
||||
await flushAnnounce();
|
||||
|
||||
expect(announceSpy).toHaveBeenCalledTimes(1);
|
||||
const announce = announceSpy.mock.calls[0]?.[0] as { childRunId?: string };
|
||||
const announce = (announceSpy.mock.calls[0]?.[0] ?? {}) as { childRunId?: string };
|
||||
expect(announce.childRunId).toBe("run-failed-restart");
|
||||
});
|
||||
|
||||
@@ -234,7 +234,7 @@ describe("subagent registry steer restarts", () => {
|
||||
await flushAnnounce();
|
||||
|
||||
const childRunIds = announceSpy.mock.calls.map(
|
||||
(call) => (call[0] as { childRunId?: string }).childRunId,
|
||||
(call) => ((call[0] ?? {}) as { childRunId?: string }).childRunId,
|
||||
);
|
||||
expect(childRunIds.filter((id) => id === "run-parent")).toHaveLength(2);
|
||||
expect(childRunIds.filter((id) => id === "run-child")).toHaveLength(1);
|
||||
|
||||
@@ -3,6 +3,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import type { ModelDefinitionConfig } from "../../config/types.models.js";
|
||||
import { createOpenClawCodingTools } from "../pi-tools.js";
|
||||
import { createHostSandboxFsBridge } from "../test-helpers/host-sandbox-fs-bridge.js";
|
||||
import { __testing, createImageTool, resolveImageModelConfigForTool } from "./image-tool.js";
|
||||
@@ -62,6 +63,18 @@ function createMinimaxImageConfig(): OpenClawConfig {
|
||||
};
|
||||
}
|
||||
|
||||
function makeModelDefinition(id: string, input: Array<"text" | "image">): ModelDefinitionConfig {
|
||||
return {
|
||||
id,
|
||||
name: id,
|
||||
reasoning: false,
|
||||
input,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 128_000,
|
||||
maxTokens: 8_192,
|
||||
};
|
||||
}
|
||||
|
||||
async function expectImageToolExecOk(
|
||||
tool: {
|
||||
execute: (toolCallId: string, input: { prompt: string; image: string }) => Promise<unknown>;
|
||||
@@ -171,8 +184,8 @@ describe("image tool implicit imageModel config", () => {
|
||||
providers: {
|
||||
acme: {
|
||||
models: [
|
||||
{ id: "text-1", input: ["text"] },
|
||||
{ id: "vision-1", input: ["text", "image"] },
|
||||
makeModelDefinition("text-1", ["text"]),
|
||||
makeModelDefinition("vision-1", ["text", "image"]),
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -215,7 +228,7 @@ describe("image tool implicit imageModel config", () => {
|
||||
models: {
|
||||
providers: {
|
||||
acme: {
|
||||
models: [{ id: "vision-1", input: ["text", "image"] }],
|
||||
models: [makeModelDefinition("vision-1", ["text", "image"])],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
|
||||
let backend: "builtin" | "qmd" = "builtin";
|
||||
let searchImpl: () => Promise<unknown[]> = async () => [
|
||||
@@ -42,6 +43,10 @@ vi.mock("../../memory/index.js", () => {
|
||||
|
||||
import { createMemoryGetTool, createMemorySearchTool } from "./memory-tool.js";
|
||||
|
||||
function asOpenClawConfig(config: Partial<OpenClawConfig>): OpenClawConfig {
|
||||
return config as OpenClawConfig;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
backend = "builtin";
|
||||
searchImpl = async () => [
|
||||
@@ -61,7 +66,10 @@ beforeEach(() => {
|
||||
describe("memory search citations", () => {
|
||||
it("appends source information when citations are enabled", async () => {
|
||||
backend = "builtin";
|
||||
const cfg = { memory: { citations: "on" }, agents: { list: [{ id: "main", default: true }] } };
|
||||
const cfg = asOpenClawConfig({
|
||||
memory: { citations: "on" },
|
||||
agents: { list: [{ id: "main", default: true }] },
|
||||
});
|
||||
const tool = createMemorySearchTool({ config: cfg });
|
||||
if (!tool) {
|
||||
throw new Error("tool missing");
|
||||
@@ -74,7 +82,10 @@ describe("memory search citations", () => {
|
||||
|
||||
it("leaves snippet untouched when citations are off", async () => {
|
||||
backend = "builtin";
|
||||
const cfg = { memory: { citations: "off" }, agents: { list: [{ id: "main", default: true }] } };
|
||||
const cfg = asOpenClawConfig({
|
||||
memory: { citations: "off" },
|
||||
agents: { list: [{ id: "main", default: true }] },
|
||||
});
|
||||
const tool = createMemorySearchTool({ config: cfg });
|
||||
if (!tool) {
|
||||
throw new Error("tool missing");
|
||||
@@ -87,10 +98,10 @@ describe("memory search citations", () => {
|
||||
|
||||
it("clamps decorated snippets to qmd injected budget", async () => {
|
||||
backend = "qmd";
|
||||
const cfg = {
|
||||
const cfg = asOpenClawConfig({
|
||||
memory: { citations: "on", backend: "qmd", qmd: { limits: { maxInjectedChars: 20 } } },
|
||||
agents: { list: [{ id: "main", default: true }] },
|
||||
};
|
||||
});
|
||||
const tool = createMemorySearchTool({ config: cfg });
|
||||
if (!tool) {
|
||||
throw new Error("tool missing");
|
||||
@@ -102,10 +113,10 @@ describe("memory search citations", () => {
|
||||
|
||||
it("honors auto mode for direct chats", async () => {
|
||||
backend = "builtin";
|
||||
const cfg = {
|
||||
const cfg = asOpenClawConfig({
|
||||
memory: { citations: "auto" },
|
||||
agents: { list: [{ id: "main", default: true }] },
|
||||
};
|
||||
});
|
||||
const tool = createMemorySearchTool({
|
||||
config: cfg,
|
||||
agentSessionKey: "agent:main:discord:dm:u123",
|
||||
@@ -120,10 +131,10 @@ describe("memory search citations", () => {
|
||||
|
||||
it("suppresses citations for auto mode in group chats", async () => {
|
||||
backend = "builtin";
|
||||
const cfg = {
|
||||
const cfg = asOpenClawConfig({
|
||||
memory: { citations: "auto" },
|
||||
agents: { list: [{ id: "main", default: true }] },
|
||||
};
|
||||
});
|
||||
const tool = createMemorySearchTool({
|
||||
config: cfg,
|
||||
agentSessionKey: "agent:main:discord:group:c123",
|
||||
|
||||
@@ -11,11 +11,11 @@ import {
|
||||
|
||||
describe("resolveSessionToolsVisibility", () => {
|
||||
it("defaults to tree when unset or invalid", () => {
|
||||
expect(resolveSessionToolsVisibility({} as OpenClawConfig)).toBe("tree");
|
||||
expect(resolveSessionToolsVisibility({} as unknown as OpenClawConfig)).toBe("tree");
|
||||
expect(
|
||||
resolveSessionToolsVisibility({
|
||||
tools: { sessions: { visibility: "invalid" } },
|
||||
} as OpenClawConfig),
|
||||
} as unknown as OpenClawConfig),
|
||||
).toBe("tree");
|
||||
});
|
||||
|
||||
@@ -23,7 +23,7 @@ describe("resolveSessionToolsVisibility", () => {
|
||||
expect(
|
||||
resolveSessionToolsVisibility({
|
||||
tools: { sessions: { visibility: "ALL" } },
|
||||
} as OpenClawConfig),
|
||||
} as unknown as OpenClawConfig),
|
||||
).toBe("all");
|
||||
});
|
||||
});
|
||||
@@ -33,7 +33,7 @@ describe("resolveEffectiveSessionToolsVisibility", () => {
|
||||
const cfg = {
|
||||
tools: { sessions: { visibility: "all" } },
|
||||
agents: { defaults: { sandbox: { sessionToolsVisibility: "spawned" } } },
|
||||
} as OpenClawConfig;
|
||||
} as unknown as OpenClawConfig;
|
||||
expect(resolveEffectiveSessionToolsVisibility({ cfg, sandboxed: true })).toBe("tree");
|
||||
});
|
||||
|
||||
@@ -41,21 +41,21 @@ describe("resolveEffectiveSessionToolsVisibility", () => {
|
||||
const cfg = {
|
||||
tools: { sessions: { visibility: "all" } },
|
||||
agents: { defaults: { sandbox: { sessionToolsVisibility: "all" } } },
|
||||
} as OpenClawConfig;
|
||||
} as unknown as OpenClawConfig;
|
||||
expect(resolveEffectiveSessionToolsVisibility({ cfg, sandboxed: true })).toBe("all");
|
||||
});
|
||||
});
|
||||
|
||||
describe("sandbox session-tools context", () => {
|
||||
it("defaults sandbox visibility clamp to spawned", () => {
|
||||
expect(resolveSandboxSessionToolsVisibility({} as OpenClawConfig)).toBe("spawned");
|
||||
expect(resolveSandboxSessionToolsVisibility({} as unknown as OpenClawConfig)).toBe("spawned");
|
||||
});
|
||||
|
||||
it("restricts non-subagent sandboxed sessions to spawned visibility", () => {
|
||||
const cfg = {
|
||||
tools: { sessions: { visibility: "all" } },
|
||||
agents: { defaults: { sandbox: { sessionToolsVisibility: "spawned" } } },
|
||||
} as OpenClawConfig;
|
||||
} as unknown as OpenClawConfig;
|
||||
const context = resolveSandboxedSessionToolContext({
|
||||
cfg,
|
||||
agentSessionKey: "agent:main:main",
|
||||
@@ -71,7 +71,7 @@ describe("sandbox session-tools context", () => {
|
||||
const cfg = {
|
||||
tools: { sessions: { visibility: "all" } },
|
||||
agents: { defaults: { sandbox: { sessionToolsVisibility: "spawned" } } },
|
||||
} as OpenClawConfig;
|
||||
} as unknown as OpenClawConfig;
|
||||
const context = resolveSandboxedSessionToolContext({
|
||||
cfg,
|
||||
agentSessionKey: "agent:main:subagent:abc",
|
||||
@@ -85,7 +85,7 @@ describe("sandbox session-tools context", () => {
|
||||
|
||||
describe("createAgentToAgentPolicy", () => {
|
||||
it("denies cross-agent access when disabled", () => {
|
||||
const policy = createAgentToAgentPolicy({} as OpenClawConfig);
|
||||
const policy = createAgentToAgentPolicy({} as unknown as OpenClawConfig);
|
||||
expect(policy.enabled).toBe(false);
|
||||
expect(policy.isAllowed("main", "main")).toBe(true);
|
||||
expect(policy.isAllowed("main", "ops")).toBe(false);
|
||||
@@ -99,7 +99,7 @@ describe("createAgentToAgentPolicy", () => {
|
||||
allow: ["ops-*", "main"],
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig);
|
||||
} as unknown as OpenClawConfig);
|
||||
|
||||
expect(policy.isAllowed("ops-a", "ops-b")).toBe(true);
|
||||
expect(policy.isAllowed("main", "ops-a")).toBe(true);
|
||||
@@ -113,7 +113,7 @@ describe("createSessionVisibilityGuard", () => {
|
||||
action: "send",
|
||||
requesterSessionKey: "agent:main:main",
|
||||
visibility: "all",
|
||||
a2aPolicy: createAgentToAgentPolicy({} as OpenClawConfig),
|
||||
a2aPolicy: createAgentToAgentPolicy({} as unknown as OpenClawConfig),
|
||||
});
|
||||
|
||||
expect(guard.check("agent:ops:main")).toEqual({
|
||||
@@ -129,7 +129,7 @@ describe("createSessionVisibilityGuard", () => {
|
||||
action: "history",
|
||||
requesterSessionKey: "agent:main:main",
|
||||
visibility: "self",
|
||||
a2aPolicy: createAgentToAgentPolicy({} as OpenClawConfig),
|
||||
a2aPolicy: createAgentToAgentPolicy({} as unknown as OpenClawConfig),
|
||||
});
|
||||
|
||||
expect(guard.check("agent:main:main")).toEqual({ allowed: true });
|
||||
|
||||
@@ -16,7 +16,7 @@ function redirectResponse(location: string): Response {
|
||||
status: 302,
|
||||
headers: makeHeaders({ location }),
|
||||
body: { cancel: vi.fn() },
|
||||
} as Response;
|
||||
} as unknown as Response;
|
||||
}
|
||||
|
||||
function textResponse(body: string): Response {
|
||||
@@ -25,7 +25,7 @@ function textResponse(body: string): Response {
|
||||
status: 200,
|
||||
headers: makeHeaders({ "content-type": "text/plain" }),
|
||||
text: async () => body,
|
||||
} as Response;
|
||||
} as unknown as Response;
|
||||
}
|
||||
|
||||
function setMockFetch(impl?: (...args: unknown[]) => unknown) {
|
||||
|
||||
@@ -77,7 +77,7 @@ function errorHtmlResponse(
|
||||
text: async () => html,
|
||||
};
|
||||
}
|
||||
function requestUrl(input: RequestInfo): string {
|
||||
function requestUrl(input: RequestInfo | URL): string {
|
||||
if (typeof input === "string") {
|
||||
return input;
|
||||
}
|
||||
@@ -90,9 +90,9 @@ function requestUrl(input: RequestInfo): string {
|
||||
return "";
|
||||
}
|
||||
|
||||
function installMockFetch(impl: (input: RequestInfo) => Promise<Response>) {
|
||||
const mockFetch = vi.fn(impl);
|
||||
global.fetch = mockFetch;
|
||||
function installMockFetch(impl: (input: RequestInfo | URL) => Promise<Response>) {
|
||||
const mockFetch = vi.fn(async (input: RequestInfo | URL) => await impl(input));
|
||||
global.fetch = mockFetch as typeof global.fetch;
|
||||
return mockFetch;
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ describe("web_fetch extraction fallbacks", () => {
|
||||
});
|
||||
|
||||
it("wraps fetched text with external content markers", async () => {
|
||||
installMockFetch((input: RequestInfo) =>
|
||||
installMockFetch((input: RequestInfo | URL) =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
@@ -183,7 +183,7 @@ describe("web_fetch extraction fallbacks", () => {
|
||||
|
||||
it("enforces maxChars after wrapping", async () => {
|
||||
const longText = "x".repeat(5_000);
|
||||
installMockFetch((input: RequestInfo) =>
|
||||
installMockFetch((input: RequestInfo | URL) =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
@@ -206,7 +206,7 @@ describe("web_fetch extraction fallbacks", () => {
|
||||
});
|
||||
|
||||
it("honors maxChars even when wrapper overhead exceeds limit", async () => {
|
||||
installMockFetch((input: RequestInfo) =>
|
||||
installMockFetch((input: RequestInfo | URL) =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
@@ -232,7 +232,7 @@ describe("web_fetch extraction fallbacks", () => {
|
||||
// The sanitization of these fields is verified by external-content.test.ts tests.
|
||||
|
||||
it("falls back to firecrawl when readability returns no content", async () => {
|
||||
installMockFetch((input: RequestInfo) => {
|
||||
installMockFetch((input: RequestInfo | URL) => {
|
||||
const url = requestUrl(input);
|
||||
if (url.includes("api.firecrawl.dev")) {
|
||||
return Promise.resolve(firecrawlResponse("firecrawl content")) as Promise<Response>;
|
||||
@@ -253,8 +253,11 @@ describe("web_fetch extraction fallbacks", () => {
|
||||
});
|
||||
|
||||
it("throws when readability is disabled and firecrawl is unavailable", async () => {
|
||||
installMockFetch((input: RequestInfo) =>
|
||||
Promise.resolve(htmlResponse("<html><body>hi</body></html>", requestUrl(input))),
|
||||
installMockFetch(
|
||||
(input: RequestInfo | URL) =>
|
||||
Promise.resolve(
|
||||
htmlResponse("<html><body>hi</body></html>", requestUrl(input)),
|
||||
) as Promise<Response>,
|
||||
);
|
||||
|
||||
const tool = createFetchTool({
|
||||
@@ -268,7 +271,7 @@ describe("web_fetch extraction fallbacks", () => {
|
||||
});
|
||||
|
||||
it("throws when readability is empty and firecrawl fails", async () => {
|
||||
installMockFetch((input: RequestInfo) => {
|
||||
installMockFetch((input: RequestInfo | URL) => {
|
||||
const url = requestUrl(input);
|
||||
if (url.includes("api.firecrawl.dev")) {
|
||||
return Promise.resolve(firecrawlError()) as Promise<Response>;
|
||||
@@ -288,7 +291,7 @@ describe("web_fetch extraction fallbacks", () => {
|
||||
});
|
||||
|
||||
it("uses firecrawl when direct fetch fails", async () => {
|
||||
installMockFetch((input: RequestInfo) => {
|
||||
installMockFetch((input: RequestInfo | URL) => {
|
||||
const url = requestUrl(input);
|
||||
if (url.includes("api.firecrawl.dev")) {
|
||||
return Promise.resolve(firecrawlResponse("firecrawl fallback", url)) as Promise<Response>;
|
||||
@@ -314,7 +317,7 @@ describe("web_fetch extraction fallbacks", () => {
|
||||
it("wraps external content and clamps oversized maxChars", async () => {
|
||||
const large = "a".repeat(80_000);
|
||||
installMockFetch(
|
||||
(input: RequestInfo) =>
|
||||
(input: RequestInfo | URL) =>
|
||||
Promise.resolve(textResponse(large, requestUrl(input))) as Promise<Response>,
|
||||
);
|
||||
|
||||
@@ -340,8 +343,11 @@ describe("web_fetch extraction fallbacks", () => {
|
||||
"<!doctype html><html><head><title>Not Found</title></head><body><h1>Not Found</h1><p>" +
|
||||
long +
|
||||
"</p></body></html>";
|
||||
installMockFetch((input: RequestInfo) =>
|
||||
Promise.resolve(errorHtmlResponse(html, 404, requestUrl(input), "Text/HTML; charset=utf-8")),
|
||||
installMockFetch(
|
||||
(input: RequestInfo | URL) =>
|
||||
Promise.resolve(
|
||||
errorHtmlResponse(html, 404, requestUrl(input), "Text/HTML; charset=utf-8"),
|
||||
) as Promise<Response>,
|
||||
);
|
||||
|
||||
const tool = createFetchTool({ firecrawl: { enabled: false } });
|
||||
@@ -361,8 +367,9 @@ describe("web_fetch extraction fallbacks", () => {
|
||||
it("strips HTML errors when content-type is missing", async () => {
|
||||
const html =
|
||||
"<!DOCTYPE HTML><html><head><title>Oops</title></head><body><h1>Oops</h1></body></html>";
|
||||
installMockFetch((input: RequestInfo) =>
|
||||
Promise.resolve(errorHtmlResponse(html, 500, requestUrl(input), null)),
|
||||
installMockFetch(
|
||||
(input: RequestInfo | URL) =>
|
||||
Promise.resolve(errorHtmlResponse(html, 500, requestUrl(input), null)) as Promise<Response>,
|
||||
);
|
||||
|
||||
const tool = createFetchTool({ firecrawl: { enabled: false } });
|
||||
@@ -377,7 +384,7 @@ describe("web_fetch extraction fallbacks", () => {
|
||||
});
|
||||
|
||||
it("wraps firecrawl error details", async () => {
|
||||
installMockFetch((input: RequestInfo) => {
|
||||
installMockFetch((input: RequestInfo | URL) => {
|
||||
const url = requestUrl(input);
|
||||
if (url.includes("api.firecrawl.dev")) {
|
||||
return Promise.resolve({
|
||||
|
||||
Reference in New Issue
Block a user