@alexkroman1/aai-cli 5.9.0 → 5.10.1

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 (43) hide show
  1. package/bin.mjs +32 -0
  2. package/dist/{_agent-BPEXSdWX.mjs → _agent-DS2PUJcl.mjs} +1 -1
  3. package/dist/{_bundler-Cjaxa2wi.mjs → _bundler-DC17suWN.mjs} +2 -2
  4. package/dist/{_config-BJm2o795.mjs → _config-DMyolIk9.mjs} +92 -10
  5. package/dist/_config.d.ts +12 -0
  6. package/dist/_dev-env.d.ts +38 -0
  7. package/dist/{_dev-server-Cnf4jNzE.mjs → _dev-server-B3ivqyEd.mjs} +72 -54
  8. package/dist/_dev-server.d.ts +2 -2
  9. package/dist/{_init-DVNeyGSl.mjs → _init-CYLvYU-B.mjs} +3 -3
  10. package/dist/{_server-common-CnaP_Urf.mjs → _server-common-DX8Bfrf5.mjs} +1 -1
  11. package/dist/{_slug-api-CQw1YGR5.mjs → _slug-api-fRNNR8tz.mjs} +1 -1
  12. package/dist/{_templates-DDx08LIG.mjs → _templates-BWJOiWOO.mjs} +2 -2
  13. package/dist/{_typecheck-gate-BT4iPz7A.mjs → _typecheck-gate-DB-PY0A3.mjs} +1 -1
  14. package/dist/{_ui-RZmPgrF6.mjs → _ui-DfwfDbT-.mjs} +26 -1
  15. package/dist/_ui.d.ts +19 -0
  16. package/dist/{_utils-Ch0J4s6a.mjs → _utils-8KKw-bzi.mjs} +9 -2
  17. package/dist/_utils.d.ts +9 -2
  18. package/dist/{build-WdGtFsSx.mjs → build-CACFbdQ4.mjs} +4 -4
  19. package/dist/cli.mjs +20 -20
  20. package/dist/{client-bundler-yiWoXrgb.mjs → client-bundler-DONm-khu.mjs} +1 -1
  21. package/dist/client-bundler.mjs +1 -1
  22. package/dist/{delete-Dr1Jn65f.mjs → delete-DEZ7u3u4.mjs} +5 -4
  23. package/dist/delete.d.ts +5 -0
  24. package/dist/{deploy-Bij1jES6.mjs → deploy-Ch0d_jje.mjs} +7 -7
  25. package/dist/{dev-HBCgChsA.mjs → dev-CdeRcYiQ.mjs} +6 -6
  26. package/dist/{init-BYGp3Usr.mjs → init-GQsNWkLJ.mjs} +7 -7
  27. package/dist/{login-CXazkkcR.mjs → login-BeFUiU6M.mjs} +6 -7
  28. package/dist/scaffold/CLAUDE.md +35 -12
  29. package/dist/scaffold/package.json +3 -3
  30. package/dist/{secret-vhGe-r2a.mjs → secret-4_dYyrpA.mjs} +2 -2
  31. package/dist/{storage-CMPsAmch.mjs → storage-BTfErOOW.mjs} +2 -2
  32. package/dist/{studio-B6zDNPPr.mjs → studio-LNvXtWak.mjs} +6 -6
  33. package/dist/templates/dispatch-center/agent.ts +4 -6
  34. package/dist/templates/retail/agent.ts +4 -6
  35. package/dist/templates/retail/registry.test.ts +5 -3
  36. package/dist/templates/retail/resolve.test.ts +6 -4
  37. package/dist/templates/retail/seed.test.ts +21 -13
  38. package/dist/templates/retail/shared.test.ts +9 -5
  39. package/dist/templates/solo-rpg/agent.test.ts +7 -5
  40. package/dist/templates/solo-rpg/shared.ts +2 -2
  41. package/dist/{test-CkXvfcpq.mjs → test-C-V98oC-.mjs} +3 -4
  42. package/dist/typecheck.mjs +65 -14
  43. package/package.json +5 -4
package/dist/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { i as installStdoutGuard, n as fail, o as writeLine, r as getOutputMode, t as CliError } from "./_output-CC300DzW.mjs";
3
- import { i as silenceOutput, n as log } from "./_ui-RZmPgrF6.mjs";
4
- import { a as errorMessage, l as resolveCwd, o as fileExists, t as AGENT_ENTRY } from "./_utils-Ch0J4s6a.mjs";
3
+ import { a as silenceOutput, n as log } from "./_ui-DfwfDbT-.mjs";
4
+ import { a as errorMessage, l as resolveCwd, o as fileExists, t as AGENT_ENTRY } from "./_utils-8KKw-bzi.mjs";
5
5
  import { existsSync, readFileSync } from "node:fs";
6
6
  import path from "node:path";
7
7
  import { fileURLToPath } from "node:url";
