@alexkroman1/aai-cli 9.2.0 → 10.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 (81) hide show
  1. package/dist/{_bundler-CDuYl5Gb.mjs → _bundler-B31MqYaD.mjs} +7 -33
  2. package/dist/_bundler.d.ts +3 -41
  3. package/dist/{_dev-server-BAR8Ya43.mjs → _dev-server-Bo5dSBzQ.mjs} +140 -44
  4. package/dist/_dev-server.d.ts +1 -8
  5. package/dist/_dev-vite-config.d.ts +21 -0
  6. package/dist/{_init-BNotRgV1.mjs → _init-BURbdHNM.mjs} +1 -1
  7. package/dist/{_slug-api--ZBmIGH-.mjs → _slug-api-CfGQq1Tt.mjs} +1 -1
  8. package/dist/{_templates-4WcKOjS5.mjs → _templates-0Zr2z6yA.mjs} +37 -2
  9. package/dist/_templates.d.ts +29 -0
  10. package/dist/{build-DvDdSwAS.mjs → build-DKC2e9o_.mjs} +1 -2
  11. package/dist/cli.mjs +46 -22
  12. package/dist/{delete-8pjH8n8n.mjs → delete-X_CRgCf6.mjs} +1 -1
  13. package/dist/{deploy-CjU3e-3P.mjs → deploy-CchW5pmw.mjs} +2 -2
  14. package/dist/{dev-dFzdvwa7.mjs → dev-BfVl1uBA.mjs} +1 -1
  15. package/dist/{init-DK7GpIF9.mjs → init-BRfoc2EK.mjs} +2 -2
  16. package/dist/{logs-D2Yhqe2w.mjs → logs-hWnJ66Bl.mjs} +2 -2
  17. package/dist/scaffold/CLAUDE.md +162 -54
  18. package/dist/scaffold/package.json +4 -6
  19. package/dist/scaffold/server.mjs +12 -23
  20. package/dist/{secret-CwgDLpSy.mjs → secret-B-3T5tvr.mjs} +2 -2
  21. package/dist/{studio-D3-HWIfk.mjs → studio-wVWNLREn.mjs} +4 -3
  22. package/dist/templates/call-audit/agent.eval.test.ts +1 -1
  23. package/dist/templates/call-audit/agent.test.ts +80 -8
  24. package/dist/templates/call-audit/client.tsx +87 -3
  25. package/dist/templates/call-audit/workflows/audit.ts +52 -32
  26. package/dist/templates/call-audit/workflows/ingest.ts +15 -15
  27. package/dist/templates/call-audit/workflows/media.ts +1 -1
  28. package/dist/templates/call-audit/workflows/summarize.ts +0 -4
  29. package/dist/templates/link-digest/agent.eval.test.ts +4 -2
  30. package/dist/templates/link-digest/agent.test.ts +25 -5
  31. package/dist/templates/link-digest/client.tsx +65 -7
  32. package/dist/templates/link-digest/workflows/digest.ts +39 -29
  33. package/dist/templates/podcast-digest/agent.eval.test.ts +5 -5
  34. package/dist/templates/podcast-digest/agent.test.ts +108 -2
  35. package/dist/templates/podcast-digest/client.tsx +89 -8
  36. package/dist/templates/podcast-digest/workflows/digest.ts +67 -48
  37. package/dist/templates/podcast-digest/workflows/feeds.ts +1 -4
  38. package/dist/templates/podcast-digest/workflows/slack.ts +5 -6
  39. package/dist/templates/recap-workflow/agent.eval.test.ts +72 -8
  40. package/dist/templates/recap-workflow/agent.test.ts +323 -83
  41. package/dist/templates/recap-workflow/shared.ts +4 -3
  42. package/dist/templates/recap-workflow/workflows/recap.ts +322 -86
  43. package/dist/templates/recap-workflow/workflows/tokens.ts +21 -0
  44. package/dist/templates/redline/agent.eval.test.ts +1 -1
  45. package/dist/templates/redline/agent.test.ts +1 -1
  46. package/dist/templates/redline/client.tsx +83 -3
  47. package/dist/templates/redline/workflows/redline.ts +26 -23
  48. package/dist/templates/research-workflow/agent.eval.test.ts +9 -8
  49. package/dist/templates/research-workflow/agent.test.ts +32 -6
  50. package/dist/templates/research-workflow/agent.ts +4 -4
  51. package/dist/templates/research-workflow/shared.ts +4 -3
  52. package/dist/templates/research-workflow/workflows/prompts.ts +1 -1
  53. package/dist/templates/research-workflow/workflows/research.ts +35 -38
  54. package/dist/templates/spoken-summary/agent.eval.test.ts +3 -3
  55. package/dist/templates/spoken-summary/agent.test.ts +10 -8
  56. package/dist/templates/spoken-summary/client.tsx +72 -3
  57. package/dist/templates/spoken-summary/workflows/summarize.ts +25 -21
  58. package/dist/templates/spoken-summary/workflows/transcribe.ts +15 -18
  59. package/dist/templates/transcription-workflow/agent.eval.test.ts +1 -1
  60. package/dist/templates/transcription-workflow/agent.test.ts +626 -14
  61. package/dist/templates/transcription-workflow/agent.ts +6 -4
  62. package/dist/templates/transcription-workflow/client.tsx +106 -308
  63. package/dist/templates/transcription-workflow/recover.test.ts +141 -0
  64. package/dist/templates/transcription-workflow/recover.ts +130 -0
  65. package/dist/templates/transcription-workflow/run-panel.tsx +203 -0
  66. package/dist/templates/transcription-workflow/total-latency.tsx +144 -0
  67. package/dist/templates/transcription-workflow/workflows/batch.ts +21 -24
  68. package/dist/templates/transcription-workflow/workflows/downsample.ts +273 -0
  69. package/dist/templates/transcription-workflow/workflows/normalize.ts +17 -40
  70. package/dist/templates/transcription-workflow/workflows/stream.ts +177 -24
  71. package/dist/templates/transcription-workflow/workflows/transcribe.ts +69 -32
  72. package/dist/templates/transcription-workflow/workflows/wav.ts +15 -4
  73. package/dist/worker-bundler.d.ts +0 -14
  74. package/dist/worker-bundler.mjs +149 -1
  75. package/dist/{workflow-BflATMmU.mjs → workflow-BBu5oEDw.mjs} +84 -10
  76. package/dist/workflow.d.ts +1 -0
  77. package/package.json +4 -6
  78. package/dist/_workflow-scan.d.ts +0 -108
  79. package/dist/worker-bundler-CCVEDjm1.mjs +0 -650
  80. package/dist/workflow-bundler.d.ts +0 -138
  81. package/dist/{_studio-CKrsixd0.mjs → _studio-B1waXMuR.mjs} +1 -1
