@alexkroman1/aai-cli 5.14.0 → 6.1.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 (231) hide show
  1. package/dist/{_agent-C8Jk-ZcP.mjs → _agent-vGW5SBnX.mjs} +23 -5
  2. package/dist/_agent.d.ts +18 -0
  3. package/dist/{_api-client-BX8Qv51n.mjs → _api-client-LgLksMhN.mjs} +45 -8
  4. package/dist/_api-client.d.ts +39 -0
  5. package/dist/{_bundler-BuIyDdvs.mjs → _bundler-BapCUUJN.mjs} +42 -14
  6. package/dist/_bundler.d.ts +43 -5
  7. package/dist/_cli-common.d.ts +67 -3
  8. package/dist/{_config-CzwAWOWr.mjs → _config-D_s09e7g.mjs} +36 -18
  9. package/dist/_deploy.d.ts +2 -5
  10. package/dist/_dev-restart.d.ts +6 -1
  11. package/dist/{_dev-server-Ch7K8e8i.mjs → _dev-server-DQ1t9sbw.mjs} +94 -38
  12. package/dist/_dev-server.d.ts +30 -2
  13. package/dist/_e2e-test-utils.d.ts +80 -0
  14. package/dist/_fault-mode.d.ts +175 -0
  15. package/dist/{_init-CbkrE2wq.mjs → _init-CpISqoR8.mjs} +3 -3
  16. package/dist/{_output-CC300DzW.mjs → _output-CKkmWs7i.mjs} +8 -1
  17. package/dist/_output.d.ts +9 -2
  18. package/dist/{_server-common-61fL__39.mjs → _server-common-6e0QI4mq.mjs} +1 -1
  19. package/dist/{_slug-api-D30zPmsn.mjs → _slug-api-D2heILuC.mjs} +2 -2
  20. package/dist/{_studio-D2ciIhbi.mjs → _studio-zAJycZ8j.mjs} +3 -3
  21. package/dist/{_templates-B1kt3BGt.mjs → _templates-iDGjGiPf.mjs} +4 -6
  22. package/dist/{_typecheck-gate-4v9UBZHh.mjs → _typecheck-gate-BXvUNnfs.mjs} +1 -1
  23. package/dist/{_utils-0DHbcqUb.mjs → _utils-B8QmtFhK.mjs} +21 -7
  24. package/dist/_utils.d.ts +18 -2
  25. package/dist/{_vite-env-Dg_QlVv0.mjs → _vite-env-BNveawd1.mjs} +31 -1
  26. package/dist/_vite-env.d.ts +30 -0
  27. package/dist/build-BO1Ni6oJ.mjs +62 -0
  28. package/dist/build.d.ts +19 -0
  29. package/dist/cli-workflow.d.ts +10 -0
  30. package/dist/cli.d.ts +1 -2
  31. package/dist/cli.mjs +406 -224
  32. package/dist/{client-bundler-C9qXL8A2.mjs → client-bundler-B8Q9F8gJ.mjs} +3 -22
  33. package/dist/client-bundler.mjs +1 -1
  34. package/dist/{delete-DCRX3d96.mjs → delete-4I0uNR4a.mjs} +6 -7
  35. package/dist/delete.d.ts +2 -8
  36. package/dist/{deploy-DjsEV-40.mjs → deploy-DJ3HRcia.mjs} +13 -14
  37. package/dist/{dev-CvPH5dbF.mjs → dev-C6SWaAZP.mjs} +3 -3
  38. package/dist/{eject-LgBRfnbH.mjs → eject-Zrzk5KV3.mjs} +41 -15
  39. package/dist/eject.d.ts +30 -3
  40. package/dist/{init-BppxMR8r.mjs → init-BTRGiPQr.mjs} +61 -26
  41. package/dist/init.d.ts +11 -0
  42. package/dist/{login-BQQaxJhL.mjs → login-k0Z3VQu9.mjs} +5 -5
  43. package/dist/project-config.mjs +1 -1
  44. package/dist/scaffold/CLAUDE.md +498 -91
  45. package/dist/scaffold/package.json +11 -8
  46. package/dist/scaffold/pnpm-workspace.yaml +8 -0
  47. package/dist/scaffold/server.mjs +53 -57
  48. package/dist/{secret-Ds9dvqAj.mjs → secret-DzSQx1Ds.mjs} +6 -3
  49. package/dist/{storage-DbrQSHFk.mjs → storage-C-Do0mIA.mjs} +18 -6
  50. package/dist/{studio-GSFd4s5O.mjs → studio-BGVmCA2G.mjs} +10 -10
  51. package/dist/templates/code-interpreter/agent.ts +0 -2
  52. package/dist/templates/dispatch-center/agent.test.ts +17 -19
  53. package/dist/templates/dispatch-center/agent.ts +2 -29
  54. package/dist/templates/dispatch-center/client.tsx +33 -16
  55. package/dist/templates/dispatch-center/shared.ts +53 -19
  56. package/dist/templates/dispatch-center/tools/incident_add_note.ts +12 -14
  57. package/dist/templates/dispatch-center/tools/incident_create.ts +41 -44
  58. package/dist/templates/dispatch-center/tools/incident_escalate.ts +59 -61
  59. package/dist/templates/dispatch-center/tools/incident_get.ts +3 -4
  60. package/dist/templates/dispatch-center/tools/incident_triage.ts +43 -45
  61. package/dist/templates/dispatch-center/tools/incident_update_status.ts +39 -41
  62. package/dist/templates/dispatch-center/tools/ops_dashboard.ts +2 -5
  63. package/dist/templates/dispatch-center/tools/ops_protocols.ts +1 -1
  64. package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +20 -23
  65. package/dist/templates/dispatch-center/tools/resources_dispatch.ts +52 -54
  66. package/dist/templates/dispatch-center/tools/resources_get_available.ts +2 -4
  67. package/dist/templates/dispatch-center/tools/resources_update_status.ts +39 -42
  68. package/dist/templates/embedded-assets/agent.test.ts +83 -0
  69. package/dist/templates/embedded-assets/agent.ts +1 -48
  70. package/dist/templates/embedded-assets/shared.ts +17 -0
  71. package/dist/templates/embedded-assets/system-prompt.md +13 -0
  72. package/dist/templates/embedded-assets/tools/list_topics.ts +9 -0
  73. package/dist/templates/embedded-assets/tools/search_knowledge.ts +26 -0
  74. package/dist/templates/health-assistant/agent.test.ts +169 -0
  75. package/dist/templates/health-assistant/agent.ts +1 -166
  76. package/dist/templates/health-assistant/fda.ts +100 -0
  77. package/dist/templates/health-assistant/tools/check_drug_interaction.ts +59 -0
  78. package/dist/templates/health-assistant/tools/medication_lookup.ts +31 -0
  79. package/dist/templates/infocom-adventure/agent.test.ts +205 -0
  80. package/dist/templates/infocom-adventure/agent.ts +3 -106
  81. package/dist/templates/infocom-adventure/client.tsx +28 -12
  82. package/dist/templates/infocom-adventure/shared.ts +28 -11
  83. package/dist/templates/infocom-adventure/tools/game_state_drop.ts +13 -0
  84. package/dist/templates/infocom-adventure/tools/game_state_flag.ts +13 -0
  85. package/dist/templates/infocom-adventure/tools/game_state_get.ts +16 -0
  86. package/dist/templates/infocom-adventure/tools/game_state_history.ts +14 -0
  87. package/dist/templates/infocom-adventure/tools/game_state_move.ts +14 -0
  88. package/dist/templates/infocom-adventure/tools/game_state_restart.ts +13 -0
  89. package/dist/templates/infocom-adventure/tools/game_state_score.ts +13 -0
  90. package/dist/templates/infocom-adventure/tools/game_state_take.ts +13 -0
  91. package/dist/templates/link-digest/agent.test.ts +223 -0
  92. package/dist/templates/link-digest/agent.ts +79 -0
  93. package/dist/templates/link-digest/client.tsx +146 -0
  94. package/dist/templates/link-digest/workflows/digest.ts +248 -0
  95. package/dist/templates/math-buddy/agent.ts +0 -2
  96. package/dist/templates/night-owl/agent.test.ts +80 -0
  97. package/dist/templates/night-owl/agent.ts +1 -68
  98. package/dist/templates/night-owl/system-prompt.md +10 -0
  99. package/dist/templates/night-owl/tools/recommend.ts +64 -0
  100. package/dist/templates/personal-finance/agent.ts +0 -2
  101. package/dist/templates/pizza-ordering/agent.test.ts +52 -47
  102. package/dist/templates/pizza-ordering/agent.ts +9 -158
  103. package/dist/templates/pizza-ordering/shared.ts +35 -12
  104. package/dist/templates/pizza-ordering/tools/add_pizza.ts +31 -0
  105. package/dist/templates/pizza-ordering/tools/place_order.ts +22 -0
  106. package/dist/templates/pizza-ordering/tools/remove_pizza.ts +22 -0
  107. package/dist/templates/pizza-ordering/tools/set_customer_name.ts +13 -0
  108. package/dist/templates/pizza-ordering/tools/update_pizza.ts +28 -0
  109. package/dist/templates/pizza-ordering/tools/view_order.ts +21 -0
  110. package/dist/templates/plan-and-execute/agent.test.ts +411 -0
  111. package/dist/templates/plan-and-execute/agent.ts +23 -0
  112. package/dist/templates/plan-and-execute/client.tsx +115 -0
  113. package/dist/templates/plan-and-execute/graph.ts +178 -0
  114. package/dist/templates/plan-and-execute/prompts.ts +110 -0
  115. package/dist/templates/plan-and-execute/shared.ts +161 -0
  116. package/dist/templates/plan-and-execute/system-prompt.md +18 -0
  117. package/dist/templates/plan-and-execute/tools/plan_status.ts +20 -0
  118. package/dist/templates/plan-and-execute/tools/revise_plan.ts +63 -0
  119. package/dist/templates/plan-and-execute/tools/start_plan.ts +51 -0
  120. package/dist/templates/plan-and-execute/tools/work_next_step.ts +101 -0
  121. package/dist/templates/recap-workflow/agent.test.ts +762 -0
  122. package/dist/templates/recap-workflow/agent.ts +95 -0
  123. package/dist/templates/recap-workflow/shared.ts +43 -0
  124. package/dist/templates/recap-workflow/tools/cancel_recap.ts +23 -0
  125. package/dist/templates/recap-workflow/tools/keep_transcript.ts +31 -0
  126. package/dist/templates/recap-workflow/tools/recap_progress.ts +27 -0
  127. package/dist/templates/recap-workflow/tools/recap_status.ts +46 -0
  128. package/dist/templates/recap-workflow/tools/request_recap.ts +43 -0
  129. package/dist/templates/recap-workflow/workflows/recap.ts +544 -0
  130. package/dist/templates/recap-workflow/workflows/tokens.ts +26 -0
  131. package/dist/templates/redline/agent.test.ts +222 -0
  132. package/dist/templates/redline/agent.ts +89 -0
  133. package/dist/templates/redline/client.tsx +184 -0
  134. package/dist/templates/redline/workflows/prompts.ts +68 -0
  135. package/dist/templates/redline/workflows/redline.ts +258 -0
  136. package/dist/templates/research-workflow/agent.test.ts +525 -0
  137. package/dist/templates/research-workflow/agent.ts +94 -0
  138. package/dist/templates/research-workflow/shared.ts +35 -0
  139. package/dist/templates/research-workflow/tools/file_it_now.ts +22 -0
  140. package/dist/templates/research-workflow/tools/request_research.ts +33 -0
  141. package/dist/templates/research-workflow/tools/research_progress.ts +26 -0
  142. package/dist/templates/research-workflow/tools/research_status.ts +40 -0
  143. package/dist/templates/research-workflow/workflows/prompts.ts +143 -0
  144. package/dist/templates/research-workflow/workflows/research.ts +558 -0
  145. package/dist/templates/retail/agent.test.ts +236 -172
  146. package/dist/templates/retail/agent.ts +5 -42
  147. package/dist/templates/retail/client.tsx +33 -11
  148. package/dist/templates/retail/registry.test.ts +31 -16
  149. package/dist/templates/retail/resolve.ts +28 -64
  150. package/dist/templates/retail/shared.test.ts +4 -3
  151. package/dist/templates/retail/shared.ts +24 -7
  152. package/dist/templates/retail/store.test.ts +12 -10
  153. package/dist/templates/retail/store.ts +69 -30
  154. package/dist/templates/retail/tools/cancel_pending_order.ts +4 -9
  155. package/dist/templates/retail/tools/exchange_delivered_order_items.ts +22 -11
  156. package/dist/templates/retail/tools/find_user_id_by_email.ts +5 -9
  157. package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +5 -7
  158. package/dist/templates/retail/tools/get_item_details.ts +5 -7
  159. package/dist/templates/retail/tools/get_order_details.ts +5 -7
  160. package/dist/templates/retail/tools/get_product_details.ts +4 -7
  161. package/dist/templates/retail/tools/get_user_details.ts +4 -7
  162. package/dist/templates/retail/tools/list_all_product_types.ts +5 -7
  163. package/dist/templates/retail/tools/modify_pending_order_address.ts +4 -9
  164. package/dist/templates/retail/tools/modify_pending_order_items.ts +4 -16
  165. package/dist/templates/retail/tools/modify_pending_order_payment.ts +3 -9
  166. package/dist/templates/retail/tools/modify_user_address.ts +4 -9
  167. package/dist/templates/retail/tools/return_delivered_order_items.ts +3 -9
  168. package/dist/templates/retail/tools/transfer_to_human_agents.ts +1 -1
  169. package/dist/templates/solo-rpg/agent.test.ts +152 -29
  170. package/dist/templates/solo-rpg/agent.ts +3 -21
  171. package/dist/templates/solo-rpg/client.tsx +19 -6
  172. package/dist/templates/solo-rpg/shared.ts +19 -11
  173. package/dist/templates/solo-rpg/tools/action_roll.ts +2 -4
  174. package/dist/templates/solo-rpg/tools/burn_momentum.ts +2 -4
  175. package/dist/templates/solo-rpg/tools/check_state.ts +6 -6
  176. package/dist/templates/solo-rpg/tools/load_game.ts +1 -1
  177. package/dist/templates/solo-rpg/tools/oracle.ts +18 -11
  178. package/dist/templates/solo-rpg/tools/save_game.ts +10 -7
  179. package/dist/templates/solo-rpg/tools/setup_character.ts +1 -1
  180. package/dist/templates/solo-rpg/tools/update_state.ts +3 -4
  181. package/dist/templates/support-line/agent.test.ts +343 -0
  182. package/dist/templates/support-line/agent.ts +27 -0
  183. package/dist/templates/support-line/client.tsx +122 -0
  184. package/dist/templates/support-line/graph.ts +224 -0
  185. package/dist/templates/support-line/knowledge.json +65 -0
  186. package/dist/templates/support-line/prompts.ts +95 -0
  187. package/dist/templates/support-line/shared.ts +240 -0
  188. package/dist/templates/support-line/system-prompt.md +17 -0
  189. package/dist/templates/support-line/tools/answer_question.ts +72 -0
  190. package/dist/templates/support-line/tools/list_topics.ts +22 -0
  191. package/dist/templates/support-line/tools/log_ticket.ts +30 -0
  192. package/dist/templates/transcription-workflow/agent.test.ts +563 -0
  193. package/dist/templates/transcription-workflow/agent.ts +92 -0
  194. package/dist/templates/transcription-workflow/client.tsx +234 -0
  195. package/dist/templates/transcription-workflow/workflows/transcribe.ts +416 -0
  196. package/dist/templates/transcription-workflow/workflows/wav.ts +314 -0
  197. package/dist/templates/travel-concierge/agent.test.ts +338 -0
  198. package/dist/templates/travel-concierge/agent.ts +29 -0
  199. package/dist/templates/travel-concierge/client.tsx +161 -0
  200. package/dist/templates/travel-concierge/routing.ts +138 -0
  201. package/dist/templates/travel-concierge/shared.ts +547 -0
  202. package/dist/templates/travel-concierge/system-prompt.md +24 -0
  203. package/dist/templates/travel-concierge/tools/book_car_rental.ts +20 -0
  204. package/dist/templates/travel-concierge/tools/book_excursion.ts +18 -0
  205. package/dist/templates/travel-concierge/tools/book_hotel.ts +20 -0
  206. package/dist/templates/travel-concierge/tools/cancel_action.ts +7 -0
  207. package/dist/templates/travel-concierge/tools/cancel_ticket.ts +16 -0
  208. package/dist/templates/travel-concierge/tools/complete_or_escalate.ts +7 -0
  209. package/dist/templates/travel-concierge/tools/confirm_action.ts +7 -0
  210. package/dist/templates/travel-concierge/tools/lookup_booking.ts +35 -0
  211. package/dist/templates/travel-concierge/tools/search_car_rentals.ts +33 -0
  212. package/dist/templates/travel-concierge/tools/search_excursions.ts +37 -0
  213. package/dist/templates/travel-concierge/tools/search_flights.ts +43 -0
  214. package/dist/templates/travel-concierge/tools/search_hotels.ts +36 -0
  215. package/dist/templates/travel-concierge/tools/to_car_rental_assistant.ts +11 -0
  216. package/dist/templates/travel-concierge/tools/to_excursion_assistant.ts +11 -0
  217. package/dist/templates/travel-concierge/tools/to_flight_assistant.ts +11 -0
  218. package/dist/templates/travel-concierge/tools/to_hotel_assistant.ts +11 -0
  219. package/dist/templates/travel-concierge/tools/update_ticket.ts +22 -0
  220. package/dist/templates/web-researcher/agent.ts +0 -2
  221. package/dist/templates/web-researcher/system-prompt.md +10 -0
  222. package/dist/{test-2LWBcXvY.mjs → test-B6ZBxSpk.mjs} +2 -2
  223. package/dist/typecheck.mjs +17 -15
  224. package/dist/worker-bundler-CIdDhZ0c.mjs +398 -0
  225. package/dist/worker-bundler.d.ts +14 -0
  226. package/dist/worker-bundler.mjs +1 -108
  227. package/dist/workflow-B-O97AqI.mjs +125 -0
  228. package/dist/workflow-bundler.d.ts +133 -0
  229. package/dist/workflow.d.ts +67 -0
  230. package/package.json +13 -9
  231. package/dist/build-DQJ1ZSm9.mjs +0 -39
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as serverOrigin, i as readProjectConfig, n as ensureApiKey, r as readGlobalConfig, t as approveServer } from "./_config-CzwAWOWr.mjs";
2
+ import { a as serverOrigin, i as readProjectConfig, n as ensureApiKey, r as readGlobalConfig, t as approveServer } from "./_config-D_s09e7g.mjs";
3
3
  import { existsSync } from "node:fs";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
