@alexkroman1/aai-ui 13.2.0 → 14.0.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 (83) hide show
  1. package/README.md +159 -69
  2. package/dist/{_colors-j8XMToi9.js → _colors-CpZO-88A.js} +25 -2
  3. package/dist/{_module-url-C_4gRVL0.js → _module-url-C13kAJ87.js} +1 -1
  4. package/dist/_recover-run.d.ts +13 -7
  5. package/dist/_submission-state.d.ts +92 -0
  6. package/dist/_upload-files.d.ts +2 -2
  7. package/dist/_upload-report.d.ts +26 -0
  8. package/dist/{_utils-B6498_bm.js → _utils-DnQDM9Uy.js} +4 -4
  9. package/dist/_utils.d.ts +3 -3
  10. package/dist/_web-storage.d.ts +43 -0
  11. package/dist/_workflow-files.d.ts +1 -1
  12. package/dist/{aai-logo-CXZGPSIY.js → aai-logo-CFlomZlS.js} +1 -1
  13. package/dist/agent-state-labels.d.ts +60 -0
  14. package/dist/audio.js +21 -15
  15. package/dist/{chat-view-DDTtrh7N.js → chat-view-C_3T7Ln8.js} +100 -33
  16. package/dist/{client-config-BT_kWID5.js → client-config-DJQHnYjm.js} +5 -5
  17. package/dist/client-config.d.ts +4 -4
  18. package/dist/client-dir.d.ts +1 -1
  19. package/dist/client-dir.js +2 -2
  20. package/dist/components/_colors.d.ts +23 -0
  21. package/dist/components/_form-readiness.d.ts +1 -1
  22. package/dist/components/bullet-list.d.ts +74 -0
  23. package/dist/components/button.js +4 -4
  24. package/dist/components/chat-view.js +1 -1
  25. package/dist/components/console-shell.d.ts +16 -20
  26. package/dist/components/controls.js +4 -4
  27. package/dist/components/facts.d.ts +81 -0
  28. package/dist/components/form-fields.d.ts +6 -6
  29. package/dist/components/form-types.d.ts +1 -1
  30. package/dist/components/form.d.ts +1 -1
  31. package/dist/components/message-list.js +1 -1
  32. package/dist/components/session-error-banner.d.ts +69 -0
  33. package/dist/components/sidebar-layout.js +1 -1
  34. package/dist/components/start-screen.js +4 -4
  35. package/dist/components/tool-call-block.js +1 -1
  36. package/dist/components/tool-config-context.d.ts +1 -1
  37. package/dist/components/workflow-progress.d.ts +11 -4
  38. package/dist/context.d.ts +142 -19
  39. package/dist/context.js +156 -18
  40. package/dist/default-client/assets/{audio-BuDICbPf.js → audio-9zQsNc1w.js} +1 -1
  41. package/dist/default-client/assets/index-BTv30Z4F.css +2 -0
  42. package/dist/default-client/assets/index-RAZ-29Sz.js +284 -0
  43. package/dist/default-client/index.html +2 -2
  44. package/dist/default-client.d.ts +1 -1
  45. package/dist/define-client.d.ts +19 -19
  46. package/dist/define-client.js +20 -20
  47. package/dist/{eyebrow-C6ZFuiz6.js → eyebrow-UfmSz9yy.js} +1 -1
  48. package/dist/hooks.d.ts +44 -8
  49. package/dist/hooks.js +20 -14
  50. package/dist/index.d.ts +13 -8
  51. package/dist/index.js +1447 -1160
  52. package/dist/internal.d.ts +2 -2
  53. package/dist/internal.js +5 -5
  54. package/dist/{message-list-BJYyuIcR.js → message-list-CdOnSh5m.js} +23 -16
  55. package/dist/page.d.ts +11 -11
  56. package/dist/session-core-audio-setup.d.ts +1 -1
  57. package/dist/session-core-dial.d.ts +0 -2
  58. package/dist/{session-core-DxBYsfHA.js → session-core-gwePM95B.js} +135 -62
  59. package/dist/session-core-messages.d.ts +2 -2
  60. package/dist/session-core-types.d.ts +58 -1
  61. package/dist/session-core.d.ts +6 -6
  62. package/dist/session-core.js +2 -2
  63. package/dist/session-resume-store.d.ts +3 -3
  64. package/dist/{tool-call-block-tcPQAkcP.js → tool-call-block-C2t_5fpp.js} +30 -12
  65. package/dist/{tool-config-context-DzAofqi_.js → tool-config-context-Es4YUzV2.js} +2 -2
  66. package/dist/types.d.ts +19 -4
  67. package/dist/types.js +3 -3
  68. package/dist/{url-chips-YqhCjWfQ.js → url-chips-BxhzZgk2.js} +5 -5
  69. package/dist/use-conversation.d.ts +1 -1
  70. package/dist/use-run-key.d.ts +44 -10
  71. package/dist/{use-user-transcript-C14qWFu2.js → use-user-transcript-uyHhzy4d.js} +4 -3
  72. package/dist/use-workflow-form.d.ts +64 -91
  73. package/dist/{use-workflow-progress-Cu0SxMyg.js → use-workflow-run-CP2ekKPV.js} +254 -257
  74. package/dist/use-workflow-stream.d.ts +5 -2
  75. package/dist/use-workflows.d.ts +77 -0
  76. package/dist/workflow-client.d.ts +1 -1
  77. package/dist/worklets/capture-processor.js +2 -2
  78. package/dist/worklets/playback-processor.js +2 -2
  79. package/package.json +6 -6
  80. package/styles.css +78 -0
  81. package/dist/default-client/assets/index-B1_ROnTJ.js +0 -284
  82. package/dist/default-client/assets/index-S5fkKi6B.css +0 -2
  83. package/dist/tsdown.config.d.ts +0 -2
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `@alexkroman1/aai-ui/internal` — the plumbing `client()` and the default
2
+ * `@alexkroman1/aai-ui/internal` — the plumbing `mountClient()` and the default
3
3
  * client install for themselves, NOT part of the public client API and not
