@alexkroman1/aai-ui 13.2.0 → 13.3.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 (41) hide show
  1. package/dist/{_colors-j8XMToi9.js → _colors-CZ6OlPbL.js} +1 -1
  2. package/dist/{_module-url-C_4gRVL0.js → _module-url-C13kAJ87.js} +1 -1
  3. package/dist/_recover-run.d.ts +11 -5
  4. package/dist/{_utils-B6498_bm.js → _utils-CyzjK0gW.js} +1 -1
  5. package/dist/{aai-logo-CXZGPSIY.js → aai-logo-CFlomZlS.js} +1 -1
  6. package/dist/audio.js +1 -1
  7. package/dist/{chat-view-DDTtrh7N.js → chat-view-Bv5VFJIE.js} +6 -6
  8. package/dist/{client-config-BT_kWID5.js → client-config-DD820zHn.js} +1 -1
  9. package/dist/client-dir.js +1 -1
  10. package/dist/components/button.js +2 -2
  11. package/dist/components/chat-view.js +1 -1
  12. package/dist/components/controls.js +2 -2
  13. package/dist/components/message-list.js +1 -1
  14. package/dist/components/sidebar-layout.js +1 -1
  15. package/dist/components/start-screen.js +4 -4
  16. package/dist/components/tool-call-block.js +1 -1
  17. package/dist/context.js +1 -1
  18. package/dist/default-client.d.ts +1 -1
  19. package/dist/define-client.js +6 -6
  20. package/dist/{eyebrow-C6ZFuiz6.js → eyebrow-UfmSz9yy.js} +1 -1
  21. package/dist/hooks.js +2 -2
  22. package/dist/index.d.ts +2 -1
  23. package/dist/index.js +1168 -1114
  24. package/dist/internal.js +5 -5
  25. package/dist/{message-list-BJYyuIcR.js → message-list-C0pL7x41.js} +7 -7
  26. package/dist/{session-core-DxBYsfHA.js → session-core-C9elBIdu.js} +11 -11
  27. package/dist/session-core.js +1 -1
  28. package/dist/{tool-call-block-tcPQAkcP.js → tool-call-block-Bunc6rCw.js} +6 -6
  29. package/dist/{tool-config-context-DzAofqi_.js → tool-config-context-Bh8p3DtG.js} +1 -1
  30. package/dist/types.js +1 -1
  31. package/dist/{url-chips-YqhCjWfQ.js → url-chips-C2u7QPv8.js} +3 -3
  32. package/dist/use-run-key.d.ts +44 -10
  33. package/dist/{use-user-transcript-C14qWFu2.js → use-user-transcript-DFTSEuZN.js} +1 -1
  34. package/dist/use-workflow-form.d.ts +31 -90
  35. package/dist/{use-workflow-progress-Cu0SxMyg.js → use-workflow-run-CXGEcM0l.js} +256 -256
  36. package/dist/use-workflow-stream.d.ts +4 -1
  37. package/dist/use-workflows.d.ts +77 -0
  38. package/dist/worklets/capture-processor.js +2 -2
  39. package/dist/worklets/playback-processor.js +2 -2
  40. package/package.json +6 -6
  41. package/dist/tsdown.config.d.ts +0 -2
@@ -1,9 +1,9 @@
1
- import { t as pageBaseUrl } from "./_utils-B6498_bm.js";
1
+ import { t as pageBaseUrl } from "./_utils-CyzjK0gW.js";
2
2
  import { useCallback, useEffect, useRef, useState } from "react";
3
3
  import { errorMessage } from "@alexkroman1/aai";
4
4
  import { omitUndefined } from "@alexkroman1/aai/utils";
5
5
  import { createWorkflowApiClient, isTerminal, isTerminal as isTerminal$1, readEventStream } from "@alexkroman1/aai/workflow-api";