@@ -100,16 +100,34 @@ async function resolveDeployTarget(cwd, explicitServer) {
100
100
  apiKey: await ensureApiKey()
101
101
  };
102
102
  }
103
+ /**
104
+ * The deployed slug a slug-scoped command needs, or the sentence naming
105
+ * `aai publish`.
106
+ *
107
+ * A pulled-but-never-published project has a config without a slug — for
108
+ * slug-scoped commands (secret, storage, delete) that is the same as never
109
+ * deployed. Exported so a command that has ALREADY resolved the target
110
+ * (`aai delete`) can demand the slug without resolving it a second time; one
111
+ * sentence, one place.
112
+ *
113
+ * The slug's SHAPE was already enforced by `resolveDeployTarget`, for every
114
+ * command rather than only the slug-scoped ones — see `assertValidConfigSlug`.
115
+ * Deliberately not re-checked here: two copies of that guard is what let
116
+ * `publish` ship without one.
117
+ */
118
+ function requireDeployedSlug(config) {
119
+ if (!config?.slug) throw new Error("This project has no deployed agent — run `aai publish` first");
120
+ return config.slug;
121
+ }
103
122
  /** Like resolveDeployTarget, but requires an existing deployment (project config). */
104
123
  async function getServerInfo(cwd, explicitServer) {
105
124
  const { config, serverUrl, apiKey } = await resolveDeployTarget(cwd, explicitServer);
106
- if (!config?.slug) throw new Error("This project has no deployed agent — run `aai publish` first");
107
125
  return {
108
126
  serverUrl,
109
- slug: config.slug,
127
+ slug: requireDeployedSlug(config),
110
128
  apiKey,
111
- studioProject: config.studioProject
129
+ studioProject: config?.studioProject
112
130
  };
113
131
  }