4
4
  * covered by semver. A `client.tsx` should never import from here; everything
5
5
  * an author writes a page or a custom chrome against lives on the root export
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * These names used to ride on the root barrel, tagged `@internal` and nothing
9
9
  * else, which meant they were in a client author's autocomplete beside
10
- * `client()`, `<Form>`, `useAgentState` and `useWorkflowRun` — eight symbols an
10
+ * `mountClient()`, `<Form>`, `useAgentState` and `useWorkflowRun` — eight symbols an
11
11
  * author is invited to reach for and no capability contract covers. Keeping
12
12
  * them on their own subpath keeps the root importable surface the same shape as
13
13
  * the promise: what is on it is contracted.
package/dist/internal.js CHANGED
@@ -1,8 +1,8 @@
1
- import { r as loadClientConfig, t as buildAgentUrl } from "./client-config-BT_kWID5.js";
1
+ import { r as loadClientConfig, t as buildAgentUrl } from "./client-config-DJQHnYjm.js";
2
2
  import { SessionProvider, ThemeProvider } from "./context.js";
3
- import { n as SessionUrlChips, r as UiUrlChip, t as ApiUrlChip } from "./url-chips-YqhCjWfQ.js";
4
- import { t as TRANSCRIBING_PLACEHOLDER } from "./use-user-transcript-C14qWFu2.js";
5
- import { t as ToolConfigContext } from "./tool-config-context-DzAofqi_.js";
6
- import { i as MAX_MISSING_READS, r as DEFAULT_WORKFLOW_POLL_MS, t as DEFAULT_PROGRESS_POLL_MS } from "./use-workflow-progress-Cu0SxMyg.js";
3
+ import { n as SessionUrlChips, r as UiUrlChip, t as ApiUrlChip } from "./url-chips-BxhzZgk2.js";
4
+ import { t as TRANSCRIBING_PLACEHOLDER } from "./use-user-transcript-uyHhzy4d.js";
5
+ import { t as ToolConfigContext } from "./tool-config-context-Es4YUzV2.js";
6
+ import { i as DEFAULT_PROGRESS_POLL_MS, n as MAX_MISSING_READS, t as DEFAULT_WORKFLOW_POLL_MS } from "./use-workflow-run-CP2ekKPV.js";
7
7
  import { VOICE_CAPTURE_CONSTRAINTS } from "./types.js";
8
8
  export { ApiUrlChip, DEFAULT_PROGRESS_POLL_MS, DEFAULT_WORKFLOW_POLL_MS, MAX_MISSING_READS, SessionProvider, SessionUrlChips, TRANSCRIBING_PLACEHOLDER, ThemeProvider, ToolConfigContext, UiUrlChip, VOICE_CAPTURE_CONSTRAINTS, buildAgentUrl, loadClientConfig };
@@ -1,14 +1,14 @@
1
1
  import { useSessionSelector, useTheme } from "./context.js";
