@alexkroman1/aai-cli 6.10.1 → 7.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 (162) hide show
  1. package/dist/{_agent-DpH2pBJd.mjs → _agent-BTwZJu4P.mjs} +2 -2
  2. package/dist/{_bundler-C2BDwU7r.mjs → _bundler-B4RqNF5Z.mjs} +2 -2
  3. package/dist/_dev-env.d.ts +22 -2
  4. package/dist/{_dev-server-LLLWnFBs.mjs → _dev-server-CiKFH9qw.mjs} +38 -47
  5. package/dist/_dev-server.d.ts +0 -17
  6. package/dist/{_init-C57bWAQ8.mjs → _init-CE4HKdgV.mjs} +3 -2
  7. package/dist/{_preflight-BtfaYtbE.mjs → _preflight-Co1G9Jww.mjs} +1 -1
  8. package/dist/_preflight.d.ts +1 -1
  9. package/dist/{_slug-api-BxrsJXVI.mjs → _slug-api-nRFaBEPJ.mjs} +2 -2
  10. package/dist/{_studio-U3jhKlA3.mjs → _studio-DR5yzZ1T.mjs} +2 -1
  11. package/dist/{_templates-Dxb_P2Wz.mjs → _templates-FI3xPEMj.mjs} +1 -1
  12. package/dist/_workflow-scan.d.ts +108 -0
  13. package/dist/{build-Dfza2pRI.mjs → build-6jfaakgc.mjs} +5 -2
  14. package/dist/cli.mjs +30 -25
  15. package/dist/{client-bundler-BEIqgOtd.mjs → client-bundler-CWnG42cU.mjs} +2 -1
  16. package/dist/client-bundler.mjs +1 -1
  17. package/dist/{delete-vbpjGzqs.mjs → delete-Dl_LSfvI.mjs} +3 -3
  18. package/dist/{deploy-B8lubiRT.mjs → deploy-DvYeD8ia.mjs} +5 -5
  19. package/dist/{dev-Uq5ujP8a.mjs → dev-B59JIfWO.mjs} +1 -1
  20. package/dist/{eject-31gjtaHF.mjs → eject-C9WJyyr2.mjs} +1 -1
  21. package/dist/{init-l6xfU4xX.mjs → init-CpJVjEg0.mjs} +3 -3
  22. package/dist/{login-C71-qz8F.mjs → login-BBuM1sxH.mjs} +3 -4
  23. package/dist/{logs-BWVGSjLR.mjs → logs-9q-psa4q.mjs} +2 -2
  24. package/dist/project-config.mjs +1 -1
  25. package/dist/scaffold/CLAUDE.md +93 -14
  26. package/dist/scaffold/package.json +5 -3
  27. package/dist/scaffold/server.mjs +13 -4
  28. package/dist/scaffold/vite.config.ts +1 -1
  29. package/dist/{secret-BuMuFR4B.mjs → secret-CVvSLIDV.mjs} +2 -2
  30. package/dist/{storage-DzRZ-eCw.mjs → storage-BvUrnvM3.mjs} +2 -2
  31. package/dist/{studio-BCNUpDgP.mjs → studio--MUV0cid.mjs} +4 -4
  32. package/dist/templates/call-audit/agent.test.ts +818 -0
  33. package/dist/templates/call-audit/agent.ts +171 -0
  34. package/dist/templates/call-audit/client.tsx +198 -0
  35. package/dist/templates/call-audit/workflows/audit.ts +298 -0
  36. package/dist/templates/call-audit/workflows/ingest.ts +251 -0
  37. package/dist/templates/call-audit/workflows/media.ts +637 -0
  38. package/dist/templates/call-audit/workflows/summarize.ts +201 -0
  39. package/dist/templates/call-audit/workflows/sync-api.ts +44 -0
  40. package/dist/templates/dispatch-center/agent.test.ts +149 -25
  41. package/dist/templates/dispatch-center/client.tsx +239 -129
  42. package/dist/templates/dispatch-center/shared.ts +99 -1
  43. package/dist/templates/dispatch-center/system-prompt.md +3 -1
  44. package/dist/templates/dispatch-center/tools/incident_add_note.ts +16 -13
  45. package/dist/templates/dispatch-center/tools/incident_create.ts +19 -1
  46. package/dist/templates/dispatch-center/tools/incident_escalate.ts +68 -59
  47. package/dist/templates/dispatch-center/tools/incident_triage.ts +57 -43
  48. package/dist/templates/dispatch-center/tools/incident_update_status.ts +49 -40
  49. package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +8 -2
  50. package/dist/templates/dispatch-center/tools/resources_dispatch.ts +69 -52
  51. package/dist/templates/dispatch-center/tools/resources_update_status.ts +45 -41
  52. package/dist/templates/embedded-assets/agent.test.ts +17 -14
  53. package/dist/templates/health-assistant/agent.test.ts +22 -4
  54. package/dist/templates/infocom-adventure/agent.test.ts +4 -6
  55. package/dist/templates/infocom-adventure/client.tsx +246 -164
  56. package/dist/templates/link-digest/agent.test.ts +24 -19
  57. package/dist/templates/link-digest/client.tsx +47 -61
  58. package/dist/templates/link-digest/workflows/digest.ts +19 -31
  59. package/dist/templates/night-owl/agent.test.ts +70 -19
  60. package/dist/templates/night-owl/agent.ts +5 -0
  61. package/dist/templates/night-owl/client.tsx +56 -56
  62. package/dist/templates/night-owl/shared.ts +24 -0
  63. package/dist/templates/night-owl/tools/recommend.ts +18 -6
  64. package/dist/templates/pizza-ordering/agent.test.ts +27 -27
  65. package/dist/templates/pizza-ordering/client.tsx +9 -26
  66. package/dist/templates/plan-and-execute/agent.test.ts +99 -58
  67. package/dist/templates/plan-and-execute/agent.ts +1 -1
  68. package/dist/templates/plan-and-execute/client.tsx +12 -15
  69. package/dist/templates/plan-and-execute/shared.ts +71 -2
  70. package/dist/templates/plan-and-execute/tools/plan_status.ts +18 -6
  71. package/dist/templates/plan-and-execute/tools/revise_plan.ts +18 -10
  72. package/dist/templates/plan-and-execute/tools/start_plan.ts +13 -2
  73. package/dist/templates/plan-and-execute/tools/work_next_step.ts +70 -22
  74. package/dist/templates/podcast-digest/agent.test.ts +746 -0
  75. package/dist/templates/podcast-digest/agent.ts +139 -0
  76. package/dist/templates/podcast-digest/client.tsx +154 -0
  77. package/dist/templates/podcast-digest/workflows/digest.ts +411 -0
  78. package/dist/templates/podcast-digest/workflows/feeds.ts +507 -0
  79. package/dist/templates/podcast-digest/workflows/slack.ts +209 -0
  80. package/dist/templates/recap-workflow/agent.test.ts +107 -96
  81. package/dist/templates/recap-workflow/tools/recap_progress.ts +9 -12
  82. package/dist/templates/recap-workflow/tools/recap_status.ts +8 -9
  83. package/dist/templates/recap-workflow/tools/request_recap.ts +2 -1
  84. package/dist/templates/recap-workflow/workflows/recap.ts +48 -41
  85. package/dist/templates/redline/agent.test.ts +17 -15
  86. package/dist/templates/redline/client.tsx +12 -12
  87. package/dist/templates/redline/workflows/redline.ts +19 -31
  88. package/dist/templates/research-workflow/agent.test.ts +60 -59
  89. package/dist/templates/research-workflow/tools/research_progress.ts +9 -12
  90. package/dist/templates/research-workflow/tools/research_status.ts +9 -11
  91. package/dist/templates/research-workflow/workflows/research.ts +44 -61
  92. package/dist/templates/retail/agent.test.ts +26 -23
  93. package/dist/templates/retail/client.tsx +226 -117
  94. package/dist/templates/retail/registry.test.ts +38 -6
  95. package/dist/templates/retail/store.test.ts +82 -15
  96. package/dist/templates/retail/store.ts +174 -47
  97. package/dist/templates/retail/system-prompt.md +11 -2
  98. package/dist/templates/retail/tools/cancel_pending_order.ts +2 -2
  99. package/dist/templates/retail/tools/exchange_delivered_order_items.ts +2 -2
  100. package/dist/templates/retail/tools/find_user_id_by_email.ts +8 -5
  101. package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +4 -5
  102. package/dist/templates/retail/tools/get_item_details.ts +3 -4
  103. package/dist/templates/retail/tools/get_order_details.ts +3 -4
  104. package/dist/templates/retail/tools/get_product_details.ts +3 -4
  105. package/dist/templates/retail/tools/get_user_details.ts +2 -2
  106. package/dist/templates/retail/tools/list_all_product_types.ts +5 -5
  107. package/dist/templates/retail/tools/modify_pending_order_address.ts +2 -2
  108. package/dist/templates/retail/tools/modify_pending_order_items.ts +2 -3
  109. package/dist/templates/retail/tools/modify_pending_order_payment.ts +2 -2
  110. package/dist/templates/retail/tools/modify_user_address.ts +2 -2
  111. package/dist/templates/retail/tools/return_delivered_order_items.ts +2 -2
  112. package/dist/templates/retail/tools/transfer_to_human_agents.ts +8 -4
  113. package/dist/templates/solo-rpg/agent.test.ts +227 -48
  114. package/dist/templates/solo-rpg/client.tsx +38 -37
  115. package/dist/templates/solo-rpg/shared.ts +145 -19
  116. package/dist/templates/solo-rpg/system-prompt.md +3 -2
  117. package/dist/templates/solo-rpg/tools/action_roll.ts +82 -63
  118. package/dist/templates/solo-rpg/tools/burn_momentum.ts +85 -50
  119. package/dist/templates/solo-rpg/tools/check_state.ts +24 -7
  120. package/dist/templates/solo-rpg/tools/load_game.ts +13 -1
  121. package/dist/templates/solo-rpg/tools/save_game.ts +16 -7
  122. package/dist/templates/solo-rpg/tools/setup_character.ts +22 -2
  123. package/dist/templates/solo-rpg/tools/update_state.ts +113 -100
  124. package/dist/templates/spoken-summary/agent.test.ts +314 -0
  125. package/dist/templates/spoken-summary/agent.ts +155 -0
  126. package/dist/templates/spoken-summary/client.tsx +185 -0
  127. package/dist/templates/spoken-summary/workflows/summarize.ts +237 -0
  128. package/dist/templates/spoken-summary/workflows/transcribe.ts +138 -0
  129. package/dist/templates/support-line/agent.test.ts +11 -16
  130. package/dist/templates/support-line/agent.ts +1 -1
  131. package/dist/templates/support-line/client.tsx +9 -9
  132. package/dist/templates/support-line/nodes.ts +100 -0
  133. package/dist/templates/support-line/procedure.ts +407 -0
  134. package/dist/templates/support-line/tools/answer_question.ts +17 -4
  135. package/dist/templates/transcription-workflow/agent.test.ts +246 -123
  136. package/dist/templates/transcription-workflow/agent.ts +21 -7
  137. package/dist/templates/transcription-workflow/client.tsx +17 -42
  138. package/dist/templates/transcription-workflow/workflows/batch.ts +79 -180
  139. package/dist/templates/transcription-workflow/workflows/normalize.ts +247 -0
  140. package/dist/templates/transcription-workflow/workflows/stitch.ts +0 -11
  141. package/dist/templates/transcription-workflow/workflows/stream.ts +10 -9
  142. package/dist/templates/transcription-workflow/workflows/sync-api.ts +26 -94
  143. package/dist/templates/transcription-workflow/workflows/transcribe.ts +55 -40
  144. package/dist/templates/transcription-workflow/workflows/wav.ts +31 -31
  145. package/dist/templates/travel-concierge/agent.test.ts +64 -33
  146. package/dist/templates/travel-concierge/client.tsx +11 -23
  147. package/dist/templates/travel-concierge/routing.ts +34 -15
  148. package/dist/templates/travel-concierge/shared.ts +70 -3
  149. package/dist/templates/travel-concierge/tools/book_car_rental.ts +2 -2
  150. package/dist/templates/travel-concierge/tools/book_excursion.ts +2 -2
  151. package/dist/templates/travel-concierge/tools/book_hotel.ts +2 -2
  152. package/dist/templates/travel-concierge/tools/cancel_ticket.ts +2 -2
  153. package/dist/templates/travel-concierge/tools/update_ticket.ts +2 -2
  154. package/dist/{worker-bundler-CGD4r8Kc.mjs → worker-bundler-COxnqstQ.mjs} +221 -3
  155. package/dist/worker-bundler.mjs +1 -1
  156. package/dist/{workflow-CFpxOFfQ.mjs → workflow-D2AQf2Pl.mjs} +28 -1
  157. package/dist/workflow-bundler.d.ts +6 -1
  158. package/dist/workflow.d.ts +1 -1
  159. package/package.json +5 -4
  160. package/dist/templates/support-line/graph.ts +0 -224
  161. package/dist/{_config-D_s09e7g.mjs → _config-CmJOFsAP.mjs} +1 -1
  162. /package/dist/templates/plan-and-execute/{graph.ts → procedure.ts} +0 -0
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok } from "./_output-CKkmWs7i.mjs";
3
3
  import { n as log } from "./_ui-u7T4YooX.mjs";