114
132
  //#endregion
115
- export { resolveServerUrl as a, resolveDeployTarget as i, getServerInfo as n, isDevMode as r, getMonorepoRoot as t };
133
+ export { resolveDeployTarget as a, requireDeployedSlug as i, getServerInfo as n, resolveServerUrl as o, isDevMode as r, getMonorepoRoot as t };
package/dist/_agent.d.ts CHANGED
@@ -33,6 +33,24 @@ export declare function resolveDeployTarget(cwd: string, explicitServer?: string
33
33
  serverUrl: string;
34
34
  apiKey: string;
35
35
  }>;
36
+ /**
37
+ * The deployed slug a slug-scoped command needs, or the sentence naming
38
+ * `aai publish`.
39
+ *
40
+ * A pulled-but-never-published project has a config without a slug — for
41
+ * slug-scoped commands (secret, storage, delete) that is the same as never
42
+ * deployed. Exported so a command that has ALREADY resolved the target
43
+ * (`aai delete`) can demand the slug without resolving it a second time; one
44
+ * sentence, one place.
45
+ *
46
+ * The slug's SHAPE was already enforced by `resolveDeployTarget`, for every
47
+ * command rather than only the slug-scoped ones — see `assertValidConfigSlug`.
48
+ * Deliberately not re-checked here: two copies of that guard is what let
49
+ * `publish` ship without one.
50
+ */
51
+ export declare function requireDeployedSlug(config: {
52
+ slug?: string | undefined;
53
+ } | null): string;
36
54
  /** Like resolveDeployTarget, but requires an existing deployment (project config). */
