diff --git a/.github/config-allowlist.txt b/.github/config-allowlist.txt index 2ec5891a14..d6a43a5140 100644 --- a/.github/config-allowlist.txt +++ b/.github/config-allowlist.txt @@ -87,6 +87,7 @@ packages/a2ui-renderer/tsdown.config.ts packages/agentcore-runner/tsdown.config.ts packages/core/tsdown.config.ts packages/demo-agents/tsdown.config.ts +packages/mcp-apps-renderer/tsdown.config.ts packages/react-core/tsdown.config.ts packages/react-native/tsdown.config.ts packages/react-textarea/tsdown.config.ts diff --git a/.github/workflows/static_bundle_size.yml b/.github/workflows/static_bundle_size.yml index 6673b61642..2d9add37cf 100644 --- a/.github/workflows/static_bundle_size.yml +++ b/.github/workflows/static_bundle_size.yml @@ -19,7 +19,7 @@ env: NX_VERBOSE_LOGGING: true jobs: - # Posts a per-PR comment with per-file gzip diffs for the 10 in-scope packages. + # Posts a per-PR comment with per-file gzip diffs for the 11 in-scope packages. # Runs preactjs/compressed-size-action, which builds the PR head and the base # branch, scans the `pattern` glob in each, and diffs the gzip sizes; the # comment updates in place on subsequent pushes. No hard-fail (Phase 1) — see @@ -64,9 +64,9 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} # Use the root `build` script (present on both this branch and the base # branch) so compressed-size-action can build both sides for comparison. - # The `pattern` below restricts measurement to the 10 in-scope packages. + # The `pattern` below restricts measurement to the 11 in-scope packages. build-script: build - pattern: "packages/{core,shared,react-core,react-ui,react-textarea,runtime-client-gql,web-inspector,voice,a2ui-renderer,react-native}/dist/**/*.{mjs,js,cjs}" + pattern: "packages/{core,shared,react-core,react-ui,react-textarea,runtime-client-gql,web-inspector,voice,a2ui-renderer,react-native,mcp-apps-renderer}/dist/**/*.{mjs,js,cjs}" # Measures what an app importing { CopilotChat } from # @copilotkit/react-core/v2 bundles, by driving esbuild over a synthetic entry diff --git a/.github/workflows/static_compat.yml b/.github/workflows/static_compat.yml index 6a86bb9e8e..928b475bde 100644 --- a/.github/workflows/static_compat.yml +++ b/.github/workflows/static_compat.yml @@ -49,9 +49,9 @@ jobs: - name: Build packages run: > npx nx run-many -t build - --projects=@copilotkit/core,@copilotkit/shared,@copilotkit/react-core,@copilotkit/react-ui,@copilotkit/react-textarea,@copilotkit/runtime-client-gql,@copilotkit/web-inspector,@copilotkit/voice,@copilotkit/a2ui-renderer + --projects=@copilotkit/core,@copilotkit/shared,@copilotkit/react-core,@copilotkit/react-ui,@copilotkit/react-textarea,@copilotkit/runtime-client-gql,@copilotkit/web-inspector,@copilotkit/voice,@copilotkit/a2ui-renderer,@copilotkit/mcp-apps-renderer - name: Run compat-check run: > npx nx run-many -t compat-check - --projects=@copilotkit/core,@copilotkit/shared,@copilotkit/react-core,@copilotkit/react-ui,@copilotkit/react-textarea,@copilotkit/runtime-client-gql,@copilotkit/web-inspector,@copilotkit/voice,@copilotkit/a2ui-renderer + --projects=@copilotkit/core,@copilotkit/shared,@copilotkit/react-core,@copilotkit/react-ui,@copilotkit/react-textarea,@copilotkit/runtime-client-gql,@copilotkit/web-inspector,@copilotkit/voice,@copilotkit/a2ui-renderer,@copilotkit/mcp-apps-renderer diff --git a/examples/v1/next-pages-router/next.config.mjs b/examples/v1/next-pages-router/next.config.mjs index d5456a15d4..8d688f7fa8 100644 --- a/examples/v1/next-pages-router/next.config.mjs +++ b/examples/v1/next-pages-router/next.config.mjs @@ -1,6 +1,12 @@ /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, + // Bundle the SDK's global KaTeX CSS for the Pages Router. + transpilePackages: ["@copilotkit/react-core"], + experimental: { + // react-syntax-highlighter's CommonJS entry loads ESM refractor languages. + esmExternals: "loose", + }, }; export default nextConfig; diff --git a/examples/v1/next-pages-router/package.json b/examples/v1/next-pages-router/package.json index 4dac686e68..8d2a39535c 100644 --- a/examples/v1/next-pages-router/package.json +++ b/examples/v1/next-pages-router/package.json @@ -18,7 +18,7 @@ "@google/generative-ai": "^0.11.2", "@heroicons/react": "^2.0.18", "clsx": "^1.2.1", - "next": "14.2.35", + "next": "15.5.24", "openai": "^4.85.1", "react": "^18", "react-dom": "^18" diff --git a/examples/v1/next-pages-router/pages/api/copilotkit.ts b/examples/v1/next-pages-router/pages/api/copilotkit.ts index b0add0eeb6..ff4771d0e3 100644 --- a/examples/v1/next-pages-router/pages/api/copilotkit.ts +++ b/examples/v1/next-pages-router/pages/api/copilotkit.ts @@ -3,11 +3,9 @@ import { OpenAIAdapter, copilotRuntimeNextJSPagesRouterEndpoint, } from "@copilotkit/runtime"; -import { NextApiRequest, NextApiResponse } from "next"; -import OpenAI from "openai"; +import type { NextApiRequest, NextApiResponse } from "next"; -const openai = new OpenAI(); -const serviceAdapter = new OpenAIAdapter({ openai }); +const serviceAdapter = new OpenAIAdapter(); const runtime = new CopilotRuntime({ actions: [ diff --git a/examples/v1/state-machine/package.json b/examples/v1/state-machine/package.json index 522803c338..52732549ba 100644 --- a/examples/v1/state-machine/package.json +++ b/examples/v1/state-machine/package.json @@ -15,7 +15,7 @@ "@copilotkit/runtime-client-gql": "workspace:*", "clsx": "^1.2.1", "motion": "^11.18.1", - "next": "14.2.35", + "next": "15.5.24", "react": "^18.3.1", "react-dom": "^18.3.1", "reactflow": "^11.11.4", diff --git a/examples/v1/travel/package.json b/examples/v1/travel/package.json index abb214e380..cb1937ca93 100644 --- a/examples/v1/travel/package.json +++ b/examples/v1/travel/package.json @@ -33,7 +33,7 @@ "groq-sdk": "^0.5.0", "leaflet": "^1.9.4", "lucide-react": "^0.414.0", - "next": "14.2.35", + "next": "15.5.24", "openai": "^4.85.1", "react": "18.3.1", "react-dom": "18.3.1", diff --git a/package.json b/package.json index 730fab57aa..ffdc56aa9c 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "vitest": "^4.1.3" }, "engines": { - "node": ">=18" + "node": ">=20" }, "packageManager": "pnpm@10.33.4", "pnpm": { @@ -106,21 +106,20 @@ "react-dom": "*" } }, + "//": "Next.js overrides keep each supported major on its security floor (GHSA-2xp9-vwfh-vxw4, GHSA-p293-qw3h-jr36). Next 14 examples declare Next 15 explicitly because neither advisory has a Next 14 fix.", "overrides": { "streamdown>react": "^19.0.0", "@types/react": "19.1.8", "@types/react-dom": "^19.0.2", "react": "19.2.3", "react-dom": "19.2.3", - "next@<=15.4.11": "15.4.11", - "next@>=15.5.0 <15.5.15": "15.5.15", + "next@>=15.0.0 <15.5.24": "15.5.24", + "next@>=16.0.0 <16.3.3": "16.3.3", "send@<=0.19.0": "0.19.0", "path-to-regexp@<=0.1.12": "0.1.13", "serve-static@<=1.16.0": "1.16.0", "prismjs@<=1.30.0": "1.30.0", - "pino@<=10.1.1": "10.1.1", "@copilotkit/license-verifier": "~0.5.0", - "next": "^16.0.10", "defu@<=6.1.4": ">=6.1.5", "minimatch": ">=9.0.6", "minimatch@>=10.0.0 <10.2.1": ">=10.2.1", @@ -169,7 +168,6 @@ "file-type": ">=21.3.1", "@langchain/community": ">=1.1.14", "langsmith": ">=0.5.18", - "next@>=16.0.0 <16.2.3": "16.2.3", "validator": ">=13.15.20", "markdown-it": ">=14.1.1", "mdast-util-to-hast@>=13.0.0 <13.2.1": ">=13.2.1", diff --git a/packages/agentcore-runner/package.json b/packages/agentcore-runner/package.json index 8aff2681a6..053239916a 100644 --- a/packages/agentcore-runner/package.json +++ b/packages/agentcore-runner/package.json @@ -44,6 +44,6 @@ "vitest": "^3.0.5" }, "engines": { - "node": ">=18" + "node": ">=20" } } diff --git a/packages/core/package.json b/packages/core/package.json index 63e64f9b2e..39fbdf8776 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -58,6 +58,6 @@ "zod": "^3.25.75" }, "engines": { - "node": ">=18" + "node": ">=20" } } diff --git a/packages/mcp-apps-renderer/README.md b/packages/mcp-apps-renderer/README.md new file mode 100644 index 0000000000..31a47cc930 --- /dev/null +++ b/packages/mcp-apps-renderer/README.md @@ -0,0 +1,45 @@ +# @copilotkit/mcp-apps-renderer + +Framework-agnostic MCP Apps host for CopilotKit: the app↔host protocol on top of +[`@modelcontextprotocol/ext-apps`](https://www.npmjs.com/package/@modelcontextprotocol/ext-apps) +(AppBridge + PostMessage transport, sandbox proxy, per-thread request queue, +`ui/message` / `ui/open-link` / `tools/call` proxy, tool input/result forwarding, +`ui/request-display-mode`). The React / Vue / Angular renderers consume it as thin +adapters: they create the sandbox iframe and wire reactive state, while all +protocol logic lives in `bindMcpApp`. + +## Entry points + +| Import | Contents | Bundle | +| ---------------------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- | +| `@copilotkit/mcp-apps-renderer` | `bindMcpApp` + the full session API | **ESM only** — it wraps the ESM-only ext-apps bridge, and is meant to be loaded lazily via a dynamic `import()`. | +| `@copilotkit/mcp-apps-renderer/activity` | `MCPAppsActivityType`, `MCPAppsActivityContentSchema`, `ɵrunMcpFollowUp` | ESM + CJS. Bridge-free: importing it to register the activity does **not** pull the ext-apps bundle. | + +The root is ESM-only on purpose: `@modelcontextprotocol/ext-apps` ships ESM only, +so a CommonJS root would emit a `require()` of an ES module and fail with +`ERR_REQUIRE_ESM`. Consume `bindMcpApp` via a dynamic `import()` (which resolves +ESM from any module system), and import the bridge-free `/activity` surface for +synchronous activity registration. + +## Script-tag / UMD usage + +This package also ships a UMD build of the bridge-free `/activity` entry: +`dist/activity.umd.js`, which defines the global +`CopilotKitMcpAppsRendererActivity`. + +`@copilotkit/react-core`'s UMD build references that global (it externalizes +`@copilotkit/mcp-apps-renderer/activity` to register the built-in MCP Apps +activity). **Script-tag consumers of react-core's UMD must therefore load +`activity.umd.js` before `@copilotkit/react-core`'s UMD bundle**, alongside the +other UMD globals it depends on (React, `CopilotKitCore`, +`CopilotKitA2UIRenderer`, …): + +```html + + + +``` + +Only the bridge-free `/activity` surface has a UMD build; the ext-apps bridge +itself (`bindMcpApp`) is loaded lazily via `import()` and is not part of the UMD +graph, so it only loads when an MCP App is actually rendered. diff --git a/packages/mcp-apps-renderer/package.json b/packages/mcp-apps-renderer/package.json new file mode 100644 index 0000000000..0c7754517c --- /dev/null +++ b/packages/mcp-apps-renderer/package.json @@ -0,0 +1,77 @@ +{ + "name": "@copilotkit/mcp-apps-renderer", + "version": "1.71.0", + "private": false, + "description": "Framework-agnostic MCP Apps host for CopilotKit - the app<->host protocol (ext-apps AppBridge), sandbox proxy, and shared renderer consumed by the React, Vue, and Angular frontends", + "keywords": [ + "ai", + "copilotkit", + "mcp", + "mcp-apps", + "ext-apps", + "renderer", + "ui" + ], + "homepage": "https://github.com/CopilotKit/CopilotKit", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/CopilotKit/CopilotKit.git", + "directory": "packages/mcp-apps-renderer" + }, + "files": [ + "dist" + ], + "type": "module", + "module": "./dist/index.mjs", + "types": "./dist/index.d.mts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + } + }, + "./activity": { + "import": { + "types": "./dist/activity.d.mts", + "default": "./dist/activity.mjs" + }, + "require": { + "types": "./dist/activity.d.cts", + "default": "./dist/activity.cjs" + } + }, + "./package.json": "./package.json" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsdown", + "compat-check": "es-check es2022 --module 'dist/**/!(*.umd).{mjs,cjs,js}' && es-check es2018 'dist/**/*.umd.js'", + "check-types": "tsc --noEmit -p tsconfig.json", + "test": "vitest run", + "test:watch": "vitest", + "publint": "publint .", + "attw": "attw --pack . --profile esm-only --entrypoints . && attw --pack . --profile node16 --exclude-entrypoints ." + }, + "dependencies": { + "@copilotkit/shared": "workspace:*", + "@modelcontextprotocol/ext-apps": "^1.7.5", + "@modelcontextprotocol/sdk": "^1.29.0" + }, + "peerDependencies": { + "@ag-ui/client": "0.0.59", + "zod": ">=3.25" + }, + "devDependencies": { + "@ag-ui/client": "0.0.59", + "@modelcontextprotocol/sdk": "^1.29.0", + "jsdom": "^26.1.0", + "tsdown": "^0.20.3", + "typescript": "5.9.2", + "vitest": "^3.2.4", + "zod": "^3.25.76" + } +} diff --git a/packages/mcp-apps-renderer/src/__tests__/sandbox.test.ts b/packages/mcp-apps-renderer/src/__tests__/sandbox.test.ts new file mode 100644 index 0000000000..51fb0f1a5a --- /dev/null +++ b/packages/mcp-apps-renderer/src/__tests__/sandbox.test.ts @@ -0,0 +1,77 @@ +import { describe, expect, it } from "vitest"; +import { LATEST_PROTOCOL_VERSION } from "@modelcontextprotocol/ext-apps"; +import { buildSandboxHTML } from "../sandbox"; +import { + MCP_OPEN_LINK_BLOCKED_SCHEMES, + MCPAppsActivityType, +} from "../constants"; +// MCP_APPS_PROTOCOL_VERSION lives on the bridge side (session) so the bridge-free +// `./constants` / `./activity` entry never pulls the ext-apps bundle. +import { MCP_APPS_PROTOCOL_VERSION } from "../session"; +import { MCPAppsActivityContentSchema } from "../content-schema"; + +describe("buildSandboxHTML", () => { + it("produces a sandbox proxy document that announces sandbox-proxy-ready", () => { + const html = buildSandboxHTML(); + expect(html).toContain(""); + expect(html).toContain("ui/notifications/sandbox-proxy-ready"); + expect(html).toContain("Content-Security-Policy"); + // relays the widget HTML on sandbox-resource-ready + expect(html).toContain("ui/notifications/sandbox-resource-ready"); + }); + + it("appends extra CSP domains to script-src and frame-src", () => { + const html = buildSandboxHTML(["https://example.com"]); + expect(html).toContain("https://example.com"); + }); +}); + +describe("constants", () => { + it("exposes the mcp-apps activity type", () => { + expect(MCPAppsActivityType).toBe("mcp-apps"); + }); + + it("sources the protocol version from ext-apps (no hand-maintained literal)", () => { + // MCP_APPS_PROTOCOL_VERSION is a re-export of the bridge's own + // LATEST_PROTOCOL_VERSION, so it can never drift from the spec version. + expect(MCP_APPS_PROTOCOL_VERSION).toBe(LATEST_PROTOCOL_VERSION); + }); + + it("negotiates the expected current protocol version (2026-01-26 canary)", () => { + // Canary: if ext-apps bumps the version, this fails so we review the change. + expect(MCP_APPS_PROTOCOL_VERSION).toBe("2026-01-26"); + }); + + it("blocks script/HTML-executing schemes for ui/open-link", () => { + for (const scheme of [ + "javascript:", + "data:", + "vbscript:", + "blob:", + "file:", + ]) { + expect(MCP_OPEN_LINK_BLOCKED_SCHEMES.has(scheme)).toBe(true); + } + expect(MCP_OPEN_LINK_BLOCKED_SCHEMES.has("https:")).toBe(false); + }); +}); + +describe("content schema", () => { + it("validates a well-formed activity content", () => { + const parsed = MCPAppsActivityContentSchema.safeParse({ + result: { content: [{ type: "text", text: "ok" }], isError: false }, + resourceUri: "ui://server/dashboard", + serverHash: "abc123", + toolInput: { q: "hi" }, + }); + expect(parsed.success).toBe(true); + }); + + it("rejects content missing resourceUri", () => { + const parsed = MCPAppsActivityContentSchema.safeParse({ + result: {}, + serverHash: "abc123", + }); + expect(parsed.success).toBe(false); + }); +}); diff --git a/packages/mcp-apps-renderer/src/__tests__/session.e2e.test.ts b/packages/mcp-apps-renderer/src/__tests__/session.e2e.test.ts new file mode 100644 index 0000000000..67bd0dcacc --- /dev/null +++ b/packages/mcp-apps-renderer/src/__tests__/session.e2e.test.ts @@ -0,0 +1,358 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import type { AbstractAgent } from "@ag-ui/client"; +import { bindMcpApp } from "../session"; +import type { McpAppSession } from "../session"; +import type { MCPAppsActivityContent } from "../content-schema"; + +// --------------------------------------------------------------------------- +// Minimal agent mock: only what bindMcpApp + the request queue touch. +// --------------------------------------------------------------------------- +function makeAgent(overrides?: Partial>) { + const addMessageCalls: Array<{ id: string; role: string; content: string }> = + []; + const runAgentCalls: Array = []; + const agent = { + agentId: "test-agent", + threadId: "thread-1", + isRunning: false, + addMessageCalls, + runAgentCalls, + addMessage(msg: { id: string; role: string; content: string }) { + addMessageCalls.push(msg); + }, + subscribe() { + return { unsubscribe() {} }; + }, + async runAgent(input?: any) { + runAgentCalls.push(input); + const req = input?.forwardedProps?.__proxiedMCPRequest; + if (req?.method === "resources/read") { + return { + result: { + contents: [ + { + uri: req.params?.uri, + mimeType: "text/html", + text: "Widget", + }, + ], + }, + newMessages: [], + }; + } + if (req?.method === "tools/call") { + return { + result: { + content: [{ type: "text", text: "tool ok" }], + isError: false, + }, + newMessages: [], + }; + } + return { result: {}, newMessages: [] }; + }, + ...overrides, + }; + return agent as unknown as AbstractAgent & { + addMessageCalls: typeof addMessageCalls; + runAgentCalls: typeof runAgentCalls; + }; +} + +function makeContent( + over?: Partial, +): MCPAppsActivityContent { + return { + resourceUri: "ui://test/app", + serverHash: "hash-123", + result: { content: [], isError: false }, + toolInput: {}, + ...over, + } as MCPAppsActivityContent; +} + +const tick = (ms = 30) => new Promise((r) => setTimeout(r, ms)); + +/** Dispatch a JSON-RPC message from the iframe (source = its contentWindow). */ +function fromIframe(iframe: HTMLIFrameElement, data: unknown) { + window.dispatchEvent( + new MessageEvent("message", { + data, + source: iframe.contentWindow, + origin: "", + }), + ); +} + +/** Capture messages the host posts back to the iframe. */ +function captureOutgoing(iframe: HTMLIFrameElement) { + const captured: any[] = []; + const cw = iframe.contentWindow as Window; + const orig = cw.postMessage.bind(cw); + cw.postMessage = ((message: unknown, ...args: unknown[]) => { + captured.push(message); + return (orig as any)(message, ...args); + }) as typeof cw.postMessage; + return captured; +} + +let sessions: McpAppSession[] = []; +let iframes: HTMLIFrameElement[] = []; + +function mount() { + const iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + iframes.push(iframe); + return iframe; +} + +/** + * Bind + wait for setup (resource fetch + connect) + simulate the sandbox proxy + * ready handshake, mirroring the react-core MCP e2e harness. + */ +async function bindAndConnect( + iframe: HTMLIFrameElement, + agent: AbstractAgent, + content = makeContent(), + hooks?: Parameters[0]["hooks"], +) { + const session = bindMcpApp({ + iframe, + getContent: () => content, + getAgent: () => agent, + host: { runAgent: async () => ({ result: undefined, newMessages: [] }) }, + hooks, + }); + sessions.push(session); + await tick(60); // let fetchResource + bridge.connect settle + const captured = captureOutgoing(iframe); + fromIframe(iframe, { + jsonrpc: "2.0", + method: "ui/notifications/sandbox-proxy-ready", + }); + await tick(30); + return { session, captured }; +} + +afterEach(() => { + sessions.forEach((s) => s.teardown()); + sessions = []; + iframes.forEach((f) => f.remove()); + iframes = []; + vi.restoreAllMocks(); +}); + +describe("bindMcpApp", () => { + it("fetches the resource through the agent and loads the sandbox", async () => { + const agent = makeAgent(); + const iframe = mount(); + await bindAndConnect(iframe, agent); + + const readCall = agent.runAgentCalls.find( + (c) => + c?.forwardedProps?.__proxiedMCPRequest?.method === "resources/read", + ); + expect(readCall).toBeDefined(); + expect(iframe.getAttribute("data-testid")).toBe("mcp-app-iframe"); + expect(iframe.srcdoc).toContain("sandbox-proxy-ready"); + }); + + it("proxies tools/call through the agent and returns the result to the iframe", async () => { + const agent = makeAgent(); + const iframe = mount(); + const { captured } = await bindAndConnect(iframe, agent); + + const id = "call-1"; + fromIframe(iframe, { + jsonrpc: "2.0", + id, + method: "tools/call", + params: { name: "do_thing", arguments: { a: 1 } }, + }); + await tick(40); + + const toolCall = agent.runAgentCalls.find( + (c) => c?.forwardedProps?.__proxiedMCPRequest?.method === "tools/call", + ); + expect(toolCall).toBeDefined(); + const response = captured.find((m) => m && m.id === id && "result" in m); + expect(response?.result?.content?.[0]?.text).toBe("tool ok"); + }); + + it("opens https links and blocks javascript: links (ui/open-link)", async () => { + const agent = makeAgent(); + const iframe = mount(); + const { captured } = await bindAndConnect(iframe, agent); + const openSpy = vi.spyOn(window, "open").mockImplementation(() => null); + + fromIframe(iframe, { + jsonrpc: "2.0", + id: "ok", + method: "ui/open-link", + params: { url: "https://example.com" }, + }); + await tick(20); + expect(openSpy).toHaveBeenCalledWith( + "https://example.com", + "_blank", + "noopener,noreferrer", + ); + + openSpy.mockClear(); + fromIframe(iframe, { + jsonrpc: "2.0", + id: "bad", + // eslint-disable-next-line no-script-url + method: "ui/open-link", + params: { url: "javascript:alert(1)" }, + }); + await tick(20); + expect(openSpy).not.toHaveBeenCalled(); + const badResp = captured.find((m) => m && m.id === "bad" && "result" in m); + expect(badResp?.result).toMatchObject({ isError: true }); + }); + + it("adds a ui/message to the agent (role from _meta.copilotkit)", async () => { + const agent = makeAgent(); + const iframe = mount(); + await bindAndConnect(iframe, agent); + + fromIframe(iframe, { + jsonrpc: "2.0", + id: "msg-1", + method: "ui/message", + params: { + content: [{ type: "text", text: "hello from widget" }], + _meta: { copilotkit: { role: "assistant", followUp: false } }, + }, + }); + await tick(30); + + const call = agent.addMessageCalls.find( + (c) => c.content === "hello from widget", + ); + expect(call).toBeDefined(); + expect(call?.role).toBe("assistant"); + }); + + it("fires onInitialized when the widget reports initialized", async () => { + const agent = makeAgent(); + const iframe = mount(); + const onInitialized = vi.fn(); + const { session } = await bindAndConnect(iframe, agent, makeContent(), { + onInitialized, + }); + + fromIframe(iframe, { + jsonrpc: "2.0", + method: "ui/notifications/initialized", + }); + await tick(20); + expect(onInitialized).toHaveBeenCalled(); + + // tool input pushed after initialize reaches the iframe + session.sendToolInput({ a: 1 }); + await tick(10); + }); +}); + +// --------------------------------------------------------------------------- +// ui/initialize is handled entirely by the ext-apps AppBridge that bindMcpApp +// constructs. These tests pin the negotiation contract at the package level (the +// compile-time tie to the spec the extraction argues for), because the host +// setup lives here now: +// - the bridge validates params against the spec schema, so a widget that omits +// the required fields (e.g. appCapabilities) fails initialize with -32603; +// - the host advertises only the latest MCP Apps protocol version, so a widget +// declaring a different version string gets the host version back; +// - the host context seeded at AppBridge construction is advertised at +// initialize (deterministic, not a post-connect race). +// --------------------------------------------------------------------------- +describe("bindMcpApp ui/initialize negotiation", () => { + const LATEST_PROTOCOL_VERSION = "2026-01-26"; + + it("negotiates and returns the host context for a well-formed initialize", async () => { + const agent = makeAgent(); + const iframe = mount(); + const { captured } = await bindAndConnect(iframe, agent); + + const reqId = "init-ok"; + fromIframe(iframe, { + jsonrpc: "2.0", + id: reqId, + method: "ui/initialize", + params: { + appInfo: { name: "test-widget", version: "1.0.0" }, + appCapabilities: {}, + protocolVersion: LATEST_PROTOCOL_VERSION, + }, + }); + await tick(30); + + const response = captured.find( + (m) => m && m.jsonrpc === "2.0" && m.id === reqId && m.result, + ); + expect(response).toBeDefined(); + expect(response).not.toHaveProperty("error"); + // Protocol version is negotiated to the host's latest. + expect(response.result.protocolVersion).toBe(LATEST_PROTOCOL_VERSION); + // Host context seeded at construction is advertised at initialize. + expect(response.result.hostContext).toMatchObject({ + theme: "light", + platform: "web", + }); + }); + + it("rejects an initialize that omits required fields with -32603", async () => { + const agent = makeAgent(); + const iframe = mount(); + const { captured } = await bindAndConnect(iframe, agent); + + // Empty params: no appInfo / appCapabilities / protocolVersion. The bridge + // validates against the spec schema and rejects before any host handler. + const reqId = "init-bad"; + fromIframe(iframe, { + jsonrpc: "2.0", + id: reqId, + method: "ui/initialize", + params: {}, + }); + await tick(30); + + const errorResponse = captured.find( + (m) => m && m.jsonrpc === "2.0" && m.id === reqId && m.error, + ); + expect(errorResponse).toBeDefined(); + expect(errorResponse.error.code).toBe(-32603); + expect(typeof errorResponse.error.message).toBe("string"); + expect(errorResponse.error.message.length).toBeGreaterThan(0); + }); + + it("returns the host protocol version, not the widget's, when they differ", async () => { + const agent = makeAgent(); + const iframe = mount(); + const { captured } = await bindAndConnect(iframe, agent); + + // "2025-06-18" is a base-MCP-protocol version (and what the old hand-rolled + // Vue/Angular hosts hardcode). The bridge supports only its own MCP Apps + // version and returns that, rather than echoing the widget's. + const reqId = "init-version"; + fromIframe(iframe, { + jsonrpc: "2.0", + id: reqId, + method: "ui/initialize", + params: { + appInfo: { name: "legacy-widget", version: "1.0.0" }, + appCapabilities: {}, + protocolVersion: "2025-06-18", + }, + }); + await tick(30); + + const response = captured.find( + (m) => m && m.jsonrpc === "2.0" && m.id === reqId && m.result, + ); + expect(response).toBeDefined(); + expect(response.result.protocolVersion).toBe(LATEST_PROTOCOL_VERSION); + }); +}); diff --git a/packages/mcp-apps-renderer/src/activity.ts b/packages/mcp-apps-renderer/src/activity.ts new file mode 100644 index 0000000000..307e565538 --- /dev/null +++ b/packages/mcp-apps-renderer/src/activity.ts @@ -0,0 +1,11 @@ +// Lightweight, bridge-free surface for MCP Apps activity registration. +// +// This entry re-exports ONLY the pieces that have no dependency on the ext-apps +// AppBridge (the ~40-50 kB gzipped MCP SDK + zod protocol bundle): the activity +// type + content schema (for the host's activity registry) and the ui/message +// follow-up runner. Frontends import these statically to register the MCP-apps +// activity, then load `bindMcpApp` from the package root (`.`) lazily via a +// dynamic import so a non-MCP app never pays for the bridge. +export * from "./constants"; +export * from "./content-schema"; +export * from "./follow-up"; diff --git a/packages/mcp-apps-renderer/src/constants.ts b/packages/mcp-apps-renderer/src/constants.ts new file mode 100644 index 0000000000..88c912381e --- /dev/null +++ b/packages/mcp-apps-renderer/src/constants.ts @@ -0,0 +1,26 @@ +/** + * Activity type for MCP Apps events - must match the middleware's MCPAppsActivityType. + */ +export const MCPAppsActivityType = "mcp-apps"; + +/** + * URL schemes a widget may NOT open via ui/open-link. The ext-apps schema + * validates `url` as a plain string only (noopener/noreferrer does not restrict + * the scheme), so ui/open-link could otherwise become an XSS vector. + * + * We use a denylist rather than an allowlist on purpose: deep links use + * arbitrary, app-defined schemes (`myapp:`, `whatsapp:`, `slack:`, `spotify:`, + * `sms:`, ...) that an allowlist could never enumerate, and `window.open`ing them + * just hands off to an OS handler - it does not execute script in the page, so + * it is not an XSS risk. Universal links / App Links are plain `https:` URLs and + * pass regardless. What IS dangerous is the small, well-known set of schemes + * that execute script or render attacker HTML in the page context; block those + * and allow everything else (including deep links). + */ +export const MCP_OPEN_LINK_BLOCKED_SCHEMES = new Set([ + "javascript:", + "data:", + "vbscript:", + "blob:", + "file:", +]); diff --git a/packages/mcp-apps-renderer/src/content-schema.ts b/packages/mcp-apps-renderer/src/content-schema.ts new file mode 100644 index 0000000000..b1471726f1 --- /dev/null +++ b/packages/mcp-apps-renderer/src/content-schema.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; + +/** + * Zod schema for MCP Apps activity content (middleware 0.0.2 format). The + * framework activity registries validate the activity content with this before + * handing it to the renderer/session. + */ +export const MCPAppsActivityContentSchema = z.object({ + result: z.object({ + content: z.array(z.any()).optional(), + structuredContent: z.any().optional(), + isError: z.boolean().optional(), + }), + // Resource URI to fetch (e.g., "ui://server/dashboard") + resourceUri: z.string(), + // MD5 hash of server config (renamed from serverId in 0.0.1) + serverHash: z.string(), + // Optional stable server ID from config (takes precedence over serverHash) + serverId: z.string().optional(), + // Original tool input arguments + toolInput: z.record(z.string(), z.unknown()).optional(), +}); + +export type MCPAppsActivityContent = z.infer< + typeof MCPAppsActivityContentSchema +>; diff --git a/packages/mcp-apps-renderer/src/follow-up.ts b/packages/mcp-apps-renderer/src/follow-up.ts new file mode 100644 index 0000000000..22a6625e3b --- /dev/null +++ b/packages/mcp-apps-renderer/src/follow-up.ts @@ -0,0 +1,51 @@ +import type { AbstractAgent, RunAgentResult } from "@ag-ui/client"; + +/** + * The subset of `CopilotKitCore` that {@link ɵrunMcpFollowUp} depends on. + * Declared structurally so the runner can be unit-tested without a full core. + */ +export interface ɵMcpFollowUpHost { + runAgent(params: { agent: AbstractAgent }): Promise; +} + +/** + * Run an MCP app `ui/message` follow-up, scoped to the thread it was enqueued + * for (issue #5819). + * + * The MCP request queue delays follow-up work until the agent is idle. There is + * a single shared registry agent per id, and switching threads overwrites its + * `threadId`/`messages` in place. So if the host switches threads while a + * follow-up is queued, running it now would execute against — and stream into — + * the now-foreground thread. + * + * - **Same thread** (the common case): run on the shared agent, unchanged. + * - **Thread changed**: the shared agent has moved on, so the follow-up can no + * longer run in its originating thread's context. Drop it rather than leak it + * into the current thread. (The MCP app already received its `ui/message` ack + * at enqueue time; only the optional agent turn is skipped.) + * + * @internal exported for testing. + */ +export async function ɵrunMcpFollowUp({ + host, + agent, + capturedThreadId, +}: { + host: ɵMcpFollowUpHost; + agent: AbstractAgent; + capturedThreadId: string; +}): Promise { + const currentThreadId = agent.threadId || "default"; + const originThreadId = capturedThreadId || "default"; + + if (currentThreadId === originThreadId) { + return host.runAgent({ agent }); + } + + console.warn( + "[MCPAppsRenderer] ui/message follow-up dropped: the thread changed " + + `(${originThreadId} → ${currentThreadId}) between enqueue and execution, ` + + "so running it would leak into the now-foreground thread.", + ); + return { result: undefined, newMessages: [] }; +} diff --git a/packages/mcp-apps-renderer/src/index.ts b/packages/mcp-apps-renderer/src/index.ts new file mode 100644 index 0000000000..5016520c20 --- /dev/null +++ b/packages/mcp-apps-renderer/src/index.ts @@ -0,0 +1,14 @@ +// @copilotkit/mcp-apps-renderer - framework-agnostic MCP Apps host. +// +// This package owns the app<->host protocol (ext-apps AppBridge), the sandbox +// proxy, the per-thread request queue, ui/message extensions, and the shared +// content schema. The React/Vue/Angular renderers consume it as thin adapters +// (they only mount the iframe and wire reactive state); ext-apps + the MCP SDK +// are declared here and nowhere else. + +export * from "./constants"; +export * from "./content-schema"; +export * from "./sandbox"; +export * from "./request-queue"; +export * from "./follow-up"; +export * from "./session"; diff --git a/packages/mcp-apps-renderer/src/request-queue.ts b/packages/mcp-apps-renderer/src/request-queue.ts new file mode 100644 index 0000000000..806e2183eb --- /dev/null +++ b/packages/mcp-apps-renderer/src/request-queue.ts @@ -0,0 +1,131 @@ +import type { AbstractAgent, RunAgentResult } from "@ag-ui/client"; + +/** + * Queue for serializing MCP app requests to an agent. + * Ensures requests wait for the agent to stop running and are processed one at a time. + */ +export class MCPAppsRequestQueue { + private queues = new Map< + string, + Array<{ + execute: () => Promise; + resolve: (result: RunAgentResult) => void; + reject: (error: Error) => void; + }> + >(); + private processing = new Map(); + + /** + * Add a request to the queue for a specific agent thread. + * Returns a promise that resolves when the request completes. + */ + async enqueue( + agent: AbstractAgent, + request: () => Promise, + ): Promise { + const threadId = agent.threadId || "default"; + + return new Promise((resolve, reject) => { + // Get or create queue for this thread + let queue = this.queues.get(threadId); + if (!queue) { + queue = []; + this.queues.set(threadId, queue); + } + + // Add request to queue + queue.push({ execute: request, resolve, reject }); + + // Start processing if not already running + this.processQueue(threadId, agent); + }); + } + + /** + * Drain a thread's queue one request at a time, waiting for the agent to go + * idle before each. Re-entrant-safe (a single processor per thread) and drops + * the thread's map entries once fully drained to keep the shared queue bounded. + */ + private async processQueue( + threadId: string, + agent: AbstractAgent, + ): Promise { + // If already processing this queue, return + if (this.processing.get(threadId)) { + return; + } + + this.processing.set(threadId, true); + + try { + const queue = this.queues.get(threadId); + if (!queue) return; + + while (queue.length > 0) { + const item = queue[0]!; + + try { + // Wait for any active run to complete before processing + await this.waitForAgentIdle(agent); + + // Execute the request + const result = await item.execute(); + item.resolve(result); + } catch (error) { + item.reject( + error instanceof Error ? error : new Error(String(error)), + ); + } + + // Remove processed item + queue.shift(); + } + } finally { + // Drop the drained thread entries from both maps. `mcpAppsRequestQueue` is + // shared for the page lifetime, so retaining an entry per thread id would + // grow unbounded as threads come and go. + const queue = this.queues.get(threadId); + if (!queue || queue.length === 0) { + this.queues.delete(threadId); + this.processing.delete(threadId); + } else { + this.processing.set(threadId, false); + } + } + } + + /** + * Resolve once the agent is not running. Subscribes to run-finalized/failed and + * also polls as a fallback for reconnect scenarios where events do not fire. + */ + private waitForAgentIdle(agent: AbstractAgent): Promise { + return new Promise((resolve) => { + if (!agent.isRunning) { + resolve(); + return; + } + + let done = false; + const finish = () => { + if (done) return; + done = true; + clearInterval(checkInterval); + sub.unsubscribe(); + resolve(); + }; + + const sub = agent.subscribe({ + onRunFinalized: finish, + onRunFailed: finish, + }); + + // Fallback for reconnect scenarios where events don't fire + const checkInterval = setInterval(() => { + if (!agent.isRunning) finish(); + }, 500); + }); + } +} + +// Shared per-thread queue instance for all MCP app requests. +export const mcpAppsRequestQueue = new MCPAppsRequestQueue(); diff --git a/packages/mcp-apps-renderer/src/sandbox.ts b/packages/mcp-apps-renderer/src/sandbox.ts new file mode 100644 index 0000000000..19d492cc04 --- /dev/null +++ b/packages/mcp-apps-renderer/src/sandbox.ts @@ -0,0 +1,51 @@ +/** + * Build the sandbox-proxy HTML document loaded into the outer iframe. + * + * The proxy relays postMessage between the host and the inner sandboxed widget + * and announces `ui/notifications/sandbox-proxy-ready` once ready. `extraCspDomains` + * (from the resource's `_meta.ui.csp`) are appended to the `script-src`/`frame-src` + * CSP directives so a widget can load its own approved origins. + * + * @param extraCspDomains Optional additional origins allowed by the sandbox CSP. + * @returns The complete sandbox-proxy HTML document as a string. + */ +export function buildSandboxHTML(extraCspDomains?: string[]): string { + const baseScriptSrc = + "'self' 'wasm-unsafe-eval' 'unsafe-inline' 'unsafe-eval' blob: data: http://localhost:* https://localhost:*"; + const baseFrameSrc = "* blob: data: http://localhost:* https://localhost:*"; + const extra = extraCspDomains?.length ? " " + extraCspDomains.join(" ") : ""; + const scriptSrc = baseScriptSrc + extra; + const frameSrc = baseFrameSrc + extra; + + return ` + + + + + + + + + +`; +} diff --git a/packages/mcp-apps-renderer/src/session.ts b/packages/mcp-apps-renderer/src/session.ts new file mode 100644 index 0000000000..7254fec9c5 --- /dev/null +++ b/packages/mcp-apps-renderer/src/session.ts @@ -0,0 +1,382 @@ +import { + AppBridge, + LATEST_PROTOCOL_VERSION, + PostMessageTransport, +} from "@modelcontextprotocol/ext-apps/app-bridge"; +import type { AbstractAgent } from "@ag-ui/client"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { z } from "zod"; +import { randomUUID } from "@copilotkit/shared"; +import { buildSandboxHTML } from "./sandbox"; +import { mcpAppsRequestQueue } from "./request-queue"; +import { ɵrunMcpFollowUp } from "./follow-up"; +import type { ɵMcpFollowUpHost } from "./follow-up"; +import { MCP_OPEN_LINK_BLOCKED_SCHEMES } from "./constants"; +import type { MCPAppsActivityContent } from "./content-schema"; + +/** + * The MCP Apps protocol version this host negotiates. Sourced directly from the + * ext-apps bridge (single source of truth, no hand-maintained literal). It lives + * here (a bridge-side module) rather than in the bridge-free `./constants` / + * `./activity` entry so the lightweight activity-registration surface stays free + * of the ext-apps bundle; consumers that need the version import it from the + * package root, which already loads the bridge. + */ +export const MCP_APPS_PROTOCOL_VERSION = LATEST_PROTOCOL_VERSION; + +/** + * Permissive `ui/message` schema. ext-apps restricts the request to + * `role: "user"` with no `followUp`, but CopilotKit intentionally extends + * `ui/message` with `role` ("user" | "assistant") and `followUp` (documented + * behavior with dedicated tests). We register our own handler (instead of the + * bridge's strict `onmessage`) so those extensions survive. + * + * Going forward, widgets SHOULD pass the extensions under + * `params._meta.copilotkit`; the top-level `role`/`followUp` fields are the + * legacy channel, kept for backward compatibility and slated for deprecation. + */ +const CopilotKitUiMessageSchema = z.object({ + method: z.literal("ui/message"), + params: z + .object({ + role: z.string().optional(), + content: z.array(z.any()).optional(), + followUp: z.boolean().optional(), + _meta: z.record(z.string(), z.any()).optional(), + }) + .passthrough(), +}); + +/** A resource fetched from the MCP server via the agent proxy. */ +export interface FetchedResource { + uri: string; + mimeType?: string; + text?: string; + blob?: string; + _meta?: { + ui?: { + prefersBorder?: boolean; + csp?: { + connectDomains?: string[]; + resourceDomains?: string[]; + }; + }; + }; +} + +/** Reactive callbacks the framework adapter wires to its own state. */ +export interface McpAppSessionHooks { + /** The widget reported a new content size (ui/notifications/size-changed). */ + onSizeChanged?(size: { width?: number; height?: number }): void; + /** The widget finished initializing (safe to push tool input/result). */ + onInitialized?(): void; + /** The fetched resource metadata (e.g. prefersBorder) is available. */ + onResource?(resource: FetchedResource): void; + /** Setup failed (resource fetch, connect, ...). */ + onError?(err: Error): void; +} + +export interface BindMcpAppOptions { + /** + * The sandbox iframe. The adapter creates and OWNS this element (mounts it in + * its render model, sizes it, removes it on unmount). The session only + * configures the sandbox contract (sandbox attr, testid, srcdoc) and talks to + * it through the bridge - it never creates, moves, or removes the iframe. + */ + iframe: HTMLIFrameElement; + /** Returns the current activity content (resourceUri, serverHash, tool input/result). */ + getContent: () => MCPAppsActivityContent; + /** Returns the current agent (may change across renders). */ + getAgent: () => AbstractAgent | undefined; + /** CopilotKit host, for ui/message follow-up runs (issue #5819). */ + host: ɵMcpFollowUpHost; + hooks?: McpAppSessionHooks; +} + +export interface McpAppSession { + /** Forward the tool call input to the widget (host -> app). Buffered until ready. */ + sendToolInput(args: Record): void; + /** Forward the tool result to the widget (host -> app). Buffered until ready. */ + sendToolResult(result: CallToolResult): void; + /** Disconnect the bridge and release listeners. Does NOT remove the iframe. */ + teardown(): void; +} + +/** + * Bind an MCP App to a host-provided sandbox iframe: fetch the widget resource + * through the agent, connect the ext-apps `AppBridge` over a PostMessage + * transport, and wire the app<->host protocol (ui/message, ui/open-link, + * tools/call + resources/read proxy, size, host context). Framework-agnostic: + * the React/Vue/Angular renderers create the iframe and wire reactive state via + * `hooks`, but all protocol logic lives here. + */ +export function bindMcpApp(opts: BindMcpAppOptions): McpAppSession { + const { iframe, getContent, getAgent, host, hooks } = opts; + + let disposed = false; + let ready = false; + let bridge: AppBridge | null = null; + let pendingToolInput: Record | undefined; + let pendingToolResult: CallToolResult | undefined; + + /** Flush any buffered tool input/result to the widget once it is initialized. */ + const flushPending = () => { + if (!ready || !bridge) return; + if (pendingToolInput !== undefined) { + void bridge.sendToolInput({ arguments: pendingToolInput }); + pendingToolInput = undefined; + } + if (pendingToolResult !== undefined) { + void bridge.sendToolResult(pendingToolResult); + pendingToolResult = undefined; + } + }; + + /** Fetch the widget resource (`resources/read`) through the agent proxy queue. */ + const fetchResource = async (): Promise => { + const agent = getAgent(); + if (!agent) { + throw new Error("No agent available to fetch resource"); + } + const { resourceUri, serverHash, serverId } = getContent(); + const runResult = await mcpAppsRequestQueue.enqueue(agent, () => + agent.runAgent({ + forwardedProps: { + __proxiedMCPRequest: { + serverHash, + serverId, + method: "resources/read", + params: { uri: resourceUri }, + }, + }, + }), + ); + const resultData = runResult.result as + | { contents?: FetchedResource[] } + | undefined; + const resource = resultData?.contents?.[0]; + if (!resource) { + throw new Error("No resource content in response"); + } + return resource; + }; + + /** + * Fetch the resource, configure + load the sandbox iframe, construct the + * AppBridge, wire the app->host handlers, and connect the transport. + */ + const setup = async () => { + try { + const resource = await fetchResource(); + if (disposed) return; + hooks?.onResource?.(resource); + + // Configure the sandbox iframe (contract shared across frontends). + iframe.setAttribute( + "sandbox", + "allow-scripts allow-same-origin allow-forms", + ); + // Cross-frontend MCP-apps surface contract: every frontend must expose the + // sandbox iframe under the SAME testid so one shared probe (harness + // `d5-mcp-apps`) and one shared e2e spec can assert the surface mounted + // without per-frontend selectors. + iframe.setAttribute("data-testid", "mcp-app-iframe"); + iframe.setAttribute("title", "Interactive MCP application"); + + const cspDomains = resource._meta?.ui?.csp?.resourceDomains; + iframe.srcdoc = buildSandboxHTML(cspDomains); + + const win = iframe.contentWindow; + if (!win) { + throw new Error("Sandbox iframe has no contentWindow"); + } + + let html: string; + if (resource.text) { + html = resource.text; + } else if (resource.blob) { + html = atob(resource.blob); + } else { + throw new Error("Resource has no text or blob content"); + } + + bridge = new AppBridge( + null, + { name: "CopilotKit MCP Apps Host", version: "1.0.0" }, + { openLinks: {}, logging: {}, message: { text: {} } }, + // Seed the host context at construction so it is already in place when + // the widget's ui/initialize is handled (deterministic, not a race). + { hostContext: { theme: "light", platform: "web" } }, + ); + + // Sandbox handshake: on proxy ready, load the widget HTML into the inner + // sandboxed iframe. + bridge.onsandboxready = () => { + void bridge?.sendSandboxResourceReady({ html }); + }; + + // --- App -> host requests --- + // ui/message: custom handler preserving CopilotKit role/followUp extensions + // (via _meta.copilotkit first, then legacy top-level fields). + bridge.setRequestHandler(CopilotKitUiMessageSchema, async (req) => { + const currentAgent = getAgent(); + if (!currentAgent) { + console.warn("[MCPAppsRenderer] ui/message: No agent available"); + return { isError: false }; + } + try { + const params = req.params; + const ck = (params._meta?.copilotkit ?? {}) as { + role?: string; + followUp?: boolean; + }; + const role = + (ck.role as "user" | "assistant") || + (params.role as "user" | "assistant") || + "user"; + const textContent = + ( + params.content as + | Array<{ type: string; text?: string }> + | undefined + ) + ?.filter((c) => c.type === "text" && c.text) + .map((c) => c.text) + .join("\n") || ""; + if (textContent) { + currentAgent.addMessage({ + id: randomUUID(), + role, + content: textContent, + }); + } + const followUp = ck.followUp ?? params.followUp; + const shouldFollowUp = followUp ?? role === "user"; + if (shouldFollowUp && textContent) { + const capturedThreadId = currentAgent.threadId || "default"; + mcpAppsRequestQueue + .enqueue(currentAgent, () => + ɵrunMcpFollowUp({ + host, + agent: currentAgent, + capturedThreadId, + }), + ) + .catch((err) => + console.error( + "[MCPAppsRenderer] ui/message agent run failed:", + err, + ), + ); + } + return { isError: false }; + } catch (err) { + console.error("[MCPAppsRenderer] ui/message error:", err); + return { isError: true }; + } + }); + + bridge.onopenlink = async ({ url }) => { + // The bridge validates `url` as a string but not the scheme. Block only + // the script-executing / attacker-HTML schemes; everything else + // (https universal links, custom-scheme deep links) is allowed. + let parsed: URL; + try { + parsed = new URL(url); + } catch { + console.warn( + "[MCPAppsRenderer] ui/open-link rejected: unparseable url", + ); + return { isError: true }; + } + if (MCP_OPEN_LINK_BLOCKED_SCHEMES.has(parsed.protocol)) { + console.warn( + "[MCPAppsRenderer] ui/open-link rejected: blocked scheme", + parsed.protocol, + ); + return { isError: true }; + } + window.open(url, "_blank", "noopener,noreferrer"); + return { isError: false }; + }; + + bridge.oncalltool = async (params) => { + const { serverHash, serverId } = getContent(); + const currentAgent = getAgent(); + if (!serverHash) { + throw new Error("No server hash available for proxying"); + } + if (!currentAgent) { + throw new Error("No agent available for proxying"); + } + const runResult = await mcpAppsRequestQueue.enqueue(currentAgent, () => + currentAgent.runAgent({ + forwardedProps: { + __proxiedMCPRequest: { + serverHash, + serverId, + method: "tools/call", + params, + }, + }, + }), + ); + return (runResult.result as CallToolResult) || { content: [] }; + }; + + // --- App -> host notifications --- + bridge.onsizechange = (p) => { + if (disposed) return; + const { width, height } = (p || {}) as { + width?: number; + height?: number; + }; + hooks?.onSizeChanged?.({ + width: typeof width === "number" ? width : undefined, + height: typeof height === "number" ? height : undefined, + }); + }; + bridge.oninitialized = () => { + if (disposed) return; + ready = true; + hooks?.onInitialized?.(); + flushPending(); + }; + bridge.onloggingmessage = (p) => { + console.log("[MCPAppsRenderer] App log:", p); + }; + + const transport = new PostMessageTransport(win, win); + await bridge.connect(transport); + if (disposed) { + await bridge.close(); + bridge = null; + return; + } + } catch (err) { + console.error("[MCPAppsRenderer] Setup error:", err); + if (!disposed) { + hooks?.onError?.(err instanceof Error ? err : new Error(String(err))); + } + } + }; + + void setup(); + + return { + sendToolInput(args) { + pendingToolInput = args; + flushPending(); + }, + sendToolResult(result) { + pendingToolResult = result; + flushPending(); + }, + teardown() { + disposed = true; + const b = bridge; + bridge = null; + void b?.close(); + }, + }; +} diff --git a/packages/mcp-apps-renderer/tsconfig.json b/packages/mcp-apps-renderer/tsconfig.json new file mode 100644 index 0000000000..67fe5a5ca6 --- /dev/null +++ b/packages/mcp-apps-renderer/tsconfig.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "composite": false, + "declaration": true, + "declarationMap": true, + "incremental": true, + "forceConsistentCasingInFileNames": true, + "inlineSources": false, + "preserveWatchOutput": true, + "sourceMap": true, + "target": "es2022", + "module": "esnext", + "lib": ["es2023", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "useDefineForClassFields": false, + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/.tsbuildinfo", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "types": ["vitest/globals"] + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/mcp-apps-renderer/tsdown.config.ts b/packages/mcp-apps-renderer/tsdown.config.ts new file mode 100644 index 0000000000..5d4008ccc0 --- /dev/null +++ b/packages/mcp-apps-renderer/tsdown.config.ts @@ -0,0 +1,69 @@ +import { defineConfig } from "tsdown"; + +const externalPkgs = [ + "@modelcontextprotocol/ext-apps", + "@modelcontextprotocol/sdk", + "@ag-ui/client", + "@copilotkit/shared", + "zod", +]; + +const isExternal = (id: string) => + externalPkgs.some((pkg) => id === pkg || id.startsWith(pkg + "/")); + +export default defineConfig([ + // Root entry: ESM only. It re-exports the session, which imports the ext-apps + // AppBridge - and ext-apps 1.7.5 is ESM-only. A CJS root would emit a + // `require()` of that ESM module and fail with ERR_REQUIRE_ESM, so we do not + // advertise a CJS root. Consumers load the bridge via a dynamic `import()` + // (react-core does), which resolves ESM from any context. + { + entry: ["src/index.ts"], + format: ["esm"], + dts: true, + sourcemap: true, + target: "es2022", + outDir: "dist", + unbundle: true, + external: isExternal, + exports: false, + }, + // Activity entry: the bridge-free registration surface (activity type, content + // schema, follow-up runner). It has no ext-apps edge, so it is safe as dual + // ESM + CJS - frontends register the activity synchronously from either module + // system without pulling the bridge. + { + entry: ["src/activity.ts"], + format: ["esm", "cjs"], + dts: true, + sourcemap: true, + target: "es2022", + outDir: "dist", + unbundle: true, + external: isExternal, + exports: false, + }, + // Activity UMD: a self-contained global for script-tag consumers of + // @copilotkit/react-core's UMD build, which externalizes this entry and maps + // it to `CopilotKitMcpAppsRendererActivity`. Bridge-free, so only zod is + // external (mapped to the shared `Zod` global, matching react-core's UMD). + { + entry: ["src/activity.ts"], + format: ["umd"], + globalName: "CopilotKitMcpAppsRendererActivity", + sourcemap: true, + // es2018 for the script-tag UMD (broad browser reach), matching a2ui-renderer. + target: "es2018", + outDir: "dist", + external: (id: string) => id === "zod" || id.startsWith("zod/"), + // Force a single self-contained bundle (matches a2ui-renderer): without this, + // a sibling chunk emitted next to activity.umd.js would leave the global + // incomplete, and es-check (syntax-only) would not catch it. + codeSplitting: false, + outputOptions(options) { + options.entryFileNames = "[name].umd.js"; + options.globals = { zod: "Zod" }; + return options; + }, + }, +]); diff --git a/packages/mcp-apps-renderer/vitest.config.mjs b/packages/mcp-apps-renderer/vitest.config.mjs new file mode 100644 index 0000000000..9f49708495 --- /dev/null +++ b/packages/mcp-apps-renderer/vitest.config.mjs @@ -0,0 +1,15 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "jsdom", + include: ["**/__tests__/**/*.{test,spec}.ts"], + passWithNoTests: true, + globals: true, + server: { + deps: { + inline: ["@modelcontextprotocol/ext-apps", "zod"], + }, + }, + }, +}); diff --git a/packages/react-core/package.json b/packages/react-core/package.json index 754149dc6c..d5831d9801 100644 --- a/packages/react-core/package.json +++ b/packages/react-core/package.json @@ -78,12 +78,12 @@ "@ag-ui/core": "0.0.59", "@copilotkit/a2ui-renderer": "workspace:*", "@copilotkit/core": "workspace:*", + "@copilotkit/mcp-apps-renderer": "workspace:*", "@copilotkit/runtime-client-gql": "workspace:*", "@copilotkit/shared": "workspace:*", "@copilotkit/web-components": "workspace:*", "@copilotkit/web-inspector": "workspace:*", "@jetbrains/websandbox": "^1.1.3", - "@modelcontextprotocol/ext-apps": "^1.7.5", "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tooltip": "^1.2.7", @@ -108,7 +108,6 @@ "esbuild": "^0.27.0", "@tailwindcss/postcss": "^4.1.11", "@tailwindcss/typography": "^0.5.16", - "@modelcontextprotocol/sdk": "^1.29.0", "@tanstack/intent": "^0.0.29", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^16.3.0", @@ -135,7 +134,6 @@ "zod": ">=3.0.0" }, "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.29.0", "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc", "zod": ">=3.25" diff --git a/packages/react-core/src/v2/components/MCPAppsActivityRenderer.tsx b/packages/react-core/src/v2/components/MCPAppsActivityRenderer.tsx index 2b6b9f44df..f603344974 100644 --- a/packages/react-core/src/v2/components/MCPAppsActivityRenderer.tsx +++ b/packages/react-core/src/v2/components/MCPAppsActivityRenderer.tsx @@ -1,282 +1,38 @@ "use client"; import React, { useEffect, useRef, useState } from "react"; -import { z } from "zod"; -import type { AbstractAgent, RunAgentResult } from "@ag-ui/client"; -import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -// Type-only import: the ext-apps bridge is a heavy dependency (it pulls the MCP -// SDK Protocol + zod schemas, ~40-50 kB gzipped). It is loaded lazily via a -// dynamic import() inside Effect 1 so that a `` app only pays for it -// when it actually renders an MCP App, not on every mount. -import type { AppBridge } from "@modelcontextprotocol/ext-apps/app-bridge"; +import type { AbstractAgent } from "@ag-ui/client"; import { useCopilotKit } from "../providers/CopilotKitProvider"; -/** - * The subset of `CopilotKitCore` that {@link ɵrunMcpFollowUp} depends on. - * Declared structurally so the runner can be unit-tested without a full core. - */ -export interface ɵMcpFollowUpHost { - runAgent(params: { agent: AbstractAgent }): Promise; -} - -/** - * Run an MCP app `ui/message` follow-up, scoped to the thread it was enqueued - * for (issue #5819). - * - * The MCP request queue delays follow-up work until the agent is idle. There is - * a single shared registry agent per id, and switching threads overwrites its - * `threadId`/`messages` in place. So if the host switches threads while a - * follow-up is queued, running it now would execute against — and stream into — - * the now-foreground thread. - * - * - **Same thread** (the common case): run on the shared agent, unchanged. - * - **Thread changed**: the shared agent has moved on, so the follow-up can no - * longer run in its originating thread's context. Drop it rather than leak it - * into the current thread. (The MCP app already received its `ui/message` ack - * at enqueue time; only the optional agent turn is skipped.) - * - * @internal exported for testing. - */ -export async function ɵrunMcpFollowUp({ - host, - agent, - capturedThreadId, -}: { - host: ɵMcpFollowUpHost; - agent: AbstractAgent; - capturedThreadId: string; -}): Promise { - const currentThreadId = agent.threadId || "default"; - const originThreadId = capturedThreadId || "default"; - - if (currentThreadId === originThreadId) { - return host.runAgent({ agent }); - } - - console.warn( - "[MCPAppsRenderer] ui/message follow-up dropped: the thread changed " + - `(${originThreadId} → ${currentThreadId}) between enqueue and execution, ` + - "so running it would leak into the now-foreground thread.", - ); - return { result: undefined, newMessages: [] }; -} - -// Build sandbox proxy HTML with optional extra CSP domains from resource metadata -function buildSandboxHTML(extraCspDomains?: string[]): string { - const baseScriptSrc = - "'self' 'wasm-unsafe-eval' 'unsafe-inline' 'unsafe-eval' blob: data: http://localhost:* https://localhost:*"; - const baseFrameSrc = "* blob: data: http://localhost:* https://localhost:*"; - const extra = extraCspDomains?.length ? " " + extraCspDomains.join(" ") : ""; - const scriptSrc = baseScriptSrc + extra; - const frameSrc = baseFrameSrc + extra; - - return ` - - - - - - - - - -`; -} - -/** - * Queue for serializing MCP app requests to an agent. - * Ensures requests wait for the agent to stop running and are processed one at a time. - */ -class MCPAppsRequestQueue { - private queues = new Map< - string, - Array<{ - execute: () => Promise; - resolve: (result: RunAgentResult) => void; - reject: (error: Error) => void; - }> - >(); - private processing = new Map(); - - /** - * Add a request to the queue for a specific agent thread. - * Returns a promise that resolves when the request completes. - */ - async enqueue( - agent: AbstractAgent, - request: () => Promise, - ): Promise { - const threadId = agent.threadId || "default"; - - return new Promise((resolve, reject) => { - // Get or create queue for this thread - let queue = this.queues.get(threadId); - if (!queue) { - queue = []; - this.queues.set(threadId, queue); - } - - // Add request to queue - queue.push({ execute: request, resolve, reject }); - - // Start processing if not already running - this.processQueue(threadId, agent); - }); - } - - private async processQueue( - threadId: string, - agent: AbstractAgent, - ): Promise { - // If already processing this queue, return - if (this.processing.get(threadId)) { - return; - } - - this.processing.set(threadId, true); - - try { - const queue = this.queues.get(threadId); - if (!queue) return; - - while (queue.length > 0) { - const item = queue[0]!; - - try { - // Wait for any active run to complete before processing - await this.waitForAgentIdle(agent); - - // Execute the request - const result = await item.execute(); - item.resolve(result); - } catch (error) { - item.reject( - error instanceof Error ? error : new Error(String(error)), - ); - } - - // Remove processed item - queue.shift(); - } - } finally { - this.processing.set(threadId, false); - } - } - - private waitForAgentIdle(agent: AbstractAgent): Promise { - return new Promise((resolve) => { - if (!agent.isRunning) { - resolve(); - return; - } - - let done = false; - const finish = () => { - if (done) return; - done = true; - clearInterval(checkInterval); - sub.unsubscribe(); - resolve(); - }; - - const sub = agent.subscribe({ - onRunFinalized: finish, - onRunFailed: finish, - }); - - // Fallback for reconnect scenarios where events don't fire - const checkInterval = setInterval(() => { - if (!agent.isRunning) finish(); - }, 500); - }); - } -} - -// Global queue instance for all MCP app requests -const mcpAppsRequestQueue = new MCPAppsRequestQueue(); - -// URL schemes a widget may NOT open via ui/open-link. The ext-apps schema -// validates `url` as a plain string only (noopener/noreferrer does not restrict -// the scheme), so ui/open-link could otherwise become an XSS vector. +// The app<->host protocol (ext-apps AppBridge, sandbox proxy, request queue, +// ui/message + open-link handlers, tool input/result) lives in the shared, +// framework-agnostic package. This file is now a THIN React adapter over it: it +// owns the iframe (create/mount/size/remove) and wires the session's reactive +// hooks to React state; all protocol logic is `bindMcpApp`. // -// We use a denylist rather than an allowlist on purpose: deep links use -// arbitrary, app-defined schemes (`myapp:`, `whatsapp:`, `slack:`, `spotify:`, -// `sms:`, ...) that an allowlist could never enumerate, and `window.open`ing them -// just hands off to an OS handler — it does not execute script in the page, so -// it is not an XSS risk. Universal links / App Links are plain `https:` URLs and -// pass regardless. What IS dangerous is the small, well-known set of schemes -// that execute script or render attacker HTML in the page context; block those -// and allow everything else (including deep links). -const MCP_OPEN_LINK_BLOCKED_SCHEMES = new Set([ - "javascript:", - "data:", - "vbscript:", - "blob:", - "file:", -]); +// The lightweight activity surface (type + content schema + follow-up runner) +// is re-exported from the package's bridge-free `/activity` entry, so importing +// it (for the activity registry) does NOT pull the ext-apps bundle. The bridge +// itself is loaded lazily via a dynamic `import("@copilotkit/mcp-apps-renderer")` +// inside the effect, so a `` app only pays for it when it actually +// renders an MCP App. +export { + MCPAppsActivityType, + MCPAppsActivityContentSchema, + ɵrunMcpFollowUp, +} from "@copilotkit/mcp-apps-renderer/activity"; +export type { + MCPAppsActivityContent, + ɵMcpFollowUpHost, +} from "@copilotkit/mcp-apps-renderer/activity"; -/** - * Activity type for MCP Apps events - must match the middleware's MCPAppsActivityType - */ -export const MCPAppsActivityType = "mcp-apps"; - -// Zod schema for activity content validation (middleware 0.0.2 format) -export const MCPAppsActivityContentSchema = z.object({ - result: z.object({ - content: z.array(z.any()).optional(), - structuredContent: z.any().optional(), - isError: z.boolean().optional(), - }), - // Resource URI to fetch (e.g., "ui://server/dashboard") - resourceUri: z.string(), - // MD5 hash of server config (renamed from serverId in 0.0.1) - serverHash: z.string(), - // Optional stable server ID from config (takes precedence over serverHash) - serverId: z.string().optional(), - // Original tool input arguments - toolInput: z.record(z.string(), z.unknown()).optional(), -}); - -export type MCPAppsActivityContent = z.infer< - typeof MCPAppsActivityContentSchema ->; - -// Type for the resource fetched from the server -interface FetchedResource { - uri: string; - mimeType?: string; - text?: string; - blob?: string; - _meta?: { - ui?: { - prefersBorder?: boolean; - csp?: { - connectDomains?: string[]; - resourceDomains?: string[]; - }; - }; - }; -} +import type { MCPAppsActivityContent } from "@copilotkit/mcp-apps-renderer/activity"; +// Type-only imports: erased at build, so they never pull the ext-apps bridge +// into the bundle. Only the dynamic import() below does, and only lazily. +import type { + McpAppSession, + FetchedResource, +} from "@copilotkit/mcp-apps-renderer"; /** * Props for the activity renderer component @@ -292,37 +48,15 @@ interface MCPAppsActivityRendererProps { * MCP Apps Extension Activity Renderer * * Renders MCP Apps UI in a sandboxed iframe with full protocol support. - * Fetches resource content on-demand via proxied MCP requests. + * Fetches resource content on-demand via proxied MCP requests. The React shell + * owns the iframe; `bindMcpApp` owns the protocol. */ -/** - * Permissive `ui/message` schema. ext-apps restricts the request to - * `role: "user"` with no `followUp`, but CopilotKit intentionally extends - * `ui/message` with `role` ("user" | "assistant") and `followUp` (documented - * behavior with dedicated tests). We register our own handler (instead of the - * bridge's strict `onmessage`) so those extensions survive the migration. - * - * Going forward, widgets SHOULD pass the extensions under - * `params._meta.copilotkit`; the top-level `role`/`followUp` fields are the - * legacy channel, kept for backward compatibility and slated for deprecation. - */ -const CopilotKitUiMessageSchema = z.object({ - method: z.literal("ui/message"), - params: z - .object({ - role: z.string().optional(), - content: z.array(z.any()).optional(), - followUp: z.boolean().optional(), - _meta: z.record(z.string(), z.any()).optional(), - }) - .passthrough(), -}); - export const MCPAppsActivityRenderer: React.FC = function MCPAppsActivityRenderer({ content, agent }) { const { copilotkit } = useCopilotKit(); const containerRef = useRef(null); const iframeRef = useRef(null); - const [iframeReady, setIframeReady] = useState(false); + const sessionRef = useRef(null); const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(true); const [iframeSize, setIframeSize] = useState<{ @@ -332,392 +66,127 @@ export const MCPAppsActivityRenderer: React.FC = const [fetchedResource, setFetchedResource] = useState(null); - // Use refs for values that shouldn't trigger re-renders but need latest values + // Latest content/agent for the session's live getters (they must read the + // current values on every proxied request, not the values at bind time). const contentRef = useRef(content); contentRef.current = content; - - // Store agent in a ref for use in async handlers const agentRef = useRef(agent); agentRef.current = agent; - // ext-apps host bridge for this widget instance (owns the app<->host protocol). - const bridgeRef = useRef(null); - - // Ref to track fetch state - survives StrictMode remounts - const fetchStateRef = useRef<{ - inProgress: boolean; - promise: Promise | null; - resourceUri: string | null; - }>({ inProgress: false, promise: null, resourceUri: null }); - - // Effect 0: Fetch the resource content on mount - // Uses ref-based deduplication to handle React StrictMode double-mounting + // Effect 1: create the sandbox iframe and bind the MCP session. Re-binds + // only when the widget identity (resourceUri/serverHash/serverId) or the + // agent/host changes - NOT when tool input/result stream in (those are + // pushed by the effects below without recreating the iframe). useEffect(() => { - const { resourceUri, serverHash, serverId } = content; - - // Check if we already have a fetch in progress for this resource - // This handles StrictMode double-mounting - second mount reuses first mount's promise - if ( - fetchStateRef.current.inProgress && - fetchStateRef.current.resourceUri === resourceUri - ) { - // Reuse the existing promise - fetchStateRef.current.promise - ?.then((resource) => { - if (resource) { - setFetchedResource(resource); - setIsLoading(false); - } - }) - .catch((err) => { - setError(err instanceof Error ? err : new Error(String(err))); - setIsLoading(false); - }); + const container = containerRef.current; + if (!container) { return; } - if (!agent) { setError(new Error("No agent available to fetch resource")); setIsLoading(false); return; } - // Mark fetch as in progress - fetchStateRef.current.inProgress = true; - fetchStateRef.current.resourceUri = resourceUri; - - // Create the fetch promise using the queue to serialize requests - const fetchPromise = (async (): Promise => { - try { - // Use queue to wait for agent to be idle and serialize requests - const runResult = await mcpAppsRequestQueue.enqueue(agent, () => - agent.runAgent({ - forwardedProps: { - __proxiedMCPRequest: { - serverHash, - serverId, // optional, takes precedence if provided - method: "resources/read", - params: { uri: resourceUri }, - }, - }, - }), - ); - - // Extract resource from result - // The response format is: { contents: [{ uri, mimeType, text?, blob?, _meta? }] } - const resultData = runResult.result as - | { contents?: FetchedResource[] } - | undefined; - const resource = resultData?.contents?.[0]; - - if (!resource) { - throw new Error("No resource content in response"); - } - - return resource; - } catch (err) { - console.error("[MCPAppsRenderer] Failed to fetch resource:", err); - throw err; - } finally { - // Mark fetch as complete - fetchStateRef.current.inProgress = false; - } - })(); - - // Store the promise for potential reuse - fetchStateRef.current.promise = fetchPromise; - - // Handle the result - fetchPromise - .then((resource) => { - if (resource) { - setFetchedResource(resource); - setIsLoading(false); - } - }) - .catch((err) => { - setError(err instanceof Error ? err : new Error(String(err))); - setIsLoading(false); - }); - - // No cleanup needed - we want the fetch to complete even if StrictMode unmounts - }, [agent, content]); - - // Effect 1: connect the ext-apps AppBridge to the sandboxed iframe. - // The bridge owns the app<->host protocol (initialize/capabilities/context, - // requests, notifications, tool input/result) over a PostMessage transport. - useEffect(() => { - if (isLoading || !fetchedResource) { - return; - } - const container = containerRef.current; - if (!container) { - return; - } - let mounted = true; - let bridge: AppBridge | null = null; - let createdIframe: HTMLIFrameElement | null = null; + setIsLoading(true); + setError(null); + + // The host owns the iframe: create + mount it here (bindMcpApp only + // configures the sandbox contract + talks to it through the bridge). + const iframe = document.createElement("iframe"); + iframe.style.width = "100%"; + iframe.style.height = "100px"; + iframe.style.border = "none"; + iframe.style.backgroundColor = "transparent"; + iframe.style.display = "block"; + container.appendChild(iframe); + iframeRef.current = iframe; const setup = async () => { try { - // Load the ext-apps bridge FIRST, before creating/attaching the iframe. - // The sandbox proxy posts `sandbox-proxy-ready` once, during srcdoc - // execution, and the PostMessageTransport must already be listening - // (via connect()) when that fires. Awaiting the dynamic import after - // the iframe is attached would let a slow import miss that - // notification, leaving the widget blank; doing it here means there is - // no event-loop yield between attaching the iframe and connecting. - // The type-only import at the top of the file keeps this lazy (see that - // note); the try/catch rethrows with an actionable message if the peer - // is missing or version-skewed. - const bridgeModule = - await import("@modelcontextprotocol/ext-apps/app-bridge").catch( - (importErr) => { - throw new Error( - "MCP Apps require '@modelcontextprotocol/ext-apps' and its peer " + - "'@modelcontextprotocol/sdk'. Install them with: npm install " + - "@modelcontextprotocol/ext-apps @modelcontextprotocol/sdk", - { cause: importErr }, - ); + // Load the bridge package lazily. The bridge is heavy (it pulls the + // MCP SDK Protocol + zod schemas, ~40-50 kB gzipped); keeping it behind + // a dynamic import() means a non-MCP `` app never pays for + // it. The try/catch rethrows with an actionable message if the package + // (or its ext-apps dependency) is missing. + const mod = await import("@copilotkit/mcp-apps-renderer").catch( + (importErr) => { + throw new Error( + "MCP Apps require '@copilotkit/mcp-apps-renderer' and its " + + "'@modelcontextprotocol/ext-apps' dependency. Reinstall your " + + "dependencies if this package is missing.", + { cause: importErr }, + ); + }, + ); + if (!mounted) { + iframe.remove(); + return; + } + + const session = mod.bindMcpApp({ + iframe, + getContent: () => contentRef.current, + getAgent: () => agentRef.current, + host: copilotkit, + hooks: { + onResource: (resource) => { + if (!mounted) return; + setFetchedResource(resource); + setIsLoading(false); }, - ); - if (!mounted) { - return; - } - const { AppBridge, PostMessageTransport } = bridgeModule; - - // Create the sandbox proxy iframe (the proxy relays postMessage between - // the host and the inner sandboxed widget). - const iframe = document.createElement("iframe"); - createdIframe = iframe; - iframe.style.width = "100%"; - iframe.style.height = "100px"; - iframe.style.border = "none"; - iframe.style.backgroundColor = "transparent"; - iframe.style.display = "block"; - iframe.setAttribute( - "sandbox", - "allow-scripts allow-same-origin allow-forms", - ); - // Cross-frontend MCP-apps surface contract: the host-created sandbox - // iframe is the addressable render surface for the MCP app, and every - // frontend must expose it under the SAME testid so one shared probe - // (harness `d5-mcp-apps`) and one shared e2e spec can assert the - // surface mounted without per-frontend selectors. Angular declares - // the same pair on its `copilot-mcp-apps-widget` template iframe; - // Vue's renderer mirrors this block. - iframe.setAttribute("data-testid", "mcp-app-iframe"); - iframe.setAttribute("title", "Interactive MCP application"); - - const cspDomains = fetchedResource._meta?.ui?.csp?.resourceDomains; - iframe.srcdoc = buildSandboxHTML(cspDomains); - iframeRef.current = iframe; - container.appendChild(iframe); - - const win = iframe.contentWindow; - if (!win) { - throw new Error("Sandbox iframe has no contentWindow"); - } - - // Extract the widget HTML from the fetched resource. Done after the - // iframe is mounted so a resource missing text/blob still leaves the - // sandbox surface present (it just never receives content). - let html: string; - if (fetchedResource.text) { - html = fetchedResource.text; - } else if (fetchedResource.blob) { - html = atob(fetchedResource.blob); - } else { - throw new Error("Resource has no text or blob content"); - } - - bridge = new AppBridge( - null, - { name: "CopilotKit MCP Apps Host", version: "1.0.0" }, - { openLinks: {}, logging: {}, message: { text: {} } }, - // Seed the host context at construction (before connect) so it is - // already in place when the widget's ui/initialize is handled. Doing - // this via setHostContext after connect would only win the race by - // luck (it depends on the notification landing before initialize), - // and #6689 relies on this seam to advertise displayMode / - // availableDisplayModes at initialize. - { hostContext: { theme: "light", platform: "web" } }, - ); - - // Sandbox handshake: when the proxy is ready, load the widget HTML into - // the inner sandboxed iframe. - bridge.onsandboxready = () => { - void bridge?.sendSandboxResourceReady({ html }); - }; - - // --- App -> host requests --- - // ui/message uses a custom handler (not the bridge's strict onmessage) - // to preserve CopilotKit's role/followUp extensions. Extensions are read - // from params._meta.copilotkit first (preferred), then from the legacy - // top-level params.role / params.followUp (deprecated). - bridge.setRequestHandler(CopilotKitUiMessageSchema, async (req) => { - const currentAgent = agentRef.current; - if (!currentAgent) { - console.warn("[MCPAppsRenderer] ui/message: No agent available"); - return { isError: false }; - } - try { - const params = req.params; - const ck = (params._meta?.copilotkit ?? {}) as { - role?: string; - followUp?: boolean; - }; - const role = - (ck.role as "user" | "assistant") || - (params.role as "user" | "assistant") || - "user"; - const textContent = - ( - params.content as - | Array<{ type: string; text?: string }> - | undefined - ) - ?.filter((c) => c.type === "text" && c.text) - .map((c) => c.text) - .join("\n") || ""; - if (textContent) { - currentAgent.addMessage({ - id: crypto.randomUUID(), - role, - content: textContent, - }); - } - const followUp = ck.followUp ?? params.followUp; - const shouldFollowUp = followUp ?? role === "user"; - if (shouldFollowUp && textContent) { - const capturedThreadId = currentAgent.threadId || "default"; - mcpAppsRequestQueue - .enqueue(currentAgent, () => - ɵrunMcpFollowUp({ - host: copilotkit, - agent: currentAgent, - capturedThreadId, - }), - ) - .catch((err) => - console.error( - "[MCPAppsRenderer] ui/message agent run failed:", - err, - ), - ); - } - return { isError: false }; - } catch (err) { - console.error("[MCPAppsRenderer] ui/message error:", err); - return { isError: true }; - } + onSizeChanged: (size) => { + if (mounted) setIframeSize(size); + }, + onError: (err) => { + if (!mounted) return; + setError(err); + setIsLoading(false); + }, + }, }); + sessionRef.current = session; - bridge.onopenlink = async ({ url }) => { - // `url` is guaranteed to be a string by the bridge's ui/open-link - // schema, but the schema does not restrict the scheme. Parse it and - // block only the script-executing / attacker-HTML schemes (see - // MCP_OPEN_LINK_BLOCKED_SCHEMES). Everything else is allowed, - // including custom-scheme deep links (`myapp:`, `whatsapp:`, ...) and - // https universal links, which hand off to an OS handler rather than - // executing in the page. - let parsed: URL; - try { - parsed = new URL(url); - } catch { - console.warn( - "[MCPAppsRenderer] ui/open-link rejected: unparseable url", - ); - return { isError: true }; - } - if (MCP_OPEN_LINK_BLOCKED_SCHEMES.has(parsed.protocol)) { - console.warn( - "[MCPAppsRenderer] ui/open-link rejected: blocked scheme", - parsed.protocol, - ); - return { isError: true }; - } - window.open(url, "_blank", "noopener,noreferrer"); - return { isError: false }; - }; - - bridge.oncalltool = async (params) => { - const { serverHash, serverId } = contentRef.current; - const currentAgent = agentRef.current; - // Keep these two failures distinct: they point at different setup - // problems when debugging the proxy wiring. - if (!serverHash) { - throw new Error("No server hash available for proxying"); - } - if (!currentAgent) { - throw new Error("No agent available for proxying"); - } - const runResult = await mcpAppsRequestQueue.enqueue( - currentAgent, - () => - currentAgent.runAgent({ - forwardedProps: { - __proxiedMCPRequest: { - serverHash, - serverId, - method: "tools/call", - params, - }, - }, - }), - ); - return (runResult.result as CallToolResult) || { content: [] }; - }; - - // --- App -> host notifications --- - bridge.onsizechange = (p) => { - if (!mounted) return; - const { width, height } = p || {}; - setIframeSize({ - width: typeof width === "number" ? width : undefined, - height: typeof height === "number" ? height : undefined, - }); - }; - bridge.oninitialized = () => { - if (mounted) setIframeReady(true); - }; - bridge.onloggingmessage = (p) => { - console.log("[MCPAppsRenderer] App log:", p); - }; - - const transport = new PostMessageTransport(win, win); - await bridge.connect(transport); - if (!mounted) { - await bridge.close(); - return; + // Push any tool input/result already present at bind time (the session + // buffers until the widget reports initialized). + const current = contentRef.current; + if (current.toolInput) { + session.sendToolInput(current.toolInput as Record); + } + if (current.result) { + session.sendToolResult( + current.result as Parameters[0], + ); } - // Host context was seeded at construction (see the AppBridge options - // above), so it is already advertised by the time ui/initialize runs. - bridgeRef.current = bridge; } catch (err) { console.error("[MCPAppsRenderer] Setup error:", err); if (mounted) { setError(err instanceof Error ? err : new Error(String(err))); + setIsLoading(false); } } }; - setup(); + void setup(); return () => { mounted = false; - bridgeRef.current = null; - void bridge?.close(); - if (createdIframe) { - createdIframe.remove(); - createdIframe = null; - } + sessionRef.current?.teardown(); + sessionRef.current = null; + iframe.remove(); iframeRef.current = null; }; - }, [isLoading, fetchedResource, copilotkit]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + agent, + copilotkit, + content.resourceUri, + content.serverHash, + content.serverId, + ]); - // Effect 2: Update iframe size when it changes + // Effect 2: size the iframe when the widget reports a new content size. useEffect(() => { if (iframeRef.current) { if (iframeSize.width !== undefined) { @@ -731,23 +200,24 @@ export const MCPAppsActivityRenderer: React.FC = } }, [iframeSize]); - // Effect 3: Send tool input when iframe ready + // Effect 3: forward tool input to the widget (buffered by the session until + // the widget is ready). useEffect(() => { - if (iframeReady && content.toolInput) { - void bridgeRef.current?.sendToolInput({ - arguments: content.toolInput as Record, - }); - } - }, [iframeReady, content.toolInput]); - - // Effect 4: Send tool result when iframe ready - useEffect(() => { - if (iframeReady && content.result) { - void bridgeRef.current?.sendToolResult( - content.result as CallToolResult, + if (content.toolInput) { + sessionRef.current?.sendToolInput( + content.toolInput as Record, ); } - }, [iframeReady, content.result]); + }, [content.toolInput]); + + // Effect 4: forward tool result to the widget. + useEffect(() => { + if (content.result) { + sessionRef.current?.sendToolResult( + content.result as Parameters[0], + ); + } + }, [content.result]); // Determine border styling based on prefersBorder metadata from fetched resource // true = show border/background, false = none, undefined = host decides (we default to none) diff --git a/packages/react-core/tsdown.config.ts b/packages/react-core/tsdown.config.ts index 41a04d22a7..f8580377e1 100644 --- a/packages/react-core/tsdown.config.ts +++ b/packages/react-core/tsdown.config.ts @@ -166,6 +166,13 @@ export default defineConfig([ "rxjs", ], }, + // UMD builds. Each externalized @copilotkit/* dep must be loaded as a global + // before this bundle. Note `@copilotkit/mcp-apps-renderer/activity` -> the + // global `CopilotKitMcpAppsRendererActivity`: the provider registers the MCP + // Apps activity from that (bridge-free) entry, so script-tag consumers must + // load `@copilotkit/mcp-apps-renderer/dist/activity.umd.js` before this bundle + // (see that package's README). The ext-apps bridge itself is NOT here; it is + // loaded lazily via dynamic import() only when an MCP App renders. { entry: { index: "src/v1-deprecated-compatibility.ts", @@ -183,6 +190,7 @@ export default defineConfig([ "@copilotkit/runtime-client-gql", "@copilotkit/web-inspector", "@copilotkit/a2ui-renderer", + "@copilotkit/mcp-apps-renderer/activity", "@ag-ui/client", "zod", /\.css$/, @@ -198,6 +206,8 @@ export default defineConfig([ "@copilotkit/runtime-client-gql": "CopilotKitRuntimeClientGQL", "@copilotkit/web-inspector": "CopilotKitWebInspector", "@copilotkit/a2ui-renderer": "CopilotKitA2UIRenderer", + "@copilotkit/mcp-apps-renderer/activity": + "CopilotKitMcpAppsRendererActivity", "@ag-ui/client": "AgUIClient", "react-markdown": "ReactMarkdown", zod: "Zod", @@ -220,6 +230,7 @@ export default defineConfig([ "@copilotkit/runtime-client-gql", "@copilotkit/web-inspector", "@copilotkit/a2ui-renderer", + "@copilotkit/mcp-apps-renderer/activity", "@ag-ui/client", "@ag-ui/core", "zod", @@ -236,6 +247,8 @@ export default defineConfig([ "@copilotkit/runtime-client-gql": "CopilotKitRuntimeClientGQL", "@copilotkit/web-inspector": "CopilotKitWebInspector", "@copilotkit/a2ui-renderer": "CopilotKitA2UIRenderer", + "@copilotkit/mcp-apps-renderer/activity": + "CopilotKitMcpAppsRendererActivity", "@ag-ui/client": "AgUIClient", "@ag-ui/core": "AgUICore", "react-markdown": "ReactMarkdown", diff --git a/packages/runtime/README.md b/packages/runtime/README.md index 1bf45db925..d5cd9e11c0 100644 --- a/packages/runtime/README.md +++ b/packages/runtime/README.md @@ -142,3 +142,25 @@ Or use the `DO_NOT_TRACK` standard: ```bash export DO_NOT_TRACK=1 ``` + +## Stopping Intelligence runs + +Await Stop before sending another message on the same thread. With +`IntelligenceAgentRunner`, `stopped: true` means the gateway acknowledged the +run's terminal events and the runtime completed local cleanup. The gateway +releases only the lock owned by that run. + +Stop requests agent cancellation and excludes late agent events from thread +history. Agents that support `detachActiveRun()` also detach their local +subscription. Older agents remain supported. An adapter must honor cancellation +to stop external work; Stop cannot undo tool calls that already took effect. + +The HTTP request and response formats are unchanged. Empty-body Stop requests +still stop the current run. Direct runner callers can pass the existing optional +`runId` to stop only that run. A missing, mismatched, or already-requested Stop +returns `false`. Failed terminal delivery rejects Stop; the HTTP handler returns +its existing error response instead of reporting success. The wait is bounded by +the existing 60-second durability window. + +No Intelligence upgrade is required. The runtime uses the existing terminal +events and supports both single-event and batched gateway acknowledgments. diff --git a/packages/runtime/package.json b/packages/runtime/package.json index e7a9703877..5856379766 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -115,7 +115,7 @@ "openai": "^4.85.1 || >=5.0.0", "partial-json": "^0.1.7", "phoenix": "^1.8.4", - "pino": "^9.2.0", + "pino": "^10.1.1", "pino-pretty": "^11.2.1", "reflect-metadata": "^0.2.2", "rxjs": "7.8.1", @@ -196,5 +196,8 @@ ] } } + }, + "engines": { + "node": ">=20" } } diff --git a/packages/runtime/src/v1-deprecated/lib/__tests__/logger.test.ts b/packages/runtime/src/v1-deprecated/lib/__tests__/logger.test.ts index 626f4a1b13..37815e1992 100644 --- a/packages/runtime/src/v1-deprecated/lib/__tests__/logger.test.ts +++ b/packages/runtime/src/v1-deprecated/lib/__tests__/logger.test.ts @@ -38,10 +38,9 @@ describe("createLogger", () => { // first path in the array ("invalid path (pid)"). Any redact path fails, not // just `pid`, so the only safe configuration is to pass none. // - // This monorepo pins pino to 10 through a pnpm override, and pino 10 swapped - // fast-redact for @pinojs/redact, which uses no code generation. That means - // the failure cannot be reproduced by running a logger here; the assertion - // has to be on the options we hand pino. + // The runtime now declares pino 10, which uses @pinojs/redact without code + // generation. Keep checking the options directly so this regression guard + // does not depend on the installed redaction implementation. it("passes no redact option to pino, because redact paths need code generation", () => { createLogger(); diff --git a/packages/runtime/src/v2/runtime/runner/__tests__/intelligence-runner.test.ts b/packages/runtime/src/v2/runtime/runner/__tests__/intelligence-runner.test.ts index 3687cf4dc4..fd890bf40e 100644 --- a/packages/runtime/src/v2/runtime/runner/__tests__/intelligence-runner.test.ts +++ b/packages/runtime/src/v2/runtime/runner/__tests__/intelligence-runner.test.ts @@ -2061,7 +2061,9 @@ describe("IntelligenceAgentRunner", () => { false, ); expect(agent.aborted).toBe(false); - expect(await runner.stop({ threadId, runId: "r-current" })).toBe(true); + const stopping = runner.stop({ threadId, runId: "r-current" }); + mockChannels[0].triggerJoin("ok"); + expect(await stopping).toBe(true); expect(agent.aborted).toBe(true); sub.unsubscribe(); }); @@ -2072,7 +2074,9 @@ describe("IntelligenceAgentRunner", () => { const agent = new MockAgent(); const sub = runner.run({ threadId, agent, input }).subscribe(); - const result = await runner.stop({ threadId }); + const stopping = runner.stop({ threadId }); + mockChannels[0].triggerJoin("ok"); + const result = await stopping; expect(result).toBe(true); expect(agent.aborted).toBe(true); @@ -2093,8 +2097,10 @@ describe("IntelligenceAgentRunner", () => { const agent = new MockAgent(); const sub = runner.run({ threadId, agent, input }).subscribe(); - expect(await runner.stop({ threadId })).toBe(true); + const stopping = runner.stop({ threadId }); expect(await runner.stop({ threadId })).toBe(false); + mockChannels[0].triggerJoin("ok"); + expect(await stopping).toBe(true); sub.unsubscribe(); }); }); diff --git a/packages/runtime/src/v2/runtime/runner/__tests__/intelligence-stop-completion.test.ts b/packages/runtime/src/v2/runtime/runner/__tests__/intelligence-stop-completion.test.ts new file mode 100644 index 0000000000..34d12a254c --- /dev/null +++ b/packages/runtime/src/v2/runtime/runner/__tests__/intelligence-stop-completion.test.ts @@ -0,0 +1,478 @@ +import { AbstractAgent, EventType } from "@ag-ui/client"; +import type { BaseEvent, RunAgentInput } from "@ag-ui/client"; +import { Observable, Subject } from "rxjs"; +import { WebSocket, WebSocketServer } from "ws"; +import { expect, test, vi } from "vitest"; +import { IntelligenceAgentRunner } from "../intelligence"; +import { CopilotRuntime } from "../../core/runtime"; +import { createCopilotRuntimeHandler } from "../../core/fetch-handler"; + +type Frame = [string | null, string, string, string, Record]; + +/** Keeps producing until the test ends it, like an adapter that ignores abort. */ +class ControlledAgent extends AbstractAgent { + readonly output = new Subject(); + readonly abortRun = vi.fn(); + runCount = 0; + + run(input: RunAgentInput): Observable { + this.runCount++; + return new Observable((subscriber) => { + subscriber.next({ + type: EventType.RUN_STARTED, + threadId: input.threadId, + runId: input.runId, + }); + return this.output.subscribe(subscriber); + }); + } +} + +/** Acknowledges a Phoenix request using the same reference and topic. */ +function reply( + socket: WebSocket, + frame: Frame, + response: Record = {}, +) { + socket.send( + JSON.stringify([ + frame[0], + frame[1], + frame[2], + "phx_reply", + { status: "ok", response }, + ]), + ); +} + +/** Exercises the real Phoenix client against a controllable gateway transport. */ +async function setup(batch = false) { + // Node 20 does not expose a global WebSocket. Keep Node 22+ on its native + // transport and supply the same protocol transport for the older CI lane. + const needsWebSocket = typeof globalThis.WebSocket === "undefined"; + if (needsWebSocket) vi.stubGlobal("WebSocket", WebSocket); + const server = new WebSocketServer({ port: 0 }); + await new Promise((resolve) => server.once("listening", resolve)); + const address = server.address(); + if (typeof address !== "object" || address === null) + throw new Error("Missing test server address"); + const events: BaseEvent[] = []; + const pending: Array<{ + socket: WebSocket; + frame: Frame; + events: BaseEvent[]; + }> = []; + const locks = new Map(); + const errors: unknown[] = []; + const subscriptions: Array<{ unsubscribe(): void }> = []; + const agents: ControlledAgent[] = []; + const runner = new IntelligenceAgentRunner({ + url: `ws://127.0.0.1:${address.port}/runner`, + }); + + server.on("connection", (socket) => { + socket.on("message", (data) => { + const frame: Frame = JSON.parse(data.toString()); + if (frame[3] === "phx_join") { + reply( + socket, + frame, + batch ? { capabilities: ["runner_event_batch_v1"] } : {}, + ); + } else if (frame[3] === "event" || frame[3] === "events") { + const received = ( + frame[3] === "events" ? frame[4].events : [frame[4]] + ) as BaseEvent[]; + pending.push({ socket, frame, events: received }); + } else { + reply(socket, frame); + } + }); + }); + + /** Models atomic terminal acceptance: only the exact run can release its lock. */ + function acceptPending() { + for (const item of pending.splice(0)) { + for (const event of item.events) { + events.push(event); + if ( + event.type === EventType.RUN_FINISHED || + event.type === EventType.RUN_ERROR + ) { + const owner = event as BaseEvent & { + threadId: string; + runId: string; + }; + if (locks.get(owner.threadId) === owner.runId) + locks.delete(owner.threadId); + } + } + reply(item.socket, item.frame); + } + } + + /** Starts a run only when the modeled hosted lock is available. */ + function start(runId = "run-1") { + if (locks.has("thread-1")) throw new Error("THREAD_LOCK_FAILED"); + const agent = new ControlledAgent(); + agents.push(agent); + locks.set("thread-1", runId); + const input: RunAgentInput = { + threadId: "thread-1", + runId, + messages: [], + tools: [], + context: [], + state: {}, + forwardedProps: {}, + }; + subscriptions.push( + runner + .run({ threadId: input.threadId, agent, input }) + .subscribe({ error: (error) => errors.push(error) }), + ); + return agent; + } + + /** Closes test-owned streams, sockets, timers, and server listeners. */ + async function teardown() { + subscriptions.forEach((subscription) => subscription.unsubscribe()); + agents.forEach((agent) => agent.output.complete()); + server.clients.forEach((socket) => socket.terminate()); + await new Promise((resolve, reject) => + server.close((error) => (error ? reject(error) : resolve())), + ); + if (needsWebSocket) vi.unstubAllGlobals(); + } + + return { + runner, + start, + pending, + events, + locks, + errors, + acceptPending, + teardown, + }; +} + +for (const batch of [false, true]) { + test(`Stop waits for terminal acceptance and permits immediate resend (${batch ? "batch" : "legacy"})`, async () => { + const fixture = await setup(batch); + try { + const agent = fixture.start(); + await vi.waitFor(() => expect(fixture.pending.length).toBeGreaterThan(0)); + fixture.acceptPending(); + await vi.waitFor(() => expect(agent.isRunning).toBe(true)); + let stopResolved = false; + + const stopping = fixture.runner + .stop({ threadId: "thread-1" }) + .then((result) => { + stopResolved = true; + return result; + }); + await vi.waitFor(() => + expect( + fixture.pending.some((item) => + item.events.some((event) => event.type === EventType.RUN_FINISHED), + ), + ).toBe(true), + ); + + expect(stopResolved).toBe(false); + expect(fixture.locks.get("thread-1")).toBe("run-1"); + expect(agent.abortRun).toHaveBeenCalledOnce(); + fixture.acceptPending(); + expect(await stopping).toBe(true); + expect(await fixture.runner.isRunning({ threadId: "thread-1" })).toBe( + false, + ); + expect(fixture.locks.has("thread-1")).toBe(false); + expect(() => fixture.start("run-2")).not.toThrow(); + expect(fixture.errors).toEqual([]); + } finally { + await fixture.teardown(); + } + }); +} + +test("Stop detaches the local AG-UI subscription when abort does nothing", async () => { + const fixture = await setup(); + try { + const agent = fixture.start(); + await vi.waitFor(() => expect(fixture.pending.length).toBeGreaterThan(0)); + fixture.acceptPending(); + expect(agent.output.observed).toBe(true); + + const stopping = fixture.runner + .stop({ threadId: "thread-1" }) + .catch((error) => error); + await vi.waitFor(() => expect(agent.output.observed).toBe(false)); + await vi.waitFor(() => expect(fixture.pending.length).toBeGreaterThan(0)); + fixture.acceptPending(); + + expect(await stopping).toBe(true); + } finally { + await fixture.teardown(); + } +}); + +for (const abortThrows of [false, true]) { + test(`Stop fences late events from older agents without detachActiveRun (abort throws: ${abortThrows})`, async () => { + const fixture = await setup(); + try { + const agent = fixture.start(); + Object.defineProperty(agent, "detachActiveRun", { value: undefined }); + if (abortThrows) + agent.abortRun.mockImplementation(() => { + throw new Error("Adapter cannot abort"); + }); + await vi.waitFor(() => expect(fixture.pending.length).toBeGreaterThan(0)); + fixture.acceptPending(); + + const stopping = fixture.runner + .stop({ threadId: "thread-1", runId: "run-1" }) + .catch((error) => error); + agent.output.next({ + type: EventType.CUSTOM, + name: "late", + value: "discard", + }); + await vi.waitFor(() => expect(fixture.pending.length).toBeGreaterThan(0)); + fixture.acceptPending(); + expect(await stopping).toBe(true); + const replacement = fixture.start("run-2"); + await vi.waitFor(() => expect(replacement.runCount).toBe(1)); + fixture.acceptPending(); + agent.output.next({ + type: EventType.CUSTOM, + name: "late", + value: "discard again", + }); + agent.output.error(new Error("Late producer rejection")); + + expect( + await fixture.runner.stop({ threadId: "thread-1", runId: "run-1" }), + ).toBe(false); + expect(replacement.abortRun).not.toHaveBeenCalled(); + expect(fixture.locks.get("thread-1")).toBe("run-2"); + expect(fixture.events.map((event) => event.type)).not.toContain( + EventType.CUSTOM, + ); + expect( + fixture.events.filter((event) => event.type === EventType.RUN_FINISHED), + ).toHaveLength(1); + expect(fixture.errors).toEqual([]); + } finally { + await fixture.teardown(); + } + }); +} + +test("Stop closes partial text and tool calls before its single terminal event", async () => { + const fixture = await setup(true); + try { + const agent = fixture.start(); + await vi.waitFor(() => expect(fixture.pending.length).toBeGreaterThan(0)); + fixture.acceptPending(); + agent.output.next({ + type: EventType.TEXT_MESSAGE_START, + messageId: "message-1", + role: "assistant", + }); + agent.output.next({ + type: EventType.TEXT_MESSAGE_CONTENT, + messageId: "message-1", + delta: "partial", + }); + agent.output.next({ + type: EventType.TOOL_CALL_START, + toolCallId: "tool-1", + toolCallName: "lookup", + }); + agent.output.next({ + type: EventType.TOOL_CALL_ARGS, + toolCallId: "tool-1", + delta: "{}", + }); + await vi.waitFor(() => + expect( + fixture.pending + .flatMap((item) => item.events) + .some((event) => event.type === EventType.TOOL_CALL_ARGS), + ).toBe(true), + ); + fixture.acceptPending(); + + const stopping = fixture.runner + .stop({ threadId: "thread-1" }) + .catch((error) => error); + expect(await fixture.runner.stop({ threadId: "thread-1" })).toBe(false); + await vi.waitFor(() => expect(fixture.pending.length).toBeGreaterThan(0)); + fixture.acceptPending(); + + expect(await stopping).toBe(true); + expect(fixture.events.map((event) => event.type)).toEqual([ + EventType.RUN_STARTED, + EventType.TEXT_MESSAGE_START, + EventType.TEXT_MESSAGE_CONTENT, + EventType.TOOL_CALL_START, + EventType.TOOL_CALL_ARGS, + EventType.TEXT_MESSAGE_END, + EventType.TOOL_CALL_END, + EventType.TOOL_CALL_RESULT, + EventType.RUN_FINISHED, + ]); + expect(fixture.events.at(-1)).toMatchObject({ + threadId: "thread-1", + runId: "run-1", + }); + } finally { + await fixture.teardown(); + } +}); + +test("Stop before channel join never starts the agent and still delivers a terminal", async () => { + const fixture = await setup(); + try { + const agent = fixture.start(); + + const stopping = fixture.runner + .stop({ threadId: "thread-1" }) + .catch((error) => error); + await vi.waitFor(() => expect(fixture.pending.length).toBeGreaterThan(0)); + fixture.acceptPending(); + await vi.waitFor(() => expect(fixture.pending.length).toBeGreaterThan(0)); + fixture.acceptPending(); + + expect(await stopping).toBe(true); + expect(agent.runCount).toBe(0); + expect(fixture.events.map((event) => event.type)).toEqual([ + EventType.RUN_STARTED, + EventType.RUN_FINISHED, + ]); + } finally { + await fixture.teardown(); + } +}); + +test("Stop does not report success if terminal delivery is permanently rejected", async () => { + const fixture = await setup(); + try { + fixture.start(); + await vi.waitFor(() => expect(fixture.pending.length).toBeGreaterThan(0)); + fixture.acceptPending(); + + const stopping = fixture.runner + .stop({ threadId: "thread-1" }) + .catch((error) => error); + await vi.waitFor(() => expect(fixture.pending.length).toBeGreaterThan(0)); + const item = fixture.pending.shift(); + if (!item) throw new Error("Missing terminal request"); + item.socket.send( + JSON.stringify([ + item.frame[0], + item.frame[1], + item.frame[2], + "phx_reply", + { + status: "error", + response: { reason: "active_lock_mismatch", retryable: false }, + }, + ]), + ); + + expect(await stopping).toBeInstanceOf(Error); + expect(fixture.locks.get("thread-1")).toBe("run-1"); + expect(fixture.errors).toHaveLength(1); + expect(fixture.events.map((event) => event.type)).toEqual([ + EventType.RUN_STARTED, + ]); + } finally { + await fixture.teardown(); + } +}); + +test("Stop rejects after the durability deadline instead of acknowledging an unreleased lock", async () => { + const fixture = await setup(); + try { + fixture.start(); + await vi.waitFor(() => expect(fixture.pending.length).toBeGreaterThan(0)); + fixture.acceptPending(); + vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] }); + + const stopping = fixture.runner + .stop({ threadId: "thread-1" }) + .catch((error) => error); + await vi.advanceTimersByTimeAsync(60_000); + + expect(await stopping).toBeInstanceOf(Error); + expect(fixture.locks.get("thread-1")).toBe("run-1"); + expect(await fixture.runner.isRunning({ threadId: "thread-1" })).toBe( + false, + ); + expect(fixture.errors).toHaveLength(1); + } finally { + vi.useRealTimers(); + await fixture.teardown(); + } +}); + +for (const mode of ["multi-route", "single-route"] as const) { + test(`The ${mode} Stop endpoint preserves its response and waits for lock release`, async () => { + const fixture = await setup(); + try { + const agent = fixture.start(); + const runtime = new CopilotRuntime({ + agents: { test: agent }, + runner: fixture.runner, + }); + const handler = createCopilotRuntimeHandler({ + runtime, + mode, + activateChannels: false, + }); + await vi.waitFor(() => expect(fixture.pending.length).toBeGreaterThan(0)); + fixture.acceptPending(); + let responded = false; + const request = + mode === "multi-route" + ? new Request("http://runtime/agent/test/stop/thread-1", { + method: "POST", + }) + : new Request("http://runtime", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + method: "agent/stop", + params: { agentId: "test", threadId: "thread-1" }, + }), + }); + + const stopping = handler(request).then((response) => { + responded = true; + return response; + }); + await vi.waitFor(() => expect(fixture.pending.length).toBeGreaterThan(0)); + expect(responded).toBe(false); + fixture.acceptPending(); + const response = await stopping; + + expect(response.status).toBe(200); + expect(await response.json()).toEqual({ + stopped: true, + interrupt: { + type: EventType.RUN_ERROR, + message: "Run stopped by user", + code: "STOPPED", + }, + }); + expect(fixture.locks.has("thread-1")).toBe(false); + expect(() => fixture.start("run-2")).not.toThrow(); + } finally { + await fixture.teardown(); + } + }); +} diff --git a/packages/runtime/src/v2/runtime/runner/intelligence.ts b/packages/runtime/src/v2/runtime/runner/intelligence.ts index 4df8bdc661..ab3e45e9c2 100644 --- a/packages/runtime/src/v2/runtime/runner/intelligence.ts +++ b/packages/runtime/src/v2/runtime/runner/intelligence.ts @@ -12,6 +12,7 @@ import { finalizeRunEvents, AG_UI_CHANNEL_EVENT, phoenixExponentialBackoff, + logger, } from "@copilotkit/shared"; import type { Channel } from "phoenix"; import { Socket } from "phoenix"; @@ -47,6 +48,11 @@ interface ThreadState { hasJoined: boolean; supportsRunnerEventBatch: boolean; producerFinished: boolean; + cancellation: Promise; + cancelRun: () => void; + completion: Promise; + resolveCompletion: (completed: boolean) => void; + stopTimer: ReturnType | null; pendingEvents: Map< string, { payload: Record; queuedAt: number } @@ -235,6 +241,14 @@ export class IntelligenceAgentRunner extends AgentRunner { run_id: input.runId, }); + let cancelRun!: () => void; + const cancellation = new Promise((resolve) => { + cancelRun = resolve; + }); + let resolveCompletion!: (completed: boolean) => void; + const completion = new Promise((resolve) => { + resolveCompletion = resolve; + }); const state: ThreadState = { threadId, runId: input.runId, @@ -249,6 +263,11 @@ export class IntelligenceAgentRunner extends AgentRunner { hasJoined: false, supportsRunnerEventBatch: false, producerFinished: false, + cancellation, + cancelRun, + completion, + resolveCompletion, + stopTimer: null, pendingEvents: new Map(), activeEventBatch: null, nextEventPushAttempt: 0, @@ -258,7 +277,10 @@ export class IntelligenceAgentRunner extends AgentRunner { socketReconnectWatchdog: null, eventRetryAttempt: 0, completeRun: () => observer.complete(), - failRun: (error) => observer.error(error), + failRun: (error) => { + startupBoundary?.rejectStartup(error); + observer.error(error); + }, }; this.threads.set(threadId, state); @@ -324,7 +346,12 @@ export class IntelligenceAgentRunner extends AgentRunner { payload.type === EventType.CUSTOM && (payload as BaseEvent & { name?: string }).name === "stop" ) { - this.stop({ threadId, runId: state.runId }); + this.stop({ threadId, runId: state.runId }).catch((error) => { + logger.error( + { err: error, threadId, runId: state.runId }, + "Failed to stop Intelligence run", + ); + }); } }); @@ -472,6 +499,7 @@ export class IntelligenceAgentRunner extends AgentRunner { return Promise.resolve(state?.isRunning ?? false); } + /** Stops this run and waits until its terminal events have been acknowledged. */ stop(request: AgentRunnerStopRequest): Promise { const state = this.threads.get(request.threadId); if (!state || !state.isRunning || state.stopRequested) { @@ -483,16 +511,51 @@ export class IntelligenceAgentRunner extends AgentRunner { state.stopRequested = true; + // Fence output before abort: adapters may emit synchronously, throw, or + // ignore cancellation. Finalization must not depend on their cooperation. + state.cancelRun(); + state.stopTimer = setTimeout(() => { + this.failThread( + state.threadId, + state, + new Error("Timed out stopping Intelligence run"), + ); + }, EVENT_DURABILITY_DEADLINE_MS); + // Direct local abort — the runtime is the authority. if (state.agent) { try { state.agent.abortRun(); } catch { - // Ignore abort errors. + // The local run is still fenced and must deliver its terminal events. + } + // Older AG-UI agents may not expose detachActiveRun. The cancellation + // race still finalizes their run without waiting for the producer. + if (typeof state.agent.detachActiveRun === "function") { + try { + Promise.resolve(state.agent.detachActiveRun()).catch((error) => { + logger.warn( + { err: error, threadId: state.threadId, runId: state.runId }, + "Failed to detach stopped agent", + ); + }); + } catch (error) { + logger.warn( + { err: error, threadId: state.threadId, runId: state.runId }, + "Failed to detach stopped agent", + ); + } } } - return Promise.resolve(true); + return state.completion.then((completed) => { + if (!completed) { + throw new Error( + "Intelligence run stopped before terminal events were acknowledged", + ); + } + return true; + }); } private async executeAgentRun( @@ -566,19 +629,26 @@ export class IntelligenceAgentRunner extends AgentRunner { }; try { - await request.agent.runAgent(request.input, { - onEvent: ({ event }: { event: BaseEvent }) => { - if (event.type === EventType.RUN_STARTED) { - pushCanonicalEvent(buildRunStartedEvent(event as RunStartedEvent)); - return; - } + if (state.stopRequested) return; + await Promise.race([ + request.agent.runAgent(request.input, { + onEvent: ({ event }: { event: BaseEvent }) => { + if (state.stopRequested || state.producerFinished) return; + if (event.type === EventType.RUN_STARTED) { + pushCanonicalEvent( + buildRunStartedEvent(event as RunStartedEvent), + ); + return; + } - ensureRunStarted(); - pushCanonicalEvent(event); - }, - }); + ensureRunStarted(); + pushCanonicalEvent(event); + }, + }), + state.cancellation, + ]); } catch (error) { - if (!this.isCurrentThreadState(threadId, state)) { + if (state.stopRequested || !this.isCurrentThreadState(threadId, state)) { return; } ensureRunStarted(); @@ -883,7 +953,7 @@ export class IntelligenceAgentRunner extends AgentRunner { return; } - this.removeThread(threadId, state); + this.removeThread(threadId, state, true); state.completeRun(); } @@ -1000,7 +1070,11 @@ export class IntelligenceAgentRunner extends AgentRunner { * Idempotent — safe to call multiple times for the same threadId * (e.g. from join error handlers, finalize, and Observable teardown). */ - private removeThread(threadId: string, state: ThreadState): void { + private removeThread( + threadId: string, + state: ThreadState, + completed = false, + ): void { if (this.threads.get(threadId) !== state) { return; } @@ -1008,6 +1082,11 @@ export class IntelligenceAgentRunner extends AgentRunner { // Delete first so concurrent calls see the entry as already removed. this.threads.delete(threadId); state.isRunning = false; + state.resolveCompletion(completed); + if (state.stopTimer !== null) { + clearTimeout(state.stopTimer); + state.stopTimer = null; + } this.clearPendingEventRetry(state); this.clearPendingEventFlush(state); if (state.eventDeadlineTimer !== null) { diff --git a/packages/sqlite-runner/package.json b/packages/sqlite-runner/package.json index 2a63fef557..4ea4796d6c 100644 --- a/packages/sqlite-runner/package.json +++ b/packages/sqlite-runner/package.json @@ -54,6 +54,6 @@ } }, "engines": { - "node": ">=18" + "node": ">=20" } } diff --git a/packages/voice/package.json b/packages/voice/package.json index 8ef288ab3d..fe27708260 100644 --- a/packages/voice/package.json +++ b/packages/voice/package.json @@ -50,6 +50,6 @@ "@copilotkit/runtime": "workspace:*" }, "engines": { - "node": ">=18" + "node": ">=20" } } diff --git a/packages/vue/package.json b/packages/vue/package.json index 51699a316f..d944b24501 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -126,6 +126,6 @@ "vue": ">=3.3.0" }, "engines": { - "node": ">=18" + "node": ">=20" } } diff --git a/packages/web-components/package.json b/packages/web-components/package.json index b290e83431..aef87a74d0 100644 --- a/packages/web-components/package.json +++ b/packages/web-components/package.json @@ -80,6 +80,6 @@ "vitest": "^3.2.4" }, "engines": { - "node": ">=18" + "node": ">=20" } } diff --git a/packages/web-inspector/package.json b/packages/web-inspector/package.json index 9b5f2fb2a2..c252644942 100644 --- a/packages/web-inspector/package.json +++ b/packages/web-inspector/package.json @@ -61,6 +61,6 @@ "ws": "^8.19.0" }, "engines": { - "node": ">=18" + "node": ">=20" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ba54748dfd..984bf35879 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,15 +10,13 @@ overrides: '@types/react-dom': ^19.0.2 react: 19.2.3 react-dom: 19.2.3 - next@<=15.4.11: 15.4.11 - next@>=15.5.0 <15.5.15: 15.5.15 + next@>=15.0.0 <15.5.24: 15.5.24 + next@>=16.0.0 <16.3.3: 16.3.3 send@<=0.19.0: 0.19.0 path-to-regexp@<=0.1.12: 0.1.13 serve-static@<=1.16.0: 1.16.0 prismjs@<=1.30.0: 1.30.0 - pino@<=10.1.1: 10.1.1 '@copilotkit/license-verifier': ~0.5.0 - next: ^16.0.10 defu@<=6.1.4: '>=6.1.5' minimatch: '>=9.0.6' minimatch@>=10.0.0 <10.2.1: '>=10.2.1' @@ -67,7 +65,6 @@ overrides: file-type: '>=21.3.1' '@langchain/community': '>=1.1.14' langsmith: '>=0.5.18' - next@>=16.0.0 <16.2.3: 16.2.3 validator: '>=13.15.20' markdown-it: '>=14.1.1' mdast-util-to-hast@>=13.0.0 <13.2.1: '>=13.2.1' @@ -246,8 +243,8 @@ importers: specifier: '>=4.11.7' version: 4.12.15 next: - specifier: 16.2.3 - version: 16.2.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + specifier: 16.3.3 + version: 16.3.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(@types/node@20.19.27)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) openai: specifier: ^6.16.0 version: 6.24.0(ws@8.21.3)(zod@3.25.76) @@ -411,8 +408,8 @@ importers: specifier: ^0.476.0 version: 0.476.0(react@19.2.3) next: - specifier: 16.2.3 - version: 16.2.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + specifier: 16.3.3 + version: 16.3.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(@types/node@20.19.27)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: specifier: 19.2.3 version: 19.2.3 @@ -496,8 +493,8 @@ importers: specifier: ^0.476.0 version: 0.476.0(react@19.2.3) next: - specifier: ^16.0.10 - version: 16.1.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + specifier: 15.5.24 + version: 15.5.24(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: specifier: 19.2.3 version: 19.2.3 @@ -587,11 +584,11 @@ importers: specifier: ^0.3.3 version: 0.3.37(@langchain/anthropic@0.3.34(@langchain/core@1.1.42(@opentelemetry/api@1.9.0)(openai@4.104.0(encoding@0.1.13)(ws@8.21.3)(zod@3.25.76))(ws@8.21.3))(zod@3.25.76))(@langchain/aws@1.1.1(@langchain/core@1.1.42(@opentelemetry/api@1.9.0)(openai@4.104.0(encoding@0.1.13)(ws@8.21.3)(zod@3.25.76))(ws@8.21.3)))(@langchain/core@1.1.42(@opentelemetry/api@1.9.0)(openai@4.104.0(encoding@0.1.13)(ws@8.21.3)(zod@3.25.76))(ws@8.21.3))(@opentelemetry/api@1.9.0)(axios@1.15.2)(encoding@0.1.13)(handlebars@4.7.9)(openai@4.104.0(encoding@0.1.13)(ws@8.21.3)(zod@3.25.76))(ws@8.21.3) next: - specifier: ^16.0.10 - version: 16.1.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + specifier: 15.5.24 + version: 15.5.24(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) next-themes: specifier: ^0.2.1 - version: 0.2.1(next@16.1.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 0.2.1(next@15.5.24(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) openai: specifier: ^4.85.1 version: 4.104.0(encoding@0.1.13)(ws@8.21.3)(zod@3.25.76) @@ -669,8 +666,8 @@ importers: specifier: ^1.2.1 version: 1.2.1 next: - specifier: ^16.0.10 - version: 16.1.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + specifier: 15.5.24 + version: 15.5.24(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) openai: specifier: ^4.85.1 version: 4.104.0(encoding@0.1.13)(ws@8.21.3)(zod@3.25.76) @@ -816,8 +813,8 @@ importers: specifier: ^0.451.0 version: 0.451.0(react@19.2.3) next: - specifier: ^16.0.10 - version: 16.1.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + specifier: 15.5.24 + version: 15.5.24(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) openai: specifier: ^4.85.1 version: 4.104.0(encoding@0.1.13)(ws@8.21.3)(zod@3.25.76) @@ -877,8 +874,8 @@ importers: specifier: ^11.18.1 version: 11.18.2(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) next: - specifier: ^16.0.10 - version: 16.1.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + specifier: 15.5.24 + version: 15.5.24(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: specifier: 19.2.3 version: 19.2.3 @@ -977,8 +974,8 @@ importers: specifier: ^0.414.0 version: 0.414.0(react@19.2.3) next: - specifier: ^16.0.10 - version: 16.1.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + specifier: 15.5.24 + version: 15.5.24(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) openai: specifier: ^4.85.1 version: 4.104.0(encoding@0.1.13)(ws@8.21.3)(zod@3.25.76) @@ -1358,8 +1355,8 @@ importers: specifier: workspace:* version: link:../../../../../packages/runtime next: - specifier: 16.2.3 - version: 16.2.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + specifier: 16.3.3 + version: 16.3.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(@types/node@22.19.11)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: specifier: 19.2.3 version: 19.2.3 @@ -1407,8 +1404,8 @@ importers: specifier: workspace:* version: link:../../../packages/react-core next: - specifier: ^16.0.10 - version: 16.1.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + specifier: 15.5.24 + version: 15.5.24(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: specifier: 19.2.3 version: 19.2.3 @@ -1670,8 +1667,8 @@ importers: specifier: '>=4.11.7' version: 4.12.15 next: - specifier: ^16.0.10 - version: 16.1.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + specifier: 15.5.24 + version: 15.5.24(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) openai: specifier: ^5.9.0 version: 5.23.2(ws@8.21.3)(zod@3.25.76) @@ -1723,13 +1720,13 @@ importers: dependencies: '@storybook/nextjs': specifier: ^10.2.10 - version: 10.3.5(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(next@16.1.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(type-fest@4.41.0)(typescript@5.8.2)(webpack-dev-server@5.2.6(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.15)))(webpack-hot-middleware@2.26.1)(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.15)) + version: 10.3.5(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(next@15.5.24(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(type-fest@4.41.0)(typescript@5.8.2)(webpack-dev-server@5.2.6(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)))(webpack-hot-middleware@2.26.1)(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)) '@storybook/react': specifier: ^10.2.10 version: 10.3.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.8.2) next: - specifier: ^16.0.10 - version: 16.1.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + specifier: 15.5.24 + version: 15.5.24(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: specifier: 19.2.3 version: 19.2.3 @@ -2667,6 +2664,37 @@ importers: specifier: ^4.1.3 version: 4.1.5(@opentelemetry/api@1.9.0)(@types/node@22.19.11)(@vitest/coverage-v8@4.1.5)(jsdom@29.1.0(@noble/hashes@1.8.0))(vite@7.3.2(@types/node@22.19.11)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass@1.101.0)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) + packages/mcp-apps-renderer: + dependencies: + '@copilotkit/shared': + specifier: workspace:* + version: link:../shared + '@modelcontextprotocol/ext-apps': + specifier: ^1.7.5 + version: 1.7.5(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@3.25.76))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(zod@3.25.76) + '@modelcontextprotocol/sdk': + specifier: '>=1.26.0' + version: 1.29.0(@cfworker/json-schema@4.1.1)(zod@3.25.76) + devDependencies: + '@ag-ui/client': + specifier: 0.0.59 + version: 0.0.59 + jsdom: + specifier: ^26.1.0 + version: 26.1.0 + tsdown: + specifier: ^0.20.3 + version: 0.20.3(@arethetypeswrong/core@0.18.2)(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(oxc-resolver@11.24.2)(publint@0.3.17)(synckit@0.11.12)(typescript@5.9.2) + typescript: + specifier: 5.9.2 + version: 5.9.2 + vitest: + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@25.6.0)(@vitest/ui@3.2.4)(jiti@2.7.0)(jsdom@26.1.0)(less@4.6.7)(lightningcss@1.32.0)(sass@1.101.0)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0) + zod: + specifier: '>=3.22.3' + version: 3.25.76 + packages/react-core: dependencies: '@ag-ui/client': @@ -2681,6 +2709,9 @@ importers: '@copilotkit/core': specifier: workspace:* version: link:../core + '@copilotkit/mcp-apps-renderer': + specifier: workspace:* + version: link:../mcp-apps-renderer '@copilotkit/runtime-client-gql': specifier: workspace:* version: link:../runtime-client-gql @@ -2696,9 +2727,6 @@ importers: '@jetbrains/websandbox': specifier: ^1.1.3 version: 1.1.3 - '@modelcontextprotocol/ext-apps': - specifier: ^1.7.5 - version: 1.7.5(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@3.25.76))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(zod@3.25.76) '@radix-ui/react-dropdown-menu': specifier: ^2.1.15 version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -2754,9 +2782,6 @@ importers: specifier: ^3.24.5 version: 3.25.1(zod@3.25.76) devDependencies: - '@modelcontextprotocol/sdk': - specifier: '>=1.26.0' - version: 1.29.0(@cfworker/json-schema@4.1.1)(zod@3.25.76) '@tailwindcss/cli': specifier: ^4.1.11 version: 4.1.18 @@ -3244,7 +3269,7 @@ importers: specifier: ^1.8.4 version: 1.8.4 pino: - specifier: 10.1.1 + specifier: ^10.1.1 version: 10.1.1 pino-pretty: specifier: ^11.2.1 @@ -7138,6 +7163,9 @@ packages: '@emnapi/runtime@1.11.2': resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} + '@emnapi/runtime@1.4.5': resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==} @@ -8070,6 +8098,10 @@ packages: resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} engines: {node: '>=18'} + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + '@img/sharp-darwin-arm64@0.33.5': resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -8082,6 +8114,12 @@ packages: cpu: [arm64] os: [darwin] + '@img/sharp-darwin-arm64@0.35.4': + resolution: {integrity: sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [darwin] + '@img/sharp-darwin-x64@0.33.5': resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -8094,6 +8132,17 @@ packages: cpu: [x64] os: [darwin] + '@img/sharp-darwin-x64@0.35.4': + resolution: {integrity: sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [darwin] + + '@img/sharp-freebsd-wasm32@0.35.4': + resolution: {integrity: sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==} + engines: {node: '>=20.9.0'} + os: [freebsd] + '@img/sharp-libvips-darwin-arm64@1.0.4': resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} cpu: [arm64] @@ -8104,6 +8153,11 @@ packages: cpu: [arm64] os: [darwin] + '@img/sharp-libvips-darwin-arm64@1.3.3': + resolution: {integrity: sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==} + cpu: [arm64] + os: [darwin] + '@img/sharp-libvips-darwin-x64@1.0.4': resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} cpu: [x64] @@ -8114,6 +8168,11 @@ packages: cpu: [x64] os: [darwin] + '@img/sharp-libvips-darwin-x64@1.3.3': + resolution: {integrity: sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==} + cpu: [x64] + os: [darwin] + '@img/sharp-libvips-linux-arm64@1.0.4': resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} cpu: [arm64] @@ -8126,6 +8185,12 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-libvips-linux-arm64@1.3.3': + resolution: {integrity: sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@img/sharp-libvips-linux-arm@1.0.5': resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} cpu: [arm] @@ -8138,18 +8203,36 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-libvips-linux-arm@1.3.3': + resolution: {integrity: sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==} + cpu: [arm] + os: [linux] + libc: [glibc] + '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] libc: [glibc] + '@img/sharp-libvips-linux-ppc64@1.3.3': + resolution: {integrity: sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] libc: [glibc] + '@img/sharp-libvips-linux-riscv64@1.3.3': + resolution: {integrity: sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + '@img/sharp-libvips-linux-s390x@1.0.4': resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} cpu: [s390x] @@ -8162,6 +8245,12 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-libvips-linux-s390x@1.3.3': + resolution: {integrity: sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@img/sharp-libvips-linux-x64@1.0.4': resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} cpu: [x64] @@ -8174,6 +8263,12 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-libvips-linux-x64@1.3.3': + resolution: {integrity: sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==} + cpu: [x64] + os: [linux] + libc: [glibc] + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} cpu: [arm64] @@ -8186,6 +8281,12 @@ packages: os: [linux] libc: [musl] + '@img/sharp-libvips-linuxmusl-arm64@1.3.3': + resolution: {integrity: sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==} + cpu: [arm64] + os: [linux] + libc: [musl] + '@img/sharp-libvips-linuxmusl-x64@1.0.4': resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} cpu: [x64] @@ -8198,6 +8299,12 @@ packages: os: [linux] libc: [musl] + '@img/sharp-libvips-linuxmusl-x64@1.3.3': + resolution: {integrity: sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==} + cpu: [x64] + os: [linux] + libc: [musl] + '@img/sharp-linux-arm64@0.33.5': resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -8212,6 +8319,13 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-linux-arm64@0.35.4': + resolution: {integrity: sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@img/sharp-linux-arm@0.33.5': resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -8226,6 +8340,13 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-linux-arm@0.35.4': + resolution: {integrity: sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==} + engines: {node: '>=20.9.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -8233,6 +8354,13 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-linux-ppc64@0.35.4': + resolution: {integrity: sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==} + engines: {node: '>=20.9.0'} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -8240,6 +8368,13 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-linux-riscv64@0.35.4': + resolution: {integrity: sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==} + engines: {node: '>=20.9.0'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + '@img/sharp-linux-s390x@0.33.5': resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -8254,6 +8389,13 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-linux-s390x@0.35.4': + resolution: {integrity: sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==} + engines: {node: '>=20.9.0'} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@img/sharp-linux-x64@0.33.5': resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -8268,6 +8410,13 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-linux-x64@0.35.4': + resolution: {integrity: sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + '@img/sharp-linuxmusl-arm64@0.33.5': resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -8282,6 +8431,13 @@ packages: os: [linux] libc: [musl] + '@img/sharp-linuxmusl-arm64@0.35.4': + resolution: {integrity: sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + '@img/sharp-linuxmusl-x64@0.33.5': resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -8296,6 +8452,13 @@ packages: os: [linux] libc: [musl] + '@img/sharp-linuxmusl-x64@0.35.4': + resolution: {integrity: sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [linux] + libc: [musl] + '@img/sharp-wasm32@0.33.5': resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -8306,12 +8469,27 @@ packages: engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] + '@img/sharp-wasm32@0.35.4': + resolution: {integrity: sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==} + engines: {node: '>=20.9.0'} + + '@img/sharp-webcontainers-wasm32@0.35.4': + resolution: {integrity: sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==} + engines: {node: '>=20.9.0'} + cpu: [wasm32] + '@img/sharp-win32-arm64@0.34.5': resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [win32] + '@img/sharp-win32-arm64@0.35.4': + resolution: {integrity: sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [win32] + '@img/sharp-win32-ia32@0.33.5': resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -8324,6 +8502,12 @@ packages: cpu: [ia32] os: [win32] + '@img/sharp-win32-ia32@0.35.4': + resolution: {integrity: sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==} + engines: {node: ^20.9.0} + cpu: [ia32] + os: [win32] + '@img/sharp-win32-x64@0.33.5': resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -8336,6 +8520,12 @@ packages: cpu: [x64] os: [win32] + '@img/sharp-win32-x64@0.35.4': + resolution: {integrity: sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [win32] + '@inquirer/ansi@1.0.2': resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} engines: {node: '>=18'} @@ -9966,11 +10156,11 @@ packages: '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4 '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.4 - '@next/env@16.1.3': - resolution: {integrity: sha512-BLP14oBOvZWXgfdJf9ao+VD8O30uE+x7PaV++QtACLX329WcRSJRO5YJ+Bcvu0Q+c/lei41TjSiFf6pXqnpbQA==} + '@next/env@15.5.24': + resolution: {integrity: sha512-mBDF7T0XKZjs9SpUAl0buizVO+O02ULjOvWX8o/AZo/5AGw/UAS1Zzcylmd4pqbftzmKQi+L/nB4jgBYKEAl5Q==} - '@next/env@16.2.3': - resolution: {integrity: sha512-ZWXyj4uNu4GCWQw9cjRxWlbD+33mcDszIo9iQxFnBX3Wmgq9ulaSJcl6VhuWx5pCWqqD+9W6Wfz7N0lM5lYPMA==} + '@next/env@16.3.3': + resolution: {integrity: sha512-U2eYQRwXj+dsqxV79zFqExDdatnNY/ZWc2nsJU1p/OgT7fd3dXwlF6OjYaFQCfMoeTA19PWq+wVmYgimVA+V+g==} '@next/eslint-plugin-next@15.4.4': resolution: {integrity: sha512-1FDsyN//ai3Jd97SEd7scw5h1yLdzDACGOPRofr2GD3sEFsBylEEoL0MHSerd4n2dq9Zm/mFMqi4+NRMOreOKA==} @@ -9978,106 +10168,106 @@ packages: '@next/eslint-plugin-next@16.0.8': resolution: {integrity: sha512-1miV0qXDcLUaOdHridVPCh4i39ElRIAraseVIbb3BEqyZ5ol9sPyjTP/GNTPV5rBxqxjF6/vv5zQTVbhiNaLqA==} - '@next/swc-darwin-arm64@16.1.3': - resolution: {integrity: sha512-CpOD3lmig6VflihVoGxiR/l5Jkjfi4uLaOR4ziriMv0YMDoF6cclI+p5t2nstM8TmaFiY6PCTBgRWB57/+LiBA==} + '@next/swc-darwin-arm64@15.5.24': + resolution: {integrity: sha512-AGdNLvxZNY6eR2iSnV+6wUa8CiHTMr4F7g3uHH7fT4ICIJBE00R9u4tzN/Vuwsw0cOi8MTD2HJcTCb6siMH88Q==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-arm64@16.2.3': - resolution: {integrity: sha512-u37KDKTKQ+OQLvY+z7SNXixwo4Q2/IAJFDzU1fYe66IbCE51aDSAzkNDkWmLN0yjTUh4BKBd+hb69jYn6qqqSg==} + '@next/swc-darwin-arm64@16.3.3': + resolution: {integrity: sha512-8Hiv32QJPwdV6KYJ8meR9SBA061tQqnIKTJDocvOXlEQqib0xMFpzArosuffFUUc0sslbh7QQ8a3Yey1QV8EIw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.1.3': - resolution: {integrity: sha512-aF4us2JXh0zn3hNxvL1Bx3BOuh8Lcw3p3Xnurlvca/iptrDH1BrpObwkw9WZra7L7/0qB9kjlREq3hN/4x4x+Q==} + '@next/swc-darwin-x64@15.5.24': + resolution: {integrity: sha512-9HrQajBMmGcrrrvDfRimiCrbAPh3E6uHJmwBovYr6Yrmi9p9PZqI876BrXX280wICh3o2XwUlp4blkB0NNBqFg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-darwin-x64@16.2.3': - resolution: {integrity: sha512-gHjL/qy6Q6CG3176FWbAKyKh9IfntKZTB3RY/YOJdDFpHGsUDXVH38U4mMNpHVGXmeYW4wj22dMp1lTfmu/bTQ==} + '@next/swc-darwin-x64@16.3.3': + resolution: {integrity: sha512-A1lgKgwVchRYmSe467zdwhxT9040dd8lH+o65sL5Jet8fjB4kegw/rDyPIpYVRb6jAqwXFOJpjIXJLxQKLiE3A==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.1.3': - resolution: {integrity: sha512-8VRkcpcfBtYvhGgXAF7U3MBx6+G1lACM1XCo1JyaUr4KmAkTNP8Dv2wdMq7BI+jqRBw3zQE7c57+lmp7jCFfKA==} + '@next/swc-linux-arm64-gnu@15.5.24': + resolution: {integrity: sha512-rl9LSfE75si0WT3cDgdUC1XYCKS+TgxC+/IjitmeycrAG18X/plIP1/vy8dd/HPycYcIvE688PD7FuvEAiEAew==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [glibc] - '@next/swc-linux-arm64-gnu@16.2.3': - resolution: {integrity: sha512-U6vtblPtU/P14Y/b/n9ZY0GOxbbIhTFuaFR7F4/uMBidCi2nSdaOFhA0Go81L61Zd6527+yvuX44T4ksnf8T+Q==} + '@next/swc-linux-arm64-gnu@16.3.3': + resolution: {integrity: sha512-bf0FIssMFueU2dm7vQEWWxk0c8UjKTdW0yzuh0sQsD8pf1+KCLDdaqhYZNMYGmXwEOiHAUzgBKudovIlcvvBjg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [glibc] - '@next/swc-linux-arm64-musl@16.1.3': - resolution: {integrity: sha512-UbFx69E2UP7MhzogJRMFvV9KdEn4sLGPicClwgqnLht2TEi204B71HuVfps3ymGAh0c44QRAF+ZmvZZhLLmhNg==} + '@next/swc-linux-arm64-musl@15.5.24': + resolution: {integrity: sha512-TlNAnpsjxSF3aAUtqnfmtXXf8m9sIDBlmF3c7bTAlnshUYu2U0OxN2uf5d0gcFwqHVEdivJNBcCaqNOwPGNimw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [musl] - '@next/swc-linux-arm64-musl@16.2.3': - resolution: {integrity: sha512-/YV0LgjHUmfhQpn9bVoGc4x4nan64pkhWR5wyEV8yCOfwwrH630KpvRg86olQHTwHIn1z59uh6JwKvHq1h4QEw==} + '@next/swc-linux-arm64-musl@16.3.3': + resolution: {integrity: sha512-W7viwCk9JY/cAkdz/A273rd5bb3RgT/IHwR7Upv90tunjBWNtAAhGhoecHh+teRNRSinuAFmE+l7fwZ4YKkrXg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [musl] - '@next/swc-linux-x64-gnu@16.1.3': - resolution: {integrity: sha512-SzGTfTjR5e9T+sZh5zXqG/oeRQufExxBF6MssXS7HPeZFE98JDhCRZXpSyCfWrWrYrzmnw/RVhlP2AxQm+wkRQ==} + '@next/swc-linux-x64-gnu@15.5.24': + resolution: {integrity: sha512-7dwtlhr0SLndqTG1z9ncRkbJswDZiKWlxzFyXDvJ2RDZRDRHp8zyMJ4D9UH/FgnQeXxxB6gZy2pMcIUoNKQ4pA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [glibc] - '@next/swc-linux-x64-gnu@16.2.3': - resolution: {integrity: sha512-/HiWEcp+WMZ7VajuiMEFGZ6cg0+aYZPqCJD3YJEfpVWQsKYSjXQG06vJP6F1rdA03COD9Fef4aODs3YxKx+RDQ==} + '@next/swc-linux-x64-gnu@16.3.3': + resolution: {integrity: sha512-0W46zw1N3ODpI6n0GeivHvvob1pooozgZVqy65k0mh4/7vr+FbY9+WpHzNVXjHipJf/A3FDheBG19H1s5A25rA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [glibc] - '@next/swc-linux-x64-musl@16.1.3': - resolution: {integrity: sha512-HlrDpj0v+JBIvQex1mXHq93Mht5qQmfyci+ZNwGClnAQldSfxI6h0Vupte1dSR4ueNv4q7qp5kTnmLOBIQnGow==} + '@next/swc-linux-x64-musl@15.5.24': + resolution: {integrity: sha512-kGZxM+WhkYs0276lFrMkj7PRtXT3Btp6cwvfSO/cCVLxJJttB5Ccnl2niaCgUja8HgSbEVnMHpg3FJWoOJ9e/g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [musl] - '@next/swc-linux-x64-musl@16.2.3': - resolution: {integrity: sha512-Kt44hGJfZSefebhk/7nIdivoDr3Ugp5+oNz9VvF3GUtfxutucUIHfIO0ZYO8QlOPDQloUVQn4NVC/9JvHRk9hw==} + '@next/swc-linux-x64-musl@16.3.3': + resolution: {integrity: sha512-H4mBso8ZTMBPtdT0PN0pBx2ayTvQuTuvS6qT13d77yVFJXAPCxkyIhLTmdMaGTJs0krQYI/qpzdHijCeihXhbg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [musl] - '@next/swc-win32-arm64-msvc@16.1.3': - resolution: {integrity: sha512-3gFCp83/LSduZMSIa+lBREP7+5e7FxpdBoc9QrCdmp+dapmTK9I+SLpY60Z39GDmTXSZA4huGg9WwmYbr6+WRw==} + '@next/swc-win32-arm64-msvc@15.5.24': + resolution: {integrity: sha512-jBDDkZ/qKAqkWivWDMkJSXUzbzV0QKRBKJjEHUAvSB97Hzw7NLzJ6yV56Lts/wjir7s4P31GYgpbS6ZL+hasAA==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-arm64-msvc@16.2.3': - resolution: {integrity: sha512-O2NZ9ie3Tq6xj5Z5CSwBT3+aWAMW2PIZ4egUi9MaWLkwaehgtB7YZjPm+UpcNpKOme0IQuqDcor7BsW6QBiQBw==} + '@next/swc-win32-arm64-msvc@16.3.3': + resolution: {integrity: sha512-cTMUJpcEGmeywofCUfhR+rSsoE33+rVPnPEYNTNdLNlsOeEg/vktOsKUSTb28vUGqD2jkm4Zaskcwn7OCI6FQg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.1.3': - resolution: {integrity: sha512-1SZVfFT8zmMB+Oblrh5OKDvUo5mYQOkX2We6VGzpg7JUVZlqe4DYOFGKYZKTweSx1gbMixyO1jnFT4thU+nNHQ==} + '@next/swc-win32-x64-msvc@15.5.24': + resolution: {integrity: sha512-JqtwjvvorjacQ0spgjmUJoxySoYgPwdT1sFdQ0/zmW4iMlP2hjYlCoJIyS7o6Epb4Fug8eco3HXFoBDUCDeH7Q==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@next/swc-win32-x64-msvc@16.2.3': - resolution: {integrity: sha512-Ibm29/GgB/ab5n7XKqlStkm54qqZE8v2FnijUPBgrd67FWrac45o/RsNlaOWjme/B5UqeWt/8KM4aWBwA1D2Kw==} + '@next/swc-win32-x64-msvc@16.3.3': + resolution: {integrity: sha512-2VR4cTBzHXaBjnGsuH6GyJjENzQOmHeAh11uY1iUhjm3j5dEUrVJuUj+VL78jaGi/Dik8xS76zEj18BsFhlVZQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -13909,6 +14099,9 @@ packages: '@swc/helpers@0.5.18': resolution: {integrity: sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==} + '@swc/helpers@0.5.23': + resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} + '@swc/types@0.1.25': resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==} @@ -21891,9 +22084,9 @@ packages: react: 19.2.3 react-dom: 19.2.3 - next@16.1.3: - resolution: {integrity: sha512-gthG3TRD+E3/mA0uDQb9lqBmx1zVosq5kIwxNN6+MRNd085GzD+9VXMPUs+GGZCbZ+GDZdODUq4Pm7CTXK6ipw==} - engines: {node: '>=20.9.0'} + next@15.5.24: + resolution: {integrity: sha512-Y+xn8EQCoC3ZbsFPyzE+tE8XOdrWeUdUF7NeXbmg9DsgAxl5UYxlsrvgVESHTyTGigoTa1bCUrxn70F5bqt0Gw==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 @@ -21912,8 +22105,8 @@ packages: sass: optional: true - next@16.2.3: - resolution: {integrity: sha512-9V3zV4oZFza3PVev5/poB9g0dEafVcgNyQ8eTRop8GvxZjV2G15FC5ARuG1eFD42QgeYkzJBJzHghNP8Ad9xtA==} + next@16.3.3: + resolution: {integrity: sha512-tuRTx1nQ/yVw83cwJBo9F+njGUgMn3UHQycreWHB8XsStvvAh1AthbI8/4IpKnFaF58F+iSiHejYOlMQ/eq83g==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -23310,6 +23503,10 @@ packages: resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.23: + resolution: {integrity: sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==} + engines: {node: ^10 || ^12 || >=14} + postcss@8.5.25: resolution: {integrity: sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==} engines: {node: ^10 || ^12 || >=14} @@ -24578,6 +24775,15 @@ packages: resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + sharp@0.35.4: + resolution: {integrity: sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==} + engines: {node: '>=20.9.0'} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -33003,6 +33209,11 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.11.3': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.4.5': dependencies: tslib: 2.8.1 @@ -34377,6 +34588,9 @@ snapshots: '@img/colour@1.0.0': {} + '@img/colour@1.1.0': + optional: true + '@img/sharp-darwin-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.0.4 @@ -34387,6 +34601,11 @@ snapshots: '@img/sharp-libvips-darwin-arm64': 1.2.4 optional: true + '@img/sharp-darwin-arm64@0.35.4': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.3.3 + optional: true + '@img/sharp-darwin-x64@0.33.5': optionalDependencies: '@img/sharp-libvips-darwin-x64': 1.0.4 @@ -34397,60 +34616,100 @@ snapshots: '@img/sharp-libvips-darwin-x64': 1.2.4 optional: true + '@img/sharp-darwin-x64@0.35.4': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.3.3 + optional: true + + '@img/sharp-freebsd-wasm32@0.35.4': + dependencies: + '@img/sharp-wasm32': 0.35.4 + optional: true + '@img/sharp-libvips-darwin-arm64@1.0.4': optional: true '@img/sharp-libvips-darwin-arm64@1.2.4': optional: true + '@img/sharp-libvips-darwin-arm64@1.3.3': + optional: true + '@img/sharp-libvips-darwin-x64@1.0.4': optional: true '@img/sharp-libvips-darwin-x64@1.2.4': optional: true + '@img/sharp-libvips-darwin-x64@1.3.3': + optional: true + '@img/sharp-libvips-linux-arm64@1.0.4': optional: true '@img/sharp-libvips-linux-arm64@1.2.4': optional: true + '@img/sharp-libvips-linux-arm64@1.3.3': + optional: true + '@img/sharp-libvips-linux-arm@1.0.5': optional: true '@img/sharp-libvips-linux-arm@1.2.4': optional: true + '@img/sharp-libvips-linux-arm@1.3.3': + optional: true + '@img/sharp-libvips-linux-ppc64@1.2.4': optional: true + '@img/sharp-libvips-linux-ppc64@1.3.3': + optional: true + '@img/sharp-libvips-linux-riscv64@1.2.4': optional: true + '@img/sharp-libvips-linux-riscv64@1.3.3': + optional: true + '@img/sharp-libvips-linux-s390x@1.0.4': optional: true '@img/sharp-libvips-linux-s390x@1.2.4': optional: true + '@img/sharp-libvips-linux-s390x@1.3.3': + optional: true + '@img/sharp-libvips-linux-x64@1.0.4': optional: true '@img/sharp-libvips-linux-x64@1.2.4': optional: true + '@img/sharp-libvips-linux-x64@1.3.3': + optional: true + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': optional: true '@img/sharp-libvips-linuxmusl-arm64@1.2.4': optional: true + '@img/sharp-libvips-linuxmusl-arm64@1.3.3': + optional: true + '@img/sharp-libvips-linuxmusl-x64@1.0.4': optional: true '@img/sharp-libvips-linuxmusl-x64@1.2.4': optional: true + '@img/sharp-libvips-linuxmusl-x64@1.3.3': + optional: true + '@img/sharp-linux-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-linux-arm64': 1.0.4 @@ -34461,6 +34720,11 @@ snapshots: '@img/sharp-libvips-linux-arm64': 1.2.4 optional: true + '@img/sharp-linux-arm64@0.35.4': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.3.3 + optional: true + '@img/sharp-linux-arm@0.33.5': optionalDependencies: '@img/sharp-libvips-linux-arm': 1.0.5 @@ -34471,16 +34735,31 @@ snapshots: '@img/sharp-libvips-linux-arm': 1.2.4 optional: true + '@img/sharp-linux-arm@0.35.4': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.3.3 + optional: true + '@img/sharp-linux-ppc64@0.34.5': optionalDependencies: '@img/sharp-libvips-linux-ppc64': 1.2.4 optional: true + '@img/sharp-linux-ppc64@0.35.4': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.3.3 + optional: true + '@img/sharp-linux-riscv64@0.34.5': optionalDependencies: '@img/sharp-libvips-linux-riscv64': 1.2.4 optional: true + '@img/sharp-linux-riscv64@0.35.4': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.3.3 + optional: true + '@img/sharp-linux-s390x@0.33.5': optionalDependencies: '@img/sharp-libvips-linux-s390x': 1.0.4 @@ -34491,6 +34770,11 @@ snapshots: '@img/sharp-libvips-linux-s390x': 1.2.4 optional: true + '@img/sharp-linux-s390x@0.35.4': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.3.3 + optional: true + '@img/sharp-linux-x64@0.33.5': optionalDependencies: '@img/sharp-libvips-linux-x64': 1.0.4 @@ -34501,6 +34785,11 @@ snapshots: '@img/sharp-libvips-linux-x64': 1.2.4 optional: true + '@img/sharp-linux-x64@0.35.4': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.3.3 + optional: true + '@img/sharp-linuxmusl-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 @@ -34511,6 +34800,11 @@ snapshots: '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 optional: true + '@img/sharp-linuxmusl-arm64@0.35.4': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.3.3 + optional: true + '@img/sharp-linuxmusl-x64@0.33.5': optionalDependencies: '@img/sharp-libvips-linuxmusl-x64': 1.0.4 @@ -34521,6 +34815,11 @@ snapshots: '@img/sharp-libvips-linuxmusl-x64': 1.2.4 optional: true + '@img/sharp-linuxmusl-x64@0.35.4': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.3.3 + optional: true + '@img/sharp-wasm32@0.33.5': dependencies: '@emnapi/runtime': 1.11.2 @@ -34531,21 +34830,40 @@ snapshots: '@emnapi/runtime': 1.8.1 optional: true + '@img/sharp-wasm32@0.35.4': + dependencies: + '@emnapi/runtime': 1.11.3 + optional: true + + '@img/sharp-webcontainers-wasm32@0.35.4': + dependencies: + '@img/sharp-wasm32': 0.35.4 + optional: true + '@img/sharp-win32-arm64@0.34.5': optional: true + '@img/sharp-win32-arm64@0.35.4': + optional: true + '@img/sharp-win32-ia32@0.33.5': optional: true '@img/sharp-win32-ia32@0.34.5': optional: true + '@img/sharp-win32-ia32@0.35.4': + optional: true + '@img/sharp-win32-x64@0.33.5': optional: true '@img/sharp-win32-x64@0.34.5': optional: true + '@img/sharp-win32-x64@0.35.4': + optional: true + '@inquirer/ansi@1.0.2': {} '@inquirer/ansi@2.0.7': {} @@ -36813,9 +37131,9 @@ snapshots: '@tybys/wasm-util': 0.10.3 optional: true - '@next/env@16.1.3': {} + '@next/env@15.5.24': {} - '@next/env@16.2.3': {} + '@next/env@16.3.3': {} '@next/eslint-plugin-next@15.4.4': dependencies: @@ -36825,52 +37143,52 @@ snapshots: dependencies: fast-glob: 3.3.1 - '@next/swc-darwin-arm64@16.1.3': + '@next/swc-darwin-arm64@15.5.24': optional: true - '@next/swc-darwin-arm64@16.2.3': + '@next/swc-darwin-arm64@16.3.3': optional: true - '@next/swc-darwin-x64@16.1.3': + '@next/swc-darwin-x64@15.5.24': optional: true - '@next/swc-darwin-x64@16.2.3': + '@next/swc-darwin-x64@16.3.3': optional: true - '@next/swc-linux-arm64-gnu@16.1.3': + '@next/swc-linux-arm64-gnu@15.5.24': optional: true - '@next/swc-linux-arm64-gnu@16.2.3': + '@next/swc-linux-arm64-gnu@16.3.3': optional: true - '@next/swc-linux-arm64-musl@16.1.3': + '@next/swc-linux-arm64-musl@15.5.24': optional: true - '@next/swc-linux-arm64-musl@16.2.3': + '@next/swc-linux-arm64-musl@16.3.3': optional: true - '@next/swc-linux-x64-gnu@16.1.3': + '@next/swc-linux-x64-gnu@15.5.24': optional: true - '@next/swc-linux-x64-gnu@16.2.3': + '@next/swc-linux-x64-gnu@16.3.3': optional: true - '@next/swc-linux-x64-musl@16.1.3': + '@next/swc-linux-x64-musl@15.5.24': optional: true - '@next/swc-linux-x64-musl@16.2.3': + '@next/swc-linux-x64-musl@16.3.3': optional: true - '@next/swc-win32-arm64-msvc@16.1.3': + '@next/swc-win32-arm64-msvc@15.5.24': optional: true - '@next/swc-win32-arm64-msvc@16.2.3': + '@next/swc-win32-arm64-msvc@16.3.3': optional: true - '@next/swc-win32-x64-msvc@16.1.3': + '@next/swc-win32-x64-msvc@15.5.24': optional: true - '@next/swc-win32-x64-msvc@16.2.3': + '@next/swc-win32-x64-msvc@16.3.3': optional: true '@ngtools/webpack@22.1.5(@angular/compiler-cli@22.0.0(@angular/compiler@22.0.0)(typescript@6.0.3))(typescript@6.0.3)(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.6))': @@ -38155,6 +38473,22 @@ snapshots: webpack-dev-server: 5.2.6(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.15)) webpack-hot-middleware: 2.26.1 + '@pmmmwh/react-refresh-webpack-plugin@0.5.17(react-refresh@0.14.2)(type-fest@4.41.0)(webpack-dev-server@5.2.6(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)))(webpack-hot-middleware@2.26.1)(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15))': + dependencies: + ansi-html: 0.0.9 + core-js-pure: 3.47.0 + error-stack-parser: 2.1.4 + html-entities: 2.6.0 + loader-utils: 2.0.4 + react-refresh: 0.14.2 + schema-utils: 4.3.3 + source-map: 0.7.6 + webpack: 5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15) + optionalDependencies: + type-fest: 4.41.0 + webpack-dev-server: 5.2.6(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)) + webpack-hot-middleware: 2.26.1 + '@polka/url@1.0.0-next.29': {} '@popperjs/core@2.11.8': {} @@ -40739,6 +41073,33 @@ snapshots: - uglify-js - webpack-cli + '@storybook/builder-webpack5@10.3.5(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.8.2)': + dependencies: + '@storybook/core-webpack': 10.3.5(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + case-sensitive-paths-webpack-plugin: 2.4.0 + cjs-module-lexer: 1.4.3 + css-loader: 7.1.2(webpack@5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)) + es-module-lexer: 1.7.0 + fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.8.2)(webpack@5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)) + html-webpack-plugin: 5.6.5(webpack@5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)) + magic-string: 0.30.21 + storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + style-loader: 4.0.0(webpack@5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)) + terser-webpack-plugin: 5.3.16(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(webpack@5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)) + ts-dedent: 2.2.0 + webpack: 5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3) + webpack-dev-middleware: 6.1.3(webpack@5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)) + webpack-hot-middleware: 2.26.1 + webpack-virtual-modules: 0.6.2 + optionalDependencies: + typescript: 5.8.2 + transitivePeerDependencies: + - '@rspack/core' + - '@swc/core' + - esbuild + - uglify-js + - webpack-cli + '@storybook/builder-webpack5@10.6.0(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.6)(storybook@10.6.0(@types/react@19.1.8)(prettier@3.7.4)(react@19.2.3))(typescript@6.0.3)': dependencies: '@storybook/core-webpack': 10.6.0(storybook@10.6.0(@types/react@19.1.8)(prettier@3.7.4)(react@19.2.3)) @@ -40827,7 +41188,7 @@ snapshots: dependencies: react: 19.2.3 - '@storybook/nextjs@10.3.5(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(next@16.1.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(type-fest@4.41.0)(typescript@5.8.2)(webpack-dev-server@5.2.6(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.15)))(webpack-hot-middleware@2.26.1)(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.15))': + '@storybook/nextjs@10.3.5(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(next@15.5.24(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(type-fest@4.41.0)(typescript@5.8.2)(webpack-dev-server@5.2.6(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)))(webpack-hot-middleware@2.26.1)(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15))': dependencies: '@babel/core': 7.28.5 '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.5) @@ -40842,33 +41203,33 @@ snapshots: '@babel/preset-react': 7.28.5(@babel/core@7.28.5) '@babel/preset-typescript': 7.28.5(@babel/core@7.28.5) '@babel/runtime': 7.29.2 - '@pmmmwh/react-refresh-webpack-plugin': 0.5.17(react-refresh@0.14.2)(type-fest@4.41.0)(webpack-dev-server@5.2.6(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.15)))(webpack-hot-middleware@2.26.1)(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.15)) - '@storybook/builder-webpack5': 10.3.5(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.8.2) - '@storybook/preset-react-webpack': 10.3.5(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.8.2) + '@pmmmwh/react-refresh-webpack-plugin': 0.5.17(react-refresh@0.14.2)(type-fest@4.41.0)(webpack-dev-server@5.2.6(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)))(webpack-hot-middleware@2.26.1)(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)) + '@storybook/builder-webpack5': 10.3.5(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.8.2) + '@storybook/preset-react-webpack': 10.3.5(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.8.2) '@storybook/react': 10.3.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.8.2) '@types/semver': 7.7.1 - babel-loader: 9.2.1(@babel/core@7.28.5)(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.15)) - css-loader: 6.11.0(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.15)) + babel-loader: 9.2.1(@babel/core@7.28.5)(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)) + css-loader: 6.11.0(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)) image-size: 2.0.2 loader-utils: 3.3.1 - next: 16.1.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) - node-polyfill-webpack-plugin: 2.0.1(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.15)) + next: 15.5.24(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + node-polyfill-webpack-plugin: 2.0.1(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)) postcss: 8.5.15 - postcss-loader: 8.2.0(postcss@8.5.15)(typescript@5.8.2)(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.15)) + postcss-loader: 8.2.0(postcss@8.5.15)(typescript@5.8.2)(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) react-refresh: 0.14.2 resolve-url-loader: 5.0.0 - sass-loader: 16.0.6(sass@1.101.0)(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.15)) + sass-loader: 16.0.6(sass@1.101.0)(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)) semver: 7.7.4 storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - style-loader: 3.3.4(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.15)) + style-loader: 3.3.4(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)) styled-jsx: 5.1.7(@babel/core@7.28.5)(react@19.2.3) tsconfig-paths: 4.2.0 tsconfig-paths-webpack-plugin: 4.2.0 optionalDependencies: typescript: 5.8.2 - webpack: 5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.15) + webpack: 5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15) transitivePeerDependencies: - '@rspack/core' - '@swc/core' @@ -40933,6 +41294,29 @@ snapshots: - uglify-js - webpack-cli + '@storybook/preset-react-webpack@10.3.5(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.8.2)': + dependencies: + '@storybook/core-webpack': 10.3.5(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.8.2)(webpack@5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)) + '@types/semver': 7.7.1 + magic-string: 0.30.21 + react: 19.2.3 + react-docgen: 7.1.1 + react-dom: 19.2.3(react@19.2.3) + resolve: 1.22.11 + semver: 7.8.5 + storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + tsconfig-paths: 4.2.0 + webpack: 5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3) + optionalDependencies: + typescript: 5.8.2 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - supports-color + - uglify-js + - webpack-cli + '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.8.2)(webpack@5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3))': dependencies: debug: 4.4.3(supports-color@5.5.0) @@ -40947,6 +41331,20 @@ snapshots: transitivePeerDependencies: - supports-color + '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.8.2)(webpack@5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3))': + dependencies: + debug: 4.4.3(supports-color@5.5.0) + endent: 2.1.0 + find-cache-dir: 3.3.2 + flat-cache: 3.2.0 + micromatch: 4.0.8 + react-docgen-typescript: 2.4.0(typescript@5.8.2) + tslib: 2.8.1 + typescript: 5.8.2 + webpack: 5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3) + transitivePeerDependencies: + - supports-color + '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.9.2)(webpack@5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3))': dependencies: debug: 4.4.3(supports-color@5.5.0) @@ -41127,6 +41525,23 @@ snapshots: '@swc/core-win32-x64-msvc': 1.15.8 '@swc/helpers': 0.5.18 + '@swc/core@1.15.8(@swc/helpers@0.5.23)': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.25 + optionalDependencies: + '@swc/core-darwin-arm64': 1.15.8 + '@swc/core-darwin-x64': 1.15.8 + '@swc/core-linux-arm-gnueabihf': 1.15.8 + '@swc/core-linux-arm64-gnu': 1.15.8 + '@swc/core-linux-arm64-musl': 1.15.8 + '@swc/core-linux-x64-gnu': 1.15.8 + '@swc/core-linux-x64-musl': 1.15.8 + '@swc/core-win32-arm64-msvc': 1.15.8 + '@swc/core-win32-ia32-msvc': 1.15.8 + '@swc/core-win32-x64-msvc': 1.15.8 + '@swc/helpers': 0.5.23 + '@swc/core@1.5.28(@swc/helpers@0.5.18)': dependencies: '@swc/counter': 0.1.3 @@ -41154,6 +41569,10 @@ snapshots: dependencies: tslib: 2.8.1 + '@swc/helpers@0.5.23': + dependencies: + tslib: 2.8.1 + '@swc/types@0.1.25': dependencies: '@swc/counter': 0.1.3 @@ -43681,6 +44100,13 @@ snapshots: schema-utils: 4.3.3 webpack: 5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.15) + babel-loader@9.2.1(@babel/core@7.28.5)(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)): + dependencies: + '@babel/core': 7.28.5 + find-cache-dir: 4.0.0 + schema-utils: 4.3.3 + webpack: 5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15) + babel-plugin-istanbul@6.1.1: dependencies: '@babel/helper-plugin-utils': 7.29.7 @@ -45227,6 +45653,19 @@ snapshots: optionalDependencies: webpack: 5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.15) + css-loader@6.11.0(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)): + dependencies: + icss-utils: 5.1.0(postcss@8.5.26) + postcss: 8.5.26 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.26) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.26) + postcss-modules-scope: 3.2.1(postcss@8.5.26) + postcss-modules-values: 4.0.0(postcss@8.5.26) + postcss-value-parser: 4.2.0 + semver: 7.8.5 + optionalDependencies: + webpack: 5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15) + css-loader@7.1.2(webpack@5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)): dependencies: icss-utils: 5.1.0(postcss@8.5.15) @@ -45240,6 +45679,19 @@ snapshots: optionalDependencies: webpack: 5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3) + css-loader@7.1.2(webpack@5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)): + dependencies: + icss-utils: 5.1.0(postcss@8.5.15) + postcss: 8.5.15 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.15) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.15) + postcss-modules-scope: 3.2.1(postcss@8.5.15) + postcss-modules-values: 4.0.0(postcss@8.5.15) + postcss-value-parser: 4.2.0 + semver: 7.7.3 + optionalDependencies: + webpack: 5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3) + css-loader@7.1.2(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.6)): dependencies: icss-utils: 5.1.0(postcss@8.5.15) @@ -47462,6 +47914,23 @@ snapshots: typescript: 5.8.2 webpack: 5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3) + fork-ts-checker-webpack-plugin@9.1.0(typescript@5.8.2)(webpack@5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)): + dependencies: + '@babel/code-frame': 7.29.7 + chalk: 4.1.2 + chokidar: 4.0.3 + cosmiconfig: 8.3.6(typescript@5.8.2) + deepmerge: 4.3.1 + fs-extra: 10.1.0 + memfs: 3.5.3 + minimatch: 10.2.4 + node-abort-controller: 3.1.1 + schema-utils: 3.3.0 + semver: 7.8.5 + tapable: 2.3.0 + typescript: 5.8.2 + webpack: 5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3) + fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.2)(webpack@5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)): dependencies: '@babel/code-frame': 7.29.7 @@ -48399,6 +48868,16 @@ snapshots: optionalDependencies: webpack: 5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3) + html-webpack-plugin@5.6.5(webpack@5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)): + dependencies: + '@types/html-minifier-terser': 6.1.0 + html-minifier-terser: 6.1.0 + lodash: 4.18.1 + pretty-error: 4.0.0 + tapable: 2.3.0 + optionalDependencies: + webpack: 5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3) + html-webpack-plugin@5.6.5(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.6)): dependencies: '@types/html-minifier-terser': 6.1.0 @@ -51890,6 +52369,18 @@ snapshots: '@swc/core': 1.15.8(@swc/helpers@0.5.18) esbuild: 0.27.3 + minimizer-webpack-plugin@5.6.1(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.3 + terser: 5.49.0 + webpack: 5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15) + optionalDependencies: + '@swc/core': 1.15.8(@swc/helpers@0.5.23) + esbuild: 0.27.3 + postcss: 8.5.15 + minipass-collect@2.0.1: dependencies: minipass: 7.1.2 @@ -52092,31 +52583,30 @@ snapshots: - supports-color - unified - next-themes@0.2.1(next@16.1.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + next-themes@0.2.1(next@15.5.24(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - next: 16.1.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 15.5.24(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - next@16.1.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0): + next@15.5.24(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0): dependencies: - '@next/env': 16.1.3 + '@next/env': 15.5.24 '@swc/helpers': 0.5.15 - baseline-browser-mapping: 2.9.13 - caniuse-lite: 1.0.30001769 + caniuse-lite: 1.0.30001809 postcss: 8.4.31 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) styled-jsx: 5.1.6(@babel/core@8.0.1)(react@19.2.3) optionalDependencies: - '@next/swc-darwin-arm64': 16.1.3 - '@next/swc-darwin-x64': 16.1.3 - '@next/swc-linux-arm64-gnu': 16.1.3 - '@next/swc-linux-arm64-musl': 16.1.3 - '@next/swc-linux-x64-gnu': 16.1.3 - '@next/swc-linux-x64-musl': 16.1.3 - '@next/swc-win32-arm64-msvc': 16.1.3 - '@next/swc-win32-x64-msvc': 16.1.3 + '@next/swc-darwin-arm64': 15.5.24 + '@next/swc-darwin-x64': 15.5.24 + '@next/swc-linux-arm64-gnu': 15.5.24 + '@next/swc-linux-arm64-musl': 15.5.24 + '@next/swc-linux-x64-gnu': 15.5.24 + '@next/swc-linux-x64-musl': 15.5.24 + '@next/swc-win32-arm64-msvc': 15.5.24 + '@next/swc-win32-x64-msvc': 15.5.24 '@opentelemetry/api': 1.9.0 '@playwright/test': 1.59.1 babel-plugin-react-compiler: 1.0.0 @@ -52126,32 +52616,62 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@16.2.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0): + next@16.3.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(@types/node@20.19.27)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0): dependencies: - '@next/env': 16.2.3 - '@swc/helpers': 0.5.15 - baseline-browser-mapping: 2.10.24 - caniuse-lite: 1.0.30001793 - postcss: 8.4.31 + '@next/env': 16.3.3 + '@swc/helpers': 0.5.23 + baseline-browser-mapping: 2.11.15 + caniuse-lite: 1.0.30001809 + postcss: 8.5.23 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) styled-jsx: 5.1.6(@babel/core@8.0.1)(react@19.2.3) optionalDependencies: - '@next/swc-darwin-arm64': 16.2.3 - '@next/swc-darwin-x64': 16.2.3 - '@next/swc-linux-arm64-gnu': 16.2.3 - '@next/swc-linux-arm64-musl': 16.2.3 - '@next/swc-linux-x64-gnu': 16.2.3 - '@next/swc-linux-x64-musl': 16.2.3 - '@next/swc-win32-arm64-msvc': 16.2.3 - '@next/swc-win32-x64-msvc': 16.2.3 + '@next/swc-darwin-arm64': 16.3.3 + '@next/swc-darwin-x64': 16.3.3 + '@next/swc-linux-arm64-gnu': 16.3.3 + '@next/swc-linux-arm64-musl': 16.3.3 + '@next/swc-linux-x64-gnu': 16.3.3 + '@next/swc-linux-x64-musl': 16.3.3 + '@next/swc-win32-arm64-msvc': 16.3.3 + '@next/swc-win32-x64-msvc': 16.3.3 '@opentelemetry/api': 1.9.0 '@playwright/test': 1.59.1 babel-plugin-react-compiler: 1.0.0 sass: 1.101.0 - sharp: 0.34.5 + sharp: 0.35.4(@types/node@20.19.27) transitivePeerDependencies: - '@babel/core' + - '@types/node' + - babel-plugin-macros + + next@16.3.3(@babel/core@8.0.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.59.1)(@types/node@22.19.11)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0): + dependencies: + '@next/env': 16.3.3 + '@swc/helpers': 0.5.23 + baseline-browser-mapping: 2.11.15 + caniuse-lite: 1.0.30001809 + postcss: 8.5.23 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + styled-jsx: 5.1.6(@babel/core@8.0.1)(react@19.2.3) + optionalDependencies: + '@next/swc-darwin-arm64': 16.3.3 + '@next/swc-darwin-x64': 16.3.3 + '@next/swc-linux-arm64-gnu': 16.3.3 + '@next/swc-linux-arm64-musl': 16.3.3 + '@next/swc-linux-x64-gnu': 16.3.3 + '@next/swc-linux-x64-musl': 16.3.3 + '@next/swc-win32-arm64-msvc': 16.3.3 + '@next/swc-win32-x64-msvc': 16.3.3 + '@opentelemetry/api': 1.9.0 + '@playwright/test': 1.59.1 + babel-plugin-react-compiler: 1.0.0 + sass: 1.101.0 + sharp: 0.35.4(@types/node@22.19.11) + transitivePeerDependencies: + - '@babel/core' + - '@types/node' - babel-plugin-macros ng-packagr@21.2.3(@angular/compiler-cli@21.2.18(@angular/compiler@21.2.18)(typescript@5.9.3))(tailwindcss@4.2.2)(tslib@2.8.1)(typescript@5.9.3): @@ -52498,6 +53018,35 @@ snapshots: vm-browserify: 1.1.2 webpack: 5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.15) + node-polyfill-webpack-plugin@2.0.1(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)): + dependencies: + assert: 2.1.0 + browserify-zlib: 0.2.0 + buffer: 6.0.3 + console-browserify: 1.2.0 + constants-browserify: 1.0.0 + crypto-browserify: 3.12.1 + domain-browser: 4.23.0 + events: 3.3.0 + filter-obj: 2.0.2 + https-browserify: 1.0.0 + os-browserify: 0.3.0 + path-browserify: 1.0.1 + process: 0.11.10 + punycode: 2.3.1 + querystring-es3: 0.2.1 + readable-stream: 4.7.0 + stream-browserify: 3.0.0 + stream-http: 3.2.0 + string_decoder: 1.3.0 + timers-browserify: 2.0.12 + tty-browserify: 0.0.1 + type-fest: 2.19.0 + url: 0.11.4 + util: 0.12.5 + vm-browserify: 1.1.2 + webpack: 5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15) + node-releases@2.0.27: {} node-releases@2.0.45: {} @@ -53976,6 +54525,17 @@ snapshots: transitivePeerDependencies: - typescript + postcss-loader@8.2.0(postcss@8.5.15)(typescript@5.8.2)(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)): + dependencies: + cosmiconfig: 9.0.0(typescript@5.8.2) + jiti: 2.6.1 + postcss: 8.5.15 + semver: 7.7.3 + optionalDependencies: + webpack: 5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15) + transitivePeerDependencies: + - typescript + postcss-loader@8.2.0(postcss@8.5.6)(typescript@6.0.3)(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.6)): dependencies: cosmiconfig: 9.0.0(typescript@6.0.3) @@ -54227,6 +54787,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.5.23: + dependencies: + nanoid: 3.3.18 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postcss@8.5.25: dependencies: nanoid: 3.3.18 @@ -55809,6 +56375,13 @@ snapshots: sass: 1.101.0 webpack: 5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.15) + sass-loader@16.0.6(sass@1.101.0)(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)): + dependencies: + neo-async: 2.6.2 + optionalDependencies: + sass: 1.101.0 + webpack: 5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15) + sass-loader@17.0.0(sass@1.101.0)(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.6)): optionalDependencies: sass: 1.101.0 @@ -56117,6 +56690,74 @@ snapshots: '@img/sharp-win32-ia32': 0.34.5 '@img/sharp-win32-x64': 0.34.5 + sharp@0.35.4(@types/node@20.19.27): + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.5 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.35.4 + '@img/sharp-darwin-x64': 0.35.4 + '@img/sharp-freebsd-wasm32': 0.35.4 + '@img/sharp-libvips-darwin-arm64': 1.3.3 + '@img/sharp-libvips-darwin-x64': 1.3.3 + '@img/sharp-libvips-linux-arm': 1.3.3 + '@img/sharp-libvips-linux-arm64': 1.3.3 + '@img/sharp-libvips-linux-ppc64': 1.3.3 + '@img/sharp-libvips-linux-riscv64': 1.3.3 + '@img/sharp-libvips-linux-s390x': 1.3.3 + '@img/sharp-libvips-linux-x64': 1.3.3 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.3 + '@img/sharp-libvips-linuxmusl-x64': 1.3.3 + '@img/sharp-linux-arm': 0.35.4 + '@img/sharp-linux-arm64': 0.35.4 + '@img/sharp-linux-ppc64': 0.35.4 + '@img/sharp-linux-riscv64': 0.35.4 + '@img/sharp-linux-s390x': 0.35.4 + '@img/sharp-linux-x64': 0.35.4 + '@img/sharp-linuxmusl-arm64': 0.35.4 + '@img/sharp-linuxmusl-x64': 0.35.4 + '@img/sharp-webcontainers-wasm32': 0.35.4 + '@img/sharp-win32-arm64': 0.35.4 + '@img/sharp-win32-ia32': 0.35.4 + '@img/sharp-win32-x64': 0.35.4 + '@types/node': 20.19.27 + optional: true + + sharp@0.35.4(@types/node@22.19.11): + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.5 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.35.4 + '@img/sharp-darwin-x64': 0.35.4 + '@img/sharp-freebsd-wasm32': 0.35.4 + '@img/sharp-libvips-darwin-arm64': 1.3.3 + '@img/sharp-libvips-darwin-x64': 1.3.3 + '@img/sharp-libvips-linux-arm': 1.3.3 + '@img/sharp-libvips-linux-arm64': 1.3.3 + '@img/sharp-libvips-linux-ppc64': 1.3.3 + '@img/sharp-libvips-linux-riscv64': 1.3.3 + '@img/sharp-libvips-linux-s390x': 1.3.3 + '@img/sharp-libvips-linux-x64': 1.3.3 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.3 + '@img/sharp-libvips-linuxmusl-x64': 1.3.3 + '@img/sharp-linux-arm': 0.35.4 + '@img/sharp-linux-arm64': 0.35.4 + '@img/sharp-linux-ppc64': 0.35.4 + '@img/sharp-linux-riscv64': 0.35.4 + '@img/sharp-linux-s390x': 0.35.4 + '@img/sharp-linux-x64': 0.35.4 + '@img/sharp-linuxmusl-arm64': 0.35.4 + '@img/sharp-linuxmusl-x64': 0.35.4 + '@img/sharp-webcontainers-wasm32': 0.35.4 + '@img/sharp-win32-arm64': 0.35.4 + '@img/sharp-win32-ia32': 0.35.4 + '@img/sharp-win32-x64': 0.35.4 + '@types/node': 22.19.11 + optional: true + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -56812,10 +57453,18 @@ snapshots: dependencies: webpack: 5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.15) + style-loader@3.3.4(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)): + dependencies: + webpack: 5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15) + style-loader@4.0.0(webpack@5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)): dependencies: webpack: 5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3) + style-loader@4.0.0(webpack@5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)): + dependencies: + webpack: 5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3) + style-loader@4.0.0(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.6)): dependencies: webpack: 5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.6) @@ -57108,6 +57757,18 @@ snapshots: '@swc/core': 1.15.8(@swc/helpers@0.5.18) esbuild: 0.27.3 + terser-webpack-plugin@5.3.16(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(webpack@5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.3 + serialize-javascript: 7.0.5 + terser: 5.46.0 + webpack: 5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3) + optionalDependencies: + '@swc/core': 1.15.8(@swc/helpers@0.5.23) + esbuild: 0.27.3 + terser-webpack-plugin@5.6.1(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.6)(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.6)): dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -59448,6 +60109,16 @@ snapshots: optionalDependencies: webpack: 5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3) + webpack-dev-middleware@6.1.3(webpack@5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)): + dependencies: + colorette: 2.0.20 + memfs: 3.5.3 + mime-types: 2.1.35 + range-parser: 1.2.1 + schema-utils: 4.3.3 + optionalDependencies: + webpack: 5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3) + webpack-dev-middleware@6.1.3(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.6)): dependencies: colorette: 2.0.20 @@ -59493,6 +60164,18 @@ snapshots: optionalDependencies: webpack: 5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(postcss@8.5.6) + webpack-dev-middleware@7.4.5(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)): + dependencies: + colorette: 2.0.20 + memfs: 4.51.1 + mime-types: 3.0.2 + on-finished: 2.4.1 + range-parser: 1.2.1 + schema-utils: 4.3.3 + optionalDependencies: + webpack: 5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15) + optional: true + webpack-dev-middleware@8.0.3(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.6)): dependencies: memfs: 4.68.1 @@ -59630,6 +60313,45 @@ snapshots: - supports-color - utf-8-validate + webpack-dev-server@5.2.6(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)): + dependencies: + '@types/bonjour': 3.5.13 + '@types/connect-history-api-fallback': 1.5.4 + '@types/express': 4.17.25 + '@types/express-serve-static-core': 4.19.7 + '@types/serve-index': 1.9.4 + '@types/serve-static': 1.15.10 + '@types/sockjs': 0.3.36 + '@types/ws': 8.18.1 + ansi-html-community: 0.0.8 + bonjour-service: 1.3.0 + chokidar: 3.6.0 + colorette: 2.0.20 + compression: 1.8.1 + connect-history-api-fallback: 2.0.0 + express: 5.2.1 + graceful-fs: 4.2.11 + http-proxy-middleware: 2.0.9(@types/express@4.17.25) + ipaddr.js: 2.3.0 + launch-editor: 2.14.1 + open: 10.2.0 + p-retry: 6.2.1 + schema-utils: 4.3.3 + selfsigned: 5.5.0 + serve-index: 1.9.1 + sockjs: 0.3.24 + spdy: 4.0.2 + webpack-dev-middleware: 7.4.5(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)) + ws: 8.19.0 + optionalDependencies: + webpack: 5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15) + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + optional: true + webpack-hot-middleware@2.26.1: dependencies: ansi-html-community: 0.0.8 @@ -59695,6 +60417,38 @@ snapshots: - esbuild - uglify-js + webpack@5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3): + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.16.0 + acorn-import-phases: 1.0.4(acorn@8.16.0) + browserslist: 4.28.8 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.19.0 + es-module-lexer: 2.0.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.1 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 4.3.3 + tapable: 2.3.0 + terser-webpack-plugin: 5.3.16(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(webpack@5.105.0(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)) + watchpack: 2.5.2 + webpack-sources: 3.3.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.3): dependencies: '@types/estree': 1.0.8 @@ -59950,6 +60704,42 @@ snapshots: - postcss - uglify-js + webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15): + dependencies: + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.16.0 + browserslist: 4.28.8 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.24.5 + es-module-lexer: 2.3.2 + eslint-scope: 5.1.1 + events: 3.3.0 + graceful-fs: 4.2.11 + mime-db: 1.54.0 + minimizer-webpack-plugin: 5.6.1(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)(webpack@5.109.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(esbuild@0.27.3)(postcss@8.5.15)) + neo-async: 2.6.2 + schema-utils: 4.3.3 + tapable: 2.3.3 + watchpack: 2.5.2 + webpack-sources: 3.5.1 + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - uglify-js + websocket-driver@0.7.4: dependencies: http-parser-js: 0.5.10 diff --git a/release.config.json b/release.config.json index b541f2ef7d..f1a2eae754 100644 --- a/release.config.json +++ b/release.config.json @@ -6,6 +6,7 @@ "@copilotkit/a2ui-renderer", "@copilotkit/agentcore-runner", "@copilotkit/core", + "@copilotkit/mcp-apps-renderer", "@copilotkit/react-core", "@copilotkit/react-native", "@copilotkit/react-textarea", diff --git a/scripts/release/lib/build-release-notification.wrapper.test.ts b/scripts/release/lib/build-release-notification.wrapper.test.ts index 33b07aac38..5d3b833aa7 100644 --- a/scripts/release/lib/build-release-notification.wrapper.test.ts +++ b/scripts/release/lib/build-release-notification.wrapper.test.ts @@ -145,10 +145,10 @@ describe("resolvePackageCountSafe", () => { }); it("returns the real package count for the monorepo scope (drift guard)", () => { - // Pins the actual count from release.config.json (16). If the package set + // Pins the actual count from release.config.json (17). If the package set // drifts, this catches the staleness of the hardcoded "16 packages" // assertions in build-release-notification.test.ts. - expect(resolvePackageCountSafe("monorepo")).toBe(16); + expect(resolvePackageCountSafe("monorepo")).toBe(17); }); it("returns the real package count for the shared Channels scope (drift guard)", () => { diff --git a/scripts/release/public-api/manifest.v1.json b/scripts/release/public-api/manifest.v1.json index fcf7f063c9..75f9afa734 100644 --- a/scripts/release/public-api/manifest.v1.json +++ b/scripts/release/public-api/manifest.v1.json @@ -167,7 +167,7 @@ ], "compatibility": { "engines": { - "node": ">=18" + "node": ">=20" }, "provenance": [ { @@ -989,7 +989,7 @@ ], "compatibility": { "engines": { - "node": ">=18" + "node": ">=20" }, "provenance": [ { @@ -1017,6 +1017,90 @@ } } }, + { + "name": "@copilotkit/mcp-apps-renderer", + "version": "1.71.0", + "sourceDirectory": "packages/mcp-apps-renderer", + "entrypoints": [ + { + "importPath": "@copilotkit/mcp-apps-renderer", + "exportKey": ".", + "kind": "code", + "conditions": { + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + } + }, + "provenance": { + "kind": "package-json", + "path": "packages/mcp-apps-renderer/package.json", + "selector": "exports[\".\"]" + } + }, + { + "importPath": "@copilotkit/mcp-apps-renderer/activity", + "exportKey": "./activity", + "kind": "code", + "conditions": { + "import": { + "types": "./dist/activity.d.mts", + "default": "./dist/activity.mjs" + }, + "require": { + "types": "./dist/activity.d.cts", + "default": "./dist/activity.cjs" + } + }, + "provenance": { + "kind": "package-json", + "path": "packages/mcp-apps-renderer/package.json", + "selector": "exports[\"./activity\"]" + } + }, + { + "importPath": "@copilotkit/mcp-apps-renderer/package.json", + "exportKey": "./package.json", + "kind": "metadata", + "conditions": "./package.json", + "provenance": { + "kind": "package-json", + "path": "packages/mcp-apps-renderer/package.json", + "selector": "exports[\"./package.json\"]" + } + } + ], + "compatibility": { + "peerDependencies": { + "@ag-ui/client": "0.0.59", + "zod": ">=3.25" + }, + "provenance": [ + { + "kind": "package-json", + "path": "packages/mcp-apps-renderer/package.json", + "selector": "peerDependencies" + } + ] + }, + "provenance": { + "name": { + "kind": "package-json", + "path": "packages/mcp-apps-renderer/package.json", + "selector": "name" + }, + "version": { + "kind": "package-json", + "path": "packages/mcp-apps-renderer/package.json", + "selector": "version" + }, + "releaseScope": { + "kind": "release-config", + "path": "release.config.json", + "selector": "scopes.monorepo.packages" + } + } + }, { "name": "@copilotkit/react-core", "version": "1.71.1", @@ -1103,7 +1187,6 @@ ], "compatibility": { "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.29.0", "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc", "zod": ">=3.25" @@ -1587,6 +1670,9 @@ } ], "compatibility": { + "engines": { + "node": ">=20" + }, "peerDependencies": { "@anthropic-ai/sdk": ">=0.57.0", "@langchain/aws": ">=0.1.9", @@ -1611,6 +1697,11 @@ "openai" ], "provenance": [ + { + "kind": "package-json", + "path": "packages/runtime/package.json", + "selector": "engines" + }, { "kind": "package-json", "path": "packages/runtime/package.json", @@ -1917,7 +2008,7 @@ ], "compatibility": { "engines": { - "node": ">=18" + "node": ">=20" }, "peerDependencies": { "better-sqlite3": "^12.2.0" @@ -1994,7 +2085,7 @@ ], "compatibility": { "engines": { - "node": ">=18" + "node": ">=20" }, "peerDependencies": { "@copilotkit/runtime": "workspace:*" @@ -2100,7 +2191,7 @@ ], "compatibility": { "engines": { - "node": ">=18" + "node": ">=20" }, "peerDependencies": { "vue": ">=3.3.0" @@ -2195,7 +2286,7 @@ ], "compatibility": { "engines": { - "node": ">=18" + "node": ">=20" }, "peerDependencies": { "lit": "^3.3.2" @@ -2264,7 +2355,7 @@ ], "compatibility": { "engines": { - "node": ">=18" + "node": ">=20" }, "provenance": [ { diff --git a/showcase/shell-docs/src/content/docs/cookbook/oracle-agent-spec-memory.mdx b/showcase/shell-docs/src/content/docs/cookbook/oracle-agent-spec-memory.mdx index 02bdaf1990..048985abbc 100644 --- a/showcase/shell-docs/src/content/docs/cookbook/oracle-agent-spec-memory.mdx +++ b/showcase/shell-docs/src/content/docs/cookbook/oracle-agent-spec-memory.mdx @@ -49,7 +49,7 @@ the current conversation. ## Prerequisites -- **Python 3.12** (required — `oracleagentmemory` ships a cp312 wheel), [`uv`](https://docs.astral.sh/uv/), Node.js 18+ +- **Python 3.12** (required — `oracleagentmemory` ships a cp312 wheel), [`uv`](https://docs.astral.sh/uv/), Node.js 20+ - Docker (for the local Oracle AI Database) or your own Oracle AI Database - `OPENAI_API_KEY` (defaults use OpenAI via litellm) diff --git a/showcase/shell-docs/src/content/docs/deploy/aws-lambda.mdx b/showcase/shell-docs/src/content/docs/deploy/aws-lambda.mdx index 46e08efa54..eece5f7956 100644 --- a/showcase/shell-docs/src/content/docs/deploy/aws-lambda.mdx +++ b/showcase/shell-docs/src/content/docs/deploy/aws-lambda.mdx @@ -26,7 +26,7 @@ The handler code below is the same for both streaming paths; only the event shap ## Function URL with response streaming -Response streaming requires three things: a Function URL with its invoke mode set to `RESPONSE_STREAM`, a handler wrapped in `awslambda.streamifyResponse`, and a Node.js managed runtime (Node.js 18 or later). +Response streaming requires three things: a Function URL with its invoke mode set to `RESPONSE_STREAM`, a handler wrapped in `awslambda.streamifyResponse`, and a Node.js managed runtime of version 20 or later. AWS supports response streaming from Node.js 18, but `@copilotkit/runtime` requires Node.js 20. ### 1. The handler diff --git a/showcase/shell-docs/src/content/snippets/langgraph-platform-deployment-tabs.mdx b/showcase/shell-docs/src/content/snippets/langgraph-platform-deployment-tabs.mdx index 813bc05e8d..a543df4191 100644 --- a/showcase/shell-docs/src/content/snippets/langgraph-platform-deployment-tabs.mdx +++ b/showcase/shell-docs/src/content/snippets/langgraph-platform-deployment-tabs.mdx @@ -24,7 +24,7 @@ import { Tab } from "../components/react/tabs"; ``` ```bash - # For TypeScript with Node 18 or above + # For TypeScript with Node 20 or above npx @langchain/langgraph-cli dev --host localhost --port 8000 ```