import { describe, expect, test } from "bun:test"; import type { DynamicPageSurfaceData, UiSurfaceShowDynamicPage, } from "../daemon/message-protocol.js"; import { INTERACTIVE_SURFACE_TYPES } from "../daemon/message-protocol.js"; import { uiShowTool, uiUpdateTool } from "../tools/ui-surface/definitions.js"; // --------------------------------------------------------------------------- // DynamicPageSurfaceData shape // --------------------------------------------------------------------------- describe("DynamicPageSurfaceData shape", () => { test("accepts an object with html, width, and height", () => { const data: DynamicPageSurfaceData = { html: "

Hi

", width: 400, height: 300, }; expect(data.html).toBe("

Hi

"); expect(data.width).toBe(400); expect(data.height).toBe(300); }); test("width and height are optional", () => { const data: DynamicPageSurfaceData = { html: "

Hello

", }; expect(data.html).toBe("

Hello

"); expect(data.width).toBeUndefined(); expect(data.height).toBeUndefined(); }); }); // --------------------------------------------------------------------------- // Tool definition includes dynamic_page // --------------------------------------------------------------------------- describe("Tool definition includes dynamic_page", () => { test("input_schema surface_type enum includes dynamic_page", () => { const definition = uiShowTool; const surfaceTypeEnum = ( definition.input_schema as { properties: { surface_type: { enum: string[] } }; } ).properties.surface_type.enum; expect(surfaceTypeEnum).toContain("dynamic_page"); }); test("description mentions dynamic_page", () => { const definition = uiShowTool; expect(definition.description).toContain("dynamic_page"); }); }); // --------------------------------------------------------------------------- // Tool execution guard // --------------------------------------------------------------------------- describe("ui_show dynamic_page app substitute guard", () => { test("rejects dynamic_page when the model labels it as an app build", async () => { let proxied = false; const result = await uiShowTool.execute( { surface_type: "dynamic_page", title: "JARVIS 1020 Test Counter", activity: "Building the JARVIS 1020 Test Counter app", data: { html: "", preview: { title: "JARVIS 1020 Test Counter", subtitle: "Click INCREMENT to count up", }, }, }, { conversationId: "conversation-123", workingDir: "/tmp", trustClass: "guardian", proxyToolResolver: async () => { proxied = true; return { content: "proxied", isError: false }; }, }, ); expect(result.isError).toBe(true); expect(result.content).toContain('skill: "app-builder"'); expect(proxied).toBe(false); }); test("rejects dynamic_page with a clean title but substantial interactive html", async () => { let proxied = false; const result = await uiShowTool.execute( { surface_type: "dynamic_page", title: "Labor Market Stats", data: { html: "
", preview: { title: "Labor Market Stats" }, }, }, { conversationId: "conversation-123", workingDir: "/tmp", trustClass: "guardian", proxyToolResolver: async () => { proxied = true; return { content: "proxied", isError: false }; }, }, ); expect(result.isError).toBe(true); expect(result.content).toContain('skill: "app-builder"'); expect(proxied).toBe(false); }); test("rejects dynamic_page with empty data and does not proxy", async () => { let proxied = false; const result = await uiShowTool.execute( { surface_type: "dynamic_page", title: "SKILL.md — elevenlabs-tts", activity: "Reopening SKILL.md with rendered content", data: {}, }, { conversationId: "conversation-123", workingDir: "/tmp", trustClass: "guardian", proxyToolResolver: async () => { proxied = true; return { content: "proxied", isError: false }; }, }, ); expect(result.isError).toBe(true); expect(result.content).toContain("data.html"); expect(proxied).toBe(false); }); test("redirects a weak open model to a declarative surface, not 'resend HTML'", async () => { let proxied = false; const result = await uiShowTool.execute( { surface_type: "dynamic_page", title: "Fable 5 vs MiniMax M3", data: {}, }, { conversationId: "conversation-123", workingDir: "/tmp", trustClass: "guardian", attribution: { resolvedModel: "accounts/fireworks/models/minimax-m3", } as never, proxyToolResolver: async () => { proxied = true; return { content: "proxied", isError: false }; }, }, ); expect(result.isError).toBe(true); expect(result.content).toContain('surface_type: "table"'); expect(result.content).toContain("work_result"); expect(result.content).not.toContain("Resend ui_show with the full HTML"); expect(proxied).toBe(false); }); test("keeps the resend-HTML hint for a capable model", async () => { const result = await uiShowTool.execute( { surface_type: "dynamic_page", title: "Fable 5 vs MiniMax M3", data: {}, }, { conversationId: "conversation-123", workingDir: "/tmp", trustClass: "guardian", attribution: { resolvedModel: "claude-opus-4-8" } as never, proxyToolResolver: async () => ({ content: "proxied", isError: false }), }, ); expect(result.isError).toBe(true); expect(result.content).toContain("Resend ui_show with the full HTML"); expect(result.content).not.toContain('surface_type: "table"'); }); test("rejects dynamic_page with whitespace-only html and does not proxy", async () => { let proxied = false; const result = await uiShowTool.execute( { surface_type: "dynamic_page", title: "Blank", data: { html: " \n " }, }, { conversationId: "conversation-123", workingDir: "/tmp", trustClass: "guardian", proxyToolResolver: async () => { proxied = true; return { content: "proxied", isError: false }; }, }, ); expect(result.isError).toBe(true); expect(proxied).toBe(false); }); test("allows transient non-app dynamic_page surfaces", async () => { let proxied = false; const result = await uiShowTool.execute( { surface_type: "dynamic_page", title: "My Slides", data: { html: "

