cortena-ui 1.8.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +90 -13
  3. package/dist/agent-chat/mcp-app/bridge.d.ts +194 -0
  4. package/dist/agent-chat/mcp-app/bridge.js +327 -0
  5. package/dist/agent-chat/mcp-app/bridge.js.map +1 -0
  6. package/dist/agent-chat/mcp-app/frame.d.ts +64 -0
  7. package/dist/agent-chat/mcp-app/frame.js +195 -0
  8. package/dist/agent-chat/mcp-app/frame.js.map +1 -0
  9. package/dist/agent-chat/mcp-app/index.d.ts +8 -0
  10. package/dist/agent-chat/mcp-app/render-tool-call.d.ts +47 -0
  11. package/dist/agent-chat/mcp-app/render-tool-call.js +77 -0
  12. package/dist/agent-chat/mcp-app/render-tool-call.js.map +1 -0
  13. package/dist/agent-chat/mcp-app/resource.d.ts +148 -0
  14. package/dist/agent-chat/mcp-app/resource.js +172 -0
  15. package/dist/agent-chat/mcp-app/resource.js.map +1 -0
  16. package/dist/agent-chat/mcp-app/sandbox.d.ts +90 -0
  17. package/dist/agent-chat/mcp-app/sandbox.js +177 -0
  18. package/dist/agent-chat/mcp-app/sandbox.js.map +1 -0
  19. package/dist/agent-chat/mcp-app/theme.d.ts +33 -0
  20. package/dist/agent-chat/mcp-app/theme.js +157 -0
  21. package/dist/agent-chat/mcp-app/theme.js.map +1 -0
  22. package/dist/agent-chat/session.d.ts +40 -0
  23. package/dist/agent-chat/session.js +264 -6
  24. package/dist/agent-chat/session.js.map +1 -1
  25. package/dist/agent-chat/store.d.ts +46 -0
  26. package/dist/agent-chat/store.js +176 -13
  27. package/dist/agent-chat/store.js.map +1 -1
  28. package/dist/agent-chat.d.ts +10 -3
  29. package/dist/agent-chat.js +9 -3
  30. package/dist/components/agent-chat.d.ts +39 -2
  31. package/dist/components/agent-chat.js +212 -24
  32. package/dist/components/agent-chat.js.map +1 -1
  33. package/dist/components/badge.d.ts +1 -1
  34. package/dist/components/button.d.ts +1 -1
  35. package/dist/components/chip.d.ts +1 -1
  36. package/package.json +1 -1
  37. package/src/agent-chat/mcp-app/bridge.ts +455 -0
  38. package/src/agent-chat/mcp-app/frame.tsx +332 -0
  39. package/src/agent-chat/mcp-app/index.ts +56 -0
  40. package/src/agent-chat/mcp-app/render-tool-call.tsx +175 -0
  41. package/src/agent-chat/mcp-app/resource.ts +273 -0
  42. package/src/agent-chat/mcp-app/sandbox.ts +244 -0
  43. package/src/agent-chat/mcp-app/theme.ts +184 -0
  44. package/src/agent-chat/session.ts +341 -5
  45. package/src/agent-chat/store.ts +279 -9
  46. package/src/components/agent-chat.tsx +388 -70
  47. package/src/entries/agent-chat.ts +56 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,90 @@
3
3
  Notable changes per release. Versions before 1.6.0 are recorded in the git log
4
4
  and in `../../CONSUMING.md`; this file starts where the changelog does.
5
5
 
