@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.
- package/dist/{_agent-DpH2pBJd.mjs → _agent-BTwZJu4P.mjs} +2 -2
- package/dist/{_bundler-C2BDwU7r.mjs → _bundler-B4RqNF5Z.mjs} +2 -2
- package/dist/_dev-env.d.ts +22 -2
- package/dist/{_dev-server-LLLWnFBs.mjs → _dev-server-CiKFH9qw.mjs} +38 -47
- package/dist/_dev-server.d.ts +0 -17
- package/dist/{_init-C57bWAQ8.mjs → _init-CE4HKdgV.mjs} +3 -2
- package/dist/{_preflight-BtfaYtbE.mjs → _preflight-Co1G9Jww.mjs} +1 -1
- package/dist/_preflight.d.ts +1 -1
- package/dist/{_slug-api-BxrsJXVI.mjs → _slug-api-nRFaBEPJ.mjs} +2 -2
- package/dist/{_studio-U3jhKlA3.mjs → _studio-DR5yzZ1T.mjs} +2 -1
- package/dist/{_templates-Dxb_P2Wz.mjs → _templates-FI3xPEMj.mjs} +1 -1
- package/dist/_workflow-scan.d.ts +108 -0
- package/dist/{build-Dfza2pRI.mjs → build-6jfaakgc.mjs} +5 -2
- package/dist/cli.mjs +30 -25
- package/dist/{client-bundler-BEIqgOtd.mjs → client-bundler-CWnG42cU.mjs} +2 -1
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-vbpjGzqs.mjs → delete-Dl_LSfvI.mjs} +3 -3
- package/dist/{deploy-B8lubiRT.mjs → deploy-DvYeD8ia.mjs} +5 -5
- package/dist/{dev-Uq5ujP8a.mjs → dev-B59JIfWO.mjs} +1 -1
- package/dist/{eject-31gjtaHF.mjs → eject-C9WJyyr2.mjs} +1 -1
- package/dist/{init-l6xfU4xX.mjs → init-CpJVjEg0.mjs} +3 -3
- package/dist/{login-C71-qz8F.mjs → login-BBuM1sxH.mjs} +3 -4
- package/dist/{logs-BWVGSjLR.mjs → logs-9q-psa4q.mjs} +2 -2
- package/dist/project-config.mjs +1 -1
- package/dist/scaffold/CLAUDE.md +93 -14
- package/dist/scaffold/package.json +5 -3
- package/dist/scaffold/server.mjs +13 -4
- package/dist/scaffold/vite.config.ts +1 -1
- package/dist/{secret-BuMuFR4B.mjs → secret-CVvSLIDV.mjs} +2 -2
- package/dist/{storage-DzRZ-eCw.mjs → storage-BvUrnvM3.mjs} +2 -2
- package/dist/{studio-BCNUpDgP.mjs → studio--MUV0cid.mjs} +4 -4
- package/dist/templates/call-audit/agent.test.ts +818 -0
- package/dist/templates/call-audit/agent.ts +171 -0
- package/dist/templates/call-audit/client.tsx +198 -0
- package/dist/templates/call-audit/workflows/audit.ts +298 -0
- package/dist/templates/call-audit/workflows/ingest.ts +251 -0
- package/dist/templates/call-audit/workflows/media.ts +637 -0
- package/dist/templates/call-audit/workflows/summarize.ts +201 -0
- package/dist/templates/call-audit/workflows/sync-api.ts +44 -0
- package/dist/templates/dispatch-center/agent.test.ts +149 -25
- package/dist/templates/dispatch-center/client.tsx +239 -129
- package/dist/templates/dispatch-center/shared.ts +99 -1
- package/dist/templates/dispatch-center/system-prompt.md +3 -1
- package/dist/templates/dispatch-center/tools/incident_add_note.ts +16 -13
- package/dist/templates/dispatch-center/tools/incident_create.ts +19 -1
- package/dist/templates/dispatch-center/tools/incident_escalate.ts +68 -59
- package/dist/templates/dispatch-center/tools/incident_triage.ts +57 -43
- package/dist/templates/dispatch-center/tools/incident_update_status.ts +49 -40
- package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +8 -2
- package/dist/templates/dispatch-center/tools/resources_dispatch.ts +69 -52
- package/dist/templates/dispatch-center/tools/resources_update_status.ts +45 -41
- package/dist/templates/embedded-assets/agent.test.ts +17 -14
- package/dist/templates/health-assistant/agent.test.ts +22 -4
- package/dist/templates/infocom-adventure/agent.test.ts +4 -6
- package/dist/templates/infocom-adventure/client.tsx +246 -164
- package/dist/templates/link-digest/agent.test.ts +24 -19
- package/dist/templates/link-digest/client.tsx +47 -61
- package/dist/templates/link-digest/workflows/digest.ts +19 -31
- package/dist/templates/night-owl/agent.test.ts +70 -19
- package/dist/templates/night-owl/agent.ts +5 -0
- package/dist/templates/night-owl/client.tsx +56 -56
- package/dist/templates/night-owl/shared.ts +24 -0
- package/dist/templates/night-owl/tools/recommend.ts +18 -6
- package/dist/templates/pizza-ordering/agent.test.ts +27 -27
- package/dist/templates/pizza-ordering/client.tsx +9 -26
- package/dist/templates/plan-and-execute/agent.test.ts +99 -58
- package/dist/templates/plan-and-execute/agent.ts +1 -1
- package/dist/templates/plan-and-execute/client.tsx +12 -15
- package/dist/templates/plan-and-execute/shared.ts +71 -2
- package/dist/templates/plan-and-execute/tools/plan_status.ts +18 -6
- package/dist/templates/plan-and-execute/tools/revise_plan.ts +18 -10
- package/dist/templates/plan-and-execute/tools/start_plan.ts +13 -2
- package/dist/templates/plan-and-execute/tools/work_next_step.ts +70 -22
- package/dist/templates/podcast-digest/agent.test.ts +746 -0
- package/dist/templates/podcast-digest/agent.ts +139 -0
- package/dist/templates/podcast-digest/client.tsx +154 -0
- package/dist/templates/podcast-digest/workflows/digest.ts +411 -0
- package/dist/templates/podcast-digest/workflows/feeds.ts +507 -0
- package/dist/templates/podcast-digest/workflows/slack.ts +209 -0
- package/dist/templates/recap-workflow/agent.test.ts +107 -96
- package/dist/templates/recap-workflow/tools/recap_progress.ts +9 -12
- package/dist/templates/recap-workflow/tools/recap_status.ts +8 -9
- package/dist/templates/recap-workflow/tools/request_recap.ts +2 -1
- package/dist/templates/recap-workflow/workflows/recap.ts +48 -41
- package/dist/templates/redline/agent.test.ts +17 -15
- package/dist/templates/redline/client.tsx +12 -12
- package/dist/templates/redline/workflows/redline.ts +19 -31
- package/dist/templates/research-workflow/agent.test.ts +60 -59
- package/dist/templates/research-workflow/tools/research_progress.ts +9 -12
- package/dist/templates/research-workflow/tools/research_status.ts +9 -11
- package/dist/templates/research-workflow/workflows/research.ts +44 -61
- package/dist/templates/retail/agent.test.ts +26 -23
- package/dist/templates/retail/client.tsx +226 -117
- package/dist/templates/retail/registry.test.ts +38 -6
- package/dist/templates/retail/store.test.ts +82 -15
- package/dist/templates/retail/store.ts +174 -47
- package/dist/templates/retail/system-prompt.md +11 -2
- package/dist/templates/retail/tools/cancel_pending_order.ts +2 -2
- package/dist/templates/retail/tools/exchange_delivered_order_items.ts +2 -2
- package/dist/templates/retail/tools/find_user_id_by_email.ts +8 -5
- package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +4 -5
- package/dist/templates/retail/tools/get_item_details.ts +3 -4
- package/dist/templates/retail/tools/get_order_details.ts +3 -4
- package/dist/templates/retail/tools/get_product_details.ts +3 -4
- package/dist/templates/retail/tools/get_user_details.ts +2 -2
- package/dist/templates/retail/tools/list_all_product_types.ts +5 -5
- package/dist/templates/retail/tools/modify_pending_order_address.ts +2 -2
- package/dist/templates/retail/tools/modify_pending_order_items.ts +2 -3
- package/dist/templates/retail/tools/modify_pending_order_payment.ts +2 -2
- package/dist/templates/retail/tools/modify_user_address.ts +2 -2
- package/dist/templates/retail/tools/return_delivered_order_items.ts +2 -2
- package/dist/templates/retail/tools/transfer_to_human_agents.ts +8 -4
- package/dist/templates/solo-rpg/agent.test.ts +227 -48
- package/dist/templates/solo-rpg/client.tsx +38 -37
- package/dist/templates/solo-rpg/shared.ts +145 -19
- package/dist/templates/solo-rpg/system-prompt.md +3 -2
- package/dist/templates/solo-rpg/tools/action_roll.ts +82 -63
- package/dist/templates/solo-rpg/tools/burn_momentum.ts +85 -50
- package/dist/templates/solo-rpg/tools/check_state.ts +24 -7
- package/dist/templates/solo-rpg/tools/load_game.ts +13 -1
- package/dist/templates/solo-rpg/tools/save_game.ts +16 -7
- package/dist/templates/solo-rpg/tools/setup_character.ts +22 -2
- package/dist/templates/solo-rpg/tools/update_state.ts +113 -100
- package/dist/templates/spoken-summary/agent.test.ts +314 -0
- package/dist/templates/spoken-summary/agent.ts +155 -0
- package/dist/templates/spoken-summary/client.tsx +185 -0
- package/dist/templates/spoken-summary/workflows/summarize.ts +237 -0
- package/dist/templates/spoken-summary/workflows/transcribe.ts +138 -0
- package/dist/templates/support-line/agent.test.ts +11 -16
- package/dist/templates/support-line/agent.ts +1 -1
- package/dist/templates/support-line/client.tsx +9 -9
- package/dist/templates/support-line/nodes.ts +100 -0
- package/dist/templates/support-line/procedure.ts +407 -0
- package/dist/templates/support-line/tools/answer_question.ts +17 -4
- package/dist/templates/transcription-workflow/agent.test.ts +246 -123
- package/dist/templates/transcription-workflow/agent.ts +21 -7
- package/dist/templates/transcription-workflow/client.tsx +17 -42
- package/dist/templates/transcription-workflow/workflows/batch.ts +79 -180
- package/dist/templates/transcription-workflow/workflows/normalize.ts +247 -0
- package/dist/templates/transcription-workflow/workflows/stitch.ts +0 -11
- package/dist/templates/transcription-workflow/workflows/stream.ts +10 -9
- package/dist/templates/transcription-workflow/workflows/sync-api.ts +26 -94
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +55 -40
- package/dist/templates/transcription-workflow/workflows/wav.ts +31 -31
- package/dist/templates/travel-concierge/agent.test.ts +64 -33
- package/dist/templates/travel-concierge/client.tsx +11 -23
- package/dist/templates/travel-concierge/routing.ts +34 -15
- package/dist/templates/travel-concierge/shared.ts +70 -3
- package/dist/templates/travel-concierge/tools/book_car_rental.ts +2 -2
- package/dist/templates/travel-concierge/tools/book_excursion.ts +2 -2
- package/dist/templates/travel-concierge/tools/book_hotel.ts +2 -2
- package/dist/templates/travel-concierge/tools/cancel_ticket.ts +2 -2
- package/dist/templates/travel-concierge/tools/update_ticket.ts +2 -2
- package/dist/{worker-bundler-CGD4r8Kc.mjs → worker-bundler-COxnqstQ.mjs} +221 -3
- package/dist/worker-bundler.mjs +1 -1
- package/dist/{workflow-CFpxOFfQ.mjs → workflow-D2AQf2Pl.mjs} +28 -1
- package/dist/workflow-bundler.d.ts +6 -1
- package/dist/workflow.d.ts +1 -1
- package/package.json +5 -4
- package/dist/templates/support-line/graph.ts +0 -224
- package/dist/{_config-D_s09e7g.mjs → _config-CmJOFsAP.mjs} +1 -1
- /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 {
|
|
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
|
-
/**
|
|
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
|
|
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.
|
|
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
|
|
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
|
package/dist/worker-bundler.mjs
CHANGED
|
@@ -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-
|
|
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.
|
|
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
|
package/dist/workflow.d.ts
CHANGED
|
@@ -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": "
|
|
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": "
|
|
48
|
-
"@alexkroman1/aai
|
|
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.
|
|
57
|
+
"aai-templates": "0.3.7"
|
|
57
58
|
},
|
|
58
59
|
"peerDependencies": {
|
|
59
60
|
"vitest": "^4.1.10"
|