Hello

", preview: { title: "Slides", subtitle: "3 slides about Apple", }, }, }, { conversationId: "conversation-123", workingDir: "/tmp", trustClass: "guardian", proxyToolResolver: async () => { proxied = true; return { content: "proxied", isError: false }; }, }, ); expect(result).toEqual({ content: "proxied", isError: false }); expect(proxied).toBe(true); }); }); describe("ui_update empty payload guard", () => { function makeCtx(onProxy: () => void) { return { conversationId: "conversation-123", workingDir: "/tmp", trustClass: "guardian" as const, proxyToolResolver: async () => { onProxy(); return { content: "Surface updated", isError: false }; }, }; } test("rejects an empty data object and does not proxy", async () => { let proxied = false; const result = await uiUpdateTool.execute( { surface_id: "surf-1", data: {} }, makeCtx(() => { proxied = true; }), ); expect(result.isError).toBe(true); expect(result.content).toContain("empty `data` payload"); expect(proxied).toBe(false); }); test("rejects missing data and does not proxy", async () => { let proxied = false; const result = await uiUpdateTool.execute( { surface_id: "surf-1" }, makeCtx(() => { proxied = true; }), ); expect(result.isError).toBe(true); expect(proxied).toBe(false); }); test("rejects data containing only nested empty objects", async () => { let proxied = false; const result = await uiUpdateTool.execute( { surface_id: "surf-1", data: { templateData: {} } }, makeCtx(() => { proxied = true; }), ); expect(result.isError).toBe(true); expect(proxied).toBe(false); }); test("rejects a task_progress update with an empty steps array", async () => { let proxied = false; const result = await uiUpdateTool.execute( { surface_id: "surf-1", data: { templateData: { steps: [] } } }, makeCtx(() => { proxied = true; }), ); expect(result.isError).toBe(true); expect(proxied).toBe(false); }); test("allows a task_progress step update", async () => { let proxied = false; const result = await uiUpdateTool.execute( { surface_id: "surf-1", data: { templateData: { steps: [{ label: "Read memo", status: "completed" }], }, }, }, makeCtx(() => { proxied = true; }), ); expect(result.isError).toBe(false); expect(proxied).toBe(true); }); test("allows a status-only update without resending steps", async () => { let proxied = false; const result = await uiUpdateTool.execute( { surface_id: "surf-1", data: { templateData: { status: "completed" } } }, makeCtx(() => { proxied = true; }), ); expect(result.isError).toBe(false); expect(proxied).toBe(true); }); }); // --------------------------------------------------------------------------- // task_progress ui_show appends the update hint to its return value // --------------------------------------------------------------------------- describe("ui_show task_progress update hint", () => { const ctx = { conversationId: "conversation-123", workingDir: "/tmp", trustClass: "guardian" as const, proxyToolResolver: async () => ({ content: "Surface displayed (surface_id: surf-1).", isError: false, }), }; test("appends the ui_update hint after a task_progress card is shown", async () => { const result = await uiShowTool.execute( { surface_type: "card", template: "task_progress", templateData: { status: "in_progress", steps: [] }, }, ctx, ); expect(result.isError).toBe(false); expect(result.content).toContain("Surface displayed (surface_id: surf-1)."); expect(result.content).toContain("call ui_update with this surface_id"); }); test("recognizes task_progress nested under data", async () => { const result = await uiShowTool.execute( { surface_type: "card", data: { template: "task_progress", templateData: { status: "in_progress" }, }, }, ctx, ); expect(result.content).toContain("call ui_update with this surface_id"); }); test("does not append the hint for a non-task_progress card", async () => { const result = await uiShowTool.execute( { surface_type: "card", data: { title: "Plain", body: "hi" } }, ctx, ); expect(result.content).toBe("Surface displayed (surface_id: surf-1)."); }); test("does not append the hint when the surface call errors", async () => { const result = await uiShowTool.execute( { surface_type: "card", template: "task_progress", templateData: { status: "in_progress" }, }, { ...ctx, proxyToolResolver: async () => ({ content: "blocked on this channel", isError: true, }), }, ); expect(result.isError).toBe(true); expect(result.content).toBe("blocked on this channel"); }); }); // --------------------------------------------------------------------------- // UiSurfaceShowDynamicPage structure // --------------------------------------------------------------------------- describe("UiSurfaceShowDynamicPage structure", () => { test("can construct a well-typed UiSurfaceShowDynamicPage object", () => { const msg: UiSurfaceShowDynamicPage = { type: "ui_surface_show", conversationId: "session-abc", surfaceId: "surface-123", surfaceType: "dynamic_page", data: { html: "
Content
" }, }; expect(msg.type).toBe("ui_surface_show"); expect(msg.surfaceType).toBe("dynamic_page"); expect(typeof msg.data.html).toBe("string"); expect(msg.conversationId).toBe("session-abc"); expect(msg.surfaceId).toBe("surface-123"); }); }); // --------------------------------------------------------------------------- // Interactivity // --------------------------------------------------------------------------- describe("dynamic_page interactivity", () => { test("dynamic_page is in the interactive surface types list", () => { expect(INTERACTIVE_SURFACE_TYPES).toContain("dynamic_page"); }); });