@alexkroman1/aai-cli 8.2.1 → 9.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/_dev-env.d.ts +15 -2
- package/dist/{_dev-server-Dt3TCjhj.mjs → _dev-server-BAR8Ya43.mjs} +182 -55
- package/dist/_dev-server.d.ts +5 -30
- package/dist/_dev-typecheck.d.ts +38 -0
- package/dist/_dev-vite-config.d.ts +74 -0
- package/dist/_e2e-test-utils.d.ts +33 -1
- package/dist/_init-BNotRgV1.mjs +163 -0
- package/dist/_resource-commands.d.ts +0 -1
- package/dist/{build-BBxf8vom.mjs → build-DvDdSwAS.mjs} +1 -1
- package/dist/cli.mjs +22 -93
- package/dist/{delete-CPf347lo.mjs → delete-8pjH8n8n.mjs} +2 -1
- package/dist/{deploy-BqV6ShIe.mjs → deploy-CjU3e-3P.mjs} +3 -3
- package/dist/{dev-QuuOOUtv.mjs → dev-dFzdvwa7.mjs} +3 -2
- package/dist/dev.d.ts +2 -0
- package/dist/{eval-DfQ8cm5p.mjs → eval-DwNwdqmS.mjs} +1 -1
- package/dist/{init-Cp5GZg0B.mjs → init-DK7GpIF9.mjs} +7 -6
- package/dist/scaffold/.env.example +7 -4
- package/dist/scaffold/CLAUDE.md +152 -97
- package/dist/scaffold/global.d.ts +12 -0
- package/dist/scaffold/package.json +4 -4
- package/dist/scaffold/server.mjs +49 -2
- package/dist/scaffold/tsconfig.json +1 -0
- package/dist/scaffold/vitest.config.ts +24 -0
- package/dist/{studio-DUfCH9I8.mjs → studio-D3-HWIfk.mjs} +3 -3
- package/dist/templates/briefing-desk/agent.test.ts +3 -15
- package/dist/templates/briefing-desk/shared.ts +3 -3
- package/dist/templates/call-audit/agent.eval.test.ts +8 -1
- package/dist/templates/call-audit/agent.test.ts +0 -1
- package/dist/templates/call-audit/agent.ts +4 -3
- package/dist/templates/call-audit/client.tsx +4 -7
- package/dist/templates/call-audit/workflows/audit.ts +10 -1
- package/dist/templates/code-interpreter/agent.eval.test.ts +14 -43
- package/dist/templates/dispatch-center/agent.eval.test.ts +16 -26
- package/dist/templates/embedded-assets/agent.eval.test.ts +6 -16
- package/dist/templates/embedded-assets/agent.test.ts +3 -16
- package/dist/templates/health-assistant/agent.eval.test.ts +5 -15
- package/dist/templates/health-assistant/agent.test.ts +3 -14
- package/dist/templates/infocom-adventure/agent.eval.test.ts +6 -16
- package/dist/templates/infocom-adventure/agent.test.ts +3 -17
- package/dist/templates/link-digest/agent.test.ts +0 -14
- package/dist/templates/link-digest/agent.ts +9 -5
- package/dist/templates/link-digest/client.tsx +8 -6
- package/dist/templates/math-buddy/agent.eval.test.ts +11 -40
- package/dist/templates/night-owl/agent.eval.test.ts +15 -44
- package/dist/templates/night-owl/agent.test.ts +2 -15
- package/dist/templates/personal-finance/agent.eval.test.ts +10 -39
- package/dist/templates/pipeline-simple/agent.test.ts +73 -0
- package/dist/templates/pizza-ordering/agent.eval.test.ts +2 -17
- package/dist/templates/pizza-ordering/agent.test.ts +2 -17
- package/dist/templates/plan-and-execute/agent.eval.test.ts +2 -12
- package/dist/templates/plan-and-execute/agent.test.ts +3 -23
- package/dist/templates/plan-and-execute/shared.ts +1 -1
- package/dist/templates/podcast-digest/agent.test.ts +0 -1
- package/dist/templates/podcast-digest/client.tsx +3 -6
- package/dist/templates/podcast-digest/workflows/digest.ts +16 -8
- package/dist/templates/recap-workflow/agent.eval.test.ts +7 -14
- package/dist/templates/recap-workflow/agent.test.ts +3 -18
- package/dist/templates/recap-workflow/agent.ts +7 -5
- package/dist/templates/redline/agent.test.ts +0 -13
- package/dist/templates/redline/agent.ts +3 -2
- package/dist/templates/redline/client.tsx +9 -4
- package/dist/templates/research-workflow/agent.eval.test.ts +7 -14
- package/dist/templates/research-workflow/agent.test.ts +3 -15
- package/dist/templates/research-workflow/agent.ts +7 -5
- package/dist/templates/research-workflow/workflows/research.ts +8 -2
- package/dist/templates/retail/agent.eval.test.ts +10 -25
- package/dist/templates/retail/registry.test.ts +16 -13
- package/dist/templates/solo-rpg/agent.eval.test.ts +5 -15
- package/dist/templates/solo-rpg/agent.test.ts +17 -124
- package/dist/templates/solo-rpg/shared.ts +13 -61
- package/dist/templates/spoken-summary/agent.test.ts +0 -1
- package/dist/templates/spoken-summary/agent.ts +5 -4
- package/dist/templates/spoken-summary/client.tsx +4 -7
- package/dist/templates/support-line/agent.eval.test.ts +2 -12
- package/dist/templates/support-line/agent.test.ts +3 -22
- package/dist/templates/transcription-workflow/agent.ts +4 -4
- package/dist/templates/transcription-workflow/client.tsx +5 -5
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +10 -1
- package/dist/templates/travel-concierge/agent.eval.test.ts +5 -15
- package/dist/templates/travel-concierge/agent.test.ts +3 -23
- package/dist/templates/web-researcher/agent.eval.test.ts +2 -5
- package/dist/test-CvwgeVSQ.mjs +181 -0
- package/dist/test.d.ts +33 -2
- package/package.json +4 -4
- package/dist/_init-BhEfQ6Yi.mjs +0 -93
- package/dist/storage-Bfs7QQWq.mjs +0 -66
- package/dist/storage.d.ts +0 -22
- package/dist/templates/solo-rpg/tools/load_game.ts +0 -37
- package/dist/templates/solo-rpg/tools/save_game.ts +0 -32
- package/dist/test-BvyT6sAc.mjs +0 -104
package/dist/_dev-env.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ export declare function hostModeEnv(providerEnv: Record<string, string>): Record
|
|
|
30
30
|
*/
|
|
31
31
|
export declare function devBindHost(): string | undefined;
|
|
32
32
|
/**
|
|
33
|
-
* File watching is OPT-IN — `AAI_DEV_WATCH=1
|
|
33
|
+
* File watching is OPT-IN — `aai dev --watch`, or `AAI_DEV_WATCH=1`.
|
|
34
34
|
*
|
|
35
35
|
* A restart rebuilds the bundle and replaces the server, which drops nothing
|
|
36
36
|
* mid-request but does end in-flight voice sessions. That is the right default
|
|
@@ -38,8 +38,21 @@ export declare function devBindHost(): string | undefined;
|
|
|
38
38
|
* host for twenty minutes: a stray formatter save, a `.env` touch, or a git
|
|
39
39
|
* operation restarts the server underneath the run, and the harness reports it
|
|
40
40
|
* as a provider failure several records deep.
|
|
41
|
+
*
|
|
42
|
+
* **The FLAG exists because the variable was undiscoverable**, which made the
|
|
43
|
+
* default a defect rather than a decision: `aai dev --help` listed nothing about
|
|
44
|
+
* watching, `AAI_DEV_WATCH` appeared in no document a user reads, and the guide
|
|
45
|
+
* shipped into every scaffolded project opened with "Iterate in `pnpm dev` —
|
|
46
|
+
* hot reload". So the promise was false and there was no way to find the switch
|
|
47
|
+
* that makes it true. The variable stays for a process supervisor, which has an
|
|
48
|
+
* environment and no argv.
|
|
49
|
+
*
|
|
50
|
+
* The flag WINS when passed, in both directions: `--watch` turns it on where the
|
|
51
|
+
* variable is unset or off, and `--watch=false` turns it off where the variable
|
|
52
|
+
* says on — an explicit argument that a stale exported variable could override
|
|
53
|
+
* would be the same discoverability bug wearing the fix's clothes.
|
|
41
54
|
*/
|
|
42
|
-
export declare function devWatchEnabled(): boolean;
|
|
55
|
+
export declare function devWatchEnabled(flag?: boolean | undefined): boolean;
|
|
43
56
|
/**
|
|
44
57
|
* The logger the dev server's runtime writes through.
|
|
45
58
|
*
|
|
@@ -4,6 +4,7 @@ import { i as errorCode, o as errorMessage$1 } from "./_utils-B8QmtFhK.mjs";
|
|
|
4
4
|
import { n as fallbackHtmlPlugin } from "./client-bundler-CWnG42cU.mjs";
|
|
5
5
|
import { t as DEDUPED_PEERS } from "./_vite-env-BNveawd1.mjs";
|
|
6
6
|
import { n as buildWorkflows, t as buildWorker } from "./worker-bundler-CCVEDjm1.mjs";
|
|
7
|
+
import { typecheckProject } from "./typecheck.mjs";
|
|
7
8
|
import { n as ensureApiKey } from "./_config-CmJOFsAP.mjs";
|
|
8
9
|
import { n as createWorkerEvaluator } from "./_bundler-CDuYl5Gb.mjs";
|
|
9
10
|
import { t as resolveServerEnv } from "./_server-common-6e0QI4mq.mjs";
|
|
@@ -12,7 +13,7 @@ import path from "node:path";
|
|
|
12
13
|
import { omitUndefined } from "@alexkroman1/aai/utils";
|
|
13
14
|
import { createCoalescingRunner, sleep } from "@alexkroman1/aai/internal";
|
|
14
15
|
import { agentConfigWarnings } from "@alexkroman1/aai/manifest";
|
|
15
|
-
import { WORKFLOW_API_PREFIX, createRuntime, createServer, requiredProviderEnvVars, withHostCredentialFallback } from "@alexkroman1/aai-runtime";
|
|
16
|
+
import { WORKFLOW_API_PREFIX, createRuntime, createServer, ensureSessionStateSchema, requiredProviderEnvVars, withHostCredentialFallback } from "@alexkroman1/aai-runtime";
|
|
16
17
|
import { configureWorkflowWorld, consoleLogger, createWorkflowSurface, handleWorkflowRequest, publishStepEnv, startWorkflowWorldIfDeclared } from "@alexkroman1/aai-runtime/internal";
|
|
17
18
|
import { defaultClientDir } from "@alexkroman1/aai-ui/client-dir";
|
|
18
19
|
import { watch } from "chokidar";
|
|
@@ -42,7 +43,7 @@ function devBindHost() {
|
|
|
42
43
|
return host ? host : void 0;
|
|
43
44
|
}
|
|
44
45
|
/**
|
|
45
|
-
* File watching is OPT-IN — `AAI_DEV_WATCH=1
|
|
46
|
+
* File watching is OPT-IN — `aai dev --watch`, or `AAI_DEV_WATCH=1`.
|
|
46
47
|
*
|
|
47
48
|
* A restart rebuilds the bundle and replaces the server, which drops nothing
|
|
48
49
|
* mid-request but does end in-flight voice sessions. That is the right default
|
|
@@ -50,8 +51,22 @@ function devBindHost() {
|
|
|
50
51
|
* host for twenty minutes: a stray formatter save, a `.env` touch, or a git
|
|
51
52
|
* operation restarts the server underneath the run, and the harness reports it
|
|
52
53
|
* as a provider failure several records deep.
|
|
54
|
+
*
|
|
55
|
+
* **The FLAG exists because the variable was undiscoverable**, which made the
|
|
56
|
+
* default a defect rather than a decision: `aai dev --help` listed nothing about
|
|
57
|
+
* watching, `AAI_DEV_WATCH` appeared in no document a user reads, and the guide
|
|
58
|
+
* shipped into every scaffolded project opened with "Iterate in `pnpm dev` —
|
|
59
|
+
* hot reload". So the promise was false and there was no way to find the switch
|
|
60
|
+
* that makes it true. The variable stays for a process supervisor, which has an
|
|
61
|
+
* environment and no argv.
|
|
62
|
+
*
|
|
63
|
+
* The flag WINS when passed, in both directions: `--watch` turns it on where the
|
|
64
|
+
* variable is unset or off, and `--watch=false` turns it off where the variable
|
|
65
|
+
* says on — an explicit argument that a stale exported variable could override
|
|
66
|
+
* would be the same discoverability bug wearing the fix's clothes.
|
|
53
67
|
*/
|
|
54
|
-
function devWatchEnabled() {
|
|
68
|
+
function devWatchEnabled(flag) {
|
|
69
|
+
if (flag !== void 0) return flag;
|
|
55
70
|
return /^(1|true|yes|on)$/i.test(process.env.AAI_DEV_WATCH?.trim() ?? "");
|
|
56
71
|
}
|
|
57
72
|
/**
|
|
@@ -215,6 +230,149 @@ function createRestartSupervisor(ops) {
|
|
|
215
230
|
};
|
|
216
231
|
}
|
|
217
232
|
//#endregion
|
|
233
|
+
//#region _dev-typecheck.ts
|
|
234
|
+
/**
|
|
235
|
+
* The background typecheck under `aai dev`.
|
|
236
|
+
*
|
|
237
|
+
* `assertTypechecks` is wired into `build`, `deploy` and `studio` — and not
|
|
238
|
+
* into `dev`, the loop an author actually lives in. That made the whole
|
|
239
|
+
* compile-message system in `agent-params.ts` (five families of hand-written
|
|
240
|
+
* diagnostic sentences, the most careful work in the SDK) conditional on the
|
|
241
|
+
* author having an editor open on the right file. Demonstrated: a project whose
|
|
242
|
+
* `agent.ts` fails `tsc --noEmit` starts cleanly under `aai dev` and serves,
|
|
243
|
+
* and the typo is found later by `aai build` — or by a caller.
|
|
244
|
+
*
|
|
245
|
+
* Three properties, and each is the reason this is not just a call to
|
|
246
|
+
* `assertTypechecks`:
|
|
247
|
+
*
|
|
248
|
+
* - **It never blocks and never fails the server.** `aai dev` must start with a
|
|
249
|
+
* type error in an unrelated file; refusing to serve would be a worse tool
|
|
250
|
+
* than one that says nothing. The report is a `notify`, not a throw.
|
|
251
|
+
* - **It coalesces.** A save touches several files and the watcher debounces to
|
|
252
|
+
* a restart; without coalescing a burst spawns a `tsc` per restart, and `tsc`
|
|
253
|
+
* on a real project is seconds. A request during a run schedules exactly one
|
|
254
|
+
* more, which is `createCoalescingRunner`'s contract.
|
|
255
|
+
* - **It is quiet when clean.** A dev server that prints "types OK" on every
|
|
256
|
+
* save trains the author to stop reading it, which is how the failure this
|
|
257
|
+
* exists to surface gets missed again.
|
|
258
|
+
*/
|
|
259
|
+
/**
|
|
260
|
+
* A coalescing background typechecker for the project at `cwd`.
|
|
261
|
+
*
|
|
262
|
+
* `report` is injected so the dev-server specs can read what an author would
|
|
263
|
+
* have seen without capturing stdout — the same seam every other reporter in
|
|
264
|
+
* this package takes.
|
|
265
|
+
*/
|
|
266
|
+
function createDevTypecheck(cwd, report = notify) {
|
|
267
|
+
const runner = createCoalescingRunner(async () => {
|
|
268
|
+
const result = await typecheckProject(cwd);
|
|
269
|
+
if (result.ok) return;
|
|
270
|
+
report("warn", `Type errors — \`aai dev\` keeps serving, but \`aai build\` will refuse:\n${result.output}`);
|
|
271
|
+
});
|
|
272
|
+
return { request() {
|
|
273
|
+
runner.trigger().catch((err) => {
|
|
274
|
+
report("error", `Background typecheck failed to run: ${String(err)}`);
|
|
275
|
+
});
|
|
276
|
+
} };
|
|
277
|
+
}
|
|
278
|
+
//#endregion
|
|
279
|
+
//#region _dev-vite-config.ts
|
|
280
|
+
/**
|
|
281
|
+
* The Vite dev server's config for a project with a `client.tsx`.
|
|
282
|
+
*
|
|
283
|
+
* Its own module because it is the whole agent API as the BROWSER can see it —
|
|
284
|
+
* a table worth reading without the 400 lines of watcher, restart and env
|
|
285
|
+
* plumbing that surround it in `_dev-server.ts`, which is also what pushed that
|
|
286
|
+
* file past the length cap.
|
|
287
|
+
*/
|
|
288
|
+
/**
|
|
289
|
+
* Vite dev-server config for the client SPA. Extracted so the proxy wiring
|
|
290
|
+
* is unit-testable: `/websocket` MUST proxy with `ws: true` or `aai dev`
|
|
291
|
+
* with a `client.tsx` serves a page whose WebSocket never connects.
|
|
292
|
+
*
|
|
293
|
+
* **This table is the whole agent API as the browser can see it**, which is
|
|
294
|
+
* the thing to hold in mind before adding a route to `createServer`. Vite owns
|
|
295
|
+
* the port the user is told to open and answers everything not listed here
|
|
296
|
+
* itself — with a bare 404 carrying none of the agent server's headers, so the
|
|
297
|
+
* failure looks like a missing route rather than a missing proxy entry.
|
|
298
|
+
*
|
|
299
|
+
* `/workflows` is why that matters beyond voice. A WORKFLOW APP
|
|
300
|
+
* (`workflowApp()`) has no session and no socket: `page()` mounts a
|
|
301
|
+
* form and every single thing it does — listing workflows, starting a run,
|
|
302
|
+
* polling it, streaming its events — is a same-origin `fetch` under that
|
|
303
|
+
* prefix. Unproxied, the two workflow-app templates were dead on arrival under
|
|
304
|
+
* `aai dev` (`404 POST /workflows/runs` the instant the form is submitted)
|
|
305
|
+
* while the backend served the API correctly one port over. A string key
|
|
306
|
+
* prefix-matches, so this one entry covers `/runs`, `/runs/:id` and the
|
|
307
|
+
* `/runs/:id/events` SSE stream.
|
|
308
|
+
*
|
|
309
|
+
* `strictPort` because the reported URL is `http://localhost:<port>` —
|
|
310
|
+
* without it, Vite silently binds port+N when the port is busy and the
|
|
311
|
+
* printed/JSON-returned URL points at whatever else was listening.
|
|
312
|
+
*
|
|
313
|
+
* `AAI_DEV_HOST` reaches BOTH servers. Binding only the backend left Vite —
|
|
314
|
+
* the port the user is told to open — on loopback, i.e. failing exactly the
|
|
315
|
+
* case that variable exists for (`aai dev` in a container, reached from the host).
|
|
316
|
+
*
|
|
317
|
+
* ## The target is an IP LITERAL, and that is a fix rather than a style choice
|
|
318
|
+
*
|
|
319
|
+
* `127.0.0.1`, never `localhost`. Vite opens a FRESH upstream connection for
|
|
320
|
+
* every WebSocket upgrade — an HTTP request reuses a pooled keep-alive socket and
|
|
321
|
+
* so resolves rarely — which means a hostname here is one `getaddrinfo` per
|
|
322
|
+
* session handshake. That lookup runs on libuv's threadpool (four threads,
|
|
323
|
+
* shared with every other fs and DNS call in a process that is ALSO serving the
|
|
324
|
+
* agent), and under load it intermittently stalls for almost exactly two
|
|
325
|
+
* seconds.
|
|
326
|
+
*
|
|
327
|
+
* Measured on the `retail` template, session handshakes to `session.configured`:
|
|
328
|
+
*
|
|
329
|
+
* | Target | conc | rps | p50 | p99 |
|
|
330
|
+
* | --- | --- | --- | --- | --- |
|
|
331
|
+
* | `localhost` | 1 | 12-18 | 8-11 ms | 2.0 s |
|
|
332
|
+
* | `localhost` | 10 | 0.6 | 16.7 s | 16.7 s |
|
|
333
|
+
* | `127.0.0.1` | 1 | 89-207 | 4-9 ms | 23-49 ms |
|
|
334
|
+
* | `127.0.0.1` | 20 | 260 | 73 ms | 166 ms |
|
|
335
|
+
*
|
|
336
|
+
* The `localhost` row is not a slow proxy, it is a queue: one handshake in thirty
|
|
337
|
+
* stalls two seconds on its own, and at concurrency 10 the stalls pile onto four
|
|
338
|
+
* threads until a sustained burst left the proxy refusing upgrades entirely until
|
|
339
|
+
* the dev server was restarted. With the literal it recovers from a burst and
|
|
340
|
+
* sits within ~1.5x of the backend port.
|
|
341
|
+
*
|
|
342
|
+
* Localized by timing the phases separately — TCP connect and the first frame
|
|
343
|
+
* were always fast, the 101 was not — and then by comparing the instant the
|
|
344
|
+
* client sent its upgrade against the backend's own log line for it: 23.808 out,
|
|
345
|
+
* 25.796 in, answered in 5 ms. The two seconds were spent before Vite dialled,
|
|
346
|
+
* which is what pointed at resolution rather than at either server.
|
|
347
|
+
*
|
|
348
|
+
* Not a behaviour change: `localhost` resolved to loopback anyway, so this
|
|
349
|
+
* removes the lookup and nothing else. An `AAI_DEV_HOST` that binds the backend
|
|
350
|
+
* to ONE non-loopback interface was unreachable through the proxy before this and
|
|
351
|
+
* still is.
|
|
352
|
+
*/
|
|
353
|
+
function viteDevConfig(cwd, vitePort, backendPort) {
|
|
354
|
+
const target = `http://127.0.0.1:${backendPort}`;
|
|
355
|
+
return {
|
|
356
|
+
root: cwd,
|
|
357
|
+
plugins: [fallbackHtmlPlugin(cwd)],
|
|
358
|
+
resolve: { dedupe: DEDUPED_PEERS },
|
|
359
|
+
server: {
|
|
360
|
+
port: vitePort,
|
|
361
|
+
strictPort: true,
|
|
362
|
+
...omitUndefined({ host: devBindHost() }),
|
|
363
|
+
proxy: {
|
|
364
|
+
"/health": target,
|
|
365
|
+
"/client-config": target,
|
|
366
|
+
"/websocket": {
|
|
367
|
+
target,
|
|
368
|
+
ws: true
|
|
369
|
+
},
|
|
370
|
+
[WORKFLOW_API_PREFIX]: target
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
//#endregion
|
|
218
376
|
//#region _dev-server.ts
|
|
219
377
|
/**
|
|
220
378
|
* Dev server for directory-based agents.
|
|
@@ -332,57 +490,6 @@ function watchDirectory(dir, onChange) {
|
|
|
332
490
|
return watcher;
|
|
333
491
|
}
|
|
334
492
|
/**
|
|
335
|
-
* Vite dev-server config for the client SPA. Extracted so the proxy wiring
|
|
336
|
-
* is unit-testable: `/websocket` MUST proxy with `ws: true` or `aai dev`
|
|
337
|
-
* with a `client.tsx` serves a page whose WebSocket never connects.
|
|
338
|
-
*
|
|
339
|
-
* **This table is the whole agent API as the browser can see it**, which is
|
|
340
|
-
* the thing to hold in mind before adding a route to `createServer`. Vite owns
|
|
341
|
-
* the port the user is told to open and answers everything not listed here
|
|
342
|
-
* itself — with a bare 404 carrying none of the agent server's headers, so the
|
|
343
|
-
* failure looks like a missing route rather than a missing proxy entry.
|
|
344
|
-
*
|
|
345
|
-
* `/workflows` is why that matters beyond voice. A WORKFLOW APP
|
|
346
|
-
* (`workflowApp()`) has no session and no socket: `page()` mounts a
|
|
347
|
-
* form and every single thing it does — listing workflows, starting a run,
|
|
348
|
-
* polling it, streaming its events — is a same-origin `fetch` under that
|
|
349
|
-
* prefix. Unproxied, the two workflow-app templates were dead on arrival under
|
|
350
|
-
* `aai dev` (`404 POST /workflows/runs` the instant the form is submitted)
|
|
351
|
-
* while the backend served the API correctly one port over. A string key
|
|
352
|
-
* prefix-matches, so this one entry covers `/runs`, `/runs/:id` and the
|
|
353
|
-
* `/runs/:id/events` SSE stream.
|
|
354
|
-
*
|
|
355
|
-
* `strictPort` because the reported URL is `http://localhost:<port>` —
|
|
356
|
-
* without it, Vite silently binds port+N when the port is busy and the
|
|
357
|
-
* printed/JSON-returned URL points at whatever else was listening.
|
|
358
|
-
*
|
|
359
|
-
* `AAI_DEV_HOST` reaches BOTH servers. Binding only the backend left Vite —
|
|
360
|
-
* the port the user is told to open — on loopback, i.e. failing exactly the
|
|
361
|
-
* case that variable exists for (`aai dev` in a container, reached from the host).
|
|
362
|
-
*/
|
|
363
|
-
function viteDevConfig(cwd, vitePort, backendPort) {
|
|
364
|
-
const target = `http://localhost:${backendPort}`;
|
|
365
|
-
return {
|
|
366
|
-
root: cwd,
|
|
367
|
-
plugins: [fallbackHtmlPlugin(cwd)],
|
|
368
|
-
resolve: { dedupe: DEDUPED_PEERS },
|
|
369
|
-
server: {
|
|
370
|
-
port: vitePort,
|
|
371
|
-
strictPort: true,
|
|
372
|
-
...omitUndefined({ host: devBindHost() }),
|
|
373
|
-
proxy: {
|
|
374
|
-
"/health": target,
|
|
375
|
-
"/client-config": target,
|
|
376
|
-
"/websocket": {
|
|
377
|
-
target,
|
|
378
|
-
ws: true
|
|
379
|
-
},
|
|
380
|
-
[WORKFLOW_API_PREFIX]: target
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
/**
|
|
386
493
|
* Start the dev server for a directory-based agent.
|
|
387
494
|
*
|
|
388
495
|
* Returns a cleanup function to shut down the server and watchers.
|
|
@@ -396,11 +503,26 @@ async function startDevServer(opts) {
|
|
|
396
503
|
const evaluateWorker = createWorkerEvaluator();
|
|
397
504
|
const devLogger = createDevLogger(outputSilenced());
|
|
398
505
|
let workflowWorldStarted = false;
|
|
506
|
+
/**
|
|
507
|
+
* Whether the session-state tables have been ensured this process.
|
|
508
|
+
*
|
|
509
|
+
* Once, like the workflow world above and for the same reason: a rebuild
|
|
510
|
+
* replaces the routes, not the storage behind them, and re-running the DDL on
|
|
511
|
+
* every file save would be two round trips per keystroke burst.
|
|
512
|
+
*/
|
|
513
|
+
let sessionSchemaEnsured = false;
|
|
399
514
|
/** Full build sequence, shared by initial startup and every restart. */
|
|
400
515
|
async function buildServer() {
|
|
401
516
|
const worker = await loadWorker(cwd, evaluateWorker);
|
|
402
517
|
const agentDef = worker.agent;
|
|
403
518
|
const env = await resolveAgentEnv(cwd, agentDef);
|
|
519
|
+
if (env.DATABASE_URL && !sessionSchemaEnsured) {
|
|
520
|
+
sessionSchemaEnsured = true;
|
|
521
|
+
await ensureSessionStateSchema({
|
|
522
|
+
url: env.DATABASE_URL,
|
|
523
|
+
logger: devLogger
|
|
524
|
+
});
|
|
525
|
+
}
|
|
404
526
|
const world = configureWorkflowWorld({
|
|
405
527
|
databaseUrl: env.DATABASE_URL,
|
|
406
528
|
port: backendPort,
|
|
@@ -442,7 +564,12 @@ async function startDevServer(opts) {
|
|
|
442
564
|
await viteServer?.close().catch(() => void 0);
|
|
443
565
|
}
|
|
444
566
|
});
|
|
445
|
-
|
|
567
|
+
const devTypecheck = createDevTypecheck(cwd);
|
|
568
|
+
devTypecheck.request();
|
|
569
|
+
watcher = devWatchEnabled(opts.watch) ? watchDirectory(cwd, () => {
|
|
570
|
+
devTypecheck.request();
|
|
571
|
+
supervisor.request();
|
|
572
|
+
}) : void 0;
|
|
446
573
|
let boundServer;
|
|
447
574
|
try {
|
|
448
575
|
const initialServer = await buildServer();
|
package/dist/_dev-server.d.ts
CHANGED
|
@@ -63,37 +63,12 @@ export declare function watchDirectory(dir: string, onChange: () => void): FSWat
|
|
|
63
63
|
export type DevServerOptions = {
|
|
64
64
|
cwd: string;
|
|
65
65
|
port: number;
|
|
66
|
+
/**
|
|
67
|
+
* Restart on a file change. Omitted, `AAI_DEV_WATCH` decides — see
|
|
68
|
+
* {@link devWatchEnabled} for why watching is opt-in at all.
|
|
69
|
+
*/
|
|
70
|
+
watch?: boolean | undefined;
|
|
66
71
|
};
|
|
67
|
-
/**
|
|
68
|
-
* Vite dev-server config for the client SPA. Extracted so the proxy wiring
|
|
69
|
-
* is unit-testable: `/websocket` MUST proxy with `ws: true` or `aai dev`
|
|
70
|
-
* with a `client.tsx` serves a page whose WebSocket never connects.
|
|
71
|
-
*
|
|
72
|
-
* **This table is the whole agent API as the browser can see it**, which is
|
|
73
|
-
* the thing to hold in mind before adding a route to `createServer`. Vite owns
|
|
74
|
-
* the port the user is told to open and answers everything not listed here
|
|
75
|
-
* itself — with a bare 404 carrying none of the agent server's headers, so the
|
|
76
|
-
* failure looks like a missing route rather than a missing proxy entry.
|
|
77
|
-
*
|
|
78
|
-
* `/workflows` is why that matters beyond voice. A WORKFLOW APP
|
|
79
|
-
* (`workflowApp()`) has no session and no socket: `page()` mounts a
|
|
80
|
-
* form and every single thing it does — listing workflows, starting a run,
|
|
81
|
-
* polling it, streaming its events — is a same-origin `fetch` under that
|
|
82
|
-
* prefix. Unproxied, the two workflow-app templates were dead on arrival under
|
|
83
|
-
* `aai dev` (`404 POST /workflows/runs` the instant the form is submitted)
|
|
84
|
-
* while the backend served the API correctly one port over. A string key
|
|
85
|
-
* prefix-matches, so this one entry covers `/runs`, `/runs/:id` and the
|
|
86
|
-
* `/runs/:id/events` SSE stream.
|
|
87
|
-
*
|
|
88
|
-
* `strictPort` because the reported URL is `http://localhost:<port>` —
|
|
89
|
-
* without it, Vite silently binds port+N when the port is busy and the
|
|
90
|
-
* printed/JSON-returned URL points at whatever else was listening.
|
|
91
|
-
*
|
|
92
|
-
* `AAI_DEV_HOST` reaches BOTH servers. Binding only the backend left Vite —
|
|
93
|
-
* the port the user is told to open — on loopback, i.e. failing exactly the
|
|
94
|
-
* case that variable exists for (`aai dev` in a container, reached from the host).
|
|
95
|
-
*/
|
|
96
|
-
export declare function viteDevConfig(cwd: string, vitePort: number, backendPort: number): import("vite").InlineConfig;
|
|
97
72
|
/**
|
|
98
73
|
* Start the dev server for a directory-based agent.
|
|
99
74
|
*
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The background typecheck under `aai dev`.
|
|
3
|
+
*
|
|
4
|
+
* `assertTypechecks` is wired into `build`, `deploy` and `studio` — and not
|
|
5
|
+
* into `dev`, the loop an author actually lives in. That made the whole
|
|
6
|
+
* compile-message system in `agent-params.ts` (five families of hand-written
|
|
7
|
+
* diagnostic sentences, the most careful work in the SDK) conditional on the
|
|
8
|
+
* author having an editor open on the right file. Demonstrated: a project whose
|
|
9
|
+
* `agent.ts` fails `tsc --noEmit` starts cleanly under `aai dev` and serves,
|
|
10
|
+
* and the typo is found later by `aai build` — or by a caller.
|
|
11
|
+
*
|
|
12
|
+
* Three properties, and each is the reason this is not just a call to
|
|
13
|
+
* `assertTypechecks`:
|
|
14
|
+
*
|
|
15
|
+
* - **It never blocks and never fails the server.** `aai dev` must start with a
|
|
16
|
+
* type error in an unrelated file; refusing to serve would be a worse tool
|
|
17
|
+
* than one that says nothing. The report is a `notify`, not a throw.
|
|
18
|
+
* - **It coalesces.** A save touches several files and the watcher debounces to
|
|
19
|
+
* a restart; without coalescing a burst spawns a `tsc` per restart, and `tsc`
|
|
20
|
+
* on a real project is seconds. A request during a run schedules exactly one
|
|
21
|
+
* more, which is `createCoalescingRunner`'s contract.
|
|
22
|
+
* - **It is quiet when clean.** A dev server that prints "types OK" on every
|
|
23
|
+
* save trains the author to stop reading it, which is how the failure this
|
|
24
|
+
* exists to surface gets missed again.
|
|
25
|
+
*/
|
|
26
|
+
/** What {@link createDevTypecheck} hands back: fire-and-forget, already coalesced. */
|
|
27
|
+
export type DevTypecheck = {
|
|
28
|
+
/** Schedule a run. Returns immediately; never rejects. */
|
|
29
|
+
request(): void;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A coalescing background typechecker for the project at `cwd`.
|
|
33
|
+
*
|
|
34
|
+
* `report` is injected so the dev-server specs can read what an author would
|
|
35
|
+
* have seen without capturing stdout — the same seam every other reporter in
|
|
36
|
+
* this package takes.
|
|
37
|
+
*/
|
|
38
|
+
export declare function createDevTypecheck(cwd: string, report?: (level: "warn" | "error", message: string) => void): DevTypecheck;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Vite dev server's config for a project with a `client.tsx`.
|
|
3
|
+
*
|
|
4
|
+
* Its own module because it is the whole agent API as the BROWSER can see it —
|
|
5
|
+
* a table worth reading without the 400 lines of watcher, restart and env
|
|
6
|
+
* plumbing that surround it in `_dev-server.ts`, which is also what pushed that
|
|
7
|
+
* file past the length cap.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Vite dev-server config for the client SPA. Extracted so the proxy wiring
|
|
11
|
+
* is unit-testable: `/websocket` MUST proxy with `ws: true` or `aai dev`
|
|
12
|
+
* with a `client.tsx` serves a page whose WebSocket never connects.
|
|
13
|
+
*
|
|
14
|
+
* **This table is the whole agent API as the browser can see it**, which is
|
|
15
|
+
* the thing to hold in mind before adding a route to `createServer`. Vite owns
|
|
16
|
+
* the port the user is told to open and answers everything not listed here
|
|
17
|
+
* itself — with a bare 404 carrying none of the agent server's headers, so the
|
|
18
|
+
* failure looks like a missing route rather than a missing proxy entry.
|
|
19
|
+
*
|
|
20
|
+
* `/workflows` is why that matters beyond voice. A WORKFLOW APP
|
|
21
|
+
* (`workflowApp()`) has no session and no socket: `page()` mounts a
|
|
22
|
+
* form and every single thing it does — listing workflows, starting a run,
|
|
23
|
+
* polling it, streaming its events — is a same-origin `fetch` under that
|
|
24
|
+
* prefix. Unproxied, the two workflow-app templates were dead on arrival under
|
|
25
|
+
* `aai dev` (`404 POST /workflows/runs` the instant the form is submitted)
|
|
26
|
+
* while the backend served the API correctly one port over. A string key
|
|
27
|
+
* prefix-matches, so this one entry covers `/runs`, `/runs/:id` and the
|
|
28
|
+
* `/runs/:id/events` SSE stream.
|
|
29
|
+
*
|
|
30
|
+
* `strictPort` because the reported URL is `http://localhost:<port>` —
|
|
31
|
+
* without it, Vite silently binds port+N when the port is busy and the
|
|
32
|
+
* printed/JSON-returned URL points at whatever else was listening.
|
|
33
|
+
*
|
|
34
|
+
* `AAI_DEV_HOST` reaches BOTH servers. Binding only the backend left Vite —
|
|
35
|
+
* the port the user is told to open — on loopback, i.e. failing exactly the
|
|
36
|
+
* case that variable exists for (`aai dev` in a container, reached from the host).
|
|
37
|
+
*
|
|
38
|
+
* ## The target is an IP LITERAL, and that is a fix rather than a style choice
|
|
39
|
+
*
|
|
40
|
+
* `127.0.0.1`, never `localhost`. Vite opens a FRESH upstream connection for
|
|
41
|
+
* every WebSocket upgrade — an HTTP request reuses a pooled keep-alive socket and
|
|
42
|
+
* so resolves rarely — which means a hostname here is one `getaddrinfo` per
|
|
43
|
+
* session handshake. That lookup runs on libuv's threadpool (four threads,
|
|
44
|
+
* shared with every other fs and DNS call in a process that is ALSO serving the
|
|
45
|
+
* agent), and under load it intermittently stalls for almost exactly two
|
|
46
|
+
* seconds.
|
|
47
|
+
*
|
|
48
|
+
* Measured on the `retail` template, session handshakes to `session.configured`:
|
|
49
|
+
*
|
|
50
|
+
* | Target | conc | rps | p50 | p99 |
|
|
51
|
+
* | --- | --- | --- | --- | --- |
|
|
52
|
+
* | `localhost` | 1 | 12-18 | 8-11 ms | 2.0 s |
|
|
53
|
+
* | `localhost` | 10 | 0.6 | 16.7 s | 16.7 s |
|
|
54
|
+
* | `127.0.0.1` | 1 | 89-207 | 4-9 ms | 23-49 ms |
|
|
55
|
+
* | `127.0.0.1` | 20 | 260 | 73 ms | 166 ms |
|
|
56
|
+
*
|
|
57
|
+
* The `localhost` row is not a slow proxy, it is a queue: one handshake in thirty
|
|
58
|
+
* stalls two seconds on its own, and at concurrency 10 the stalls pile onto four
|
|
59
|
+
* threads until a sustained burst left the proxy refusing upgrades entirely until
|
|
60
|
+
* the dev server was restarted. With the literal it recovers from a burst and
|
|
61
|
+
* sits within ~1.5x of the backend port.
|
|
62
|
+
*
|
|
63
|
+
* Localized by timing the phases separately — TCP connect and the first frame
|
|
64
|
+
* were always fast, the 101 was not — and then by comparing the instant the
|
|
65
|
+
* client sent its upgrade against the backend's own log line for it: 23.808 out,
|
|
66
|
+
* 25.796 in, answered in 5 ms. The two seconds were spent before Vite dialled,
|
|
67
|
+
* which is what pointed at resolution rather than at either server.
|
|
68
|
+
*
|
|
69
|
+
* Not a behaviour change: `localhost` resolved to loopback anyway, so this
|
|
70
|
+
* removes the lookup and nothing else. An `AAI_DEV_HOST` that binds the backend
|
|
71
|
+
* to ONE non-loopback interface was unreachable through the proxy before this and
|
|
72
|
+
* still is.
|
|
73
|
+
*/
|
|
74
|
+
export declare function viteDevConfig(cwd: string, vitePort: number, backendPort: number): import("vite").InlineConfig;
|
|
@@ -3,13 +3,25 @@
|
|
|
3
3
|
* registry setup, dependency installation, and process/server utilities.
|
|
4
4
|
* Each e2e suite performs its own setup/teardown using these helpers.
|
|
5
5
|
*/
|
|
6
|
-
import type
|
|
6
|
+
import { type ChildProcess } from "node:child_process";
|
|
7
7
|
import type { MockRegistry } from "./_mock-registry.ts";
|
|
8
8
|
export declare const dir: string;
|
|
9
9
|
export declare const packagesDir: string;
|
|
10
10
|
export declare const pm: "pnpm" | "npm" | "yarn";
|
|
11
11
|
export declare function aaiEnv(): NodeJS.ProcessEnv;
|
|
12
12
|
export declare function aai(aaiBin: string, args: string[], cwd: string, timeoutMs?: number): void;
|
|
13
|
+
/**
|
|
14
|
+
* {@link aai}, but CAPTURING — for the cases whose subject is what the CLI
|
|
15
|
+
* SAID rather than that it exited 0.
|
|
16
|
+
*
|
|
17
|
+
* Separate rather than a flag, because `stdio: "inherit"` is load-bearing for
|
|
18
|
+
* every other call: an e2e failure's only diagnosis trail is the child's output
|
|
19
|
+
* in the run log, and capturing it by default would take that away.
|
|
20
|
+
*/
|
|
21
|
+
export declare function aaiOutput(aaiBin: string, args: string[], cwd: string, timeoutMs?: number): {
|
|
22
|
+
stdout: string;
|
|
23
|
+
stderr: string;
|
|
24
|
+
};
|
|
13
25
|
/**
|
|
14
26
|
* Build the CLI and return the path to the built binary.
|
|
15
27
|
*
|
|
@@ -39,6 +51,26 @@ export declare function startRegistry(): Promise<MockRegistry>;
|
|
|
39
51
|
export declare function waitForHealth(url: string, child?: ChildProcess, timeoutMs?: number): Promise<void>;
|
|
40
52
|
/** Wait for a child process to exit (for clean teardown). */
|
|
41
53
|
export declare function waitForExit(child: ChildProcess, timeoutMs?: number): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Run a scaffolded project the way a self-hoster does — `npm start`, which runs
|
|
56
|
+
* the project's own `prestart` (`aai build`) and then `server.mjs`.
|
|
57
|
+
*
|
|
58
|
+
* Extracted because there are two legs now and the spawn is the fiddly half: the
|
|
59
|
+
* port has to be read off stdout, and every way this can fail — a build error, a
|
|
60
|
+
* missing artifact, a throwing agent — reports on STDERR, so both streams are
|
|
61
|
+
* buffered and both go into the failure. Discarding stderr leaves a bare
|
|
62
|
+
* "exited with code 1" naming none of them, which cost a full diagnosis cycle
|
|
63
|
+
* once already.
|
|
64
|
+
*
|
|
65
|
+
* `PORT=0` lets the OS assign one: this suite runs servers concurrently and a
|
|
66
|
+
* fixed port is an EADDRINUSE flake waiting to happen.
|
|
67
|
+
*/
|
|
68
|
+
export declare function startSelfHostedServer(projectDir: string, extraEnv?: NodeJS.ProcessEnv): Promise<{
|
|
69
|
+
url: string;
|
|
70
|
+
port: number;
|
|
71
|
+
stop: () => Promise<void>;
|
|
72
|
+
output: () => string;
|
|
73
|
+
}>;
|
|
42
74
|
/**
|
|
43
75
|
* Whether a failed install may be excused as the mock registry's npmjs
|
|
44
76
|
* passthrough failing rather than as a real dependency-resolution break.
|