2
- import { i as primaryTint, r as inkTint } from "./_colors-j8XMToi9.js";
3
- import { n as useUserTranscript } from "./use-user-transcript-C14qWFu2.js";
4
- import { t as ToolCallBlock } from "./tool-call-block-tcPQAkcP.js";
2
+ import { a as inkTint, o as primaryTint } from "./_colors-CpZO-88A.js";
3
+ import { n as useUserTranscript } from "./use-user-transcript-uyHhzy4d.js";
4
+ import { t as ToolCallBlock } from "./tool-call-block-C2t_5fpp.js";
5
5
  import clsx from "clsx";
6
6
  import { StickToBottom } from "use-stick-to-bottom";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  import { memo, useMemo } from "react";
9
9
  import ReactMarkdown from "react-markdown";
10
10
  import remarkGfm from "remark-gfm";
11
- //#region components/auto-scroll.tsx
11
+ //#region src/components/auto-scroll.tsx
12
12
  /** @jsxImportSource react */
13
13
  /**
14
14
  * A scroll container that stays pinned to the bottom as its content grows,
@@ -66,7 +66,7 @@ function AutoScroll({ children, className, contentClassName, scrollClassName = "
66
66
  });
67
67
  }
68
68
  //#endregion
69
- //#region use-conversation.ts
69
+ //#region src/use-conversation.ts
70
70
  /**
71
71
  * `useConversation` — the exchange, already assembled, with nothing rendered.
72
72
  *
@@ -142,7 +142,7 @@ function isThinking(state, messages, toolCalls) {
142
142
  * Subscribe to the conversation: the interleaved exchange, the streaming
143
143
  * utterance, the live transcript and the thinking rule — with no markup.
144
144
  *
145
- * Must be used inside the provider `client()` installs.
145
+ * Must be used inside the provider `mountClient()` installs.
146
146
  *
147
147
  * @example A custom bubble, keeping every rule `<MessageList>` knows
148
148
  * ```tsx
@@ -179,19 +179,26 @@ function useConversation() {
179
179
  const toolCalls = useSessionSelector((s) => s.toolCalls);
180
180
  const streaming = useSessionSelector((s) => s.agentTranscript);
181
181
  const transcript = useUserTranscript();
182
- return {
183
- items: useMemo(() => interleave(messages, toolCalls), [messages, toolCalls]),
182
+ const items = useMemo(() => interleave(messages, toolCalls), [messages, toolCalls]);
183
+ const thinking = useMemo(() => isThinking(state, messages, toolCalls), [
184
+ state,
185
+ messages,
186
+ toolCalls
187
+ ]);
188
+ return useMemo(() => ({
189
+ items,
184
190
  streaming,
185
191
  transcript,
186
- thinking: useMemo(() => isThinking(state, messages, toolCalls), [
187
- state,
188
- messages,
189
- toolCalls
190
- ])
191
- };
192
+ thinking
193
+ }), [
194
+ items,
195
+ streaming,
196
+ transcript,
197
+ thinking
198
+ ]);
192
199
  }
193
200
  //#endregion
194
- //#region components/markdown.tsx
201
+ //#region src/components/markdown.tsx
195
202
  /** @jsxImportSource react */
196
203
  const BARE_ORDERED_MARKER = /^(\s*)(\d{1,9})([.)])\s*$/;
197
204
  const BARE_BULLET_MARKER = /^(\s*)([-*+])\s*$/;
@@ -377,7 +384,7 @@ const Markdown = memo(function Markdown({ text, variant = "default" }) {
377
384
  });
378
385
  });
379
386
  //#endregion
380
- //#region components/message-list.tsx
387
+ //#region src/components/message-list.tsx
381
388
  /** @jsxImportSource react */
