@alexkroman1/aai-ui 13.3.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 (73) hide show
  1. package/README.md +159 -69
  2. package/dist/{_colors-CZ6OlPbL.js → _colors-CpZO-88A.js} +24 -1
  3. package/dist/_recover-run.d.ts +2 -2
  4. package/dist/_submission-state.d.ts +92 -0
  5. package/dist/_upload-files.d.ts +2 -2
  6. package/dist/_upload-report.d.ts +26 -0
  7. package/dist/{_utils-CyzjK0gW.js → _utils-DnQDM9Uy.js} +3 -3
  8. package/dist/_utils.d.ts +3 -3
  9. package/dist/_web-storage.d.ts +43 -0
  10. package/dist/_workflow-files.d.ts +1 -1
  11. package/dist/agent-state-labels.d.ts +60 -0
  12. package/dist/audio.js +20 -14
  13. package/dist/{chat-view-Bv5VFJIE.js → chat-view-C_3T7Ln8.js} +96 -29
  14. package/dist/{client-config-DD820zHn.js → client-config-DJQHnYjm.js} +4 -4
  15. package/dist/client-config.d.ts +4 -4
  16. package/dist/client-dir.d.ts +1 -1
  17. package/dist/client-dir.js +1 -1
  18. package/dist/components/_colors.d.ts +23 -0
  19. package/dist/components/_form-readiness.d.ts +1 -1
  20. package/dist/components/bullet-list.d.ts +74 -0
  21. package/dist/components/button.js +3 -3
  22. package/dist/components/chat-view.js +1 -1
  23. package/dist/components/console-shell.d.ts +16 -20
  24. package/dist/components/controls.js +3 -3
  25. package/dist/components/facts.d.ts +81 -0
  26. package/dist/components/form-fields.d.ts +6 -6
  27. package/dist/components/form-types.d.ts +1 -1
  28. package/dist/components/form.d.ts +1 -1
  29. package/dist/components/message-list.js +1 -1
  30. package/dist/components/session-error-banner.d.ts +69 -0
  31. package/dist/components/start-screen.js +1 -1
  32. package/dist/components/tool-call-block.js +1 -1
  33. package/dist/components/tool-config-context.d.ts +1 -1
  34. package/dist/components/workflow-progress.d.ts +11 -4
  35. package/dist/context.d.ts +142 -19
  36. package/dist/context.js +155 -17
  37. package/dist/default-client/assets/{audio-BuDICbPf.js → audio-9zQsNc1w.js} +1 -1
  38. package/dist/default-client/assets/index-BTv30Z4F.css +2 -0
  39. package/dist/default-client/assets/index-RAZ-29Sz.js +284 -0
  40. package/dist/default-client/index.html +2 -2
  41. package/dist/define-client.d.ts +19 -19
  42. package/dist/define-client.js +19 -19
  43. package/dist/hooks.d.ts +44 -8
  44. package/dist/hooks.js +19 -13
  45. package/dist/index.d.ts +11 -7
  46. package/dist/index.js +418 -185
  47. package/dist/internal.d.ts +2 -2
  48. package/dist/internal.js +5 -5
  49. package/dist/{message-list-C0pL7x41.js → message-list-CdOnSh5m.js} +19 -12
  50. package/dist/page.d.ts +11 -11
  51. package/dist/session-core-audio-setup.d.ts +1 -1
  52. package/dist/session-core-dial.d.ts +0 -2
  53. package/dist/{session-core-C9elBIdu.js → session-core-gwePM95B.js} +125 -52
  54. package/dist/session-core-messages.d.ts +2 -2
  55. package/dist/session-core-types.d.ts +58 -1
  56. package/dist/session-core.d.ts +6 -6
  57. package/dist/session-core.js +2 -2
  58. package/dist/session-resume-store.d.ts +3 -3
  59. package/dist/{tool-call-block-Bunc6rCw.js → tool-call-block-C2t_5fpp.js} +27 -9
  60. package/dist/{tool-config-context-Bh8p3DtG.js → tool-config-context-Es4YUzV2.js} +1 -1
  61. package/dist/types.d.ts +19 -4
  62. package/dist/types.js +2 -2
  63. package/dist/{url-chips-C2u7QPv8.js → url-chips-BxhzZgk2.js} +4 -4
  64. package/dist/use-conversation.d.ts +1 -1
  65. package/dist/{use-user-transcript-DFTSEuZN.js → use-user-transcript-uyHhzy4d.js} +3 -2
  66. package/dist/use-workflow-form.d.ts +34 -2
  67. package/dist/{use-workflow-run-CXGEcM0l.js → use-workflow-run-CP2ekKPV.js} +3 -6
  68. package/dist/use-workflow-stream.d.ts +1 -1
  69. package/dist/workflow-client.d.ts +1 -1
  70. package/package.json +2 -2
  71. package/styles.css +78 -0
  72. package/dist/default-client/assets/index-B1_ROnTJ.js +0 -284
  73. package/dist/default-client/assets/index-S5fkKi6B.css +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-DD820zHn.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-C2u7QPv8.js";