6
- //#region workflow-client.ts
6
+ //#region src/workflow-client.ts
7
7
  /**
8
8
  * Browser client for the workflow HTTP API (`aai/host/workflow-api.ts`).
9
9
  *
@@ -73,7 +73,7 @@ function createWorkflowApi(opts = {}) {
73
73
  });
74
74
  }
75
75
  //#endregion
76
- //#region _workflow-api-ref.ts
76
+ //#region src/_workflow-api-ref.ts
77
77
  /**
78
78
  * The client preamble every workflow hook needs, once.
79
79
  *
@@ -121,7 +121,7 @@ function useWorkflowApiRef(api) {
121
121
  }, []);
122
122
  }
123
123
  //#endregion
124
- //#region _repeat-until.ts
124
+ //#region src/_repeat-until.ts
125
125
  /**
126
126
  * A bounded read, re-armed from the SETTLED read — the loop both workflow
127
127
  * watchers are built out of.
@@ -165,256 +165,7 @@ function repeatUntil(intervalMs, step) {
165
165
  };
166
166
  }
167
167
  //#endregion
168
- //#region workflow-events.ts
169
- /**
170
- * Watching a run over server-sent events — the PUSH half of `useWorkflowRun`.
171
- *
172
- * Its own module because the seam is clean: everything in `workflow-client.ts`
173
- * is request/response shaping plus the poll, and this is one long-lived stream
174
- * and the SSE parser it needs.
175
- *
176
- * @internal
177
- */
178
- /**
179
- * Watch a run over SSE, falling back to the caller's poll on any failure.
180
- *
181
- * The poll stays the fallback rather than being replaced, and that is the whole
182
- * shape of this: a stream is an optimisation over a mechanism that already
183
- * works, so every way it can fail — an older agent with no `/events` route, a
184
- * proxy that buffers, a network that drops it — has to degrade to the thing that
185
- * does. What it buys is real, though: on the platform every polled read BROKERS,
186
- * so N open tabs at `DEFAULT_WORKFLOW_POLL_MS` is N/2 brokered requests a
187
- * second, each able to boot a sandbox. One stream per tab replaces all of it.
188
- *
189
- * `EventSource` is not used, for two reasons that both matter here: it cannot
190
- * send an `Authorization` header (an agent with `AAI_WORKFLOW_API_TOKEN` set
191
- * would be unreachable), and it reconnects on its own schedule, which would
192
- * fight the caller's. A `fetch` stream gives both back.
193
- *
194
- * Returns a stop function. `onFallback` is called at most once, when this stream
195
- * cannot be relied on and the poll should take over.
196
- */
197
- function watchRunEvents(getClient, runId, onRun, onSettled, onFallback) {
198
- const controller = new AbortController();
199
- let handedOver = false;
200
- const handOver = () => {
201
- if (handedOver || controller.signal.aborted) return;
202
- handedOver = true;
203
- onFallback();
204
- };
205
- /**
206
- * Consume the stream. Resolves `"settled"` when the run reached a state
207
- * nothing will change, and `"fallback"` for every other ending — including a
208
- * clean end with no final frame, which is a dropped connection.
209
- *
210
- * A named function rather than an inline IIFE so `watchRunEvents` stays under
211
- * the cognitive-complexity cap, and so the two outcomes are a return value
212
- * instead of two callbacks invoked from six places.
213
- */
214
- const pump = async () => {
215
- const res = await getClient().watch(runId, controller.signal);
216
- if (!(res.ok && res.body)) return "fallback";
217
- for await (const frame of readEventStream(res.body, controller.signal)) {
218
- if (frame.event === "run" && frame.data) onRun(frame.data);
219
- const outcome = endingFor(frame.event);
220
- if (outcome) return outcome;
221
- }
222
- return "fallback";
223
- };
224
- pump().then((outcome) => outcome === "settled" ? onSettled() : handOver(), () => handOver());
225
- return () => controller.abort();
226
- }
227
- /**
228
- * Does this frame END the stream, and does the run need watching afterwards?
229
- *
230
- * `done` and `missing` are both final and neither wants a reconnect: the run is
231
- * terminal, or the id will never exist (a 404 is a stable answer — the world's
232
- * record is durable). `idle` is the stream handing ITSELF back after its
233
- * duration cap, so that one falls back to the poll. Anything else is not an
234
- * ending.
235
- */
236
- function endingFor(event) {
237
- if (event === "done" || event === "missing") return "settled";
238
- return event === "idle" ? "fallback" : void 0;
239
- }
240
- //#endregion
241
- //#region use-workflow-run.ts
242
- /**
243
- * `useWorkflowRun` — watch one run until it settles.
244
- *
245
- * Split from `workflow-client.ts` on the seam that module's doc already draws:
246
- * everything there is a REQUEST (one call, one answer, no React), and everything
247
- * here is the loop that keeps asking. They are read for different reasons — the
248
- * client is what a script or a `curl` equivalent needs, this is what a page needs
249
- * — and only this half imports React.
250
- *
251
- * `workflow-events.ts` sits under it as the streaming fast path, and
252
- * `use-workflow-form.ts` above it as the form-shaped caller.
253
- */
254
- /** How often {@link useWorkflowRun} re-reads a live run when it has to poll. */
255
- const DEFAULT_WORKFLOW_POLL_MS = 2e3;
256
- /**
257
- * Consecutive "no such run" reads {@link useWorkflowRun} tolerates before giving
258
- * up on the id.
259
- *
260
- * Small on purpose: a 404 is a stable answer, so the budget exists only to
261
- * absorb a first read that races the run's creation — not to keep hoping.
262
- * Unbounded, a stale id polls (and, on the platform, BROKERS) for as long as the
263
- * tab is open.
264
- */
265
- const MAX_MISSING_READS = 3;
266
- /**
267
- * Poll `runId` until it is terminal, reporting each read. Returns a stop
268
- * function.
269
- *
270
- * Module-level rather than inline in the hook below, so neither function carries
271
- * the whole loop's branching — and so the loop can be read without React in the
272
- * way.
273
- */
274
- function pollUntilTerminal(getClient, runId, intervalMs, onRun, onError, onStopped) {
275
- let missing = 0;
276
- /**
277
- * A read that came back empty. Resolves whether the loop should STOP.
278
- *
279
- * A 404 is a STABLE answer — a run the agent does not know about now will not
280
- * appear later — and retrying it unbounded is how a stale id (one restored
281
- * from `localStorage`, or one whose agent was redeployed onto a fresh
282
- * database) polls forever: the page stays `polling` and therefore busy, and on
283
- * the platform every read BROKERS, so a tab's worth of dead ids keeps
284
- * sandboxes resident. A small budget is kept anyway, because the first read
285
- * can race a replica that has not yet seen the run.
286
- */
287
- const onMissing = (signal) => {
288
- missing += 1;
289
- if (missing < 3) return false;
290
- if (!signal.aborted) onError(`No workflow run ${runId}`);
291
- return true;
292
- };
293
- const read = async (signal) => {
294
- try {
295
- const next = await getClient().get(runId);
296
- if (signal.aborted) return true;
297
- if (!next) return onMissing(signal);
298
- missing = 0;
299
- onRun(next);
300
- return isTerminal(next);
301
- } catch (err) {
302
- if (!signal.aborted) onError(errorMessage(err));
303
- return false;
304
- }
305
- };
306
- return repeatUntil(intervalMs, async (signal) => {
307
- if (!await read(signal)) return false;
308
- if (!signal.aborted) onStopped();
309
- return true;
310
- });
311
- }
312
- /**
313
- * Watch one run until it reaches a terminal status.
314
- *
315
- * A watch rather than a subscription because a run is durable and the page is
316
- * not: it can complete while the tab is closed, on a different sandbox, hours
317
- * later. There is no session to reconnect — the id is the whole state.
318
- *
319
- * Which is also the limit of what this hook can do on its own. An id is state a
320
- * RELOAD destroys, so a page holding nothing else comes back unable to name a
321
- * run that is still going. The durable handle is `StartOptions.key`, read back
322
- * with `find(workflow, key)`, and the hook that owns the id is where that
323
- * belongs: `useWorkflowSubmit({ key, recover: true })` adopts the key's newest
324
- * run as it mounts and passes the id here. See `_recover-run.ts` — the reason
325
- * recovery is NOT in this hook is `reset()`, which leaves the owner holding no
326
- * id on purpose, and a watcher that re-resolved one from a key would undo it.
327
- *
328
- * The stream (`GET /runs/:id/events`) is tried first and the poll is its
329
- * fallback, so an agent deployed before that route existed still works. Watching
330
- * STOPS on a terminal status, so a finished run costs nothing; passing
331
- * `undefined` (nothing started yet) also costs nothing.
332
- *
333
- * @example
334
- * ```tsx
335
- * import type { ToolInputSchema, WorkflowDef } from "@alexkroman1/aai";
336
- * import type { WorkflowOutputOf } from "@alexkroman1/aai/workflow-api";
337
- * import { useWorkflowRun } from "@alexkroman1/aai-ui";
338
- *
339
- * // A real page writes `import type { digest } from "./agent.ts"`. Stood in
340
- * // for here so the example compiles on its own.
341
- * declare const digest: WorkflowDef<ToolInputSchema, Promise<{ points: string[] }>>;
342
- *
343
- * type Digest = WorkflowOutputOf<typeof digest>;
344
- *
345
- * function RunPanel({ runId }: { runId: string | undefined }) {
346
- * // The type argument is what makes `run.output` a `Digest` below.
347
- * const { run, error, polling } = useWorkflowRun<Digest>(runId);
348
- * if (error !== undefined) return <p role="alert">{error}</p>;
349
- * if (run?.status === "completed") {
350
- * return (
351
- * <ul>
352
- * {run.output.points.map((point) => (
353
- * <li key={point}>{point}</li>
354
- * ))}
355
- * </ul>
356
- * );
357
- * }
358
- * return <p>{polling ? "Working…" : "Nothing running."}</p>;
359
- * }
360
- * ```
361
- *
362
- * @typeParam R - The workflow's output type. Supplying it is what makes
363
- * `run.status === "completed"` narrow to a typed `run.output` instead of
364
- * `unknown`. Derive it with `WorkflowOutputOf<typeof myWorkflow>` — a
365
- * type-only import of `agent.ts` is erased, so it costs the bundle nothing.
366
- * @param runId - The run to watch. `undefined` costs nothing, so a page may
367
- * pass its state straight through before a run exists.
368
- * @param opts - `api` when the page holds its own client; `intervalMs` to
369
- * change the poll interval the stream falls back to.
370
- * @returns The latest snapshot, the last read's error, and whether the watch
371
- * is still going — see {@link UseWorkflowRunResult}.
372
- *
373
- * @public
374
- */
375
- function useWorkflowRun(runId, opts = {}) {
376
- const { api, intervalMs = DEFAULT_WORKFLOW_POLL_MS } = opts;
377
- const [run, setRun] = useState(void 0);
378
- const [error, setError] = useState(void 0);
379
- /**
380
- * Has the watch stopped for a reason the snapshot does not show?
381
- *
382
- * Only one such reason exists — an id the agent kept reporting as unknown,
383
- * past {@link MAX_MISSING_READS} — and it leaves `run` undefined, so `polling`
384
- * derived from `isTerminal(run)` alone would stay true forever.
385
- */
386
- const [stopped, setStopped] = useState(false);
387
- const getClient = useWorkflowApiRef(api);
388
- useEffect(() => {
389
- setRun(void 0);
390
- setError(void 0);
391
- setStopped(false);
392
- if (!runId) return;
393
- const onRun = (next) => {
394
- setRun(next);
395
- setError(void 0);
396
- };
397
- let stopPoll;
398
- const stopStream = watchRunEvents(getClient, runId, onRun, () => setStopped(true), () => {
399
- stopPoll = pollUntilTerminal(getClient, runId, intervalMs, onRun, setError, () => setStopped(true));
400
- });
401
- return () => {
402
- stopStream();
403
- stopPoll?.();
404
- };
405
- }, [
406
- runId,
407
- intervalMs,
408
- getClient
409
- ]);
410
- return {
411
- run,
412
- error,
413
- polling: runId !== void 0 && !stopped && !isTerminal(run)
414
- };
415
- }
416
- //#endregion
417
- //#region use-workflow-progress.ts
168
+ //#region src/use-workflow-progress.ts
418
169
  /**
419
170
  * `useWorkflowProgress` — read what a run has WRITTEN while it runs.
420
171
  *
@@ -560,7 +311,7 @@ async function consumeFrames(body, signal) {
560
311
  * floor is inclusive rather than exclusive — read exclusively, this loop lost the
561
312
  * chunk sitting AT its cursor on every re-open, so a run writing one line per
562
313
  * poll delivered every other line.
563
- * `packages/aai-runtime/workflow-stream-cursor.test.ts` states it as a property
314
+ * `packages/aai-runtime/src/workflow-stream-cursor.test.ts` states it as a property
564
315
  * over generated polling schedules; this module's own spec pins the URLs.
565
316
  *
566
317
  * ## A negative `startIndex` is resolved on the FIRST read, not carried
@@ -684,4 +435,253 @@ function useWorkflowProgress(runId, opts = {}) {
684
435
  };
685
436
  }
686
437
  //#endregion
687
- export { useWorkflowRun as a, isTerminal$1 as c, MAX_MISSING_READS as i, useWorkflowProgress as n, useWorkflowApiRef as o, DEFAULT_WORKFLOW_POLL_MS as r, createWorkflowApi as s, DEFAULT_PROGRESS_POLL_MS as t };
438
+ //#region src/workflow-events.ts
439
+ /**
440
+ * Watching a run over server-sent events — the PUSH half of `useWorkflowRun`.
441
+ *
442
+ * Its own module because the seam is clean: everything in `workflow-client.ts`
443
+ * is request/response shaping plus the poll, and this is one long-lived stream
444
+ * and the SSE parser it needs.
445
+ *
446
+ * @internal
447
+ */
448
+ /**
449
+ * Watch a run over SSE, falling back to the caller's poll on any failure.
450
+ *
451
+ * The poll stays the fallback rather than being replaced, and that is the whole
452
+ * shape of this: a stream is an optimisation over a mechanism that already
453
+ * works, so every way it can fail — an older agent with no `/events` route, a
454
+ * proxy that buffers, a network that drops it — has to degrade to the thing that
455
+ * does. What it buys is real, though: on the platform every polled read BROKERS,
456
+ * so N open tabs at `DEFAULT_WORKFLOW_POLL_MS` is N/2 brokered requests a
457
+ * second, each able to boot a sandbox. One stream per tab replaces all of it.
458
+ *
459
+ * `EventSource` is not used, for two reasons that both matter here: it cannot
460
+ * send an `Authorization` header (an agent with `AAI_WORKFLOW_API_TOKEN` set
461
+ * would be unreachable), and it reconnects on its own schedule, which would
462
+ * fight the caller's. A `fetch` stream gives both back.
463
+ *
464
+ * Returns a stop function. `onFallback` is called at most once, when this stream
465
+ * cannot be relied on and the poll should take over.
466
+ */
467
+ function watchRunEvents(getClient, runId, onRun, onSettled, onFallback) {
468
+ const controller = new AbortController();
469
+ let handedOver = false;
470
+ const handOver = () => {
471
+ if (handedOver || controller.signal.aborted) return;
472
+ handedOver = true;
473
+ onFallback();
474
+ };
475
+ /**
476
+ * Consume the stream. Resolves `"settled"` when the run reached a state
477
+ * nothing will change, and `"fallback"` for every other ending — including a
478
+ * clean end with no final frame, which is a dropped connection.
479
+ *
480
+ * A named function rather than an inline IIFE so `watchRunEvents` stays under
481
+ * the cognitive-complexity cap, and so the two outcomes are a return value
482
+ * instead of two callbacks invoked from six places.
483
+ */
484
+ const pump = async () => {
485
+ const res = await getClient().watch(runId, controller.signal);
486
+ if (!(res.ok && res.body)) return "fallback";
487
+ for await (const frame of readEventStream(res.body, controller.signal)) {
488
+ if (frame.event === "run" && frame.data) onRun(frame.data);
489
+ const outcome = endingFor(frame.event);
490
+ if (outcome) return outcome;
491
+ }
492
+ return "fallback";
493
+ };
494
+ pump().then((outcome) => outcome === "settled" ? onSettled() : handOver(), () => handOver());
495
+ return () => controller.abort();
496
+ }
497
+ /**
498
+ * Does this frame END the stream, and does the run need watching afterwards?
499
+ *
500
+ * `done` and `missing` are both final and neither wants a reconnect: the run is
501
+ * terminal, or the id will never exist (a 404 is a stable answer — the world's
502
+ * record is durable). `idle` is the stream handing ITSELF back after its
503
+ * duration cap, so that one falls back to the poll. Anything else is not an
504
+ * ending.
505
+ */
506
+ function endingFor(event) {
507
+ if (event === "done" || event === "missing") return "settled";
508
+ return event === "idle" ? "fallback" : void 0;
509
+ }
510
+ //#endregion
511
+ //#region src/use-workflow-run.ts
512
+ /**
513
+ * `useWorkflowRun` — watch one run until it settles.
514
+ *
515
+ * Split from `workflow-client.ts` on the seam that module's doc already draws:
516
+ * everything there is a REQUEST (one call, one answer, no React), and everything
517
+ * here is the loop that keeps asking. They are read for different reasons — the
518
+ * client is what a script or a `curl` equivalent needs, this is what a page needs
519
+ * — and only this half imports React.
520
+ *
521
+ * `workflow-events.ts` sits under it as the streaming fast path, and
522
+ * `use-workflow-form.ts` above it as the form-shaped caller.
523
+ */
524
+ /** How often {@link useWorkflowRun} re-reads a live run when it has to poll. */
525
+ const DEFAULT_WORKFLOW_POLL_MS = 2e3;
526
+ /**
527
+ * Consecutive "no such run" reads {@link useWorkflowRun} tolerates before giving
528
+ * up on the id.
529
+ *
530
+ * Small on purpose: a 404 is a stable answer, so the budget exists only to
531
+ * absorb a first read that races the run's creation — not to keep hoping.
532
+ * Unbounded, a stale id polls (and, on the platform, BROKERS) for as long as the
533
+ * tab is open.
534
+ */
535
+ const MAX_MISSING_READS = 3;
536
+ /**
537
+ * Poll `runId` until it is terminal, reporting each read. Returns a stop
538
+ * function.
539
+ *
540
+ * Module-level rather than inline in the hook below, so neither function carries
541
+ * the whole loop's branching — and so the loop can be read without React in the
542
+ * way.
543
+ */
544
+ function pollUntilTerminal(getClient, runId, intervalMs, onRun, onError, onStopped) {
545
+ let missing = 0;
546
+ /**
547
+ * A read that came back empty. Resolves whether the loop should STOP.
548
+ *
549
+ * A 404 is a STABLE answer — a run the agent does not know about now will not
550
+ * appear later — and retrying it unbounded is how a stale id (one restored
551
+ * from `localStorage`, or one whose agent was redeployed onto a fresh
552
+ * database) polls forever: the page stays `polling` and therefore busy, and on
553
+ * the platform every read BROKERS, so a tab's worth of dead ids keeps
554
+ * sandboxes resident. A small budget is kept anyway, because the first read
555
+ * can race a replica that has not yet seen the run.
556
+ */
557
+ const onMissing = (signal) => {
558
+ missing += 1;
559
+ if (missing < 3) return false;
560
+ if (!signal.aborted) onError(`No workflow run ${runId}`);
561
+ return true;
562
+ };
563
+ const read = async (signal) => {
564
+ try {
565
+ const next = await getClient().get(runId);
566
+ if (signal.aborted) return true;
567
+ if (!next) return onMissing(signal);
568
+ missing = 0;
569
+ onRun(next);
570
+ return isTerminal(next);
571
+ } catch (err) {
572
+ if (!signal.aborted) onError(errorMessage(err));
573
+ return false;
574
+ }
575
+ };
576
+ return repeatUntil(intervalMs, async (signal) => {
577
+ if (!await read(signal)) return false;
578
+ if (!signal.aborted) onStopped();
579
+ return true;
580
+ });
581
+ }
582
+ /**
583
+ * Watch one run until it reaches a terminal status.
584
+ *
585
+ * A watch rather than a subscription because a run is durable and the page is
586
+ * not: it can complete while the tab is closed, on a different sandbox, hours
587
+ * later. There is no session to reconnect — the id is the whole state.
588
+ *
589
+ * Which is also the limit of what this hook can do on its own. An id is state a
590
+ * RELOAD destroys, so a page holding nothing else comes back unable to name a
591
+ * run that is still going. The durable handle is `StartOptions.key`, read back
592
+ * with `find(workflow, key)`, and the hook that owns the id is where that
593
+ * belongs: `useWorkflowSubmit({ key, recover: true })` adopts the key's newest
594
+ * run as it mounts and passes the id here. See `_recover-run.ts` — the reason
595
+ * recovery is NOT in this hook is `reset()`, which leaves the owner holding no
596
+ * id on purpose, and a watcher that re-resolved one from a key would undo it.
597
+ *
598
+ * The stream (`GET /runs/:id/events`) is tried first and the poll is its
599
+ * fallback, so an agent deployed before that route existed still works. Watching
600
+ * STOPS on a terminal status, so a finished run costs nothing; passing
601
+ * `undefined` (nothing started yet) also costs nothing.
602
+ *
603
+ * @example
604
+ * ```tsx
605
+ * import type { ToolInputSchema, WorkflowDef } from "@alexkroman1/aai";
606
+ * import type { WorkflowOutputOf } from "@alexkroman1/aai/workflow-api";
607
+ * import { useWorkflowRun } from "@alexkroman1/aai-ui";
608
+ *
609
+ * // A real page writes `import type { digest } from "./agent.ts"`. Stood in
610
+ * // for here so the example compiles on its own.
611
+ * declare const digest: WorkflowDef<ToolInputSchema, Promise<{ points: string[] }>>;
612
+ *
613
+ * type Digest = WorkflowOutputOf<typeof digest>;
614
+ *
615
+ * function RunPanel({ runId }: { runId: string | undefined }) {
616
+ * // The type argument is what makes `run.output` a `Digest` below.
617
+ * const { run, error, polling } = useWorkflowRun<Digest>(runId);
618
+ * if (error !== undefined) return <p role="alert">{error}</p>;
619
+ * if (run?.status === "completed") {
620
+ * return (
621
+ * <ul>
622
+ * {run.output.points.map((point) => (
623
+ * <li key={point}>{point}</li>
624
+ * ))}
625
+ * </ul>
626
+ * );
627
+ * }
628
+ * return <p>{polling ? "Working…" : "Nothing running."}</p>;
629
+ * }
630
+ * ```
631
+ *
632
+ * @typeParam R - The workflow's output type. Supplying it is what makes
633
+ * `run.status === "completed"` narrow to a typed `run.output` instead of
634
+ * `unknown`. Derive it with `WorkflowOutputOf<typeof myWorkflow>` — a
635
+ * type-only import of `agent.ts` is erased, so it costs the bundle nothing.
636
+ * @param runId - The run to watch. `undefined` costs nothing, so a page may
637
+ * pass its state straight through before a run exists.
638
+ * @param opts - `api` when the page holds its own client; `intervalMs` to
639
+ * change the poll interval the stream falls back to.
640
+ * @returns The latest snapshot, the last read's error, and whether the watch
641
+ * is still going — see {@link UseWorkflowRunResult}.
642
+ *
643
+ * @public
644
+ */
645
+ function useWorkflowRun(runId, opts = {}) {
646
+ const { api, intervalMs = DEFAULT_WORKFLOW_POLL_MS } = opts;
647
+ const [run, setRun] = useState(void 0);
648
+ const [error, setError] = useState(void 0);
649
+ /**
650
+ * Has the watch stopped for a reason the snapshot does not show?
651
+ *
652
+ * Only one such reason exists — an id the agent kept reporting as unknown,
653
+ * past {@link MAX_MISSING_READS} — and it leaves `run` undefined, so `polling`
654
+ * derived from `isTerminal(run)` alone would stay true forever.
655
+ */
656
+ const [stopped, setStopped] = useState(false);
657
+ const getClient = useWorkflowApiRef(api);
658
+ useEffect(() => {
659
+ setRun(void 0);
660
+ setError(void 0);
661
+ setStopped(false);
662
+ if (!runId) return;
663
+ const onRun = (next) => {
664
+ setRun(next);
665
+ setError(void 0);
666
+ };
667
+ let stopPoll;
668
+ const stopStream = watchRunEvents(getClient, runId, onRun, () => setStopped(true), () => {
669
+ stopPoll = pollUntilTerminal(getClient, runId, intervalMs, onRun, setError, () => setStopped(true));
670
+ });
671
+ return () => {
672
+ stopStream();
673
+ stopPoll?.();
674
+ };
675
+ }, [
676
+ runId,
677
+ intervalMs,
678
+ getClient
679
+ ]);
680
+ return {
681
+ run,
682
+ error,
683
+ polling: runId !== void 0 && !stopped && !isTerminal(run)
684
+ };
685
+ }
686
+ //#endregion
687
+ export { useWorkflowProgress as a, isTerminal$1 as c, DEFAULT_PROGRESS_POLL_MS as i, MAX_MISSING_READS as n, useWorkflowApiRef as o, useWorkflowRun as r, createWorkflowApi as s, DEFAULT_WORKFLOW_POLL_MS as t };
@@ -93,7 +93,10 @@ import type { SubmitInputOf } from "./workflow-def-types.ts";
93
93
  * would sit waiting for bytes nobody is sending until its own abandonment