382
389
  const DOT_STYLES = [
383
390
  0,
package/dist/page.d.ts CHANGED
@@ -1,15 +1,15 @@
1
1
  /** @jsxImportSource react */
2
2
  /**
3
- * `page()` — mount a WORKFLOW APP's UI: React, theme, no session.
3
+ * `mountPage()` — mount a WORKFLOW APP's UI: React, theme, no session.
4
4
  *
5
- * The twin of `client()` for an agent whose front door is a form rather than a
5
+ * The twin of `mountClient()` for an agent whose front door is a form rather than a
6
6
  * microphone (`workflowApp()`). It is a separate entry rather than
7
- * an option on `client()` because of what `client()` unavoidably does: it
8
- * constructs a `SessionCore`, which owns a WebSocket URL provider, an audio
7
+ * an option on `mountClient()` because of what `mountClient()` unavoidably does: it
8
+ * constructs a `BrowserSession`, which owns a WebSocket URL provider, an audio
9
9
  * graph, and a microphone request. A flag would have to make all of that
10
10
  * conditional, and every session hook would then have to answer "what does this
11
11
  * mean with no session?" — so the honest split is two mounts. A page that wants
12
- * voice uses `client()`; a page that wants neither audio nor a socket uses this.
12
+ * voice uses `mountClient()`; a page that wants neither audio nor a socket uses this.
13
13
  *
14
14
  * Authoring is otherwise identical — the file is still `client.tsx`, still
15
15
  * React, still Tailwind, still the same theme tokens — so a workflow app reads
@@ -19,7 +19,7 @@
19
19
  import { type ComponentType } from "react";
20
20
  import type { ClientTheme } from "./types.ts";
21
21
  /**
22
- * Configuration for {@link page}.
22
+ * Configuration for {@link mountPage}.
23
23
  *
24
24
  * @public
25
25
  */
@@ -33,14 +33,14 @@ export type PageConfig = {
33
33
  target?: string | HTMLElement;
34
34
  /**
35
35
  * Page title. Set only when given, so a title the HTML shell declared is never
36
- * clobbered — the same rule `client()`'s custom-component tier follows.
36
+ * clobbered — the same rule `mountClient()`'s custom-component tier follows.
37
37
  */
38
38
  name?: string;
39
39
  /** Theme color overrides, read by the same tokens the voice components use. */
40
40
  theme?: ClientTheme;
41
41
  };
42
42
  /**
43
- * Handle returned by {@link page}. `Disposable`, so `using` works.
43
+ * Handle returned by {@link mountPage}. `Disposable`, so `using` works.
44
44
  *
45
45
  * @public
46
46
  */
@@ -59,7 +59,7 @@ export type PageHandle = {
59
59
  *
60
60
  * @example
61
61
  * ```tsx
62
- * import { createWorkflowApi, page, useWorkflowRun } from "@alexkroman1/aai-ui";
62
+ * import { createWorkflowApi, mountPage, useWorkflowRun } from "@alexkroman1/aai-ui";
63
63
  * import { useState } from "react";
64
64
  *
65
65
  * // Hoisted: a client built in render is a new object every render.
@@ -78,11 +78,11 @@ export type PageHandle = {
78
78
  * );
79
79
  * }
80
80
  *
81
- * page({ name: "Digest", component: App });
81
+ * mountPage({ name: "Digest", component: App });
82
82
  * ```
83
83
  *
84
84
  * @throws If the target element is not found in the DOM.
85
85
  *
86
86
  * @public
87
87
  */
88
- export declare function page(config: PageConfig): PageHandle;
88
+ export declare function mountPage(config: PageConfig): PageHandle;
@@ -1,7 +1,7 @@
1
1
  import type { SessionCommand } from "@alexkroman1/aai/protocol";
2
2
  import type { VoiceIO } from "./audio.ts";
3
3
  import type { SessionStateMachine } from "./session-core-state.ts";
4
- import type { ConnState, SessionSnapshot } from "./session-core-types.ts";
4
+ import { type ConnState, type SessionSnapshot } from "./session-core-types.ts";
5
5
  /** Dependencies `initAudioCapture` needs from the owning session core. */
6
6
  export type AudioSetupDeps = {
7
7
  sendJson: (msg: SessionCommand) => void;
@@ -22,8 +22,6 @@ export type DialOptions = {
22
22
  resumeSessionId?: string | undefined;
23
23
  };
24
24
  export type Dialer = {
25
- /** The URL for the next attempt — partysocket's async provider. */
26
- url(): Promise<string>;
27
25
  /** A socket for this attempt. */
28
26
  open(): InstanceType<WebSocketConstructor>;
29
27
  /**
@@ -1,4 +1,4 @@
1
- import { r as loadClientConfig, t as buildAgentUrl } from "./client-config-BT_kWID5.js";
1
+ import { r as loadClientConfig, t as buildAgentUrl } from "./client-config-DJQHnYjm.js";
2
2
  import { MIC_SEND_MAX_BUFFERED_BYTES } from "./types.js";
3
3
  import { SessionEventSchema, lenientParse } from "@alexkroman1/aai/protocol";
4
4
  import { errorMessage, safeJsonParse } from "@alexkroman1/aai";
@@ -6,7 +6,45 @@ import { omitUndefined } from "@alexkroman1/aai/utils";
6
6
  import { DEFAULT_MAX_HISTORY, WS_OPEN, createEpoch, toArgsRecord } from "@alexkroman1/aai/internal";
7
7
  import ReconnectingWebSocket from "partysocket/ws";
8
8
  import { and, assign, createActor, not, setup, stateIn } from "xstate";
9
- //#region session-core-audio-setup.ts
9
+ //#region src/session-core-types.ts
10
+ /**
11
+ * The two liveness fields at rest.
12
+ *
13
+ * `running` and `recording` ride with almost every state transition and were
14
+ * spread as a pair of literals at seven sites across three modules — the same
15
+ * shape `session-core-state.ts` folded `state` and `error` out of, one field
16
+ * short. Naming it relates them: a transition that ends the call says so once,
17
+ * and a reader looking for "what stops the mic" finds one symbol rather than a
18
+ * grep.
19
+ *
20
+ * It is deliberately NOT the whole snapshot patch — a transition still supplies
21
+ * its own `agentState.apply(...)` projection beside this.
22
+ */
23
+ const STOPPED = {
24
+ running: false,
25
+ recording: false
26
+ };
27
+ /**
28
+ * A turn boundary: end the current turn and settle whatever it was playing.
29
+ *
30
+ * The two calls are one fact and were written out at four sites — `cancel()`
31
+ * and `reset()` here, `reply.cancelled` and `session.reset` on the server side
32
+ * — where the pair is load-bearing in both halves. The bump stops a stale drain
33
+ * continuation from stamping `"listening"` over a state the session has since
34
+ * moved to; the flush settles the interrupted turn's `done()` so it cannot
35
+ * strand.
36
+ *
37
+ * Two further sites bump WITHOUT flushing (`cleanupAudio`, a committed user
38
+ * transcript) and stay spelled out, which is the point of naming this one: a
39
+ * bump on its own now reads as a deliberate choice rather than a forgotten
40
+ * flush.
41
+ */
42
+ function bargeIn(conn) {
43
+ conn.turn.bump();
44
+ conn.voiceIO?.flush();
45
+ }
46
+ //#endregion
47
+ //#region src/session-core-audio-setup.ts
10
48
  /**
11
49
  * Audio-path initialization for the voice session core.
12
50
  *
@@ -60,11 +98,11 @@ async function initAudioCapture(conn, msg, deps) {
60
98
  type: "FAILED",
61
99
  error: {
62
100
  code: "audio",
63
- message
101
+ message,
102
+ fatal: false
64
103
  }
65
104
  }),
66
- running: false,
67
- recording: false
105
+ ...STOPPED
68
106
  });
69
107
  };
70
108
  try {
@@ -119,7 +157,7 @@ async function initAudioCapture(conn, msg, deps) {
119
157
  }
120
158
  }
121
159
  //#endregion
122
- //#region session-core-close.ts
160
+ //#region src/session-core-close.ts
123
161
  /**
124
162
  * What a socket's CLOSE means to the caller.
125
163
  *
@@ -160,7 +198,7 @@ function closeFailure(event, socketErrored) {
160
198
  return socketErrored ? "WebSocket connection error" : null;
161
199
  }
162
200
  //#endregion
163
- //#region session-core-reconnect.ts
201
+ //#region src/session-core-reconnect.ts
164
202
  /**
165
203
  * Automatic reconnection for the browser session socket, built on
166
204
  * partysocket's `ReconnectingWebSocket`. Kept out of `session-core.ts` so
@@ -209,7 +247,7 @@ function reconnectPending(socket) {
209
247
  return socket instanceof ReconnectingWebSocket && socket.shouldReconnect && socket.retryCount < RECONNECT_OPTIONS.maxRetries;
210
248
  }
211
249
  //#endregion
212
- //#region session-core-url.ts
250
+ //#region src/session-core-url.ts
213
251
  /** Build the session WebSocket URL from the platform URL and resume state. */
214
252
  function buildWsUrl(platformUrl, resume, sessionId) {
215
253
  return applyResumeParams(buildAgentUrl(platformUrl, "websocket"), resume, sessionId);
@@ -232,7 +270,56 @@ function applyResumeParams(wsUrl, resume, sessionId) {
232
270
  return wsUrl;
233
271
  }
234
272
  //#endregion
235
- //#region session-resume-store.ts
273
+ //#region src/_web-storage.ts
274
+ /**
275
+ * The store, or nothing.
276
+ *
277
+ * Not exported: a caller that reaches for the store itself has stepped outside
278
+ * the guard, which is the whole point of this module.
279
+ */
280
+ function storeFor(kind) {
281
+ return kind === "local" ? globalThis.localStorage : globalThis.sessionStorage;
282
+ }
283
+ /** The stored value, or undefined — for a missing entry and an absent store alike. */
284
+ function storageGet(kind, key) {
285
+ try {
286
+ return storeFor(kind)?.getItem(key) ?? void 0;
287
+ } catch {
288
+ return;
289
+ }
290
+ }
291
+ /** Remember a value. A store that refuses is a no-op, never a throw. */
292
+ function storageSet(kind, key, value) {
293
+ try {
294
+ storeFor(kind)?.setItem(key, value);
295
+ } catch {}
296
+ }
297
+ /** Forget a value. Nothing stored and no store are the same outcome. */
298
+ function storageRemove(kind, key) {
299
+ try {
300
+ storeFor(kind)?.removeItem(key);
301
+ } catch {}
302
+ }
303
+ /**
304
+ * A storage key namespaced by a URL.
305
+ *
306
+ * `target` is resolved against the document, so a relative path ("./", the
307
+ * default-client case) and the absolute form of the same agent agree on one
308
+ * key.
309
+ *
310
+ * @param prefix - The owning module's namespace, e.g. `"aai:session:"`.
311
+ * @param target - What to resolve — an agent's `platformUrl`, or `"./"` for the
312
+ * page's own directory.
313
+ */
314
+ function urlSlot(prefix, target) {
315
+ try {
316
+ return `${prefix}${new URL(target, globalThis.location?.href).href}`;
317
+ } catch {
318
+ return `${prefix}${target}`;
319
+ }
320
+ }
321
+ //#endregion
322
+ //#region src/session-resume-store.ts
236
323
  /**
237
324
  * Where a session id survives a page RELOAD.
238
325
  *
@@ -258,32 +345,22 @@ function applyResumeParams(wsUrl, resume, sessionId) {
258
345
  * Keyed by the agent's own URL, so two agents served from one origin — which is
259
346
  * every deployed agent, at `/:slug/` — cannot inherit each other's session.
260
347
  *
261
- * Every access is guarded: storage throws outright in some contexts (Safari
262
- * private mode, storage blocked by policy), and a session that cannot be
263
- * remembered must degrade to today's behaviour rather than failing to start.
348
+ * Every access is guarded, and the guard lives in `_web-storage.ts`: a session
349
+ * that cannot be remembered must degrade to today's behaviour rather than
350
+ * failing to start.
264
351
  */
265
352
  const PREFIX = "aai:session:";
266
353
  /** One agent's slot in storage. */
267
354
  function keyFor(platformUrl) {
268
- try {
269
- return `${PREFIX}${new URL(platformUrl, globalThis.location?.href).href}`;
270
- } catch {
271
- return `${PREFIX}${platformUrl}`;
272
- }
355
+ return urlSlot(PREFIX, platformUrl);
273
356
  }
274
357
  /** The stored session id for this agent, or undefined. @internal */
275
358
  function readStoredSessionId(platformUrl) {
276
- try {
277
- return globalThis.sessionStorage?.getItem(keyFor(platformUrl)) ?? void 0;
278
- } catch {
279
- return;
280
- }
359
+ return storageGet("session", keyFor(platformUrl));
281
360
  }
282
361
  /** Remember this agent's session id for the next load. @internal */
283
362
  function writeStoredSessionId(platformUrl, sessionId) {
284
- try {
285
- globalThis.sessionStorage?.setItem(keyFor(platformUrl), sessionId);
286
- } catch {}
363
+ storageSet("session", keyFor(platformUrl), sessionId);
287
364
  }
288
365
  /**
289
366
  * Forget it, so the next load is a NEW session.
@@ -295,12 +372,10 @@ function writeStoredSessionId(platformUrl, sessionId) {
295
372
  * @internal
296
373
  */
297
374
  function clearStoredSessionId(platformUrl) {
298
- try {
299
- globalThis.sessionStorage?.removeItem(keyFor(platformUrl));
300
- } catch {}
375
+ storageRemove("session", keyFor(platformUrl));
301
376
  }
302
377
  //#endregion
303
- //#region session-core-dial.ts
378
+ //#region src/session-core-dial.ts
304
379
  /**
305
380
  * How the next connection attempt is DIALLED, and the resume identity it dials
306
381
  * with.
@@ -363,7 +438,6 @@ function createDialer(options) {
363
438
  return (cfg?.sessionUrl ? buildBrokeredWsUrl(cfg.sessionUrl, hasConnected, sessionId) : buildWsUrl(options.platformUrl, hasConnected, sessionId)).toString();
364
439
  }
365
440
  return {
366
- url,
367
441
  open: () => {
368
442
  if (options.WebSocket) return new options.WebSocket(buildWsUrl(options.platformUrl, hasConnected, sessionId).toString());
369
443
  return openReconnectingSocket(url);
@@ -383,7 +457,7 @@ function createDialer(options) {
383
457
  };
384
458
  }
385
459
  //#endregion
386
- //#region session-core-handshake.ts
460
+ //#region src/session-core-handshake.ts
387
461
  /**
388
462
  * The deadline on a socket that opened but never became a session.
389
463
  *
@@ -407,7 +481,8 @@ function createDialer(options) {
407
481
  /** What the session reports once the budget below is spent. */
408
482
  const HANDSHAKE_ERROR = {
409
483
  code: "connection",
410
- message: "Agent did not complete the session handshake"
484
+ message: "Agent did not complete the session handshake",
485
+ fatal: false
411
486
  };
412
487
  /** How long an OPEN socket may go without a `config` frame. */
413
488
  const HANDSHAKE_TIMEOUT_MS = 1e4;
@@ -463,7 +538,7 @@ function createHandshakeGuard(opts) {
463
538
  };
464
539
  }
465
540
  //#endregion
466
- //#region session-core-messages.ts
541
+ //#region src/session-core-messages.ts
467
542
  /**
468
543
  * Incoming-message handling for the voice session core.
469
544
  *
@@ -507,7 +582,7 @@ function appendCapped(list, item, cap) {
507
582
  *
508
583
  * Encapsulates the per-session dedup counters (`customEventSeq`,
509
584
  * `messageSeq`, `toolCallSeq`) that previously lived as closure locals in
510
- * `createSessionCore`. The turn-boundary epoch is NOT one of them: it lives
585
+ * `createBrowserSession`. The turn-boundary epoch is NOT one of them: it lives
511
586
  * on `conn` because the session core bumps it on teardown too (see
512
587
  * `ConnState.turn`).
513
588
  */
@@ -611,7 +686,8 @@ function createMessageHandlers(deps) {
611
686
  console.error("Agent error:", e.message);
612
687
  const error = {
613
688
  code: e.code,
614
- message: e.message
689
+ message: e.message,
690
+ fatal: e.fatal !== false
615
691
  };
616
692
  if (e.fatal === false) updateState(agentState.apply({
617
693
  type: "TURN_ERROR",
@@ -625,8 +701,7 @@ function createMessageHandlers(deps) {
625
701
  type: "FATAL",
626
702
  error
627
703
  }),
628
- running: false,
629
- recording: false
704
+ ...STOPPED
630
705
  });
631
706
  }
632
707
  }
@@ -677,14 +752,12 @@ function createMessageHandlers(deps) {
677
752
  toListening();
678
753
  break;
679
754
  case "reply.cancelled":
680
- conn.turn.bump();
681
- conn.voiceIO?.flush();
755
+ bargeIn(conn);
682
756
  commitAgentTranscript();
683
757
  toListening({ userTranscript: null });
684
758
  break;
685
759
  case "session.reset": {
686
- conn.turn.bump();
687
- conn.voiceIO?.flush();
760
+ bargeIn(conn);
688
761
  const next = agentState.apply({ type: "RESET" });
689
762
  updateState(agentState.fatal() ? next : {
690
763
  ...CLEARED_SESSION_STATE,
@@ -798,7 +871,7 @@ function createMessageHandlers(deps) {
798
871
  };
799
872
  }
800
873
  //#endregion
801
- //#region session-core-state.ts
874
+ //#region src/session-core-state.ts
802
875
  /**
803
876
  * The browser session's {@link AgentState}, and the error beside it, as a
804
877
  * statechart.
@@ -980,7 +1053,7 @@ function createSessionStateMachine() {
980
1053
  };
981
1054
  }
982
1055
  //#endregion
983
- //#region session-core.ts
1056
+ //#region src/session-core.ts
984
1057
  /**
985
1058
  * Framework-agnostic voice session core.
986
1059
  *
@@ -1000,27 +1073,27 @@ function createSessionStateMachine() {
1000
1073
  * Uses a subscribe/getSnapshot pattern for state management, compatible with
1001
1074
  * React's `useSyncExternalStore` and other external store integrations.
1002
1075
  *
1003
- * Most clients never call this: `client()` creates a core and installs it in
1076
+ * Most clients never call this: `mountClient()` creates a core and installs it in
1004
1077
  * React context for the hooks. Reach for it directly when building a
1005
1078
  * non-React UI (or wiring the session into another framework's store).
1006
1079
  *
1007
1080
  * @example
1008
1081
  * ```ts
1009
- * import { createSessionCore, type SessionSnapshot } from "@alexkroman1/aai-ui";
1082
+ * import { createBrowserSession, type SessionSnapshot } from "@alexkroman1/aai-ui";
1010
1083
  *
1011
1084
  * declare function render(snapshot: SessionSnapshot): void;
1012
1085
  *
1013
- * const session = createSessionCore({ platformUrl: "https://host/my-agent/" });
1086
+ * const session = createBrowserSession({ platformUrl: "https://host/my-agent/" });
1014
1087
  * session.subscribe(() => render(session.getSnapshot()));
1015
1088
  * session.start();
1016
1089
  * ```
1017
1090
  *
1018
1091
  * @param options - Session configuration including the platform server URL.
1019
- * @returns A {@link SessionCore} handle for controlling the session.
1092
+ * @returns A {@link BrowserSession} handle for controlling the session.
1020
1093
  *
1021
1094
  * @public
1022
1095
  */
1023
- function createSessionCore(options) {
1096
+ function createBrowserSession(options) {
1024
1097
  let currentSnapshot = {
1025
1098
  ...CLEARED_SESSION_STATE,
1026
1099
  state: "disconnected",
@@ -1192,8 +1265,7 @@ function createSessionCore(options) {
1192
1265
  type: "FAILED",
1193
1266
  error: HANDSHAKE_ERROR
1194
1267
  }),
1195
- running: false,
1196
- recording: false
1268
+ ...STOPPED
1197
1269
  });
1198
1270
  }
1199
1271
  });
@@ -1232,24 +1304,22 @@ function createSessionCore(options) {
1232
1304
  type: "FAILED",
1233
1305
  error: {
1234
1306
  code: "connection",
1235
- message: failure
1307
+ message: failure,
1308
+ fatal: false
1236
1309
  }
1237
1310
  }),
1238
- running: false,
1239
- recording: false
1311
+ ...STOPPED
1240
1312
  });
