@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
@@ -35,7 +35,15 @@
35
35
  * agent that can rebook a flight and one that can rebook a flight *by mistake*.
36
36
  */
37
37
 
38
- import { type DeepReadonly, pushCapped, sessionSlot, type ToolFailure } from "@alexkroman1/aai";
38
+ import {
39
+ type DeepReadonly,
40
+ type DialogSpec,
41
+ dialog,
42
+ pushCapped,
43
+ sessionSlot,
44
+ type ToolContext,
45
+ type ToolFailure,
46
+ } from "@alexkroman1/aai";
39
47
 
40
48
  // ─── The booking world ───────────────────────────────────────────────────────
41
49
  // Their notebook downloads a sqlite database of a real airline's schedule and
@@ -341,6 +349,51 @@ export function note(state: TripState, entry: string): void {
341
349
 
342
350
  // ─── The confirmation gate ───────────────────────────────────────────────────
343
351
 
352
+ /**
353
+ * The gate as a STATE MAP — their `interrupt_before`, which is a graph construct.
354
+ *
355
+ * This port had to hand-roll it: a voice session had no graph, so "halt before a
356
+ * sensitive tool and resume on approval" became "every sensitive tool STAGES and
357
+ * `confirm_action` is the only thing that applies one". That half is unchanged
358
+ * and is still the mechanism — see {@link stageAction}. What the machine adds is
359
+ * the PRECONDITION on the other side of the halt: `confirm_action` and
360
+ * `cancel_action` are legal only while something is actually waiting, which was
361
+ * a null check inside each of them and is now `when`.
362
+ *
363
+ * **The staging tools are deliberately NOT gated on `browsing`.**
364
+ * {@link stageAction}'s refusal NAMES the sentence already waiting, which is what
365
+ * lets the model settle that one first and restage the rest; a state gate can
366
+ * only say "you are in awaitingConfirmation", since a state's instruction is
367
+ * static. So the specific refusal stays where it is and the flow follows it.
368
+ */
369
+ const gateSpec = {
370
+ initial: "browsing",
371
+ states: {
372
+ browsing: {
373
+ instruction:
374
+ "Nothing is waiting for the caller's yes. Stage a change with a booking tool first.",
375
+ on: { STAGED: "awaitingConfirmation" },
376
+ },
377
+ awaitingConfirmation: {
378
+ instruction:
379
+ "Read the staged change back and hear a clear yes or no, then use confirm_action or cancel_action.",
380
+ on: { SETTLED: "browsing" },
381
+ },
382
+ },
383
+ } as const satisfies DialogSpec;
384
+
385
+ /**
386
+ * Whether a change is waiting on the caller's word.
387
+ *
388
+ * Its own slot, beside {@link tripSlot}: the flow holds the POSITION and the
389
+ * trip holds the staged action itself, because an inspectable
390
+ * {@link PendingAction} is what `confirm_action` re-derives the effect from. One
391
+ * tool call always moves both — {@link stageAction} sends `STAGED` in the same
392
+ * synchronous window it writes `pending` in, and the two settling tools send
393
+ * `SETTLED` only on success.
394
+ */
395
+ export const gateFlow = dialog("gate", gateSpec);
396
+
344
397
  /**
345
398
  * Describe a staged action in one sentence, in the second person — this is
346
399
  * read aloud, so it is a question's worth of text and not a receipt.
@@ -396,6 +449,7 @@ export function describeAction(action: DeepReadonly<PendingAction>): string | To
396
449
  * model ask about that one first and restage the rest afterwards.
397
450
  */
398
451
  export function stageAction(
452
+ ctx: ToolContext,
399
453
  state: TripState,
400
454
  action: PendingAction,
401
455
  ):
@@ -413,6 +467,11 @@ export function stageAction(
413
467
  };
414
468
  }
415
469
  state.pending = action;
