@runtypelabs/persona 4.28.2 → 4.29.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 (103) hide show
  1. package/dist/animations/glyph-cycle.d.cts +1 -1
  2. package/dist/animations/glyph-cycle.d.ts +1 -1
  3. package/dist/animations/wipe.d.cts +1 -1
  4. package/dist/animations/wipe.d.ts +1 -1
  5. package/dist/approval-ui.d.cts +1 -1
  6. package/dist/approval-ui.d.ts +1 -1
  7. package/dist/artifacts-ui.d.cts +2 -2
  8. package/dist/artifacts-ui.d.ts +2 -2
  9. package/dist/chunk-BkW8s3fj.js +2 -0
  10. package/dist/chunk-CIVsNcrk.js +1 -0
  11. package/dist/chunk-CKdlNyzM.js +9 -0
  12. package/dist/{chunk-DrYAfpvk.js → chunk-DjOvgFsn.js} +1 -1
  13. package/dist/chunk-lA5CJCgK.js +1 -0
  14. package/dist/client-history.js +1 -1
  15. package/dist/codegen.cjs +1 -1
  16. package/dist/codegen.js +1 -1
  17. package/dist/{composer-input-C39npJaz.d.cts → composer-input-D3sB8_P_.d.cts} +1 -1
  18. package/dist/{composer-input-C39npJaz.d.ts → composer-input-D3sB8_P_.d.ts} +1 -1
  19. package/dist/{context-mention-manager-DLCB1l9E.d.cts → context-mention-manager-D2qxE5op.d.cts} +1 -1
  20. package/dist/{context-mention-manager-DLCB1l9E.d.ts → context-mention-manager-D2qxE5op.d.ts} +1 -1
  21. package/dist/context-mentions-inline.d.cts +2 -2
  22. package/dist/context-mentions-inline.d.ts +2 -2
  23. package/dist/context-mentions.d.cts +3 -3
  24. package/dist/context-mentions.d.ts +3 -3
  25. package/dist/{defaults-DiAgWHXy.d.cts → defaults-Dsw66XqE.d.cts} +1 -1
  26. package/dist/{defaults-DiAgWHXy.d.ts → defaults-Dsw66XqE.d.ts} +1 -1
  27. package/dist/event-stream-view.d.cts +1 -1
  28. package/dist/event-stream-view.d.ts +1 -1
  29. package/dist/{fallback-speech-engine-C5_PUZjH.d.cts → fallback-speech-engine-DJpJcWAk.d.cts} +1 -1
  30. package/dist/{fallback-speech-engine-C5_PUZjH.d.ts → fallback-speech-engine-DJpJcWAk.d.ts} +1 -1
  31. package/dist/forms-ui.d.cts +2 -2
  32. package/dist/forms-ui.d.ts +2 -2
  33. package/dist/history-view.d.cts +1 -1
  34. package/dist/history-view.d.ts +1 -1
  35. package/dist/index.cjs +45 -44
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +7 -7
  38. package/dist/index.d.ts +7 -7
  39. package/dist/index.global.js +1 -1
  40. package/dist/index.global.js.map +1 -1
  41. package/dist/index.js +34 -33
  42. package/dist/index.js.map +1 -1
  43. package/dist/{markdown-parsers-loader-BBdyIEU7.d.cts → markdown-parsers-loader-CCY7T4Up.d.cts} +1 -1
  44. package/dist/{markdown-parsers-loader-BBdyIEU7.d.ts → markdown-parsers-loader-CCY7T4Up.d.ts} +1 -1
  45. package/dist/{preview-Chjb0bmI.d.cts → preview-DKKpfTo7.d.cts} +2 -2
  46. package/dist/{preview-Chjb0bmI.d.ts → preview-DKKpfTo7.d.ts} +2 -2
  47. package/dist/{session-a_qIp4xp.d.ts → session-DleMr9dZ.d.cts} +13 -7
  48. package/dist/{session-a_qIp4xp.d.cts → session-DleMr9dZ.d.ts} +13 -7
  49. package/dist/session-reconnect.d.cts +2 -2
  50. package/dist/session-reconnect.d.ts +2 -2
  51. package/dist/smart-dom-reader.d.cts +1 -1
  52. package/dist/smart-dom-reader.d.ts +1 -1
  53. package/dist/theme-editor-preview.cjs +41 -40
  54. package/dist/theme-editor-preview.d.cts +1 -1
  55. package/dist/theme-editor-preview.d.ts +1 -1
  56. package/dist/theme-editor-preview.js +20 -20
  57. package/dist/theme-editor.d.cts +2 -2
  58. package/dist/theme-editor.d.ts +2 -2
  59. package/dist/theme-reference.d.cts +2 -2
  60. package/dist/theme-reference.d.ts +2 -2
  61. package/dist/{types-BdO1jNRm.d.cts → types-oAZ1VReS.d.cts} +166 -21
  62. package/dist/{types-BdO1jNRm.d.ts → types-oAZ1VReS.d.ts} +166 -21
  63. package/dist/voice-client-tools.cjs +1 -0
  64. package/dist/voice-client-tools.d.cts +13 -0
  65. package/dist/voice-client-tools.d.ts +13 -0
  66. package/dist/voice-client-tools.js +1 -0
  67. package/dist/voice-runtime.cjs +3 -3
  68. package/dist/voice-runtime.d.cts +7 -3
  69. package/dist/voice-runtime.d.ts +7 -3
  70. package/dist/voice-runtime.js +4 -4
  71. package/dist/voice-worklet-player.d.cts +2 -2
  72. package/dist/voice-worklet-player.d.ts +2 -2
  73. package/dist/{webmcp-runtime-entry-B6bpVWSa.d.ts → webmcp-runtime-entry-Deg0NfmB.d.cts} +26 -5
  74. package/dist/{webmcp-runtime-entry-B6bpVWSa.d.cts → webmcp-runtime-entry-Deg0NfmB.d.ts} +26 -5
  75. package/dist/webmcp-runtime.cjs +2 -1
  76. package/dist/webmcp-runtime.d.cts +1 -1
  77. package/dist/webmcp-runtime.d.ts +1 -1
  78. package/dist/webmcp-runtime.js +2 -1
  79. package/package.json +6 -1
  80. package/src/client.ts +37 -0
  81. package/src/client.voice-tools.test.ts +65 -0
  82. package/src/generated/runtype-openapi-contract.ts +38 -0
  83. package/src/index-core.ts +11 -0
  84. package/src/index-global.ts +6 -0
  85. package/src/session.test.ts +26 -0
  86. package/src/session.ts +8 -1
  87. package/src/session.voice.test.ts +43 -0
  88. package/src/types.ts +141 -20
  89. package/src/voice/runtype-voice-provider.ts +85 -3
  90. package/src/voice/session-voice-wiring.ts +26 -1
  91. package/src/voice/voice-client-tools-session.ts +116 -0
  92. package/src/voice/voice-delegation.ts +9 -0
  93. package/src/voice/voice.test.ts +360 -0
  94. package/src/voice-client-tools-bundle.test.ts +37 -0
  95. package/src/voice-client-tools-loader.ts +24 -0
  96. package/src/voice-client-tools.ts +9 -0
  97. package/src/webmcp-bridge.test.ts +152 -1
  98. package/src/webmcp-bridge.ts +5 -6
  99. package/src/webmcp-polyfill.integration.test.ts +5 -1
  100. package/src/webmcp-runtime-entry.ts +151 -26
  101. package/dist/chunk-CztSjKHK.js +0 -9
  102. package/dist/chunk-HUOUif-P.js +0 -1
  103. package/dist/chunk-UlcxdHef.js +0 -1
@@ -268,7 +268,11 @@ describe("WebMcpBridge.snapshotForDispatch", () => {
268
268
  annotations: {
269
269
  readOnlyHint: false,
270
270
  untrustedContentHint: false,
271
- destructiveHint: true, // MCP-only today; not in the WebMCP dict
271
+ destructiveHint: true,
272
+ consequentialHint: true,
273
+ idempotentHint: false,
274
+ openWorldHint: false,
275
+ debugging: true,
272
276
  evil: () => "not JSON",
273
277
  },
274
278
  }),
@@ -277,6 +281,11 @@ describe("WebMcpBridge.snapshotForDispatch", () => {
277
281
  expect(snap[0]!.annotations).toEqual({
278
282
  readOnlyHint: false,
279
283
  untrustedContentHint: false,
284
+ destructiveHint: true,
285
+ consequentialHint: true,
286
+ idempotentHint: false,
287
+ openWorldHint: false,
288
+ debugging: true,
280
289
  });
281
290
  expect(snap[0]!).not.toHaveProperty("untrustedContentHint");
282
291
  });
@@ -380,6 +389,20 @@ describe("setWebMcpPolyfillLoader", () => {
380
389
  });
381
390
 
