@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
package/dist/index.js CHANGED
@@ -1,25 +1,189 @@
1
- import { n as fetchClientConfig } from "./client-config-DD820zHn.js";
2
- import { i as AutoScroll, n as Markdown, r as useConversation, t as MessageList } from "./message-list-C0pL7x41.js";
3
- import { ThemeProvider, useSession, useSessionSelector, useTheme } from "./context.js";
4
- import { r as inkTint } from "./_colors-CZ6OlPbL.js";
1
+ import { n as fetchClientConfig } from "./client-config-DJQHnYjm.js";
2
+ import { i as AutoScroll, n as Markdown, r as useConversation, t as MessageList } from "./message-list-CdOnSh5m.js";
3
+ import { ThemeProvider, useSession, useSessionActions, useSessionError, useSessionSelector, useSessionStatus, useTheme } from "./context.js";
4
+ import { a as inkTint, i as focusRingStyle, n as FOCUS_RING } from "./_colors-CpZO-88A.js";
5
5
  import { Button } from "./components/button.js";
6
- import { n as ConsoleShell, t as ChatView } from "./chat-view-Bv5VFJIE.js";
7
- import { n as setPageTitle } from "./_utils-CyzjK0gW.js";
6
+ import { n as ConsoleShell, r as SessionErrorBanner, t as ChatView } from "./chat-view-C_3T7Ln8.js";
7
+ import { n as setPageTitle } from "./_utils-DnQDM9Uy.js";
8
8
  import { Controls } from "./components/controls.js";
9
- import { n as useUserTranscript } from "./use-user-transcript-DFTSEuZN.js";
10
- import { n as ToolCallRow } from "./tool-call-block-Bunc6rCw.js";
9
+ import { n as useUserTranscript } from "./use-user-transcript-uyHhzy4d.js";
10
+ import { n as ToolCallRow } from "./tool-call-block-C2t_5fpp.js";
11
11
  import { SidebarLayout } from "./components/sidebar-layout.js";
12
12
  import { StartScreen } from "./components/start-screen.js";
13
- import { a as useWorkflowProgress, c as isTerminal, o as useWorkflowApiRef, r as useWorkflowRun, s as createWorkflowApi } from "./use-workflow-run-CXGEcM0l.js";
14
- import { t as createSessionCore } from "./session-core-C9elBIdu.js";
15
- import { client, mountRoot, resolveContainer } from "./define-client.js";
13
+ import { a as useWorkflowProgress, c as isTerminal, o as useWorkflowApiRef, r as useWorkflowRun, s as createWorkflowApi } from "./use-workflow-run-CP2ekKPV.js";
14
+ import { i as urlSlot, n as storageGet, r as storageSet, t as createBrowserSession } from "./session-core-gwePM95B.js";
15
+ import { mountClient, mountRoot, resolveContainer } from "./define-client.js";
16
16
  import { useAgentState, useEvent, useToolCallStart, useToolResult } from "./hooks.js";
17
17
  import clsx from "clsx";
