@alexkroman1/aai-cli 13.3.0 → 15.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{_agent-Clbq3a4h.mjs → _agent-BzUeqOdj.mjs} +32 -9
- package/dist/_agent.d.ts +23 -0
- package/dist/_artifacts-BJOYGQPp.mjs +21 -0
- package/dist/_artifacts.d.ts +16 -0
- package/dist/_build-target.d.ts +172 -0
- package/dist/{_bundler-DEkjO1d8.mjs → _bundler-DM0d0M7m.mjs} +8 -7
- package/dist/_cli-common.d.ts +45 -6
- package/dist/{_config-ZY2euhdh.mjs → _config-DYzC6WMD.mjs} +2 -2
- package/dist/_dev-env.d.ts +1 -1
- package/dist/{_dev-server-C13xuGB1.mjs → _dev-server-BzWB6-4y.mjs} +25 -24
- package/dist/_dev-vite-config.d.ts +3 -3
- package/dist/_e2e-test-utils.d.ts +1 -1
- package/dist/{_init-Ci4QGjp8.mjs → _init-Bsi3DZNJ.mjs} +9 -23
- package/dist/{_output-DbJt9gsl.mjs → _output-DBP9Op_d.mjs} +22 -13
- package/dist/_output.d.ts +10 -9
- package/dist/{_preflight-B2laWPdU.mjs → _preflight-CcAR577V.mjs} +2 -2
- package/dist/_resource-commands.d.ts +9 -9
- package/dist/_server-common-De0haHr9.mjs +70 -0
- package/dist/_server-common.d.ts +20 -1
- package/dist/{_slug-api-DlKpj91X.mjs → _slug-api-CGvc2YSQ.mjs} +18 -9
- package/dist/_slug-api.d.ts +19 -0
- package/dist/{_studio-DTi3EdXQ.mjs → _studio-DnR_BqFp.mjs} +7 -3
- package/dist/_studio-commands.d.ts +13 -13
- package/dist/{_templates-V4l5sZJy.mjs → _templates-CIlJ3Vay.mjs} +4 -4
- package/dist/_templates.d.ts +1 -1
- package/dist/{_typecheck-gate-sV4N5F93.mjs → _typecheck-gate-U3jcil8L.mjs} +3 -2
- package/dist/_typecheck-gate.d.ts +3 -1
- package/dist/{_utils-Dhd058D9.mjs → _utils-D5JGcjiW.mjs} +43 -1
- package/dist/_utils.d.ts +35 -0
- package/dist/_vercel-output.d.ts +63 -0
- package/dist/{_workflow-determinism-C9l8uC2R.mjs → _workflow-determinism-CvpqMpdh.mjs} +18 -26
- package/dist/_workflow-determinism.d.ts +3 -3
- package/dist/build-BhEaxBPu.mjs +481 -0
- package/dist/build.d.ts +23 -10
- package/dist/cli.d.ts +1 -2
- package/dist/cli.mjs +155 -138
- package/dist/{client-bundler-CT-_26c5.mjs → client-bundler-6mTLs6ny.mjs} +5 -5
- package/dist/client-bundler.d.ts +1 -1
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-RqlQZ6Y_.mjs → delete-BdFnbnVR.mjs} +4 -4
- package/dist/{deploy-B7f3CK7v.mjs → deploy-CGqPU5U-.mjs} +11 -11
- package/dist/{dev-DamXieW_.mjs → dev-Bx9gYBHM.mjs} +3 -3
- package/dist/{eval-h3DTVfRw.mjs → eval-B3I7FqN9.mjs} +3 -3
- package/dist/{init-D-vFrV8h.mjs → init-CFyusRbq.mjs} +48 -20
- package/dist/init.d.ts +14 -0
- package/dist/{login-CDiemNmC.mjs → login-DaLrPOFg.mjs} +4 -6
- package/dist/{logs-DGwPNCzB.mjs → logs-7BRwq0cG.mjs} +21 -14
- package/dist/project-config.mjs +1 -1
- package/dist/scaffold/CLAUDE.md +155 -76
- package/dist/scaffold/package.json +6 -6
- package/dist/{secret-V1w7y9-j.mjs → secret-Cjp912yF.mjs} +5 -5
- package/dist/secret.d.ts +8 -6
- package/dist/start.d.ts +112 -0
- package/dist/start.mjs +156 -0
- package/dist/{studio-BpMCfUNx.mjs → studio-C_zuRC_z.mjs} +13 -15
- package/dist/templates/briefing-desk/agent.eval.test.ts +156 -0
- package/dist/templates/call-audit/agent.test.ts +11 -6
- package/dist/templates/call-audit/agent.ts +2 -2
- package/dist/templates/call-audit/client.tsx +27 -42
- package/dist/templates/call-audit/workflows/audit.ts +7 -5
- package/dist/templates/call-audit/workflows/ingest.ts +7 -7
- package/dist/templates/call-audit/workflows/media.ts +12 -37
- package/dist/templates/call-audit/workflows/summarize.ts +8 -8
- package/dist/templates/call-audit/workflows/sync-api.ts +3 -3
- package/dist/templates/code-interpreter/agent.test.ts +103 -0
- package/dist/templates/dispatch-center/agent.test.ts +7 -7
- package/dist/templates/dispatch-center/client.tsx +53 -61
- package/dist/templates/dispatch-center/shared.ts +0 -8
- package/dist/templates/dispatch-center/tools/ops_dashboard.ts +4 -1
- package/dist/templates/embedded-assets/agent.eval.test.ts +4 -2
- package/dist/templates/health-assistant/agent.eval.test.ts +4 -2
- package/dist/templates/infocom-adventure/client.tsx +40 -36
- package/dist/templates/link-digest/agent.test.ts +3 -3
- package/dist/templates/link-digest/agent.ts +2 -2
- package/dist/templates/link-digest/client.tsx +81 -19
- package/dist/templates/link-digest/workflows/digest.ts +13 -16
- package/dist/templates/math-buddy/agent.test.ts +126 -0
- package/dist/templates/night-owl/agent.eval.test.ts +4 -2
- package/dist/templates/night-owl/client.tsx +8 -2
- package/dist/templates/personal-finance/agent.test.ts +127 -0
- package/dist/templates/pipeline-simple/agent.test.ts +2 -2
- package/dist/templates/pizza-ordering/client.tsx +5 -4
- package/dist/templates/pizza-ordering/shared.ts +3 -8
- package/dist/templates/pizza-ordering/tools/add_pizza.ts +3 -2
- package/dist/templates/pizza-ordering/tools/place_order.ts +3 -2
- package/dist/templates/pizza-ordering/tools/remove_pizza.ts +3 -2
- package/dist/templates/pizza-ordering/tools/update_pizza.ts +3 -2
- package/dist/templates/pizza-ordering/tools/view_order.ts +3 -2
- package/dist/templates/plan-and-execute/agent.test.ts +10 -5
- package/dist/templates/plan-and-execute/client.tsx +2 -2
- package/dist/templates/podcast-digest/agent.test.ts +20 -11
- package/dist/templates/podcast-digest/client.tsx +16 -22
- package/dist/templates/podcast-digest/workflows/digest.ts +21 -19
- package/dist/templates/podcast-digest/workflows/feeds.ts +61 -33
- package/dist/templates/podcast-digest/workflows/slack.ts +7 -7
- package/dist/templates/recap-workflow/agent.eval.test.ts +21 -13
- package/dist/templates/recap-workflow/agent.test.ts +27 -23
- package/dist/templates/recap-workflow/workflows/recap.ts +24 -28
- package/dist/templates/redline/agent.eval.test.ts +5 -8
- package/dist/templates/redline/agent.test.ts +1 -1
- package/dist/templates/redline/client.tsx +36 -34
- package/dist/templates/redline/workflows/redline.ts +17 -17
- package/dist/templates/research-workflow/agent.eval.test.ts +5 -5
- package/dist/templates/research-workflow/agent.test.ts +10 -7
- package/dist/templates/research-workflow/workflows/research.ts +28 -30
- package/dist/templates/retail/agent.test.ts +17 -15
- package/dist/templates/retail/cancel.ts +7 -6
- package/dist/templates/retail/client.tsx +39 -58
- package/dist/templates/retail/payment.ts +4 -3
- package/dist/templates/retail/pending.test.ts +19 -19
- package/dist/templates/retail/store.test.ts +5 -5
- package/dist/templates/retail/swap.ts +8 -11
- package/dist/templates/solo-rpg/agent.eval.test.ts +2 -16
- package/dist/templates/solo-rpg/agent.test.ts +12 -12
- package/dist/templates/solo-rpg/client.tsx +3 -3
- package/dist/templates/spoken-summary/agent.test.ts +9 -9
- package/dist/templates/spoken-summary/agent.ts +3 -3
- package/dist/templates/spoken-summary/client.tsx +23 -30
- package/dist/templates/spoken-summary/workflows/summarize.ts +11 -11
- package/dist/templates/spoken-summary/workflows/transcribe.ts +14 -14
- package/dist/templates/support-line/agent.ts +8 -0
- package/dist/templates/support-line/client.tsx +4 -3
- package/dist/templates/support-line/prompts.ts +0 -2
- package/dist/templates/transcription-workflow/agent.test.ts +20 -12
- package/dist/templates/transcription-workflow/agent.ts +2 -2
- package/dist/templates/transcription-workflow/client.tsx +15 -14
- package/dist/templates/transcription-workflow/recover.ts +1 -1
- package/dist/templates/transcription-workflow/run-panel.tsx +34 -14
- package/dist/templates/transcription-workflow/total-latency.tsx +46 -20
- package/dist/templates/transcription-workflow/workflows/batch.ts +15 -15
- package/dist/templates/transcription-workflow/workflows/downsample.ts +1 -1
- package/dist/templates/transcription-workflow/workflows/normalize.ts +15 -19
- package/dist/templates/transcription-workflow/workflows/stream.ts +12 -12
- package/dist/templates/transcription-workflow/workflows/sync-api.ts +3 -3
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +23 -34
- package/dist/templates/travel-concierge/agent.eval.test.ts +2 -2
- package/dist/templates/travel-concierge/agent.test.ts +15 -7
- package/dist/templates/travel-concierge/client.tsx +2 -2
- package/dist/templates/travel-concierge/routing.ts +64 -55
- package/dist/templates/travel-concierge/shared.ts +10 -14
- package/dist/templates/travel-concierge/tools/cancel_action.ts +3 -1
- package/dist/templates/travel-concierge/tools/complete_or_escalate.ts +3 -1
- package/dist/templates/travel-concierge/tools/confirm_action.ts +3 -1
- package/dist/templates/travel-concierge/tools/lookup_booking.ts +4 -3
- package/dist/templates/travel-concierge/tools/search_car_rentals.ts +3 -2
- package/dist/templates/travel-concierge/tools/search_excursions.ts +3 -2
- package/dist/templates/travel-concierge/tools/search_flights.ts +3 -2
- package/dist/templates/travel-concierge/tools/search_hotels.ts +3 -2
- package/dist/templates/web-researcher/agent.test.ts +130 -0
- package/dist/{test-guenJUxi.mjs → test-D032V2cA.mjs} +4 -17
- package/dist/typecheck.mjs +1 -1
- package/dist/worker-bundler.d.ts +1 -1
- package/dist/worker-bundler.mjs +8 -8
- package/dist/{workflow-BZyKLkhV.mjs → workflow-CVioyO3V.mjs} +6 -5
- package/package.json +9 -4
- package/dist/_server-common-BV4_CrP3.mjs +0 -43
- package/dist/build-CWyFGe-W.mjs +0 -69
- package/dist/scaffold/server.mjs +0 -204
package/dist/start.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `aai start` — serve a BUILT agent from a plain Node process.
|
|
3
|
+
*
|
|
4
|
+
* The deployment counterpart of `aai dev`: no file watching, no Vite, no
|
|
5
|
+
* typecheck, no source evaluation. It loads the artifact `aai build` left at
|
|
6
|
+
* {@link WORKER_ARTIFACT_REL} — the same one `aai publish` uploads and the
|
|
7
|
+
* managed platform runs — so a self-hosted agent and a deployed one cannot
|
|
8
|
+
* behave differently.
|
|
9
|
+
*
|
|
10
|
+
* ## Why this is a command rather than a file in every project
|
|
11
|
+
*
|
|
12
|
+
* It used to be `scaffold/server.mjs`, ~300 lines of boot that `aai init`
|
|
13
|
+
* copied into every scaffolded project: worker load, env resolution, schema
|
|
14
|
+
* DDL, client-directory probing, error classification, listen, signal
|
|
15
|
+
* handlers. Shipping that as source made each of those a fact a USER's
|
|
16
|
+
* repository asserted, so improving any of them reached only projects
|
|
17
|
+
* scaffolded afterwards, and an existing project silently kept the old
|
|
18
|
+
* behaviour with nothing to report the drift.
|
|
19
|
+
*
|
|
20
|
+
* Every framework that solved this solved it the same way: the boot belongs to
|
|
21
|
+
* the framework and the project holds none of it. Next has `next start` and
|
|
22
|
+
* generates a `server.js` for `output: "standalone"` rather than asking anyone
|
|
23
|
+
* to write one. Nitro's default production preset is `node-server`, emitted
|
|
24
|
+
* into the build directory. The custom server is a documented opt-out, not the
|
|
25
|
+
* default everyone inherits — and here that opt-out is
|
|
26
|
+
* {@link createProjectServer}, which builds the server and binds nothing.
|
|
27
|
+
*
|
|
28
|
+
* ## Why the CLI rather than the runtime
|
|
29
|
+
*
|
|
30
|
+
* Booting a project needs three things at once: the runtime, the project's
|
|
31
|
+
* `.aai/` layout, and the prebuilt browser client. Only this package depends on
|
|
32
|
+
* all three — `aai-runtime` may not import `@alexkroman1/aai-ui`, and
|
|
33
|
+
* konsistent's `runtime-package-boundary` carries the install-weight argument
|
|
34
|
+
* for why that stays true. `client-dir.ts`'s own module doc records that this
|
|
35
|
+
* composition has always lived in an ENTRY POINT; this is that entry point,
|
|
36
|
+
* owned by the framework instead of copied into each project.
|
|
37
|
+
*
|
|
38
|
+
* The cost, stated because it is real: `npm start` needs `@alexkroman1/aai-cli`
|
|
39
|
+
* installed, so a production image carries a build toolchain it does not run.
|
|
40
|
+
* Next makes the same trade — `next` is a `dependency`, not a `devDependency` —
|
|
41
|
+
* and it is why the scaffold moves this package to `dependencies`.
|
|
42
|
+
*/
|
|
43
|
+
import type { AgentDef } from "@alexkroman1/aai";
|
|
44
|
+
import { type AgentServer } from "@alexkroman1/aai-runtime";
|
|
45
|
+
export { CLIENT_ARTIFACT_REL } from "./_artifacts.ts";
|
|
46
|
+
/** The port `aai start` binds when neither an argument nor `PORT` says otherwise. */
|
|
47
|
+
export declare const DEFAULT_START_PORT = 3000;
|
|
48
|
+
/** Options for {@link createProjectServer} and {@link executeStart}. */
|
|
49
|
+
export interface StartOptions {
|
|
50
|
+
/** Project root — the directory holding `agent.ts` and `.aai/`. */
|
|
51
|
+
cwd: string;
|
|
52
|
+
/** Port to bind. Defaults to `PORT`, then {@link DEFAULT_START_PORT}. */
|
|
53
|
+
port?: number | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* Address to bind. Defaults to `HOST`, then loopback.
|
|
56
|
+
*
|
|
57
|
+
* Loopback is deliberate: this server has no request authentication of its
|
|
58
|
+
* own, so exposing it is an explicit act, and `HOST=0.0.0.0` is how a
|
|
59
|
+
* container says so. An EMPTY `HOST` means unset, not "every interface".
|
|
60
|
+
*/
|
|
61
|
+
host?: string | undefined;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Load the built agent, or fail saying what to run.
|
|
65
|
+
*
|
|
66
|
+
* A `file:` URL rather than a relative specifier, because on Windows a bare
|
|
67
|
+
* POSIX-looking path is not a valid module specifier.
|
|
68
|
+
*/
|
|
69
|
+
export declare function loadBuiltAgent(cwd: string): Promise<AgentDef>;
|
|
70
|
+
/**
|
|
71
|
+
* Build this project's {@link AgentServer} WITHOUT binding a socket.
|
|
72
|
+
*
|
|
73
|
+
* The seam a custom server is written against, and the one a serverless host
|
|
74
|
+
* needs: Vercel documents `export default <http.Server>` as its Node WebSocket
|
|
75
|
+
* shape and binds the socket itself, so such a host takes `AgentServer.node`
|
|
76
|
+
* and never calls `listen()`. `aai build --target vercel` emits an entry that
|
|
77
|
+
* does exactly this, so nothing host-specific is committed to a project.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts no-check
|
|
81
|
+
* // The entry `aai build --target vercel` bundles into
|
|
82
|
+
* // `.vercel/output/functions/index.func/` — `no-check` because this file
|
|
83
|
+
* // lives in the USER's project, where `@alexkroman1/aai-cli/start` resolves;
|
|
84
|
+
* // it cannot resolve from inside this package.
|
|
85
|
+
* import { createProjectServer } from "@alexkroman1/aai-cli/start";
|
|
86
|
+
*
|
|
87
|
+
* const server = (await createProjectServer({ cwd: import.meta.dirname })).node;
|
|
88
|
+
*
|
|
89
|
+
* export default function handler(req, res) {
|
|
90
|
+
* server.emit("request", req, res);
|
|
91
|
+
* }
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare function createProjectServer(options: StartOptions): Promise<AgentServer>;
|
|
95
|
+
/** What {@link executeStart} answers, for `--json` and for tests. */
|
|
96
|
+
export interface StartResult {
|
|
97
|
+
ok: true;
|
|
98
|
+
data: {
|
|
99
|
+
name: string;
|
|
100
|
+
port: number | undefined;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Serve the built agent and keep serving it: bind, announce, and shut down
|
|
105
|
+
* cleanly on a signal.
|
|
106
|
+
*
|
|
107
|
+
* The signal listeners are SYNCHRONOUS. An `async` one hands its promise to
|
|
108
|
+
* `process`, which discards what a listener returns — so a `close()` that
|
|
109
|
+
* rejected would surface as an unhandled rejection, i.e. a crash with a stack
|
|
110
|
+
* trace on Ctrl-C, instead of the non-zero exit a failed shutdown should be.
|
|
111
|
+
*/
|
|
112
|
+
export declare function executeStart(options: StartOptions): Promise<StartResult>;
|
package/dist/start.mjs
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { n as log } from "./_ui-DCt4qZrk.mjs";
|
|
3
|
+
import { n as WORKER_ARTIFACT_REL, t as CLIENT_ARTIFACT_REL } from "./_artifacts-BJOYGQPp.mjs";
|
|
4
|
+
import { n as resolveServerEnv, t as DEPLOY_ENV_FILES } from "./_server-common-De0haHr9.mjs";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
8
|
+
import { createAgentServer, ensureSessionStateSchema, ensureWorkflowJournalSchema, withHostCredentialFallback } from "@alexkroman1/aai-runtime";
|
|
9
|
+
import { defaultClientDir } from "@alexkroman1/aai-ui/client-dir";
|
|
10
|
+
//#region src/start.ts
|
|
11
|
+
/**
|
|
12
|
+
* `aai start` — serve a BUILT agent from a plain Node process.
|
|
13
|
+
*
|
|
14
|
+
* The deployment counterpart of `aai dev`: no file watching, no Vite, no
|
|
15
|
+
* typecheck, no source evaluation. It loads the artifact `aai build` left at
|
|
16
|
+
* {@link WORKER_ARTIFACT_REL} — the same one `aai publish` uploads and the
|
|
17
|
+
* managed platform runs — so a self-hosted agent and a deployed one cannot
|
|
18
|
+
* behave differently.
|
|
19
|
+
*
|
|
20
|
+
* ## Why this is a command rather than a file in every project
|
|
21
|
+
*
|
|
22
|
+
* It used to be `scaffold/server.mjs`, ~300 lines of boot that `aai init`
|
|
23
|
+
* copied into every scaffolded project: worker load, env resolution, schema
|
|
24
|
+
* DDL, client-directory probing, error classification, listen, signal
|
|
25
|
+
* handlers. Shipping that as source made each of those a fact a USER's
|
|
26
|
+
* repository asserted, so improving any of them reached only projects
|
|
27
|
+
* scaffolded afterwards, and an existing project silently kept the old
|
|
28
|
+
* behaviour with nothing to report the drift.
|
|
29
|
+
*
|
|
30
|
+
* Every framework that solved this solved it the same way: the boot belongs to
|
|
31
|
+
* the framework and the project holds none of it. Next has `next start` and
|
|
32
|
+
* generates a `server.js` for `output: "standalone"` rather than asking anyone
|
|
33
|
+
* to write one. Nitro's default production preset is `node-server`, emitted
|
|
34
|
+
* into the build directory. The custom server is a documented opt-out, not the
|
|
35
|
+
* default everyone inherits — and here that opt-out is
|
|
36
|
+
* {@link createProjectServer}, which builds the server and binds nothing.
|
|
37
|
+
*
|
|
38
|
+
* ## Why the CLI rather than the runtime
|
|
39
|
+
*
|
|
40
|
+
* Booting a project needs three things at once: the runtime, the project's
|
|
41
|
+
* `.aai/` layout, and the prebuilt browser client. Only this package depends on
|
|
42
|
+
* all three — `aai-runtime` may not import `@alexkroman1/aai-ui`, and
|
|
43
|
+
* konsistent's `runtime-package-boundary` carries the install-weight argument
|
|
44
|
+
* for why that stays true. `client-dir.ts`'s own module doc records that this
|
|
45
|
+
* composition has always lived in an ENTRY POINT; this is that entry point,
|
|
46
|
+
* owned by the framework instead of copied into each project.
|
|
47
|
+
*
|
|
48
|
+
* The cost, stated because it is real: `npm start` needs `@alexkroman1/aai-cli`
|
|
49
|
+
* installed, so a production image carries a build toolchain it does not run.
|
|
50
|
+
* Next makes the same trade — `next` is a `dependency`, not a `devDependency` —
|
|
51
|
+
* and it is why the scaffold moves this package to `dependencies`.
|
|
52
|
+
*/
|
|
53
|
+
/** The port `aai start` binds when neither an argument nor `PORT` says otherwise. */
|
|
54
|
+
const DEFAULT_START_PORT = 3e3;
|
|
55
|
+
/**
|
|
56
|
+
* Load the built agent, or fail saying what to run.
|
|
57
|
+
*
|
|
58
|
+
* A `file:` URL rather than a relative specifier, because on Windows a bare
|
|
59
|
+
* POSIX-looking path is not a valid module specifier.
|
|
60
|
+
*/
|
|
61
|
+
async function loadBuiltAgent(cwd) {
|
|
62
|
+
const workerPath = path.join(cwd, WORKER_ARTIFACT_REL);
|
|
63
|
+
if (!existsSync(workerPath)) throw new Error(`No built agent at ${WORKER_ARTIFACT_REL}. Run \`aai build\` first — the scaffold's \`prestart\` script normally does it for you.`);
|
|
64
|
+
return (await import(pathToFileURL(workerPath).href)).default;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Static assets to serve at `/`: this project's own built UI when it has one,
|
|
68
|
+
* otherwise the prebuilt default client that ships inside `@alexkroman1/aai-ui`.
|
|
69
|
+
*
|
|
70
|
+
* A `client.tsx` that has not been BUILT is worth saying out loud — the server
|
|
71
|
+
* would otherwise serve the default UI and look like it had ignored the file.
|
|
72
|
+
*/
|
|
73
|
+
function resolveClientDir(cwd) {
|
|
74
|
+
const built = path.join(cwd, CLIENT_ARTIFACT_REL);
|
|
75
|
+
if (existsSync(path.join(built, "index.html"))) return built;
|
|
76
|
+
if (existsSync(path.join(cwd, "client.tsx"))) log.warn("client.tsx is not built — serving the default UI. Run `aai build` first.");
|
|
77
|
+
return defaultClientDir();
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Build this project's {@link AgentServer} WITHOUT binding a socket.
|
|
81
|
+
*
|
|
82
|
+
* The seam a custom server is written against, and the one a serverless host
|
|
83
|
+
* needs: Vercel documents `export default <http.Server>` as its Node WebSocket
|
|
84
|
+
* shape and binds the socket itself, so such a host takes `AgentServer.node`
|
|
85
|
+
* and never calls `listen()`. `aai build --target vercel` emits an entry that
|
|
86
|
+
* does exactly this, so nothing host-specific is committed to a project.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts no-check
|
|
90
|
+
* // The entry `aai build --target vercel` bundles into
|
|
91
|
+
* // `.vercel/output/functions/index.func/` — `no-check` because this file
|
|
92
|
+
* // lives in the USER's project, where `@alexkroman1/aai-cli/start` resolves;
|
|
93
|
+
* // it cannot resolve from inside this package.
|
|
94
|
+
* import { createProjectServer } from "@alexkroman1/aai-cli/start";
|
|
95
|
+
*
|
|
96
|
+
* const server = (await createProjectServer({ cwd: import.meta.dirname })).node;
|
|
97
|
+
*
|
|
98
|
+
* export default function handler(req, res) {
|
|
99
|
+
* server.emit("request", req, res);
|
|
100
|
+
* }
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
async function createProjectServer(options) {
|
|
104
|
+
const { cwd } = options;
|
|
105
|
+
const agent = await loadBuiltAgent(cwd);
|
|
106
|
+
const env = await resolveServerEnv(cwd, void 0, DEPLOY_ENV_FILES);
|
|
107
|
+
if (env.DATABASE_URL) {
|
|
108
|
+
await ensureSessionStateSchema({
|
|
109
|
+
url: env.DATABASE_URL,
|
|
110
|
+
logger: console
|
|
111
|
+
});
|
|
112
|
+
await ensureWorkflowJournalSchema({
|
|
113
|
+
url: env.DATABASE_URL,
|
|
114
|
+
logger: console
|
|
115
|
+
});
|
|
116
|
+
} else log.warn("No DATABASE_URL: session state and durable runs live in THIS process's memory.\nOne replica is fine. Behind a load balancer, enable sticky sessions so a reconnect (the client re-dials with ?sessionId=) reaches the same process — or set DATABASE_URL and let every replica share the state.");
|
|
117
|
+
return createAgentServer({
|
|
118
|
+
agent,
|
|
119
|
+
env,
|
|
120
|
+
providerEnv: withHostCredentialFallback(env),
|
|
121
|
+
clientDir: resolveClientDir(cwd),
|
|
122
|
+
...process.env.PUBLIC_URL?.trim() ? { publicUrl: process.env.PUBLIC_URL.trim() } : {}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Serve the built agent and keep serving it: bind, announce, and shut down
|
|
127
|
+
* cleanly on a signal.
|
|
128
|
+
*
|
|
129
|
+
* The signal listeners are SYNCHRONOUS. An `async` one hands its promise to
|
|
130
|
+
* `process`, which discards what a listener returns — so a `close()` that
|
|
131
|
+
* rejected would surface as an unhandled rejection, i.e. a crash with a stack
|
|
132
|
+
* trace on Ctrl-C, instead of the non-zero exit a failed shutdown should be.
|
|
133
|
+
*/
|
|
134
|
+
async function executeStart(options) {
|
|
135
|
+
const agent = await loadBuiltAgent(options.cwd);
|
|
136
|
+
const server = await createProjectServer(options);
|
|
137
|
+
const port = options.port ?? Number(process.env.PORT ?? 3e3);
|
|
138
|
+
const host = options.host ?? (process.env.HOST?.trim() || void 0);
|
|
139
|
+
await server.listen(port, host);
|
|
140
|
+
log.info(`${agent.name} listening on http://${host ?? "127.0.0.1"}:${server.port}`);
|
|
141
|
+
for (const signal of ["SIGINT", "SIGTERM"]) process.once(signal, () => {
|
|
142
|
+
server.close().then(() => process.exit(0), (error) => {
|
|
143
|
+
log.error(`shutdown failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
return {
|
|
148
|
+
ok: true,
|
|
149
|
+
data: {
|
|
150
|
+
name: agent.name,
|
|
151
|
+
port: server.port
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
156
|
+
export { CLIENT_ARTIFACT_REL, DEFAULT_START_PORT, createProjectServer, executeStart, loadBuiltAgent };
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as ok, t as CliError } from "./_output-
|
|
2
|
+
import { a as ok, t as CliError } from "./_output-DBP9Op_d.mjs";
|
|
3
3
|
import { n as log, t as fmtUrl } from "./_ui-DCt4qZrk.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
4
|
+
import { l as formatCappedList } from "./_utils-D5JGcjiW.mjs";
|
|
5
|
+
import { s as updateProjectConfig } from "./_config-DYzC6WMD.mjs";
|
|
6
|
+
import { n as resolveServerEnv } from "./_server-common-De0haHr9.mjs";
|
|
7
|
+
import { o as resolveDeployTarget } from "./_agent-BzUeqOdj.mjs";
|
|
8
|
+
import { layerScaffold } from "./_templates-CIlJ3Vay.mjs";
|
|
9
|
+
import { a as publishStudioProject, c as studioProjectUrl, f as checkedResponse, i as projectNameFromDir, n as fetchStudioProject, o as pushStudioSource, r as listStudioProjects, s as studioProjectApiUrl, t as collectSourceFiles, u as apiRequest } from "./_studio-DnR_BqFp.mjs";
|
|
9
10
|
import { existsSync } from "node:fs";
|
|
10
11
|
import path from "node:path";
|
|
11
|
-
import { isRecord, omitUndefined } from "@alexkroman1/aai/utils";
|
|
12
|
+
import { isRecord, omitUndefined, plural } from "@alexkroman1/aai/utils";
|
|
12
13
|
import { mkdir, readdir, writeFile } from "node:fs/promises";
|
|
13
14
|
import { isPathInside } from "@alexkroman1/aai-runtime/internal";
|
|
14
15
|
//#region src/studio.ts
|
|
@@ -62,7 +63,7 @@ async function notFoundHint(serverUrl, apiKey) {
|
|
|
62
63
|
const projects = await listStudioProjects(serverUrl, apiKey).catch(() => null);
|
|
63
64
|
if (projects === null) return "Run `aai list` to see your projects.";
|
|
64
65
|
if (projects.length === 0) return "This login has no studio projects at all. If yours are in the studio, the CLI is linked to a different account — run `aai login` again, approve it in a browser signed in to the account that owns the project, then `aai list`.";
|
|
65
|
-
return `Your projects: ${projects
|
|
66
|
+
return `Your projects: ${formatCappedList(projects)}.`;
|
|
66
67
|
}
|
|
67
68
|
async function executePull(opts) {
|
|
68
69
|
const { serverUrl, apiKey } = await resolveDeployTarget(opts.cwd, opts.server);
|
|
@@ -159,17 +160,14 @@ async function syncEnvSecrets(cwd, serverUrl, apiKey, project) {
|
|
|
159
160
|
method: "PUT",
|
|
160
161
|
body: env
|
|
161
162
|
});
|
|
162
|
-
log.info(`Synced ${names.length}
|
|
163
|
+
log.info(`Synced ${names.length} ${plural(names.length, "secret")} from .env`);
|
|
163
164
|
return names;
|
|
164
165
|
}
|
|
165
166
|
async function executePublish(opts) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
await assertTypechecks(opts.cwd);
|
|
169
|
-
}
|
|
167
|
+
const { assertTypechecks } = await import("./_typecheck-gate-U3jcil8L.mjs");
|
|
168
|
+
await assertTypechecks(opts.cwd, { skip: opts.skipTypecheck });
|
|
170
169
|
const pushed = await pushProject(opts);
|
|
171
170
|
const { project, serverUrl, apiKey } = pushed;
|
|
172
|
-
const hadSlug = pushed.slug !== void 0;
|
|
173
171
|
if (pushed.slug) await syncEnvSecrets(opts.cwd, serverUrl, apiKey, project);
|
|
174
172
|
log.step(`Publishing ${project} (builds in the project's sandbox)…`);
|
|
175
173
|
const result = checkedResponse(await publishStudioProject(serverUrl, apiKey, project, { skipTypecheck: opts.skipTypecheck }), (value) => isRecord(value) && typeof value.slug === "string" && typeof value.output === "string", `the publish route at ${serverUrl}`);
|
|
@@ -178,7 +176,7 @@ async function executePublish(opts) {
|
|
|
178
176
|
serverUrl,
|
|
179
177
|
slug: result.slug
|
|
180
178
|
});
|
|
181
|
-
if (!
|
|
179
|
+
if (!pushed.slug) {
|
|
182
180
|
if ((await syncEnvSecrets(opts.cwd, serverUrl, apiKey, result.slug)).length > 0) log.info("They apply on the next `aai publish`.");
|
|
183
181
|
}
|
|
184
182
|
const agentUrl = `${serverUrl}/${result.slug}`;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// An EVAL: does the desk really DELEGATE, or does it brief you from memory?
|
|
2
|
+
//
|
|
3
|
+
// `agent.test.ts` settles what each tool does once it has been called — it
|
|
4
|
+
// hands `researchAngle` a `stubDelegate` and asserts on the board. What it
|
|
5
|
+
// cannot settle is the two things this template exists to demonstrate: that the
|
|
6
|
+
// MODEL turns "tell me about home battery prices" into ONE `research_topic`
|
|
7
|
+
// call carrying several angles rather than one call per angle, and that nothing
|
|
8
|
+
// the caller hears came from a web tool the DESK holds — because it holds none.
|
|
9
|
+
//
|
|
10
|
+
// Run it with `aai eval`. Without a provider key every case runs against a
|
|
11
|
+
// SCRIPTED model (its `stubReply`), which proves the wiring and nothing about
|
|
12
|
+
// the choice — so the two claims above are `{ live: true }` and the recap case,
|
|
13
|
+
// whose whole point is that it spends no model at all, is not.
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The def a DEPLOYED agent runs: authored, plus what `tools/` declares, plus
|
|
17
|
+
* its PROMPT.
|
|
18
|
+
*
|
|
19
|
+
* Taken from `virtual:aai/agent` rather than a hand-written glob: the plugin
|
|
20
|
+
* expands it against THIS file's own directory, so the spec needs no glob and
|
|
21
|
+
* no shared helper — which matters because this file SHIPS, and a scaffolded
|
|
22
|
+
* project has no repo helper to import. `agent.ts` here is three fields, so an
|
|
23
|
+
* eval driving it alone would measure an agent with no tools and the FRAMEWORK
|
|
24
|
+
* DEFAULT prompt — i.e. a desk that has never heard of a researcher, on which
|
|
25
|
+
* every claim below would pass or fail for the wrong reason. The reasoning is
|
|
26
|
+
* spelled out in `../code-interpreter/agent.eval.test.ts`.
|
|
27
|
+
*/
|
|
28
|
+
import agentDef from "virtual:aai/agent";
|
|
29
|
+
import { describeTurn, toolNames, toolResultIn } from "@alexkroman1/aai-runtime/eval";
|
|
30
|
+
import { describeEval } from "@alexkroman1/aai-runtime/eval/vitest";
|
|
31
|
+
import { expect } from "vitest";
|
|
32
|
+
import { z } from "zod";
|
|
33
|
+
import { MAX_ANGLES } from "./shared.ts";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Every tool the desk declares — the two that reach the outside world through a
|
|
37
|
+
* subagent, and the recap that reaches nothing.
|
|
38
|
+
*
|
|
39
|
+
* Named here because the isolation claim is stated as a NEGATIVE — no
|
|
40
|
+
* `web_search`, no `visit_webpage` — and a negative over a hand-typed list is
|
|
41
|
+
* the assertion that goes quietly true when a tool is renamed. Every call the
|
|
42
|
+
* desk makes must be one of these three names.
|
|
43
|
+
*/
|
|
44
|
+
const DESK_TOOLS: readonly string[] = ["research_topic", "verify_claim", "briefing_so_far"];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* What `research_topic` answers with, as a schema rather than a cast.
|
|
48
|
+
*
|
|
49
|
+
* `toolResultIn` takes one for the reason `night-owl`'s spec records: a result
|
|
50
|
+
* that stopped carrying `findings` FAILS here naming the field, where a cast
|
|
51
|
+
* hands the assertions `undefined` and fails a line later on something
|
|
52
|
+
* unrelated.
|
|
53
|
+
*/
|
|
54
|
+
const BriefingResult = z.object({
|
|
55
|
+
topic: z.string(),
|
|
56
|
+
findings: z.array(z.object({ angle: z.string(), summary: z.string() })),
|
|
57
|
+
failed: z.array(z.object({ angle: z.string() })),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/** What `briefing_so_far` answers with on an EMPTY board. */
|
|
61
|
+
const EmptyRecap = z.object({
|
|
62
|
+
topic: z.null(),
|
|
63
|
+
findings: z.array(z.unknown()).length(0),
|
|
64
|
+
message: z.string(),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/** What `verify_claim` answers with. */
|
|
68
|
+
const Verdict = z.object({ claim: z.string(), verdict: z.string() });
|
|
69
|
+
|
|
70
|
+
describeEval(agentDef, (test) => {
|
|
71
|
+
test(
|
|
72
|
+
"a recap on an empty board costs no researcher",
|
|
73
|
+
async ({ session }) => {
|
|
74
|
+
const turn = await session.say("What have you got for me so far?");
|
|
75
|
+
|
|
76
|
+
// The one tool here that spends no model at all, and the case that can
|
|
77
|
+
// therefore run against a script: the desk answers the recap out of its
|
|
78
|
+
// own slot. A desk that reached for `research_topic` to find out what it
|
|
79
|
+
// already knows is the regression — it bills the caller for four
|
|
80
|
+
// researchers to answer "nothing yet".
|
|
81
|
+
expect(toolNames(turn.toolCalls), describeTurn(turn)).toEqual(["briefing_so_far"]);
|
|
82
|
+
// And the slot really resolved: an empty board reports itself as empty
|
|
83
|
+
// rather than throwing or answering with a half-built shape.
|
|
84
|
+
expect(toolResultIn(turn.toolCalls, "briefing_so_far", EmptyRecap).topic).toBeNull();
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
stubReply: [
|
|
88
|
+
{ tool: "briefing_so_far", args: {} },
|
|
89
|
+
"Nothing yet — tell me a subject and I'll put some researchers on it.",
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
test(
|
|
95
|
+
"fans one subject out across angles in a SINGLE call",
|
|
96
|
+
async ({ session }) => {
|
|
97
|
+
const turn = await session.say("What's going on with home battery prices?");
|
|
98
|
+
|
|
99
|
+
// One call carrying several angles, never one call per angle. That is the
|
|
100
|
+
// whole economic claim of the template — the angles run in parallel, so
|
|
101
|
+
// the caller waits for the slowest rather than the sum — and a desk that
|
|
102
|
+
// called the tool three times in a row would satisfy any assertion that
|
|
103
|
+
// merely counted angles.
|
|
104
|
+
const calls = turn.toolCalls.filter((call) => call.name === "research_topic");
|
|
105
|
+
expect(calls, describeTurn(turn)).toHaveLength(1);
|
|
106
|
+
const angles = z.array(z.string()).parse(calls[0]?.args.angles);
|
|
107
|
+
expect(angles.length).toBeGreaterThan(1);
|
|
108
|
+
expect(angles.length).toBeLessThanOrEqual(MAX_ANGLES);
|
|
109
|
+
// Each angle stands on its own — the prompt's rule, and the one a
|
|
110
|
+
// researcher cannot recover from, having heard none of this call. A bare
|
|
111
|
+
// "the same for Europe" is a handful of characters; a self-contained
|
|
112
|
+
// question is a sentence.
|
|
113
|
+
for (const angle of angles) expect(angle.length).toBeGreaterThan(15);
|
|
114
|
+
|
|
115
|
+
// What came back is what the researchers concluded, not what the desk
|
|
116
|
+
// believes: every finding carries prose, and the board holds the topic.
|
|
117
|
+
const result = toolResultIn(turn.toolCalls, "research_topic", BriefingResult);
|
|
118
|
+
expect(result.findings).not.toEqual([]);
|
|
119
|
+
for (const finding of result.findings) expect(finding.summary.length).toBeGreaterThan(40);
|
|
120
|
+
|
|
121
|
+
// The isolation claim, and the reason this template is not
|
|
122
|
+
// `web-researcher`: the desk has NO web tools, so anything it says about
|
|
123
|
+
// the world crossed back out of a subagent. A `web_search` in this list
|
|
124
|
+
// would mean the builtins had leaked onto the parent.
|
|
125
|
+
expect(toolNames(turn.toolCalls).filter((name) => !DESK_TOOLS.includes(name))).toEqual([]);
|
|
126
|
+
expect(turn.text).not.toBe("");
|
|
127
|
+
},
|
|
128
|
+
// Live only: a script cannot choose the angles, and choosing them is the
|
|
129
|
+
// measurement. It also cannot run a subagent — the researcher resolves a
|
|
130
|
+
// model of its own from `shared.ts`, which the turn's stub never covers.
|
|
131
|
+
{ live: true },
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
test(
|
|
135
|
+
"checks a claim the caller pushes back on instead of defending it",
|
|
136
|
+
async ({ session }) => {
|
|
137
|
+
const turn = await session.say(
|
|
138
|
+
"Someone told me home batteries pay for themselves in two years. " +
|
|
139
|
+
"Is that right? Check it for me.",
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// `verify_claim` is the cheaper subagent on the narrower surface, and
|
|
143
|
+
// reaching for it rather than answering is the prompt's rule that a live
|
|
144
|
+
// model actually has to keep. The claim it forwards must be the sentence
|
|
145
|
+
// it was given, not a keyword — a fact-checker handed "batteries" answers
|
|
146
|
+
// confidently about nothing.
|
|
147
|
+
const calls = turn.toolCalls.filter((call) => call.name === "verify_claim");
|
|
148
|
+
expect(calls, describeTurn(turn)).toHaveLength(1);
|
|
149
|
+
const verdict = toolResultIn(turn.toolCalls, "verify_claim", Verdict);
|
|
150
|
+
expect(verdict.claim.split(/\s+/).length).toBeGreaterThan(3);
|
|
151
|
+
expect(verdict.verdict).not.toBe("");
|
|
152
|
+
expect(toolNames(turn.toolCalls).filter((name) => !DESK_TOOLS.includes(name))).toEqual([]);
|
|
153
|
+
},
|
|
154
|
+
{ live: true },
|
|
155
|
+
);
|
|
156
|
+
});
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
import { readdir } from "node:fs/promises";
|
|
33
33
|
import { tmpdir } from "node:os";
|
|
34
34
|
import { FatalError, RetryableError } from "@alexkroman1/aai/step-errors";
|
|
35
|
-
import {
|
|
35
|
+
import { createWorkflowContext, stubSpeech, WORKFLOW_CONTEXT_NOW } from "@alexkroman1/aai/testing";
|
|
36
36
|
import {
|
|
37
37
|
installStubGateway,
|
|
38
38
|
installStubReporter,
|
|
@@ -516,7 +516,7 @@ describe("planning where to cut", () => {
|
|
|
516
516
|
test("the last segment never addresses a byte the file does not have", () => {
|
|
517
517
|
// The twelve-byte bug this API shape exists to prevent: planning from a
|
|
518
518
|
// duration rounded to whole milliseconds put `endByte` at 640,512 for a
|
|
519
|
-
// 640,500-byte file. `
|
|
519
|
+
// 640,500-byte file. `stepReadUpload` clamps a window to the stored size, so
|
|
520
520
|
// nothing threw — the plan was simply describing audio that does not exist.
|
|
521
521
|
// Found by running the real argv against a real ffmpeg, which is the only
|
|
522
522
|
// place a twelve-byte error was ever going to surface.
|
|
@@ -867,14 +867,14 @@ describe("the body's step policy", () => {
|
|
|
867
867
|
* only reads `ctx.steps` afterwards, so it is built once.
|
|
868
868
|
*/
|
|
869
869
|
const walkedCtx = () =>
|
|
870
|
-
|
|
870
|
+
createWorkflowContext({
|
|
871
871
|
runSteps: false,
|
|
872
872
|
// The body reads the clock at both ends with `ctx.now()`, which the engine
|
|
873
873
|
// journals under `now!0` and `now!1`. A PRODUCER here rather than a fixed
|
|
874
874
|
// number, so the two reaches differ and `elapsedMs` is assertable — based
|
|
875
|
-
// on `
|
|
875
|
+
// on `WORKFLOW_CONTEXT_NOW`, the instant the fake otherwise freezes at, rather
|
|
876
876
|
// than on a second arbitrary epoch nobody can relate to the first.
|
|
877
|
-
now: clockReads(
|
|
877
|
+
now: clockReads(WORKFLOW_CONTEXT_NOW, WORKFLOW_CONTEXT_NOW + 3000),
|
|
878
878
|
results: {
|
|
879
879
|
ingestRecording: {
|
|
880
880
|
audio: "upl_pcm",
|
|
@@ -937,7 +937,7 @@ describe("the body's step policy", () => {
|
|
|
937
937
|
/**
|
|
938
938
|
* `auditFlow` on the real replay engine — as far as this tier can take it.
|
|
939
939
|
*
|
|
940
|
-
* The block above drives the body through `
|
|
940
|
+
* The block above drives the body through `createWorkflowContext` with
|
|
941
941
|
* `runSteps: false` and a journaled result per step, which is what makes it
|
|
942
942
|
* affordable: `ingestRecording` shells out to the ffmpeg toolchain, which this
|
|
943
943
|
* tier cannot usefully feed — the `ingestRecording` and `narrate` specs above
|
|
@@ -970,6 +970,11 @@ describe("the run is DURABLE, as far as ffmpeg allows", () => {
|
|
|
970
970
|
});
|
|
971
971
|
installStubReporter();
|
|
972
972
|
vi.stubEnv("ASSEMBLYAI_API_KEY", "test-key");
|
|
973
|
+
// Neither binary is this block's subject (see its doc), and a real
|
|
974
|
+
// conversion here would make a developer with ffmpeg installed run a
|
|
975
|
+
// slower, different test from CI, which has none.
|
|
976
|
+
vi.stubEnv("AAI_FFMPEG_PATH", "/nonexistent/ffmpeg");
|
|
977
|
+
vi.stubEnv("AAI_FFPROBE_PATH", "/nonexistent/ffprobe");
|
|
973
978
|
});
|
|
974
979
|
|
|
975
980
|
test("a FatalError in the first step fails the run on ONE attempt, not six", async () => {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* - `transcription-workflow` owns the FAN-OUT — why the sync transcription
|
|
21
21
|
* endpoint's 120-second cap forces one, and how a run survives dying on segment
|
|
22
22
|
* 27 of 60.
|
|
23
|
-
* - `spoken-summary` owns the audio ROUND TRIP — `stepSpeak`, `
|
|
23
|
+
* - `spoken-summary` owns the audio ROUND TRIP — `stepSpeak`, `stepWriteUpload`, and
|
|
24
24
|
* why a page needs `api.download` rather than a URL.
|
|
25
25
|
*
|
|
26
26
|
* **What this template adds is a decoder**, and everything downstream changes
|
|
@@ -153,7 +153,7 @@ export const audit: WorkflowDef<typeof auditInput, CallAudit> = workflow({
|
|
|
153
153
|
input: auditInput,
|
|
154
154
|
// The one line that makes the form take a file: `<WorkflowFields>` renders a
|
|
155
155
|
// picker for this property, `useWorkflowSubmit` stores the chosen file, and the
|
|
156
|
-
// ingest step reads it back with `
|
|
156
|
+
// ingest step reads it back with `stepReadUpload`.
|
|
157
157
|
uploads: ["recording"],
|
|
158
158
|
run: auditFlow,
|
|
159
159
|
});
|