6
+ ## 1.10.0
7
+
8
+ 1.9.0 was never released: it was published by mistake and has been deprecated.
9
+
10
+ ### Added
11
+
12
+ - **Screens inside the pop-up** (DESIGN-78). An extension's tool can answer
13
+ with its own screen — a `ui://` resource — and until now only cortenaweb
14
+ could show it; the pop-up inside the extension had no host. The host is now
15
+ in this package, on the `cortena-ui/agent-chat` entry:
16
+ - `McpAppFrame` — the sandboxed iframe (`allow-scripts allow-forms`, never
17
+ `allow-same-origin`), the `srcdoc` with the CSP written ahead of the app's
18
+ markup (`default-src 'none'`, no `unsafe-eval`, `form-action 'none'`), the
19
+ JSON-RPC bridge (`ui/initialize`, `ui/message`, `tools/call`,
20
+ `ui/update-model-context`, size and theme), the source and origin check
21
+ on every message, the height clamp (120–720 px), the `ui/message` bound
22
+ (`MCP_APP_MAX_MESSAGE_CHARS`, 4,000 chars; longer is refused with
23
+ `-32602`, since that text reaches the model) and the theme push. It holds
24
+ no credential: `callTool({ name, arguments, extensionId, lane })` is
25
+ supplied by the mounting app. A name that visibly belongs to another
26
+ extension (`mcp__notes__x`, `notes.note.delete`) is refused with `-32602`
27
+ before the relay is reached; a bare name (`task_get`, and equally
28
+ `broker_invoke`, `exec`, `read_file`) names no extension, so the frame
29
+ does not claim to confine it — it arrives as `lane: "bare"` beside
30
+ `extensionId`, for a relay that resolves it on that extension and nowhere
31
+ else. A resource another extension owns, a non-HTML type or a document
32
+ over
33
+ `MCP_APP_MAX_DOCUMENT_CHARS` (512,000; exported so a caller can make the
34
+ bound its own later, PLATFORM-82) is drawn as nothing and reported through
35
+ `onRefused`. Design tokens go in as `tokensCss` (inlined) or `tokensUrl`
36
+ (linked; its origin joins `style-src`/`font-src`); nothing is fetched
37
+ implicitly.
38
+ - `renderMcpAppToolCall({ extensionId, callTool, onMessage, … })` — a
39
+ `renderToolCall` for one extension. A screen is drawn only when the
40
+ *producing tool* can be named as this extension's — `mcp__<extensionId>__*`,
41
+ or `broker_invoke` whose `source` argument is this extension — **and** the
42
+ result carries that extension's embedded `ui://` resource (with
43
+ `structuredContent` from the result, or from
44
+ `_meta.cortena.structuredContent` on the block, as a reopened chat carries
45
+ it). A result is not a permission: an `exec` or a `read_file` whose output
46
+ happens to embed a `ui://` block keeps the ordinary card. `isEligible`
47
+ replaces the allowlist for a host whose tools are spelled another way.
48
+ Anything else returns `undefined` and the default card is drawn. Same type
49
+ as the seam in every mode.
50
+ - `AgentChatPopup` and owned-mode `AgentChat` take `renderToolCall` as
51
+ hosted mode does — the prop was on the shared base already; this release
52
+ pins it with a type-level test that the 1.7.0 mount shapes still compile.
53
+ See "Screens inside the pop-up" in `README.md`.
54
+ - **The pop-up keeps every turn, and a screen can answer back** (DESIGN-80).
55
+ The pop-up used to hold the current turn only: `send` emptied the tool
56
+ cards, so an extension's screen was unmounted by the very turn it asked
57
+ for. It now behaves like the main chat.
58
+ - **A turn's tool record is its own.** Each turn keeps its steps, its cards
59
+ and its screens, and they are drawn in the transcript **under the answer
60
+ that turn produced** rather than in a strip at the foot that the next run
61
+ clears. A screen from three turns ago is still on the page and still
62
+ running. `AgentChatState` gains `turns?: AgentChatTurn[]` — optional, so a
63
+ host projecting its own store onto `UseAgentChatResult` compiles and
64
+ behaves exactly as before — with `TURN_LIMIT` (20) turns kept.
65
+ - **A reopened chat gets the record back, not just the prose.**
66
+ `parseHistory(records)` returns the messages *and* the per-turn tool
67
+ record behind them: the calls (`tool_use`, `toolCall`, `serverToolUse` —
68
+ matched with the case and the underscores taken out, as cortenacore
69
+ matches them), their results, and the embedded `ui://` resource with its
70
+ payload read from `_meta.cortena.structuredContent` where the pod
71
+ persisted it. The budget is cortenaweb's: 1,100,000 characters for one
72
+ result (`MAX_HISTORY_TOOL_RESULT_CHARS`) and 2,000,000 for a whole load
73
+ (`MAX_HISTORY_SNAPSHOT_RESULT_CHARS`), spent newest turn first; past it a
74
+ result becomes a note giving its size, with the call id, the tool's name
75
+ and whether it failed all kept. `parseHistoryMessages` is unchanged.
76
+ - **`AgentToolRenderContext` gains `send(text)`**, and
77
+ `AgentChatToolsSlotProps` gains the same field for a host that replaces
78
+ the region and builds the context itself. Optional on both, so a host
79
+ that builds the render context as an object literal of its own — as
80
+ cortenaweb's step accordion does — compiles unchanged. `renderMcpAppToolCall` wires
81
+ `onMessage` to it by default, so `onMessage` is now optional: a
82
+ `ui/message` from a screen goes out as the MCP App action envelope —
83
+ `{ toolCallId, appUri, serverName, name: "message", payload: { text } }` —
84
+ attributed to the screen, with no user bubble, bounded by
85
+ `MCP_APP_MAX_MESSAGE_CHARS` (4,000) and refused while a run is live.
86
+ - A host that supplies `components.Tools` still owns the whole tool region:
87
+ it is handed every turn's entries, and the transcript draws none.
88
+ - See "Screens inside the pop-up" in `README.md`.
89
+
6
90
  ## 1.8.0