1241
1313
  }, { signal: sig });
1242
1314
  }
1243
1315
  function cancel() {
1244
1316
  if (!openSocket()) return;
1245
- conn.turn.bump();
1246
- conn.voiceIO?.flush();
1317
+ bargeIn(conn);
1247
1318
  updateState(agentState.apply({ type: "LISTEN" }));
1248
1319
  sendJson({ type: "cancel" });
1249
1320
  }
1250
1321
  function reset() {
1251
- conn.turn.bump();
1252
- conn.voiceIO?.flush();
1322
+ bargeIn(conn);
1253
1323
  if (openSocket()) {
1254
1324
  sendJson({ type: "reset" });
1255
1325
  return;
@@ -1261,8 +1331,7 @@ function createSessionCore(options) {
1261
1331
  teardownConnection();
1262
1332
  updateState({
1263
1333
  ...agentState.apply({ type: "DISCONNECT" }),
1264
- running: false,
1265
- recording: false
1334
+ ...STOPPED
1266
1335
  });
1267
1336
  }
1268
1337
  function start() {
@@ -1286,10 +1355,13 @@ function createSessionCore(options) {
1286
1355
  ...CLEARED_SESSION_STATE,
1287
1356
  ...agentState.apply({ type: "END" }),
1288
1357
  started: false,
1289
- running: false,
1290
- recording: false
1358
+ ...STOPPED
1291
1359
  });
1292
1360
  }
1361
+ function restart() {
1362
+ end();
1363
+ start();
1364
+ }
1293
1365
  return {
1294
1366
  getSnapshot,
1295
1367
  subscribe,
@@ -1301,10 +1373,11 @@ function createSessionCore(options) {
1301
1373
  start,
1302
1374
  toggle,
1303
1375
  end,
1376
+ restart,
1304
1377
  [Symbol.dispose]() {
1305
1378
  disconnect();
1306
1379
  }
1307
1380
  };
1308
1381
  }
1309
1382
  //#endregion
1310
- export { createSessionCore as t };
1383
+ export { urlSlot as i, storageGet as n, storageSet as r, createBrowserSession as t };
@@ -1,5 +1,5 @@
1
1
  import type { SessionStateMachine } from "./session-core-state.ts";
2
- import type { ConnState, SessionSnapshot } from "./session-core-types.ts";
2
+ import { type ConnState, type SessionSnapshot } from "./session-core-types.ts";
3
3
  /**
4
4
  * Snapshot fields cleared when a session's conversation state is wiped —
5
5
  * shared by the initial snapshot, `resetState()`, and `session.reset`.
@@ -56,7 +56,7 @@ type MessageHandlers = {
56
56
  *
57
57
  * Encapsulates the per-session dedup counters (`customEventSeq`,
58
58
  * `messageSeq`, `toolCallSeq`) that previously lived as closure locals in
59
- * `createSessionCore`. The turn-boundary epoch is NOT one of them: it lives
59
+ * `createBrowserSession`. The turn-boundary epoch is NOT one of them: it lives
60
60
  * on `conn` because the session core bumps it on teardown too (see
61
61
  * `ConnState.turn`).
62
62
  */