@alexkroman1/aai-ui 0.12.2 → 0.13.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 (215) hide show
  1. package/README.md +257 -0
  2. package/dist/_colors-CpZO-88A.js +78 -0
  3. package/dist/_module-url-C13kAJ87.js +15 -0
  4. package/dist/_page-shell.d.ts +11 -0
  5. package/dist/_recover-run.d.ts +79 -0
  6. package/dist/_repeat-until.d.ts +30 -0
  7. package/dist/_run-controls.d.ts +33 -0
  8. package/dist/_submission-state.d.ts +92 -0
  9. package/dist/_tap-to-talk-state.d.ts +88 -0
  10. package/dist/_utils-Mi-SCFA1.js +47 -0
  11. package/dist/_utils.d.ts +26 -0
  12. package/dist/_web-storage.d.ts +43 -0
  13. package/dist/_workflow-api-ref.d.ts +37 -0
  14. package/dist/_workflow-files.d.ts +34 -0
  15. package/dist/_workflow-test-defs.d.ts +38 -0
  16. package/dist/aai-logo-D8pMVn9_.js +85 -0
  17. package/dist/agent-state-labels.d.ts +65 -0
  18. package/dist/agent-state.d.ts +108 -0
  19. package/dist/audio/capture.d.ts +44 -0
  20. package/dist/audio/index.d.ts +18 -0
  21. package/dist/audio/index.js +431 -0
  22. package/dist/audio/preconnect.d.ts +87 -0
  23. package/dist/audio/voice-io.d.ts +112 -0
  24. package/dist/build-default-client.d.ts +1 -0
  25. package/dist/chat-view-BCbI7gD4.js +396 -0
  26. package/dist/client-config-C7fQrioM.js +139 -0
  27. package/dist/client-config.d.ts +119 -0
  28. package/dist/client-dir.d.ts +44 -0
  29. package/dist/client-dir.js +58 -0
  30. package/dist/client-identity.d.ts +131 -0
  31. package/dist/client-tool.d.ts +51 -0
  32. package/dist/components/_colors.d.ts +73 -0
  33. package/dist/components/_form-readiness.d.ts +56 -0
  34. package/dist/components/_form-values.d.ts +19 -0
  35. package/dist/components/aai-logo.d.ts +12 -0
  36. package/dist/components/audio-result.d.ts +94 -0
  37. package/dist/components/auto-scroll.d.ts +74 -0
  38. package/dist/components/bullet-list.d.ts +74 -0
  39. package/dist/components/button.d.ts +58 -0
  40. package/dist/components/button.js +85 -0
  41. package/dist/components/chat-view.d.ts +35 -0
  42. package/dist/components/chat-view.js +3 -0
  43. package/dist/components/console-shell.d.ts +85 -0
  44. package/dist/components/controls.d.ts +33 -0
  45. package/dist/components/controls.js +49 -0
  46. package/dist/components/conversation-view.d.ts +130 -0
  47. package/dist/components/eyebrow.d.ts +12 -0
  48. package/dist/components/facts.d.ts +81 -0
  49. package/dist/components/form-fields.d.ts +142 -0
  50. package/dist/components/form-types.d.ts +73 -0
  51. package/dist/components/form.d.ts +96 -0
  52. package/dist/components/markdown.d.ts +57 -0
  53. package/dist/components/message-list.d.ts +48 -0
  54. package/dist/components/message-list.js +3 -0
  55. package/dist/components/session-controls.d.ts +125 -0
  56. package/dist/components/session-error-banner.d.ts +69 -0
  57. package/dist/components/session-state-dot.d.ts +111 -0
  58. package/dist/components/sidebar-layout.d.ts +42 -0
  59. package/dist/components/sidebar-layout.js +56 -0
  60. package/dist/components/start-screen.d.ts +37 -0
  61. package/dist/components/start-screen.js +71 -0
  62. package/dist/components/tool-call-block.d.ts +28 -0
  63. package/dist/components/tool-call-block.js +3 -0
  64. package/dist/components/tool-call-row.d.ts +75 -0
  65. package/dist/components/tool-config-context.d.ts +22 -0
  66. package/dist/components/upload-progress.d.ts +76 -0
  67. package/dist/components/url-chips.d.ts +29 -0
  68. package/dist/components/workflow-fields.d.ts +46 -0
  69. package/dist/components/workflow-pending-note.d.ts +81 -0
  70. package/dist/components/workflow-progress.d.ts +81 -0
  71. package/dist/components/workflow-run-error.d.ts +48 -0
  72. package/dist/components/workflow-run-panel.d.ts +79 -0
  73. package/dist/context.d.ts +288 -0
  74. package/dist/context.js +382 -0
  75. package/dist/conversation-log.d.ts +88 -0
  76. package/dist/default-client/assets/_module-url-BX0RuRU2.js +1 -0
  77. package/dist/default-client/assets/audio-cGFGID-2.js +1 -0
  78. package/dist/default-client/assets/capture-processor-BUABoo3_.js +90 -0
  79. package/dist/default-client/assets/index-8ihb1Urm.css +2 -0
  80. package/dist/default-client/assets/index-BQURVARl.js +126 -0
  81. package/dist/default-client/assets/playback-processor-ABOa11j0.js +314 -0
  82. package/dist/default-client/favicon.ico +0 -0
  83. package/dist/default-client/index.html +15 -0
  84. package/dist/default-client.d.ts +23 -0
  85. package/dist/define-client.d.ts +162 -26
  86. package/dist/define-client.js +201 -55
  87. package/dist/eyebrow-UfmSz9yy.js +27 -0
  88. package/dist/hooks-DN4gHYsj.js +182 -0
  89. package/dist/hooks.d.ts +172 -0
  90. package/dist/hooks.js +3 -0
  91. package/dist/hooks.test-d.d.ts +14 -0
  92. package/dist/inbox-protocol.d.ts +128 -0
  93. package/dist/inbox.d.ts +132 -0
  94. package/dist/index.d.ts +129 -32
  95. package/dist/index.js +5503 -18
  96. package/dist/internal.d.ts +43 -0
  97. package/dist/internal.js +8 -0
  98. package/dist/linked-client.d.ts +70 -0
  99. package/dist/message-list-DHZcLhRA.js +682 -0
  100. package/dist/notice-player.d.ts +70 -0
  101. package/dist/page.d.ts +114 -0
  102. package/dist/phone.d.ts +50 -0
  103. package/dist/route-fetch.d.ts +79 -0
  104. package/dist/session/audio-effects.d.ts +31 -0
  105. package/dist/session/audio-setup.d.ts +57 -0
  106. package/dist/session/audio-state.d.ts +157 -0
  107. package/dist/session/browser-session.d.ts +30 -0
  108. package/dist/session/close.d.ts +26 -0
  109. package/dist/session/dial.d.ts +65 -0
  110. package/dist/session/handshake.d.ts +60 -0
  111. package/dist/session/index.d.ts +17 -0
  112. package/dist/session/index.js +2 -0
  113. package/dist/session/messages.d.ts +63 -0
  114. package/dist/session/mic.d.ts +64 -0
  115. package/dist/session/preconnect.d.ts +48 -0
  116. package/dist/session/reconnect.d.ts +25 -0
  117. package/dist/session/resume-store.d.ts +57 -0
  118. package/dist/session/state.d.ts +146 -0
  119. package/dist/session/ticket.d.ts +44 -0
  120. package/dist/session/types.d.ts +476 -0
  121. package/dist/session/url.d.ts +17 -0
  122. package/dist/session/user-turn.d.ts +55 -0
  123. package/dist/session-CE0Ipsb3.js +2129 -0
  124. package/dist/stored-value.d.ts +103 -0
  125. package/dist/tool-call-block-uXtMOUWN.js +204 -0
  126. package/dist/tool-config-context-Es4YUzV2.js +19 -0
  127. package/dist/types.d.ts +272 -34
  128. package/dist/types.js +35 -4
  129. package/dist/upload/files.d.ts +83 -0
  130. package/dist/upload/index.d.ts +15 -0
  131. package/dist/upload/pause.d.ts +31 -0
  132. package/dist/upload/recall.d.ts +67 -0
  133. package/dist/upload/report.d.ts +26 -0
  134. package/dist/upload/session.d.ts +99 -0
  135. package/dist/url-chips-oyy_Eiqh.js +239 -0
  136. package/dist/use-client-runs.d.ts +87 -0
  137. package/dist/use-conversation-log.d.ts +86 -0
  138. package/dist/use-conversation.d.ts +131 -0
  139. package/dist/use-copy.d.ts +74 -0
  140. package/dist/use-download-url.d.ts +84 -0
  141. package/dist/use-flash.d.ts +61 -0
  142. package/dist/use-inbox.d.ts +101 -0
  143. package/dist/use-push-to-talk.d.ts +105 -0
  144. package/dist/use-route-mutation.d.ts +113 -0
  145. package/dist/use-route.d.ts +82 -0
  146. package/dist/use-run-key.d.ts +133 -0
  147. package/dist/use-session-controls.d.ts +69 -0
  148. package/dist/use-session-id.d.ts +57 -0
  149. package/dist/use-tap-to-talk.d.ts +138 -0
  150. package/dist/use-user-transcript-uyHhzy4d.js +73 -0
  151. package/dist/use-user-transcript.d.ts +75 -0
  152. package/dist/use-workflow-form.d.ts +285 -0
  153. package/dist/use-workflow-progress.d.ts +137 -0
  154. package/dist/use-workflow-run-DdhWmbx-.js +697 -0
  155. package/dist/use-workflow-run.d.ts +100 -0
  156. package/dist/use-workflow-runs.d.ts +71 -0
  157. package/dist/use-workflow-stream.d.ts +154 -0
  158. package/dist/use-workflows.d.ts +77 -0
  159. package/dist/workflow-client.d.ts +136 -0
  160. package/dist/workflow-def-types.d.ts +31 -0
  161. package/dist/workflow-events.d.ts +39 -0
  162. package/dist/workflow-field-kind.d.ts +95 -0
  163. package/dist/workflow-status-labels.d.ts +35 -0
  164. package/dist/worklets/_module-url.d.ts +10 -0
  165. package/dist/worklets/_playback-bench-harness.d.ts +181 -0
  166. package/dist/worklets/_playback-bench-host.d.ts +63 -0
  167. package/dist/worklets/_playback-bench-page.d.ts +65 -0
  168. package/dist/worklets/_tts-trace-harness.d.ts +142 -0
  169. package/dist/worklets/_worklet-test-utils.d.ts +27 -0
  170. package/dist/worklets/capture-processor.d.ts +4 -2
  171. package/dist/worklets/capture-processor.js +73 -33
  172. package/dist/worklets/playback-processor.d.ts +4 -2
  173. package/dist/worklets/playback-processor.js +258 -42
  174. package/package.json +58 -31
  175. package/styles.css +152 -30
  176. package/dist/_components/app.d.ts +0 -23
  177. package/dist/_components/app.js +0 -41
  178. package/dist/_components/button.d.ts +0 -50
  179. package/dist/_components/button.js +0 -46
  180. package/dist/_components/chat-view.d.ts +0 -29
  181. package/dist/_components/chat-view.js +0 -61
  182. package/dist/_components/controls.d.ts +0 -18
  183. package/dist/_components/controls.js +0 -37
  184. package/dist/_components/error-banner.d.ts +0 -21
  185. package/dist/_components/error-banner.js +0 -27
  186. package/dist/_components/message-bubble.d.ts +0 -23
  187. package/dist/_components/message-bubble.js +0 -35
  188. package/dist/_components/message-list.d.ts +0 -21
  189. package/dist/_components/message-list.js +0 -76
  190. package/dist/_components/sidebar-layout.d.ts +0 -21
  191. package/dist/_components/sidebar-layout.js +0 -36
  192. package/dist/_components/start-screen.d.ts +0 -26
  193. package/dist/_components/start-screen.js +0 -51
  194. package/dist/_components/state-indicator.d.ts +0 -29
  195. package/dist/_components/state-indicator.js +0 -37
  196. package/dist/_components/thinking-indicator.d.ts +0 -16
  197. package/dist/_components/thinking-indicator.js +0 -34
  198. package/dist/_components/tool-call-block.d.ts +0 -27
  199. package/dist/_components/tool-call-block.js +0 -118
  200. package/dist/_components/tool-icons.d.ts +0 -17
  201. package/dist/_components/tool-icons.js +0 -128
  202. package/dist/_components/transcript.d.ts +0 -25
  203. package/dist/_components/transcript.js +0 -35
  204. package/dist/audio.d.ts +0 -45
  205. package/dist/audio.js +0 -121
  206. package/dist/client-context.d.ts +0 -33
  207. package/dist/client-context.js +0 -15
  208. package/dist/client-handler.d.ts +0 -43
  209. package/dist/session-CWB7vmqz.js +0 -429
  210. package/dist/session.d.ts +0 -69
  211. package/dist/session.js +0 -2
  212. package/dist/signals.d.ts +0 -120
  213. package/dist/signals.js +0 -161
  214. package/dist/tsdown.config.d.ts +0 -2
  215. package/dist/types.test-d.d.ts +0 -7