37
55
  export declare function getServerInfo(cwd: string, explicitServer?: string): Promise<{
38
56
  serverUrl: string;
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { omitUndefined } from "@alexkroman1/aai/utils";
2
+ import { t as CliError } from "./_output-CKkmWs7i.mjs";
3
+ import { isRecord, omitUndefined } from "@alexkroman1/aai/utils";
3
4
  import { FetchError, ofetch } from "ofetch";
4
5
  //#region _api-client.ts
5
6
  /**
@@ -13,6 +14,17 @@ const HINT_INVALID_API_KEY = "Your API key may be invalid. Run `aai` to re-enter
13
14
  /** 404 hint for requests scoped to a deployed agent's slug. */
14
15
  const HINT_NOT_DEPLOYED = "The agent may not be deployed. Check `.aai/project.json` for the correct slug.";
15
16
  /**
17
+ * {@link ApiTestSeam} as request options, with absent entries OMITTED rather
18
+ * than passed as `undefined` (which `exactOptionalPropertyTypes` refuses and
19
+ * which would override `apiRequest`'s own defaults with nothing).
20
+ */
21
+ function apiTestSeam(opts) {
22
+ return omitUndefined({
23
+ fetch: opts.fetch,
24
+ retryDelay: opts.retryDelay
25
+ });
26
+ }
27
+ /**
16
28
  * Send an authenticated request to the platform API and return the parsed
17
29
  * JSON response. Throws a descriptive error with status-specific hints on
18
30
  * failure (the 401 hint is always included; pass more via `hints`).
@@ -36,6 +48,32 @@ async function apiRequest(url, opts) {
36
48
  }
37
49
  }
38
50
  /**
51
+ * A 2xx body CHECKED against the shape the caller was promised, rather than
52
+ * cast to it.
53
+ *
54
+ * `apiRequest<T>` is a cast: `T` describes what the platform sends and nothing
55
+ * verifies it, so a 200 from something that is not our server — an intercepting
56
+ * proxy, a captive portal, a mismatched or half-deployed backend — flows on as
57
+ * a fully-typed object whose fields are `undefined`. That is not hypothetical
58
+ * and not merely cosmetic: a deploy whose response lacked `slug` printed
59
+ * `Deployed https://server/undefined` and wrote `slug: undefined` into
60
+ * `.aai/project.json`, where `JSON.stringify` DROPS it — so the next deploy saw
61
+ * no slug, minted a fresh one, and orphaned the running agent. `aai publish`
62
+ * grew a hand-written guard for exactly that (`studio.ts`); this is that guard
63
+ * with a name, so the next response shape gets it without the incident first.
64
+ *
65
+ * The predicate is the caller's because the shape is: this only owns the
66
+ * failure, whose code and hint are the same wherever it fires.
67
+ */
68
+ function checkedResponse(value, isExpected, what) {
69
+ if (isExpected(value)) return value;
70
+ throw new CliError("bad_response", `Unexpected response from ${what}.`, "Check that --server points at an aai platform server, then try again.");
71
+ }
72
+ /** True when every element of `value` is a string (an empty array qualifies). */
73
+ function isStringArray(value) {
74
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string");
75
+ }
76
+ /**
39
77
  * Collapse `{"error": "..."}` payloads embedded in a message into their own
40
78
  * text. Studio Publish runs the real `aai deploy` inside the sandbox, so its
41
79
  * failures arrive wrapped twice and stringifying them produced a
@@ -57,11 +95,10 @@ function unwrapEmbeddedErrors(message, depth = 0) {
57
95
  /** The messages of a Zod issue tree, deduped and flattened. */
58
96
  function zodIssueMessages(value) {
59
97
  if (Array.isArray(value)) return value.flatMap(zodIssueMessages);
60
- if (value === null || typeof value !== "object") return [];
61
- const node = value;
62
- const nested = zodIssueMessages(node.issues);
98
+ if (!isRecord(value)) return [];
99
+ const nested = zodIssueMessages(value.issues);
63
100
  if (nested.length > 0) return nested;
64
- return typeof node.message === "string" ? [node.message] : [];
101
+ return typeof value.message === "string" ? [value.message] : [];
65
102
  }
66
103
  /**
67
104
  * A human-readable one-liner for a server error body.
@@ -74,10 +111,10 @@ function zodIssueMessages(value) {
74
111
  */
75
112
  function describeErrorBody(data) {
76
113
  if (typeof data === "string") return data;
77
- if (data === null || typeof data !== "object") return JSON.stringify(data ?? "");
114
+ if (!isRecord(data)) return JSON.stringify(data ?? "");
78
115
  const error = data.error;
79
116
  if (typeof error === "string") return unwrapEmbeddedErrors(error);
80
- if (error !== null && typeof error === "object") {
117
+ if (isRecord(error)) {
81
118
  const { message } = error;
82
119
  if (typeof message === "string") {
83
120
  try {
@@ -102,4 +139,4 @@ function toApiError(err, url, opts) {
102
139
  return new Error(`${opts.action} failed: could not reach ${url}\n ${hint}`, { cause });
103
140
  }
104
141
  //#endregion
105
- export { apiRequest as n, HINT_NOT_DEPLOYED as t };
142
+ export { isStringArray as a, checkedResponse as i, apiRequest as n, apiTestSeam as r, HINT_NOT_DEPLOYED as t };
@@ -35,12 +35,51 @@ export type ApiRequestOptions = {
35
35
  */
36
36
  allow404?: boolean;
37
37
  };
38
+ /**
39
+ * The two options a caller only ever supplies from a TEST — a fetch
40
+ * implementation and a zeroed retry delay.
41
+ *
42
+ * ONE declaration, so a wrapper around `apiRequest` (`runDeploy`, `runDelete`)
43
+ * says "plus the test seam" instead of restating both fields and both doc
44
+ * comments. They were written out three times, and the two copies were already
45
+ * spelled differently from each other.
46
+ */
47
+ export type ApiTestSeam = Pick<ApiRequestOptions, "fetch" | "retryDelay">;
48
+ /**
49
+ * {@link ApiTestSeam} as request options, with absent entries OMITTED rather
50
+ * than passed as `undefined` (which `exactOptionalPropertyTypes` refuses and
51
+ * which would override `apiRequest`'s own defaults with nothing).
52
+ */
53
+ export declare function apiTestSeam(opts: ApiTestSeam): ApiTestSeam;
38
54
  /**
39
55
  * Send an authenticated request to the platform API and return the parsed
40
56
  * JSON response. Throws a descriptive error with status-specific hints on
41
57
  * failure (the 401 hint is always included; pass more via `hints`).
42
58
  */
43
59
  export declare function apiRequest<T = unknown>(url: string, opts: ApiRequestOptions): Promise<T>;
60
+ /**
61
+ * A 2xx body CHECKED against the shape the caller was promised, rather than
62
+ * cast to it.
63
+ *
64
+ * `apiRequest<T>` is a cast: `T` describes what the platform sends and nothing
65
+ * verifies it, so a 200 from something that is not our server — an intercepting
66
+ * proxy, a captive portal, a mismatched or half-deployed backend — flows on as
67
+ * a fully-typed object whose fields are `undefined`. That is not hypothetical
68
+ * and not merely cosmetic: a deploy whose response lacked `slug` printed
69
+ * `Deployed https://server/undefined` and wrote `slug: undefined` into
70
+ * `.aai/project.json`, where `JSON.stringify` DROPS it — so the next deploy saw
71
+ * no slug, minted a fresh one, and orphaned the running agent. `aai publish`
72
+ * grew a hand-written guard for exactly that (`studio.ts`); this is that guard
73
+ * with a name, so the next response shape gets it without the incident first.
74
+ *
75
+ * The predicate is the caller's because the shape is: this only owns the
76
+ * failure, whose code and hint are the same wherever it fires.
77
+ */
78
+ export declare function checkedResponse<T>(value: unknown, isExpected: (value: unknown) => value is T,
79
+ /** The route, named as a human would say it — "the deploy route at <url>". */
80
+ what: string): T;
81
+ /** True when every element of `value` is a string (an empty array qualifies). */
82
+ export declare function isStringArray(value: unknown): value is string[];
44
83
  /**
45
84
  * A human-readable one-liner for a server error body.
46
85
  *
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { u as validateAgentExport } from "./_utils-0DHbcqUb.mjs";
3
- import { t as buildClient } from "./client-bundler-C9qXL8A2.mjs";
4
- import { buildWorker } from "./worker-bundler.mjs";
2
+ import { f as validateAgentExport } from "./_utils-B8QmtFhK.mjs";
3
+ import { t as buildClient } from "./client-bundler-B8Q9F8gJ.mjs";
4
+ import { n as buildWorkflows, t as buildWorker } from "./worker-bundler-CIdDhZ0c.mjs";
5
5
  import path from "node:path";
6
6
  import { pathToFileURL } from "node:url";
7
7
  import { mkdtemp, rm, writeFile } from "node:fs/promises";
@@ -17,10 +17,14 @@ import { hash } from "node:crypto";
17
17
  * config and evaluates nothing, so every read of one happens here.
18
18
  */
19
19
  async function buildAgentBundle(cwd, opts = {}) {
20
- const [worker, clientFiles] = await Promise.all([buildWorker(cwd, opts), buildClient(cwd)]);
20
+ const [workflows, clientFiles] = await Promise.all([buildWorkflows(cwd), buildClient(cwd)]);
21
21
  return {
22
- worker,
23
- clientFiles
22
+ worker: await buildWorker(cwd, {
23
+ ...opts,
24
+ workflows
25
+ }),
26
+ clientFiles,
27
+ ...workflows && { workflows }
24
28
  };
25
29
  }
26
30
  /**
@@ -47,6 +51,26 @@ async function evalWorkerBundle(code) {
47
51
  validateAgentExport(agentDef);
48
52
  return agentDef;
49
53
  }
54
+ /**
55
+ * {@link evalWorkerBundle}, keeping the workflow exports the AgentDef cannot
56
+ * carry.
57
+ *
58
+ * A separate function rather than a wider return from `evalWorkerBundle`,
59
+ * because every other caller wants the agent and nothing else — and the two
60
+ * strings are ~76 KB that a deploy path has no use for.
61
+ */
62
+ async function evalWorkerWithWorkflows(code) {
63
+ const mod = await importWorkerModule(code);
64
+ const agentDef = mod.default ?? mod;
65
+ validateAgentExport(agentDef);
66
+ const workflowCode = mod.__aaiWorkflowCode;
67
+ const stepCode = mod.__aaiStepCode;
68
+ return {
69
+ agent: agentDef,
70
+ workflowCode: typeof workflowCode === "string" ? workflowCode : void 0,
71
+ stepCode: typeof stepCode === "string" ? stepCode : void 0
72
+ };
73
+ }
50
74
  /** Import a built worker from a temp file. See {@link evalWorkerBundle}. */
51
75
  async function importWorkerModule(code) {
52
76
  const dir = await mkdtemp(path.join(tmpdir(), "aai-worker-"));
@@ -78,23 +102,27 @@ async function evalWorkerConfig(code) {
78
102
  return (await importWorkerModule(code)).__aaiConfig;
79
103
  }
80
104
  /**
81
- * Memoizing wrapper around `evalWorkerBundle` for long-lived callers (the
82
- * dev server): byte-identical worker code returns the previously evaluated
83
- * AgentDef without touching the ESM registry. No-op saves and formatter
105
+ * Memoizing wrapper around `evalWorkerWithWorkflows` for long-lived callers
106
+ * (the dev server): byte-identical worker code returns the previously
107
+ * evaluated result without touching the ESM registry. No-op saves and formatter
84
108
  * churn are the common watcher events, so this caps the registry leak (see
85
109
  * `evalWorkerBundle`) to genuinely-new bundles — the residual one-module-per-
86
110
  * distinct-build leak is accepted for the reasons documented there.
111
+ *
112
+ * It evaluates the WORKFLOW-carrying variant because its one caller is
113
+ * `aai dev`, which has no guest to re-read the bundle: dropping the two strings
114
+ * here is what left the dev server unable to serve a workflow at all.
87
115
  */
88
116
  function createWorkerEvaluator() {
89
117
  let lastHash;
90
- let lastAgentDef;
118
+ let lastWorker;
91
119
  return async (code) => {
92
120
  const codeHash = hash("sha256", code);
93
- if (lastAgentDef && codeHash === lastHash) return lastAgentDef;
94
- const agentDef = await evalWorkerBundle(code);
121
+ if (lastWorker && codeHash === lastHash) return lastWorker;
122
+ const worker = await evalWorkerWithWorkflows(code);
95
123
  lastHash = codeHash;
96
- lastAgentDef = agentDef;
97
- return agentDef;
124
+ lastWorker = worker;
125
+ return worker;
98
126
  };
99
127
  }
100
128
  //#endregion
@@ -1,11 +1,21 @@
1
1
  import type { AgentDef } from "@alexkroman1/aai";
2
2
  import { type BuildWorkerOptions } from "./worker-bundler.ts";
3
- /** Output from the bundler: worker ESM + client files. */
3
+ import { type WorkflowBundleOutput } from "./workflow-bundler.ts";
4
+ /** Output from the bundler: worker ESM + client files + workflow artifacts. */
4
5
  export type DirectoryBundleOutput = {
5
6
  /** ESM bundle of agent.ts (tool execute functions + hook handlers). */
6
7
  worker: string;
7
8
  /** Static client files from Vite build. Empty if no client.tsx. */
8
9
  clientFiles: Record<string, string>;
10
+ /**
11
+ * The project's durable workflows, or undefined when it declares none.
12
+ *
13
+ * Built here rather than in the guest because the transform is per TENANT: the
14
+ * guest image is baked once and serves many agents, so there is no
15
+ * `workflows/` directory in existence when it is built. See
16
+ * `workflow-bundler.ts`.
17
+ */
18
+ workflows?: WorkflowBundleOutput;
9
19
  };
10
20
  /**
11
21
  * Bundle an agent directory: build agent.ts into worker ESM + client files.
@@ -35,6 +45,30 @@ export declare function buildAgentBundle(cwd: string, opts?: BuildWorkerOptions)
35
45
  * experimental.
36
46
  */
37
47
  export declare function evalWorkerBundle(code: string): Promise<AgentDef>;
48
+ /**
49
+ * The agent plus the compiled workflow surface a worker carries.
50
+ *
51
+ * `evalWorkerBundle` returns only the `AgentDef`, which is all a deploy needs —
52
+ * the guest re-reads the bundle itself. `aai dev` never hands the bundle to a
53
+ * guest, so this is the only path by which the workflow code reaches a running
54
+ * server locally, and dropping it is why `aai dev` served no workflows at first.
55
+ */
56
+ export type EvaluatedWorker = {
57
+ agent: AgentDef;
58
+ /** `__aaiWorkflowCode` — the flow bundle, for `workflowEntrypoint`. */
59
+ workflowCode: string | undefined;
60
+ /** `__aaiStepCode` — evaluated to register the project's step functions. */
61
+ stepCode: string | undefined;
62
+ };
63
+ /**
64
+ * {@link evalWorkerBundle}, keeping the workflow exports the AgentDef cannot
65
+ * carry.
66
+ *
67
+ * A separate function rather than a wider return from `evalWorkerBundle`,
68
+ * because every other caller wants the agent and nothing else — and the two
69
+ * strings are ~76 KB that a deploy path has no use for.
70
+ */
71
+ export declare function evalWorkerWithWorkflows(code: string): Promise<EvaluatedWorker>;
38
72
  /**
39
73
  * The config a built worker describes about itself — its `__aaiConfig`
40
74
  * export, generated by `buildWorker`'s wrapper entry (`toAgentConfig(def)`
@@ -50,11 +84,15 @@ export declare function evalWorkerBundle(code: string): Promise<AgentDef>;
50
84
  */
51
85
  export declare function evalWorkerConfig(code: string): Promise<unknown>;
52
86
  /**
53
- * Memoizing wrapper around `evalWorkerBundle` for long-lived callers (the
54
- * dev server): byte-identical worker code returns the previously evaluated
55
- * AgentDef without touching the ESM registry. No-op saves and formatter
87
+ * Memoizing wrapper around `evalWorkerWithWorkflows` for long-lived callers
88
+ * (the dev server): byte-identical worker code returns the previously
89
+ * evaluated result without touching the ESM registry. No-op saves and formatter
56
90
  * churn are the common watcher events, so this caps the registry leak (see
57
91
  * `evalWorkerBundle`) to genuinely-new bundles — the residual one-module-per-
58
92
  * distinct-build leak is accepted for the reasons documented there.
93
+ *
94
+ * It evaluates the WORKFLOW-carrying variant because its one caller is
95
+ * `aai dev`, which has no guest to re-read the bundle: dropping the two strings
96
+ * here is what left the dev server unable to serve a workflow at all.
59
97
  */
60
- export declare function createWorkerEvaluator(): (code: string) => Promise<AgentDef>;
98
+ export declare function createWorkerEvaluator(): (code: string) => Promise<EvaluatedWorker>;
@@ -1,4 +1,4 @@
1
- import type { ArgsDef, CommandDef } from "citty";
1
+ import { type ArgsDef, type CommandDef, type CommandMeta, type ParsedArgs } from "citty";
2
2
  import { type CommandResult, type OutputMode } from "./_output.ts";
3
3
  /** Shared arg definitions for citty commands. */
4
4
  export declare const sharedArgs: {
@@ -33,6 +33,17 @@ export declare function findUnknownFlags(rawArgs: string[], argsDef: ArgsDef): s
33
33
  * reads `subCommands` and `args`, and the concrete generics differ per command.
34
34
  */
35
35
  type AnyCommandDef = CommandDef<ArgsDef>;
36
+ /**
37
+ * `aai secret put` for a subcommand, `aai` for the root — the name to put in
38
+ * front of `--help` when telling someone which usage to read.
39
+ *
40
+ * Beside {@link resolve} because it is the same job: `meta` is a `Resolvable`,
41
+ * so only the plain-object form carries a name synchronously and anything else
42
+ * degrades to the binary name rather than blocking an error path on a resolve.
43
+ * This module owns citty's `Resolvable` handling; `cli.ts` had its own copy of
44
+ * the narrowing.
45
+ */
46
+ export declare function commandPath<T extends ArgsDef>(cmd: CommandDef<T>, parent?: CommandDef<T>): string;
36
47
  /**
37
48
  * Unknown flags in `argv` for whichever (possibly nested) subcommand it
38
49
  * selects — `[]` when everything is declared.
@@ -56,10 +67,63 @@ export declare function setup(opts?: {
56
67
  * key, and commands with no platform traffic never prompt at all.
57
68
  *
58
69
  * A thrown error and a returned `fail(...)` converge here on one emitter:
59
- * human mode logs the message, JSON mode writes exactly one result line,
60
- * and both exit 1.
70
+ * human mode logs the message and the hint, JSON mode writes exactly one
71
+ * result line, and both exit 1.
72
+ *
73
+ * That convergence is the whole contract and it was HALF TRUE for a long
74
+ * time: only the `catch` printed anything, so a body that RETURNED a failure
75
+ * fell straight through to the JSON check and `process.exit(1)`. In human
76
+ * mode that is a bare exit 1 with an empty terminal — `aai test` with no
77
+ * runner binary on PATH (message and "is it on your PATH?" both discarded),
78
+ * every `aai workflow` verb against a booting sandbox (the agent's own
79
+ * sentence plus `HINT_BROKER` discarded), `aai secret put` with an empty
80
+ * value, and `aai storage disable`'s "re-run with --force". Nine paths, all
81
+ * silent. Emitting is therefore driven by the RESULT rather than by which
82
+ * arm produced it.
61
83
  */
62
84
  export declare function runCommand(args: {
63
85
  json?: boolean | undefined;
64
86
  }, fn: (mode: OutputMode) => Promise<CommandResult<unknown>>): Promise<void>;
87
+ /**
88
+ * What a command needs from the working directory, stated rather than
89
+ * re-derived:
90
+ *
91
+ * - `"agent"` — an agent project. Refuses a directory with no `agent.ts`.
92
+ * - `"any"` — the working directory, whatever is in it.
93
+ * - `"none"` — no directory policy; the body resolves whatever it needs.
94
+ */
95
+ export type CwdPolicy = "agent" | "any" | "none";
96
+ /** `undefined` under `"none"`, so a body cannot read a cwd it never asked for. */
97
+ type CwdFor<P extends CwdPolicy> = P extends "none" ? undefined : string;
98
+ /** Everything a command body gets, in one argument. */
99
+ export type ExecContext<T extends ArgsDef, P extends CwdPolicy> = {
100
+ args: ParsedArgs<T>;
101
+ mode: OutputMode;
102
+ cwd: CwdFor<P>;
103
+ };
104
+ /**
105
+ * Define a citty command whose body is an EXECUTOR — the shape all
106
+ * twenty-four of this CLI's leaf commands have.
107
+ *
108
+ * Written longhand, each one restates the same four lines (`async run({ args
109
+ * })`, `await runCommand(args, async (mode) => …)`, a cwd resolution, a lazy
110
+ * `await import`) and, in the middle of them, DECIDES the working-directory
111
+ * policy — which is the part that is not boilerplate and the part that has
112
+ * been wrong in production: `aai test` shipped calling `setup()` bare instead
113
+ * of `setup({ agent: true })`, so in a directory with no `agent.ts` it found
114
+ * no test file, reported `{ passed: true, skipped: true }` and exited 0. A
115
+ * green result for a project that is not there reads, in CI, exactly like a
116
+ * passing suite.
117
+ *
118
+ * `cwd` is a required field of the spec, so that decision is stated once per
119
+ * command in the one place a reader looks for it, and "forgot to pass
120
+ * `{ agent: true }`" stops being a representable mistake — the alternative
121
+ * spelling is a policy name that does not exist.
122
+ */
123
+ export declare function defineExec<const T extends ArgsDef, P extends CwdPolicy>(spec: {
124
+ meta: CommandMeta;
125
+ args: T;
126
+ cwd: P;
127
+ run: (ctx: ExecContext<T, P>) => Promise<CommandResult<unknown>>;
128
+ }): CommandDef<T>;
65
129
  export {};
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { t as CliError } from "./_output-CC300DzW.mjs";
3
- import { a as errorMessage, c as readJson, d as writeJson } from "./_utils-0DHbcqUb.mjs";
2
+ import { t as CliError } from "./_output-CKkmWs7i.mjs";
3
+ import { c as isEexist, l as readJson, o as errorMessage, p as writeJson } from "./_utils-B8QmtFhK.mjs";
4
4
  import { mkdtempSync } from "node:fs";
5
5
  import path from "node:path";
6
6
  import fs from "node:fs/promises";
7
7
  import { tmpdir } from "node:os";
8
- import { setTimeout } from "node:timers/promises";
8
+ import { sleep } from "@alexkroman1/aai/internal";
9
9
  import envPaths from "env-paths";
10
10
  import { z } from "zod";
11
11
  //#region _config.ts
@@ -146,32 +146,50 @@ const CONFIG_LOCK_STALE_MS = 1e4;
146
146
  */
147
147
  async function withGlobalConfigLock(dir, fn) {
148
148
  const lockPath = path.join(dir, "config.lock");
149
+ const held = await acquireConfigLock(dir, lockPath);
150
+ try {
151
+ return await fn();
152
+ } finally {
153
+ if (held) await fs.rm(lockPath, { force: true }).catch(() => void 0);
154
+ }
155
+ }
156
+ /** True when this process created the lockfile; false means "proceed unlocked". */
157
+ async function acquireConfigLock(dir, lockPath) {
149
158
  const deadline = Date.now() + CONFIG_LOCK_TIMEOUT_MS;
150
- let held = false;
151
159
  for (;;) try {
152
160
  await fs.mkdir(dir, {
153
161
  recursive: true,
154
162
  mode: 448
155
163
  });
156
164
  await (await fs.open(lockPath, "wx", 384)).close();
157
- held = true;
158
- break;
165
+ return true;
159
166
  } catch (err) {
160
- if (err.code !== "EEXIST") break;
161
- if (await fs.stat(lockPath).then((s) => Date.now() - s.mtimeMs).catch(() => 0) > CONFIG_LOCK_STALE_MS) {
162
- await fs.rm(lockPath, { force: true }).catch(() => void 0);
163
- continue;
164
- }
165
- if (Date.now() >= deadline) break;
166
- await setTimeout(CONFIG_LOCK_RETRY_MS);
167
- }
168
- try {
169
- return await fn();
170
- } finally {
171
- if (held) await fs.rm(lockPath, { force: true }).catch(() => void 0);
167
+ if (!isEexist(err)) return false;
168
+ if (!await contendForLock(lockPath, deadline)) return false;
172
169
  }
173
170
  }
174
171
  /**
172
+ * One turn of the contention loop: break the lock if it is stale, otherwise
173
+ * wait a beat. False means STOP — the deadline passed, or the lock cannot be
174
+ * broken at all.
175
+ *
176
+ * That second case is the one worth naming. `fs.rm`'s `force` masks only
177
+ * ENOENT and there is no `recursive`, so an entry that is not an ordinary file
178
+ * — a `config.lock` DIRECTORY, an immutable or permission-denied file — makes
179
+ * the removal throw every time. Swallowing that throw and looping restarted
180
+ * the loop ABOVE the deadline check, so `aai login` (and every `--server`
181
+ * invocation) spun in a tight async loop with no output and no exit: bounded
182
+ * acquisition, the contract stated above, held in every case except the one
183
+ * where breaking the lock is impossible. The removal's OUTCOME is what decides
184
+ * whether looping is progress.
185
+ */
186
+ async function contendForLock(lockPath, deadline) {
187
+ if (await fs.stat(lockPath).then((s) => Date.now() - s.mtimeMs).catch(() => 0) > CONFIG_LOCK_STALE_MS) return await fs.rm(lockPath, { force: true }).then(() => true, () => false) && Date.now() < deadline;
188
+ if (Date.now() >= deadline) return false;
189
+ await sleep(CONFIG_LOCK_RETRY_MS);
190
+ return true;
191
+ }
192
+ /**
175
193
  * Apply `update` to the global config under the cross-process lock, re-reading
176
194
  * inside it so the merge is against current contents rather than a snapshot
177
195
  * taken before the lock was held. Every read-modify-write of the global config
package/dist/_deploy.d.ts CHANGED
@@ -1,5 +1,6 @@
1
+ import { type ApiTestSeam } from "./_api-client.ts";
1
2
  import type { DirectoryBundleOutput } from "./_bundler.ts";
2
- export type DeployOpts = {
3
+ export type DeployOpts = ApiTestSeam & {
3
4
  url: string;
4
5
  bundle: DirectoryBundleOutput;
5
6
  /** Env var values from .env to send to the server. */
@@ -14,10 +15,6 @@ export type DeployOpts = {
14
15
  * by the studio's own in-guest deploy, not by ordinary users.
15
16
  */
16
17
  allowPreviewSlug?: boolean;
17
- /** Retry delay override for tests (0 = no real sleeps on retry paths). */
18
- retryDelay?: number;
19
- /** Optional fetch implementation for testing. Defaults to globalThis.fetch. */
20
- fetch?: typeof globalThis.fetch;
21
18
  };
22
19
  export type DeployResult = {
23
20
  slug: string;
@@ -31,7 +31,12 @@ export type RestartOps<S> = {
31
31
  * swallowed so one leak can't strand the others.
32
32
  */
33
33
  teardown?: () => Promise<void>;
34
- /** Injectable so retry specs don't sleep real wall-clock. */
34
+ /**
35
+ * Injectable so retry specs need neither wall-clock nor fake timers. The
36
+ * default is the repo's one `sleep`, which virtual time CAN drive — this seam
37
+ * predates it and existed because `node:timers/promises` cannot be driven at
38
+ * all (see `aai/sdk/sleep.ts`).
39
+ */
35
40
  sleep?: (ms: number) => Promise<void>;
36
41
  };
37
42
  export type RestartSupervisor<S> = {