@@ -1,650 +0,0 @@
1
- #!/usr/bin/env node
2
- import { i as errorCode } from "./_utils-B8QmtFhK.mjs";
3
- import { n as withPreservedNodeEnv } from "./_vite-env-BNveawd1.mjs";
4
- import { builtinModules } from "node:module";
5
- import path from "node:path";
6
- import fs from "node:fs/promises";
7
- import { build } from "vite";
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
- * The bundle's lines, each already attributed to the module esbuild's last
48
- * `// <path>` header named (undefined before the first one). Header lines are
49
- * consumed rather than yielded — no scan has anything to say about them.
50
- *
51
- * Both scans below walk the bundle exactly this way; sharing the walk is what
52
- * keeps "how a line is attributed" one answer rather than two copies that can
53
- * disagree.
54
- */
55
- function* attributedLines(workflowCode) {
56
- let module;
57
- for (const line of workflowCode.split("\n")) {
58
- const header = MODULE_COMMENT.exec(line.trim());
59
- if (header) {
60
- module = header[1];
61
- continue;
62
- }
63
- yield {
64
- line,
65
- module
66
- };
67
- }
68
- }
69
- /**
70
- * The dedupe key for one finding — what was found, and where.
71
- *
72
- * A NUL separates the two halves (neither can contain one, so the key cannot
73
- * collide) and is spelled as an ESCAPE, never the raw byte: one control
74
- * character makes a file binary to `git grep`, and every ratchet here is a
75
- * `git grep`. See "Never write a control character" in AGENTS.md.
76
- */
77
- function siteKey(found, module) {
78
- return `${found}\u0000${module ?? ""}`;
79
- }
80
- /**
81
- * Find the Node builtins a flow bundle would `require` at load.
82
- *
83
- * The flow bundle is compiled in a `node:vm` `Script` whose context has
84
- * `module` and `exports` and **no `require`**, so one of these is a run that
85
- * dies at replay with `ReferenceError: require is not defined` — never a build
86
- * failure, and never a symptom before the first run. The WDK's own builder
87
- * bundles everything for exactly this reason and carries
88
- * `createNodeModuleErrorPlugin` to reject a builtin import at build time.
89
- *
90
- * That plugin has two blind spots this scan covers, and both are the DEPLOYED
91
- * shape rather than an exotic one:
92
- *
93
- * - It reports a violation only when it can point at the import LINE in a
94
- * first-party file, matched with a single-line regex — so a multi-line
95
- * `import {\n x,\n} from "pkg"` finds nothing and the builtin is marked
96
- * external in silence.
97
- * - It resolves that file against `process.cwd()`, which is not the project
98
- * being built when the studio builds a workspace, so the read fails and the
99
- * same silent path is taken.
100
- *
101
- * Both were reproduced. What reaches the VM either way is
102
- * `var import_node_child_process = require("node:child_process");` at the top
103
- * of the bundle, i.e. every run of every workflow in the project fails, and the
104
- * stack names a line of generated code inside a dependency.
105
- *
106
- * Restricted to builtin specifiers deliberately: those are the only ones this
107
- * builder leaves external (it marks nothing else so, precisely so nothing can
108
- * need a `require`), and a narrow set is what keeps the scan from reading the
109
- * text of a prompt as a violation.
110
- *
111
- * @internal
112
- */
113
- function findVmRequires(workflowCode) {
114
- const found = [];
115
- const seen = /* @__PURE__ */ new Set();
116
- for (const { line, module } of attributedLines(workflowCode)) for (const [, specifier] of line.matchAll(REQUIRE_CALL)) {
117
- if (specifier === void 0 || !RUNTIME_MODULES.has(specifier)) continue;
118
- const key = siteKey(specifier, module);
119
- if (seen.has(key)) continue;
120
- seen.add(key);
121
- found.push({
122
- specifier,
123
- module
124
- });
125
- }
126
- return found;
127
- }
128
- /**
129
- * Calls whose answer differs between a run and its replays, and what to say
130
- * about each.
131
- *
132
- * **A workflow body REPLAYS from the top on every resume** — after a `sleep`,
133
- * after a redeploy, after the container was reclaimed — and only a step's
134
- * result is journaled. So a body that reads the clock gets a different time on
135
- * every pass, and a body that fetches performs the request again, both silently:
136
- * the run completes, and its output is built from values that disagree with the
137
- * ones the earlier passes saw. The scaffold guide has always carried this rule
138
- * with the words "all of which fail silently if broken", and nothing checked it.
139
- */
140
- const REPLAY_UNSAFE = [
141
- {
142
- re: /(?<![\w$.])Date\.now\s*\(/g,
143
- fix: "reads a different clock on every replay — take the time in a `\"use step\"` body, whose result is journaled"
144
- },
145
- {
146
- re: /(?<![\w$.])new Date\s*\(\s*\)/g,
147
- fix: "reads a different clock on every replay — take the time in a `\"use step\"` body, whose result is journaled"
148
- },
149
- {
150
- re: /(?<![\w$.])Math\.random\s*\(/g,
151
- fix: "draws a different number on every replay — draw it in a `\"use step\"` body"
152
- },
153
- {
154
- re: /(?<![\w$.])crypto\.randomUUID\s*\(/g,
155
- fix: "mints a different id on every replay — mint it in a `\"use step\"` body"
156
- },
157
- {
158
- re: /(?<![\w$.])fetch\s*\(/g,
159
- fix: "runs again on every replay, and the VM has no fetch to run it with — call `stepFetch` from a `\"use step\"` body"
160
- }
161
- ];
162
- /**
163
- * Replay-unsafe calls the flow bundle carries, attributed to the project's OWN
164
- * `workflows/` files.
165
- *
166
- * Attribution is the whole design. The bundle inlines every non-external
167
- * dependency a workflow module imports — zod, a markdown parser, whatever — and
168
- * third-party code is full of `Date.now()` on paths a workflow never reaches, so
169
- * a scan of the bundle's text reports a library and blocks a correct project.
170
- * esbuild writes a `// <path>` header per module (the same one
171
- * {@link findVmRequires} reads), so lines can be charged to the file they were
172
- * written in, and only the project's own workflow sources are read.
173
- *
174
- * Scanning the BUNDLE rather than the sources is what makes a `"use step"` body
175
- * exempt for free: the workflow-mode transform has already removed them, so
176
- * what is left is the part that really does replay.
177
- *
178
- * @internal
179
- */
180
- function findReplayUnsafeCalls(workflowCode) {
181
- const found = [];
182
- const seen = /* @__PURE__ */ new Set();
183
- for (const { line, module } of attributedLines(workflowCode)) {
184
- if (module === void 0 || !isProjectWorkflowModule(module)) continue;
185
- for (const { re, fix } of REPLAY_UNSAFE) for (const [call] of line.matchAll(re)) {
186
- const key = siteKey(call, module);
187
- if (seen.has(key)) continue;
188
- seen.add(key);
189
- found.push({
190
- call,
191
- fix,
192
- module
193
- });
194
- }
195
- }
196
- return found;
197
- }
198
- /**
199
- * Is this bundled module one of the project's own `workflows/` files?
200
- *
201
- * A dependency's path runs through `node_modules/`, which is excluded first so
202
- * a package that happens to live in a directory called `workflows` cannot be
203
- * read as the project's.
204
- */
205
- function isProjectWorkflowModule(module) {
206
- const posix = module.split(path.sep).join("/");
207
- return !posix.includes("node_modules/") && /(?:^|\/)workflows\//.test(posix);
208
- }
209
- /**
210
- * The warning `aai build` and `aai dev` print for a replay-unsafe call.
211
- *
212
- * A WARNING and not a build failure, deliberately. The attribution above makes
213
- * the scan accurate about which FILE a call is in, and it cannot know whether a
214
- * plain function in a `workflows/` module is reached from a body (where the
215
- * rule bites) or only from a step (where it does not) — so the one thing it
216
- * must not do is refuse a correct project. A silent build was the actual
217
- * problem; a line naming the file solves it without that risk.
218
- */
219
- function replayWarnings(workflowCode) {
220
- return findReplayUnsafeCalls(workflowCode).map(({ call, fix, module }) => `${module}: \`${call}…\` ${fix}.`);
221
- }
222
- /**
223
- * Fail the build when the flow bundle carries a `require` — see
224
- * {@link findVmRequires} for what that means and why nothing upstream catches it.
225
- *
226
- * The message has to name the MODULE as well as the specifier, because the
227
- * import that caused it is not in the file an author is looking at: only a
228
- * `"use step"` body is stripped from this bundle, so a value a `workflows/`
229
- * module holds at module scope — an exported helper, a constant — keeps its
230
- * import, and that import's whole graph rides into the VM.
231
- */
232
- function assertNoVmRequires(workflowCode) {
233
- const sites = findVmRequires(workflowCode);
234
- if (sites.length === 0) return;
235
- const lines = sites.map(({ specifier, module }) => ` ${specifier}${module === void 0 ? "" : ` — from ${module}`}`);
236
- throw new Error([
237
- `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.`,
238
- ...lines,
239
- "",
240
- "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."
241
- ].join("\n"));
242
- }
243
- //#endregion
244
- //#region workflow-bundler.ts
245
- /**
246
- * Workflow bundling — turning a project's `workflows/` directory into the two
247
- * small artifacts a guest needs to run durable workflows.
248
- *
249
- * Public (no `_` prefix) for the same reason as `worker-bundler.ts`: the studio
250
- * builds its workspaces through this too, so a workflow published from the
251
- * browser comes out of the same pass as one from `aai build`.
252
- *
253
- * @internal — build hook for aai-server/the studio; not a supported public API
254
- * and not covered by semver.
255
- *
256
- * ## Why this exists at all
257
- *
258
- * The Workflow Development Kit's own integrations (Next, Nitro, SvelteKit) build
259
- * workflow routes while building the SERVER, by scanning `workflows/` and
260
- * emitting handler files. That model does not fit this platform: the guest image
261
- * is baked once and then serves MANY tenants' agents, each arriving over
262
- * `bundle/load` at runtime. There is no `workflows/` directory in existence when
263
- * the harness is built. So the transform has to happen per tenant at deploy
264
- * time — here — and the guest receives the result as data.
265
- *
266
- * ## The two artifacts, and why they are small
267
- *
268
- * A default `workflow build` emits a 3.7 MB flow bundle and a 12 MB step bundle,
269
- * because each one inlines the whole WDK runtime. Neither could ride the guest's
270
- * single-ESM-string delivery. Two settings fix that, and both are load-bearing:
271
- *
272
- * - **`externalPackages: ["workflow", …]`** — the WDK is resolved from the
273
- * guest's BAKED image instead of being inlined. Step bundle: 12 MB → ~7 KB.
274
- * - **`bundleFinalOutput: false`** — skips wrapping the workflow-mode code in a
275
- * runtime host, which is exactly what `workflowEntrypoint(code)` supplies
276
- * itself at the other end. Dialog bundle: 3.7 MB → ~69 KB.
277
- *
278
- * So `workflowCode` is passed to `workflowEntrypoint()` in the guest and
279
- * `stepCode` is evaluated there to register its step functions.
280
- *
281
- * ## The THIRD transform, which is the agent bundle's
282
- *
283
- * Those two are what the queue runs. The agent bundle needs a third —
284
- * `applySwcTransform(…, "client")`, wired in as {@link workflowClientPlugin} —
285
- * and without it the whole mechanism is inert: `ctx.workflows.start` reads the
286
- * `workflowId` the compiler attaches to a directive body, and Vite bundling
287
- * `workflows/research.ts` the ordinary way attaches nothing. The symptom is
288
- * `MISSING_WORKFLOW_ID_MESSAGE` at the first `start()` — an agent that builds,
289
- * deploys, boots and answers the phone, and cannot start a run.
290
- *
291
- * The plugin transforms EXACTLY the files the builder scanned (`inputFiles`),
292
- * not "every file with a directive". Same list, same `moduleSpecifierRoot`,
293
- * therefore the same ids by construction — a body outside `workflows/` cannot
294
- * acquire an id that no flow bundle registered, which would trade a clear
295
- * failure at `start()` for a run that enqueues and then fails at replay.
296
- *
297
- * ## Config details that are not optional
298
- *
299
- * Each of these cost real time to find, and none fails in a way that names
300
- * itself:
301
- *
302
- * - `dirs` must carry the `./` prefix. A bare `"workflows"` crashes inside
303
- * enhanced-resolve with `Cannot read properties of undefined (reading
304
- * 'length')` from `join(undefined, …)`, nowhere near the cause.
305
- * - Every bundle path must be ABSOLUTE, for the same reason.
306
- * - `projectRoot` and `moduleSpecifierRoot` must be set explicitly; they do not
307
- * default to `workingDir` on the path this takes.
308
- * - `keepInterimBundleContext` must stay off — it leaves an esbuild watch context
309
- * alive, so a one-shot build never exits.
310
- */
311
- /**
312
- * The directory a project's `"use workflow"` bodies live in.
313
- *
314
- * The WDK builder's convention, not ours, and it is why a workflow body cannot
315
- * live in `agent.ts`: only files under here are transformed. A body outside it
316
- * runs inline, once, with no durability and nothing reporting that.
317
- */
318
- const WORKFLOWS_DIR = "workflows";
319
- /**
320
- * Packages left OUT of both bundles, resolved from the guest's baked image.
321
- *
322
- * `workflow` alone would nearly do it — its subpaths are what user code imports —
323
- * but the `@workflow/*` internals are what the transform's own emitted imports
324
- * reach for, and inlining those is most of the 12 MB.
325
- */
326
- const WDK_EXTERNAL = [
327
- "workflow",
328
- "@workflow/core",
329
- "@workflow/errors",
330
- "@workflow/utils",
331
- "@workflow/world"
332
- ];
333
- /** Scratch directory for the builder's file output, under the CLI's own dot-dir. */
334
- const SCRATCH_REL = path.join(".aai", "workflow-build");
335
- /**
336
- * What makes a CJS dependency survive into the ESM step bundle.
337
- *
338
- * esbuild cannot statically rewrite `require("node:assert")` inside a bundled
339
- * CommonJS module, so it emits a `__require` shim — and that shim's fallback
340
- * THROWS: `Dynamic require of "node:assert" is not supported`. A step bundles
341
- * everything it imports and npm is full of CJS, so any step reaching a package
342
- * with a CJS dependency anywhere in its graph failed AT MODULE LOAD, before a
343
- * single line of the step ran.
344
- *
345
- * That is not a hypothetical, and the way it presents is the argument for
346
- * fixing it here. `research-workflow` imports `webSearch` from
347
- * `@alexkroman1/aai/tools`, which reaches `host/ssrf.ts`, which imports
348
- * **undici** — 118 dynamic requires, all of them `node:` builtins. `aai dev`
349
- * then never listened at all: the message named a Node builtin the author
350
- * never mentions, nothing named the package or the import that pulled it, and
351
- * because the step bundle is loaded before the server binds there was no
352
- * server to ask. It also does not reproduce in-tree, where `@dev/source`
353
- * resolves the SDK to TypeScript — only against the published `dist` — so
354
- * every gate short of the e2e suite was green.
355
- *
356
- * The shim itself is the fix's whole mechanism: esbuild writes
357
- * `typeof require !== "undefined" ? require : <thrower>`, so a real `require`
358
- * in scope is USED. This defines one from `import.meta.url` and is prepended,
359
- * which puts it ahead of the `var __require = …` initializer that reads it.
360
- * The flow bundle deliberately gets none — it is compiled in a `node:vm`
361
- * Script, where `import.meta` does not exist.
362
- */
363
- const STEP_REQUIRE_SHIM = [
364
- "import { createRequire as __aaiCreateRequire } from \"node:module\";",
365
- "const require = __aaiCreateRequire(import.meta.url);",
366
- ""
367
- ].join("\n");
368
- /**
369
- * The builder, with the config gotchas above applied once.
370
- *
371
- * `BaseBuilder` is abstract over `build()`, so a subclass is the supported way to
372
- * choose which bundles to emit and how — the WDK's own framework integrations do
373
- * the same.
374
- */
375
- var AaiWorkflowBuilder = class extends BaseBuilder {
376
- /**
377
- * Where `build()` leaves its result.
378
- *
379
- * `BaseBuilder.build()` is typed `Promise<void>`, so a subclass cannot return
380
- * the artifacts from it — and parameter properties are unavailable under this
381
- * repo's `erasableSyntaxOnly`, so the fields are declared the long way too.
382
- */
383
- output;
384
- /**
385
- * The two bundles `build()` reads back.
386
- *
387
- * Held as fields because `super()` is also told where to write them: derived
388
- * a second time inside `build()`, the two spellings of each path were free to
389
- * disagree, and a `build()` reading a file the builder never wrote is a
390
- * "produced nothing" failure that names neither path.
391
- */
392
- flowFile;
393
- stepFile;
394
- constructor(cwd, outDir) {
395
- const flowFile = path.join(outDir, "flow.mjs");
396
- const stepFile = path.join(outDir, "step.mjs");
397
- super({
398
- buildTarget: "standalone",
399
- dirs: [`./${WORKFLOWS_DIR}`],
400
- workingDir: cwd,
401
- projectRoot: cwd,
402
- moduleSpecifierRoot: cwd,
403
- externalPackages: [...WDK_EXTERNAL],
404
- stepsBundlePath: stepFile,
405
- workflowsBundlePath: flowFile,
406
- webhookBundlePath: path.join(outDir, "webhook.mjs"),
407
- suppressCreateWorkflowsBundleLogs: true,
408
- suppressCreateWebhookBundleLogs: true,
409
- suppressCreateManifestLogs: true
410
- });
411
- this.flowFile = flowFile;
412
- this.stepFile = stepFile;
413
- }
414
- async build() {
415
- const inputFiles = await this.getInputFiles();
416
- const { manifest } = await this.createWorkflowsBundle({
417
- inputFiles,
418
- format: "esm",
419
- outfile: this.flowFile,
420
- bundleFinalOutput: false
421
- });
422
- await this.createStepsBundle({
423
- inputFiles,
424
- format: "esm",
425
- outfile: this.stepFile
426
- });
427
- const [workflowCode, stepCode] = await Promise.all([fs.readFile(this.flowFile, "utf-8"), fs.readFile(this.stepFile, "utf-8")]);
428
- assertNoVmRequires(workflowCode);
429
- this.output = {
430
- workflowCode,
431
- stepCode: STEP_REQUIRE_SHIM + stepCode,
432
- manifest,
433
- inputFiles,
434
- warnings: replayWarnings(workflowCode)
435
- };
436
- }
437
- };
438
- /**
439
- * Attach the compiler's `workflowId`/`stepId` to the agent bundle's copy of
440
- * each directive body — the third transform, see the module doc.
441
- *
442
- * `enforce: "pre"` so it sees the file before Vite's own TypeScript pass: the
443
- * swc transform reads types itself (it is given the filename to infer syntax),
444
- * and its output is plain JS that the later pass leaves alone.
445
- *
446
- * @internal
447
- */
448
- function workflowClientPlugin(cwd, inputFiles) {
449
- const scanned = new Set(inputFiles.map((file) => path.resolve(cwd, file)));
450
- return {
451
- name: "aai:workflow-client",
452
- enforce: "pre",
453
- async transform(code, id) {
454
- const file = path.resolve(id.split("?")[0] ?? id);
455
- if (!scanned.has(file)) return;
456
- if (!shouldTransformFile(file, detectWorkflowPatterns(code))) return;
457
- const { code: transformed } = await applySwcTransform(path.relative(cwd, file), code, "client", file, cwd, cwd);
458
- return {
459
- code: transformed,
460
- map: null
461
- };
462
- }
463
- };
464
- }
465
- /**
466
- * Build a project's workflows, or resolve `undefined` when it declares none.
467
- *
468
- * `undefined` rather than empty strings, because "this project has no workflows"
469
- * is the common case — every voice agent that never reaches for one — and the
470
- * guest must be able to tell it apart from "the build produced nothing", which
471
- * would be a bug.
472
- *
473
- * @internal
474
- */
475
- async function buildWorkflows(cwd) {
476
- if (!await hasWorkflowsDir(cwd)) return;
477
- const outDir = path.join(cwd, SCRATCH_REL);
478
- await fs.mkdir(outDir, { recursive: true });
479
- try {
480
- const builder = new AaiWorkflowBuilder(cwd, outDir);
481
- await builder.build();
482
- const built = builder.output;
483
- if (!built) return;
484
- return built.workflowCode.trim() === "" ? void 0 : built;
485
- } finally {
486
- await fs.rm(outDir, {
487
- recursive: true,
488
- force: true
489
- }).catch(() => void 0);
490
- }
491
- }
492
- /** Does this project have a `workflows/` directory with anything in it? */
493
- async function hasWorkflowsDir(cwd) {
494
- try {
495
- return (await fs.readdir(path.join(cwd, WORKFLOWS_DIR))).some((name) => /\.(ts|tsx|js|jsx|mjs)$/.test(name));
496
- } catch {
497
- return false;
498
- }
499
- }
500
- //#endregion
501
- //#region worker-bundler.ts
502
- /**
503
- * Generated wrapper entry, written under `.aai/` for the duration of the
504
- * build (the CLI's own scratch dir — dot-paths are ignored by the dev
505
- * watcher, and the studio's workspace materialization never writes there).
506
- */
507
- const WRAPPER_ENTRY_REL = path.join(".aai", "worker-entry.ts");
508
- /** Extensions a tool module may be authored in (mirrors the SDK's registry). */
509
- const TOOL_MODULE_EXT_RE = /\.(?:m?ts|tsx)$/;
510
- /** A co-located spec is not a tool. */
511
- const TOOL_SPEC_RE = /\.(?:test|spec)\.[^.]+$/;
512
- /**
513
- * The project's `tools/` directory, as file names relative to it.
514
- *
515
- * **This is the whole of "discovery", and it happens HERE because the guest has
516
- * no filesystem.** A sandbox is handed one ESM string, so the only place a
517
- * directory can be turned into modules is where the bundle is assembled — the
518
- * same lowering eve does (`readdir` → static import list, which the bundler then
519
- * follows). The names are validated by `toolRegistry` at bundle-evaluation time
520
- * rather than here, so one implementation owns the rules.
521
- *
522
- * Sorted, so the emitted entry is byte-stable for a given directory: an entry
523
- * that reordered per readdir would change the bundle hash for no reason.
524
- */
525
- async function discoverToolFiles(cwd) {
526
- const root = path.join(cwd, "tools");
527
- let entries;
528
- try {
529
- entries = await fs.readdir(root, {
530
- withFileTypes: true,
531
- recursive: true
532
- });
533
- } catch (err) {
534
- if (errorCode(err) === "ENOENT") return [];
535
- throw err;
536
- }
537
- return entries.filter((e) => e.isFile() && TOOL_MODULE_EXT_RE.test(e.name) && !TOOL_SPEC_RE.test(e.name)).map((e) => path.relative(root, path.join(e.parentPath, e.name)).split(path.sep).join("/")).sort();
538
- }
539
- /** The prose slot: one file, beside `agent.ts`, named by convention. */
540
- const SYSTEM_PROMPT_FILE = "system-prompt.md";
541
- /**
542
- * Whether the project keeps its system prompt in a file.
543
- *
544
- * The other half of "a file beside `agent.ts` can BE the thing", and it happens
545
- * here for the same reason `discoverToolFiles` does — the guest has no
546
- * filesystem, so the read belongs where the bundle is assembled. What the file
547
- * MEANS is `withSystemPrompt`'s to decide (the author may have imported and
548
- * composed it, in which case discovery must not apply it twice); this only
549
- * answers whether there is one.
550
- */
551
- async function hasSystemPromptFile(cwd) {
552
- const nested = path.join(cwd, "system-prompt");
553
- if ((await fs.stat(nested).catch(() => void 0))?.isDirectory() === true) throw new Error(`${nested} is a directory. A system prompt is ONE file — rename it to ${SYSTEM_PROMPT_FILE}, or import the pieces yourself and compose them into \`systemPrompt\`. There is deliberately no concatenation order for a directory.`);
554
- try {
555
- return (await fs.stat(path.join(cwd, SYSTEM_PROMPT_FILE))).isFile();
556
- } catch (err) {
557
- if (errorCode(err) === "ENOENT") return false;
558
- throw err;
559
- }
560
- }
561
- function wrapperEntrySource(runtime, workflows, toolFiles, systemPromptFile) {
562
- const toolImports = toolFiles.map((file, i) => `import * as __aaiTool${i} from "../tools/${file}";`).join("\n");
563
- const toolEntries = toolFiles.map((file, i) => ` ${JSON.stringify(`tools/${file}`)}: __aaiTool${i},`).join("\n");
564
- return `import def from "../agent.ts";
565
- import { agentToolsToSchemas, toAgentConfig, toolRegistry, withSystemPrompt, withTools } from "@alexkroman1/aai/manifest";
566
- ${runtime ? `import { createRuntime } from "@alexkroman1/aai-runtime";` : ""}
567
- ${systemPromptFile ? `import __aaiSystemPrompt from "../${SYSTEM_PROMPT_FILE}?raw";` : ""}
568
- ${toolImports}
569
- // A tool's name is its file name. The map is built here rather than written in
570
- // agent.ts, so a file that exists is a tool the model can call — there is no
571
- // registration step to forget.
572
- //
573
- // \`system-prompt.md\` arrives the same way, and the \`?raw\` lives HERE rather
574
- // than in the author's own \`agent.ts\`: it is a Vite convention, and the whole
575
- // point of generating this entry is that a bundler feature never has to appear
576
- // in user-authored space.
577
- const __aaiAgent = ${systemPromptFile ? "withSystemPrompt(" : ""}withTools(
578
- def,
579
- toolRegistry({
580
- ${toolEntries}
581
- }),
582
- )${systemPromptFile ? ", __aaiSystemPrompt)" : ""};
583
- export default __aaiAgent;
584
- export const __aaiConfig = {
585
- ...toAgentConfig(__aaiAgent),
586
- toolSchemas: agentToolsToSchemas(__aaiAgent.tools ?? {}),
587
- };
588
- ${runtime ? `export const __aaiCreateRuntime = (opts: Record<string, unknown>) =>
589
- createRuntime({ ...opts, agent: __aaiAgent });
590
- ` : ""}${workflows ? `// The compiled workflow surface, carried as DATA. \`__aaiWorkflowCode\` goes to
591
- // \`workflowEntrypoint(code)\` and \`__aaiStepCode\` is evaluated by the guest so its
592
- // \`registerStepFunction\` calls run. Strings rather than modules because the guest
593
- // receives exactly one ESM string and never sees this project's filesystem.
594
- export const __aaiWorkflowCode = ${JSON.stringify(workflows.workflowCode)};
595
- export const __aaiStepCode = ${JSON.stringify(workflows.stepCode)};
596
- ` : ""}`;
597
- }
598
- /**
599
- * Bundle agent.ts into a single ESM string for the sandbox worker.
600
- *
601
- * Zod is bundled in — zod 4's `Function()` usage is wrapped in try/catch
602
- * and gracefully degrades in restricted environments like Deno.
603
- *
604
- * @internal — build hook for aai-server/the studio; not a supported public
605
- * API and not covered by semver.
606
- */
607
- async function buildWorker(cwd, opts = {}) {
608
- const wrapperPath = path.join(cwd, WRAPPER_ENTRY_REL);
609
- const [, toolFiles, systemPromptFile] = await Promise.all([
610
- fs.mkdir(path.dirname(wrapperPath), { recursive: true }),
611
- discoverToolFiles(cwd),
612
- hasSystemPromptFile(cwd)
613
- ]);
614
- await fs.writeFile(wrapperPath, wrapperEntrySource(opts.runtime !== false, opts.workflows, toolFiles, systemPromptFile), "utf-8");
615
- const plugins = [...opts.plugins ?? [], ...opts.workflows ? [workflowClientPlugin(cwd, opts.workflows.inputFiles)] : []];
616
- let result;
617
- try {
618
- result = await withPreservedNodeEnv(() => build({
619
- root: cwd,
620
- logLevel: "silent",
621
- ...opts.configFile === false && { configFile: false },
622
- ...plugins.length > 0 && { plugins },
623
- ssr: { noExternal: true },
624
- build: {
625
- ssr: true,
626
- lib: {
627
- entry: wrapperPath,
628
- formats: ["es"],
629
- fileName: "worker"
630
- },
631
- target: "node20",
632
- minify: opts.minify ? "oxc" : false,
633
- write: false,
634
- rollupOptions: { output: {
635
- entryFileNames: "[name].js",
636
- codeSplitting: false
637
- } }
638
- }
639
- }));
640
- } finally {
641
- await fs.rm(wrapperPath, { force: true }).catch(() => void 0);
642
- }
643
- const output = Array.isArray(result) ? result[0] : result;
644
- if (!output) throw new Error("Vite produced no output for agent.ts");
645
- const chunk = output.output.find((o) => o.type === "chunk" && o.isEntry);
646
- if (!chunk) throw new Error("Vite produced no entry chunk for agent.ts");
647
- return chunk.code;
648
- }
649
- //#endregion
650
- export { buildWorkflows as n, buildWorker as t };