@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,10 +1,10 @@
1
1
  import { useSessionCore, useSessionSelector } from "../context.js";
2
2
  import { Button } from "./button.js";
3
- import { n as SessionUrlChips } from "../url-chips-YqhCjWfQ.js";
3
+ import { n as SessionUrlChips } from "../url-chips-BxhzZgk2.js";
4
4
  import clsx from "clsx";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
6
  import { memo } from "react";
7
- //#region components/controls.tsx
7
+ //#region src/components/controls.tsx
8
8
  /** @jsxImportSource react */
9
9
  /**
10
10
  * Session control buttons: **Stop / Resume** and **New Conversation**.
@@ -27,7 +27,7 @@ import { memo } from "react";
27
27
  */
28
28
  const Controls = memo(function Controls({ className }) {
29
29
  const running = useSessionSelector((s) => s.running);
30
- const { toggle, reset } = useSessionCore();
30
+ const { toggle, restart } = useSessionCore();
31
31
  return /* @__PURE__ */ jsxs("div", {
32
32
  className: clsx("flex flex-wrap items-center gap-3 shrink-0", className),
33
33
  children: [
@@ -38,7 +38,7 @@ const Controls = memo(function Controls({ className }) {
38
38
  }),
39
39
  /* @__PURE__ */ jsx(Button, {
40
40
  variant: "ghost",
41
- onClick: reset,
41
+ onClick: restart,
42
42
  children: "New Conversation"
43
43
  }),
44
44
  /* @__PURE__ */ jsx(SessionUrlChips, { className: "basis-full sm:basis-auto sm:ml-auto sm:max-w-[60%]" })
@@ -0,0 +1,81 @@
1
+ import type { ReactNode } from "react";
2
+ /**
3
+ * Props for {@link Facts}.
4
+ *
5
+ * @public
6
+ */
7
+ export type FactsProps = {
8
+ /**
9
+ * The facts, in reading order. Anything `false`, `null`, `undefined` or the
10
+ * empty string is DROPPED, so a page writes `cond && \`${n} skipped\`` inline
11
+ * instead of splicing a separator into a conditional string.
12
+ *
13
+ * `0` is NOT dropped — it is a fact ("0 words"), and treating it as absent is
14
+ * the bug a plain truthiness filter would ship.
15
+ *
16
+ * They are TEXT rather than `ReactNode`: every one of the nine lines this
17
+ * replaced was a string, and taking strings is what lets this JOIN them (see
18
+ * the component doc) instead of interleaving keyed separator elements.
19
+ */
20
+ items: readonly (string | number | false | null | undefined)[];
21
+ /**
22
+ * Which of the two muted typographies the pages use. `"sm"` is
23
+ * `text-sm opacity-70`, `"xs"` is `text-xs opacity-60` — the size and the
24
+ * muting move together, because that is the pair every site had.
25
+ */
26
+ size?: "sm" | "xs" | undefined;
27
+ /**
28
+ * The element to render. `"p"` by default; `"span"` for a line that sits
29
+ * inside phrasing content, where a `<p>` is invalid nesting the browser will
30
+ * reparent out from under React.
31
+ */
32
+ as?: "p" | "span" | undefined;
33
+ /**
34
+ * ADDED to the base classes rather than replacing them — `tabular-nums`,
35
+ * `uppercase tracking-[1.2px]`. There is no `tailwind-merge` in this package,
36
+ * so a class that CONFLICTS with a base one is not reliably the winner.
37
+ */
38
+ className?: string | undefined;
39
+ };
40
+ /**
41
+ * A muted line of run facts, joined by `·` — "6 segments · 12:04 of audio ·
42
+ * 1,840 words".
43
+ *
44
+ * Nine pages had written this by hand under four different typographies for
45
+ * one role, two of them (`call-audit` and `spoken-summary`) byte-identical down
46
+ * to the payload. Three things it takes off the caller:
47
+ *
48
+ * - **The separator cannot be forgotten, and neither can the space around it.**
49
+ * Four of the nine carried a literal `{" "}` at the end of a line, because
50
+ * Prettier's wrap ate the space that made `· ` read as a separator rather
51
+ * than as punctuation glued to the next word. A line that is correct only
52
+ * because somebody remembered an invisible JSX expression is exactly the
53
+ * thing a component should own.
54
+ * - **A fact worth omitting is omitted, and by the caller's own condition.**
55
+ * The hand-written shape for a conditional fact was to splice the separator
56
+ * into the string — `{x ? \` · budget exhausted\` : ""}` — which puts the
57
+ * punctuation in two places and gets the leading separator wrong the moment
58
+ * the fact before it also disappears. Passing the condition and letting this
59
+ * drop it keeps the separator in one place.
60
+ * - **A line with nothing left to say renders NOTHING.** With every fact
61
+ * conditional, the alternative is a muted empty row, or a bare `·`.
62
+ *
63
+ * The facts are JOINED into one string rather than interleaved as elements, and
64
+ * that is why the prop is text: joined, there is no per-fact `key` to invent —
65
+ * the same reasoning `WorkflowProgress` gives for its log lines. A line that
66
+ * genuinely needs an element in it (a link) wants its own markup.
67
+ *
68
+ * @example
69
+ * ```tsx
70
+ * import { Facts } from "@alexkroman1/aai-ui";
71
+ *
72
+ * function RunFacts({ words, cut }: { words: number; cut: number }) {
73
+ * return <Facts size="xs" items={[`${words} words`, cut > 0 && `${cut} blind cuts`]} />;
74
+ * }
75
+ * ```
76
+ *
77
+ * @param props - Facts-line props.
78
+ *
79
+ * @public
80
+ */
81
+ export declare function Facts({ items, size, as, className }: FactsProps): ReactNode;
@@ -1,5 +1,5 @@
1
1
  import type { InputHTMLAttributes, ReactNode, SelectHTMLAttributes, TextareaHTMLAttributes } from "react";
2
- import type { FieldShell, FileRead } from "./form-types.ts";
2
+ import type { FieldShell, FileReadMode } from "./form-types.ts";
3
3
  /**
4
4
  * Label + control + hint, in the layout every field here uses.
5
5
  *
@@ -47,7 +47,7 @@ export declare function Field({ label, hint, htmlFor, className, children, }: {
47
47
  *
48
48
  * @public
49
49
  */
50
- export declare function TextField({ name, label, hint, className, ...rest }: FieldShell & Omit<InputHTMLAttributes<HTMLInputElement>, "name" | "className">): import("react").JSX.Element;
50
+ export declare function TextField(props: FieldShell & Omit<InputHTMLAttributes<HTMLInputElement>, "name" | "className">): import("react").JSX.Element;
51
51
  /**
52
52
  * A number input. Contributes a NUMBER to {@link FormValues}, or nothing when
53
53
  * left empty.
@@ -60,7 +60,7 @@ export declare function TextField({ name, label, hint, className, ...rest }: Fie
60
60
  *
61
61
  * @public
62
62
  */
63
- export declare function NumberField({ name, label, hint, className, ...rest }: FieldShell & Omit<InputHTMLAttributes<HTMLInputElement>, "name" | "className" | "type">): import("react").JSX.Element;
63
+ export declare function NumberField(props: FieldShell & Omit<InputHTMLAttributes<HTMLInputElement>, "name" | "className" | "type">): import("react").JSX.Element;
64
64
  /**
65
65
  * A multi-line text input.
66
66
  *
@@ -116,14 +116,14 @@ export declare function CheckboxField({ name, label, hint, className, ...rest }:
116
116
  * travel in it. With `upload` the field contributes the `File` itself,
117
117
  * `useWorkflowSubmit` stores it through `POST /workflows/uploads` before
118
118
  * starting the run, and the input carries the upload id — which a step reads
119
- * windows of with `readUpload`. Declaring the property in the workflow's
119
+ * windows of with `stepReadUpload`. Declaring the property in the workflow's
120
120
  * `uploads` list makes `<WorkflowFields>` render exactly this, so a declared
121
121
  * form needs no file markup at all.
122
122
  *
123
123
  * **Without it the field describes the file and does not read it.** `read`
124
124
  * exists for the cases where the bytes really are small and really are the
125
125
  * input — a CSV of ids, a config — and the size is the author's to check. See
126
- * {@link FileRead} for the four values; `upload` is shorthand for
126
+ * {@link FileReadMode} for the four values; `upload` is shorthand for
127
127
  * `read="upload"`.
128
128
  *
129
129
  * Otherwise accepts every `<input>` attribute except `name`, `className` and
@@ -136,7 +136,7 @@ export declare function CheckboxField({ name, label, hint, className, ...rest }:
136
136
  * @public
137
137
  */
138
138
  export declare function FileField({ name, label, hint, className, read, upload, ...rest }: FieldShell & {
139
- read?: FileRead;
139
+ read?: FileReadMode;
140
140
  /** Shorthand for `read="upload"` — see above. */
141
141
  upload?: boolean;
142
142
  } & Omit<InputHTMLAttributes<HTMLInputElement>, "name" | "className" | "type">): import("react").JSX.Element;
@@ -46,7 +46,7 @@ export type FileValue = {
46
46
  *
47
47
  * @public
48
48
  */
49
- export type FileRead = "none" | "text" | "dataUrl" | "upload";
49
+ export type FileReadMode = "none" | "text" | "dataUrl" | "upload";
50
50
  /**
51
51
  * The props every field in `form.tsx` shares.
52
52
  *
@@ -2,7 +2,7 @@ import type { ButtonHTMLAttributes, FormHTMLAttributes, ReactNode } from "react"
2
2
  import { type ButtonSize, type ButtonVariant } from "./button.tsx";
3
3
  import type { FormValues } from "./form-types.ts";
4
4
  export { CheckboxField, Field, FileField, NumberField, SelectField, TextAreaField, TextField, } from "./form-fields.tsx";
5
- export type { FieldShell, FileRead, FileValue, FormValues } from "./form-types.ts";
5
+ export type { FieldShell, FileReadMode, FileValue, FormValues } from "./form-types.ts";
6
6
  /** Props of {@link Form}. */
7
7
  export type FormProps = {
8
8
  /**
@@ -1,3 +1,3 @@
1
- import { t as MessageList } from "../message-list-BJYyuIcR.js";
1
+ import { t as MessageList } from "../message-list-CdOnSh5m.js";
2
2
  import "../context.js";
3
3
  export { MessageList };
@@ -0,0 +1,69 @@
1
+ import type { ReactNode } from "react";
2
+ /**
3
+ * Props of {@link SessionErrorBanner}.
4
+ *
5
+ * Every field is optional, so `<SessionErrorBanner />` is the whole call. It is
6
+ * a NAMED type all the same, for the reason `ControlsProps` and
7
+ * `ConsoleShellProps` are: an inline object literal in the signature leaves a
8
+ * `createElement(SessionErrorBanner, { className })` caller unable to infer the
9
+ * props at all, and gives the reference page nothing to link to.
10
+ *
11
+ * @public
12
+ */
13
+ export type SessionErrorBannerProps = {
14
+ /** Additional CSS class names for the banner, appended to its own. */
15
+ className?: string | undefined;
16
+ };
17
+ /**
18
+ * The announced banner for a failed session: the error's message and code, or
19
+ * nothing at all when the session is fine.
20
+ *
21
+ * **This used to be four lines inside `ConsoleShell`, and that is why it is its
22
+ * own component.** The banner was the reason `ConsoleShell` was published —
23
+ * `role="alert"` is the one part of that component a reviewer cannot see is
24
+ * missing, since per the `fatalError` latch in `session-core.ts` the banner is
25
+ * the ONLY remaining signal a session died (the state eyebrow beside it goes
26
+ * back to reading like a live session), and a screen reader is never told an
27
+ * unannounced one appeared. But `ConsoleShell` is a whole FRAME: a centred
28
+ * `max-w-190` column with its own header and footer. Every full-bleed chrome —
29
+ * a two-pane board, a CRT — therefore could not adopt it, rebuilt the banner
30
+ * instead, and the three that did had ALREADY drifted: one rendered
31
+ * `ERROR: {message}` and dropped the code entirely, one `ERROR: {message}
32
+ * ({code})`, one `{message} ({code})`. Splitting the banner out is what lets a
33
+ * chrome take the announced-error decision without taking the layout, and
34
+ * `ConsoleShell` composes this rather than keeping a second copy, so the two
35
+ * cannot drift again.
36
+ *
37
+ * **It reads the session itself.** There is no `error` prop: a banner that
38
+ * takes its text from the caller is a banner a caller can forget to wire, which
39
+ * is exactly the failure above with an extra step. It subscribes narrowly via
40
+ * {@link useSessionError}, so a page that renders it does not re-render with
41
+ * the transcript.
42
+ *
43
+ * **The code is shown, always.** `SessionError.code` is the eight-member wire
44
+ * union — it is what a user pastes into a bug report and the only part of the
45
+ * error that is stable across wordings — and the chrome that dropped it left
46
+ * its readers with a sentence and no way to say which failure it was.
47
+ *
48
+ * Must be rendered inside the providers `client()` installs.
49
+ *
50
+ * @example A full-bleed chrome that wants the banner and not the frame
51
+ * ```tsx
52
+ * import { SessionErrorBanner } from "@alexkroman1/aai-ui";
53
+ *
54
+ * function Board() {
55
+ * return (
56
+ * <div className="grid grid-cols-[1fr_320px] h-screen">
57
+ * <main>…</main>
58
+ * <aside>…</aside>
59
+ * <SessionErrorBanner className="col-span-2" />
60
+ * </div>
61
+ * );
62
+ * }
63
+ * ```
64
+ *
65
+ * @param props - See {@link SessionErrorBannerProps}.
66
+ *
67
+ * @public
68
+ */
69
+ export declare function SessionErrorBanner({ className }: SessionErrorBannerProps): ReactNode;
@@ -1,7 +1,7 @@
1
1
  import { useTheme } from "../context.js";
2
2
  import clsx from "clsx";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
- //#region components/sidebar-layout.tsx
4
+ //#region src/components/sidebar-layout.tsx
5
5
  /** @jsxImportSource react */
6
6
  /**
7
7
  * A two-column layout with a fixed-width sidebar and a flexible main area.
@@ -1,11 +1,11 @@
1
1
  import { useSessionCore, useSessionSelector, useTheme } from "../context.js";
2
- import { r as inkTint } from "../_colors-j8XMToi9.js";
2
+ import { a as inkTint } from "../_colors-CpZO-88A.js";
3
3
  import { Button } from "./button.js";
4
- import { t as AaiLogo } from "../aai-logo-CXZGPSIY.js";
5
- import { t as Eyebrow } from "../eyebrow-C6ZFuiz6.js";
4
+ import { t as AaiLogo } from "../aai-logo-CFlomZlS.js";
5
+ import { t as Eyebrow } from "../eyebrow-UfmSz9yy.js";
6
6
  import clsx from "clsx";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
- //#region components/start-screen.tsx
8
+ //#region src/components/start-screen.tsx
9
9
  /** @jsxImportSource react */
10
10
  /**
11
11
  * A centered start screen: a white card on the cream page with the logo, an
@@ -1,3 +1,3 @@
1
1
  import "../context.js";
2
- import { t as ToolCallBlock } from "../tool-call-block-tcPQAkcP.js";
2
+ import { t as ToolCallBlock } from "../tool-call-block-C2t_5fpp.js";
3
3
  export { ToolCallBlock };
@@ -8,7 +8,7 @@ export type ToolDisplayConfig = Record<string, {
8
8
  label?: string;
9
9
  }>;
10
10
  /**
11
- * Context for tool display configuration. Installed by `client()` from
11
+ * Context for tool display configuration. Installed by `mountClient()` from
12
12
  * `ClientConfig.tools`; the built-in components read it via `useToolConfig`.
13
13
  *
14
14
  * @internal
@@ -1,17 +1,17 @@
1
- import type { ReactNode } from "react";
1
+ import { type ReactNode } from "react";
2
2
  import type { WorkflowApi } from "../workflow-client.ts";
3
3
  /**
4
4
  * What a run has said so far, rendered.
5
5
  *
6
6
  * The complement of a status line, and the reason both exist: a run is
7
7
  * `running` for its whole life, so a one-round job and a ten-round one look
8
- * identical while they happen. These lines come from the run itself (`report()`
8
+ * identical while they happen. These lines come from the run itself (`stepReport()`
9
9
  * in a `"use step"` body), which is the only channel a workflow has before it
10
10
  * produces an output.
11
11
  *
12
- * Three rules are baked in, and they are why this is a component rather than
12
+ * Four rules are baked in, and they are why this is a component rather than
13
13
  * three lines each page writes for itself — the two templates that had written
14
- * it had written all three, comments included:
14
+ * it had written three of them, comments included:
15
15
  *
16
16
  * - **It renders nothing until there is something to render.** `supported` is
17
17
  * what keeps this from being an empty box forever on an agent deployed before
@@ -25,6 +25,13 @@ import type { WorkflowApi } from "../workflow-client.ts";
25
25
  * or opening a finished run tomorrow — shows how it got there rather than an
26
26
  * empty box. That is `useWorkflowProgress`'s doing; this is what makes it
27
27
  * visible.
28
+ * - **They are ANNOUNCED**, for the reason the first paragraph gives: this is
29
+ * the only channel a run has before it produces an output, and a `<pre>` that
30
+ * grows is a silent one. A screen-reader user pressing "Digest" got nothing
31
+ * between the click and a terminal state minutes later — no "fetching", no
32
+ * "summarising", no evidence the button did anything. See `role="log"` below.
33
+ * The six pages that render this pass only `className`, so no template could
34
+ * have fixed it locally; that is what makes it this component's job.
28
35
  *
29
36
  * @example
30
37
  * ```tsx
package/dist/context.d.ts CHANGED
@@ -1,34 +1,45 @@
1
1
  import { type ReactNode } from "react";
2
- import type { SessionCore, SessionSnapshot } from "./session-core-types.ts";
3
- import type { ClientTheme } from "./types.ts";
2
+ import type { BrowserSession, SessionSnapshot } from "./session-core-types.ts";
3
+ import type { AgentState, ClientTheme, SessionError } from "./types.ts";
4
4
  /**
5
- * Provides the {@link SessionCore} the session hooks read. `client()`
5
+ * Provides the {@link BrowserSession} the session hooks read. `mountClient()`
6
6
  * installs it automatically; a custom tree only needs it when bypassing
7
- * `client()` and mounting React itself.
7
+ * `mountClient()` and mounting React itself.
8
8
  *
9
9
  * @internal
10
10
  */
11
- export declare function SessionProvider({ value, children }: {
12
- value: SessionCore;
11
+ export declare function SessionProvider({ value, children, }: {
12
+ value: BrowserSession;
13
13
  children?: ReactNode;
14
- }): import("react").FunctionComponentElement<import("react").ProviderProps<SessionCore | null>>;
14
+ }): import("react").FunctionComponentElement<import("react").ProviderProps<BrowserSession | null>>;
15
+ /**
16
+ * The session's control methods, and nothing else — what a `client.tsx` may
17
+ * legitimately CALL on a session, as against what it may read.
18
+ *
19
+ * Declared once and merged into {@link Session} rather than written out at both
20
+ * places: the two lists have to be the same list, and a member added to one and
21
+ * not the other is a hook that cannot do what `useSession()` can.
22
+ *
23
+ * Method signatures come from {@link BrowserSession} — one source of truth.
24
+ *
25
+ * @public
26
+ */
27
+ export type SessionActions = Pick<BrowserSession, "start" | "cancel" | "resetState" | "reset" | "restart" | "disconnect" | "toggle" | "end">;
15
28
  /**
16
29
  * What {@link useSession} returns: the live {@link SessionSnapshot} fields
17
30
  * (`state`, `messages`, `toolCalls`, `agentState`, live transcripts, `error`,
18
31
  * `apiUrl`, `started`/`running`/`recording`, …) merged with the session's
19
- * control methods (`start`, `toggle`, `reset`, `resetState`, `disconnect`,
20
- * `cancel`, `end`).
32
+ * control methods (`start`, `toggle`, `reset`, `restart`, `resetState`,
33
+ * `disconnect`, `cancel`, `end`).
21
34
  *
22
35
  * Note there is no text-send method — sessions are voice-only; the only
23
36
  * client→server inputs are audio and the control methods above.
24
37
  *
25
- * Method signatures come from {@link SessionCore} — one source of truth.
26
- *
27
38
  * @public
28
39
  */
29
- export type Session = SessionSnapshot & Pick<SessionCore, "start" | "cancel" | "resetState" | "reset" | "disconnect" | "toggle" | "end">;
40
+ export type Session = SessionSnapshot & SessionActions;
30
41
  /**
31
- * Return the raw {@link SessionCore} from context without subscribing to
42
+ * Return the raw {@link BrowserSession} from context without subscribing to
32
43
  * snapshot changes. Useful for accessing stable methods (`start`, `toggle`,
33
44
  * `reset`, …) from components that select narrow state via
34
45
  * {@link useSessionSelector}.
@@ -36,15 +47,73 @@ export type Session = SessionSnapshot & Pick<SessionCore, "start" | "cancel" | "
36
47
  * Not part of the package's public export surface — internal to aai-ui
37
48
  * components.
38
49
  */
39
- export declare function useSessionCore(): SessionCore;
50
+ export declare function useSessionCore(): BrowserSession;
51
+ /**
52
+ * The session's control methods — `start`, `cancel`, `resetState`, `reset`,
53
+ * `restart`, `disconnect`, `toggle`, `end` — with **no snapshot
54
+ * subscription**.
55
+ *
56
+ * This is the narrow half of {@link useSession}, and it is the half a custom
57
+ * chrome could not reach. `<Controls>` and `<StartScreen>` in this package pair
58
+ * a one-field `useSessionSelector` with this package's own `useSessionCore`
59
+ * (`context.ts`, unpublished); a `client.tsx`
60
+ * could not, because that hook is not published — so a footer needing `start`
61
+ * and `toggle` held a WHOLE-SNAPSHOT `useSession()`, and `session-core.ts`
62
+ * rebuilds the snapshot object on every change. Measured consequence: four
63
+ * components across three templates re-rendered on every STT partial and every
64
+ * streaming delta, in files whose every other component is narrowly subscribed
65
+ * on purpose. One of them (`infocom-adventure`'s `TitleScreen`) reads nothing
66
+ * from the snapshot at all and subscribes to all of it for `session.start`.
67
+ *
68
+ * **Why publishing this does not reopen what `/internal` closed.**
69
+ * `useSessionCore` hands back the STORE — `subscribe`, `getSnapshot`,
70
+ * `connect`, `Symbol.dispose` — which is the framework's own plumbing, the same
71
+ * category as the providers and `buildAgentUrl` that live on
72
+ * `@alexkroman1/aai-ui/internal`. A client that holds it can subscribe out of
73
+ * band of React, dial a socket the mount did not, and dispose the session under
74
+ * the tree that is rendering it. What comes back from here is the SAME eight
75
+ * methods `useSession()` already publishes on its result, built into a fresh
76
+ * object rather than passed through, so the store is not reachable from it.
77
+ * There is no new capability here — only the existing one without the
78
+ * subscription tax.
79
+ *
80
+ * Identity-stable per core, so it is safe in a dependency array and in a
81
+ * `memo()` child's props: the methods are closures created once by
82
+ * `createBrowserSession`, and the object wrapping them is memoized on the core.
83
+ *
84
+ * Throws outside the provider `mountClient()` installs, like every session hook.
85
+ *
86
+ * @example A footer that acts on the session without re-rendering with it
87
+ * ```tsx
88
+ * import { useSessionActions, useSessionSelector } from "@alexkroman1/aai-ui";
89
+ *
90
+ * function Footer() {
91
+ * // Two narrow subscriptions and no snapshot read: this row re-renders when
92
+ * // `running` flips, and not on every transcript delta.
93
+ * const running = useSessionSelector((s) => s.running);
94
+ * const { toggle, end } = useSessionActions();
95
+ * return (
96
+ * <>
97
+ * <button onClick={toggle}>{running ? "Pause" : "Resume"}</button>
98
+ * <button onClick={end}>Hang up</button>
99
+ * </>
100
+ * );
101
+ * }
102
+ * ```
103
+ *
104
+ * @returns The eight control methods — see {@link SessionActions}.
105
+ *
106
+ * @public
107
+ */
108
+ export declare function useSessionActions(): SessionActions;
40
109
  /**
41
110
  * Return the live {@link Session}: the current snapshot fields plus the
42
111
  * control methods (`start`, `toggle`, `reset`, `resetState`, `disconnect`,
43
112
  * `cancel`, `end`).
44
113
  *
45
- * Throws if used outside the provider `client()` installs (the error names
114
+ * Throws if used outside the provider `mountClient()` installs (the error names
46
115
  * `<SessionProvider>` — you only mount that yourself when bypassing
47
- * `client()`). Re-renders the component on *every* snapshot change; for a
116
+ * `mountClient()`). Re-renders the component on *every* snapshot change; for a
48
117
  * component that reads one field, prefer {@link useSessionSelector} for a
49
118
  * targeted subscription.
50
119
  *
@@ -94,9 +163,63 @@ export declare function useSession(): Session;
94
163
  */
95
164
  export declare function useSessionSelector<T>(selector: (snapshot: SessionSnapshot) => T, isEqual?: (a: T, b: T) => boolean): T;
96
165
  /**
97
- * Provides the theme the components read via `useTheme`. `client()` installs
166
+ * The agent's live {@link AgentState} — `disconnected`, `connecting`, `ready`,
167
+ * `listening`, `thinking`, `speaking`, `error` — on its own narrow
168
+ * subscription.
169
+ *
170
+ * `useSessionSelector((s) => s.state)` spelled once. It is one of exactly two
171
+ * snapshot fields that more than one custom chrome ever selects (the other is
172
+ * {@link useSessionError}), and it had been written inline at eight sites —
173
+ * including inside this package and, worse, in `ConsoleShell`'s own `@example`,
174
+ * which taught the inline form to everyone who read it.
175
+ *
176
+ * **Named `useSessionStatus`, not `useSessionState`.** `useAgentState` is the
177
+ * SLOT hook — the agent's own synced application state, whatever a
178
+ * `sessionSlot()` projects — and `AgentState` here is the phase of the CALL.
179
+ * Two different concepts one letter apart, so the shorter-sounding name is the
180
+ * one deliberately not taken.
181
+ *
182
+ * Pair it with {@link AGENT_STATE_LABELS} for a rendered word; the raw member
183
+ * is a wire value, not a label.
184
+ *
185
+ * @example
186
+ * ```tsx
187
+ * import { AGENT_STATE_LABELS, useSessionStatus } from "@alexkroman1/aai-ui";
188
+ *
189
+ * function StatusDot() {
190
+ * const status = useSessionStatus();
191
+ * return <span data-state={status}>{AGENT_STATE_LABELS[status]}</span>;
192
+ * }
193
+ * ```
194
+ *
195
+ * @returns The current agent state.
196
+ *
197
+ * @public
198
+ */
199
+ export declare function useSessionStatus(): AgentState;
200
+ /**
201
+ * The session's current {@link SessionError}, or `null` when there is none, on
202
+ * its own narrow subscription.
203
+ *
204
+ * The other half of {@link useSessionStatus} — the second of the two fields a
205
+ * custom chrome reads over and over, and the one whose absence is invisible:
206
+ * per the `fatalError` latch in `session-core.ts` the error is the ONLY
207
+ * remaining signal that a session died, since the state beside it goes back to
208
+ * reading like a live one.
209
+ *
210
+ * A chrome rendering it owes `role="alert"` — which is what
211
+ * {@link SessionErrorBanner} is for, and why reaching for that beats reaching
212
+ * for this.
213
+ *
214
+ * @returns The current error, or `null`.
215
+ *
216
+ * @public
217
+ */
218
+ export declare function useSessionError(): SessionError | null;
219
+ /**
220
+ * Provides the theme the components read via `useTheme`. `mountClient()` installs
98
221
  * it automatically (from `ClientConfig.theme`); a custom tree only needs it
99
- * when bypassing `client()` and mounting React itself.
222
+ * when bypassing `mountClient()` and mounting React itself.
100
223
  *
101
224
  * @internal
102
225
  */
@@ -110,7 +233,7 @@ export declare function ThemeProvider({ value, children, }: {
110
233
  * provider is present, so components can call it unconditionally.
111
234
  *
112
235
  * This is how a custom component stays on the agent's palette: a
113
- * `client({ theme })` override reaches it here, where a hardcoded colour or a
236
+ * `mountClient({ theme })` override reaches it here, where a hardcoded colour or a
114
237
  * Tailwind class cannot see it.
115
238
  *
116
239
  * @example