@alexkroman1/aai-cli 6.11.0 → 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 +40 -19
- package/dist/scaffold/package.json +5 -3
- package/dist/scaffold/server.mjs +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 +57 -204
- package/dist/templates/call-audit/agent.ts +32 -19
- package/dist/templates/call-audit/client.tsx +17 -54
- package/dist/templates/call-audit/workflows/audit.ts +11 -18
- package/dist/templates/call-audit/workflows/ingest.ts +106 -114
- package/dist/templates/call-audit/workflows/media.ts +2 -12
- package/dist/templates/call-audit/workflows/summarize.ts +47 -52
- package/dist/templates/call-audit/workflows/sync-api.ts +7 -7
- 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 +97 -94
- 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 +36 -40
- 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 +68 -97
- package/dist/templates/spoken-summary/agent.ts +30 -17
- package/dist/templates/spoken-summary/client.tsx +10 -50
- package/dist/templates/spoken-summary/workflows/summarize.ts +17 -22
- package/dist/templates/spoken-summary/workflows/transcribe.ts +19 -26
- 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 +94 -194
- package/dist/templates/transcription-workflow/agent.ts +1 -1
- package/dist/templates/transcription-workflow/client.tsx +17 -42
- package/dist/templates/transcription-workflow/workflows/batch.ts +19 -22
- package/dist/templates/transcription-workflow/workflows/normalize.ts +90 -186
- package/dist/templates/transcription-workflow/workflows/stitch.ts +0 -11
- package/dist/templates/transcription-workflow/workflows/stream.ts +4 -5
- package/dist/templates/transcription-workflow/workflows/sync-api.ts +6 -6
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +32 -26
- package/dist/templates/transcription-workflow/workflows/wav.ts +0 -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/call-audit/workflows/temp-media.ts +0 -138
- 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
|
@@ -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"
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 the AAI authors. MIT license.
|
|
2
|
-
/**
|
|
3
|
-
* Moving bytes between the upload store and a local file, which is what an
|
|
4
|
-
* ffmpeg step spends most of its lines on.
|
|
5
|
-
*
|
|
6
|
-
* No directive, so it sits under `workflows/` untransformed and is called FROM
|
|
7
|
-
* steps, inheriting their environment. It exists because both ffmpeg steps in
|
|
8
|
-
* this template need the same three things and the third one is the one that is
|
|
9
|
-
* easy to get wrong.
|
|
10
|
-
*
|
|
11
|
-
* ## Why a temp file at all
|
|
12
|
-
*
|
|
13
|
-
* `@alexkroman1/aai/ffmpeg` takes bytes as happily as a path, and for a short
|
|
14
|
-
* clip bytes are the better call. This desk uses paths, for two reasons that are
|
|
15
|
-
* both properties of real recordings rather than preferences:
|
|
16
|
-
*
|
|
17
|
-
* - **A pipe cannot seek.** An `.m4a` off a phone usually carries its `moov`
|
|
18
|
-
* index at the END of the file, so ffmpeg reading it from `pipe:0` fails with
|
|
19
|
-
* `moov atom not found`. That is the flagship input.
|
|
20
|
-
* - **Piped output is capped**, at `DEFAULT_MAX_FFMPEG_OUTPUT_BYTES` (64 MiB),
|
|
21
|
-
* which is about half an hour of this desk's 16 kHz mono PCM. The desk exists
|
|
22
|
-
* for the two-hour call.
|
|
23
|
-
*
|
|
24
|
-
* ## A temp file may not outlive its step
|
|
25
|
-
*
|
|
26
|
-
* A step is journaled by its RETURN VALUE and may be dispatched into a different
|
|
27
|
-
* process than its neighbours, so a path in a return value is a path that is
|
|
28
|
-
* replayed after the file behind it is gone — and the failure mode is a resumed
|
|
29
|
-
* run reading a directory that another run is using. {@link withTempDir} makes
|
|
30
|
-
* the lifetime a lexical scope: the directory is created on entry, removed on
|
|
31
|
-
* exit, and what crosses the step boundary is an upload id.
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
import { mkdtemp, open, rm } from "node:fs/promises";
|
|
35
|
-
import { tmpdir } from "node:os";
|
|
36
|
-
import { join } from "node:path";
|
|
37
|
-
import { readUpload } from "@alexkroman1/aai/utils";
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Bytes moved per `readUpload`, and per write.
|
|
41
|
-
*
|
|
42
|
-
* Large enough that a two-hour recording is a few hundred round trips rather
|
|
43
|
-
* than tens of thousands, and small enough that a step's resident set is a
|
|
44
|
-
* constant rather than a function of the recording. The number this must NOT be
|
|
45
|
-
* is "the whole file", which is the shape every first draft has.
|
|
46
|
-
*/
|
|
47
|
-
export const WINDOW_BYTES = 8 * 1024 * 1024;
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Run `work` with a private temp directory, and remove it afterwards.
|
|
51
|
-
*
|
|
52
|
-
* `join(tmpdir(), …)` rather than a `/tmp` literal, which is this repo's rule
|
|
53
|
-
* (`guard-invariants` rule 11) and not merely portability theatre: on Windows a
|
|
54
|
-
* literal `/tmp/x` is DRIVE-RELATIVE, so it resolves somewhere that does not
|
|
55
|
-
* exist and every write fails with ENOENT. A step runs in a Linux guest when it
|
|
56
|
-
* is deployed and on the developer's own machine under `aai dev`, which is the
|
|
57
|
-
* half that makes it matter.
|
|
58
|
-
*
|
|
59
|
-
* The removal is in a `finally`, so it also runs on the failure paths — a guest's
|
|
60
|
-
* disk is small, and a step that leaves a copy of every recording it touched
|
|
61
|
-
* fills it. `force` so a run that never created its output does not fail HERE and
|
|
62
|
-
* replace the real error with this one.
|
|
63
|
-
*/
|
|
64
|
-
export async function withTempDir<T>(work: (dir: string) => Promise<T>): Promise<T> {
|
|
65
|
-
const dir = await mkdtemp(join(tmpdir(), "aai-call-audit-"));
|
|
66
|
-
try {
|
|
67
|
-
return await work(dir);
|
|
68
|
-
} finally {
|
|
69
|
-
await rm(dir, { recursive: true, force: true });
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Write an upload to a local path, a window at a time.
|
|
75
|
-
*
|
|
76
|
-
* A `for` loop rather than a fan-out deliberately: the bytes land in one file at
|
|
77
|
-
* one offset each, so concurrency buys nothing and costs exactly the memory the
|
|
78
|
-
* windows are here to bound.
|
|
79
|
-
*
|
|
80
|
-
* `windowBytes` defaults to {@link WINDOW_BYTES}; see {@link fileChunks} for why
|
|
81
|
-
* it is a parameter at all.
|
|
82
|
-
*/
|
|
83
|
-
export async function materializeUpload(
|
|
84
|
-
uploadId: string,
|
|
85
|
-
size: number,
|
|
86
|
-
path: string,
|
|
87
|
-
windowBytes: number = WINDOW_BYTES,
|
|
88
|
-
): Promise<void> {
|
|
89
|
-
const handle = await open(path, "w");
|
|
90
|
-
try {
|
|
91
|
-
for (let at = 0; at < size; at += windowBytes) {
|
|
92
|
-
const slice = await readUpload(uploadId, {
|
|
93
|
-
start: at,
|
|
94
|
-
end: Math.min(at + windowBytes, size),
|
|
95
|
-
});
|
|
96
|
-
await handle.write(slice.bytes);
|
|
97
|
-
}
|
|
98
|
-
} finally {
|
|
99
|
-
await handle.close();
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* A local file as the stream `writeUpload` takes.
|
|
105
|
-
*
|
|
106
|
-
* A generator rather than `readFile`, for the same reason the windows above
|
|
107
|
-
* exist: the normalized PCM is the largest thing this desk touches, and handing
|
|
108
|
-
* the store an `AsyncIterable` is what keeps it off the heap.
|
|
109
|
-
*
|
|
110
|
-
* **The `.slice()` is load-bearing.** One buffer is reused across reads, so
|
|
111
|
-
* yielding a view of it hands the consumer memory the next read overwrites — a
|
|
112
|
-
* bug whose symptom is a stored file made of the LAST chunk repeated, and which
|
|
113
|
-
* does not reproduce whenever the consumer happens to copy before the next
|
|
114
|
-
* iteration.
|
|
115
|
-
*
|
|
116
|
-
* `windowBytes` is a parameter with a default for exactly that reason, and it is
|
|
117
|
-
* the one testability seam in this template. The aliasing bug above only manifests
|
|
118
|
-
* across MULTIPLE reads, so at the real 8 MiB window a spec would have to write a
|
|
119
|
-
* 16 MB file to reach it — and a first draft of that spec used 200 KB, passed with
|
|
120
|
-
* the `.slice()` deleted, and would have shipped a test proving nothing. A small
|
|
121
|
-
* window makes the multi-chunk path a few kilobytes instead.
|
|
122
|
-
*/
|
|
123
|
-
export async function* fileChunks(
|
|
124
|
-
path: string,
|
|
125
|
-
windowBytes: number = WINDOW_BYTES,
|
|
126
|
-
): AsyncIterable<Uint8Array> {
|
|
127
|
-
const handle = await open(path, "r");
|
|
128
|
-
try {
|
|
129
|
-
const buffer = new Uint8Array(windowBytes);
|
|
130
|
-
for (;;) {
|
|
131
|
-
const { bytesRead } = await handle.read(buffer, 0, buffer.length, null);
|
|
132
|
-
if (bytesRead === 0) return;
|
|
133
|
-
yield buffer.subarray(0, bytesRead).slice();
|
|
134
|
-
}
|
|
135
|
-
} finally {
|
|
136
|
-
await handle.close();
|
|
137
|
-
}
|
|
138
|
-
}
|