94
94
  * bound. That is the same reason `_upload-recall.ts` deliberately does not
95
95
  * recall for this hook, one layer up: here the id is part of a run's INPUT.
96
- * `key` itself still works, and still makes the run findable.
96
+ * `key` itself still works, and still makes the run findable — but it is NOT
97
+ * defaulted here the way `useWorkflowSubmit` defaults it, because the whole
98
+ * value of that default is the lookup this hook refuses, and minting a key
99
+ * nothing will ever read back is a slot left in storage for no one.
97
100
  *
98
101
  * `parallel` COMPOSES with what this hook is for rather than competing with it.
99
102
  * The run still starts before the bytes, and the store still publishes how far
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The agent's declared workflows — what a form is rendered FROM.
3
+ *
4
+ * Split out of `use-workflow-form.ts` at the 500-line cap, along the seam that
5
+ * file's own doc already drew: it held "the two hooks a FORM needs", and only
6
+ * one of them is about a RUN. This is the other one, and it shares nothing with
7
+ * its former neighbours but the client ref every hook here uses — no state, no
8
+ * run id, no upload.
9
+ */
10
+ import type { WorkflowSummary } from "@alexkroman1/aai/workflow-api";
11
+ import type { WorkflowApi } from "./workflow-client.ts";
12
+ /** Options for {@link useWorkflows}. */
13
+ export type UseWorkflowsOptions = {
14
+ /** The client to read the listing with. Defaults to one for the page's own agent. */
15
+ api?: WorkflowApi;
16
+ /**
17
+ * Skip the lookup entirely, reporting an empty listing that is not loading.
18
+ *
19
+ * For a caller that may or may not need the listing and cannot decide with a
20
+ * conditional hook — `<WorkflowFields>` handed a summary rather than a name is
21
+ * the one in this package. It reports `loading: false`, because a skipped
22
+ * lookup is finished rather than pending.
23
+ */
24
+ skip?: boolean;
25
+ };
26
+ /** What {@link useWorkflows} reports. */
27
+ export type UseWorkflowsResult = {
28
+ /** The agent's declared workflows, each with the JSON Schema of its input. */
29
+ workflows: WorkflowSummary[];
30
+ /** True until the listing lands, so a form can hold its fields back. */
31
+ loading: boolean;
32
+ /** The lookup's failure. Set alongside an EMPTY list, which is why it exists. */
33
+ error: string | undefined;
34
+ };
35
+ /**
36
+ * Read the agent's declared workflows.
37
+ *
38
+ * What `<WorkflowFields>` renders a form FROM: each summary carries the JSON
39
+ * Schema of that workflow's input, converted server-side precisely so a browser
40
+ * can read it.
41
+ *
42
+ * The failure is reported rather than swallowed, because the alternative is an
43
+ * empty list — which renders as a form with no fields and reads as "this agent
44
+ * declares no workflows" about an agent that was merely unreachable.
45
+ *
46
+ * @example
47
+ * ```tsx
48
+ * import { useWorkflows } from "@alexkroman1/aai-ui";
49
+ *
50
+ * // A page rendering its own chrome from the listing — a picker, say. A form
51
+ * // for ONE workflow wants `<WorkflowFields workflow="name" />` instead,
52
+ * // which does this lookup itself.
53
+ * function WorkflowPicker({ onPick }: { onPick: (name: string) => void }) {
54
+ * const { workflows, loading, error } = useWorkflows();
55
+ * if (loading) return <p>Loading…</p>;
56
+ * if (error !== undefined) return <p role="alert">{error}</p>;
57
+ * return (
58
+ * <ul>
59
+ * {workflows.map((summary) => (
60
+ * <li key={summary.name}>
61
+ * <button type="button" onClick={() => onPick(summary.name)}>
62
+ * {summary.description ?? summary.name}
63
+ * </button>
64
+ * </li>
65
+ * ))}
66
+ * </ul>
67
+ * );
68
+ * }
69
+ * ```
70
+ *
71
+ * @param opts - See {@link UseWorkflowsOptions}.
72
+ * @returns The listing, its loading flag and its failure — see
73
+ * {@link UseWorkflowsResult}.
74
+ *
75
+ * @public
76
+ */
77
+ export declare function useWorkflows(opts?: UseWorkflowsOptions): UseWorkflowsResult;
@@ -1,6 +1,6 @@
1
1
  import { MIC_BUFFER_SECONDS, MIC_SILENCE_PROBE_MS } from "../types.js";