18
18
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
19
- import { createContext, createElement, useCallback, useContext, useEffect, useId, useRef, useState } from "react";
19
+ import { createContext, createElement, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
20
20
  import { errorMessage } from "@alexkroman1/aai";
21
21
  import { formatBytes, isRecord, omitUndefined } from "@alexkroman1/aai/utils";
22
22
  import { createEpoch } from "@alexkroman1/aai/internal";
23
+ //#region src/agent-state-labels.ts
24
+ /**
25
+ * The default label per {@link AgentState}.
26
+ *
27
+ * Override the ones your page has a better word for and keep the rest:
28
+ *
29
+ * ```ts
30
+ * import type { AgentState } from "@alexkroman1/aai-ui";
31
+ * import { AGENT_STATE_LABELS } from "@alexkroman1/aai-ui";
32
+ *
33
+ * // A dispatch board that shouts, and renames one state.
34
+ * const STATE_LABEL = { ...AGENT_STATE_LABELS, thinking: "Processing" };
35
+ * const shout = (s: AgentState) => STATE_LABEL[s].toUpperCase();
36
+ * ```
37
+ *
38
+ * **Sentence case, deliberately.** A template that wants caps applies its own
39
+ * `.toUpperCase()`, and a template that wants Title Case is already there;
40
+ * shipping the shouted form instead would leave the two chromes that do not
41
+ * shout with a string they have to un-shout, which no case transform does
42
+ * correctly.
43
+ *
44
+ * Two wordings are decisions rather than transliterations of the member name:
45
+ *
46
+ * - `disconnected` is **"Idle"**. It is the state a session is in BEFORE it has
47
+ * ever started as well as after it ends, so it is the first word most callers
48
+ * see; "Disconnected" reads as a fault on a page where nothing has gone
49
+ * wrong yet. Both chromes that mapped this state by hand chose "Idle" too.
50
+ * - `connecting` and `thinking` carry an ellipsis, `listening` and `speaking`
51
+ * do not. The first two are waits with nothing for the caller to do; the
52
+ * other two describe someone actually talking. Same distinction
53
+ * `WORKFLOW_STATUS_LABELS` draws with its one "Working…".
54
+ *
55
+ * @public
56
+ */
57
+ const AGENT_STATE_LABELS = {
58
+ disconnected: "Idle",
59
+ connecting: "Connecting…",
60
+ ready: "Ready",
61
+ listening: "Listening",
62
+ thinking: "Thinking…",
63
+ speaking: "Speaking",
64
+ error: "Error"
65
+ };
66
+ //#endregion
67
+ //#region src/components/bullet-list.tsx
68
+ /** @jsxImportSource react */
69
+ /**
70
+ * A disc-bulleted list of short strings — a run's key points, findings, risks.
71
+ *
72
+ * Five pages had written this, byte-identical apart from a `text-sm` suffix on
73
+ * two of them, and all five had the same two defects. Both are the reason this
74
+ * is a component rather than four lines a page repeats:
75
+ *
76
+ * - **All five keyed by the string itself, and these lists are MODEL OUTPUT.**
77
+ * Two identical bullets are entirely plausible — a summariser that repeats
78
+ * itself is a bad summary, not a bad program — and a repeated string is then
79
+ * a duplicate `key`: React warns, and the two `<li>`s contend for one slot in
80
+ * the reconciliation. What is keyed here instead is the content PLUS how many
81
+ * times that content has already appeared in this list, which is unique by
82
+ * construction and unchanged by a re-render that did not change the text.
83
+ * (Position alone would also be sound — these lists are replaced wholesale by
84
+ * each new output and never reordered — but it is what `noArrayIndexKey`
85
+ * exists to talk you out of, and a unique key is one `Map` away, so there is
86
+ * no reason to spend a lint suppression on it.)
87
+ * - **Three of the five rendered an empty `<ul>` under a heading.** Two had
88
+ * hand-rolled `if (items.length === 0) return null` and three had not, so the
89
+ * same absent field was "nothing" on two pages and a stray heading with a
90
+ * void under it on three. Emptiness renders NOTHING here, `title` included:
91
+ * a heading over no bullets is a claim the run did not make.
92
+ *
93
+ * @example
94
+ * ```tsx
95
+ * import { BulletList } from "@alexkroman1/aai-ui";
96
+ *
97
+ * function Findings({ risks }: { risks: string[] }) {
98
+ * return <BulletList title="Risks" items={risks} size="sm" />;
99
+ * }
100
+ * ```
101
+ *
102
+ * @param props - Bullet-list props.
103
+ *
104
+ * @public
105
+ */
106
+ function BulletList({ items, title, size = "base", className }) {
107
+ if (items.length === 0) return null;
108
+ const seen = /* @__PURE__ */ new Map();
109
+ const list = /* @__PURE__ */ jsx("ul", {
110
+ className: clsx("flex list-disc flex-col gap-1 pl-5", size === "sm" && "text-sm", className),
111
+ children: items.map((item) => {
112
+ const nth = seen.get(item) ?? 0;
113
+ seen.set(item, nth + 1);
114
+ return /* @__PURE__ */ jsx("li", { children: item }, `${nth}:${item}`);
115
+ })
116
+ });
117
+ const heading = title === void 0 || title === null || title === false ? void 0 : title;
118
+ if (heading === void 0) return list;
119
+ return /* @__PURE__ */ jsxs("section", {
120
+ className: "flex flex-col gap-1",
121
+ children: [/* @__PURE__ */ jsx("h3", {
122
+ className: "text-sm font-medium opacity-70",
123
+ children: heading
124
+ }), list]
125
+ });
126
+ }
127
+ //#endregion
128
+ //#region src/components/facts.tsx
129
+ /** @jsxImportSource react */
130
+ /** What goes between two facts. A separator, not a bullet — U+00B7, not U+2022. */
131
+ const FACT_SEPARATOR = " · ";
132
+ /**
133
+ * A muted line of run facts, joined by `·` — "6 segments · 12:04 of audio ·
134
+ * 1,840 words".
135
+ *
136
+ * Nine pages had written this by hand under four different typographies for
137
+ * one role, two of them (`call-audit` and `spoken-summary`) byte-identical down
138
+ * to the payload. Three things it takes off the caller:
139
+ *
140
+ * - **The separator cannot be forgotten, and neither can the space around it.**
141
+ * Four of the nine carried a literal `{" "}` at the end of a line, because
142
+ * Prettier's wrap ate the space that made `· ` read as a separator rather
143
+ * than as punctuation glued to the next word. A line that is correct only
144
+ * because somebody remembered an invisible JSX expression is exactly the
145
+ * thing a component should own.
146
+ * - **A fact worth omitting is omitted, and by the caller's own condition.**
147
+ * The hand-written shape for a conditional fact was to splice the separator
148
+ * into the string — `{x ? \` · budget exhausted\` : ""}` — which puts the
149
+ * punctuation in two places and gets the leading separator wrong the moment
150
+ * the fact before it also disappears. Passing the condition and letting this
151
+ * drop it keeps the separator in one place.
152
+ * - **A line with nothing left to say renders NOTHING.** With every fact
153
+ * conditional, the alternative is a muted empty row, or a bare `·`.
154
+ *
155
+ * The facts are JOINED into one string rather than interleaved as elements, and
156
+ * that is why the prop is text: joined, there is no per-fact `key` to invent —
157
+ * the same reasoning `WorkflowProgress` gives for its log lines. A line that
158
+ * genuinely needs an element in it (a link) wants its own markup.
159
+ *
160
+ * @example
161
+ * ```tsx
162
+ * import { Facts } from "@alexkroman1/aai-ui";
163
+ *
164
+ * function RunFacts({ words, cut }: { words: number; cut: number }) {
165
+ * return <Facts size="xs" items={[`${words} words`, cut > 0 && `${cut} blind cuts`]} />;
166
+ * }
167
+ * ```
168
+ *
169
+ * @param props - Facts-line props.
170
+ *
171
+ * @public
172
+ */
173
+ function Facts({ items, size = "sm", as = "p", className }) {
174
+ const shown = items.filter((item) => item !== false && item !== null && item !== void 0 && item !== "");
175
+ if (shown.length === 0) return null;
176
+ const text = shown.join(FACT_SEPARATOR);
177
+ const classes = clsx(size === "xs" ? "text-xs opacity-60" : "text-sm opacity-70", className);
178
+ return as === "span" ? /* @__PURE__ */ jsx("span", {
179
+ className: classes,
180
+ children: text
181
+ }) : /* @__PURE__ */ jsx("p", {
182
+ className: classes,
183
+ children: text
184
+ });
185
+ }
186
+ //#endregion
23
187
  //#region src/components/_form-readiness.ts
24
188
  /**
25
189
  * Whether a form's own fields exist yet.
@@ -47,7 +211,7 @@ import { createEpoch } from "@alexkroman1/aai/internal";
47
211
  * one describes an element that EXISTS. So this is a context rather than an
48
212
  * attribute, and it carries the one fact a DOM read cannot recover.
49
213
  *
50
- * A form with no such children is ready by definition — `useFormFieldsPending`
214
+ * A form with no such children is ready by definition — `useFormReadiness`
51
215
  * outside a provider reports nothing pending, so every hand-written form is
52
216
  * unaffected and `Form` keeps working outside this package.
53
217
  */
@@ -173,7 +337,7 @@ function readFiles(files, read) {
173
337
  if (read === "upload") return Promise.resolve([...files]);
174
338
  return Promise.all(files.map((file) => describeFile(file, read)));
175
339
  }
176
- /** The `data-aai-read` attribute as a {@link FileRead}, defaulting to `"none"`. */
340
+ /** The `data-aai-read` attribute as a {@link FileReadMode}, defaulting to `"none"`. */
177
341
  function readMode(raw) {
178
342
  return raw === "text" || raw === "dataUrl" || raw === "upload" ? raw : "none";
179
343
  }
@@ -274,12 +438,12 @@ function Field({ label, hint, htmlFor, className, children }) {
274
438
  function useControlProps() {
275
439
  const theme = useTheme();
276
440
  return {
277
- className: clsx("w-full rounded-aai border px-3 py-2 text-sm font-aai", "outline-none focus-visible:[outline:2px_solid] focus-visible:[outline-offset:2px]", "disabled:cursor-not-allowed disabled:opacity-50"),
441
+ className: clsx("w-full rounded-aai border px-3 py-2 text-sm font-aai", FOCUS_RING, "disabled:cursor-not-allowed disabled:opacity-50"),
278
442
  style: {
279
443
  background: theme.surface,
280
444
  color: theme.text,
281
445
  borderColor: theme.border,
282
- outlineColor: theme.primary
446
+ ...focusRingStyle(theme.primary)
283
447
  }
284
448
  };
285
449
  }
@@ -311,16 +475,14 @@ function useFileControlProps() {
311
475
  };
312
476
  }
313
477
  /**
314
- * A single-line text input.
478
+ * `TextField` and `NumberField`, which differ only in the `type` they set.
315
479
  *
316
- * Accepts every `<input>` attribute except `name` and `className`, which this
317
- * component owns, plus the shared {@link FieldShell} props.
318
- *
319
- * @param props - {@link FieldShell} props plus `<input>` attributes.
320
- *
321
- * @public
480
+ * Internal, because the two stay separate PUBLIC exports: each is named in the
481
+ * `components` capability contract and in the API report, and a caller writes
482
+ * the one whose value shape it wants — `NumberField` is the one that
483
+ * contributes a number to {@link FormValues}.
322
484
  */
323
- function TextField({ name, label, hint, className, ...rest }) {
485
+ function InputField({ defaultType, name, label, hint, className, ...rest }) {
324
486
  const id = useId();
325
487
  const control = useControlProps();
326
488
  return /* @__PURE__ */ jsx(Field, {
@@ -331,13 +493,29 @@ function TextField({ name, label, hint, className, ...rest }) {
331
493
  children: /* @__PURE__ */ jsx("input", {
332
494
  id,
333
495
  name,
334
- type: "text",
496
+ type: defaultType,
335
497
  ...control,
336
498
  ...rest
337
499
  })
338
500
  });
339
501
  }
340
502
  /**
503
+ * A single-line text input.
504
+ *
505
+ * Accepts every `<input>` attribute except `name` and `className`, which this
506
+ * component owns, plus the shared {@link FieldShell} props.
507
+ *
508
+ * @param props - {@link FieldShell} props plus `<input>` attributes.
509
+ *
510
+ * @public
511
+ */
512
+ function TextField(props) {
513
+ return /* @__PURE__ */ jsx(InputField, {
514
+ defaultType: "text",
515
+ ...props
516
+ });
517
+ }
518
+ /**
341
519
  * A number input. Contributes a NUMBER to {@link FormValues}, or nothing when
342
520
  * left empty.
343
521
  *
@@ -349,21 +527,10 @@ function TextField({ name, label, hint, className, ...rest }) {
349
527
  *
350
528
  * @public
351
529
  */
352
- function NumberField({ name, label, hint, className, ...rest }) {
353
- const id = useId();
354
- const control = useControlProps();
355
- return /* @__PURE__ */ jsx(Field, {
356
- label,
357
- hint,
358
- htmlFor: id,
359
- className,
360
- children: /* @__PURE__ */ jsx("input", {
361
- id,
362
- name,
363
- type: "number",
364
- ...control,
365
- ...rest
366
- })
530
+ function NumberField(props) {
531
+ return /* @__PURE__ */ jsx(InputField, {
532
+ defaultType: "number",
533
+ ...props
367
534
  });
368
535
  }
369
536
  /**
@@ -474,14 +641,14 @@ function CheckboxField({ name, label, hint, className, ...rest }) {
474
641
  * travel in it. With `upload` the field contributes the `File` itself,
475
642
  * `useWorkflowSubmit` stores it through `POST /workflows/uploads` before
476
643
  * starting the run, and the input carries the upload id — which a step reads
477
- * windows of with `readUpload`. Declaring the property in the workflow's
644
+ * windows of with `stepReadUpload`. Declaring the property in the workflow's
478
645
  * `uploads` list makes `<WorkflowFields>` render exactly this, so a declared
479
646
  * form needs no file markup at all.
480
647
  *
481
648
  * **Without it the field describes the file and does not read it.** `read`
482
649
  * exists for the cases where the bytes really are small and really are the
483
650
  * input — a CSV of ids, a config — and the size is the author's to check. See
484
- * {@link FileRead} for the four values; `upload` is shorthand for
651
+ * {@link FileReadMode} for the four values; `upload` is shorthand for
485
652
  * `read="upload"`.
486
653
  *
487
654
  * Otherwise accepts every `<input>` attribute except `name`, `className` and
@@ -997,13 +1164,13 @@ function humanize(name) {
997
1164
  *
998
1165
  * The complement of a status line, and the reason both exist: a run is
999
1166
  * `running` for its whole life, so a one-round job and a ten-round one look
1000
- * identical while they happen. These lines come from the run itself (`report()`
1167
+ * identical while they happen. These lines come from the run itself (`stepReport()`
1001
1168
  * in a `"use step"` body), which is the only channel a workflow has before it
1002
1169
  * produces an output.
1003
1170
  *
1004
- * Three rules are baked in, and they are why this is a component rather than
1171
+ * Four rules are baked in, and they are why this is a component rather than
1005
1172
  * three lines each page writes for itself — the two templates that had written
1006
- * it had written all three, comments included:
1173
+ * it had written three of them, comments included:
1007
1174
  *
1008
1175
  * - **It renders nothing until there is something to render.** `supported` is
1009
1176
  * what keeps this from being an empty box forever on an agent deployed before
@@ -1017,6 +1184,13 @@ function humanize(name) {
1017
1184
  * or opening a finished run tomorrow — shows how it got there rather than an
1018
1185
  * empty box. That is `useWorkflowProgress`'s doing; this is what makes it
1019
1186
  * visible.
1187
+ * - **They are ANNOUNCED**, for the reason the first paragraph gives: this is
1188
+ * the only channel a run has before it produces an output, and a `<pre>` that
1189
+ * grows is a silent one. A screen-reader user pressing "Digest" got nothing
1190
+ * between the click and a terminal state minutes later — no "fetching", no
1191
+ * "summarising", no evidence the button did anything. See `role="log"` below.
1192
+ * The six pages that render this pass only `className`, so no template could
1193
+ * have fixed it locally; that is what makes it this component's job.
1020
1194
  *
1021
1195
  * @example
1022
1196
  * ```tsx
@@ -1033,27 +1207,33 @@ function humanize(name) {
1033
1207
  */
1034
1208
  function WorkflowProgress({ runId, api, className, placeholder, lines }) {
1035
1209
  const { progress, streaming, supported } = useWorkflowProgress(runId, omitUndefined({ api }));
1036
- const shown = lines === void 0 ? progress : progress.slice(Math.max(progress.length - lines, 0));
1037
- if (!supported || shown.length === 0) return placeholder ?? null;
1210
+ const text = useMemo(() => {
1211
+ const shown = lines === void 0 ? progress : progress.slice(Math.max(progress.length - lines, 0));
1212
+ return shown.length === 0 ? null : shown.join("\n");
1213
+ }, [progress, lines]);
1214
+ if (!supported || text === null) return placeholder ?? null;
1038
1215
  return /* @__PURE__ */ jsxs("pre", {
1216
+ role: "log",
1217
+ "aria-live": "polite",
1218
+ "aria-atomic": "false",
1039
1219
  className: clsx(className ?? "whitespace-pre-wrap border-l pl-4 text-xs opacity-70"),
1040
- children: [shown.join("\n"), streaming && "\n…"]
1220
+ children: [text, streaming && "\n…"]
1041
1221
  });
1042
1222
  }
1043
1223
  //#endregion
1044
1224
  //#region src/page.tsx
1045
1225
  /** @jsxImportSource react */
1046
1226
  /**
1047
- * `page()` — mount a WORKFLOW APP's UI: React, theme, no session.
1227
+ * `mountPage()` — mount a WORKFLOW APP's UI: React, theme, no session.
1048
1228
  *
1049
- * The twin of `client()` for an agent whose front door is a form rather than a
1229
+ * The twin of `mountClient()` for an agent whose front door is a form rather than a
1050
1230
  * microphone (`workflowApp()`). It is a separate entry rather than
1051
- * an option on `client()` because of what `client()` unavoidably does: it
1052
- * constructs a `SessionCore`, which owns a WebSocket URL provider, an audio
1231
+ * an option on `mountClient()` because of what `mountClient()` unavoidably does: it
1232
+ * constructs a `BrowserSession`, which owns a WebSocket URL provider, an audio
1053
1233
  * graph, and a microphone request. A flag would have to make all of that
1054
1234
  * conditional, and every session hook would then have to answer "what does this
1055
1235
  * mean with no session?" — so the honest split is two mounts. A page that wants
1056
- * voice uses `client()`; a page that wants neither audio nor a socket uses this.
1236
+ * voice uses `mountClient()`; a page that wants neither audio nor a socket uses this.
1057
1237
  *
1058
1238
  * Authoring is otherwise identical — the file is still `client.tsx`, still
1059
1239
  * React, still Tailwind, still the same theme tokens — so a workflow app reads
@@ -1069,7 +1249,7 @@ function WorkflowProgress({ runId, api, className, placeholder, lines }) {
1069
1249
  *
1070
1250
  * @example
1071
1251
  * ```tsx
1072
- * import { createWorkflowApi, page, useWorkflowRun } from "@alexkroman1/aai-ui";
1252
+ * import { createWorkflowApi, mountPage, useWorkflowRun } from "@alexkroman1/aai-ui";
1073
1253
  * import { useState } from "react";
1074
1254
  *
1075
1255
  * // Hoisted: a client built in render is a new object every render.
@@ -1088,14 +1268,14 @@ function WorkflowProgress({ runId, api, className, placeholder, lines }) {
1088
1268
  * );
1089
1269
  * }
1090
1270
  *
1091
- * page({ name: "Digest", component: App });
1271
+ * mountPage({ name: "Digest", component: App });
1092
1272
  * ```
1093
1273
  *
1094
1274
  * @throws If the target element is not found in the DOM.
1095
1275
  *
1096
1276
  * @public
1097
1277
  */
1098
- function page(config) {
1278
+ function mountPage(config) {
1099
1279
  const container = resolveContainer(config.target);
1100
1280
  setPageTitle(config.name);
1101
1281
  return mountRoot(container, createElement(ThemeProvider, { value: config.theme }, createElement(config.component)));
@@ -1286,13 +1466,7 @@ function useDownloadUrl(uploadId, opts = {}) {
1286
1466
  const PREFIX$1 = "aai:run-key:";
1287
1467
  /** This page's own slot — see "The slot is keyed by the page's own URL". */
1288
1468
  function slotFor() {
1289
- const href = globalThis.location?.href;
1290
- if (href === void 0) return PREFIX$1;
1291
- try {
1292
- return `${PREFIX$1}${new URL("./", href).href}`;
1293
- } catch {
1294
- return `${PREFIX$1}${href}`;
1295
- }
1469
+ return urlSlot(PREFIX$1, "./");
1296
1470
  }
1297
1471
  /**
1298
1472
  * Read the key this page already has, or mint and remember one.
@@ -1302,17 +1476,12 @@ function slotFor() {
1302
1476
  * hand `recover` one nothing was ever started under.
1303
1477
  */
1304
1478
  function mintRunKey(storage) {
1305
- try {
1306
- const store = storage === "local" ? globalThis.localStorage : globalThis.sessionStorage;
1307
- const slot = slotFor();
1308
- const stored = store?.getItem(slot);
1309
- if (stored !== null && stored !== void 0) return stored;
1310
- const minted = crypto.randomUUID();
1311
- store?.setItem(slot, minted);
1312
- return minted;
1313
- } catch {
1314
- return crypto.randomUUID();
1315
- }
1479
+ const slot = slotFor();
1480
+ const stored = storageGet(storage, slot);
1481
+ if (stored !== void 0) return stored;
1482
+ const minted = crypto.randomUUID();
1483
+ storageSet(storage, slot, minted);
1484
+ return minted;
1316
1485
  }
1317
1486
  /**
1318
1487
  * The key `useWorkflowSubmit` uses when the page named none.
@@ -1338,7 +1507,7 @@ function mintRunKey(storage) {
1338
1507
  * @internal
1339
1508
  */
1340
1509
  function useDefaultRunKey(explicit) {
1341
- const [minted] = useState(() => explicit === void 0 ? mintRunKey("session") : crypto.randomUUID());
1510
+ const [minted] = useState(() => explicit === void 0 ? mintRunKey("session") : "");
1342
1511
  return explicit ?? minted;
1343
1512
  }
1344
1513
  /**
@@ -1422,11 +1591,11 @@ function useRunKey(options = {}) {
1422
1591
  */
1423
1592
  function useRecoveredRun(opts) {
1424
1593
  const { workflow, key, enabled, getClient } = opts;
1425
- const [recovering, setRecovering] = useState(enabled && key !== void 0);
1594
+ const [recovering, setRecovering] = useState(enabled);
1426
1595
  const handlers = useRef(opts);
1427
1596
  handlers.current = opts;
1428
1597
  useEffect(() => {
1429
- if (!enabled || key === void 0) return;
1598
+ if (!enabled) return;
1430
1599
  let cancelled = false;
1431
1600
  setRecovering(true);
1432
1601
  getClient().find(workflow, key, { limit: 1 }).then((found) => {
@@ -1489,6 +1658,99 @@ function useRunControls(runId, getClient) {
1489
1658
  };
1490
1659
  }
1491
1660
  //#endregion
1661
+ //#region src/_upload-pause.ts
1662
+ /**
1663
+ * The two buttons a page puts over a live upload, bound to whichever gate the
1664
+ * submission is currently holding.
1665
+ *
1666
+ * `useWorkflowSubmit` and `useWorkflowStream` both own an {@link UploadGate} and
1667
+ * both reported the pause the same way: park the gate, then fold `paused` into
1668
+ * the status the bar is already drawing. Folded rather than replaced, because
1669
+ * everything else about that status — which file, how far, of how many — is
1670
+ * still true. Two copies of that rule are two copies that can stop agreeing
1671
+ * about what a paused bar says.
1672
+ */
1673
+ /**
1674
+ * Bind pause/resume to the live submission's gate.
1675
+ *
1676
+ * @param getGate - Reads the gate out of the caller's ref, so the callbacks stay
1677
+ * stable across submissions rather than being re-created per gate.
1678
+ * @param setUpload - The status setter the progress bar renders from.
1679
+ *
1680
+ * @internal
1681
+ */
1682
+ function useUploadPause(getGate, setUpload) {
1683
+ const setPaused = useCallback((paused) => {
1684
+ setUpload((current) => current ? {
1685
+ ...current,
1686
+ paused
1687
+ } : current);
1688
+ }, [setUpload]);
1689
+ return {
1690
+ pauseUpload: useCallback(() => {
1691
+ getGate()?.pause();
1692
+ setPaused(true);
1693
+ }, [getGate, setPaused]),
1694
+ resumeUpload: useCallback(() => {
1695
+ getGate()?.resume();
1696
+ setPaused(false);
1697
+ }, [getGate, setPaused])
1698
+ };
1699
+ }
1700
+ //#endregion
1701
+ //#region src/_submission-state.ts
1702
+ /** The shared submission scaffold. See the module doc. */
1703
+ function useSubmissionState() {
1704
+ const [runId, setRunId] = useState(void 0);
1705
+ const [starting, setStarting] = useState(false);
1706
+ const [startError, setStartError] = useState(void 0);
1707
+ const [upload, setUpload] = useState(void 0);
1708
+ const current = useRef(void 0);
1709
+ const begin = useCallback((token) => {
1710
+ setStarting(true);
1711
+ setStartError(void 0);
1712
+ setRunId(void 0);
1713
+ current.current?.gate.cancel();
1714
+ current.current = token;
1715
+ }, []);
1716
+ const end = useCallback((token) => {
1717
+ if (current.current !== token) return;
1718
+ current.current = void 0;
1719
+ setStarting(false);
1720
+ setUpload(void 0);
1721
+ }, []);
1722
+ const reset = useCallback(() => {
1723
+ current.current?.gate.cancel();
1724
+ current.current = void 0;
1725
+ setRunId(void 0);
1726
+ setStartError(void 0);
1727
+ setUpload(void 0);
1728
+ }, []);
1729
+ const { pauseUpload, resumeUpload } = useUploadPause(useCallback(() => current.current?.gate, []), setUpload);
1730
+ return {
1731
+ runId,
1732
+ starting,
1733
+ startError,
1734
+ upload,
1735
+ actions: useMemo(() => ({
1736
+ setRunId,
1737
+ setStartError,
1738
+ setUpload,
1739
+ begin,
1740
+ end,
1741
+ reset,
1742
+ pauseUpload,
1743
+ resumeUpload
1744
+ }), [
1745
+ begin,
1746
+ end,
1747
+ reset,
1748
+ pauseUpload,
1749
+ resumeUpload
1750
+ ])
1751
+ };
1752
+ }
1753
+ //#endregion
1492
1754
  //#region src/_upload-recall.ts
1493
1755
  /**
1494
1756
  * Where an upload's ID survives a page RELOAD.
@@ -1554,9 +1816,9 @@ function fingerprint(file) {
1554
1816
  }
1555
1817
  /** This scope's remembered ids, or nothing at all — a parse failure is nothing. */
1556
1818
  function read(scope) {
1819
+ const raw = storageGet("session", keyFor(scope));
1820
+ if (raw === void 0) return {};
1557
1821
  try {
1558
- const raw = globalThis.sessionStorage?.getItem(keyFor(scope));
1559
- if (raw === null || raw === void 0) return {};
1560
1822
  const parsed = JSON.parse(raw);
1561
1823
  return isRecord(parsed) ? parsed : {};
1562
1824
  } catch {
@@ -1564,9 +1826,7 @@ function read(scope) {
1564
1826
  }
1565
1827
  }
1566
1828
  function write(scope, entries) {
1567
- try {
1568
- globalThis.sessionStorage?.setItem(keyFor(scope), JSON.stringify(entries));
1569
- } catch {}
1829
+ storageSet("session", keyFor(scope), JSON.stringify(entries));
1570
1830
  }
1571
1831
  /**
1572
1832
  * The id this file was last being stored under in this tab, if any.
@@ -1710,7 +1970,7 @@ function createUploadGate() {
1710
1970
  *
1711
1971
  * Its own module because both submit hooks need the identical answer and then do
1712
1972
  * two different things with it — `useWorkflowSubmit` stores each file and passes
1713
- * its id, `useWorkflowStream` cuts it into parts and passes the group they share.
1973
+ * its id, `useWorkflowStream` mints the id first so the run can start on it.
1714
1974
  * A second copy of this predicate would be a form field that one hook treats as a
1715
1975
  * file and the other does not, which is invisible until the run reads the wrong
1716
1976
  * kind of string.
@@ -1914,43 +2174,39 @@ async function uploadFiles(api, input, report, parallel, session) {
1914
2174
  return out;
1915
2175
  }
1916
2176
  //#endregion
1917
- //#region src/_upload-pause.ts
2177
+ //#region src/_upload-report.ts
1918
2178
  /**
1919
- * The two buttons a page puts over a live upload, bound to whichever gate the
1920
- * submission is currently holding.
2179
+ * What the bar draws, as a comparable string.
1921
2180
  *
1922
- * `useWorkflowSubmit` and `useWorkflowStream` both own an {@link UploadGate} and
1923
- * both reported the pause the same way: park the gate, then fold `paused` into
1924
- * the status the bar is already drawing. Folded rather than replaced, because
1925
- * everything else about that status — which file, how far, of how many — is
1926
- * still true. Two copies of that rule are two copies that can stop agreeing
1927
- * about what a paused bar says.
2181
+ * The fraction is quantized to a tenth of a percent — finer than anything the
2182
+ * bar or the byte readout resolves. Everything else is compared exactly:
2183
+ * `paused` is a state change a person is waiting to see, and the name/index
2184
+ * pair moving means a different FILE, which must never be coalesced away.
1928
2185
  */
2186
+ function renderKey(status) {
2187
+ const { name, index, count, loaded, total, fraction, paused } = status;
2188
+ const shown = fraction === void 0 ? loaded : Math.round(fraction * 1e3);
2189
+ return `${paused} ${index} ${count} ${total ?? -1} ${shown} ${name}`;
2190
+ }
1929
2191
  /**
1930
- * Bind pause/resume to the live submission's gate.
2192
+ * Wrap an `UploadStatus` setter so redundant reports never reach React.
1931
2193
  *
1932
- * @param getGate - Reads the gate out of the caller's ref, so the callbacks stay
1933
- * stable across submissions rather than being re-created per gate.
1934
- * @param setUpload - The status setter the progress bar renders from.
1935
- *
1936
- * @internal
2194
+ * Clearing the status (`undefined`) always passes and resets the comparison —
2195
+ * it ends one file's bar, and the next file must not be coalesced against the
2196
+ * previous one's last frame.
1937
2197
  */
1938
- function useUploadPause(getGate, setUpload) {
1939
- const setPaused = useCallback((paused) => {
1940
- setUpload((current) => current ? {
1941
- ...current,
1942
- paused
1943
- } : current);
1944
- }, [setUpload]);
1945
- return {
1946
- pauseUpload: useCallback(() => {
1947
- getGate()?.pause();
1948
- setPaused(true);
1949
- }, [getGate, setPaused]),
1950
- resumeUpload: useCallback(() => {
1951
- getGate()?.resume();
1952
- setPaused(false);
1953
- }, [getGate, setPaused])
2198
+ function coalesceUploadReports(set) {
2199
+ let last;
2200
+ return (status) => {
2201
+ if (status === void 0) {
2202
+ last = void 0;
2203
+ set(void 0);
2204
+ return;
2205
+ }
2206
+ const key = renderKey(status);
2207
+ if (key === last) return;
2208
+ last = key;
2209
+ set(status);
1954
2210
  };
1955
2211
  }
1956
2212
  //#endregion
@@ -2025,78 +2281,66 @@ function useUploadPause(getGate, setUpload) {
2025
2281
  function useWorkflowSubmit(workflow, opts = {}) {
2026
2282
  const { api, recover = true, wait, intervalMs, parallel } = opts;
2027
2283
  const key = useDefaultRunKey(opts.key);
2028
- const [runId, setRunId] = useState(void 0);
2029
- const [starting, setStarting] = useState(false);
2030
- const [startError, setStartError] = useState(void 0);
2031
- const [upload, setUpload] = useState(void 0);
2032
- const session = useRef(void 0);
2284
+ const state = useSubmissionState();
2285
+ const { runId, actions } = state;
2033
2286
  const getClient = useWorkflowApiRef(api);
2034
2287
  const tracked = useWorkflowRun(runId, omitUndefined({
2035
2288
  api,
2036
2289
  intervalMs
2037
2290
  }));
2038
2291
  const { wake, cancel } = useRunControls(runId, getClient);
2292
+ const [startedHere, setStartedHere] = useState(false);
2039
2293
  const recovering = useRecoveredRun({
2040
2294
  workflow,
2041
2295
  key,
2042
2296
  enabled: recover,
2043
2297
  getClient,
2044
2298
  onFound: (found) => {
2045
- setRunId((current) => current ?? found);
2299
+ actions.setRunId((current) => current ?? found);
2046
2300
  },
2047
- onError: setStartError
2301
+ onError: actions.setStartError
2048
2302
  });
2049
2303
  const submit = useCallback(async (input) => {
2050
2304
  const client = getClient();
2051
- setStarting(true);
2052
- setStartError(void 0);
2053
- setRunId(void 0);
2054
- session.current?.gate.cancel();
2055
2305
  const current = createUploadSession(workflow);
2056
- session.current = current;
2306
+ setStartedHere(true);
2307
+ actions.begin(current);
2057
2308
  try {
2058
2309
  const options = omitUndefined({ key });
2059
- const started = await uploadFiles(client, input, setUpload, parallel, current);
2060
- setRunId(wait === void 0 ? await client.start(workflow, started, options) : (await client.startAndWait(workflow, started, {
2310
+ const started = await uploadFiles(client, input, coalesceUploadReports(actions.setUpload), parallel, current);
2311
+ actions.setRunId(wait === void 0 ? await client.start(workflow, started, options) : (await client.startAndWait(workflow, started, {
2061
2312
  ...options,
2062
2313
  wait
2063
2314
  })).runId);
2064
2315
  } catch (err) {
2065
- if (!current.gate.cancelled) setStartError(errorMessage(err));
2316
+ if (!current.gate.cancelled) actions.setStartError(errorMessage(err));
2066
2317
  } finally {
2067
- if (session.current === current) {
2068
- session.current = void 0;
2069
- setStarting(false);
2070
- setUpload(void 0);
2071
- }
2318
+ actions.end(current);
2072
2319
  }
2073
2320
  }, [
2074
2321
  workflow,
2075
2322
  key,
2076
2323
  wait,
2077
2324
  parallel,
2078
- getClient
2325
+ getClient,
2326
+ actions
2079
2327
  ]);
2080
- const reset = useCallback(() => {
2081
- session.current?.gate.cancel();
2082
- session.current = void 0;
2083
- setRunId(void 0);
2084
- setStartError(void 0);
2085
- setUpload(void 0);
2086
- }, []);
2087
- const { pauseUpload, resumeUpload } = useUploadPause(useCallback(() => session.current?.gate, []), setUpload);
2088
2328
  return {
2089
2329
  submit,
2090
2330
  submitForm: submit,
2091
- reset,
2331
+ reset: useCallback(() => {
2332
+ setStartedHere(false);
2333
+ actions.reset();
2334
+ }, [actions]),
2092
2335
  wake,
2093
2336
  cancel,
2094
- pauseUpload,
2095
- resumeUpload,
2337
+ pauseUpload: actions.pauseUpload,
2338
+ resumeUpload: actions.resumeUpload,
2096
2339
  run: tracked.run,
2097
- pending: recovering || starting || tracked.polling,
2098
- upload,
2099
- error: startError ?? tracked.error
2340
+ startedHere,
2341
+ pending: recovering || state.starting || tracked.polling,
2342
+ upload: state.upload,
2343
+ error: state.startError ?? tracked.error
2100
2344
  };
2101
2345
  }
2102
2346
  //#endregion
@@ -2226,7 +2470,7 @@ function useWorkflowRuns(workflow, opts = {}) {
2226
2470
  * earlier version of this hook took a `cut` callback and uploaded N parts into a
2227
2471
  * "group" that a separate call had to seal; it worked, and every piece of it was
2228
2472
  * something the caller had to get right. The whole of that is replaced by the store
2229
- * publishing `size` as bytes land — which `readUpload` already clamped to — so the
2473
+ * publishing `size` as bytes land — which `stepReadUpload` already clamped to — so the
2230
2474
  * run does exactly what it does over a finished file and simply waits for windows to
2231
2475
  * become present.
2232
2476
  *
@@ -2304,25 +2548,21 @@ function useWorkflowRuns(workflow, opts = {}) {
2304
2548
  */
2305
2549
  function useWorkflowStream(workflow, opts = {}) {
2306
2550
  const { api, key, intervalMs, parallel } = opts;
2307
- const [runId, setRunId] = useState(void 0);
2308
- const [starting, setStarting] = useState(false);
2309
- const [startError, setStartError] = useState(void 0);
2310
- const [upload, setUpload] = useState(void 0);
2311
- const gateRef = useRef(void 0);
2551
+ const state = useSubmissionState();
2552
+ const { runId, actions } = state;
2312
2553
  const getClient = useWorkflowApiRef(api);
2313
2554
  const tracked = useWorkflowRun(runId, omitUndefined({
2314
2555
  api,
2315
2556
  intervalMs
2316
2557
  }));
2317
2558
  const { wake, cancel } = useRunControls(runId, getClient);
2559
+ const [startedHere, setStartedHere] = useState(false);
2318
2560
  const submit = useCallback(async (input) => {
2319
2561
  const client = getClient();
2320
- setStarting(true);
2321
- setStartError(void 0);
2322
- setRunId(void 0);
2323
- gateRef.current?.cancel();
2324
- const gate = createUploadGate();
2325
- gateRef.current = gate;
2562
+ const current = { gate: createUploadGate() };
2563
+ const { gate } = current;
2564
+ setStartedHere(true);
2565
+ actions.begin(current);
2326
2566
  let started;
2327
2567
  try {
2328
2568
  const id = randomUploadId();
@@ -2335,7 +2575,7 @@ function useWorkflowStream(workflow, opts = {}) {
2335
2575
  });
2336
2576
  started = begun.runId;
2337
2577
  const chosen = begun.file;
2338
- setRunId(started);
2578
+ actions.setRunId(started);
2339
2579
  if (!chosen) return;
2340
2580
  await streamFile({
2341
2581
  client,
@@ -2343,45 +2583,38 @@ function useWorkflowStream(workflow, opts = {}) {
2343
2583
  id,
2344
2584
  file: chosen,
2345
2585
  parallel,
2346
- report: setUpload
2586
+ report: coalesceUploadReports(actions.setUpload)
2347
2587
  });
2348
2588
  await client.wake(started).catch(() => void 0);
2349
2589
  } catch (err) {
2350
- if (!gate.cancelled) setStartError(errorMessage(err));
2590
+ if (!gate.cancelled) actions.setStartError(errorMessage(err));
2351
2591
  if (started) await client.cancel(started).catch(() => void 0);
2352
2592
  } finally {
2353
- if (gateRef.current === gate) {
2354
- gateRef.current = void 0;
2355
- setStarting(false);
2356
- setUpload(void 0);
2357
- }
2593
+ actions.end(current);
2358
2594
  }
2359
2595
  }, [
2360
2596
  workflow,
2361
2597
  key,
2362
2598
  parallel,
2363
- getClient
2599
+ getClient,
2600
+ actions
2364
2601
  ]);
2365
- const reset = useCallback(() => {
2366
- gateRef.current?.cancel();
2367
- gateRef.current = void 0;
2368
- setRunId(void 0);
2369
- setStartError(void 0);
2370
- setUpload(void 0);
2371
- }, []);
2372
- const { pauseUpload, resumeUpload } = useUploadPause(useCallback(() => gateRef.current, []), setUpload);
2373
2602
  return {
2374
2603
  submit,
2375
2604
  submitForm: submit,
2376
- reset,
2605
+ reset: useCallback(() => {
2606
+ setStartedHere(false);
2607
+ actions.reset();
2608
+ }, [actions]),
2377
2609
  wake,
2378
2610
  cancel,
2379
- pauseUpload,
2380
- resumeUpload,
2611
+ pauseUpload: actions.pauseUpload,
2612
+ resumeUpload: actions.resumeUpload,
2381
2613
  run: tracked.run,
2382
- pending: starting || tracked.polling,
2383
- upload,
2384
- error: startError ?? tracked.error
2614
+ startedHere,
2615
+ pending: state.starting || tracked.polling,
2616
+ upload: state.upload,
2617
+ error: state.startError ?? tracked.error
2385
2618
  };
2386
2619
  }
2387
2620
  /**
@@ -2503,4 +2736,4 @@ const WORKFLOW_STATUS_LABELS = {
2503
2736
  cancelled: "Cancelled"
2504
2737
  };
2505
2738
  //#endregion
2506
- export { AutoScroll, Button, ChatView, CheckboxField, ConsoleShell, Controls, Field, FileField, Form, Markdown, MessageList, NumberField, SelectField, SidebarLayout, StartScreen, SubmitButton, TextAreaField, TextField, ToolCallRow, UploadProgressBar, WORKFLOW_STATUS_LABELS, WorkflowFields, WorkflowProgress, client, createSessionCore, createWorkflowApi, fetchClientConfig, isTerminal, page, useAgentState, useConversation, useDownloadUrl, useEvent, useRunKey, useSession, useSessionSelector, useTheme, useToolCallStart, useToolResult, useUserTranscript, useWorkflowProgress, useWorkflowRun, useWorkflowRuns, useWorkflowStream, useWorkflowSubmit, useWorkflows };
2739
+ export { AGENT_STATE_LABELS, AutoScroll, BulletList, Button, ChatView, CheckboxField, ConsoleShell, Controls, Facts, Field, FileField, Form, Markdown, MessageList, NumberField, SelectField, SessionErrorBanner, SidebarLayout, StartScreen, SubmitButton, TextAreaField, TextField, ToolCallRow, UploadProgressBar, WORKFLOW_STATUS_LABELS, WorkflowFields, WorkflowProgress, createBrowserSession, createWorkflowApi, fetchClientConfig, isTerminal, mountClient, mountPage, useAgentState, useConversation, useDownloadUrl, useEvent, useRunKey, useSession, useSessionActions, useSessionError, useSessionSelector, useSessionStatus, useTheme, useToolCallStart, useToolResult, useUserTranscript, useWorkflowProgress, useWorkflowRun, useWorkflowRuns, useWorkflowStream, useWorkflowSubmit, useWorkflows };