diff --git a/extensions/canvas/index.test.ts b/extensions/canvas/index.test.ts index 1c47559a57d..d14662b2fde 100644 --- a/extensions/canvas/index.test.ts +++ b/extensions/canvas/index.test.ts @@ -3,6 +3,7 @@ import type { AnyAgentTool, OpenClawPluginApi } from "openclaw/plugin-sdk/plugin import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api"; import { beforeEach, describe, expect, it, vi } from "vitest"; import canvasPlugin from "./index.js"; +import { SHOW_WIDGET_REQUIRED_CLIENT_CAPS } from "./src/tool-schema.js"; const mocks = vi.hoisted(() => { const httpHandler = { @@ -11,6 +12,7 @@ const mocks = vi.hoisted(() => { close: vi.fn(async () => {}), }; const toolExecute = vi.fn(async () => ({ content: [{ type: "text", text: "ok" }] })); + const widgetToolExecute = vi.fn(async () => ({ content: [{ type: "text", text: "widget" }] })); return { httpHandler, createCanvasHttpRouteHandler: vi.fn(() => httpHandler), @@ -25,6 +27,14 @@ const mocks = vi.hoisted(() => { parameters: {}, execute: toolExecute, })), + widgetToolExecute, + createShowWidgetTool: vi.fn(() => ({ + label: "Show Widget", + name: "show_widget", + description: "Show Widget", + parameters: {}, + execute: widgetToolExecute, + })), }; }); @@ -45,11 +55,18 @@ vi.mock("./src/tool.js", () => ({ createCanvasTool: mocks.createCanvasTool, })); +vi.mock("./src/widget-tool.js", () => ({ + createShowWidgetTool: mocks.createShowWidgetTool, +})); + function registerCanvas() { const routes: Array[0]> = []; const services: Array[0]> = []; const resolvers: Array[0]> = []; - const tools: Array[0]> = []; + const tools: Array<{ + tool: Parameters[0]; + opts: Parameters[1]; + }> = []; const cliFeatures: Array<{ registrar: Parameters[0]; opts: Parameters[1]; @@ -62,7 +79,7 @@ function registerCanvas() { registerHttpRoute: (route) => routes.push(route), registerService: (service) => services.push(service), registerHostedMediaResolver: (resolver) => resolvers.push(resolver), - registerTool: (tool) => tools.push(tool), + registerTool: (tool, opts) => tools.push({ tool, opts }), registerNodeCliFeature: (registrar, opts) => cliFeatures.push({ registrar, opts }), registerNodeInvokePolicy: vi.fn(), }), @@ -97,11 +114,13 @@ describe("Canvas plugin entry", () => { const { resolvers, tools, cliFeatures } = registerCanvas(); expect(resolvers).toHaveLength(1); - expect(tools).toHaveLength(1); + expect(tools).toHaveLength(2); + expect(tools.map(({ opts }) => opts?.name)).toEqual([undefined, "show_widget"]); expect(cliFeatures).toHaveLength(1); expect(mocks.resolveCanvasHttpPathToLocalPath).not.toHaveBeenCalled(); expect(mocks.createDefaultCanvasCliDependencies).not.toHaveBeenCalled(); expect(mocks.createCanvasTool).not.toHaveBeenCalled(); + expect(mocks.createShowWidgetTool).not.toHaveBeenCalled(); await expect(resolvers[0]?.("/__openclaw__/canvas/documents/id/index.html")).resolves.toBe( "/tmp/canvas-asset", @@ -118,21 +137,42 @@ describe("Canvas plugin entry", () => { expect(mocks.createDefaultCanvasCliDependencies).toHaveBeenCalledTimes(1); expect(mocks.registerNodesCanvasCommands).toHaveBeenCalledTimes(1); - const toolFactory = tools[0]; - expect(typeof toolFactory).toBe("function"); - const tool = (toolFactory as Exclude)({ - config: {}, - workspaceDir: "/tmp/workspace", + const registeredTools = tools.map(({ tool: toolFactory }) => { + expect(typeof toolFactory).toBe("function"); + const tool = (toolFactory as Exclude)({ + config: {}, + workspaceDir: "/tmp/workspace", + sessionId: "session-1", + agentId: "agent-1", + }); + expect(Array.isArray(tool)).toBe(false); + return tool as AnyAgentTool; }); - expect(Array.isArray(tool)).toBe(false); - expect((tool as AnyAgentTool).name).toBe("canvas"); + expect(registeredTools.map((tool) => tool.name)).toEqual(["canvas", "show_widget"]); + expect(registeredTools[1]?.requiredClientCaps).toEqual(SHOW_WIDGET_REQUIRED_CLIENT_CAPS); expect(mocks.createCanvasTool).not.toHaveBeenCalled(); + expect(mocks.createShowWidgetTool).not.toHaveBeenCalled(); - await (tool as AnyAgentTool).execute("tool-call", { action: "hide" }); + const [canvasTool, showWidgetTool] = registeredTools; + await canvasTool?.execute("tool-call", { action: "hide" }); expect(mocks.createCanvasTool).toHaveBeenCalledWith({ config: {}, workspaceDir: "/tmp/workspace", }); expect(mocks.toolExecute).toHaveBeenCalledWith("tool-call", { action: "hide" }); + + await showWidgetTool?.execute("widget-call", { + title: "Status", + widget_code: "

ready

", + }); + expect(mocks.createShowWidgetTool).toHaveBeenCalledWith({ + config: {}, + sessionId: "session-1", + agentId: "agent-1", + }); + expect(mocks.widgetToolExecute).toHaveBeenCalledWith("widget-call", { + title: "Status", + widget_code: "

ready

", + }); }); }); diff --git a/extensions/codex/src/app-server/dynamic-tool-build.test.ts b/extensions/codex/src/app-server/dynamic-tool-build.test.ts index 5a9f3ba57b8..72c62da6d5d 100644 --- a/extensions/codex/src/app-server/dynamic-tool-build.test.ts +++ b/extensions/codex/src/app-server/dynamic-tool-build.test.ts @@ -280,6 +280,7 @@ describe("Codex app-server dynamic tool build", () => { const workspaceDir = path.join(tempDir, "workspace"); const params = createParams(path.join(tempDir, "session.jsonl"), workspaceDir); params.disableTools = false; + params.runtimePlan = createCodexRuntimePlanFixture(); params.clientCaps = ["tool-events", "inline-widgets"]; let receivedClientCaps: string[] | undefined; setOpenClawCodingToolsFactoryForTests((options) => { diff --git a/src/plugins/bundled-plugin-metadata.test.ts b/src/plugins/bundled-plugin-metadata.test.ts index 7756fd9cbe1..17f92b4f43f 100644 --- a/src/plugins/bundled-plugin-metadata.test.ts +++ b/src/plugins/bundled-plugin-metadata.test.ts @@ -36,6 +36,7 @@ const EXPECTED_BUNDLED_STARTUP_PLUGIN_IDS = [ "bonjour", "browser", "canvas", + "codex-supervisor", "device-pair", "diagnostics-otel", "diagnostics-prometheus",