4
- import { t as TRANSCRIBING_PLACEHOLDER } from "./use-user-transcript-DFTSEuZN.js";
5
- import { t as ToolConfigContext } from "./tool-config-context-Bh8p3DtG.js";
6
- import { i as DEFAULT_PROGRESS_POLL_MS, n as MAX_MISSING_READS, t as DEFAULT_WORKFLOW_POLL_MS } from "./use-workflow-run-CXGEcM0l.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,7 +1,7 @@
1
1
  import { useSessionSelector, useTheme } from "./context.js";
2
- import { i as primaryTint, r as inkTint } from "./_colors-CZ6OlPbL.js";
3
- import { n as useUserTranscript } from "./use-user-transcript-DFTSEuZN.js";
4
- import { t as ToolCallBlock } from "./tool-call-block-Bunc6rCw.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";
@@ -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,16 +179,23 @@ 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
201
  //#region src/components/markdown.tsx
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-DD820zHn.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,6 +6,44 @@ 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 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
9
47
  //#region src/session-core-audio-setup.ts
10
48
  /**
11
49
  * Audio-path initialization for the voice session core.
@@ -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 {
@@ -232,6 +270,55 @@ function applyResumeParams(wsUrl, resume, sessionId) {
232
270
  return wsUrl;
233
271
  }
234
272
  //#endregion
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
235
322
  //#region src/session-resume-store.ts
236
323
  /**
237
324
  * Where a session id survives a page RELOAD.
@@ -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,9 +372,7 @@ 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
378
  //#region src/session-core-dial.ts
@@ -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);
@@ -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;
@@ -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,
@@ -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
  */