7
91
 
8
92
  ### Added
package/README.md CHANGED
@@ -300,24 +300,101 @@ cortenacore.http.endpoints.agui.enabled = true
300
300
  cortenacore.http.endpoints.controlPlane.enabled = true
301
301
  ```
302
302
 
303
- ### The MCP App frame
303
+ ### Screens inside the pop-up
304
304
 
305
- Rendering an extension's own screen means a sandboxed iframe, a `srcdoc` CSP, a
306
- JSON-RPC postMessage bridge and a `tools/invoke` proxy holding a credential —
307
- host concerns, not component ones. `renderToolCall` is the seam: it is handed
308
- each tool call and the `ui://` resource its result carries, and a node it
309
- returns replaces the default card.
305
+ An extension's tool may answer with the extension's own screen — an embedded
306
+ `ui://` resource whose body is HTML — and the pop-up draws it inside the
307
+ conversation, sandboxed, with a postMessage bridge (DESIGN-78; the protocol as
308
+ implemented is cortena's `docs/mcp-apps.md`). `renderToolCall` is the seam,
309
+ in every mode, and `renderMcpAppToolCall` answers it for one extension:
310
310
 
311
311
  ```tsx
312
- <AgentChatPopup
313
- extension={extension}
314
- client={client}
315
- renderToolCall={({ entry, mcpAppUri }) =>
316
- mcpAppUri ? <McpAppFrame uri={mcpAppUri} result={entry.output} /> : undefined
317
- }
318
- />
312
+ const renderToolCall = renderMcpAppToolCall({ extensionId: "tasks", callTool });
313
+ <AgentChatPopup extension={extension} client={client} renderToolCall={renderToolCall} />
319
314
  ```
320
315
 
316
+ That is the whole round trip. A tool answers with a screen, the screen is
317
+ drawn under the answer it came with, a click inside it starts the next turn,
318
+ and the screen is still there when the answer arrives (DESIGN-80).
319
+
320
+ - **Only a tool that can be named as yours may draw a screen.** The default
321
+ allowlist is `mcp__<extensionId>__*`, and `broker_invoke` whose `source`
322
+ argument is your extension. A result is not a permission: an `exec` that
323
+ cat'd a fixture or a `read_file` over your test data answers with whatever
324
+ `ui://` block was in the file, and that stays an ordinary tool card. Pass
325
+ `isEligible(entry)` to replace the rule if your host's tools are spelled a
326
+ third way — you are replacing an allowlist, so you are deciding which tools
327
+ may put unreviewed HTML on the page.
328
+ - `callTool(call)` is **yours**: the relay that reaches your extension — your
329
+ own MCP relay, or cortenaweb's `/tools/invoke` — and whatever credential it
330
+ takes stays with it. The frame never sees a token. It hands you
331
+ `{ name, arguments, extensionId, lane }`, and the lane is the point:
332
+
333
+ ```ts
334
+ type McpAppToolCaller = (call: {
335
+ name: string;
336
+ arguments: Record<string, unknown>;
337
+ extensionId: string;
338
+ lane: "mcp" | "broker" | "bare";
339
+ }) => Promise<unknown>;
340
+ ```
341
+
342
+ `lane: "mcp"` (`mcp__tasks__x`) and `lane: "broker"` (`tasks.task.get`)
343
+ carry the extension in the name, and a name that carries a *different* one
344
+ (`mcp__notes__x`, `notes.note.delete`) is refused with `-32602` before your
345
+ relay is reached. `lane: "bare"` (`task_get`) carries nothing: `exec`,
346
+ `read_file` and `broker_invoke` are bare names too. **Resolve a bare name on
347
+ `extensionId` and nowhere else** — never forward it as a tool name of your
348
+ own — and an app cannot reach past its extension whatever it asks for.
349
+ - `onMessage(text)` is the app asking for something to be said, and **it is
350
+ wired for you**: with nothing passed, the text goes out through the seam's
351
+ own `send` as the next turn, attributed to the screen and not to the user —
352
+ nobody typed it, so no user bubble is drawn. What reaches the agent is the
353
+ MCP App action envelope (`{ toolCallId, appUri, serverName, name: "message",
354
+ payload: { text } }`), which is what lets an agent tell an event from your
355
+ board apart from one from another extension's card. It is bounded: the host
356
+ refuses text over 4,000 characters (`MCP_APP_MAX_MESSAGE_CHARS`) with
357
+ `-32602`, because this text reaches the model, and it is a no-op while a run
358
+ is already live — one run at a time, the same rule the composer follows.
359
+ Pass your own `onMessage` to do something else with it instead.
360
+ `onModelContext(ctx)` is its latest context for the next run.
361
+ - A call from a tool outside the allowlist, or a result carrying another
362
+ extension's screen, a bare link, or no screen at all, returns `undefined`
363
+ and the default card is drawn.
364
+ - Design tokens are not fetched implicitly. Pass `tokensCss` (the sheet,
365
+ inlined ahead of the app's markup) or `tokensUrl` (linked; its origin joins
366
+ `style-src` and `font-src`, never `connect-src`).
367
+
368
+ What the frame owns and no caller can widen: `sandbox="allow-scripts
369
+ allow-forms"` (never `allow-same-origin`, so the document runs in an opaque
370
+ origin with no storage and no reach into the page), a CSP written into the
371
+ `srcdoc` ahead of the app's markup (`default-src 'none'`, no `unsafe-eval`,
372
+ `form-action 'none'`; `_meta.ui.csp` on the resource may open named `https`
373
+ origins and nothing else), a message check that accepts only its own frame's
374
+ window at an opaque origin, a height the app asks for clamped to 120–720 px,
375
+ and a document bound of `MCP_APP_MAX_DOCUMENT_CHARS` (512,000) past which the
376
+ resource is refused. A refusal draws nothing and reports through
377
+ `onRefused(reason)`. `McpAppFrame` is exported for a host that finds the
378
+ resource itself.
379
+
380
+ **Where a screen lives.** Each turn keeps its own tool record — its steps, its
381
+ cards and its screens — and the record is drawn in the transcript under the
382
+ answer that turn produced, not in a strip at the foot that the next `send`
383
+ empties. So a screen from three turns ago is still on the page, still running,
384
+ and a turn sent from a screen does not unmount the screen that sent it. The
385
+ record of a turn survives a reopen too: `chat.history` carries the calls and
386
+ their results, so the cards, the worded steps and any screen come back with the
387
+ prose (`parseHistory`). Twenty turns are kept (`TURN_LIMIT`), and a reopen
388
+ spends at most 2 MB on tool results, newest turn first; past that a result
389
+ becomes a note that says how big it was, with its call, its name and its
390
+ outcome intact.
391
+
392
+ `AgentToolRenderContext` therefore carries `send(text)` — the handle
393
+ `renderMcpAppToolCall` wires `onMessage` to. A host that replaces the whole
394
+ tool region with `components.Tools` gets the same handle on its slot props and
395
+ passes it into `renderToolCall` itself; that host owns every card, so the
396
+ transcript draws none, and its `entries` are every turn's, not just the newest.
397
+
321
398
  ### Hosting the surface