@@ -0,0 +1,105 @@
1
+ /**
2
+ * `usePushToTalk` — a hold-to-speak button, for an agent that declares
3
+ * `turnTaking: { detection: "manual" }`.
4
+ *
5
+ * The three session edges underneath (`session.userTurn`'s `start`, `commit`
6
+ * and `clear`) are one line each; what a button needs on top of them is
7
+ * where hand-written versions go wrong, and every one of the four is a turn
8
+ * that is left OPEN — the microphone live and nothing ever answered:
9
+ *
10
+ * - **The pointer leaves.** A `mouseup` outside the button never reaches it.
11
+ * The handlers here capture the pointer on the way down, so the release
12
+ * arrives wherever it happens; an `onPointerCancel` (a scroll gesture, a
13
+ * system dialog) DISCARDS the turn rather than answering half a sentence.
14
+ * - **The key repeats.** Holding a key fires `keydown` over and over, and each
15
+ * one would re-open the turn. Only the first counts.
16
+ * - **The window loses focus mid-hold.** The `keyup` goes to another window
17
+ * and is never delivered, so the turn is committed on `blur` — what the
18
+ * caller would have done had the release been seen.
19
+ * - **The component unmounts mid-hold.** The turn is discarded on the way out.
20
+ *
21
+ * And one that is a nuisance rather than a stuck turn: the hold key is ignored
22
+ * while focus is in a text field, where Space is a character.
23
+ */
24
+ /** Options for {@link usePushToTalk}. */
25
+ export type UsePushToTalkOptions = {
26
+ /**
27
+ * The keyboard key that holds the turn open, as a `KeyboardEvent.code` —
28
+ * `"Space"` by default, so the whole page is a walkie-talkie. `false` turns
29
+ * the global key off; the button itself still answers Space and Enter while
30
+ * it has focus.
31
+ */
32
+ holdKey?: string | false;
33
+ };
34
+ /**
35
+ * What {@link usePushToTalk} returns.
36
+ *
37
+ * @public
38
+ */
39
+ export type UsePushToTalkResult = {
40
+ /** Whether a turn is being held open right now — the button is DOWN. */
41
+ talking: boolean;
42
+ /**
43
+ * Whether pressing would do anything: the call is live. False before Start
44
+ * and while paused, which is when a button should render disabled.
45
+ */
46
+ ready: boolean;
47
+ /** Open a turn. Interrupts the agent if it is speaking. Ignored while held. */
48
+ press: () => void;
49
+ /** Close the turn and have the agent answer it. Ignored unless held. */
50
+ release: () => void;
51
+ /** Close the turn and discard it — nothing is answered. Ignored unless held. */
52
+ cancel: () => void;
53
+ /**
54
+ * Spread onto a `<button>`: pointer capture, the keyboard pair, and
55
+ * `aria-pressed`. The handlers are the whole contract — style it however you
56
+ * like.
57
+ */
58
+ buttonProps: {
59
+ onPointerDown: (event: {
60
+ currentTarget: Element;
61
+ pointerId: number;
62
+ }) => void;
63
+ onPointerUp: () => void;
64
+ onPointerCancel: () => void;
65
+ onKeyDown: (event: {
66
+ key: string;
67
+ repeat: boolean;
68
+ preventDefault(): void;
69
+ }) => void;
70
+ onKeyUp: (event: {
71
+ key: string;
72
+ preventDefault(): void;
73
+ }) => void;
74
+ onContextMenu: (event: {
75
+ preventDefault(): void;
76
+ }) => void;
77
+ disabled: boolean;
78
+ "aria-pressed": boolean;
79
+ };
80
+ };
81
+ /**
82
+ * Hold-to-speak over the session's push-to-talk methods, with the four ways a
83
+ * turn gets stuck open handled — see this module's doc.
84
+ *
85
+ * Must be used inside the provider `mountClient()` installs, against an agent
86
+ * declaring `turnTaking: { detection: "manual" }`; any other agent ignores the commands and
87
+ * its server says so once.
88
+ *
89
+ * @example A hold-to-talk button
90
+ * ```tsx
91
+ * import { usePushToTalk } from "@alexkroman1/aai-ui";
92
+ *
93
+ * function TalkButton() {
94
+ * const { talking, buttonProps } = usePushToTalk();
95
+ * return (
96
+ * <button type="button" {...buttonProps}>
97
+ * {talking ? "Listening… release to send" : "Hold to talk (or hold Space)"}
98
+ * </button>
99
+ * );
100
+ * }
101
+ * ```
102
+ *
103
+ * @public
104
+ */
105
+ export declare function usePushToTalk(options?: UsePushToTalkOptions): UsePushToTalkResult;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * `useRouteMutation` — WRITE to one of the agent's own `/api` routes from a
3
+ * component: the writing half of `useRoute`, over `routeFetch`.
4
+ *
5
+ * Every save, delete and connect button on a device-twin page wrote the same
6
+ * nine lines: set a busy flag, call the route, clear the error on success or
7
+ * set it from the thrown message on failure, clear the flag, and re-read the
8
+ * list the write changed. This is those lines once, plus the three things the
9
+ * copies got wrong:
10
+ *
11
+ * - **A write settling after unmount sets no state and calls nothing** — the
12
+ * page navigated away, and its `reload` would read into a dead component.
13
+ * - **An OLDER write settling after a newer one does not overwrite its
14
+ * outcome** — each write is numbered, and the error shown is that of the
15
+ * highest-numbered write to have settled (`useRoute`'s stale-read rule, for
16
+ * writes, which cannot be dropped the way a read can).
17
+ * - **`busy` names WHICH write is in flight**, not just that one is — a row of
18
+ * buttons disables the one that was pressed, where a boolean greyed them all.
19
+ *
20
+ * `run` never rejects: a failure is `error` (the route's own `{ error }`
21
+ * sentence when it answered one) and `run` resolves `undefined`.
22
+ *
23
+ * @module
24
+ */
25
+ import { type RouteMethod } from "./route-fetch.ts";
26
+ /**
27
+ * Options for {@link useRouteMutation}.
28
+ *
29
+ * @public
30
+ */
31
+ export type UseRouteMutationOptions = {
32
+ /**
33
+ * The `?client=` to send. Default: the session's client
34
+ * (`session.identity.clientId()`, read per write) inside `mountClient()`,
35
+ * none on a page with no session.
36
+ */
37
+ client?: string | undefined;
38
+ /**
39
+ * Called after every write settles, success or failure — a `useRoute`'s
40
+ * `reload`, so the list shows what the write did. Not called after unmount.
41
+ * Read at settle time, so an inline arrow is fine.
42
+ */
43
+ onSettled?: (() => void) | undefined;
44
+ };
45
+ /**
46
+ * Options for one {@link UseRouteMutationResult.run} call.
47
+ *
48
+ * @public
49
+ */
50
+ export type RouteMutationRunOptions = {
51
+ /**
52
+ * What {@link UseRouteMutationResult.busy} names while this write is in
53
+ * flight — the field being saved, the row being deleted. Default:
54
+ * `"<METHOD> <path>"`.
55
+ */
56
+ key?: string | undefined;
57
+ };
58
+ /**
59
+ * What {@link useRouteMutation} returns.
60
+ *
61
+ * @public
62
+ */
63
+ export type UseRouteMutationResult = {
64
+ /**
65
+ * Send one write: `routeFetch(method, path, body, { client })`. Resolves the
66
+ * route's answer, or `undefined` when it failed (the reason is in `error`) —
67
+ * never rejects.
68
+ */
69
+ run: <T = unknown>(method: RouteMethod, path: string, body?: unknown, options?: RouteMutationRunOptions) => Promise<T | undefined>;
70
+ /**
71
+ * The key of the newest write still in flight (see
72
+ * {@link RouteMutationRunOptions.key}), or `undefined` when none is —
73
+ * `busy === "email"` for one field, `busy !== undefined` for any.
74
+ */
75
+ busy: string | undefined;
76
+ /** Why the newest settled write failed — the route's `{ error }` when it said — or `undefined`. */
77
+ error: string | undefined;
78
+ /** Forget `error` — when the reader dismissed it, or edited the field again. */
79
+ clearError: () => void;
80
+ };
81
+ /**
82
+ * Write to the agent's own routes, with a busy key, the last error and a
83
+ * re-read after — see this module's doc.
84
+ *
85
+ * @example A profile field that saves, then re-reads
86
+ * ```tsx
87
+ * import { useRoute, useRouteMutation } from "@alexkroman1/aai-ui";
88
+ *
89
+ * function Name() {
90
+ * const { data, reload } = useRoute<{ name: string }>("/profile");
91
+ * const { run, busy, error } = useRouteMutation({ onSettled: reload });
92
+ * return (
93
+ * <form
94
+ * onSubmit={(e) => {
95
+ * e.preventDefault();
96
+ * const name = new FormData(e.currentTarget).get("name");
97
+ * void run("PUT", "/profile", { name }, { key: "name" });
98
+ * }}
99
+ * >
100
+ * <input name="name" defaultValue={data?.name} disabled={busy === "name"} />
101
+ * {error && <p role="alert">{error}</p>}
102
+ * </form>
103
+ * );
104
+ * }
105
+ * ```
106
+ *
107
+ * @param options - `client` and `onSettled`; see {@link UseRouteMutationOptions}.
108
+ * @returns `run`, the busy key, the last error and `clearError`; see
109
+ * {@link UseRouteMutationResult}.
110
+ *
111
+ * @public
112
+ */
113
+ export declare function useRouteMutation(options?: UseRouteMutationOptions): UseRouteMutationResult;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * `useRoute` — read one of the agent's own `/api` routes into a component, and
3
+ * again on demand or on a poll. The reading half of `routeFetch`.
4
+ *
5
+ * What a hand-written `useEffect` + `fetch` got wrong here: a slow earlier read
6
+ * overwriting a newer one (each read carries an epoch, and a stale one is
7
+ * dropped — `useWorkflowRuns`' rule), a read landing after unmount, and a
8
+ * failure rendered as an empty answer. `data` is KEPT through a failed re-read,
9
+ * so a poll that hiccups does not blank the panel; `error` says it is stale.
10
+ *
11
+ * @module
12
+ */
13
+ /**
14
+ * Options for {@link useRoute}.
15
+ *
16
+ * @public
17
+ */
18
+ export type UseRouteOptions = {
19
+ /** Read again every this many ms, while mounted. Default: never. */
20
+ pollMs?: number | undefined;
21
+ /**
22
+ * The `?client=` to send. Default: the session's client
23
+ * (`session.identity.clientId()`, read per request) inside `mountClient()`,
24
+ * none on a page with no session.
25
+ */
26
+ client?: string | undefined;
27
+ };
28
+ /**
29
+ * What {@link useRoute} returns.
30
+ *
31
+ * @public
32
+ */
33
+ export type UseRouteResult<T> = {
34
+ /** The last answer, kept through a failed re-read. `undefined` until the first. */
35
+ data: T | undefined;
36
+ /** Why the LAST read failed — the route's `{ error }` when it said — or `undefined`. */
37
+ error: string | undefined;
38
+ /** Whether a read is in flight. */
39
+ loading: boolean;
40
+ /** Read again now — after a write, say. */
41
+ reload: () => void;
42
+ };
43
+ /**
44
+ * `GET` one of the agent's own routes, on mount, on `reload()` and every
45
+ * `pollMs` — see this module's doc.
46
+ *
47
+ * @example A list that re-reads after each delete
48
+ * ```tsx
49
+ * import { routeFetch, useClientId, useRoute } from "@alexkroman1/aai-ui";
50
+ *
51
+ * type Memory = { id: string; text: string };
52
+ *
53
+ * function Memories() {
54
+ * const client = useClientId();
55
+ * const { data, error, reload } = useRoute<{ memories: Memory[] }>("/memories");
56
+ * if (error) return <p role="alert">{error}</p>;
57
+ * return (
58
+ * <ul>
59
+ * {data?.memories.map((m) => (
60
+ * <li key={m.id}>
61
+ * {m.text}
62
+ * <button
63
+ * type="button"
64
+ * onClick={() => void routeFetch("DELETE", `/memories/${m.id}`, undefined, { client }).then(reload)}
65
+ * >
66
+ * Forget
67
+ * </button>
68
+ * </li>
69
+ * ))}
70
+ * </ul>
71
+ * );
72
+ * }
73
+ * ```
74
+ *
75
+ * @typeParam T - The shape the route answers. Not checked.
76
+ * @param path - The route's path as declared, without `/api`. `null` reads nothing.
77
+ * @param options - `pollMs` and `client`; see {@link UseRouteOptions}.
78
+ * @returns The answer, the last error, and `reload`; see {@link UseRouteResult}.
79
+ *
80
+ * @public
81
+ */
82
+ export declare function useRoute<T = unknown>(path: string | null, options?: UseRouteOptions): UseRouteResult<T>;
@@ -0,0 +1,133 @@
1
+ /**
2
+ * The handle a page keeps on the runs it started, across a reload.
3
+ *
4
+ * `useWorkflowSubmit` is what makes a run survivable — the run id is that
5
+ * hook's own state, so a refresh loses it while the run carries on — and the
6
+ * `key` is what it looks the run up BY, because it is a lookup CAPABILITY:
7
+ * there is no per-user filtering behind `find`, so the key IS the scoping
8
+ * mechanism. Choosing one is easy to get wrong in three separate ways, and six
9
+ * shipped templates had each written the same twenty lines to get it right.
10
+ * This is those lines.
11
+ *
12
+ * **`useWorkflowSubmit` now mints one for itself** ({@link useDefaultRunKey}),
13
+ * so a page resumes its own run across a reload with nothing written at the
14
+ * call site — six of six page templates passed `useRunKey()` and
15
+ * `recover: true`, which is a default in the wrong place. The hook stays
16
+ * PUBLIC for the page that wants to choose: an app with accounts passes the
17
+ * ACCOUNT's own id instead, and a run then follows the person to a new device,
18
+ * which is a promise only a login can keep; a page whose run outlives the tab
19
+ * passes `useRunKey({ storage: "local" })`.
20
+ *
21
+ * ## Three properties, and the rejected alternatives are why each one matters
22
+ *
23
+ * - **Opaque** — `crypto.randomUUID()`, never derived from what was submitted.
24
+ * A key derived from the input collides the moment two people submit the same
25
+ * thing, and they then recover each other's runs; it also carries what they
26
+ * typed into a lookup token the platform deliberately stopped logging.
27
+ * - **Short.** A `randomUUID` is 36 characters, well inside the 256 that
28
+ * `POST /workflows/runs` allows a key.
29
+ * - **Minted once per load and written back for the next one**, which is the
30
+ * whole mechanism: the load that presses the button records the key with the
31
+ * run, and the load after it finds the run by producing the same key.
32
+ *
33
+ * Storage rather than the page's own URL, for all of them. A `?key=` parameter
34
+ * survives more (a new tab, a bookmark, a shared link) and that is the problem:
35
+ * a URL is pasted into chats, copied into referrers and kept in history, and
36
+ * what a leaked one buys is somebody else's work — reading it, and `cancel()` on
37
+ * it. An app with accounts should pass the ACCOUNT's own id here instead, and
38
+ * then a run follows the person to a new device, which is a promise only a login
39
+ * can keep.
40
+ *
41
+ * ## The storage is the caller's decision, and it is not a detail
42
+ *
43
+ * `"session"` (the default) dies with the tab, which covers exactly the
44
+ * interruption most pages have — a reload, a same-tab navigation, a crashed tab
45
+ * — and is the same lifetime as this package's other two stores, the session
46
+ * resume id (`session/resume-store.ts`) and the upload recall
47
+ * (`upload/recall.ts`), so both halves of a reload make the same promise.
48
+ *
49
+ * `"local"` is for a run that outlives all of that BY DESIGN — one that sleeps
50
+ * between digests and may live a month, where closing the browser on Tuesday and
51
+ * coming back on Friday to press Stop is the ordinary case rather than an edge
52
+ * one, and a tab-scoped key would answer that with an empty form beside a run
53
+ * still posting somewhere. It is as far as a key can go without a login, and no
54
+ * further. `podcast-digest-workflow` is that template, and the reason this hook is still
55
+ * called by name anywhere; the other five take the tab-scoped default the
56
+ * submit hook mints for them.
57
+ *
58
+ * ## Anything ELSE a page stores back must be VALIDATED on read
59
+ *
60
+ * This key needs no validation, and it is worth saying why, because it is the
61
+ * exception: any string is a legal key, so a value from storage can only fail to
62
+ * match a run. A page that remembers something more — which MODE submitted, say
63
+ * — is remembering a value it will turn into a name, and storage hands back a
64
+ * string some earlier version of that page wrote: a renamed mode, a hand-edited
65
+ * value, a slot another app on the origin happens to share. Unchecked, that
66
+ * starts a run called `undefined` and answers a 400 nobody typed. Check it
67
+ * against the page's own list on the way out (`recalledMode` in
68
+ * `transcription-workflow/recover.ts` is the worked example) — the recall is the
69
+ * page's, the validation is not optional.
70
+ *
71
+ * ## The slot is keyed by the page's own URL
72
+ *
73
+ * Every deployed agent is served from one origin at `/:slug/`, so a fixed name
74
+ * would have two agents scaffolded from the same template recover each other's
75
+ * runs. The key is the page's own directory — resolved through `"./"`, which
76
+ * drops the query and the hash, since a reload carrying `?foo` or `#bar` has to
77
+ * find the same key. Same call `session/resume-store.ts` makes, for the same
78
+ * reason.
79
+ *
80
+ * One key per PAGE is right even for a page driving several workflows: `find` is
81
+ * scoped by workflow as well as by key, so three hooks sharing one key recover
82
+ * three separate runs. `transcription-workflow` is that page.
83
+ *
84
+ * Every access is guarded. Storage THROWS outright in some contexts (Safari
85
+ * private mode, an iframe blocked by policy) and is ABSENT in others (any
86
+ * server-side render), and a page that cannot remember its key must degrade to
87
+ * the behaviour it would have had anyway — one run per load — rather than
88
+ * failing to render.
89
+ */
90
+ /**
91
+ * The key `useWorkflowSubmit` uses when the page named none.
92
+ *
93
+ * Two things it does that a plain `useRunKey()` at the call site cannot, and
94
+ * both are about a page that DID name one:
95
+ *
96
+ * - **It mints nothing when the caller has a key.** Minting writes to storage,
97
+ * so an unconditional `useRunKey()` inside the hook would leave a slot behind
98
+ * on every page that passes an account id and never reads it back.
99
+ * - **It stays reactive to the caller's key.** A key that arrives late — an
100
+ * account id resolved after a login — must reach the lookup, which re-asks on
101
+ * a changed key by design; freezing it into `useState` would pin the page to
102
+ * whatever it held on its first render.
103
+ *
104
+ * The minted half is still frozen for the component's life, which is what
105
+ * `useRunKey` freezes it for: a fresh key per render would record every run
106
+ * under a name the next load cannot produce.
107
+ *
108
+ * @param explicit - The caller's own key, or undefined for a page with none.
109
+ * @returns The key to record runs under and look them up by.
110
+ *
111
+ * @internal
112
+ */
113
+ export declare function useDefaultRunKey(explicit: string | undefined): string;
114
+ /**
115
+ * A lookup key for `useWorkflowSubmit({ key })`, stable across reloads.
116
+ *
117
+ * @param options - See the module doc for the whole argument. The storage kind
118
+ * is read once, when the key is minted: a value that changed afterwards would
119
+ * be asking to move a key that has already been recorded with a run.
120
+ * @returns The key to record runs under and to look them up by — the same one
121
+ * for the life of the component, and for the next load in the same tab (or the
122
+ * same browser, under `"local"`).
123
+ */
124
+ export declare function useRunKey(options?: {
125
+ /**
126
+ * Which store keeps the key between loads.
127
+ *
128
+ * `"session"` (the default) dies with the tab; `"local"` survives the
129
+ * browser closing, which is what a run that sleeps for days needs. See "The
130
+ * storage is the caller's decision".
131
+ */
132
+ storage?: "session" | "local";
133
+ }): string;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * `useSessionControls` — the two flags and four methods a control row is made
3
+ * of, and nothing else.
4
+ *
5
+ * Every custom chrome's control row opened with the same three lines:
6
+ * `useSessionActions()` for the methods, then two one-field
7
+ * `useSessionSelector` calls for `started` and `running`. That is the right
8
+ * shape — the whole-snapshot `useSession()` it replaced re-rendered four
9
+ * buttons at STT-partial rate to read two booleans that flip once a call — and
10
+ * it is also three lines every row writes identically, which is what a hook is
11
+ * for. `<SessionControls>` is built on it; a chrome whose buttons are too
12
+ * unusual for even that component's render slot takes the hook alone.
13
+ */
14
+ /**
15
+ * What {@link useSessionControls} returns.
16
+ *
17
+ * @public
18
+ */
19
+ export type UseSessionControlsResult = {
20
+ /** Whether a call has been started and not yet ended — the Start/End hinge. */
21
+ started: boolean;
22
+ /** Whether the started call is live rather than paused — the Pause/Resume hinge. */
23
+ running: boolean;
24
+ /** Dial. What the button before `started` presses. */
25
+ start: () => void;
26
+ /** Pause a running call, or resume a paused one. */
27
+ toggle: () => void;
28
+ /**
29
+ * Hang up AND dial again — a brand-new session with fresh session-scoped
30
+ * state, a fresh greeting, and the chrome kept on the call. `end()` then
31
+ * `start()`, never `reset()`: `reset()` clears the conversation and reconnects
32
+ * carrying the same session id, so every `sessionSlot` on the agent survives
33
+ * and the next tool call repopulates the board, cart or game that was just
34
+ * abandoned. Three chromes each found this and wrote the pair by hand.
35
+ */
36
+ restart: () => void;
37
+ /**
38
+ * Hang up. Flips `started` back, so a chrome returns to its Start button and
39
+ * the next `start()` is a new session. `reset()` would keep the call live.
40
+ */
41
+ end: () => void;
42
+ };
43
+ /**
44
+ * The state and the actions a Start / Pause–Resume / New conversation / End row
45
+ * renders from, on two one-field subscriptions.
46
+ *
47
+ * Must be used inside the provider `mountClient()` installs.
48
+ *
49
+ * @example A footer that renders its own buttons
50
+ * ```tsx
51
+ * import { useSessionControls } from "@alexkroman1/aai-ui";
52
+ *
53
+ * function Footer() {
54
+ * const { started, running, start, toggle, end } = useSessionControls();
55
+ * if (!started) return <button type="button" onClick={start}>Begin</button>;
56
+ * return (
57
+ * <>
58
+ * <button type="button" onClick={toggle}>{running ? "Hold" : "Resume"}</button>
59
+ * <button type="button" onClick={end}>Hang up</button>
60
+ * </>
61
+ * );
62
+ * }
63
+ * ```
64
+ *
65
+ * @returns See {@link UseSessionControlsResult}.
66
+ *
67
+ * @public
68
+ */
69
+ export declare function useSessionControls(): UseSessionControlsResult;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * `useSessionId()` and `useClientId()` — the two ids a page keys things by, as
3
+ * React state.
4
+ *
5
+ * Both read `session.identity` through the session's own subscription, so they
6
+ * are current without any wiring. The alternative every app wrote was an
7
+ * external store fed by `mountClient({ onSessionId })` — which is set up
8
+ * OUTSIDE the component tree, fires only on a `config` frame (so it never says
9
+ * "no session" after `end()`), and has to be threaded to wherever the id is
10
+ * read. The session already knows both ids; these hooks just ask it.
11
+ *
12
+ * `useSyncExternalStore` over the snapshot subscription rather than a store of
13
+ * their own: each returns a string (or `undefined`), which compares by value,
14
+ * so a component re-renders only when ITS id changes, not on every frame the
15
+ * session notifies for. The dialer notifies when the confirmed session id
16
+ * changes even if no snapshot field moved with it.
17
+ *
18
+ * @module
19
+ */
20
+ /**
21
+ * The server's id for the current session: `undefined` before the session's
22
+ * first `config` frame and after `end()`; a resume or a new session updates it
23
+ * from its own `config` frame. Key a page's history by it.
24
+ *
25
+ * Treat it as sensitive — whoever holds it can resume the session and read its
26
+ * history (`VoiceSessionOptions.onSessionId` explains why).
27
+ *
28
+ * @example
29
+ * ```tsx
30
+ * import { useSessionId } from "@alexkroman1/aai-ui";
31
+ *
32
+ * function SessionTag() {
33
+ * const id = useSessionId();
34
+ * return <small>{id ?? "no session yet"}</small>;
35
+ * }
36
+ * ```
37
+ *
38
+ * @returns The current session id, or `undefined`.
39
+ *
40
+ * @public
41
+ */
42
+ export declare function useSessionId(): string | undefined;
43
+ /**
44
+ * The client id this session sends as `?client=` — the id a tool reads with
45
+ * `sessionClientId(ctx)` and the inbox socket is held under. With
46
+ * `mountClient({ client: "auto" })` it is this browser's `browserClientId()`;
47
+ * `undefined` when the session sends none.
48
+ *
49
+ * A `client` GETTER is read when the session notifies (each snapshot change),
50
+ * so a page that changes what its getter answers sees the new id on the next
51
+ * render the session causes.
52
+ *
53
+ * @returns The client id, or `undefined`.
54
+ *
55
+ * @public
56
+ */
57
+ export declare function useClientId(): string | undefined;