@@ -166,7 +166,7 @@ const list = defineCommand({
166
166
  async run({ args }) {
167
167
  await runCommand(args, async () => {
168
168
  const cwd = resolveCwd();
169
- const { executeList } = await import("./studio-B6zDNPPr.mjs");
169
+ const { executeList } = await import("./studio-LNvXtWak.mjs");
170
170
  return executeList({
171
171
  cwd,
172
172
  server: args.server
@@ -201,7 +201,7 @@ const pull = defineCommand({
201
201
  async run({ args }) {
202
202
  await runCommand(args, async () => {
203
203
  const cwd = resolveCwd();
204
- const { executePull } = await import("./studio-B6zDNPPr.mjs");
204
+ const { executePull } = await import("./studio-LNvXtWak.mjs");
205
205
  return executePull({
206
206
  cwd,
207
207
  project: args.project,
@@ -229,7 +229,7 @@ const push = defineCommand({
229
229
  async run({ args }) {
230
230
  await runCommand(args, async () => {
231
231
  const cwd = await setup({ agent: true });
232
- const { executePush } = await import("./studio-B6zDNPPr.mjs");
232
+ const { executePush } = await import("./studio-LNvXtWak.mjs");
233
233
  return executePush({
234
234
  cwd,
235
235
  server: args.server,
@@ -259,7 +259,7 @@ const publish = defineCommand({
259
259
  async run({ args }) {
260
260
  await runCommand(args, async () => {
261
261
  const cwd = await setup({ agent: true });
262
- const { executePublish } = await import("./studio-B6zDNPPr.mjs");
262
+ const { executePublish } = await import("./studio-LNvXtWak.mjs");
263
263
  return executePublish({
264
264
  cwd,
265
265
  server: args.server,
@@ -317,7 +317,7 @@ const init = defineCommand({
317
317
  },
318
318
  async run({ args }) {
319
319
  await runCommand(args, async (mode) => {
320
- const { executeInit } = await import("./init-BYGp3Usr.mjs");
320
+ const { executeInit } = await import("./init-GQsNWkLJ.mjs");
321
321
  return executeInit({
322
322
  dir: args.dir,
323
323
  force: args.force,
@@ -346,7 +346,7 @@ const dev = defineCommand({
346
346
  async run({ args }) {
347
347
  await runCommand(args, async () => {
348
348
  const cwd = await setup({ agent: true });
349
- const { executeDev } = await import("./dev-HBCgChsA.mjs");
349
+ const { executeDev } = await import("./dev-CdeRcYiQ.mjs");
350
350
  return executeDev({
351
351
  cwd,
352
352
  port: args.port
@@ -363,7 +363,7 @@ const test = defineCommand({
363
363
  async run({ args }) {
364
364
  await runCommand(args, async () => {
365
365
  const cwd = await setup();
366
- const { executeTest } = await import("./test-CkXvfcpq.mjs");
366
+ const { executeTest } = await import("./test-C-V98oC-.mjs");
367
367
  return executeTest(cwd);
368
368
  });
369
369
  }
@@ -387,7 +387,7 @@ const build = defineCommand({
387
387
  async run({ args }) {
388
388
  await runCommand(args, async () => {
389
389
  const cwd = await setup({ agent: true });
390
- const { executeBuild } = await import("./build-WdGtFsSx.mjs");
390
+ const { executeBuild } = await import("./build-CACFbdQ4.mjs");
391
391
  return executeBuild({
392
392
  cwd,
393
393
  skipTests: args.skipTests,
@@ -421,7 +421,7 @@ const deploy = defineCommand({
421
421
  async run({ args }) {
422
422
  await runCommand(args, async () => {
423
423
  const cwd = await setup({ agent: true });
424
- const { executeDeploy } = await import("./deploy-Bij1jES6.mjs");
424
+ const { executeDeploy } = await import("./deploy-Ch0d_jje.mjs");
425
425
  return executeDeploy({
426
426
  cwd,
427
427
  server: args.server,
@@ -444,7 +444,7 @@ const del = defineCommand({
444
444
  async run({ args }) {
445
445
  await runCommand(args, async () => {
446
446
  const cwd = await setup();
447
- const { executeDelete } = await import("./delete-Dr1Jn65f.mjs");
447
+ const { executeDelete } = await import("./delete-DEZ7u3u4.mjs");
448
448
  return executeDelete({
449
449
  cwd,
450
450
  server: args.server
@@ -475,7 +475,7 @@ const secret = defineCommand({
475
475
  async run({ args }) {
476
476
  await runCommand(args, async (mode) => {
477
477
  const cwd = await setup();
478
- const { executeSecretPut, NO_INPUT, readStdin } = await import("./secret-vhGe-r2a.mjs");
478
+ const { executeSecretPut, NO_INPUT, readStdin } = await import("./secret-4_dYyrpA.mjs");
479
479
  const value = mode === "json" ? await readStdin() : void 0;
480
480
  if (mode === "json" && !value) throw new CliError(...NO_INPUT);
481
481
  return executeSecretPut(cwd, args.name, value, args.server);
@@ -499,7 +499,7 @@ const secret = defineCommand({
499
499
  async run({ args }) {
500
500
  await runCommand(args, async () => {
501
501
  const cwd = await setup();
502
- const { executeSecretDelete } = await import("./secret-vhGe-r2a.mjs");
502
+ const { executeSecretDelete } = await import("./secret-4_dYyrpA.mjs");
503
503
  return executeSecretDelete(cwd, args.name, args.server);
504
504
  });
505
505
  }
@@ -516,7 +516,7 @@ const secret = defineCommand({
516
516
  async run({ args }) {
517
517
  await runCommand(args, async () => {
518
518
  const cwd = await setup();
519
- const { executeSecretList } = await import("./secret-vhGe-r2a.mjs");
519
+ const { executeSecretList } = await import("./secret-4_dYyrpA.mjs");
520
520
  return executeSecretList(cwd, args.server);
521
521
  });
522
522
  }
@@ -551,7 +551,7 @@ const storage = defineCommand({
551
551
  },
552
552
  async run({ args }) {
553
553
  await runCommand(args, async () => {
554
- const { executeStorageStatus } = await import("./storage-CMPsAmch.mjs");
554
+ const { executeStorageStatus } = await import("./storage-BTfErOOW.mjs");
555
555
  return executeStorageStatus(resolveStorageCwd(args.dir), args.server);
556
556
  });
557
557
  }
@@ -568,7 +568,7 @@ const storage = defineCommand({
568
568
  },
569
569
  async run({ args }) {
570
570
  await runCommand(args, async () => {
571
- const { executeStorageEnable } = await import("./storage-CMPsAmch.mjs");
571
+ const { executeStorageEnable } = await import("./storage-BTfErOOW.mjs");
572
572
  return executeStorageEnable(resolveStorageCwd(args.dir), args.server);
573
573
  });
574
574
  }
@@ -590,7 +590,7 @@ const storage = defineCommand({
590
590
  },
591
591
  async run({ args }) {
592
592
  await runCommand(args, async () => {
593
- const { executeStorageDisable } = await import("./storage-CMPsAmch.mjs");
593
+ const { executeStorageDisable } = await import("./storage-BTfErOOW.mjs");
594
594
  return executeStorageDisable(resolveStorageCwd(args.dir), {
595
595
  server: args.server,
596
596
  force: args.force
@@ -611,7 +611,7 @@ const login = defineCommand({
611
611
  },
612
612
  async run({ args }) {
613
613
  await runCommand(args, async () => {
614
- const { executeLogin } = await import("./login-CXazkkcR.mjs");
614
+ const { executeLogin } = await import("./login-BeFUiU6M.mjs");
615
615
  return executeLogin({ server: args.server });
616
616
  });
617
617
  }
@@ -624,7 +624,7 @@ const templates = defineCommand({
624
624
  args: { json: sharedArgs.json },
625
625
  async run({ args }) {
626
626
  await runCommand(args, async (mode) => {
627
- const { listTemplates } = await import("./_templates-DDx08LIG.mjs");
627
+ const { listTemplates } = await import("./_templates-BWJOiWOO.mjs");
628
628
  const names = await listTemplates();
629
629
  if (mode === "human") {
630
630
  for (const name of names) log.message(name);
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as errorMessage$1, o as fileExists } from "./_utils-Ch0J4s6a.mjs";
2
+ import { a as errorMessage$1, o as fileExists } from "./_utils-8KKw-bzi.mjs";
3
3
  import { t as withPreservedNodeEnv } from "./_vite-env-Dg_QlVv0.mjs";
4
4
  import { existsSync, unlinkSync, writeFileSync } from "node:fs";
5
5
  import path from "node:path";
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import { t as buildClient } from "./client-bundler-yiWoXrgb.mjs";
2
+ import { t as buildClient } from "./client-bundler-DONm-khu.mjs";
3
3
  export { buildClient };
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok } from "./_output-CC300DzW.mjs";
3
- import { n as log } from "./_ui-RZmPgrF6.mjs";
4
- import { l as writeProjectConfig } from "./_config-BJm2o795.mjs";
5
- import { i as resolveDeployTarget, n as getServerInfo } from "./_agent-BPEXSdWX.mjs";
3
+ import { n as log } from "./_ui-DfwfDbT-.mjs";
4
+ import { c as writeProjectConfig } from "./_config-DMyolIk9.mjs";
5
+ import { i as resolveDeployTarget, n as getServerInfo } from "./_agent-DS2PUJcl.mjs";
6
6
  import { n as apiRequest, t as HINT_NOT_DEPLOYED } from "./_api-client-B-upMGkc.mjs";
7
7
  //#region delete.ts
8
8
  async function runDelete(opts) {
@@ -11,7 +11,8 @@ async function runDelete(opts) {
11
11
  apiKey: opts.apiKey,
12
12
  action: "delete",
13
13
  hints: { 404: HINT_NOT_DEPLOYED },
14
- ...opts.fetch ? { fetch: opts.fetch } : {}
14
+ ...opts.fetch ? { fetch: opts.fetch } : {},
15
+ ...opts.retryDelay === void 0 ? {} : { retryDelay: opts.retryDelay }
15
16
  });
16
17
  }
17
18
  /**
package/dist/delete.d.ts CHANGED
@@ -5,6 +5,11 @@ export type DeleteOpts = {
5
5
  apiKey: string;
6
6
  /** Optional fetch implementation for testing. Defaults to globalThis.fetch. */
7
7
  fetch?: typeof globalThis.fetch;
8
+ /**
9
+ * Delay between retries in ms (default 300, see `_api-client.ts`). Tests
10
+ * pass 0 so retry-path assertions don't sleep real wall-clock time.
11
+ */
12
+ retryDelay?: number;
8
13
  };
9
14
  export declare function runDelete(opts: DeleteOpts): Promise<void>;
10
15
  type DeleteData = {
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok } from "./_output-CC300DzW.mjs";
3
- import { n as log, t as fmtUrl } from "./_ui-RZmPgrF6.mjs";
4
- import { a as errorMessage } from "./_utils-Ch0J4s6a.mjs";
5
- import { t as buildAgentBundle } from "./_bundler-Cjaxa2wi.mjs";
6
- import { s as updateProjectConfig } from "./_config-BJm2o795.mjs";
7
- import { t as resolveServerEnv } from "./_server-common-CnaP_Urf.mjs";
8
- import { i as resolveDeployTarget } from "./_agent-BPEXSdWX.mjs";
9
- import { assertTypechecks } from "./_typecheck-gate-BT4iPz7A.mjs";
3
+ import { n as log, t as fmtUrl } from "./_ui-DfwfDbT-.mjs";
4
+ import { a as errorMessage } from "./_utils-8KKw-bzi.mjs";
5
+ import { t as buildAgentBundle } from "./_bundler-DC17suWN.mjs";
6
+ import { s as updateProjectConfig } from "./_config-DMyolIk9.mjs";
7
+ import { t as resolveServerEnv } from "./_server-common-DX8Bfrf5.mjs";
8
+ import { i as resolveDeployTarget } from "./_agent-DS2PUJcl.mjs";
9
+ import { assertTypechecks } from "./_typecheck-gate-DB-PY0A3.mjs";
10
10
  import { n as apiRequest } from "./_api-client-B-upMGkc.mjs";
11
11
  import { gzipSync } from "node:zlib";
12
12
  //#region _deploy.ts
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok } from "./_output-CC300DzW.mjs";
3
- import { n as log, r as parsePort, t as fmtUrl } from "./_ui-RZmPgrF6.mjs";
4
- import { i as errorDetail } from "./_utils-Ch0J4s6a.mjs";
3
+ import { i as parsePort, n as log, r as notify, t as fmtUrl } from "./_ui-DfwfDbT-.mjs";
4
+ import { i as errorDetail } from "./_utils-8KKw-bzi.mjs";
5
5
  import path from "node:path";
6
6
  import { styleText } from "node:util";
7
7
  //#region dev.ts
@@ -12,7 +12,7 @@ import { styleText } from "node:util";
12
12
  async function executeDev(opts) {
13
13
  const port = parsePort(opts.port);
14
14
  const agentName = path.basename(path.resolve(opts.cwd));
15
- const { startDevServer } = await import("./_dev-server-Cnf4jNzE.mjs");
15
+ const { startDevServer } = await import("./_dev-server-B3ivqyEd.mjs");
16
16
  let cleanup;
17
17
  let shuttingDown = false;
18
18
  const onSignal = () => {
@@ -20,7 +20,7 @@ async function executeDev(opts) {
20
20
  shuttingDown = true;
21
21
  if (!cleanup) process.exit(130);
22
22
  cleanup().then(() => process.exit(0), (err) => {
23
- log.error(`Shutdown failed: ${errorDetail(err)}`);
23
+ notify("error", `Shutdown failed: ${errorDetail(err)}`);
24
24
  process.exit(1);
25
25
  });
26
26
  };
@@ -34,10 +34,10 @@ async function executeDev(opts) {
34
34
  log.success(`${styleText("bold", agentName)} running at ${fmtUrl(url)}`);
35
35
  log.info("Press Ctrl-C to stop");
36
36
  process.on("unhandledRejection", (err) => {
37
- log.error(`Unhandled rejection: ${errorDetail(err)}`);
37
+ notify("error", `Unhandled rejection: ${errorDetail(err)}`);
38
38
  });
39
39
  process.on("uncaughtException", (err) => {
40
- log.error(`Uncaught exception: ${errorDetail(err)}`);
40
+ notify("error", `Uncaught exception: ${errorDetail(err)}`);
41
41
  });
42
42
  return ok({ url });
43
43
  }
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok } from "./_output-CC300DzW.mjs";
3
- import { a as unwrapCancel, n as log } from "./_ui-RZmPgrF6.mjs";
4
- import { a as errorMessage, c as readJson, l as resolveCwd, o as fileExists, t as AGENT_ENTRY } from "./_utils-Ch0J4s6a.mjs";
5
- import { r as isDevMode, t as getMonorepoRoot } from "./_agent-BPEXSdWX.mjs";
3
+ import { n as log, o as unwrapCancel } from "./_ui-DfwfDbT-.mjs";
4
+ import { a as errorMessage, c as readJson, l as resolveCwd, o as fileExists, t as AGENT_ENTRY } from "./_utils-8KKw-bzi.mjs";
5
+ import { r as isDevMode, t as getMonorepoRoot } from "./_agent-DS2PUJcl.mjs";
6
6
  import path from "node:path";
7
7
  import { styleText } from "node:util";
8
8
  import * as p from "@clack/prompts";
@@ -18,7 +18,7 @@ async function promptProjectName(yes) {
18
18
  defaultValue: DEFAULT_PROJECT_NAME
19
19
  }), "Setup cancelled") || DEFAULT_PROJECT_NAME;
20
20
  }
21
- /** Enable corepack so pnpm is available (scaffold declares packageManager: pnpm). */
21
+ /** Best-effort corepack enable so pnpm is available (scaffold declares packageManager: pnpm). */
22
22
  async function ensurePnpm() {
23
23
  await execa("corepack", ["enable"], { reject: false });
24
24
  }
@@ -65,7 +65,7 @@ async function installDeps(cwd, silent) {
65
65
  } catch (err) {
66
66
  s?.stop("Dependency install failed");
67
67
  log.warn(`pnpm install failed: ${errorMessage(err)}`);
68
- log.warn("Run `corepack enable && pnpm install` manually in the project directory.");
68
+ log.warn("Install pnpm (`npm install -g pnpm`), then run `pnpm install` in the project.");
69
69
  return false;
70
70
  }
71
71
  }
@@ -75,7 +75,7 @@ function resolveTargetDir(dir) {
75
75
  }
76
76
  /** Publish after init and return deploy metadata if successful. */
77
77
  async function tryPublish(cwd, server) {
78
- const { executePublish } = await import("./studio-B6zDNPPr.mjs");
78
+ const { executePublish } = await import("./studio-LNvXtWak.mjs");
79
79
  try {
80
80
  const result = await executePublish({
81
81
  cwd,
@@ -94,7 +94,7 @@ async function tryPublish(cwd, server) {
94
94
  }
95
95
  /** Scaffold the project, optionally showing a spinner. */
96
96
  async function scaffoldProject(dir, cwd, template, silent) {
97
- const { runInit } = await import("./_init-DVNeyGSl.mjs");
97
+ const { runInit } = await import("./_init-CYLvYU-B.mjs");
98
98
  const s = silent ? void 0 : p.spinner();
99
99
  s?.start(`Creating ${dir}`);
100
100
  await runInit({
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok, t as CliError } from "./_output-CC300DzW.mjs";
3
- import { n as log } from "./_ui-RZmPgrF6.mjs";
4
- import { c as writeGlobalConfig, i as readGlobalConfig, r as getConfigDir, t as approveServer } from "./_config-BJm2o795.mjs";
5
- import { a as resolveServerUrl } from "./_agent-BPEXSdWX.mjs";
3
+ import { n as log } from "./_ui-DfwfDbT-.mjs";
4
+ import { o as updateGlobalConfig, r as readGlobalConfig, t as approveServer } from "./_config-DMyolIk9.mjs";
5
+ import { a as resolveServerUrl } from "./_agent-DS2PUJcl.mjs";
6
6
  import { spawn } from "node:child_process";
7
7
  import { setTimeout } from "node:timers/promises";
8
8
  import { randomBytes } from "node:crypto";
@@ -157,11 +157,10 @@ async function executeLogin(opts, deps = {}) {
157
157
  deadline: Date.now() + (deps.timeoutMs ?? LINK_TIMEOUT_MS)
158
158
  });
159
159
  if (!granted.apiKey) throw new CliError("login_failed", "Linking your account did not return an API key.");
160
- const dir = getConfigDir();
161
- await writeGlobalConfig(dir, {
162
- ...await readGlobalConfig(dir),
160
+ await updateGlobalConfig((config) => ({
161
+ ...config,
163
162
  apiKey: granted.apiKey
164
- });
163
+ }));
165
164
  const email = granted.email ?? "your account";
166
165
  log.success(`Linked ${email} — your API key is saved for future commands.`);
167
166
  return ok({
@@ -97,8 +97,9 @@ export default agent({
97
97
  silencePrompt?: string; // instruction injected on silence timeout (requires silenceTimeoutMs)
98
98
  minBargeInWords?: number; // pipeline only — words before user speech interrupts the reply (default 2)
99
99
  interruptionMinDurationMs?: number; // pipeline only — sustained speech (ms) before an interim barge-in interrupts (default 500; 0 disables)
100
- holdPhrase?: string; // pipeline only — spoken before a silent tool-call turn (default "One moment."; "" disables)
101
- falseInterruptionTimeoutMs?: number; // pipeline only — resume an interrupted reply if no user turn commits (default 2000; 0 disables)
100
+ deadAirCoverMs?: number; // pipeline only — speak a short filler after this much silence in a turn (default 5000; 0 disables)
101
+ resumeFalseInterruption?: boolean; // pipeline only — resume an interrupted reply if no user turn commits (default true)
102
+ preemptiveGeneration?: boolean; // pipeline only — start the reply from a high-confidence interim (default true; false opts out)
102
103
  state?: () => S; // per-session mutable state, exposed as ctx.state
103
104
  // (S is inferred; see "Typing ctx.state")
104
105
  syncState?: (state: S) => unknown; // push a projection of state to the client
@@ -250,9 +251,26 @@ land). End-of-turn detection (how long a pause ends the user's turn)
250
251
  belongs to the STT provider: `assemblyAIStt({ minTurnSilenceMs })` (default
251
252
  2000 ms) / `deepgram({ endpointing })` (default 1500 ms), so mid-utterance
252
253
  pauses don't split a request.
253
- `holdPhrase` is spoken when a turn opens with a tool call and no speech.
254
- `falseInterruptionTimeoutMs` resumes an interrupted reply when a barge-in
255
- turns out to be noise (no user turn commits within the window).
254
+ `deadAirCoverMs` is how long a turn may go silent before the transport speaks
255
+ a short filler, so a long tool chain doesn't sound like a dropped call. It is
256
+ measured silence, not a guess about the turn's shape, so a reply that arrives
257
+ promptly pays nothing; `0` disables it. The wording is not yours to set — the
258
+ filler must be purely declarative and never a request for patience, or the
259
+ caller answers it and the answer barges in.
260
+ `resumeFalseInterruption` (default `true`) resumes an interrupted reply when
261
+ a barge-in turns out to be noise — no user turn ever commits. The wait is not
262
+ configurable: the resume fires once the transcript stream goes quiet with no
263
+ final, so it can never race a real turn the STT is still endpointing.
264
+ `preemptiveGeneration` (default `true`) starts generating the reply as soon
265
+ as transcription is confident the caller has finished, and uses that
266
+ already-running answer if the committed transcript matches. It can shorten the
267
+ pause before the agent speaks, and **how much is unmeasured** — nobody has
268
+ shown the size of the saving. What bounds the downside is that a speculation
269
+ never speaks, calls a tool, or enters history until the real turn adopts it, so
270
+ the worst case is a wasted request and a turn that behaves exactly as it would
271
+ with the flag off. Set `preemptiveGeneration: false` to opt out — worth doing
272
+ on a tool-heavy agent, where a speculation that reaches a tool call is thrown
273
+ away, so it is cost with no upside.
256
274
 
257
275
  ## Providers
258
276
 
@@ -651,10 +669,11 @@ interpolate values into the SQL string. The rows come back as plain objects;
651
669
  A query returning more than 1000 rows throws — always bound reads with
652
670
  `LIMIT` (paginate with `LIMIT`/`OFFSET`).
653
671
 
654
- **Storage must be enabled** or accessing `ctx.db` throws:
672
+ **The database must be enabled** or accessing `ctx.db` throws:
655
673
 
656
674
  - CLI: `aai storage enable`
657
- - Studio: the Storage toggle
675
+ - Studio: Settings pane → Database → Enable database (covers both the
676
+ preview and published agents, each with its own schema)
658
677
  - `aai dev`: set `DATABASE_URL` in the project `.env`
659
678
 
660
679
  Create tables lazily from tool code and upsert with `on conflict`:
@@ -986,9 +1005,11 @@ Common mistakes when working in aai projects:
986
1005
  platform's Modal/Deno sandbox; the self-hosted `aai dev` server has no
987
1006
  sandbox, so there `run_code` refuses with an error result. Deploy to test
988
1007
  it end-to-end, or use the `calculate` builtin for simple arithmetic in dev.
989
- - **`ctx.db` throws until storage is enabled.** Enable it with
990
- `aai storage enable` (CLI), the Storage toggle (studio), or `DATABASE_URL`
991
- in `.env` (`aai dev`) before shipping tools that persist data.
1008
+ - **`ctx.db` throws until the database is enabled.** Enable it with
1009
+ `aai storage enable` (CLI), Settings Database (studio), or
1010
+ `DATABASE_URL` in `.env` (`aai dev`) before shipping tools that persist
1011
+ data. In the studio it takes effect when each agent next deploys — the
1012
+ preview redeploys itself, production on the next publish.
992
1013
  - **The database is per-app.** Rows are shared by every session of one
993
1014
  deployment — key them yourself if sessions must not see each other's data
994
1015
  (or keep session-scoped data in `ctx.state`).
@@ -1004,7 +1025,9 @@ Common mistakes when working in aai projects:
1004
1025
  - Agent code runs in a sandboxed worker — use `fetch` for HTTP, `ctx.env`
1005
1026
  for secrets
1006
1027
  - Tool execution timeout: 30 seconds
1007
- - `maxSteps` limits tool calls per turn (default 10) — increase for
1008
- multi-tool workflows
1028
+ - `maxSteps` limits tool calls per turn (default 10) — lower it for a
1029
+ latency-sensitive agent. On reaching the cap the agent spends one more LLM
1030
+ step with tools switched off, so it answers with what it has instead of
1031
+ going silent mid-chain
1009
1032
  - Tool returns `undefined` if execute function has no return statement —
1010
1033
  always return a value
@@ -11,15 +11,15 @@
11
11
  "publish:agent": "aai publish"
12
12
  },
13
13
  "dependencies": {
14
- "@alexkroman1/aai": "^5.9.0",
15
- "@alexkroman1/aai-ui": "^5.9.0",
14
+ "@alexkroman1/aai": "^5.10.1",
15
+ "@alexkroman1/aai-ui": "^5.10.1",
16
16
  "react": "^19.2.8",
17
17
  "react-dom": "^19.2.8",
18
18
  "tailwindcss": "^4.0.0",
19
19
  "zod": "^4.4.3"
20
20
  },
21
21
  "devDependencies": {
22
- "@alexkroman1/aai-cli": "^5.9.0",
22
+ "@alexkroman1/aai-cli": "^5.10.1",
23
23
  "@tailwindcss/vite": "^4.3.3",
24
24
  "@types/node": "^26.1.1",
25
25
  "@types/react": "^19.2.17",
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok, n as fail } from "./_output-CC300DzW.mjs";
3
- import { a as unwrapCancel, n as log } from "./_ui-RZmPgrF6.mjs";
4
- import { t as slugRequest } from "./_slug-api-CQw1YGR5.mjs";
3
+ import { n as log, o as unwrapCancel } from "./_ui-DfwfDbT-.mjs";
4
+ import { t as slugRequest } from "./_slug-api-fRNNR8tz.mjs";
5
5
  import * as p from "@clack/prompts";
6
6
  import { text } from "node:stream/consumers";
7
7
  //#region secret.ts
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok, n as fail } from "./_output-CC300DzW.mjs";
3
- import { n as log } from "./_ui-RZmPgrF6.mjs";
4
- import { t as slugRequest } from "./_slug-api-CQw1YGR5.mjs";
3
+ import { n as log } from "./_ui-DfwfDbT-.mjs";
4
+ import { t as slugRequest } from "./_slug-api-fRNNR8tz.mjs";
5
5
  import * as p from "@clack/prompts";
6
6
  //#region storage.ts
7
7
  async function storageRequest(cwd, init, server) {
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok, t as CliError } from "./_output-CC300DzW.mjs";
3
- import { n as log, t as fmtUrl } from "./_ui-RZmPgrF6.mjs";
4
- import { s as updateProjectConfig } from "./_config-BJm2o795.mjs";
5
- import { t as resolveServerEnv } from "./_server-common-CnaP_Urf.mjs";
6
- import { i as resolveDeployTarget } from "./_agent-BPEXSdWX.mjs";
7
- import { layerScaffold } from "./_templates-DDx08LIG.mjs";
3
+ import { n as log, t as fmtUrl } from "./_ui-DfwfDbT-.mjs";
4
+ import { s as updateProjectConfig } from "./_config-DMyolIk9.mjs";
5
+ import { t as resolveServerEnv } from "./_server-common-DX8Bfrf5.mjs";
6
+ import { i as resolveDeployTarget } from "./_agent-DS2PUJcl.mjs";
7
+ import { layerScaffold } from "./_templates-BWJOiWOO.mjs";
8
8
  import { n as apiRequest } from "./_api-client-B-upMGkc.mjs";
9
9
  import path from "node:path";
10
10
  import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
@@ -291,7 +291,7 @@ async function syncEnvSecrets(cwd, serverUrl, apiKey, slug) {
291
291
  }
292
292
  async function executePublish(opts) {
293
293
  if (!opts.skipTypecheck) {
294
- const { assertTypechecks } = await import("./_typecheck-gate-BT4iPz7A.mjs");
294
+ const { assertTypechecks } = await import("./_typecheck-gate-DB-PY0A3.mjs");
295
295
  await assertTypechecks(opts.cwd);
296
296
  }
297
297
  const pushed = await pushProject(opts);
@@ -1,5 +1,4 @@
1
1
  import { agent } from "@alexkroman1/aai";
2
- import { assemblyAIStt } from "@alexkroman1/aai/stt";
3
2
  import { dashboardView } from "./shared.ts";
4
3
  import systemPrompt from "./system-prompt.md?raw";
5
4
  import { incidentAddNote } from "./tools/incident_add_note.ts";
@@ -20,11 +19,10 @@ export default agent({
20
19
  // One projection replaces eleven `ctx.send("incidents", ...)` calls, and
21
20
  // is the single place that decides caller PII stays server-side.
22
21
  syncState: dashboardView,
23
- // Only the STT stage is declared (LLM and TTS stay on the AssemblyAI
24
- // defaults): a dispatcher reads addresses and unit numbers in bursts with
25
- // pauses inside one message, so end-of-turn silence has to be longer than
26
- // the default or "unit twelve respond to" splits in two.
27
- stt: assemblyAIStt({ minTurnSilenceMs: 2200 }),
22
+ // A dispatcher reads addresses and unit numbers in bursts with pauses inside
23
+ // one message ("unit twelve respond to"). The default pipeline's
24
+ // `max_turn_silence` already tolerates that; reach for
25
+ // `assemblyAIStt({ maxTurnSilenceMs })` only if your callers pause longer.
28
26
  systemPrompt,
29
27
  greeting:
30
28
  "Dispatch Command Center online. Restoring operational state. I'm ready to take incoming calls, manage active incidents, or run dispatch operations. Say 'dashboard' for a full status report. What do we have.",
@@ -1,5 +1,4 @@
1
1
  import { agent } from "@alexkroman1/aai";
2
- import { assemblyAIStt } from "@alexkroman1/aai/stt";
3
2
  import type { StateSlot } from "./shared.ts";
4
3
  import { storeView } from "./shared.ts";
5
4
  import { createDefaultState } from "./store.ts";
@@ -41,11 +40,10 @@ export default agent({
41
40
  // authenticated one may reach the browser.
42
41
  syncState: storeView,
43
42
 
44
- // Callers read order numbers and ten-digit item numbers in bursts with
45
- // pauses inside one utterance, so end-of-turn silence has to be longer than
46
- // the default or "W seven six seven … eight oh seven two" splits in two.
47
- stt: assemblyAIStt({ minTurnSilenceMs: 2200 }),
48
-
43
+ // Callers read order numbers and ten-digit item numbers in bursts with pauses
44
+ // inside one utterance ("W seven six seven eight oh seven two"). The default
45
+ // pipeline's `max_turn_silence` already tolerates that; reach for
46
+ // `assemblyAIStt({ maxTurnSilenceMs })` only if your callers pause longer.
49
47
  systemPrompt,
50
48
  greeting:
51
49
  "Thanks for calling. Before I can look anything up I'll need to find your account — " +
@@ -105,16 +105,18 @@ describe("tool registry", () => {
105
105
  );
106
106
  });
107
107
 
108
+ // Both sweep the registry, so they assert softly: adding a batch of tools
109
+ // should list every one still missing its entry, not just the first.
108
110
  test("every registered tool has sample args, so the sweeps below cover it", () => {
109
111
  for (const [name] of registry) {
110
- expect(SAMPLE_ARGS[name], `add SAMPLE_ARGS["${name}"]`).toBeDefined();
112
+ expect.soft(SAMPLE_ARGS[name], `add SAMPLE_ARGS["${name}"]`).toBeDefined();
111
113
  }
112
114
  });
113
115
 
114
116
  test("every tool declares a description the model can act on", () => {
115
117
  for (const [name, def] of registry) {
116
- expect(def.description, name).toBeTruthy();
117
- expect(def.description.length, name).toBeGreaterThan(40);
118
+ expect.soft(def.description, name).toBeTruthy();
119
+ expect.soft(def.description.length, name).toBeGreaterThan(40);
118
120
  }
119
121
  });
120
122
  });
@@ -19,7 +19,9 @@ describe("normalizeOrderId", () => {
19
19
  "5866402",
20
20
  " #w 586 6402 ",
21
21
  ]) {
22
- expect(normalizeOrderId(spoken), spoken).toBe("#W5866402");
22
+ // Soft: a narrowed normalizer drops a whole family of spoken forms, and
23
+ // which ones it drops is what says where the pattern went wrong.
24
+ expect.soft(normalizeOrderId(spoken), spoken).toBe("#W5866402");
23
25
  }
24
26
  });
25
27
  });
@@ -37,7 +39,7 @@ describe("resolveOrder — canonical ids", () => {
37
39
  const state = stateFor("olivia_ito_3591");
38
40
  for (const spoken of ["#W5866402", "W 5866402", "5866402"]) {
39
41
  const order = resolveOrder(state, spoken);
40
- expect(isError(order) ? null : order.order_id, spoken).toBe("#W5866402");
42
+ expect.soft(isError(order) ? null : order.order_id, spoken).toBe("#W5866402");
41
43
  }
42
44
  });
43
45
 
@@ -52,7 +54,7 @@ describe("resolveOrder — shorthand", () => {
52
54
  const state = stateFor("olivia_ito_3591");
53
55
  for (const spoken of ["the delivered one", "my delivered order", "delivered"]) {
54
56
  const order = resolveOrder(state, spoken);
55
- expect(isError(order) ? null : order.order_id, spoken).toBe("#W5866402");
57
+ expect.soft(isError(order) ? null : order.order_id, spoken).toBe("#W5866402");
56
58
  }
57
59
  });
58
60
 
@@ -61,7 +63,7 @@ describe("resolveOrder — shorthand", () => {
61
63
  const result = resolveOrder(state, "my pending order");
62
64
  if (!isError(result)) throw new Error("expected ambiguity");
63
65
  for (const id of ["#W5442520", "#W7941031", "#W3657213"]) {
64
- expect(result.error).toContain(id);
66
+ expect.soft(result.error, `ambiguity message omits ${id}`).toContain(id);
65
67
  }
66
68
  });
67
69