322
399
 
323
400
  `AgentChatPopup` owns its state. A host that already has state of its own —
@@ -0,0 +1,194 @@
1
+ "use client";
2
+ //#region src/agent-chat/mcp-app/bridge.d.ts
3
+ /**
4
+ * The host side of the MCP Apps postMessage bridge.
5
+ *
6
+ * Implements the host role of the MCP Apps specification dated 2026-01-26 —
7
+ * the one `@modelcontextprotocol/ext-apps` 1.7.5 publishes — with the method
8
+ * names and handshake verbatim, so an app built with the SDK's `App` class
9
+ * connects with no Cortena-specific code. Ported from cortenaweb's host
10
+ * (DESIGN-36); the spec strings are copied rather than imported so this
11
+ * package does not take `@modelcontextprotocol/sdk` and a zod copy into the
12
+ * browser bundle for one JSON-RPC dialogue, and the tests pin every one.
13
+ *
14
+ * ## Who the host will listen to
15
+ *
16
+ * Three checks, in order, before a frame's message is looked at at all:
17
+ *
18
+ * 1. `event.source === iframe.contentWindow` — the one thing a frame cannot
19
+ * forge. Any other frame on the page fails here.
20
+ * 2. `event.origin === "null"` — an app runs without `allow-same-origin`, so
21
+ * its origin is opaque and serialises as the string `"null"`. A message
22
+ * from a real origin did not come from a sandboxed app, whatever its
23
+ * `source` claims.
24
+ * 3. The body must be a JSON-RPC 2.0 envelope naming a method the host serves.
25
+ *
26
+ * Replies go out with `targetOrigin: "*"`, which is correct rather than lazy:
27
+ * an opaque origin cannot be named, so `"*"` is the only value that reaches
28
+ * it, and nothing sent is a secret — the tool result the app's own extension
29
+ * produced, and the theme.
30
+ */
31
+ /** `LATEST_PROTOCOL_VERSION` in the MCP Apps spec. */
32
+ export declare const MCP_APP_PROTOCOL_VERSION = "2026-01-26";
33
+ /** Every method name the spec defines, as the host sees them. */
34
+ export declare const MCP_APP_METHOD: {
35
+ readonly INITIALIZE: "ui/initialize";
36
+ readonly OPEN_LINK: "ui/open-link";
37
+ readonly MESSAGE: "ui/message";
38
+ readonly UPDATE_MODEL_CONTEXT: "ui/update-model-context";
39
+ readonly REQUEST_DISPLAY_MODE: "ui/request-display-mode";
40
+ readonly CALL_TOOL: "tools/call";
41
+ readonly PING: "ping";
42
+ readonly INITIALIZED: "ui/notifications/initialized";
43
+ readonly SIZE_CHANGED: "ui/notifications/size-changed";
44
+ readonly TOOL_INPUT: "ui/notifications/tool-input";
45
+ readonly TOOL_RESULT: "ui/notifications/tool-result";
46
+ readonly HOST_CONTEXT_CHANGED: "ui/notifications/host-context-changed";
47
+ };
48
+ /** JSON-RPC error codes the host returns. */
49
+ export declare const MCP_APP_ERROR: {
50
+ readonly METHOD_NOT_FOUND: -32601;
51
+ readonly INVALID_PARAMS: -32602;
52
+ readonly INTERNAL: -32603;
53
+ };
54
+ /**
55
+ * The longest `ui/message` the host will carry.
56
+ *
57
+ * `ui/message` becomes a turn in the conversation, so the text an app sends
58
+ * is text that goes to the model — and an app is HTML nobody reviewed. An
59
+ * unbounded field is a way to spend a context window, and a paste of a
60
+ * hundred kilobytes is not a sentence a screen wanted said. Long enough for
61
+ * any real request; short enough that it cannot be the payload.
62
+ */
63
+ export declare const MCP_APP_MAX_MESSAGE_CHARS = 4000;
64
+ export interface McpAppHostContext {
65
+ theme?: "light" | "dark";
66
+ styles?: {
67
+ variables?: Record<string, string | undefined>;
68
+ };
69
+ displayMode?: "inline";
70
+ availableDisplayModes?: Array<"inline">;
71
+ containerDimensions?: {
72
+ maxWidth?: number;
73
+ maxHeight?: number;
74
+ };
75
+ locale?: string;
76
+ timeZone?: string;
77
+ userAgent?: string;
78
+ platform?: "web";
79
+ /** Cortena extension: where the app can fetch the design tokens, when the host serves them. */
80
+ cortena?: {
81
+ tokensCssUrl?: string;
82
+ };
83
+ [key: string]: unknown;
84
+ }
85
+ /**
86
+ * How a scoped `tools/call` reaches the app's own extension. Supplied by the
87
+ * mounting app; the frame never sees a token.
88
+ *
89
+ * The call arrives as one envelope rather than a name and arguments, because
90
+ * a name on its own cannot be confined. Two of the three spellings an app may
91
+ * use carry the extension (`mcp__tasks__x`, `tasks.task.get`) and are refused
92
+ * before they reach here when the extension is not this one; the third is
93
+ * **bare** (`task_get`), and a bare name names nothing — `broker_invoke`,
94
+ * `exec` and `read_file` are bare names too. So `extensionId` and `lane`
95
+ * travel with it: a relay handed `lane: "bare"` must resolve `name` on
96
+ * `extensionId` and nowhere else, and a relay that scopes on `extensionId`
97
+ * cannot be tricked into forwarding an app's bare name as a tool of its own.
98
+ */
99
+ export type McpAppToolCaller = (call: {
100
+ name: string;
101
+ arguments: Record<string, unknown>;
102
+ extensionId: string;
103
+ lane: "mcp" | "broker" | "bare";
104
+ }) => Promise<unknown>;
105
+ export interface McpAppBridgeOptions {
106
+ /** The frame the app runs in. Its `contentWindow` is the only valid source. */
107
+ iframe: HTMLIFrameElement;
108
+ /** The extension the app belongs to; every `tools/call` is confined to it. */
109
+ extensionId: string;
110
+ /** `_meta.ui` the resource declared, echoed in `hostCapabilities.sandbox`. */
111
+ sandbox?: {
112
+ permissions?: unknown;
113
+ csp?: unknown;
114
+ };
115
+ /** Arguments the agent passed. Sent as `ui/notifications/tool-input`. */
116
+ toolArguments?: Record<string, unknown>;
117
+ /** The whole tool result. Sent as `ui/notifications/tool-result`. */
118
+ toolResult?: unknown;
119
+ hostContext: McpAppHostContext;
120
+ /** Present only when the app's own extension can be reached. */
121
+ callTool?: McpAppToolCaller;
122
+ /** `ui/message`: the text the app wants said in the conversation. */
123
+ onMessage?: (text: string) => void;
124
+ /** The app asked for a height. */
125
+ onSizeChanged?: (size: {
126
+ width?: number;
127
+ height?: number;
128
+ }) => void;
129
+ /** The handshake completed. */
130
+ onInitialized?: () => void;
131
+ /** `ui/open-link`. Defaults to a `noopener` `window.open`. */
132
+ onOpenLink?: (url: string) => boolean;
133
+ /** The app's latest `ui/update-model-context`. */
134
+ onModelContext?: (context: {
135
+ content?: unknown[];
136
+ structuredContent?: unknown;
137
+ }) => void;
138
+ /** For tests. Defaults to `window`. */
139
+ listenTarget?: Pick<Window, "addEventListener" | "removeEventListener">;
140
+ }
141
+ export declare class McpAppHostBridge {
142
+ private readonly options;
143
+ private readonly listenTarget;
144
+ private hostContext;
145
+ private toolResult;
146
+ private initialized;
147
+ private closed;
148
+ private readonly onMessage;
149
+ constructor(options: McpAppBridgeOptions);
150
+ start(): void;
151
+ close(): void;
152
+ /** Did the app complete `ui/initialize` + `ui/notifications/initialized`? */
153
+ get isInitialized(): boolean;
154
+ /**
155
+ * Capabilities the host advertises. `serverTools` only when there is a
156
+ * caller: an app that sees it absent knows not to try, which is the spec's
157
+ * own way of saying "not here" and better than letting it call and fail.
158
+ */
159
+ private capabilities;
160
+ /**
161
+ * A replaced tool result for a screen already running.
162
+ *
163
+ * The same call re-run answers with the same document, so the frame is not
164
+ * remounted and the app never repeats its handshake — which is where the
165
+ * payload is otherwise sent. Without this the screen would keep drawing the
166
+ * first answer's data for the rest of the conversation.
167
+ */
168
+ setToolResult(result: unknown): void;
169
+ /** Push a changed theme (or anything else) to a running app. */
170
+ setHostContext(patch: McpAppHostContext): void;
171
+ private post;
172
+ private notify;
173
+ private respond;
174
+ private fail;
175
+ /** The right window, and an origin that proves it is sandboxed. */
176
+ private isFromApp;
177
+ private handleMessage;
178
+ private dispatch;
179
+ /**
180
+ * `tools/call` from the app: scoped, not proxied. The app may call tools on
181
+ * *its own* extension and nothing else — see `scopeMcpAppToolCall` for the
182
+ * rule and why. A name that visibly belongs to another extension is refused
183
+ * here, before the relay is reached; a bare name reaches the relay wrapped
184
+ * in `extensionId` and `lane: "bare"`, so the relay resolves it on this
185
+ * extension rather than taking it for a tool of its own. Whatever the relay
186
+ * answers goes back as the result, and a throw becomes a tool result the
187
+ * app can show.
188
+ */
189
+ private handleCallTool;
190
+ /** The tool's arguments and result, once the app says it is listening. */
191
+ private sendToolPayload;
192
+ }
193
+ //#endregion
194
+ //# sourceMappingURL=bridge.d.ts.map