@@ -149,7 +149,7 @@ export type SessionSnapshot = {
149
149
  *
150
150
  * @public
151
151
  */
152
- export type SessionCore = {
152
+ export type BrowserSession = {
153
153
  /** Return the current immutable state snapshot. */
154
154
  getSnapshot(): SessionSnapshot;
155
155
  /** Subscribe to state changes. Returns an unsubscribe function. */
@@ -195,6 +195,30 @@ export type SessionCore = {
195
195
  * per-session tool state, greeting included.
196
196
  */
197
197
  end(): void;
198
+ /**
199
+ * End the current call and immediately begin a new one — `end()` then
200
+ * `start()`, which is what "New Conversation" means for an agent that keeps
201
+ * SESSION-SCOPED STATE.
202
+ *
203
+ * `reset()` is the one whose name suggests this and it is not the same
204
+ * thing: it clears the transcript and reconnects, but the reconnect carries
205
+ * the same `?sessionId=`, so every `sessionSlot` on the server survives —
206
+ * the game world, the incident board, the cart. A caller who asked to start
207
+ * over gets a blank transcript in front of the old state. This drops the
208
+ * session id, so the next connect mints a fresh one and the greeting plays
209
+ * again.
210
+ *
211
+ * Three templates had each written `session.end(); session.start();` with
212
+ * the same paragraph explaining why `reset()` was wrong; the six on the
213
+ * stock shell could not, because {@link Controls} called `reset()` for them.
214
+ *
215
+ * @example
216
+ * ```ts
217
+ * declare const session: import("@alexkroman1/aai-ui").Session;
218
+ * session.restart();
219
+ * ```
220
+ */
221
+ restart(): void;
198
222
  /** Alias for `disconnect` for use with `using`. */
199
223
  [Symbol.dispose](): void;
200
224
  };
@@ -247,3 +271,36 @@ export type ConnState = {
247
271
  * buffered during mic-permission never finishes playing. */
248
272
  preInitDone: boolean;
249
273
  };
274
+ /**
275
+ * The two liveness fields at rest.
276
+ *
277
+ * `running` and `recording` ride with almost every state transition and were
278
+ * spread as a pair of literals at seven sites across three modules — the same
279
+ * shape `session-core-state.ts` folded `state` and `error` out of, one field
280
+ * short. Naming it relates them: a transition that ends the call says so once,
281
+ * and a reader looking for "what stops the mic" finds one symbol rather than a
282
+ * grep.
283
+ *
284
+ * It is deliberately NOT the whole snapshot patch — a transition still supplies
285
+ * its own `agentState.apply(...)` projection beside this.
286
+ */
287
+ export declare const STOPPED: {
288
+ readonly running: false;
289
+ readonly recording: false;
290
+ };
291
+ /**
292
+ * A turn boundary: end the current turn and settle whatever it was playing.
293
+ *
294
+ * The two calls are one fact and were written out at four sites — `cancel()`
295
+ * and `reset()` here, `reply.cancelled` and `session.reset` on the server side
296
+ * — where the pair is load-bearing in both halves. The bump stops a stale drain
297
+ * continuation from stamping `"listening"` over a state the session has since
298
+ * moved to; the flush settles the interrupted turn's `done()` so it cannot
299
+ * strand.
300
+ *
301
+ * Two further sites bump WITHOUT flushing (`cleanupAudio`, a committed user
302
+ * transcript) and stay spelled out, which is the point of naming this one: a
303
+ * bump on its own now reads as a deliberate choice rather than a forgotten
304
+ * flush.
305
+ */
306
+ export declare function bargeIn(conn: ConnState): void;
@@ -1,4 +1,4 @@
1
- import type { SessionCore } from "./session-core-types.ts";
1
+ import { type BrowserSession } from "./session-core-types.ts";
2
2
  import { type VoiceSessionOptions } from "./types.ts";
3
3
  /**
4
4
  * Create a framework-agnostic voice session core that connects to an AAI
@@ -7,24 +7,24 @@ import { type VoiceSessionOptions } from "./types.ts";
7
7
  * Uses a subscribe/getSnapshot pattern for state management, compatible with
8
8
  * React's `useSyncExternalStore` and other external store integrations.
9
9
  *
10
- * Most clients never call this: `client()` creates a core and installs it in
10
+ * Most clients never call this: `mountClient()` creates a core and installs it in
11
11
  * React context for the hooks. Reach for it directly when building a
12
12
  * non-React UI (or wiring the session into another framework's store).
13
13
  *
14
14
  * @example
15
15
  * ```ts
16
- * import { createSessionCore, type SessionSnapshot } from "@alexkroman1/aai-ui";
16
+ * import { createBrowserSession, type SessionSnapshot } from "@alexkroman1/aai-ui";
17
17
  *
18
18
  * declare function render(snapshot: SessionSnapshot): void;
19
19
  *
20
- * const session = createSessionCore({ platformUrl: "https://host/my-agent/" });
20
+ * const session = createBrowserSession({ platformUrl: "https://host/my-agent/" });
21
21
  * session.subscribe(() => render(session.getSnapshot()));
22
22
  * session.start();
23
23
  * ```
24
24
  *
25
25
  * @param options - Session configuration including the platform server URL.
26
- * @returns A {@link SessionCore} handle for controlling the session.
26
+ * @returns A {@link BrowserSession} handle for controlling the session.
27
27
  *
28
28
  * @public
29
29
  */
30
- export declare function createSessionCore(options: VoiceSessionOptions): SessionCore;
30
+ export declare function createBrowserSession(options: VoiceSessionOptions): BrowserSession;
@@ -1,2 +1,2 @@
1
- import { t as createSessionCore } from "./session-core-C9elBIdu.js";
2
- export { createSessionCore };
1
+ import { t as createBrowserSession } from "./session-core-gwePM95B.js";
2
+ export { createBrowserSession };
@@ -23,9 +23,9 @@
23
23
  * Keyed by the agent's own URL, so two agents served from one origin — which is
24
24
  * every deployed agent, at `/:slug/` — cannot inherit each other's session.
25
25
  *
26
- * Every access is guarded: storage throws outright in some contexts (Safari
27
- * private mode, storage blocked by policy), and a session that cannot be
28
- * remembered must degrade to today's behaviour rather than failing to start.
26
+ * Every access is guarded, and the guard lives in `_web-storage.ts`: a session
27
+ * that cannot be remembered must degrade to today's behaviour rather than
28
+ * failing to start.
29
29
  */
30
30
  /** The stored session id for this agent, or undefined. @internal */
31
31
  export declare function readStoredSessionId(platformUrl: string): string | undefined;