4
- import { l as writeProjectConfig } from "./_config-D_s09e7g.mjs";
5
- import { a as resolveDeployTarget, i as requireDeployedSlug } from "./_agent-DpH2pBJd.mjs";
6
- import { d as apiTestSeam, l as HINT_NOT_DEPLOYED, s as studioProjectApiUrl, u as apiRequest } from "./_studio-U3jhKlA3.mjs";
4
+ import { l as writeProjectConfig } from "./_config-CmJOFsAP.mjs";
5
+ import { a as resolveDeployTarget, i as requireDeployedSlug } from "./_agent-BTwZJu4P.mjs";
6
+ import { d as apiTestSeam, l as HINT_NOT_DEPLOYED, s as studioProjectApiUrl, u as apiRequest } from "./_studio-DR5yzZ1T.mjs";
7
7
  //#region delete.ts
8
8
  async function runDelete(opts) {
9
9
  await apiRequest(`${opts.url}/${opts.slug}`, {
@@ -2,12 +2,12 @@
2
2
  import { a as ok } from "./_output-CKkmWs7i.mjs";
3
3
  import { n as log, r as notify, t as fmtUrl } from "./_ui-u7T4YooX.mjs";
4
4
  import { o as errorMessage$1 } from "./_utils-B8QmtFhK.mjs";
5
- import { s as updateProjectConfig } from "./_config-D_s09e7g.mjs";
6
- import { i as evalWorkerConfig, t as buildAgentBundle } from "./_bundler-C2BDwU7r.mjs";
5
+ import { s as updateProjectConfig } from "./_config-CmJOFsAP.mjs";
6
+ import { i as evalWorkerConfig, t as buildAgentBundle } from "./_bundler-B4RqNF5Z.mjs";
7
7
  import { t as resolveServerEnv } from "./_server-common-6e0QI4mq.mjs";
8
- import { a as resolveDeployTarget } from "./_agent-DpH2pBJd.mjs";
8
+ import { a as resolveDeployTarget } from "./_agent-BTwZJu4P.mjs";
9
9
  import { assertTypechecks } from "./_typecheck-gate-BXvUNnfs.mjs";
10
- import { d as apiTestSeam, f as checkedResponse, i as projectNameFromDir, u as apiRequest } from "./_studio-U3jhKlA3.mjs";
10
+ import { d as apiTestSeam, f as checkedResponse, i as projectNameFromDir, u as apiRequest } from "./_studio-DR5yzZ1T.mjs";
11
11
  import { isRecord } from "@alexkroman1/aai/utils";
12
12
  import { gzipSync } from "node:zlib";
13
13
  //#region _deploy.ts
@@ -39,7 +39,7 @@ async function executeDeploy(opts) {
39
39
  const { cwd } = opts;
40
40
  const { config: projectConfig, serverUrl, apiKey } = await resolveDeployTarget(cwd, opts.server);
41
41
  if (!opts.skipTypecheck) await assertTypechecks(cwd);
42
- const preflightModule = import("./_preflight-BtfaYtbE.mjs");
42
+ const preflightModule = import("./_preflight-Co1G9Jww.mjs");
43
43
  const bundle = await buildAgentBundle(cwd, { minify: true });
44
44
  const slug = projectConfig?.slug ?? projectNameFromDir(cwd) ?? void 0;
45
45
  const uploadEnv = {
@@ -12,7 +12,7 @@ import { styleText } from "node:util";
12
12
  async function executeDev(opts) {
13
13
  const port = parsePort(opts.port);
14
14
  const agentName = path.basename(path.resolve(opts.cwd));
15
- const { startDevServer } = await import("./_dev-server-LLLWnFBs.mjs");
15
+ const { startDevServer } = await import("./_dev-server-CiKFH9qw.mjs");
16
16
  let cleanup;
17
17
  let shuttingDown = false;
18
18
  const onSignal = () => {
@@ -2,7 +2,7 @@
2
2
  import { a as ok, t as CliError } from "./_output-CKkmWs7i.mjs";
3
3
  import { n as log } from "./_ui-u7T4YooX.mjs";
4
4
  import { l as readJson, o as errorMessage, p as writeJson, s as fileExists } from "./_utils-B8QmtFhK.mjs";
5
- import { scaffoldDir } from "./_templates-Dxb_P2Wz.mjs";
5
+ import { scaffoldDir } from "./_templates-FI3xPEMj.mjs";
6
6
  import path from "node:path";
7
7
  import fs from "node:fs/promises";
8
8
  //#region eject.ts
@@ -2,7 +2,7 @@
2
2
  import { a as ok } from "./_output-CKkmWs7i.mjs";
3
3
  import { n as log, s as unwrapCancel } from "./_ui-u7T4YooX.mjs";
4
4
  import { d as resolveCwd, l as readJson, o as errorMessage, s as fileExists, t as AGENT_ENTRY } from "./_utils-B8QmtFhK.mjs";
5
- import { r as isDevMode, t as getMonorepoRoot } from "./_agent-DpH2pBJd.mjs";
5
+ import { r as isDevMode, t as getMonorepoRoot } from "./_agent-BTwZJu4P.mjs";
6
6
  import path from "node:path";
7
7
  import { styleText } from "node:util";
8
8
  import * as p from "@clack/prompts";
@@ -105,7 +105,7 @@ function collectWarnings() {
105
105
  }
106
106
  /** Publish after init and return deploy metadata if successful. */
107
107
  async function tryPublish(cwd, server, warn) {
108
- const { executePublish } = await import("./studio-BCNUpDgP.mjs");
108
+ const { executePublish } = await import("./studio--MUV0cid.mjs");
109
109
  try {
110
110
  const result = await executePublish({
111
111
  cwd,
@@ -126,7 +126,7 @@ async function tryPublish(cwd, server, warn) {
126
126
  }
127
127
  /** Scaffold the project, optionally showing a spinner. */
128
128
  async function scaffoldProject(dir, cwd, template, silent) {
129
- const { runInit } = await import("./_init-C57bWAQ8.mjs");
129
+ const { runInit } = await import("./_init-CE4HKdgV.mjs");
130
130
  await withSpinner(silent, {
131
131
  start: `Creating ${dir}`,
132
132
  done: "Project created",
@@ -1,11 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok, t as CliError } from "./_output-CKkmWs7i.mjs";
3
3
  import { n as log } from "./_ui-u7T4YooX.mjs";
4
- import { o as updateGlobalConfig, r as readGlobalConfig, t as approveServer } from "./_config-D_s09e7g.mjs";
5
- import { o as resolveServerUrl } from "./_agent-DpH2pBJd.mjs";
6
- import { linkConfirmationCode } from "@alexkroman1/aai/utils";
4
+ import { o as updateGlobalConfig, r as readGlobalConfig, t as approveServer } from "./_config-CmJOFsAP.mjs";
5
+ import { o as resolveServerUrl } from "./_agent-BTwZJu4P.mjs";
6
+ import { linkConfirmationCode, sleep } from "@alexkroman1/aai/internal";
7
7
  import { spawn } from "node:child_process";
8
- import { sleep } from "@alexkroman1/aai/internal";
9
8
  import { randomBytes } from "node:crypto";
10
9
  //#region login.ts
11
10
  /**
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok } from "./_output-CKkmWs7i.mjs";
3
3
  import { n as log } from "./_ui-u7T4YooX.mjs";
4
- import { f as checkedResponse } from "./_studio-U3jhKlA3.mjs";
5
- import { n as slugRequest } from "./_slug-api-BxrsJXVI.mjs";
4
+ import { f as checkedResponse } from "./_studio-DR5yzZ1T.mjs";
5
+ import { n as slugRequest } from "./_slug-api-nRFaBEPJ.mjs";
6
6
  import { isRecord, omitUndefined } from "@alexkroman1/aai/utils";
7
7
  import { sleep } from "@alexkroman1/aai/internal";
8
8
  function isLogsPage(value) {
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import { c as writeGlobalConfig, s as updateProjectConfig } from "./_config-D_s09e7g.mjs";
2
+ import { c as writeGlobalConfig, s as updateProjectConfig } from "./_config-CmJOFsAP.mjs";
3
3
  export { updateProjectConfig, writeGlobalConfig as writeConfigHome };
@@ -127,6 +127,8 @@ export default agent({
127
127
  llm?: LlmProvider | string; // unset stages default to AssemblyAI
128
128
  tts?: TtsProvider; // (llm also takes a model-id string)
129
129
  s2s?: S2sProvider; // explicit opt-in to speech-to-speech mode
130
+ // all four types are on "@alexkroman1/aai",
131
+ // and on their own stage subpath
130
132
  sttPrompt?: string; // STT guidance for jargon/acronyms
131
133
  builtinTools?: BuiltinTool[]; // see built-in tools table
132
134
  // (there is no `tools` field — a tool is a FILE;
@@ -344,12 +346,15 @@ async function file(digest: { url: string }) {
344
346
  }
345
347
  ```
346
348
 
347
- Three rules, all of which fail silently if broken:
349
+ Three rules. The second and third fail silently if broken; the first is
350
+ warned about by `aai build` and `aai dev`, naming the file and the call:
348
351
 
349
352
  - **The body replays from the top on every resume**, so it holds no live handle
350
353
  and makes no undurable decision — no `Date.now()`, no `Math.random()`, no
351
- `fetch`. Those belong in a step, whose result is journaled and returned
352
- unchanged on replay.
354
+ `crypto.randomUUID()`, no `fetch`. Those belong in a step, whose result is
355
+ journaled and returned unchanged on replay. (The warning reads the built
356
+ workflow bundle, where step bodies have already been removed, so a step doing
357
+ any of this is not flagged — that is what a step is FOR.)
353
358
  - **A step's arguments and return value cross a queue**, so they must be
354
359
  JSON-shaped and small. Put bytes in storage and pass the key.
355
360
  - **A step gets no tool context.** It is bundled and dispatched separately from
@@ -359,12 +364,12 @@ Three rules, all of which fail silently if broken:
359
364
  ### A step's env, and calling a model from one
360
365
 
361
366
  A step has no `ctx`, so the two things tool code takes for granted come from
362
- `@alexkroman1/aai/utils` instead. Import them from THERE and not from
367
+ `@alexkroman1/aai/step` instead. Import them from THERE and not from
363
368
  `@alexkroman1/aai` — a `workflows/*.ts` module is bundled separately, and the
364
369
  root barrel would drag the whole SDK into that bundle.
365
370
 
366
371
  ```ts no-check
367
- import { requireStepEnv, stepEnv, StepGenerateError, stepGenerate } from "@alexkroman1/aai/utils";
372
+ import { requireStepEnv, stepEnv, StepGenerateError, stepGenerate } from "@alexkroman1/aai/step";
368
373
  import { FatalError } from "workflow";
369
374
 
370
375
  async function summarize(url: string, text: string) {
@@ -399,11 +404,11 @@ deploy. Ask it for JSON and parse the reply if you need a shape.
399
404
  ### A step's HTTP: use `stepFetch`, not `fetch`
400
405
 
401
406
  Any outbound request from a step goes through `stepFetch` (also
402
- `@alexkroman1/aai/utils`). It is not a style preference — `fetch` is the wrong
407
+ `@alexkroman1/aai/step`). It is not a style preference — `fetch` is the wrong
403
408
  call to make from a step, for a reason nothing at the call site shows:
404
409
 
405
410
  ```ts no-check
406
- import { multipartBody, stepFetch, StepTransportError } from "@alexkroman1/aai/utils";
411
+ import { multipartBody, stepFetch, StepTransportError } from "@alexkroman1/aai/step";
407
412
 
408
413
  async function transcribeChunk(key: string, bytes: Uint8Array, index: number) {
409
414
  "use step";
@@ -433,7 +438,7 @@ async function transcribeChunk(key: string, bytes: Uint8Array, index: number) {
433
438
  global `fetch` offers `h2` in ALPN and the far side decides; a server that takes
434
439
  it gets every concurrent request from your process multiplexed onto ONE TCP
435
440
  connection, sharing one flow-control window. That is fine for small JSON calls
436
- and pathological for `mapInBatches` over large bodies. Measured on 8 concurrent
441
+ and pathological for `mapConcurrent` over large bodies. Measured on 8 concurrent
437
442
  17.66 MB uploads: `fetch` landed 14 of 16 at p50 8094ms, HTTP/1.1 landed 16 of
438
443
  16 at p50 3037ms.
439
444
 
@@ -466,6 +471,66 @@ Three rules come with it:
466
471
  `stepGenerate` already goes through this, so a step that only calls a model gets
467
472
  it for free.
468
473
 
474
+ ### A step can SPEAK, and store the file it made
475
+
476
+ A workflow whose answer is a FILE — a summary read aloud, a rendered image, a
477
+ generated PDF — needs two things a first draft reaches for and does not find.
478
+ Both are on `@alexkroman1/aai/step`, and `spoken-summary` is the template that
479
+ shows the whole round trip.
480
+
481
+ ```ts no-check
482
+ import { stepSpeak, writeUpload } from "@alexkroman1/aai/step";
483
+
484
+ export async function narrate(script: string) {
485
+ "use step";
486
+
487
+ const spoken = await stepSpeak(script, { voice: "jane" });
488
+ const stored = await writeUpload(spoken.audio, { name: "summary.wav", type: "audio/wav" });
489
+ return { audio: stored.id, durationMs: spoken.durationMs };
490
+ }
491
+ ```
492
+
493
+ **`stepSpeak` is `stepGenerate` for the voice.** A step is handed no
494
+ `ToolContext`, so the provider stack your `agent()` declares is not in scope —
495
+ and the session TTS surface would not help anyway: it is an event stream wired
496
+ into a live pipeline's playback, and a step has no turn to be part of and has to
497
+ return a value. So this is the smaller thing: text in, the whole utterance out
498
+ as a WAV, on the same `ASSEMBLYAI_API_KEY` everything else uses. Voices come
499
+ from `ASSEMBLYAI_TTS_VOICES` (`@alexkroman1/aai`, or `/tts`) — read that list
500
+ rather than typing an id, because a wrong one is refused *after* the socket
501
+ opens and produces silence rather than an error. The `AssemblyAITtsVoice` type
502
+ gives you autocomplete over it and nothing more: it accepts any string, so that
503
+ a voice the service adds after this release still compiles.
504
+
505
+ **`writeUpload` is `readUpload`'s other direction, and you need it.** A run's
506
+ output is read back as JSON, so audio cannot travel in one — the same rule that
507
+ keeps an uploaded recording's bytes out of a run's INPUT, arriving at the other
508
+ end of the run. Store the bytes, return the **id**, and let the page fetch it
509
+ with `api.download(id)`.
510
+
511
+ Three rules come with it:
512
+
513
+ - **Speak and store in ONE step.** A step is journaled by its return value, so
514
+ an id is replayed on a resume and bytes are not. Split in two, the audio
515
+ crosses the queue between them every time the run resumes.
516
+ - **A retried step writes a SECOND upload** and abandons the first — the store
517
+ cannot know two calls meant one file. That is the price of the step being
518
+ retryable at all, and it is the right trade.
519
+ - **Name and TYPE what you store.** The byte route serves the `type` it was
520
+ given, and a browser will not play inline a file it was handed as
521
+ `application/octet-stream`.
522
+
523
+ On the page, `api.download(id)` answers a `Blob`, not a URL — the byte route
524
+ takes the same bearer every other route does, and neither `<audio src>` nor
525
+ `<a href>` can send one, so a page built on a URL works in `aai dev` and 401s
526
+ once the agent has a token. `URL.createObjectURL(blob)` is what those elements
527
+ take; revoke it when the id changes.
528
+
529
+ Test both with `stubSpeech()` and `stubUploads(files, { writable: true })`
530
+ (`@alexkroman1/aai/testing`). The write half is opt-in on purpose: a store that
531
+ silently accepted writes could not fail a spec whose step stored a file nobody
532
+ meant it to.
533
+
469
534
  ### A builtin's failure is its RESULT, so narrow it
470
535
 
471
536
  `webSearch`, `visitWebpage` and `fetchJson` (`@alexkroman1/aai/tools`) answer
@@ -502,7 +567,7 @@ request. Everything else is the same file, React and Tailwind included.
502
567
  ```tsx no-check
503
568
  import { createWorkflowApi, page, useWorkflowRun } from "@alexkroman1/aai-ui";
504
569
  import "@alexkroman1/aai-ui/styles.css";
505
- import type { WorkflowOutputOf } from "@alexkroman1/aai";
570
+ import type { WorkflowOutputOf } from "@alexkroman1/aai/workflow-api";
506
571
  import { useState } from "react";
507
572
  import type { digest } from "./agent.ts";
508
573
 
@@ -689,11 +754,18 @@ for the providers you actually use.
689
754
  | --------------- | ---------------------- | -------------------- |
690
755
  | `assemblyAIStt` | `"universal-3-5-pro"` | `ASSEMBLYAI_API_KEY` |
691
756
  | `deepgram` | `"nova-3"` | `DEEPGRAM_API_KEY` |
692
- | `elevenlabs` | `"scribe_v2_realtime"` | `ELEVENLABS_API_KEY` |
757
+ | `elevenLabsStt` | `"scribe_v2_realtime"` | `ELEVENLABS_API_KEY` |
693
758
  | `soniox` | `"stt-rt-v3"` | `SONIOX_API_KEY` |
694
759
 
695
760
  All STT factories accept `{ model?: string, ... }`. Bare calls
696
- (`deepgram()`, `soniox()`, etc.) use the default model.
761
+ (`deepgram()`, `soniox()`, etc.) use the default model. Language is spelled
762
+ `language` where the vendor takes one code (`deepgram`, `elevenLabsStt`) and
763
+ `languages` where it takes a list (`assemblyAIStt`, `soniox`) — and only
764
+ `deepgram`'s unset value means English; the other three auto-detect.
765
+
766
+ `elevenLabsStt` carries the stage in its name because ElevenLabs is
767
+ better known for TTS: when that stage arrives, `elevenLabs` is the name it
768
+ should get.
697
769
 
698
770
  `assemblyAIStt` accepts an optional `region: "eu"` for EU data residency —
699
771
  it routes streaming transcription to AssemblyAI's EU endpoints. EU-region
@@ -714,8 +786,11 @@ API keys require it; the US endpoints reject them. Example:
714
786
  | `gateway` | `ai` (built in) | `AI_GATEWAY_API_KEY` |
715
787
  | `assemblyAILlm` | `@ai-sdk/openai` | `ASSEMBLYAI_API_KEY` |
716
788
 
717
- LLM factories require `{ model: string }`. Example:
718
- `anthropic({ model: "claude-haiku-4-5" })`.
789
+ LLM factories require `{ model: string }` — the `ModelOptions` interface,
790
+ shared by all of them except `assemblyAILlm`. Example:
791
+ `anthropic({ model: "claude-haiku-4-5" })`. The argument is required because a
792
+ third-party vendor's catalog is not this SDK's to default from;
793
+ `assemblyAILlm()` is the one bare call, since it has a default model.
719
794
 
720
795
  `openrouter` routes through [OpenRouter](https://openrouter.ai) — an
721
796
  OpenAI-compatible endpoint fronting hundreds of models addressed as
@@ -793,7 +868,11 @@ Set
793
868
  `language` only alongside a voice that speaks it, as an ISO 639-1 code —
794
869
  `"en"`, `"fr"`, `"de"`, `"it"`, `"pt"`, `"es"` are the six the catalog
795
870
  covers, and the SDK translates each to the full name the service wants.
796
- Anything else fails at session start.
871
+ An unsupported code, and a code the declared voice does not speak, are both
872
+ build errors naming the voices that do speak it — including the one you get by
873
+ setting `language` alone, since the descriptor then fills in the default
874
+ English voice. (A voice this release's catalog does not list is passed through:
875
+ the catalog is the service's, so a voice it ships later still works.)
797
876
 
798
877
  **Rime quirk:** language uses ISO 639-3 three-letter codes (e.g. `"eng"`
799
878
  not `"en"`).
@@ -13,17 +13,19 @@
13
13
  "publish:agent": "aai publish"
14
14
  },
15
15
  "dependencies": {
16
- "@alexkroman1/aai": "^6.10.1",
17
- "@alexkroman1/aai-ui": "^6.10.1",
16
+ "@alexkroman1/aai": "^7.0.0",
17
+ "@alexkroman1/aai-runtime": "^7.0.0",
18
+ "@alexkroman1/aai-ui": "^7.0.0",
18
19
  "@workflow/world-postgres": "4.3.3",
19
20
  "react": "^19.2.8",
20
21
  "react-dom": "^19.2.8",
21
22
  "tailwindcss": "^4.0.0",
22
23
  "workflow": "4.8.2",
24
+ "xstate": "^5.32.5",
23
25
  "zod": "^4.4.3"
24
26
  },
25
27
  "devDependencies": {
26
- "@alexkroman1/aai-cli": "^6.10.1",
28
+ "@alexkroman1/aai-cli": "^7.0.0",
27
29
  "@tailwindcss/vite": "^4.3.3",
28
30
  "@types/node": "^26.2.0",
29
31
  "@types/react": "^19.2.18",
@@ -17,7 +17,7 @@ import { readFile } from "node:fs/promises";
17
17
  import path from "node:path";
18
18
  import { fileURLToPath, pathToFileURL } from "node:url";
19
19
  import { parseEnv } from "node:util";
20
- import { createAgentServer, withHostCredentialFallback } from "@alexkroman1/aai/runtime";
20
+ import { createAgentServer, withHostCredentialFallback } from "@alexkroman1/aai-runtime";
21
21
  import { defaultClientDir } from "@alexkroman1/aai-ui/client-dir";
22
22
 
23
23
  const root = path.dirname(fileURLToPath(import.meta.url));
@@ -144,9 +144,18 @@ await server.listen(Number(process.env.PORT ?? 3000), host);
144
144
  console.log(`${agent.name} listening on http://${host ?? "127.0.0.1"}:${server.port}`);
145
145
 
146
146
  for (const signal of ["SIGINT", "SIGTERM"]) {
147
- process.once(signal, async () => {
147
+ // A SYNCHRONOUS listener. An `async` one hands its promise to `process`,
148
+ // which discards what a listener returns — so a `close()` that rejects would
149
+ // surface as an unhandled rejection, i.e. a crash with a stack trace on
150
+ // Ctrl-C, instead of the non-zero exit a failed shutdown should be.
151
+ process.once(signal, () => {
148
152
  // close() shuts the runtime down too — no separate runtime.shutdown().
149
- await server.close();
150
- process.exit(0);
153
+ server.close().then(
154
+ () => process.exit(0),
155
+ (error) => {
156
+ console.error(`shutdown failed: ${error?.message ?? error}`);
157
+ process.exit(1);
158
+ },
159
+ );
151
160
  });
152
161
  }
@@ -1,5 +1,5 @@
1
- import react from "@vitejs/plugin-react";
2
1
  import tailwindcss from "@tailwindcss/vite";
2
+ import react from "@vitejs/plugin-react";
3
3
  import { defineConfig } from "vite";
4
4
 
5
5
  export default defineConfig({
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok, n as fail } from "./_output-CKkmWs7i.mjs";
3
3
  import { n as log, s as unwrapCancel } from "./_ui-u7T4YooX.mjs";
4
- import { f as checkedResponse, p as isStringArray } from "./_studio-U3jhKlA3.mjs";
5
- import { t as secretRequest } from "./_slug-api-BxrsJXVI.mjs";
4
+ import { f as checkedResponse, p as isStringArray } from "./_studio-DR5yzZ1T.mjs";
5
+ import { t as secretRequest } from "./_slug-api-nRFaBEPJ.mjs";
6
6
  import { isRecord } from "@alexkroman1/aai/utils";
7
7
  import * as p from "@clack/prompts";
8
8
  import { text } from "node:stream/consumers";
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok, n as fail } from "./_output-CKkmWs7i.mjs";
3
3
  import { n as log } from "./_ui-u7T4YooX.mjs";
4
- import { f as checkedResponse } from "./_studio-U3jhKlA3.mjs";
5
- import { n as slugRequest } from "./_slug-api-BxrsJXVI.mjs";
4
+ import { f as checkedResponse } from "./_studio-DR5yzZ1T.mjs";
5
+ import { n as slugRequest } from "./_slug-api-nRFaBEPJ.mjs";
6
6
  import { isRecord } from "@alexkroman1/aai/utils";
7
7
  import * as p from "@clack/prompts";
8
8
  //#region storage.ts
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok, t as CliError } from "./_output-CKkmWs7i.mjs";
3
3
  import { n as log, t as fmtUrl } from "./_ui-u7T4YooX.mjs";
4
- import { s as updateProjectConfig } from "./_config-D_s09e7g.mjs";
4
+ import { s as updateProjectConfig } from "./_config-CmJOFsAP.mjs";
5
5
  import { t as resolveServerEnv } from "./_server-common-6e0QI4mq.mjs";
6
- import { a as resolveDeployTarget } from "./_agent-DpH2pBJd.mjs";
7
- import { layerScaffold } from "./_templates-Dxb_P2Wz.mjs";
8
- import { a as publishStudioProject, c as studioProjectUrl, f as checkedResponse, i as projectNameFromDir, n as fetchStudioProject, o as pushStudioSource, r as listStudioProjects, s as studioProjectApiUrl, t as collectSourceFiles, u as apiRequest } from "./_studio-U3jhKlA3.mjs";
6
+ import { a as resolveDeployTarget } from "./_agent-BTwZJu4P.mjs";
7
+ import { layerScaffold } from "./_templates-FI3xPEMj.mjs";
8
+ import { a as publishStudioProject, c as studioProjectUrl, f as checkedResponse, i as projectNameFromDir, n as fetchStudioProject, o as pushStudioSource, r as listStudioProjects, s as studioProjectApiUrl, t as collectSourceFiles, u as apiRequest } from "./_studio-DR5yzZ1T.mjs";
9
9
  import path from "node:path";
10
10
  import { isRecord } from "@alexkroman1/aai/utils";
11
11
  import { mkdir, readdir, writeFile } from "node:fs/promises";