470
+ // The flow moves in the SAME synchronous window `pending` is written in, so
471
+ // the position and the payload cannot be observed disagreeing. `gateFlow` is
472
+ // a different slot from `tripSlot`, so this is not a nested write to the draft
473
+ // being held — the open-draft guard is per slot.
474
+ gateFlow.send(ctx, { type: "STAGED" });
416
475
  note(state, `Awaiting confirmation: ${described}`);
417
476
  return {
418
477
  awaitingConfirmation: true,
@@ -421,13 +480,21 @@ export function stageAction(
421
480
  };
422
481
  }
423
482
 
424
- /** Apply the staged action. The ONE place any of them takes effect. */
483
+ /**
484
+ * Apply the staged action. The ONE place any of them takes effect.
485
+ *
486
+ * The "nothing is waiting" guard is gone: `confirm_action` is gated on
487
+ * `awaitingConfirmation`, so reaching here means something IS staged. The
488
+ * remaining `!action` arm is the flow and the payload disagreeing, which one
489
+ * synchronous window per transition is what rules out — it reports rather than
490
+ * throwing, because a live call is the wrong place to find out.
491
+ */
425
492
  export function applyPending(
426
493
  state: TripState,
427
494
  ): { applied: string; reference?: string } | ToolFailure {
428
495
  const action = state.pending;
429
496
  if (!action) {
430
- return { error: "Nothing is waiting for confirmation. Use the booking tool first." };
497
+ return { error: "Nothing is staged after all ask the caller again what they want." };
431
498
  }
432
499
  const described = describeAction(action);
433
500
  if (typeof described !== "string") return described;
@@ -10,8 +10,8 @@ export default tripSlot.updateTool({
10
10
  carId: z.string().max(20).describe("The car id from search_car_rentals, e.g. 'C2'"),
11
11
  days: z.number().int().min(1).max(60).describe("How many days"),
12
12
  }),
13
- execute(args, trip) {
14
- return stageAction(trip, {
13
+ execute(args, trip, ctx) {
14
+ return stageAction(ctx, trip, {
15
15
  kind: "book_car",
16
16
  carId: args.carId.toUpperCase(),
17
17
  days: args.days,
@@ -9,8 +9,8 @@ export default tripSlot.updateTool({
9
9
  inputSchema: z.object({
10
10
  excursionId: z.string().max(20).describe("The excursion id from search_excursions, e.g. 'E2'"),
11
11
  }),
12
- execute(args, trip) {
13
- return stageAction(trip, {
12
+ execute(args, trip, ctx) {
13
+ return stageAction(ctx, trip, {
14
14
  kind: "book_excursion",
15
15
  excursionId: args.excursionId.toUpperCase(),
16
16
  });
@@ -10,8 +10,8 @@ export default tripSlot.updateTool({
10
10
  hotelId: z.string().max(20).describe("The hotel id from search_hotels, e.g. 'H1'"),
11
11
  nights: z.number().int().min(1).max(30).describe("How many nights"),
12
12
  }),
13
- execute(args, trip) {
14
- return stageAction(trip, {
13
+ execute(args, trip, ctx) {
14
+ return stageAction(ctx, trip, {
15
15
  kind: "book_hotel",
16
16
  hotelId: args.hotelId.toUpperCase(),
17
17
  nights: args.nights,
@@ -9,8 +9,8 @@ export default tripSlot.updateTool({
9
9
  description:
10
10
  "Cancel the caller's ticket outright. This does NOT cancel anything yet — " +
11
11
  "it stages the cancellation so you can read it back and hear a yes.",
12
- execute(_args, trip) {
12
+ execute(_args, trip, ctx) {
13
13
  if (!trip.ticket) return { error: "There is no ticket to cancel." };
14
- return stageAction(trip, { kind: "cancel_ticket" });
14
+ return stageAction(ctx, trip, { kind: "cancel_ticket" });
15
15
  },
16
16
  });
@@ -13,10 +13,10 @@ export default tripSlot.updateTool({
13
13
  inputSchema: z.object({
14
14
  flightId: z.string().max(20).describe("The flight to move to, e.g. 'LX52'"),
15
15
  }),
16
- execute(args, trip) {
16
+ execute(args, trip, ctx) {
17
17
  if (!trip.ticket) {
18
18
  return { error: "This caller has no ticket to move — it was cancelled on this call." };
19
19
  }
20
- return stageAction(trip, { kind: "update_ticket", flightId: args.flightId.toUpperCase() });
20
+ return stageAction(ctx, trip, { kind: "update_ticket", flightId: args.flightId.toUpperCase() });
21
21
  },
22
22
  });
@@ -1,10 +1,226 @@
1
1
  #!/usr/bin/env node
2
2
  import { i as errorCode } from "./_utils-B8QmtFhK.mjs";
3
3
  import { n as withPreservedNodeEnv } from "./_vite-env-BNveawd1.mjs";
4
+ import { builtinModules } from "node:module";
4
5
  import path from "node:path";
5
6
  import fs from "node:fs/promises";
6
7
  import { build } from "vite";
7
8
  import { BaseBuilder, applySwcTransform, detectWorkflowPatterns, shouldTransformFile } from "@workflow/builders";
9
+ //#region _workflow-scan.ts
10
+ /**
11
+ * What the BUILT flow bundle carries — the two scans that read it, and the
12
+ * checks over them.
13
+ *
14
+ * Split out of `workflow-bundler.ts` when that file crossed the 500-line cap,
15
+ * along the seam the two scans already share: both read the same artifact after
16
+ * the builder has written it, both attribute a line to a module through
17
+ * esbuild's `// <path>` headers, and neither has anything to do with
18
+ * CONFIGURING the build. One of them fails the build (a `require` the workflow
19
+ * VM cannot answer) and the other warns (a call that replays differently), which
20
+ * is the only real difference between them.
21
+ *
22
+ * Internal: `workflow-bundler.ts` is the surface the studio and the CLI's own
23
+ * build call, and it re-exports nothing from here that they need.
24
+ *
25
+ * @module _workflow-scan
26
+ */
27
+ /**
28
+ * Every Node builtin, in both spellings esbuild can emit for one.
29
+ *
30
+ * `node:child_process` and bare `child_process` are the same module and the
31
+ * bundle may name it either way — a bare name only reaches the output when the
32
+ * source imported it bare, which npm is still full of.
33
+ */
34
+ const RUNTIME_MODULES = /* @__PURE__ */ new Set([...builtinModules, ...builtinModules.map((name) => `node:${name}`)]);
35
+ /**
36
+ * A `require(…)` CALL, excluding esbuild's own `__require` shim.
37
+ *
38
+ * The lookbehind is what separates the two: `__require` is the shim esbuild
39
+ * writes for a bundled CJS module's dynamic requires, and the STEP bundle
40
+ * defines a real `require` for it (see {@link STEP_REQUIRE_SHIM}). A bare
41
+ * `require` in the FLOW bundle is the different thing this scan is for.
42
+ */
43
+ const REQUIRE_CALL = /(?<![\w$.])require\(\s*"([^"]+)"\s*\)/g;
44
+ /** esbuild's per-module header — `// node_modules/pkg/index.js`, and nothing else. */
45
+ const MODULE_COMMENT = /^\/\/ (\S+\.[cm]?[jt]sx?)$/;
46
+ /**
47
+ * Find the Node builtins a flow bundle would `require` at load.
48
+ *
49
+ * The flow bundle is compiled in a `node:vm` `Script` whose context has
50
+ * `module` and `exports` and **no `require`**, so one of these is a run that
51
+ * dies at replay with `ReferenceError: require is not defined` — never a build
52
+ * failure, and never a symptom before the first run. The WDK's own builder
53
+ * bundles everything for exactly this reason and carries
54
+ * `createNodeModuleErrorPlugin` to reject a builtin import at build time.
55
+ *
56
+ * That plugin has two blind spots this scan covers, and both are the DEPLOYED
57
+ * shape rather than an exotic one:
58
+ *
59
+ * - It reports a violation only when it can point at the import LINE in a
60
+ * first-party file, matched with a single-line regex — so a multi-line
61
+ * `import {\n x,\n} from "pkg"` finds nothing and the builtin is marked
62
+ * external in silence.
63
+ * - It resolves that file against `process.cwd()`, which is not the project
64
+ * being built when the studio builds a workspace, so the read fails and the
65
+ * same silent path is taken.
66
+ *
67
+ * Both were reproduced. What reaches the VM either way is
68
+ * `var import_node_child_process = require("node:child_process");` at the top
69
+ * of the bundle, i.e. every run of every workflow in the project fails, and the
70
+ * stack names a line of generated code inside a dependency.
71
+ *
72
+ * Restricted to builtin specifiers deliberately: those are the only ones this
73
+ * builder leaves external (it marks nothing else so, precisely so nothing can
74
+ * need a `require`), and a narrow set is what keeps the scan from reading the
75
+ * text of a prompt as a violation.
76
+ *
77
+ * @internal
78
+ */
79
+ function findVmRequires(workflowCode) {
80
+ const found = [];
81
+ const seen = /* @__PURE__ */ new Set();
82
+ let module;
83
+ for (const line of workflowCode.split("\n")) {
84
+ const header = MODULE_COMMENT.exec(line.trim());
85
+ if (header) {
86
+ module = header[1];
87
+ continue;
88
+ }
89
+ for (const [, specifier] of line.matchAll(REQUIRE_CALL)) {
90
+ if (specifier === void 0 || !RUNTIME_MODULES.has(specifier)) continue;
91
+ const key = `${specifier}\u0000${module ?? ""}`;
92
+ if (seen.has(key)) continue;
93
+ seen.add(key);
94
+ found.push({
95
+ specifier,
96
+ module
97
+ });
98
+ }
99
+ }
100
+ return found;
101
+ }
102
+ /**
103
+ * Calls whose answer differs between a run and its replays, and what to say
104
+ * about each.
105
+ *
106
+ * **A workflow body REPLAYS from the top on every resume** — after a `sleep`,
107
+ * after a redeploy, after the container was reclaimed — and only a step's
108
+ * result is journaled. So a body that reads the clock gets a different time on
109
+ * every pass, and a body that fetches performs the request again, both silently:
110
+ * the run completes, and its output is built from values that disagree with the
111
+ * ones the earlier passes saw. The scaffold guide has always carried this rule
112
+ * with the words "all of which fail silently if broken", and nothing checked it.
113
+ */
114
+ const REPLAY_UNSAFE = [
115
+ {
116
+ re: /(?<![\w$.])Date\.now\s*\(/g,
117
+ fix: "reads a different clock on every replay — take the time in a `\"use step\"` body, whose result is journaled"
118
+ },
119
+ {
120
+ re: /(?<![\w$.])new Date\s*\(\s*\)/g,
121
+ fix: "reads a different clock on every replay — take the time in a `\"use step\"` body, whose result is journaled"
122
+ },
123
+ {
124
+ re: /(?<![\w$.])Math\.random\s*\(/g,
125
+ fix: "draws a different number on every replay — draw it in a `\"use step\"` body"
126
+ },
127
+ {
128
+ re: /(?<![\w$.])crypto\.randomUUID\s*\(/g,
129
+ fix: "mints a different id on every replay — mint it in a `\"use step\"` body"
130
+ },
131
+ {
132
+ re: /(?<![\w$.])fetch\s*\(/g,
133
+ fix: "runs again on every replay, and the VM has no fetch to run it with — call `stepFetch` from a `\"use step\"` body"
134
+ }
135
+ ];
136
+ /**
137
+ * Replay-unsafe calls the flow bundle carries, attributed to the project's OWN
138
+ * `workflows/` files.
139
+ *
140
+ * Attribution is the whole design. The bundle inlines every non-external
141
+ * dependency a workflow module imports — zod, a markdown parser, whatever — and
142
+ * third-party code is full of `Date.now()` on paths a workflow never reaches, so
143
+ * a scan of the bundle's text reports a library and blocks a correct project.
144
+ * esbuild writes a `// <path>` header per module (the same one
145
+ * {@link findVmRequires} reads), so lines can be charged to the file they were
146
+ * written in, and only the project's own workflow sources are read.
147
+ *
148
+ * Scanning the BUNDLE rather than the sources is what makes a `"use step"` body
149
+ * exempt for free: the workflow-mode transform has already removed them, so
150
+ * what is left is the part that really does replay.
151
+ *
152
+ * @internal
153
+ */
154
+ function findReplayUnsafeCalls(workflowCode) {
155
+ const found = [];
156
+ const seen = /* @__PURE__ */ new Set();
157
+ let module;
158
+ for (const line of workflowCode.split("\n")) {
159
+ const header = MODULE_COMMENT.exec(line.trim());
160
+ if (header) {
161
+ module = header[1];
162
+ continue;
163
+ }
164
+ if (module === void 0 || !isProjectWorkflowModule(module)) continue;
165
+ for (const { re, fix } of REPLAY_UNSAFE) for (const [call] of line.matchAll(re)) {
166
+ const key = `${call}\u0000${module}`;
167
+ if (seen.has(key)) continue;
168
+ seen.add(key);
169
+ found.push({
170
+ call,
171
+ fix,
172
+ module
173
+ });
174
+ }
175
+ }
176
+ return found;
177
+ }
178
+ /**
179
+ * Is this bundled module one of the project's own `workflows/` files?
180
+ *
181
+ * A dependency's path runs through `node_modules/`, which is excluded first so
182
+ * a package that happens to live in a directory called `workflows` cannot be
183
+ * read as the project's.
184
+ */
185
+ function isProjectWorkflowModule(module) {
186
+ const posix = module.split(path.sep).join("/");
187
+ return !posix.includes("node_modules/") && /(?:^|\/)workflows\//.test(posix);
188
+ }
189
+ /**
190
+ * The warning `aai build` and `aai dev` print for a replay-unsafe call.
191
+ *
192
+ * A WARNING and not a build failure, deliberately. The attribution above makes
193
+ * the scan accurate about which FILE a call is in, and it cannot know whether a
194
+ * plain function in a `workflows/` module is reached from a body (where the
195
+ * rule bites) or only from a step (where it does not) — so the one thing it
196
+ * must not do is refuse a correct project. A silent build was the actual
197
+ * problem; a line naming the file solves it without that risk.
198
+ */
199
+ function replayWarnings(workflowCode) {
200
+ return findReplayUnsafeCalls(workflowCode).map(({ call, fix, module }) => `${module}: \`${call}…\` ${fix}.`);
201
+ }
202
+ /**
203
+ * Fail the build when the flow bundle carries a `require` — see
204
+ * {@link findVmRequires} for what that means and why nothing upstream catches it.
205
+ *
206
+ * The message has to name the MODULE as well as the specifier, because the
207
+ * import that caused it is not in the file an author is looking at: only a
208
+ * `"use step"` body is stripped from this bundle, so a value a `workflows/`
209
+ * module holds at module scope — an exported helper, a constant — keeps its
210
+ * import, and that import's whole graph rides into the VM.
211
+ */
212
+ function assertNoVmRequires(workflowCode) {
213
+ const sites = findVmRequires(workflowCode);
214
+ if (sites.length === 0) return;
215
+ const lines = sites.map(({ specifier, module }) => ` ${specifier}${module === void 0 ? "" : ` — from ${module}`}`);
216
+ throw new Error([
217
+ `This project's workflows cannot run: the workflow bundle requires ${sites.length === 1 ? "a Node module" : "Node modules"} that the workflow VM has no \`require\` for.`,
218
+ ...lines,
219
+ "",
220
+ "Only a `\"use step\"` body is removed from this bundle, so anything a `workflows/` module holds at MODULE scope keeps its import — including an exported helper that a step body is the only caller of. Move that use inside the step body, or into a module only a step body imports."
221
+ ].join("\n"));
222
+ }
223
+ //#endregion
8
224
  //#region workflow-bundler.ts
9
225
  /**
10
226
  * Workflow bundling — turning a project's `workflows/` directory into the two
@@ -37,7 +253,7 @@ import { BaseBuilder, applySwcTransform, detectWorkflowPatterns, shouldTransform
37
253
  * guest's BAKED image instead of being inlined. Step bundle: 12 MB → ~7 KB.
38
254
  * - **`bundleFinalOutput: false`** — skips wrapping the workflow-mode code in a
39
255
  * runtime host, which is exactly what `workflowEntrypoint(code)` supplies
40
- * itself at the other end. Flow bundle: 3.7 MB → ~69 KB.
256
+ * itself at the other end. Dialog bundle: 3.7 MB → ~69 KB.
41
257
  *
42
258
  * So `workflowCode` is passed to `workflowEntrypoint()` in the guest and
43
259
  * `stepCode` is evaluated there to register its step functions.
@@ -189,11 +405,13 @@ var AaiWorkflowBuilder = class extends BaseBuilder {
189
405
  outfile: this.stepFile
190
406
  });
191
407
  const [workflowCode, stepCode] = await Promise.all([fs.readFile(this.flowFile, "utf-8"), fs.readFile(this.stepFile, "utf-8")]);
408
+ assertNoVmRequires(workflowCode);
192
409
  this.output = {
193
410
  workflowCode,
194
411
  stepCode: STEP_REQUIRE_SHIM + stepCode,
195
412
  manifest,
196
- inputFiles
413
+ inputFiles,
414
+ warnings: replayWarnings(workflowCode)
197
415
  };
198
416
  }
199
417
  };
@@ -325,7 +543,7 @@ function wrapperEntrySource(runtime, workflows, toolFiles, systemPromptFile) {
325
543
  const toolEntries = toolFiles.map((file, i) => ` ${JSON.stringify(`tools/${file}`)}: __aaiTool${i},`).join("\n");
326
544
  return `import def from "../agent.ts";
327
545
  import { agentToolsToSchemas, toAgentConfig, toolRegistry, withSystemPrompt, withTools } from "@alexkroman1/aai/manifest";
328
- ${runtime ? `import { createRuntime } from "@alexkroman1/aai/runtime";` : ""}
546
+ ${runtime ? `import { createRuntime } from "@alexkroman1/aai-runtime";` : ""}
329
547
  ${systemPromptFile ? `import __aaiSystemPrompt from "../${SYSTEM_PROMPT_FILE}?raw";` : ""}
330
548
  ${toolImports}
331
549
  // A tool's name is its file name. The map is built here rather than written in
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import { t as buildWorker } from "./worker-bundler-CGD4r8Kc.mjs";
2
+ import { t as buildWorker } from "./worker-bundler-COxnqstQ.mjs";
3
3
  export { buildWorker };
@@ -1,10 +1,37 @@
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 { n as getServerInfo } from "./_agent-DpH2pBJd.mjs";
4
+ import { n as getServerInfo } from "./_agent-BTwZJu4P.mjs";
5
5
  import { errorMessage, omitUndefined } from "@alexkroman1/aai/utils";
6
6
  import { createWorkflowApiClient } from "@alexkroman1/aai/workflow-api";
7
7
  //#region workflow.ts
8
+ /**
9
+ * `aai workflow` — reading and steering a deployed agent's durable runs from a
10
+ * terminal.
11
+ *
12
+ * A run outlives every surface that can show it: the studio's runs card is one
13
+ * project's, and a page holds only the id it started. Without this the only way
14
+ * to ask "what has this agent been doing" is to hand-build a `curl` against
15
+ * `/:slug/workflows`, which needs the platform origin and the PUBLISHED slug —
16
+ * neither of which is the project's name.
17
+ *
18
+ * **It talks to the platform's brokered route, unauthenticated by default**,
19
+ * which is the same posture the page has: that surface carries no credential
20
+ * unless the agent's operator set `AAI_WORKFLOW_API_TOKEN`, and `--token` is how
21
+ * a caller passes it. So this is deliberately NOT an `apiRequest` — the caller's
22
+ * API key is not what authorizes here, and sending it would put a platform
23
+ * credential on a route that does not want one.
24
+ *
25
+ * Every request BROKERS, so the first one may boot the agent's sandbox. That is
26
+ * the same trade the studio card makes and worth knowing before scripting a loop
27
+ * around it.
28
+ *
29
+ * **The requests are the SDK's** (`createWorkflowApiClient`,
30
+ * `@alexkroman1/aai/workflow-api`). What is left here is the two things that are
31
+ * genuinely the CLI's: turning "this directory" into an origin plus a published
32
+ * slug, and PRINTING — which is most of why the verbs exist separately from the
33
+ * client's methods.
34
+ */
8
35
  /** Runs listed when the caller names no limit — a terminal is not a dashboard. */
9
36
  const DEFAULT_RUN_LIMIT = 20;
10
37
  /**
@@ -29,7 +29,7 @@
29
29
  * guest's BAKED image instead of being inlined. Step bundle: 12 MB → ~7 KB.
30
30
  * - **`bundleFinalOutput: false`** — skips wrapping the workflow-mode code in a
31
31
  * runtime host, which is exactly what `workflowEntrypoint(code)` supplies
32
- * itself at the other end. Flow bundle: 3.7 MB → ~69 KB.
32
+ * itself at the other end. Dialog bundle: 3.7 MB → ~69 KB.
33
33
  *
34
34
  * So `workflowCode` is passed to `workflowEntrypoint()` in the guest and
35
35
  * `stepCode` is evaluated there to register its step functions.
@@ -108,6 +108,11 @@ export type WorkflowBundleOutput = {
108
108
  * section for why it is this list and not a content sniff.
109
109
  */
110
110
  inputFiles: readonly string[];
111
+ /**
112
+ * Replay-safety findings, as lines to print. See {@link replayWarnings} for
113
+ * why they are warnings rather than a failed build.
114
+ */
115
+ warnings: readonly string[];
111
116
  };
112
117
  /**
113
118
  * Attach the compiler's `workflowId`/`stepId` to the agent bundle's copy of
@@ -25,7 +25,7 @@
25
25
  * slug, and PRINTING — which is most of why the verbs exist separately from the
26
26
  * client's methods.
27
27
  */
28
- import type { WorkflowRunSnapshot, WorkflowSummary } from "@alexkroman1/aai";
28
+ import type { WorkflowRunSnapshot, WorkflowSummary } from "@alexkroman1/aai/workflow-api";
29
29
  import { type CommandResult } from "./_output.ts";
30
30
  /**
31
31
  * One run, as the API reports it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexkroman1/aai-cli",
3
- "version": "6.10.1",
3
+ "version": "7.0.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "aai": "bin.mjs"
@@ -44,8 +44,9 @@
44
44
  "p-timeout": "^7.0.1",
45
45
  "vite": "^8.2.1",
46
46
  "zod": "^4.4.3",
47
- "@alexkroman1/aai": "6.10.1",
48
- "@alexkroman1/aai-ui": "6.10.1"
47
+ "@alexkroman1/aai-ui": "7.0.0",
48
+ "@alexkroman1/aai": "7.0.0",
49
+ "@alexkroman1/aai-runtime": "7.0.0"
49
50
  },
50
51
  "devDependencies": {
51
52
  "playwright": "^1.62.1",
@@ -53,7 +54,7 @@
53
54
  "verdaccio": "^6.9.2",
54
55
  "vitest": "^4.1.10",
55
56
  "workflow": "4.8.2",
56
- "aai-templates": "0.3.6"
57
+ "aai-templates": "0.3.7"
57
58
  },
58
59
  "peerDependencies": {
59
60
  "vitest": "^4.1.10"