diff --git a/src/agents/tools/agents-list-tool.test.ts b/src/agents/tools/agents-list-tool.test.ts index f31d8702878..758e5f42ef6 100644 --- a/src/agents/tools/agents-list-tool.test.ts +++ b/src/agents/tools/agents-list-tool.test.ts @@ -3,6 +3,18 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js"; const loadConfigMock = vi.fn<() => OpenClawConfig>(); +type AgentListDetails = { + requester?: string; + allowAny?: boolean; + agents?: Array<{ + id?: string; + name?: string; + configured?: boolean; + model?: string; + agentRuntime?: { id?: string; source?: string }; + }>; +}; + vi.mock("../../config/config.js", async () => { const actual = await vi.importActual("../../config/config.js"); @@ -46,19 +58,16 @@ describe("agents_list tool", () => { "call", {}, ); + const details = result.details as AgentListDetails; - expect(result.details).toMatchObject({ - requester: "main", - agents: [ - { - id: "codex", - name: "Codex", - configured: true, - model: "openai/gpt-5.5", - agentRuntime: { id: "codex", source: "model" }, - }, - ], - }); + expect(details.requester).toBe("main"); + expect(details.agents).toHaveLength(1); + expect(details.agents?.[0]?.id).toBe("codex"); + expect(details.agents?.[0]?.name).toBe("Codex"); + expect(details.agents?.[0]?.configured).toBe(true); + expect(details.agents?.[0]?.model).toBe("openai/gpt-5.5"); + expect(details.agents?.[0]?.agentRuntime?.id).toBe("codex"); + expect(details.agents?.[0]?.agentRuntime?.source).toBe("model"); }); it("returns requester as the only target when no subagent allowlist is configured", async () => { @@ -73,17 +82,13 @@ describe("agents_list tool", () => { "call", {}, ); + const details = result.details as AgentListDetails; - expect(result.details).toMatchObject({ - requester: "main", - allowAny: false, - agents: [ - { - id: "main", - configured: true, - }, - ], - }); + expect(details.requester).toBe("main"); + expect(details.allowAny).toBe(false); + expect(details.agents).toHaveLength(1); + expect(details.agents?.[0]?.id).toBe("main"); + expect(details.agents?.[0]?.configured).toBe(true); }); it("ignores legacy env-forced plugin runtime selections", async () => { @@ -102,15 +107,12 @@ describe("agents_list tool", () => { "call", {}, ); + const details = result.details as AgentListDetails; - expect(result.details).toMatchObject({ - agents: [ - { - id: "main", - agentRuntime: { id: "codex", source: "implicit" }, - }, - ], - }); + expect(details.agents).toHaveLength(1); + expect(details.agents?.[0]?.id).toBe("main"); + expect(details.agents?.[0]?.agentRuntime?.id).toBe("codex"); + expect(details.agents?.[0]?.agentRuntime?.source).toBe("implicit"); }); it("ignores legacy per-agent runtime overrides", async () => { @@ -132,14 +134,11 @@ describe("agents_list tool", () => { "call", {}, ); + const details = result.details as AgentListDetails; - expect(result.details).toMatchObject({ - agents: [ - { - id: "strict", - agentRuntime: { id: "codex", source: "implicit" }, - }, - ], - }); + expect(details.agents).toHaveLength(1); + expect(details.agents?.[0]?.id).toBe("strict"); + expect(details.agents?.[0]?.agentRuntime?.id).toBe("codex"); + expect(details.agents?.[0]?.agentRuntime?.source).toBe("implicit"); }); }); diff --git a/src/agents/tools/heartbeat-response-tool.test.ts b/src/agents/tools/heartbeat-response-tool.test.ts index c02ab58dcb3..be660992ff3 100644 --- a/src/agents/tools/heartbeat-response-tool.test.ts +++ b/src/agents/tools/heartbeat-response-tool.test.ts @@ -11,6 +11,16 @@ function readSchemaProperty(schema: unknown, key: string): Record; } +type HeartbeatResponseDetails = { + status?: string; + outcome?: string; + notify?: boolean; + summary?: string; + notificationText?: string; + priority?: string; + nextCheck?: string; +}; + describe("createHeartbeatResponseTool", () => { it("uses flat enum schemas for provider portability", () => { const tool = createHeartbeatResponseTool(); @@ -18,14 +28,10 @@ describe("createHeartbeatResponseTool", () => { const outcome = readSchemaProperty(tool.parameters, "outcome"); const priority = readSchemaProperty(tool.parameters, "priority"); - expect(outcome).toMatchObject({ - type: "string", - enum: ["no_change", "progress", "done", "blocked", "needs_attention"], - }); - expect(priority).toMatchObject({ - type: "string", - enum: ["low", "normal", "high"], - }); + expect(outcome.type).toBe("string"); + expect(outcome.enum).toEqual(["no_change", "progress", "done", "blocked", "needs_attention"]); + expect(priority.type).toBe("string"); + expect(priority.enum).toEqual(["low", "normal", "high"]); expect(outcome).not.toHaveProperty("anyOf"); expect(priority).not.toHaveProperty("anyOf"); }); @@ -40,12 +46,11 @@ describe("createHeartbeatResponseTool", () => { }); expect(tool.name).toBe(HEARTBEAT_RESPONSE_TOOL_NAME); - expect(result.details).toMatchObject({ - status: "recorded", - outcome: "no_change", - notify: false, - summary: "Nothing needs attention.", - }); + const details = result.details as HeartbeatResponseDetails; + expect(details.status).toBe("recorded"); + expect(details.outcome).toBe("no_change"); + expect(details.notify).toBe(false); + expect(details.summary).toBe("Nothing needs attention."); }); it("accepts notification text and optional scheduling metadata", async () => { @@ -60,15 +65,14 @@ describe("createHeartbeatResponseTool", () => { nextCheck: "2026-05-01T17:00:00Z", }); - expect(result.details).toMatchObject({ - status: "recorded", - outcome: "needs_attention", - notify: true, - summary: "Build is blocked.", - notificationText: "Build is blocked on missing credentials.", - priority: "high", - nextCheck: "2026-05-01T17:00:00Z", - }); + const details = result.details as HeartbeatResponseDetails; + expect(details.status).toBe("recorded"); + expect(details.outcome).toBe("needs_attention"); + expect(details.notify).toBe(true); + expect(details.summary).toBe("Build is blocked."); + expect(details.notificationText).toBe("Build is blocked on missing credentials."); + expect(details.priority).toBe("high"); + expect(details.nextCheck).toBe("2026-05-01T17:00:00Z"); }); it("rejects missing notify because quiet vs visible delivery must be explicit", async () => { diff --git a/src/agents/tools/sessions-yield-tool.test.ts b/src/agents/tools/sessions-yield-tool.test.ts index f7def7cbb73..7b6a1ecb2b2 100644 --- a/src/agents/tools/sessions-yield-tool.test.ts +++ b/src/agents/tools/sessions-yield-tool.test.ts @@ -1,15 +1,20 @@ import { describe, expect, it, vi } from "vitest"; import { createSessionsYieldTool } from "./sessions-yield-tool.js"; +type SessionsYieldDetails = { + status?: string; + message?: string; + error?: string; +}; + describe("sessions_yield tool", () => { it("returns error when no sessionId is provided", async () => { const onYield = vi.fn(); const tool = createSessionsYieldTool({ onYield }); const result = await tool.execute("call-1", {}); - expect(result.details).toMatchObject({ - status: "error", - error: "No session context", - }); + const details = result.details as SessionsYieldDetails; + expect(details.status).toBe("error"); + expect(details.error).toBe("No session context"); expect(onYield).not.toHaveBeenCalled(); }); @@ -17,7 +22,9 @@ describe("sessions_yield tool", () => { const onYield = vi.fn(); const tool = createSessionsYieldTool({ sessionId: "test-session", onYield }); const result = await tool.execute("call-1", {}); - expect(result.details).toMatchObject({ status: "yielded", message: "Turn yielded." }); + const details = result.details as SessionsYieldDetails; + expect(details.status).toBe("yielded"); + expect(details.message).toBe("Turn yielded."); expect(onYield).toHaveBeenCalledOnce(); expect(onYield).toHaveBeenCalledWith("Turn yielded."); }); @@ -26,10 +33,9 @@ describe("sessions_yield tool", () => { const onYield = vi.fn(); const tool = createSessionsYieldTool({ sessionId: "test-session", onYield }); const result = await tool.execute("call-1", { message: "Waiting for fact-checker" }); - expect(result.details).toMatchObject({ - status: "yielded", - message: "Waiting for fact-checker", - }); + const details = result.details as SessionsYieldDetails; + expect(details.status).toBe("yielded"); + expect(details.message).toBe("Waiting for fact-checker"); expect(onYield).toHaveBeenCalledOnce(); expect(onYield).toHaveBeenCalledWith("Waiting for fact-checker"); }); @@ -37,9 +43,8 @@ describe("sessions_yield tool", () => { it("returns error without onYield callback", async () => { const tool = createSessionsYieldTool({ sessionId: "test-session" }); const result = await tool.execute("call-1", {}); - expect(result.details).toMatchObject({ - status: "error", - error: "Yield not supported in this context", - }); + const details = result.details as SessionsYieldDetails; + expect(details.status).toBe("error"); + expect(details.error).toBe("Yield not supported in this context"); }); });