382
391
  describe("WebMCP display titles", () => {
392
+ it("passes sanitized annotations to confirmation without skipping the gate", async () => {
393
+ registry.tools = [fakeTool({ name: "search", annotations: {
394
+ readOnlyHint: true, destructiveHint: false, consequentialHint: false,
395
+ debugging: "yes", extra: () => "ignored",
396
+ } })];
397
+ const onConfirm = vi.fn(async () => false);
398
+ const bridge = new WebMcpBridge({ enabled: true, onConfirm });
399
+ const result = await bridge.executeToolCall("webmcp:search", {});
400
+ expect(onConfirm).toHaveBeenCalledWith(expect.objectContaining({ annotations: {
401
+ readOnlyHint: true, destructiveHint: false, consequentialHint: false,
402
+ } }));
403
+ expect(result.isError).toBe(true);
404
+ });
405
+
383
406
  it("records declared titles on snapshot and exposes them via getWebMcpToolDisplayTitle", async () => {
384
407
  registry.tools = [
385
408
  fakeTool({ name: "add_to_cart", title: "Add to Cart" }),
@@ -811,3 +834,131 @@ describe("native preview argument conversion", () => {
811
834
  expect(nativeExecute).toHaveBeenCalledTimes(1);
812
835
  });
813
836
  });
837
+
838
+ describe("WebMcpBridge.executeVoiceToolCall", () => {
839
+ it("skips the confirm gate for a tool the registry marks readOnlyHint: true", async () => {
840
+ const onConfirm = vi.fn(async () => false);
841
+ registry.tools = [
842
+ fakeTool({ name: "next_slide", annotations: { readOnlyHint: true }, execute: () => ({ slidePosition: 2 }) }),
843
+ ];
844
+ const bridge = new WebMcpBridge({ enabled: true, onConfirm });
845
+ const r = await bridge.executeVoiceToolCall("webmcp:next_slide", {}, { autoConfirmReadOnly: true });
846
+ expect(onConfirm).not.toHaveBeenCalled();
847
+ expect(r).toEqual({ status: "completed", result: { slidePosition: 2 } });
848
+ });
849
+
850
+ it.each([
851
+ ["no annotations", undefined],
852
+ ["readOnlyHint false", { readOnlyHint: false }],
853
+ ["only other hints", { idempotentHint: true }],
854
+ ])("still shows the gate for a tool with %s, and maps a decline to denied", async (_label, annotations) => {
855
+ const onConfirm = vi.fn(async () => false);
856
+ const execute = vi.fn(() => "done");
857
+ registry.tools = [fakeTool({ name: "edit_slide", annotations, execute })];
858
+ const bridge = new WebMcpBridge({ enabled: true, onConfirm });
859
+ const r = await bridge.executeVoiceToolCall("webmcp:edit_slide", {}, { autoConfirmReadOnly: true });
860
+ expect(onConfirm).toHaveBeenCalledTimes(1);
861
+ expect(execute).not.toHaveBeenCalled();
862
+ expect(r).toEqual({ status: "denied", error: { message: "User declined the tool call." } });
863
+ });
864
+
865
+ it("shows the gate even for a read-only tool when autoConfirmReadOnly is off", async () => {
866
+ const onConfirm = vi.fn(async () => true);
867
+ registry.tools = [fakeTool({ name: "next_slide", annotations: { readOnlyHint: true } })];
868
+ const bridge = new WebMcpBridge({ enabled: true, onConfirm });
869
+ await bridge.executeVoiceToolCall("webmcp:next_slide", {});
870
+ expect(onConfirm).toHaveBeenCalledTimes(1);
871
+ });
872
+
873
+ it("answers stale for a tool missing from the registry or excluded by the allowlist", async () => {
874
+ registry.tools = [fakeTool({ name: "secret", annotations: { readOnlyHint: true } })];
875
+ const bridge = new WebMcpBridge({ enabled: true, onConfirm: allowAll, allowlist: ["public_*"] });
876
+ expect((await bridge.executeVoiceToolCall("webmcp:gone", {})).status).toBe("stale");
877
+ expect((await bridge.executeVoiceToolCall("webmcp:secret", {})).status).toBe("stale");
878
+ });
879
+
880
+ it("answers failed with the message when the page tool throws or returns an MCP error", async () => {
881
+ registry.tools = [
882
+ fakeTool({
883
+ name: "boom",
884
+ annotations: { readOnlyHint: true },
885
+ execute: () => {
886
+ throw new Error("kaboom");
887
+ },
888
+ }),
889
+ fakeTool({
890
+ name: "mcp_err",
891
+ annotations: { readOnlyHint: true },
892
+ execute: () => ({ isError: true, content: [{ type: "text", text: "no such slide" }] }),
893
+ }),
894
+ ];
895
+ const bridge = new WebMcpBridge({ enabled: true, onConfirm: allowAll });
896
+ const thrown = await bridge.executeVoiceToolCall("webmcp:boom", {}, { autoConfirmReadOnly: true });
897
+ expect(thrown).toEqual({ status: "failed", error: { message: "kaboom" } });
898
+ expect(await bridge.executeVoiceToolCall("webmcp:mcp_err", {}, { autoConfirmReadOnly: true })).toEqual({
899
+ status: "failed",
900
+ error: { message: "no such slide" },
901
+ });
902
+ });
903
+
904
+ it("hands the call's signal to the confirm handler so the gate can close on abort", async () => {
905
+ const abort = new AbortController();
906
+ const onConfirm = vi.fn(async (info: { signal?: AbortSignal }) => {
907
+ expect(info.signal).toBe(abort.signal);
908
+ return false;
909
+ });
910
+ registry.tools = [fakeTool({ name: "edit_slide" })];
911
+ const bridge = new WebMcpBridge({ enabled: true, onConfirm });
912
+ await bridge.executeVoiceToolCall("webmcp:edit_slide", {}, { signal: abort.signal, autoConfirmReadOnly: true });
913
+ expect(onConfirm).toHaveBeenCalledTimes(1);
914
+ });
915
+
916
+ it("does not run the tool when the signal aborts while the gate is open", async () => {
917
+ const abort = new AbortController();
918
+ const execute = vi.fn(() => "done");
919
+ const onConfirm = vi.fn(async () => {
920
+ abort.abort();
921
+ return true;
922
+ });
923
+ registry.tools = [fakeTool({ name: "edit_slide", execute })];
924
+ const bridge = new WebMcpBridge({ enabled: true, onConfirm });
925
+ const r = await bridge.executeVoiceToolCall("webmcp:edit_slide", {}, {
926
+ signal: abort.signal,
927
+ autoConfirmReadOnly: true,
928
+ });
929
+ expect(execute).not.toHaveBeenCalled();
930
+ expect(r.status).toBe("failed");
931
+ });
932
+
933
+ it("keeps executeToolCall's /resume shape and gate unchanged", async () => {
934
+ const onConfirm = vi.fn(async () => false);
935
+ registry.tools = [fakeTool({ name: "next_slide", annotations: { readOnlyHint: true } })];
936
+ const bridge = new WebMcpBridge({ enabled: true, onConfirm });
937
+ const r = await bridge.executeToolCall("webmcp:next_slide", {});
938
+ expect(onConfirm).toHaveBeenCalledTimes(1);
939
+ expect(r).toEqual({ isError: true, content: [{ type: "text", text: "User declined the tool call." }] });
940
+ });
941
+ });
942
+
943
+ describe("WebMcpBridge.watchToolChanges", () => {
944
+ it("calls the listener on the registry's toolchange event until unsubscribed", async () => {
945
+ const mc = Object.assign(new EventTarget(), makeModelContext());
946
+ vi.stubGlobal("document", { modelContext: mc });
947
+ const bridge = new WebMcpBridge({ enabled: true });
948
+ const listener = vi.fn();
949
+ const stop = bridge.watchToolChanges(listener);
950
+ await new Promise((resolve) => setTimeout(resolve, 0));
951
+ mc.dispatchEvent(new Event("toolchange"));
952
+ expect(listener).toHaveBeenCalledTimes(1);
953
+ stop();
954
+ mc.dispatchEvent(new Event("toolchange"));
955
+ expect(listener).toHaveBeenCalledTimes(1);
956
+ });
957
+
958
+ it("is inert on a registry without events", async () => {
959
+ const bridge = new WebMcpBridge({ enabled: true });
960
+ const stop = bridge.watchToolChanges(vi.fn());
961
+ await new Promise((resolve) => setTimeout(resolve, 0));
962
+ expect(() => stop()).not.toThrow();
963
+ });
964
+ });
@@ -25,8 +25,10 @@
25
25
  * not replace an existing context. v5's initializeWebMCPPolyfill() and v6's
26
26
  * installWebMCP() are both accepted by custom loaders.
27
27
  *
28
- * Every call passes one confirm gate before execution, regardless of
29
- * annotations.readOnlyHint. v6 callbacks also receive an invocation signal.
28
+ * Every chat-path call passes one confirm gate before execution, regardless of
29
+ * annotations.readOnlyHint. The one exception is a voice fast-tier
30
+ * `client_tool_call`, which skips the gate only for a tool whose registry entry
31
+ * declares `readOnlyHint: true`. v6 callbacks also receive an invocation signal.
30
32
  */
31
33
 
32
34
  import type { ClientToolDefinition } from "./types";
@@ -74,10 +76,7 @@ export interface ModelContextToolInfo {
74
76
  * absent on v4 and whenever the tool declared none -- the spec omits the
75
77
  * member rather than emitting an empty object.
76
78
  */
77
- annotations?: {
78
- readOnlyHint?: boolean;
79
- untrustedContentHint?: boolean;
80
- };
79
+ annotations?: ClientToolDefinition["annotations"];
81
80
  }
82
81
 
83
82
  export interface ModelContextCoreLike {
@@ -129,7 +129,11 @@ describe.each([
129
129
  type: "object",
130
130
  properties: { query: { type: "string" } },
131
131
  },
132
- annotations: { readOnlyHint: true, untrustedContentHint: true },
132
+ annotations: {
133
+ readOnlyHint: true,
134
+ untrustedContentHint: true,
135
+ ...(legacy ? {} : { consequentialHint: false, debugging: false }),
136
+ },
133
137
  untrustedContentHint: true,
134
138
  }),
135
139
  ]);
@@ -16,6 +16,7 @@
16
16
  import type {
17
17
  AgentWidgetWebMcpConfig,
18
18
  ClientToolDefinition,
19
+ VoiceClientToolOutcome,
19
20
  WebMcpConfirmHandler,
20
21
  WebMcpConfirmInfo,
21
22
  WebMcpToolResult,
@@ -38,6 +39,22 @@ export interface WebMcpBridgeDeps {
38
39
 
39
40
  const DEFAULT_TOOL_TIMEOUT_MS = 30_000;
40
41
 
42
+ /**
43
+ * One run of a page tool, before it is shaped for `/resume` (`executeToolCall`)
44
+ * or for a voice `client_tool_result` (`executeVoiceToolCall`). `message` is
45
+ * the exact text the `/resume` error result has always carried.
46
+ */
47
+ type ToolRunFailureReason = "stale" | "denied" | "failed" | "timeout" | "aborted";
48
+ type ToolRunOutcome =
49
+ | { ok: true; raw: string | null }
50
+ | { ok: false; reason: ToolRunFailureReason; message: string };
51
+
52
+ const runFailure = (reason: ToolRunFailureReason, message: string): ToolRunOutcome => ({
53
+ ok: false,
54
+ reason,
55
+ message,
56
+ });
57
+
41
58
  // Local copies of the wire-prefix helpers: pure, and duplicating them keeps
42
59
  // this chunk free of any value edge into core webmcp-bridge state.
43
60
  const WEBMCP_TOOL_PREFIX = "webmcp:";
@@ -177,11 +194,77 @@ export class WebMcpBridge {
177
194
  args: unknown,
178
195
  signal?: AbortSignal,
179
196
  ): Promise<WebMcpToolResult> {
197
+ const outcome = await this.#run(wireToolName, args, signal, false);
198
+ return outcome.ok
199
+ ? normalizeSerializedResult(outcome.raw)
200
+ : errorResult(outcome.message);
201
+ }
202
+
203
+ /**
204
+ * Execute a page tool the voice server picked for a delegation
205
+ * (`client_tool_call`) and return its `client_tool_result` outcome.
206
+ *
207
+ * Same registry lookup, allowlist, abort points and timeout as
208
+ * `executeToolCall`, with one difference: when `autoConfirmReadOnly` is set,
209
+ * a tool whose LIVE registry entry declares `annotations.readOnlyHint ===
210
+ * true` skips the confirm gate. Every other tool still shows it, and a
211
+ * decline answers `denied`. A tool missing from the registry or excluded by
212
+ * the allowlist answers `stale` (the server's snapshot is out of date).
213
+ * `signal` aborting (the caller's deadline, a hang-up) answers `failed`;
214
+ * the caller decides whether that was its timeout.
215
+ */
216
+ public async executeVoiceToolCall(
217
+ wireToolName: string,
218
+ args: unknown,
219
+ options: { signal?: AbortSignal; autoConfirmReadOnly?: boolean } = {},
220
+ ): Promise<VoiceClientToolOutcome> {
221
+ const outcome = await this.#run(
222
+ wireToolName,
223
+ args,
224
+ options.signal,
225
+ options.autoConfirmReadOnly === true,
226
+ );
227
+ if (!outcome.ok) {
228
+ return {
229
+ status: outcome.reason === "aborted" ? "failed" : outcome.reason,
230
+ error: { message: outcome.message },
231
+ };
232
+ }
233
+ return voiceOutcomeFromRaw(outcome.raw);
234
+ }
235
+
236
+ /**
237
+ * Call `listener` whenever the page's tool registry changes (the WebMCP
238
+ * `toolchange` event on `document.modelContext`). Returns an unsubscribe.
239
+ * A registry without the event never calls it: callers then keep the
240
+ * snapshot they took first.
241
+ */
242
+ public watchToolChanges(listener: () => void): () => void {
243
+ let stopped = false;
244
+ let target: EventTarget | null = null;
245
+ void this.#ensureReady().then(() => {
246
+ if (stopped || this.config.enabled !== true) return;
247
+ const mc = this.#getModelContext() as unknown as Partial<EventTarget> | null;
248
+ if (!mc || typeof mc.addEventListener !== "function") return;
249
+ target = mc as EventTarget;
250
+ target.addEventListener("toolchange", listener);
251
+ });
252
+ return () => {
253
+ stopped = true;
254
+ target?.removeEventListener("toolchange", listener);
255
+ target = null;
256
+ };
257
+ }
258
+
259
+ async #run(
260
+ wireToolName: string,
261
+ args: unknown,
262
+ signal: AbortSignal | undefined,
263
+ autoConfirmReadOnly: boolean,
264
+ ): Promise<ToolRunOutcome> {
180
265
  await this.#ensureReady();
181
266
  if (this.config.enabled !== true) {
182
- return errorResult(
183
- "WebMCP is not enabled on this widget.",
184
- );
267
+ return runFailure("failed", "WebMCP is not enabled on this widget.");
185
268
  }
186
269
 
187
270
  const mc = this.#getModelContext();
@@ -193,7 +276,8 @@ export class WebMcpBridge {
193
276
  const present =
194
277
  typeof document !== "undefined" &&
195
278
  Boolean((document as Document & { modelContext?: unknown }).modelContext);
196
- return errorResult(
279
+ return runFailure(
280
+ "failed",
197
281
  present
198
282
  ? "WebMCP is not operational: document.modelContext is present but does not expose the strict getTools()/executeTool() surface (likely a different or older WebMCP polyfill)."
199
283
  : "WebMCP bridge is not operational on this page (document.modelContext not available).",
@@ -207,13 +291,14 @@ export class WebMcpBridge {
207
291
  infos = (await mc.getTools()).filter(isLocalTool);
208
292
  } catch (err) {
209
293
  const message = err instanceof Error ? err.message : String(err);
210
- return errorResult(`Failed to read WebMCP registry: ${message}`);
294
+ return runFailure("failed", `Failed to read WebMCP registry: ${message}`);
211
295
  }
212
296
  this.#deps.recordToolDisplayTitles(infos);
213
297
  const info = infos.find((candidate) => candidate.name === bareName);
214
298
 
215
299
  if (!info) {
216
- return errorResult(
300
+ return runFailure(
301
+ "stale",
217
302
  `WebMCP tool not registered on this page: ${bareName}`,
218
303
  );
219
304
  }
@@ -225,7 +310,8 @@ export class WebMcpBridge {
225
310
  // tool. The server is the trust boundary; this is a defense-in-depth
226
311
  // convenience check to keep us symmetric with the snapshot.
227
312
  if (!this.#passesClientAllowlist(bareName)) {
228
- return errorResult(
313
+ return runFailure(
314
+ "stale",
229
315
  `WebMCP tool not allowed by client allowlist: ${bareName}`,
230
316
  );
231
317
  }
@@ -233,28 +319,33 @@ export class WebMcpBridge {
233
319
  // Bail before the confirm renders: a late approval after cancel() would
234
320
  // otherwise fire a host-page side effect with no matching /resume.
235
321
  if (signal?.aborted) {
236
- return errorResult("Aborted by cancel()");
322
+ return runFailure("aborted", "Aborted by cancel()");
237
323
  }
238
324
 
239
- // Confirm-by-default gate. Every `webmcp:*` call routes through here,
240
- // regardless of `annotations.readOnlyHint`.
325
+ // Confirm-by-default gate. Every chat-path `webmcp:*` call routes through
326
+ // here regardless of `annotations.readOnlyHint`; only a voice fast-tier
327
+ // call (`autoConfirmReadOnly`) of a tool the registry marks read-only skips it.
241
328
  const displayTitle = this.#deps.getToolDisplayTitle(bareName);
329
+ const annotations = pickAnnotations(info.annotations);
242
330
  const gateInfo: WebMcpConfirmInfo = {
243
331
  toolName: bareName,
244
332
  args,
245
333
  description: info.description,
334
+ ...(annotations ? { annotations } : {}),
246
335
  ...(displayTitle ? { title: displayTitle } : {}),
336
+ ...(signal ? { signal } : {}),
247
337
  reason: "gate",
248
338
  };
249
- if (!(await this.#requestConfirm(gateInfo))) {
250
- return errorResult("User declined the tool call.");
339
+ const skipGate = autoConfirmReadOnly && annotations?.readOnlyHint === true;
340
+ if (!skipGate && !(await this.#requestConfirm(gateInfo))) {
341
+ return runFailure("denied", "User declined the tool call.");
251
342
  }
252
343
 
253
344
  // The await above may have parked us long enough for cancel() to fire.
254
345
  // Bail before invoking `execute()` so we don't fire a side effect that
255
346
  // the server can no longer accept a `/resume` for.
256
347
  if (signal?.aborted) {
257
- return errorResult("Aborted by cancel()");
348
+ return runFailure("aborted", "Aborted by cancel()");
258
349
  }
259
350
 
260
351
  // Drive both the 30s timeout and the caller's `signal` through a single
@@ -278,18 +369,19 @@ export class WebMcpBridge {
278
369
  const raw = await mc.executeTool(info, createCompatibleToolInput(args), {
279
370
  signal: controller.signal,
280
371
  });
281
- return normalizeSerializedResult(raw);
372
+ return { ok: true, raw };
282
373
  } catch (err) {
283
374
  if (timedOut) {
284
- return errorResult(
375
+ return runFailure(
376
+ "timeout",
285
377
  `WebMCP tool '${bareName}' timed out after ${this.#timeoutMs}ms`,
286
378
  );
287
379
  }
288
380
  if (signal?.aborted) {
289
- return errorResult("Aborted by cancel()");
381
+ return runFailure("aborted", "Aborted by cancel()");
290
382
  }
291
383
  const message = err instanceof Error ? err.message : String(err);
292
- return errorResult(message);
384
+ return runFailure("failed", message);
293
385
  } finally {
294
386
  clearTimeout(timer);
295
387
  if (signal) signal.removeEventListener("abort", onAbort);
@@ -442,7 +534,7 @@ const EMPTY_INPUT_SCHEMA = { type: "object", properties: {} } as const;
442
534
  * Copy the WebMCP annotation hints onto the wire, dropping anything else.
443
535
  *
444
536
  * `getTools()` returns `annotations` since `@mcp-b/webmcp-polyfill` v5 (absent
445
- * on v4 and when the tool declared none). Only the spec's two boolean hints are
537
+ * on v4 and when the tool declared none). Only supported boolean hints are
446
538
  * forwarded -- annotations are page-authored, so an explicit copy keeps
447
539
  * functions/junk off the dispatch payload. Returns `undefined` when nothing
448
540
  * usable remains so the member is omitted, matching the spec's own
@@ -452,14 +544,14 @@ const pickAnnotations = (
452
544
  raw: ModelContextToolInfo["annotations"],
453
545
  ): ClientToolDefinition["annotations"] | undefined => {
454
546
  if (!raw || typeof raw !== "object") return undefined;
455
- const picked = {
456
- ...(typeof raw.readOnlyHint === "boolean"
457
- ? { readOnlyHint: raw.readOnlyHint }
458
- : {}),
459
- ...(typeof raw.untrustedContentHint === "boolean"
460
- ? { untrustedContentHint: raw.untrustedContentHint }
461
- : {}),
462
- };
547
+ const picked: NonNullable<ClientToolDefinition["annotations"]> = {};
548
+ for (const key of [
549
+ "readOnlyHint", "destructiveHint", "consequentialHint", "untrustedContentHint",
550
+ "idempotentHint", "openWorldHint", "debugging",
551
+ ] as const) {
552
+ const value = raw[key];
553
+ if (typeof value === "boolean") picked[key] = value;
554
+ }
463
555
  return Object.keys(picked).length > 0 ? picked : undefined;
464
556
  };
465
557
 
@@ -544,6 +636,39 @@ const errorResult = (message: string): WebMcpToolResult => ({
544
636
  content: [{ type: "text", text: message }],
545
637
  });
546
638
 
639
+ const textOfContent = (content: unknown): string =>
640
+ Array.isArray(content)
641
+ ? content
642
+ .map((block: unknown) =>
643
+ block && typeof block === "object" && typeof (block as { text?: unknown }).text === "string"
644
+ ? (block as { text: string }).text
645
+ : "",
646
+ )
647
+ .filter(Boolean)
648
+ .join("\n")
649
+ : "";
650
+
651
+ /**
652
+ * A page tool's serialized return as a voice `client_tool_result`: the tool's
653
+ * own JSON value (not the MCP content wrapper `/resume` gets), so the server
654
+ * can template a readout from its fields. A tool that returned an MCP error
655
+ * (`isError: true`) is `failed`, with its text as the message.
656
+ */
657
+ const voiceOutcomeFromRaw = (raw: string | null): VoiceClientToolOutcome => {
658
+ if (raw === null || raw === undefined) return { status: "completed" };
659
+ let parsed: unknown;
660
+ try {
661
+ parsed = JSON.parse(raw);
662
+ } catch {
663
+ return { status: "completed", result: raw };
664
+ }
665
+ if (parsed !== null && typeof parsed === "object" && (parsed as { isError?: unknown }).isError === true) {
666
+ const message = textOfContent((parsed as { content?: unknown }).content);
667
+ return { status: "failed", error: { message: message || "The page tool reported an error." } };
668
+ }
669
+ return parsed === undefined ? { status: "completed" } : { status: "completed", result: parsed };
670
+ };
671
+
547
672
  /**
548
673
  * Fallback confirm UI: `window.confirm()`. Production deployments should wire
549
674
  * `config.webmcp.onConfirm` to a handler matched to their UX (e.g. an inline
@@ -1,9 +0,0 @@
1
- import{t as e}from"./chunk-UlcxdHef.js";import{t}from"./chunk-DqbzUVEI.js";const n=16e3,r=/* @__PURE__ */ new Map([[`idle_timeout`,`Voice call ended after a quiet period.`],[`max_duration`,`This voice call reached its time limit.`],[`provider_ended`,`The voice session ended.`],[`quota`,`Voice is unavailable right now.`],[`auth_expired`,`Voice session expired. Tap the mic to reconnect.`]]),i=[`provider_error`,`server_restart`],a=`Voice connection lost.`;function o(e,t){let n=t.slice(0,4e3).trim(),r=8e3-(n?n.length+2:0)-20,i=[];for(let t of e.slice(-12).reverse()){let e=t.content.replace(/\s+/g,` `).trim();if(!e)continue;e.length>2e3&&(e=`${e.slice(0,1999)}…`);let n=`${t.role===`user`?`User`:`Assistant`}: ${e}`;if(n.length+1>r)break;r-=n.length+1,i.unshift(n)}let a=i.length>0?[`Conversation so far:\n${i.join(`
2
- `)}`]:[];return n&&a.push(n),a.join(`
3
-
4
- `)}function s(e){return e.byteLength>=44&&new DataView(e).getUint32(0,!1)===1380533830?new Uint8Array(e,44):new Uint8Array(e)}function c(e){let t=e.replace(/\/+$/,``);return/^wss?:\/\//i.test(t)?t:/^https?:\/\//i.test(t)?t.replace(/^http/i,`ws`):`${typeof window<`u`&&window.location?.protocol===`https:`?`wss:`:`ws:`}//${t}`}function l(e){return c(e).replace(/^ws/i,`http`)}function u(e){return typeof e!=`number`||!Number.isFinite(e)?3e4:Math.min(6e5,Math.max(3e4,Math.round(e)))}var d=class{#e;#t;#n;#r;#i;#a;#o;#s;#c;#l;#u;#d;#f;#p;#m;#h;#g;#_;#v;#y;#b;#x;#S;#C;#w;#T;#E;#D;#O;#k;#A;#j;#M;#N;#P;#F;#I;#L;#R;#z;#B;#V;#H;#U;#W;#G;constructor(e){this.config=e,this.type=`runtype`,this.#e=null,this.#t=null,this.#n=[],this.#r=null,this.#i=null,this.#a=null,this.#o=[],this.#s=0,this.#c=null,this.#l=!1,this.#u=!1,this.#d=0,this.#f=!1,this.#p=-1/0,this.#m=null,this.#h=!1,this.#g=!1,this.#_=0,this.#v=0,this.#b=!1,this.bridge=null,this.#x=!1,this.#S=/* @__PURE__ */ new Set,this.#C=/* @__PURE__ */ new Map,this.#w=/* @__PURE__ */ new Set,this.#E=null,this.#D=null,this.#k=!1,this.#A=!1,this.#j=!1,this.#M=null,this.#N=null,this.#P=Promise.resolve(),this.#F=/* @__PURE__ */ new Set,this.#I=/* @__PURE__ */ new Set,this.#L=null,this.#R=!1,this.#z=/* @__PURE__ */ new Set,this.#B=/* @__PURE__ */ new Set,this.#V=[],this.#H=[],this.#U=[],this.#W=[],this.#G=[]}async connect(){}setSessionBridge(e){this.bridge=e}prewarm(){if(this.#l||this.#m)return;let e=this.config?.agentId,t=this.config?.clientToken,n=this.config?.host;if(!e||!t||!n)return;let r=Date.now();if(!(r-this.#p<3e4)){this.#p=r;try{this.config?.prewarmMode===`attach`?this.#q(n,e,t):this.#K(n,e,t)}catch{}}}#K(e,t,n){if(typeof fetch!=`function`)return;let r=`${l(e)}/v1/client/agents/${encodeURIComponent(t)}/voice/prewarm`;fetch(r,{method:`POST`,headers:{Authorization:`Bearer ${n}`},keepalive:!0}).catch(()=>{})}#q(e,t,n){let r=new URLSearchParams,i=this.config?.attachIdleMs,a=u(i);i!==void 0&&r.set(`attachIdleMs`,String(a));let o=new WebSocket(this.#Z(e,t,r),[`runtype.bearer`,`runtype.attach`,n]);o.binaryType=`arraybuffer`;let s=()=>{},c={ws:o,state:`connecting`,ready:new Promise(e=>{s=e}),settle:s,openedAt:0,idleTimer:void 0};this.#m=c,o.onopen=()=>{this.#m===c&&(c.state=o.protocol===`runtype.attach`?`attached`:`live`,c.openedAt=Date.now(),this.#J(c,a),c.state===`attached`&&o.send(`{"type":"ping"}`),c.settle())},o.onmessage=e=>{if(this.#m!==c||typeof e.data!=`string`)return;let t;try{t=JSON.parse(e.data)}catch{return}t.type===`attached`&&typeof t.idleMs==`number`&&this.#J(c,t.idleMs-(Date.now()-c.openedAt))},o.onerror=()=>{},o.onclose=()=>{this.#m===c&&this.#Y()}}#J(e,t){clearTimeout(e.idleTimer),e.idleTimer=setTimeout(()=>{this.#m===e&&this.#Y()},Math.max(0,t))}#Y(){let e=this.#m;if(!e)return;this.#m=null,clearTimeout(e.idleTimer);let{ws:t}=e;if(t.onopen=null,t.onmessage=null,t.onerror=null,t.onclose=null,e.settle(),t.readyState===WebSocket.CONNECTING||t.readyState===WebSocket.OPEN)try{t.close(1e3,`prewarm released`)}catch{}}async#X(){let e=this.#m;if(!e)return null;if(e.state===`connecting`){let t;await Promise.race([e.ready,new Promise(e=>{t=setTimeout(e,5e3)})]),clearTimeout(t)}return this.#m===e?e.state===`connecting`||e.ws.readyState!==WebSocket.OPEN?(this.#Y(),null):(this.#m=null,clearTimeout(e.idleTimer),{ws:e.ws,live:e.state===`live`}):null}#Z(t,n,r){let i=[`partial_transcript`,`context`];this.bridge&&this.config?.clientDelegation!==!1&&i.push(`client_delegation`,`delegation_update`,`delegation_read_back`,`delegation_stream`),r&&i.unshift(`attach`);let a=new URLSearchParams({voiceProtocol:`runtype-browser-v1`,clientVersion:`persona/${e}`,voiceCapabilities:`full-duplex-v1`,clientCapabilities:i.join(`,`)});return r?.forEach((e,t)=>a.set(t,e)),`${c(t)}/ws/agents/${encodeURIComponent(n)}/voice?${a}`}async startListening(){if(this.#l)return;let e=this.#A;e||(this.#k=!1,this.#D=null),this.#A=!1;let r=this.config?.agentId,i=this.config?.clientToken,o=this.config?.host;if(!r)throw Error(`Runtype voice requires an agentId`);if(!i)throw Error(`Runtype voice requires a clientToken`);if(!o)throw Error(`Runtype voice requires a host (or widget apiUrl)`);let s=++this.#d;this.#f=!1,this.#l=!0;try{let c=await navigator.mediaDevices.getUserMedia({audio:{sampleRate:n,channelCount:1,echoCancellation:!0}});if(s!==this.#d){c.getTracks().forEach(e=>e.stop());return}this.#r=c;let l=new(window.AudioContext||window.webkitAudioContext)({sampleRate:n});if(this.#t=l,l.state===`suspended`){if(await Promise.race([l.resume().catch(()=>{}),new Promise(t=>setTimeout(t,e?500:1e3))]),s!==this.#d)return;if(l.state!==`running`)throw Error(e?this.#D=`${a} Tap the mic to reconnect.`:`Audio blocked until a tap`)}let u=this.config?.createPlaybackEngine?await this.config.createPlaybackEngine():new t(24e3);if(s!==this.#d){u.destroy(),c.getTracks().forEach(e=>e.stop()),l.close().catch(()=>{});return}this.#c=u,this.#de(),this.#ee(l,c,s);let d=await this.#X();if(s!==this.#d){d?.ws.close(1e3,`client ended call`);return}if(d){let{ws:e}=d;this.#e=e,this.#Q(e,s),d.live||e.send(`{"type":"start"}`),this.#te(e),this.#ge(`listening`);return}let f=new WebSocket(this.#Z(o,r),[`runtype.bearer`,i]);f.binaryType=`arraybuffer`,this.#e=f,f.onopen=()=>{s===this.#d&&(this.#te(f),this.#ge(`listening`))},this.#Q(f,s)}catch(e){throw this.#he(),this.#_e(e),this.#ge(`error`),e}}#Q(e,t){e.onmessage=e=>this.#ne(e,t),e.onerror=()=>{t!==this.#d||this.#E||(this.#_e(/* @__PURE__ */ Error(`Voice connection failed`)),this.#ge(`error`),this.#he())},e.onclose=e=>{if(this.#f){this.#f=!1;return}if(t!==this.#d)return;let n=this.#E??(e.code===4408?{reason:`idle_timeout`}:null);if(n)this.#$(n.reason,n.message);else{if(e.code!==1e3){let t=e.code?` (code ${e.code})`:``;this.#_e(/* @__PURE__ */ Error(`Voice connection closed${t}`)),this.#ge(`error`)}else this.#ge(`idle`);this.#he()}}}#$(e,t){let n=i.includes(e)&&!this.#k;if(this.#he(),n)this.#k=!0,this.#l=!0,this.#D=e===`provider_error`?`${a} Reconnecting…`:`Reconnecting…`,this.#O=setTimeout(()=>{this.#l=!1,this.#A=!0,this.startListening().catch(()=>{})},1e3+Math.random()*1e3),this.#ge(`idle`);else{if(i.includes(e))return this.#D=a,this.#_e(/* @__PURE__ */ Error(a)),this.#ge(`error`);if(e===`error`)return this.#ge(`error`);this.#D=e===`quota`&&t||r.get(e)||null,this.#ge(`idle`)}}takeNotice(){let e=this.#D;return this.#D=null,e}getDisclosure(){let e=this.config?.disclosureText;return this.#l&&this.#h&&e!==!1?e||`You're talking to an AI assistant. Voice is processed by OpenAI.`:null}async stopListening(){this.#he(),this.#ge(`idle`)}async disconnect(){this.#he(),this.#ge(`disconnected`),this.#V=[],this.#H=[],this.#U=[],this.#W=[],this.#G=[]}stopPlayback(){this.#me();let e=this.#e;e&&e.readyState===WebSocket.OPEN&&(this.#h&&(this.#_=Date.now()+2e3,e.send(`{"type":"cancel"}`)),this.#ge(`listening`))}getInterruptionMode(){return`barge-in`}isBargeInActive(){return this.#l}onLevel(e){this.#n.push(e)}async deactivateBargeIn(){this.#he(),this.#ge(`idle`)}#ee(e,t,n){let r=e.createMediaStreamSource(t);this.#i=r;let i=e.createScriptProcessor(4096,1,1);this.#a=i,i.onaudioprocess=e=>{if(n!==this.#d)return;let t=this.#e,r=t?.readyState===WebSocket.OPEN;if(t&&!r&&t.readyState!==WebSocket.CONNECTING)return;let i=e.inputBuffer.getChannelData(0),a=new Int16Array(i.length),o=0;for(let e=0;e<i.length;e++){let t=Math.max(-1,Math.min(1,i[e]));o+=t*t,a[e]=t<0?t*32768:t*32767}if(t&&r)this.#te(t),t.send(a.buffer);else for(this.#o.push(a.buffer),this.#s+=a.byteLength;this.#s>256e3;)this.#s-=this.#o.shift().byteLength;if(this.#n.length>0){let e=Math.sqrt(o/i.length),t=Math.max(0,Math.min(1,e*4));for(let e of this.#n)e(t)}},r.connect(i),i.connect(e.destination)}#te(e){for(let t of this.#o)e.send(t);this.#o=[],this.#s=0}#ne(e,t){if(t!==this.#d)return;if(e.data instanceof ArrayBuffer){this.#le(e.data,t);return}let n;try{n=JSON.parse(e.data)}catch{return}switch(n.type){case`session_config`:n.speechMode&&(this.#h=n.speechMode===`speech_to_speech`,this.#c?.setContinuousMode?.(this.#h)),Array.isArray(n.capabilities)&&(this.#S=new Set(n.capabilities)),typeof n.callId==`string`&&(this.#T=n.callId),this.#x=this.#h&&this.#S.has(`client_delegation`)&&!!this.bridge,this.#h&&!this.#u&&this.#ge(`listening`),this.#S.has(`context`)&&!this.#j&&(this.#j=!0,this.#M=this.#re());break;case`transcript_update`:{let e=n.role===`assistant`?`assistant`:`user`,r=n.utteranceId??n.turnId;if(!r||e===`assistant`&&this.#ue())break;let i=String(r);if(e===`user`)n.final===!0&&this.#ie(t),this.#R=!1;else{if(this.#S.has(`delegation_read_back`)){let e=n.delegationId;(typeof e==`string`&&this.#F.has(e)&&!this.#z.has(i)||this.#R&&!this.#z.has(i))&&(this.#B.add(i),this.#R=!1)}else this.#R&&!this.#z.has(i)&&(this.#B.add(i),this.#R=!1);if(this.#z.add(i),this.#B.has(i))break}this.#ve(e,n.text??``,n.final===!0,{turnId:i,...typeof n.startMs==`number`&&{startMs:n.startMs},...typeof n.endMs==`number`&&{endMs:n.endMs},...this.#x&&{caption:!0}});break}case`delegation_started`:case`delegation_completed`:{let e=String(n.delegationId??n.turnId);if(n.type===`delegation_started`){this.#ie(t);let r=n.input;this.#x&&r&&this.#ce(e,{delegationId:e,userText:String(r.text??``),userUtteranceIds:Array.isArray(r.userUtteranceIds)?r.userUtteranceIds.filter(e=>typeof e==`string`):[],messages:Array.isArray(r.messages)?r.messages:[]},t)}else n.speak!==!1&&this.#F.has(e)&&!this.#I.delete(e)&&!this.#S.has(`delegation_read_back`)&&this.#oe(e);this.#g=n.type===`delegation_started`,!this.#u&&!this.#ue()&&this.#ge(this.#g?`processing`:`listening`);break}case`delegation_progress`:{let e=String(n.delegationId);this.#F.has(e)&&!this.#I.has(e)&&!this.#w.has(e)&&(this.#I.add(e),this.#oe(e));break}case`delegation_cancelled`:this.#se(String(n.delegationId),n.reason!==`session_ending`&&n.reason!==`provider_cancelled`);break;case`warning`:console.warn(`[Persona voice] ${n.code}: ${n.message??``}`,this.#T??``),n.delegationId&&(n.code===`UNKNOWN_DELEGATION`||n.code===`LATE_RESULT_LIMIT`)&&this.#se(String(n.delegationId));break;case`audio_clear`:this.#me(),this.#_=0,this.#ge(`listening`);break;case`transcript_interim`:this.#ge(`listening`),this.#ve(`user`,n.text??``,!1);break;case`transcript_final`:{let e=n.role===`assistant`?`assistant`:`user`;this.#ge(e===`user`?`processing`:`speaking`),this.#ve(e,n.text??``,!0);break}case`audio_end`:this.#c?this.#c.markStreamEnd():(this.#u=!1,this.#ge(`listening`));break;case`metrics`:this.#ye({llmMs:n.llm_ms,ttsMs:n.tts_ms,firstAudioMs:n.first_audio_ms,totalMs:n.total_ms});break;case`error`:this.#_e(Error(n.error||`Voice error`)),this.#ge(`error`);break;case`session_end`:this.#E={reason:String(n.reason),...typeof n.message==`string`&&{message:n.message}}}}async#re(){let e=this.bridge?.getHistory()??[],t=``;try{let e=this.config?.callContext;t=String((typeof e==`function`?await e():e)??``)}catch{}return o(e,t)}#ie(e){let t=this.#M;if(!t)return;this.#M=null;let n=!1,r=t.then(t=>{let r=this.#e;!n&&t&&e===this.#d&&r?.readyState===WebSocket.OPEN&&r.send(JSON.stringify({type:`context`,text:t}))});this.#N={done:r,drop:()=>n=!0}}async#ae(){let e=this.#N;if(!e)return;let t,n=await Promise.race([e.done.then(()=>!1),new Promise(e=>{t=setTimeout(()=>e(!0),2e3)})]);clearTimeout(t),n&&e.drop()}#oe(e){this.#R=!0,this.#L=e}#se(e,t=!1){this.#w.add(e),this.#L===e&&(this.#R=!1),this.#I.delete(e),this.bridge?.dropDelegation?.(e,t),this.#C.get(e)?.abort()}#ce(e,t,n){let r=this.bridge,i=({type:t,...r})=>{let i=this.#e;return n!==this.#d||i?.readyState!==WebSocket.OPEN||this.#w.has(e)?!1:(i.send(JSON.stringify({type:t,delegationId:e,...r})),!0)},a=(e,t,n,r=0)=>i({type:e,status:t,text:n,...r>0&&{streamedChars:r}});this.#P=this.#P.then(async()=>{if(n!==this.#d||this.#w.has(e))return;let o=null,s=this.#S.has(`delegation_stream`)&&this.#S.has(`delegation_read_back`),c=t=>{o=(o??this.#ae()).then(()=>{i({type:`delegation_delta`,text:t})&&this.#F.add(e)})},l=await r.runDelegatedTurn(s?{...t,stream:c}:t).catch(()=>({status:`failed`,text:``}));await this.#ae(),await o;let u=s?l.streamedChars??0:0,d=!!l.followUp,f=d&&this.#S.has(`delegation_update`);if(!a(f?`delegation_update`:`delegation_result`,d&&!f?`completed`:l.status,l.text,u)||(l.status!==`failed`&&l.inChat!==!1&&this.#F.add(e),!l.followUp))return;let p=new AbortController;this.#C.set(e,p),l.followUp({signal:p.signal,approvalTimeoutMs:this.config?.approvalTimeoutMs&&Math.min(this.config.approvalTimeoutMs,54e4),readBack:f,onUpdate:e=>a(`delegation_update`,`pending_approval`,e)}).then(t=>{this.#C.delete(e),f&&t&&!p.signal.aborted&&a(`delegation_result`,t.status,t.text)})})}#le(e,t){if(t!==this.#d||!this.#c||this.#ue())return;let n=s(e);n.length!==0&&(this.#u||(this.#u=!0,this.#ge(`speaking`)),this.#de(),this.#c.enqueue(n),this.#h&&this.#fe(n.length))}#ue(){return Date.now()<this.#_}#de(){if(this.#b||!this.#c)return;this.#b=!0;let e=this.#d;this.#c.onFinished(()=>{e===this.#d&&(this.#b=!1,this.#pe())})}#fe(e){let t=Date.now();this.#v=Math.max(t,this.#v)+e/48,clearTimeout(this.#y),this.#y=setTimeout(()=>this.#pe(),this.#v-t+300)}#pe(){this.#u=!1,this.#v=0,this.#e&&this.#e.readyState===WebSocket.OPEN&&this.#ge(this.#g?`processing`:`listening`)}#me(){this.#c?.flush(),this.#b=!1,this.#de(),this.#u=!1,this.#v=0,clearTimeout(this.#y)}#he(){this.#d+=1,this.#l=!1,this.#u=!1,this.#Y(),this.#h=!1,this.#g=!1,this.#x=!1,this.#S=/* @__PURE__ */ new Set;for(let e of this.#C.values())e.abort();if(this.#C.clear(),this.#w.clear(),this.#T=void 0,this.#E=null,clearTimeout(this.#O),this.#j=!1,this.#M=null,this.#N=null,this.#P=Promise.resolve(),this.#F.clear(),this.#I.clear(),this.#L=null,this.#R=!1,this.#z.clear(),this.#B.clear(),this.#b=!1,this.#_=0,this.#v=0,clearTimeout(this.#y),this.#o=[],this.#s=0,this.#a&&=(this.#a.onaudioprocess=null,this.#a.disconnect(),null),this.#i&&=(this.#i.disconnect(),null),this.#r&&=(this.#r.getTracks().forEach(e=>e.stop()),null),this.#t&&=(this.#t.close().catch(()=>{}),null),this.#c&&=(this.#c.destroy(),null),this.#e){this.#f=!0;try{this.#e.close(1e3,`client ended call`)}catch{}this.#e=null}}onResult(e){this.#V.push(e)}onError(e){this.#H.push(e)}onStatusChange(e){this.#U.push(e)}onTranscript(e){this.#W.push(e)}onMetrics(e){this.#G.push(e)}#ge(e){this.#U.forEach(t=>t(e))}#_e(e){this.#H.forEach(t=>t(e))}#ve(e,t,n,r){this.#W.forEach(i=>i(e,t,n,r))}#ye(e){this.#G.forEach(t=>t(e))}},f=class{#e;#t;#n;#r;#i;#a;constructor(e={}){this.config=e,this.type=`browser`,this.#e=null,this.#t=[],this.#n=[],this.#r=[],this.#i=!1,this.#a=typeof window<`u`?window:void 0}async connect(){this.#r.forEach(e=>e(`connected`))}async startListening(){try{if(this.#i)throw Error(`Already listening`);if(!this.#a)throw Error(`Window object not available`);let e=this.#a.SpeechRecognition||this.#a.webkitSpeechRecognition;if(!e)throw Error(`Browser speech recognition not supported`);this.#e=new e,this.#e.lang=this.config?.language||`en-US`,this.#e.continuous=this.config?.continuous||!1,this.#e.interimResults=!0,this.#e.onresult=e=>{let t=Array.from(e.results).map(e=>e[0]).map(e=>e.transcript).join(``),n=e.results[e.results.length-1].isFinal;this.#t.forEach(e=>e({text:t,confidence:n?.8:.5,provider:`browser`})),n&&!this.config?.continuous&&this.stopListening()},this.#e.onerror=e=>{this.#n.forEach(t=>t(Error(e.error))),this.#r.forEach(e=>e(`error`))},this.#e.onstart=()=>{this.#i=!0,this.#r.forEach(e=>e(`listening`))},this.#e.onend=()=>{this.#i=!1,this.#r.forEach(e=>e(`idle`))},this.#e.start()}catch(e){throw this.#n.forEach(t=>t(e)),this.#r.forEach(e=>e(`error`)),e}}async stopListening(){this.#e&&=(this.#e.stop(),null),this.#i=!1,this.#r.forEach(e=>e(`idle`))}onResult(e){this.#t.push(e)}onError(e){this.#n.push(e)}onStatusChange(e){this.#r.push(e)}async disconnect(){await this.stopListening(),this.#r.forEach(e=>e(`disconnected`))}static isSupported(){return`SpeechRecognition`in window||`webkitSpeechRecognition`in window}};function p(e){switch(e.type){case`runtype`:if(!e.runtype)throw Error(`Runtype voice provider requires configuration`);return new d(e.runtype);case`browser`:if(!f.isSupported())throw Error(`Browser speech recognition not supported`);return new f(e.browser||{});case`custom`:{let t=e.custom;if(!t)throw Error("Custom voice provider requires a `custom` provider instance or factory");let n=typeof t==`function`?t():t;if(!n||typeof n.startListening!=`function`)throw Error("Custom voice provider `custom` must be a VoiceProvider (or a factory returning one)");return n}default:throw Error(`Unknown voice provider type: ${e.type}`)}}function m(e){if(e?.type===`custom`&&e.custom)return p({type:`custom`,custom:e.custom});if(e?.type===`runtype`&&e.runtype)return p({type:`runtype`,runtype:e.runtype});if(f.isSupported())return p({type:`browser`,browser:e?.browser||{language:`en-US`}});throw Error(`No supported voice providers available`)}function h(e){try{return m(e),!0}catch{return!1}}const g=e=>e.toLowerCase().replace(/[^\p{L}\p{N}]+/gu,``);var _=class{constructor(e){this.host=e,this.turns=/* @__PURE__ */ new Map,this.latestId=null,this.cancelled=/* @__PURE__ */ new Set}apply(e,t,n,r,i,a){let o=this.host;t=t.trim();let s=this.turns.get(r);if(s||(s={},this.turns.set(r,s),this.latestId=r),e===`user`){s.userFinal=n;let e=s.userId?o.find(s.userId):void 0;if(e){let r=s.userStartMs===void 0&&i!==void 0;r&&(s.userStartMs=i),o.upsert({...e,content:t,voiceProcessing:!n,...r&&this.placeBefore(this.startedAfter(i))})}else{s.userStartMs=i;let e=s.assistantId?o.find(s.assistantId):void 0;s.userId=o.inject({role:`user`,content:t,streaming:!1,voiceProcessing:!n,voiceCaption:a,...this.placeBefore(this.startedAfter(i)??e)}).id}}else if(!this.cancelled.has(r)){s.assistantFinal=n;let e=s.assistantId?o.find(s.assistantId):void 0;if(e){let r=s.assistantStartMs===void 0&&i!==void 0;r&&(s.assistantStartMs=i),o.upsert({...e,content:t,streaming:!n,voiceProcessing:!n,...r&&this.placeBefore(this.startedAfter(i))})}else t&&(s.assistantStartMs=i,s.assistantId=o.inject({role:`assistant`,content:t,streaming:!n,voiceProcessing:!n,voiceCaption:a,...this.placeBefore(this.startedAfter(i))}).id);n&&s.assistantId&&o.markSpoken(s.assistantId)}this.sync()}claimUserTurn(e,t=[]){let n=t[t.length-1];for(let e of t)e!==n&&(this.turns.has(e)||this.turns.set(e,{}),this.turns.get(e).claimed=!0);let r;if(n){if(r=this.turns.get(n),r||(r={},this.turns.set(n,r)),r.claimed)return null;(!r.userId||!this.host.find(r.userId))&&(r.userFinal=!0,r.userId=this.host.inject({role:`user`,content:e.trim()}).id)}else{let t=g(e),n=[...this.turns.values()].reverse().flatMap(e=>{let t=e.userId&&!e.claimed?this.host.find(e.userId):void 0;return t?[{turn:e,text:g(t.content)}]:[]}).filter(({text:e})=>e&&t);r=(n.find(({text:e})=>e===t)??n.find(({text:e})=>e.startsWith(t)||t.startsWith(e)))?.turn}return r?(r.claimed=!0,r.userId):null}cancel(){let e=this.inFlight();if(e.length===0){let t=this.awaiting();if(!t)return;e=[t]}for(let t of e)this.cancelled.add(t);this.closeReplies(e),this.sync()}fail(e){let t=this.awaiting(),n=this.inFlight();if(!t&&n.length===0)return;for(let e of n)this.turns.get(e).assistantFinal=!0;this.closeReplies(n);let r=this.host.inject({role:`assistant`,content:e,streaming:!1,voiceProcessing:!1}),i=t?this.turns.get(t):void 0;i&&(i.assistantId=r.id,i.assistantFinal=!0),this.sync()}settle(){if(this.turns.size===0)return;let e=this.pending(),t=/* @__PURE__ */ new Set;for(let e of this.turns.values())e.userId&&t.add(e.userId),e.assistantId&&(t.add(e.assistantId),this.host.markSpoken(e.assistantId));this.turns.clear(),this.latestId=null,this.host.settle(t),e&&this.host.setStreaming(!1)}startedAfter(e){if(e===void 0)return;let t;for(let n of this.turns.values())for(let[r,i]of[[n.userId,n.userStartMs],[n.assistantId,n.assistantStartMs]])r&&i!==void 0&&i>e&&(!t||i<t.start)&&(t={start:i,id:r});return t&&this.host.find(t.id)}placeBefore(e){if(!e)return;let t=e.sequence??0,n=t-1;for(let r of this.turns.values())for(let i of[r.userId,r.assistantId]){let r=i?this.host.find(i):void 0,a=r?.sequence;r?.createdAt===e.createdAt&&a!==void 0&&a>n&&a<t&&(n=a)}return{createdAt:e.createdAt,sequence:(n+t)/2}}closeReplies(e){let t=/* @__PURE__ */ new Set;for(let n of e){let e=this.turns.get(n)?.assistantId;e&&(t.add(e),this.host.markSpoken(e))}t.size>0&&this.host.settle(t)}inFlight(){let e=[];for(let[t,n]of this.turns)n.assistantId&&!n.assistantFinal&&!this.cancelled.has(t)&&e.push(t);return e}awaiting(){let e=this.latestId;if(!e)return null;let t=this.turns.get(e);return t?.userFinal&&!t.assistantId&&!t.claimed&&!this.cancelled.has(e)?e:null}pending(){return this.awaiting()!==null||this.inFlight().length>0}sync(){this.host.setStreaming(this.pending())}};const v=/* @__PURE__ */ new Set(/* @__PURE__ */ `mr,mrs,ms,dr,prof,sr,jr,st,mt,vs,etc,approx,inc,ltd,co,corp,no,nos,fig,ft,dept,est,min,max,e.g,i.e,a.m,p.m,u.s,u.k`.split(`,`)),y=/([.!?]+|[,;:])(["'”’)\]*_]*)(\s+)|\n\s*/g;function b(e){let t=/(\S+)$/.exec(e)?.[1];if(!t)return!1;let n=t.replace(/^[("'“‘[*_]+/,``).toLowerCase();if(v.has(n)||/^[a-z](?:\.[a-z])*$/.test(n))return!0;if(/^\d+$/.test(t)){let n=e.length-t.length;return n===0||/\n\s*$/.test(e.slice(0,n))}return!1}function*x(e,t){let n=new RegExp(y.source,`g`);for(let r=n.exec(e);r;r=n.exec(e)){let n=r[1];if(!e.slice(0,r.index+(n?.length??0)).trim()||n!==void 0&&(/^[,;:]$/.test(n)&&!t||n===`.`&&b(e.slice(0,r.index))))continue;let i=r.index+r[0].length;i<e.length&&(yield i)}}function S(e,t){let n=e.slice(0,t),r=0;for(let e of n.matchAll(/\s+/g))n.slice(0,e.index).trim()&&(r=e.index+e[0].length);return r}function C(e){let t=0;for(let n of x(e.slice(0,220),!0))t=n;if(t)return t;let n=S(e,220);if(n)return n;let r=e.charCodeAt(219);return r>=55296&&r<=56319?219:220}var w=class{#e=``;#t=!0;push(e){return this.#e+=e,this.#n()}flush(){let e=this.#n(),t=this.#e;if(this.#e=``,t.trim()){for(let n=0;n<t.length;){let r=t.slice(n),i=r.length>220?C(r):r.length;e.push(r.slice(0,i)),n+=i}this.#t=!1}return e}idle(){let e=this.#e;if(!e.trim()||!/(?:[.!?…]["'”’)\]*_]*|\n)\s*$/.test(e))return[];this.#e=``,this.#t=!1;let t=[];for(let n=0;n<e.length;){let r=e.slice(n),i=r.length>220?C(r):r.length;t.push(r.slice(0,i)),n+=i}return t}#n(){let e=[];for(let t=this.#r();t>0;t=this.#r())e.push(this.#e.slice(0,t)),this.#e=this.#e.slice(t),this.#t=!1;return e}#r(){let e=this.#e;if(!e.trim())return 0;let t=x(e,this.#t).next().value??0;if(t&&t<=220)return t;let n=/[.!?]["'”’)\]*_]*$/.test(e);if(this.#t&&!n){let t=e.match(/\S+\s+/g)?.length??0;if(e.trim().length>=30||t>=6){let t=S(e,220);if(t)return t}}return e.length>220?C(e):0}};const T=(e,t)=>{let n=0;for(;n<e.length&&n<t.length&&e[n]===t[n];)n+=1;return n},E=`no|nope|never ?mind|decline(?: it| that)?|cancel(?: it| that| this)?(?: (?:the|my|that|this) (?:[a-z]+ )?(?:order|request))?|don'?t (?:do|place) (?:it|that|the (?:[a-z]+ )?order)`,D=RegExp(`^(?:${E})(?: (?:${E}))?(?: thanks| thank you)?$`),O=e=>D.test(e.toLowerCase().replace(/[^a-z' ]+/g,` `).replace(/\s+/g,` `).trim()),k=()=>new Promise(e=>setTimeout(e,50)),A=(e,t)=>e.length>t?`${e.slice(0,t-1).trimEnd()}…`:e,j=e=>e.replace(/^webmcp:/,``).replace(/([a-z\d])([A-Z])/g,`$1 $2`).replace(/[_\-.]+/g,` `).trim().toLowerCase();function M(e,t=0){if(e==null||e===``)return``;if(typeof e!=`object`)return String(e);if(t>2)return``;if(Array.isArray(e))return e.map(e=>M(e,t+1)).filter(Boolean).join(`, `);let n=e,r=n.quantity??n.qty??n.count,i=n.name??n.item??n.product??n.title;return r!=null&&i!=null&&typeof i!=`object`?`${r} ${i}`:Object.entries(n).filter(([e])=>!e.startsWith(`_`)).map(([e,n])=>{let r=M(n,t+1);return r&&(t?`${j(e)} ${r}`:`${j(e)}: ${r}`)}).filter(Boolean).join(t?`, `:`; `)}function N(e){let t=e.length>1?`These actions need the user's approval in the chat before they happen:`:`This action needs the user's approval in the chat before it happens:`,n=1e3-t.length-119-2;return`${t}\n${e.map(t=>{let r=[j(t.toolName)];t.description&&r.push(`(${A(t.description.trim(),160)})`);let i=M(t.parameters);return i&&r.push(`with ${i}`),t.reason&&r.push(`because: ${A(t.reason.trim(),160)}`),A(`- ${r.join(` `)}`,Math.floor(n/e.length)-1)}).join(`
5
- `)}\n\nBriefly tell the user what you're about to do and ask them to approve or decline it in the chat. Don't claim it's done.`}const P=e=>e.variant===`approval`&&e.approval?.status===`pending`;function F(e){let t=null,n=null,r=()=>e.track(t??n),i=[],a=/* @__PURE__ */ new Map,o=/* @__PURE__ */ new Map,s=(e,t)=>{a.set(e,[...a.get(e)??[],t]),a.size>32&&a.delete(a.keys().next().value)},c=t=>e.messages().find(e=>e.id===t),l=e=>e.approvals.filter(e=>c(e)?.approval?.status===`pending`),u=e=>j(c(e)?.approval?.toolName??``),d=(t,n,r)=>{t.outcome={status:n,text:r};for(let n of l(t))e.decide(n)},f=t=>t.flatMap(t=>e.messages().find(e=>e.id===t)??[]).filter(e=>!e.variant&&e.content.trim()).map(e=>e.content.trim()).join(`
6
-
7
- `),p=async t=>{do await k();while(e.busy()&&!(t&&(e.parked()||t.failed)))};return{getHistory:()=>{let t=e.messages().filter(e=>(e.role===`user`||e.role===`assistant`)&&!e.variant&&!e.voiceProcessing&&!e.streaming&&e.content),n=!1,r=[];for(let e=t.length-1;e>=0;--e){let i=t[e];if(i.role===`user`)n=!1;else if(!i.voiceCaption)n=!0;else if(n)continue;r.unshift({role:i.role,content:i.content})}return r},dropDelegation(t,n){o.set(t,!!n),o.size>32&&o.delete(o.keys().next().value);let r=n&&i.find(e=>e.delegationId===t);r&&!r.outcome&&d(r,`timeout`,``);for(let n of a.get(t)??[])n.stopStream?.(),n.dropped=!0,n.ids.length&&e.unspoken(n.ids)},async runDelegatedTurn({delegationId:m,userText:h,userUtteranceIds:g,stream:_}){let v=i.filter(e=>l(e).length),y=v.flatMap(l);if(y.length===1&&O(h)){let e=u(y[0]);return d(v[0],`denied`,``),{status:`denied`,text:`Okay, I cancelled the ${e} request. Nothing was done.`,inChat:!1}}let b=e.parked()&&e.messages().every(e=>!P(e)||y.includes(e.id));e.busy()&&!b&&await p();let x=new Set(e.messages().map(e=>e.id)),S=()=>e.messages().filter(e=>!x.has(e.id)&&e.variant===`approval`),C={ids:[],failed:!1},E=_?new w:null,D=``,A=``,M=!1,F=e=>{for(let t of e)A+=t,_(t)},I;E&&(C.onUpdate=()=>{if(M)return;let e=f(C.ids);if(!e.startsWith(D)){M=!0,clearTimeout(I);return}if(e.length===D.length)return;let t=e.slice(D.length);D=e,F(E.push(t)),clearTimeout(I),I=setTimeout(()=>{M||F(E.idle())},200)},C.stopStream=()=>{clearTimeout(I),M=!0,C.onUpdate=void 0}),s(m,C),t=C,r();try{await e.send(h,e.claim(h,g)??void 0),await p(C)}catch{C.failed=!0}finally{t=null,r()}E&&(C.onUpdate?.(),clearTimeout(I),M||F(E.flush()),C.onUpdate=void 0,C.stopStream=void 0);let L=e=>{let t=T(A,e);return t>0?{streamedChars:t}:{}},R=C.ids.flatMap(t=>e.messages().find(e=>e.id===t)??[]),z=f(C.ids),B=C.failed?[]:S().filter(P),V=new Set(B.map(e=>j(e.approval.toolName)));for(let t of v){if(t.outcome)continue;for(let n of l(t).filter(e=>V.has(u(e))))t.replaced.add(n),e.decide(n);let n=u(t.approvals[0]);(t.approvals.every(e=>t.replaced.has(e))||b&&t.approvals.every(e=>c(e)?.approval?.status===`denied`))&&(t.outcome={status:`cancelled`,text:`The earlier ${n} request was replaced by the new one; it was not done.`})}if(B.length){let c=new Set(B.map(e=>e.id)),p={delegationId:m,approvals:[...c],at:Date.now(),replaced:/* @__PURE__ */ new Set};return i.push(p),o.get(m)&&d(p,`timeout`,``),z=`${z}\n\n${N(B.map(e=>e.approval))}`.trim(),{status:`pending_approval`,text:z,...L(z),followUp:async({signal:o,approvalTimeoutMs:h=3e5,readBack:g=!0,onUpdate:_})=>{let v={ids:[],failed:!1};a.delete(m),s(m,v);let y=()=>S().filter(e=>c.has(e.id)||v.ids.includes(e.id)),b=()=>S().filter(e=>P(e)&&!c.has(e.id)&&!i.some(t=>t.approvals.includes(e.id))&&(v.ids.includes(e.id)||e.approval?.toolType===`webmcp`&&n===v)),x=()=>{n=v,r()};g&&x();try{for(;!o.aborted&&!p.outcome&&(t||e.busy()||y().some(P));){g&&n!==v&&!l(p).length&&x();let t=b();if(t.length){for(let e of t)c.add(e.id),p.approvals.push(e.id);p.at=Date.now(),_?.(N(t.map(e=>e.approval)))}if(Date.now()-p.at>=h&&l(p).length&&(!e.busy()||e.parked())){d(p,`timeout`,`That ${u(p.approvals[0])} request expired, so nothing was done.`);break}await k()}}finally{n===v&&(n=null,r()),i.splice(i.indexOf(p),1)}if(o.aborted)return null;if(p.outcome)return p.outcome;if(v.failed)return{status:`failed`,text:`The ${u(p.approvals[0])} request didn't complete.`};let C=y(),w=e=>C.filter(e).map(e=>j(e.approval.toolName)).join(`, `),T=C.some(e=>e.approval?.status===`approved`),E=w(e=>e.approval?.status===`denied`&&!p.replaced.has(e.id)),D=w(e=>p.replaced.has(e.id)),O=w(e=>e.approval?.status===`timeout`),A=[T?f(v.ids):``,E&&`The user declined the ${E} request in the chat, so ${T?`that part was not done`:`nothing was done`}.`,D&&`The earlier ${D} request was replaced by the new one; it was not done.`,O&&`The ${O} request timed out, so it was not done.`].filter(Boolean).join(`
8
-
9
- `);return{status:T?`completed`:E?`denied`:D?`cancelled`:O?`timeout`:`completed`,text:A}}}}return!C.failed&&e.parked()||R.some(e=>e.agentMetadata?.awaitingLocalTool&&!e.agentMetadata.askUserQuestionAnswered&&!e.toolCall?.name?.startsWith(`webmcp:`))?(z=`${z}\n\nI need your answer in the chat before I can continue.`.trim(),{status:C.failed?`failed`:`completed`,text:z,...L(z)}):{status:!C.failed&&z?`completed`:`failed`,text:z,...L(z)}}}}const I=(e,t,n,r)=>{let i=e.session;try{e.keyed=n?new n({find:e=>i.messages.find(t=>t.id===e),inject:e=>i.injectMessage(e),upsert:e=>i.upsertMessage(e),settle:e=>{i.messages=i.messages.map(t=>e.has(t.id)?{...t,streaming:!1,voiceProcessing:!1}:t),i.callbacks.onMessagesChanged([...i.messages])},setStreaming:t=>{(t||!e.busy())&&e.setStreaming(t)},markSpoken:e=>{i.ttsSpokenMessageIds.add(e)}}):null,r&&t.setSessionBridge&&t.setSessionBridge(r({messages:()=>i.messages,busy:()=>e.busy(),parked:()=>i.webMcpApprovalResolvers.size>0,claim:(t,n)=>e.keyed?.claimUserTurn(t,n)??null,send:(e,t)=>i.sendMessage(e,{viaVoice:!0,voiceTurn:{userMessageId:t}}),track:t=>{e.setDelegation(t)},unspoken:e=>{for(let t of e)i.ttsSpokenMessageIds.delete(t);i.streaming||i.speakLatestAssistantMessage(e)},decide:e=>{if(i.webMcpApprovalResolvers.has(e))return i.resolveWebMcpApproval(e,`denied`);let t=i.messages.find(t=>t.id===e)?.approval;t?.status===`pending`&&i.resolveApproval(t,`denied`)}}));let a=e.generation(),o=()=>e.provider()===t&&a===e.generation(),s=(i.config.voiceRecognition??{}).processingErrorText??`Voice processing failed. Please try again.`;t.onResult(e=>{o()&&e.provider!==`runtype`&&e.text&&e.text.trim()&&i.sendMessage(e.text,{viaVoice:!0})}),t.onTranscript&&t.onTranscript((t,n,r,a)=>{if(o()){if(a?.turnId)e.keyed?.apply(t,n,r,a.turnId,a.startMs,a.caption);else if(t===`user`)e.pendingUser?i.upsertMessage({id:e.pendingUser,role:`user`,content:n,createdAt:(/* @__PURE__ */ new Date()).toISOString(),streaming:!1,voiceProcessing:!r}):e.pendingUser=i.injectMessage({role:`user`,content:n,streaming:!1,voiceProcessing:!r}).id,r&&(e.turns().start(a?.turnId),e.pendingUser=null,e.pendingAssistant=i.injectMessage({role:`assistant`,content:``,streaming:!0,voiceProcessing:!0}).id,e.setStreaming(!0));else{if(!e.turns().accepts(a?.turnId))return;e.pendingAssistant?i.upsertMessage({id:e.pendingAssistant,role:`assistant`,content:n,createdAt:(/* @__PURE__ */ new Date()).toISOString(),streaming:!r,voiceProcessing:!r}):e.pendingAssistant=i.injectMessage({role:`assistant`,content:n,streaming:!r,voiceProcessing:!r}).id,r&&(e.pendingAssistant&&i.ttsSpokenMessageIds.add(e.pendingAssistant),e.setStreaming(!1),e.pendingAssistant=null)}}}),t.onLevel&&t.onLevel(t=>{o()&&e.setLevel(Number.isFinite(t)?Math.max(0,Math.min(1,t)):0)}),t.onMetrics&&t.onMetrics(e=>{o()&&i.config.voiceRecognition?.onMetrics?.(e)}),t.onError(t=>{o()&&(console.error(`Voice error:`,t),e.pendingAssistant&&=(i.upsertMessage({id:e.pendingAssistant,role:`assistant`,content:s,createdAt:(/* @__PURE__ */ new Date()).toISOString(),streaming:!1,voiceProcessing:!1}),e.setStreaming(!1),e.pendingUser=null,null),e.keyed?.fail(s))}),t.onStatusChange(t=>{o()&&(e.setStatus(t),(t===`listening`||t===`idle`||t===`disconnected`)&&e.settlePending(t!==`listening`),(t===`idle`||t===`disconnected`)&&e.keyed?.settle(),i.callbacks.onVoiceStatusChanged?.(t))}),t.connect()}catch(e){console.error(`Failed to setup voice:`,e)}},L=e=>{let t=t=>{if(e.original)return;let n=e.config().voiceRecognition??{};e.original={backgroundColor:t.style.backgroundColor,color:t.style.color,borderColor:t.style.borderColor,iconName:n.iconName??`mic`,iconSize:parseFloat(n.iconSize??``)||e.iconFallbackPx}},n=(t,n,r)=>{let i=t.querySelector(`svg`);i&&i.remove();let a=e.original?.iconSize??(parseFloat(e.config().voiceRecognition?.iconSize??``)||e.iconFallbackPx),o=e.renderIcon(n,a,r,1.5);o&&t.appendChild(o)},r=e=>{e.classList.remove(`persona-voice-recording`,`persona-voice-processing`,`persona-voice-speaking`)};return{recording:()=>{let n=e.mic();if(!n)return;t(n);let i=e.config().voiceRecognition??{},a=i.recordingBackgroundColor,o=i.recordingIconColor,s=i.recordingBorderColor;if(r(n),n.classList.add(`persona-voice-recording`),e.setMicState(`recording`),n.style.backgroundColor=a??`var(--persona-voice-recording-bg, #ef4444)`,n.style.color=o??`var(--persona-voice-recording-indicator, #ffffff)`,o){let e=n.querySelector(`svg`);e&&e.setAttribute(`stroke`,o)}s&&(n.style.borderColor=s),n.setAttribute(`aria-label`,`Stop voice recognition`)},processing:()=>{let i=e.mic();if(!i)return;t(i);let a=e.config().voiceRecognition??{},o=e.session().getVoiceInterruptionMode(),s=e.original,c=a.processingIconName??`loader`,l=a.processingIconColor??s?.color??``,u=a.processingBackgroundColor??s?.backgroundColor??``,d=a.processingBorderColor??s?.borderColor??``;r(i),i.classList.add(`persona-voice-processing`),e.setMicState(`processing`),i.style.backgroundColor=u,i.style.borderColor=d;let f=l||`currentColor`;i.style.color=f,n(i,c,f),i.setAttribute(`aria-label`,`Processing voice input`),o===`none`&&(i.style.cursor=`default`)},speaking:()=>{let i=e.mic();if(!i)return;t(i);let a=e.config().voiceRecognition??{},o=e.session().getVoiceInterruptionMode(),s=e.original,c=o===`cancel`?`square`:o===`barge-in`?`mic`:`volume-2`,l=a.speakingIconName??c,u=a.speakingIconColor??(o===`barge-in`?a.recordingIconColor??s?.color??``:s?.color??``),d=a.speakingBackgroundColor??(o===`barge-in`?a.recordingBackgroundColor??`var(--persona-voice-recording-bg, #ef4444)`:s?.backgroundColor??``),f=a.speakingBorderColor??(o===`barge-in`?a.recordingBorderColor??``:s?.borderColor??``);r(i),i.classList.add(`persona-voice-speaking`),e.setMicState(`speaking`),i.style.backgroundColor=d,i.style.borderColor=f;let p=u||`currentColor`;i.style.color=p,n(i,l,p);let m=o===`cancel`?`Stop playback and re-record`:o===`barge-in`?`Speak to interrupt`:`Agent is speaking`;i.setAttribute(`aria-label`,m),o===`none`&&(i.style.cursor=`default`),o===`barge-in`&&i.classList.add(`persona-voice-recording`)},reset:()=>{let t=e.mic();if(!t)return;r(t),e.setMicState(`idle`);let i=e.original;i&&(t.style.backgroundColor=i.backgroundColor??``,t.style.color=i.color??``,t.style.borderColor=i.borderColor??``,n(t,i.iconName,i.color||`currentColor`),e.original=null),t.style.cursor=``,t.setAttribute(`aria-label`,`Start voice recognition`)}}};export{f as BrowserVoiceProvider,_ as KeyedVoiceTranscript,d as RuntypeVoiceProvider,m as createBestAvailableVoiceProvider,L as createMicStateStyles,p as createVoiceProvider,F as createVoiceSessionBridge,h as isVoiceSupported,I as wireSessionVoice};
@@ -1 +0,0 @@
1
- const e=e=>e.startsWith(`webmcp:`)?e.slice(7):e,t={warn(e,...t){typeof console<`u`&&typeof console.warn==`function`&&console.warn(`[Persona/WebMCP] ${e}`,...t)}};var n=class{#e;#t;#n=!1;#r=null;#i=!1;#a;constructor(e,t){this.config=e,this.#a=t,this.#e=e.onConfirm??null,this.#t=3e4}setConfirmHandler(e){this.#e=e}isOperational(){return this.config.enabled!==!0||!this.#n?!1:this.#c()!==null}async snapshotForDispatch(){if(await this.#o(),this.config.enabled!==!0)return[];let e=this.#c();if(!e)return[];let n;try{n=(await e.getTools()).filter(f)}catch(e){return t.warn(`getTools() threw: shipping an empty WebMCP snapshot.`,e),[]}this.#a.recordToolDisplayTitles(n);let r=typeof location<`u`?location.origin:``;return n.filter(e=>this.#u(e.name)).map(e=>{let t=a(e.annotations);return{name:e.name,description:e.description,parametersSchema:o(e.inputSchema),origin:`webmcp`,...r?{pageOrigin:r}:{},...t?{annotations:t}:{},...t?.untrustedContentHint===!0?{untrustedContentHint:!0}:{}}})}async executeToolCall(t,n,r){if(await this.#o(),this.config.enabled!==!0)return l(`WebMCP is not enabled on this widget.`);let i=this.#c();if(!i){let e=typeof document<`u`&&!!document.modelContext;return l(e?`WebMCP is not operational: document.modelContext is present but does not expose the strict getTools()/executeTool() surface (likely a different or older WebMCP polyfill).`:`WebMCP bridge is not operational on this page (document.modelContext not available).`)}let a=e(t),o;try{o=(await i.getTools()).filter(f)}catch(e){let t=e instanceof Error?e.message:String(e);return l(`Failed to read WebMCP registry: ${t}`)}this.#a.recordToolDisplayTitles(o);let u=o.find(e=>e.name===a);if(!u)return l(`WebMCP tool not registered on this page: ${a}`);if(!this.#u(a))return l(`WebMCP tool not allowed by client allowlist: ${a}`);if(r?.aborted)return l(`Aborted by cancel()`);let d=this.#a.getToolDisplayTitle(a),p={toolName:a,args:n,description:u.description,...d?{title:d}:{},reason:`gate`};if(!await this.#l(p))return l(`User declined the tool call.`);if(r?.aborted)return l(`Aborted by cancel()`);let m=new AbortController,h=!1,g=setTimeout(()=>{h=!0,m.abort()},this.#t),_=()=>m.abort();r&&(r.aborted?m.abort():r.addEventListener(`abort`,_,{once:!0}));try{let e=await i.executeTool(u,c(n),{signal:m.signal});return s(e)}catch(e){if(h)return l(`WebMCP tool '${a}' timed out after ${this.#t}ms`);if(r?.aborted)return l(`Aborted by cancel()`);let t=e instanceof Error?e.message:String(e);return l(t)}finally{clearTimeout(g),r&&r.removeEventListener(`abort`,_)}}#o(){return this.config.enabled===!0?(this.#r||=this.#s(),this.#r):Promise.resolve()}async#s(){try{if(this.#c()){this.#n=!0;return}let e=await this.#a.loadPolyfill();`initializeWebMCPPolyfill`in e?e.initializeWebMCPPolyfill():e.installWebMCP(),this.#n=!0}catch(e){t.warn(`Failed to load @mcp-b/webmcp-polyfill: WebMCP consumption disabled.`,e),this.#n=!1}}#c(){if(typeof document>`u`)return null;let e=document.modelContext;if(!e||typeof e!=`object`)return null;let n=e;return typeof n.getTools!=`function`||typeof n.executeTool!=`function`?(this.#i||(this.#i=!0,t.warn(`document.modelContext is present but does not expose getTools()/executeTool(): WebMCP consumption is disabled. Another (incompatible or older) WebMCP polyfill likely installed document.modelContext before Persona. Remove it, or use a polyfill implementing the strict standard surface (e.g. @mcp-b/webmcp-polyfill).`)),null):e}async#l(e){let n=this.#e??u;try{return await n(e)}catch(n){return t.warn(`Confirm handler threw for WebMCP tool '${e.toolName}'; declining.`,n),!1}}#u(e){let t=this.config.allowlist;return!t||t.length===0||t.some(t=>r(e,t))}};const r=(e,t)=>{if(t===`*`)return!0;let n=t.replace(/[.+?^${}()|[\]\\]/g,`\\$&`);return RegExp(`^`+n.replace(/\*/g,`.*`)+`$`).test(e)},i={type:`object`,properties:{}},a=e=>{if(!e||typeof e!=`object`)return;let t={...typeof e.readOnlyHint==`boolean`?{readOnlyHint:e.readOnlyHint}:{},...typeof e.untrustedContentHint==`boolean`?{untrustedContentHint:e.untrustedContentHint}:{}};return Object.keys(t).length>0?t:void 0},o=e=>{if(e===void 0||e===``)return{...i};if(typeof e==`object`)return e!==null&&!Array.isArray(e)?e:{...i};try{let t=JSON.parse(e);return typeof t==`object`&&t&&!Array.isArray(t)?t:{...i}}catch{return{...i}}},s=e=>{if(e==null)return{content:[{type:`text`,text:``}]};let t;try{t=JSON.parse(e)}catch{return{content:[{type:`text`,text:e}]}}return typeof t==`object`&&t&&Array.isArray(t.content)?t:{content:[{type:`text`,text:typeof t==`string`?t:p(t)}]}},c=e=>{let t;try{t=JSON.stringify(e)??`{}`}catch{t=`{}`}let n=JSON.parse(t);if(typeof n!=`object`||!n)throw TypeError(`WebMCP tool arguments must be an object or array.`);return Object.defineProperty(n,Symbol.toPrimitive,{value:()=>t}),n},l=e=>({isError:!0,content:[{type:`text`,text:e}]}),u=async e=>{if(typeof window>`u`||typeof window.confirm!=`function`)return!1;let t=d(e.args),n=`Allow the AI to call ${e.toolName}`+(t?`\n\nArguments:\n${t}`:``)+(e.description?`\n\n${e.description}`:``);return window.confirm(n)},d=e=>{if(e==null)return``;try{let t=JSON.stringify(e,null,2);return t.length>500?t.slice(0,500)+`…`:t}catch{return String(e)}},f=e=>(e.window===void 0||typeof window<`u`&&e.window===window)&&(e.origin===void 0||typeof location<`u`&&e.origin===location.origin),p=e=>{if(e===void 0)return``;try{return JSON.stringify(e)}catch{return String(e)}};export{n as WebMcpBridge};
@@ -1 +0,0 @@
1
- const e=`4.28.2`;export{e as t};