2
- import { t as workletModuleUrl } from "../_module-url-C_4gRVL0.js";
3
- //#region worklets/capture-processor.ts
2
+ import { t as workletModuleUrl } from "../_module-url-C13kAJ87.js";
3
+ //#region src/worklets/capture-processor.ts
4
4
  /** Raw worklet source — exported so tests can evaluate the processor directly. */
5
5
  const captureProcessorSource = `
6
6
  class CaptureProcessor extends AudioWorkletProcessor {
@@ -1,6 +1,6 @@
1
1
  import { PLAYBACK_BUFFER_SECONDS, PLAYBACK_CONCEAL_FADE_MS, PLAYBACK_CONCEAL_FLOOR, PLAYBACK_FILL_MS, PLAYBACK_PROGRESS_INTERVAL_MS } from "../types.js";
2
- import { t as workletModuleUrl } from "../_module-url-C_4gRVL0.js";
3
- //#region worklets/playback-processor.ts
2
+ import { t as workletModuleUrl } from "../_module-url-C13kAJ87.js";
3
+ //#region src/worklets/playback-processor.ts
4
4
  /** Raw worklet source — exported so tests can evaluate the processor directly. */
5
5
  const playbackProcessorSource = `
6
6
  class PlaybackProcessor extends AudioWorkletProcessor {