@alexkroman1/aai-cli 13.2.0 → 14.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/bin.mjs +1 -1
- package/dist/{_agent-BTwZJu4P.mjs → _agent-BzUeqOdj.mjs} +33 -10
- package/dist/_agent.d.ts +23 -0
- package/dist/{_bundler-C31rlcYz.mjs → _bundler-DolUCMxu.mjs} +9 -8
- package/dist/_cli-common.d.ts +45 -6
- package/dist/{_config-CmJOFsAP.mjs → _config-DYzC6WMD.mjs} +7 -4
- package/dist/_config.d.ts +25 -1
- package/dist/_dev-env.d.ts +1 -1
- package/dist/{_dev-server-DWvkNoIS.mjs → _dev-server-CSMqF8PN.mjs} +28 -30
- package/dist/_dev-vite-config.d.ts +3 -3
- package/dist/_e2e-test-utils.d.ts +15 -7
- package/dist/{_init-BURbdHNM.mjs → _init-CQ8idAwo.mjs} +64 -30
- package/dist/{_output-CKkmWs7i.mjs → _output-DBP9Op_d.mjs} +43 -3
- package/dist/_output.d.ts +24 -7
- package/dist/{_preflight-Co1G9Jww.mjs → _preflight-CcAR577V.mjs} +3 -3
- package/dist/_resource-commands.d.ts +9 -9
- package/dist/{_server-common-6e0QI4mq.mjs → _server-common-vILJp3it.mjs} +3 -3
- package/dist/{_slug-api-CfGQq1Tt.mjs → _slug-api-CGvc2YSQ.mjs} +19 -10
- package/dist/_slug-api.d.ts +19 -0
- package/dist/{_studio-B1waXMuR.mjs → _studio-DnR_BqFp.mjs} +9 -5
- package/dist/_studio-commands.d.ts +13 -13
- package/dist/{_templates-0Zr2z6yA.mjs → _templates-CK4oKoeX.mjs} +3 -3
- package/dist/{_typecheck-gate-BXvUNnfs.mjs → _typecheck-gate-U3jcil8L.mjs} +5 -4
- package/dist/_typecheck-gate.d.ts +3 -1
- package/dist/{_ui-u7T4YooX.mjs → _ui-DCt4qZrk.mjs} +1 -1
- package/dist/{_utils-B8QmtFhK.mjs → _utils-D5JGcjiW.mjs} +44 -2
- package/dist/_utils.d.ts +35 -0
- package/dist/{_vite-env-D9Qj6KPX.mjs → _vite-env-kejF0p2L.mjs} +1 -1
- package/dist/{_workflow-determinism-f9J-5Zlj.mjs → _workflow-determinism-CvpqMpdh.mjs} +19 -27
- package/dist/_workflow-determinism.d.ts +3 -3
- package/dist/build-Mxk8gWvX.mjs +108 -0
- package/dist/build.d.ts +5 -0
- package/dist/cli.mjs +172 -116
- package/dist/{client-bundler-j5c46x0G.mjs → client-bundler-BJgREAh6.mjs} +4 -4
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-X_CRgCf6.mjs → delete-BdFnbnVR.mjs} +6 -6
- package/dist/{deploy-BzZ-r4iM.mjs → deploy-uAJ4NukN.mjs} +14 -14
- package/dist/{dev-BDykFvcc.mjs → dev-DApPSaE_.mjs} +5 -5
- package/dist/{eval-0qEOdm0l.mjs → eval-BK47A_K5.mjs} +27 -11
- package/dist/eval.d.ts +2 -0
- package/dist/{init-BtJI306C.mjs → init-DukDxECd.mjs} +13 -21
- package/dist/{login-DeUETobb.mjs → login-DaLrPOFg.mjs} +6 -8
- package/dist/{logs-hWnJ66Bl.mjs → logs-7BRwq0cG.mjs} +22 -15
- package/dist/project-config.mjs +1 -1
- package/dist/scaffold/.env.example +17 -1
- package/dist/scaffold/CLAUDE.md +84 -56
- package/dist/scaffold/package.json +6 -5
- package/dist/scaffold/server.mjs +129 -25
- package/dist/secret-Cjp912yF.mjs +162 -0
- package/dist/secret.d.ts +48 -13
- package/dist/{studio-BxAS_FQQ.mjs → studio-CpHlNHUZ.mjs} +15 -17
- package/dist/templates/briefing-desk/agent.test.ts +7 -3
- package/dist/templates/call-audit/agent.test.ts +25 -9
- package/dist/templates/call-audit/agent.ts +2 -2
- package/dist/templates/call-audit/client.tsx +40 -58
- 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/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/embedded-assets/agent.test.ts +7 -1
- package/dist/templates/health-assistant/agent.eval.test.ts +4 -2
- package/dist/templates/health-assistant/agent.test.ts +8 -4
- package/dist/templates/infocom-adventure/client.tsx +40 -36
- package/dist/templates/link-digest/agent.test.ts +10 -4
- package/dist/templates/link-digest/agent.ts +2 -2
- package/dist/templates/link-digest/client.tsx +42 -36
- package/dist/templates/link-digest/workflows/digest.ts +13 -16
- package/dist/templates/night-owl/agent.eval.test.ts +4 -2
- package/dist/templates/night-owl/agent.test.ts +6 -1
- package/dist/templates/night-owl/client.tsx +8 -2
- package/dist/templates/pipeline-simple/agent.test.ts +33 -16
- 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 +33 -14
- package/dist/templates/podcast-digest/client.tsx +27 -36
- 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 +49 -32
- 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 +8 -2
- package/dist/templates/redline/client.tsx +47 -48
- 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 +17 -8
- 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/registry.test.ts +99 -58
- package/dist/templates/retail/store.test.ts +5 -5
- package/dist/templates/retail/swap.ts +8 -11
- package/dist/templates/simple/agent.test.ts +39 -6
- 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 +21 -12
- package/dist/templates/spoken-summary/agent.ts +3 -3
- package/dist/templates/spoken-summary/client.tsx +36 -47
- 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/client.tsx +4 -3
- package/dist/templates/support-line/prompts.ts +0 -2
- package/dist/templates/transcription-workflow/agent.test.ts +36 -20
- package/dist/templates/transcription-workflow/agent.ts +2 -2
- package/dist/templates/transcription-workflow/client.tsx +34 -40
- 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/shared.ts +10 -14
- 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/test-D032V2cA.mjs +242 -0
- package/dist/test.d.ts +97 -31
- package/dist/typecheck.mjs +2 -2
- package/dist/worker-bundler.mjs +3 -3
- package/dist/{workflow-BBu5oEDw.mjs → workflow-CVioyO3V.mjs} +8 -7
- package/package.json +10 -10
- package/dist/bin.d.mts +0 -2
- package/dist/build-BLlLOrng.mjs +0 -66
- package/dist/secret-B-3T5tvr.mjs +0 -61
- package/dist/test-CiLab-AA.mjs +0 -182
- package/dist/tsdown.config.d.ts +0 -2
package/dist/scaffold/CLAUDE.md
CHANGED
|
@@ -77,7 +77,8 @@ The fast loop: edit → `pnpm dev` (browser, talk to it) →
|
|
|
77
77
|
you stopped talking, barge-in, two sentences merging into one turn. Those
|
|
78
78
|
need `pnpm dev` and your own voice.
|
|
79
79
|
4. **Run `pnpm build` before declaring done** — bundles `agent.ts`,
|
|
80
|
-
type-checks,
|
|
80
|
+
type-checks, validates the manifest, and runs the WHOLE spec suite first.
|
|
81
|
+
Catches issues `dev` won't.
|
|
81
82
|
5. **Make small, focused changes** — verify each one before stacking the
|
|
82
83
|
next.
|
|
83
84
|
6. **Look at templates before writing custom code** — the CLI ships working
|
|
@@ -109,6 +110,7 @@ npx @alexkroman1/aai-cli init # Scaffold a new agent
|
|
|
109
110
|
npx @alexkroman1/aai-cli templates # List available templates
|
|
110
111
|
npx @alexkroman1/aai-cli dev # Start local dev server
|
|
111
112
|
npx @alexkroman1/aai-cli test # Run agent.test.ts via vitest
|
|
113
|
+
npx @alexkroman1/aai-cli test --all # ...or every spec in the project
|
|
112
114
|
npx @alexkroman1/aai-cli eval # Run agent.eval.test.ts against a model
|
|
113
115
|
npx @alexkroman1/aai-cli build # Bundle and validate
|
|
114
116
|
npx @alexkroman1/aai-cli deploy # Deploy to production
|
|
@@ -122,6 +124,13 @@ The scaffold's `package.json` exposes `dev`, `build`, `test`, `eval` and
|
|
|
122
124
|
`deploy` as `pnpm <name>` shortcuts. Other commands (`init`, `templates`,
|
|
123
125
|
`delete`, `secret`) are CLI-only.
|
|
124
126
|
|
|
127
|
+
**`aai test` targets `agent.test.ts` and nothing else**, which `--all` widens.
|
|
128
|
+
What matters is that a narrowed run does not report itself as a pass: when the
|
|
129
|
+
project holds spec files the run did not cover, it FAILS and names them rather
|
|
130
|
+
than printing a green line that says nothing about `tools/*.test.ts`. `pnpm
|
|
131
|
+
test` (the scaffold's own script) already runs the whole suite, and so does the
|
|
132
|
+
gate in front of `aai build`.
|
|
133
|
+
|
|
125
134
|
## Running it yourself (`npm start`)
|
|
126
135
|
|
|
127
136
|
`server.mjs` serves this agent from a plain Node process — no platform
|
|
@@ -416,14 +425,14 @@ below.
|
|
|
416
425
|
|
|
417
426
|
### Workflow bodies live in `workflows/`
|
|
418
427
|
|
|
419
|
-
A body is an ordinary exported async function of its input and a `
|
|
428
|
+
A body is an ordinary exported async function of its input and a `WorkflowContext`.
|
|
420
429
|
There is no directive and no compile step of its own — the agent bundle compiles
|
|
421
430
|
`workflows/` like any other source file — and durability is a method call:
|
|
422
431
|
|
|
423
432
|
```ts
|
|
424
|
-
import type {
|
|
433
|
+
import type { WorkflowContext } from "@alexkroman1/aai";
|
|
425
434
|
|
|
426
|
-
export async function digestFlow(input: { url: string }, ctx:
|
|
435
|
+
export async function digestFlow(input: { url: string }, ctx: WorkflowContext) {
|
|
427
436
|
const digest = await ctx.step("summarize", () => summarize(input.url));
|
|
428
437
|
|
|
429
438
|
// Suspended, not blocked: the container is free to exit here and the run
|
|
@@ -515,11 +524,11 @@ The three undurable reads a body most often wants, each journaled — read once
|
|
|
515
524
|
the first reach, and the same value on every later walk:
|
|
516
525
|
|
|
517
526
|
```ts
|
|
518
|
-
import type {
|
|
527
|
+
import type { WorkflowContext } from "@alexkroman1/aai";
|
|
519
528
|
|
|
520
529
|
declare function charge(amount: number, idempotencyKey: string, jitter: number): Promise<void>;
|
|
521
530
|
|
|
522
|
-
export async function chargeFlow(input: { amount: number }, ctx:
|
|
531
|
+
export async function chargeFlow(input: { amount: number }, ctx: WorkflowContext) {
|
|
523
532
|
const startedAt = await ctx.now(); // epoch ms, decided once
|
|
524
533
|
const idempotencyKey = await ctx.uuid(); // still the same id after a crash
|
|
525
534
|
const jitter = await ctx.random(); // one float in [0, 1), journaled per call
|
|
@@ -601,6 +610,22 @@ Five things worth knowing:
|
|
|
601
610
|
which is how a "send it now" tool cuts a scheduled wait short. Naming no ids
|
|
602
611
|
wakes every outstanding SLEEP and deliberately not a `waitFor` deadline, so
|
|
603
612
|
cutting a schedule short cannot also close an approval window.
|
|
613
|
+
- **A SUSPEND is not free, so `ctx.sleep` is not a `setTimeout`.** A wait costs
|
|
614
|
+
a journal write to record it, a queued delivery to bring the run back, and a
|
|
615
|
+
fresh WALK of the body — measured on a deployed agent at roughly a second of
|
|
616
|
+
overhead around the sleep itself, on top of whatever you asked for. There is a
|
|
617
|
+
cliff at the bottom of the range worth knowing about: a sleep shorter
|
|
618
|
+
than the round trip that records it never suspends AT ALL — `ctx.sleep("beat",
|
|
619
|
+
100)` and `ctx.sleep("beat", 0)` are both simply free — while anything longer
|
|
620
|
+
pays the whole cost. Measured, `nap(100)` and `nap(0)` came back within 30 ms
|
|
621
|
+
of each other on a run whose total was 2.1 s.
|
|
622
|
+
|
|
623
|
+
So a sub-second pause is not what this is for. For a short backoff inside a
|
|
624
|
+
step, use an ordinary timer (`sleep` from `@alexkroman1/aai/internal`) — a step
|
|
625
|
+
body may not call `ctx.sleep` anyway, and the engine refuses one that does. Use
|
|
626
|
+
`ctx.sleep` for a wait you want to SURVIVE the process, which is the thing a
|
|
627
|
+
timer cannot do. A body that polls in a loop pays the suspend per iteration,
|
|
628
|
+
which is the strongest argument for the next section: park on the callback.
|
|
604
629
|
|
|
605
630
|
#### A third-party callback is an OPTIMIZATION over a reconciling read
|
|
606
631
|
|
|
@@ -656,15 +681,15 @@ trap somebody has already paid for:
|
|
|
656
681
|
Steps are ordinary exported functions, so a spec imports and calls them. The
|
|
657
682
|
BODY needs an engine, and there are two, for two different questions.
|
|
658
683
|
|
|
659
|
-
**"What did the body ask for?"** — `
|
|
684
|
+
**"What did the body ask for?"** — `createWorkflowContext` from
|
|
660
685
|
`@alexkroman1/aai/testing`. It runs the steps and records the names, the retry
|
|
661
686
|
policies and the sleeps, over one walk with no journal. Nothing replays, so a
|
|
662
687
|
spec built on it must not claim to test durability.
|
|
663
688
|
|
|
664
689
|
```ts no-check
|
|
665
|
-
import {
|
|
690
|
+
import { createWorkflowContext } from "@alexkroman1/aai/testing";
|
|
666
691
|
|
|
667
|
-
const ctx =
|
|
692
|
+
const ctx = createWorkflowContext({ runSteps: false });
|
|
668
693
|
await digestFlow({ url: "https://example.com/a" }, ctx);
|
|
669
694
|
|
|
670
695
|
expect(ctx.steps.map((s) => s.name)).toEqual(["fetchArticle", "summarize", "file"]);
|
|
@@ -729,7 +754,7 @@ root barrel would drag the whole SDK into that bundle.
|
|
|
729
754
|
|
|
730
755
|
```ts
|
|
731
756
|
import { stepEnv } from "@alexkroman1/aai/step";
|
|
732
|
-
import {
|
|
757
|
+
import { stepGenerateOrFail } from "@alexkroman1/aai/step-errors";
|
|
733
758
|
|
|
734
759
|
async function summarize(text: string) {
|
|
735
760
|
// The agent's env by name — the same values a tool reads from `ctx.env`.
|
|
@@ -737,7 +762,7 @@ async function summarize(text: string) {
|
|
|
737
762
|
const style = stepEnv("DIGEST_STYLE") ?? "plain";
|
|
738
763
|
|
|
739
764
|
// One model call, on the agent's own ASSEMBLYAI_API_KEY and default model.
|
|
740
|
-
return await
|
|
765
|
+
return await stepGenerateOrFail(`${style} summary of:\n\n${text}`, {
|
|
741
766
|
system: "Reply with two sentences and nothing else.",
|
|
742
767
|
});
|
|
743
768
|
}
|
|
@@ -749,9 +774,9 @@ before and after a deploy. List what you read in `requiredEnv` and a deploy
|
|
|
749
774
|
checks it for you. And **`stepGenerate` is not `ctx.generate`**: it is one
|
|
750
775
|
request to the AssemblyAI LLM Gateway, with no tools and no structured output,
|
|
751
776
|
because bundling the AI SDK into a step artifact costs megabytes on every
|
|
752
|
-
deploy. Use `
|
|
777
|
+
deploy. Use `stepGenerateJsonOrFail` with a Zod `schema` if you need a shape.
|
|
753
778
|
|
|
754
|
-
### From a step, reach for the `
|
|
779
|
+
### From a step, reach for the `OrFail` call
|
|
755
780
|
|
|
756
781
|
`@alexkroman1/aai/step-errors` publishes a wrapper for every `/step` call that
|
|
757
782
|
can fail against a remote service, and **inside a step the wrapper is the one to
|
|
@@ -759,14 +784,14 @@ use**:
|
|
|
759
784
|
|
|
760
785
|
| Raw, on `@alexkroman1/aai/step` | Use this instead, on `@alexkroman1/aai/step-errors` |
|
|
761
786
|
| --- | --- |
|
|
762
|
-
| `stepGenerate` | `
|
|
763
|
-
| `stepGenerateJson` | `
|
|
764
|
-
| `stepFetch` | `
|
|
765
|
-
| `stepTranscribeSync` | `
|
|
766
|
-
| `stepTranscribeUpload` / `Submit` / `Poll` | the matching `*
|
|
767
|
-
| `sendToChannel` (`/channels`) | `
|
|
768
|
-
|
|
769
|
-
`
|
|
787
|
+
| `stepGenerate` | `stepGenerateOrFail` |
|
|
788
|
+
| `stepGenerateJson` | `stepGenerateJsonOrFail` |
|
|
789
|
+
| `stepFetch` | `stepFetchOrFail` |
|
|
790
|
+
| `stepTranscribeSync` | `stepTranscribeSyncOrFail` |
|
|
791
|
+
| `stepTranscribeUpload` / `Submit` / `Poll` | the matching `*OrFail` |
|
|
792
|
+
| `sendToChannel` (`/channels`) | `sendToChannelOrFail` |
|
|
793
|
+
|
|
794
|
+
`stepFetchOrFail` is the one that is not spelled `*OrFail`, and the name is the
|
|
770
795
|
difference: the others turn an already-thrown failure into a classified one,
|
|
771
796
|
while this also turns a NON-2XX RESPONSE into a throw — `stepFetch` resolves
|
|
772
797
|
with a `404` rather than raising it. Two changes, so two names.
|
|
@@ -804,7 +829,7 @@ bundling rule as `/step` — import them there, never through the root barrel:
|
|
|
804
829
|
recording, or `stepTranscribeUpload` → `stepTranscribeSubmit` →
|
|
805
830
|
`stepTranscribePoll` for a long one, plus `Transcript`, `TranscribeError` and
|
|
806
831
|
the `TRANSCRIBE_*` limits. (There is no `/transcribe` subpath; transcription
|
|
807
|
-
lives on `/step` with the other step primitives.) Use the `
|
|
832
|
+
lives on `/step` with the other step primitives.) Use the `OrFail`
|
|
808
833
|
wrappers above: a provider refusal — a container it will not read, a
|
|
809
834
|
recording with no speech — arrives
|
|
810
835
|
with `retryable: false`, and unclassified a step re-uploads the same bytes
|
|
@@ -841,19 +866,19 @@ export async function measure(uploadId: string) {
|
|
|
841
866
|
|
|
842
867
|
A run that finishes while nobody is on the line needs somewhere to put the
|
|
843
868
|
result. `slackChannel({ webhookUrl })` names a destination and
|
|
844
|
-
`
|
|
869
|
+
`sendToChannelOrFail(channel, message)` posts to it:
|
|
845
870
|
|
|
846
871
|
```ts no-check
|
|
847
872
|
import { type ChannelMessage, slackChannel } from "@alexkroman1/aai/channels";
|
|
848
873
|
import { requireStepEnv } from "@alexkroman1/aai/step";
|
|
849
|
-
import {
|
|
874
|
+
import { sendToChannelOrFail } from "@alexkroman1/aai/step-errors";
|
|
850
875
|
|
|
851
876
|
export async function announce(headline: string, points: string[]) {
|
|
852
877
|
const message: ChannelMessage = {
|
|
853
878
|
text: headline,
|
|
854
879
|
sections: points.map((point) => ({ text: point })),
|
|
855
880
|
};
|
|
856
|
-
return await
|
|
881
|
+
return await sendToChannelOrFail(slackChannel({ webhookUrl: requireStepEnv("SLACK_WEBHOOK_URL") }), message);
|
|
857
882
|
}
|
|
858
883
|
```
|
|
859
884
|
|
|
@@ -862,7 +887,7 @@ it with `aai secret put`. A channel's credential is its DESTINATION and is
|
|
|
862
887
|
passed in, which is why no channel reads an env var of its own. `ChannelMessage`
|
|
863
888
|
is rendered per platform, so the same message is legal on a channel kind added
|
|
864
889
|
later; `isSlackWebhookUrl` / `isSlackWorkflowTriggerUrl` validate a pasted URL
|
|
865
|
-
before a run depends on it, and `
|
|
890
|
+
before a run depends on it, and `explainChannelFailure` turns a refusal into a sentence
|
|
866
891
|
a person can act on. `podcast-digest` is the worked example.
|
|
867
892
|
|
|
868
893
|
### A step's HTTP: use `stepFetch`, not `fetch`
|
|
@@ -941,11 +966,11 @@ Both are on `@alexkroman1/aai/step`, and `spoken-summary` is the template that
|
|
|
941
966
|
shows the whole round trip.
|
|
942
967
|
|
|
943
968
|
```ts
|
|
944
|
-
import { stepSpeak,
|
|
969
|
+
import { stepSpeak, stepWriteUpload } from "@alexkroman1/aai/step";
|
|
945
970
|
|
|
946
971
|
export async function narrate(script: string) {
|
|
947
972
|
const spoken = await stepSpeak(script, { voice: "jane" });
|
|
948
|
-
const stored = await
|
|
973
|
+
const stored = await stepWriteUpload(spoken.audio, { name: "summary.wav", type: "audio/wav" });
|
|
949
974
|
return { audio: stored.id, durationMs: spoken.durationMs };
|
|
950
975
|
}
|
|
951
976
|
```
|
|
@@ -962,11 +987,13 @@ opens and produces silence rather than an error. The `AssemblyAITtsVoice` type
|
|
|
962
987
|
gives you autocomplete over it and nothing more: it accepts any string, so that
|
|
963
988
|
a voice the service adds after this release still compiles.
|
|
964
989
|
|
|
965
|
-
**`
|
|
966
|
-
output is read back as JSON, so audio cannot travel in one — the same rule
|
|
967
|
-
keeps an uploaded recording's bytes out of a run's INPUT, arriving at the
|
|
968
|
-
end of the run. Store the bytes, return the **id**, and let the page fetch
|
|
969
|
-
with `api.download(id)`.
|
|
990
|
+
**`stepWriteUpload` is `stepReadUpload`'s other direction, and you need it.** A
|
|
991
|
+
run's output is read back as JSON, so audio cannot travel in one — the same rule
|
|
992
|
+
that keeps an uploaded recording's bytes out of a run's INPUT, arriving at the
|
|
993
|
+
other end of the run. Store the bytes, return the **id**, and let the page fetch
|
|
994
|
+
it with `api.download(id)`. A step that wants the record rather than the bytes
|
|
995
|
+
reads it with `stepUploadInfo(id)`, which answers an `UploadInfo` — the name,
|
|
996
|
+
the size stored so far, and whether that is all of it.
|
|
970
997
|
|
|
971
998
|
Three rules come with it:
|
|
972
999
|
|
|
@@ -1020,12 +1047,13 @@ naming a shape is what asks the compiler to make you handle the failure.
|
|
|
1020
1047
|
|
|
1021
1048
|
### The page
|
|
1022
1049
|
|
|
1023
|
-
A workflow app's `client.tsx` mounts with `
|
|
1024
|
-
there is no session to build, so no socket, no audio graph
|
|
1025
|
-
request. Everything else is the same file, React and
|
|
1050
|
+
A workflow app's `client.tsx` mounts with `mountPage()` rather than
|
|
1051
|
+
`mountClient()` — there is no session to build, so no socket, no audio graph
|
|
1052
|
+
and no microphone request. Everything else is the same file, React and
|
|
1053
|
+
Tailwind included.
|
|
1026
1054
|
|
|
1027
1055
|
```tsx no-check
|
|
1028
|
-
import { createWorkflowApi,
|
|
1056
|
+
import { createWorkflowApi, mountPage, useWorkflowRun } from "@alexkroman1/aai-ui";
|
|
1029
1057
|
import "@alexkroman1/aai-ui/styles.css";
|
|
1030
1058
|
import type { WorkflowOutputOf } from "@alexkroman1/aai/workflow-api";
|
|
1031
1059
|
import { useState } from "react";
|
|
@@ -1053,7 +1081,7 @@ export function App() {
|
|
|
1053
1081
|
);
|
|
1054
1082
|
}
|
|
1055
1083
|
|
|
1056
|
-
|
|
1084
|
+
mountPage({ name: "Link Digest", component: App });
|
|
1057
1085
|
```
|
|
1058
1086
|
|
|
1059
1087
|
`api.start()` resolves as soon as the RUN EXISTS, not when it finishes — that
|
|
@@ -1172,7 +1200,7 @@ statement about pipeline mode, not about the SDK.
|
|
|
1172
1200
|
### Answering a phone call
|
|
1173
1201
|
|
|
1174
1202
|
A deployed voice agent already serves carrier media streams — there is nothing
|
|
1175
|
-
to switch on. `
|
|
1203
|
+
to switch on. `createRuntimeServer` mounts `WS /phone` whenever the agent is a voice
|
|
1176
1204
|
agent (`telephony` defaults to `true`, and to `false` for a `page: "static"`
|
|
1177
1205
|
workflow app, which has no stages to put on a call). Point the carrier at it
|
|
1178
1206
|
with a `carrier` query parameter naming who is dialling:
|
|
@@ -1188,7 +1216,7 @@ the bridge transcodes in both directions, so the agent, its tools and its slots
|
|
|
1188
1216
|
behave exactly as they do in the browser — a phone call is a transport, not a
|
|
1189
1217
|
mode. Nothing about `agent.ts` changes to support one.
|
|
1190
1218
|
|
|
1191
|
-
Turn the route off with `telephony: false` on `
|
|
1219
|
+
Turn the route off with `telephony: false` on `createRuntimeServer`. If you are
|
|
1192
1220
|
embedding the runtime yourself rather than deploying, the pieces are
|
|
1193
1221
|
`createTelephonyBridge`, `startTelephonySession`, `TELEPHONY_PATH` and
|
|
1194
1222
|
`carrierByName`, all on `@alexkroman1/aai-runtime`.
|
|
@@ -1268,12 +1296,12 @@ API keys require it; the US endpoints reject them. Example:
|
|
|
1268
1296
|
| Factory | SDK package | Env var |
|
|
1269
1297
|
| --------------- | ------------------- | ------------------------------ |
|
|
1270
1298
|
| `anthropicLlm` | `@ai-sdk/anthropic` | `ANTHROPIC_API_KEY` |
|
|
1271
|
-
| `
|
|
1299
|
+
| `openAILlm` | `@ai-sdk/openai` | `OPENAI_API_KEY` |
|
|
1272
1300
|
| `googleLlm` | `@ai-sdk/google` | `GOOGLE_GENERATIVE_AI_API_KEY` |
|
|
1273
1301
|
| `mistralLlm` | `@ai-sdk/mistral` | `MISTRAL_API_KEY` |
|
|
1274
|
-
| `
|
|
1302
|
+
| `xAILlm` | `@ai-sdk/xai` | `XAI_API_KEY` |
|
|
1275
1303
|
| `groqLlm` | `@ai-sdk/groq` | `GROQ_API_KEY` |
|
|
1276
|
-
| `
|
|
1304
|
+
| `openRouterLlm` | `@ai-sdk/openai` | `OPENROUTER_API_KEY` |
|
|
1277
1305
|
| `gatewayLlm` | `ai` (built in) | `AI_GATEWAY_API_KEY` |
|
|
1278
1306
|
| `assemblyAILlm` | `@ai-sdk/openai` | `ASSEMBLYAI_API_KEY` |
|
|
1279
1307
|
|
|
@@ -1283,10 +1311,10 @@ shared by all of them except `assemblyAILlm`. Example:
|
|
|
1283
1311
|
because a third-party vendor's catalog is not this SDK's to default from;
|
|
1284
1312
|
`assemblyAILlm()` is the one bare call, since it has a default model.
|
|
1285
1313
|
|
|
1286
|
-
`
|
|
1314
|
+
`openRouterLlm` routes through [OpenRouter](https://openrouter.ai) — an
|
|
1287
1315
|
OpenAI-compatible endpoint fronting hundreds of models addressed as
|
|
1288
1316
|
`"creator/model"`, e.g.
|
|
1289
|
-
`
|
|
1317
|
+
`openRouterLlm({ model: "meta-llama/llama-3.3-70b-instruct" })`. It needs
|
|
1290
1318
|
no extra SDK install (it reuses the `@ai-sdk/openai` client).
|
|
1291
1319
|
|
|
1292
1320
|
`gatewayLlm` routes through the [Vercel AI
|
|
@@ -1947,7 +1975,7 @@ Those three cover almost everything an agent wants. A database is for data that
|
|
|
1947
1975
|
must outlive a session AND be queryable: a ledger, filed records, cross-session
|
|
1948
1976
|
saves.
|
|
1949
1977
|
|
|
1950
|
-
## Custom UI — `
|
|
1978
|
+
## Custom UI — `mountClient()`
|
|
1951
1979
|
|
|
1952
1980
|
File: `client.tsx` alongside `agent.ts`. Uses **React** (not Preact).
|
|
1953
1981
|
Always import `"@alexkroman1/aai-ui/styles.css"` first.
|
|
@@ -1957,9 +1985,9 @@ Always import `"@alexkroman1/aai-ui/styles.css"` first.
|
|
|
1957
1985
|
```tsx
|
|
1958
1986
|
/// <reference types="vite/client" />
|
|
1959
1987
|
import "@alexkroman1/aai-ui/styles.css";
|
|
1960
|
-
import {
|
|
1988
|
+
import { mountClient } from "@alexkroman1/aai-ui";
|
|
1961
1989
|
|
|
1962
|
-
|
|
1990
|
+
mountClient({ name: "My Agent" });
|
|
1963
1991
|
```
|
|
1964
1992
|
|
|
1965
1993
|
### Tier 1 with sidebar
|
|
@@ -1967,7 +1995,7 @@ client({ name: "My Agent" });
|
|
|
1967
1995
|
```tsx
|
|
1968
1996
|
/// <reference types="vite/client" />
|
|
1969
1997
|
import "@alexkroman1/aai-ui/styles.css";
|
|
1970
|
-
import {
|
|
1998
|
+
import { mountClient, useEvent } from "@alexkroman1/aai-ui";
|
|
1971
1999
|
import { useState } from "react";
|
|
1972
2000
|
|
|
1973
2001
|
function Sidebar() {
|
|
@@ -1982,7 +2010,7 @@ function Sidebar() {
|
|
|
1982
2010
|
);
|
|
1983
2011
|
}
|
|
1984
2012
|
|
|
1985
|
-
|
|
2013
|
+
mountClient({ name: "My Agent", sidebar: Sidebar });
|
|
1986
2014
|
```
|
|
1987
2015
|
|
|
1988
2016
|
### Tier 2 — full custom component
|
|
@@ -1990,7 +2018,7 @@ client({ name: "My Agent", sidebar: Sidebar });
|
|
|
1990
2018
|
```tsx
|
|
1991
2019
|
/// <reference types="vite/client" />
|
|
1992
2020
|
import "@alexkroman1/aai-ui/styles.css";
|
|
1993
|
-
import {
|
|
2021
|
+
import { mountClient, useSession } from "@alexkroman1/aai-ui";
|
|
1994
2022
|
|
|
1995
2023
|
function MyApp() {
|
|
1996
2024
|
const { messages, userTranscript, started, running, start, toggle, end } =
|
|
@@ -2011,10 +2039,10 @@ function MyApp() {
|
|
|
2011
2039
|
);
|
|
2012
2040
|
}
|
|
2013
2041
|
|
|
2014
|
-
|
|
2042
|
+
mountClient({ component: MyApp });
|
|
2015
2043
|
```
|
|
2016
2044
|
|
|
2017
|
-
### `
|
|
2045
|
+
### `mountClient()` config
|
|
2018
2046
|
|
|
2019
2047
|
| Field | Type | Default | Description |
|
|
2020
2048
|
| --- | --- | --- | --- |
|
|
@@ -2172,7 +2200,7 @@ Property 'children' is missing` build error:
|
|
|
2172
2200
|
```tsx
|
|
2173
2201
|
/// <reference types="vite/client" />
|
|
2174
2202
|
import "@alexkroman1/aai-ui/styles.css";
|
|
2175
|
-
import { ChatView,
|
|
2203
|
+
import { ChatView, mountClient, StartScreen } from "@alexkroman1/aai-ui";
|
|
2176
2204
|
|
|
2177
2205
|
function PizzaApp() {
|
|
2178
2206
|
return (
|
|
@@ -2182,7 +2210,7 @@ function PizzaApp() {
|
|
|
2182
2210
|
);
|
|
2183
2211
|
}
|
|
2184
2212
|
|
|
2185
|
-
|
|
2213
|
+
mountClient({ component: PizzaApp });
|
|
2186
2214
|
```
|
|
2187
2215
|
|
|
2188
2216
|
## Styling
|
|
@@ -2190,7 +2218,7 @@ client({ component: PizzaApp });
|
|
|
2190
2218
|
- **Tailwind CSS v4** — compiled at bundle time, configured via CSS.
|
|
2191
2219
|
Do NOT create `tailwind.config.js` — it will be ignored.
|
|
2192
2220
|
- Use Tailwind classes for layout, `useTheme()` for dynamic colors.
|
|
2193
|
-
- Set theme: `
|
|
2221
|
+
- Set theme: `mountClient({ theme: { bg, primary, text, surface, border } })`.
|
|
2194
2222
|
- Override CSS custom properties for extra tokens:
|
|
2195
2223
|
`--color-aai-*`, `--radius-aai`, `--font-aai`.
|
|
2196
2224
|
- Always import `"@alexkroman1/aai-ui/styles.css"` at the top of `client.tsx`.
|
|
@@ -9,14 +9,15 @@
|
|
|
9
9
|
"prestart": "aai build --skip-tests",
|
|
10
10
|
"start": "node server.mjs",
|
|
11
11
|
"build": "aai build",
|
|
12
|
-
"test": "
|
|
12
|
+
"test": "vitest run --exclude \"**/*.eval.test.*\"",
|
|
13
|
+
"test:agent": "aai test",
|
|
13
14
|
"eval": "aai eval",
|
|
14
15
|
"publish:agent": "aai publish"
|
|
15
16
|
},
|
|
16
17
|
"dependencies": {
|
|
17
|
-
"@alexkroman1/aai": "^
|
|
18
|
-
"@alexkroman1/aai-runtime": "^
|
|
19
|
-
"@alexkroman1/aai-ui": "^
|
|
18
|
+
"@alexkroman1/aai": "^14.0.0",
|
|
19
|
+
"@alexkroman1/aai-runtime": "^14.0.0",
|
|
20
|
+
"@alexkroman1/aai-ui": "^14.0.0",
|
|
20
21
|
"react": "^19.2.8",
|
|
21
22
|
"react-dom": "^19.2.8",
|
|
22
23
|
"tailwindcss": "^4.0.0",
|
|
@@ -24,7 +25,7 @@
|
|
|
24
25
|
"zod": "^4.5.4"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
|
27
|
-
"@alexkroman1/aai-cli": "^
|
|
28
|
+
"@alexkroman1/aai-cli": "^14.0.0",
|
|
28
29
|
"@tailwindcss/vite": "^4.3.3",
|
|
29
30
|
"@types/node": "^26.4.1",
|
|
30
31
|
"@types/react": "^19.2.18",
|
package/dist/scaffold/server.mjs
CHANGED
|
@@ -122,6 +122,71 @@ function resolveClientDir() {
|
|
|
122
122
|
return defaultClientDir();
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/**
|
|
126
|
+
* The error classes that mean a DEFECT in this code rather than a mistake in
|
|
127
|
+
* the configuration.
|
|
128
|
+
*
|
|
129
|
+
* Everything below turns a boot failure into two lines and a non-zero exit,
|
|
130
|
+
* which is right for "ASSEMBLYAI_API_KEY is not set" and wrong for a
|
|
131
|
+
* `TypeError`, where the traceback is the only thing that can locate the bug.
|
|
132
|
+
* Those are re-thrown untouched.
|
|
133
|
+
*/
|
|
134
|
+
const BUG_ERRORS = [TypeError, ReferenceError, RangeError, SyntaxError];
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Every distinct message on an error and its `cause` chain, outermost first.
|
|
138
|
+
*
|
|
139
|
+
* A driver failure states the useful half one hop down — `connect to database
|
|
140
|
+
* failed: getaddrinfo ENOTFOUND db` — so printing only the top message is how a
|
|
141
|
+
* tidy envelope ends up less informative than the stack it replaced.
|
|
142
|
+
*
|
|
143
|
+
* @param {unknown} err
|
|
144
|
+
* @returns {string}
|
|
145
|
+
*/
|
|
146
|
+
function errorText(err) {
|
|
147
|
+
/** @type {string[]} */
|
|
148
|
+
const messages = [];
|
|
149
|
+
/** @type {unknown} */
|
|
150
|
+
let cursor = err;
|
|
151
|
+
while (cursor instanceof Error) {
|
|
152
|
+
if (cursor.message !== "" && !messages.includes(cursor.message)) messages.push(cursor.message);
|
|
153
|
+
cursor = cursor.cause;
|
|
154
|
+
}
|
|
155
|
+
return messages.length > 0 ? messages.join(": ") : String(err);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Run one step of BOOT, and answer a failure the way this file already answers
|
|
160
|
+
* a missing build artifact: what is wrong, then what to do, then exit 1.
|
|
161
|
+
*
|
|
162
|
+
* Without this, a missing provider key — the commonest way a first `npm start`
|
|
163
|
+
* fails — killed the process with a ten-frame traceback pointing into
|
|
164
|
+
* `node_modules/@alexkroman1/aai-runtime/dist/host-env-*.js`. The MESSAGE was
|
|
165
|
+
* already good ("AssemblyAI LLM: missing API key. Set ASSEMBLYAI_API_KEY in the
|
|
166
|
+
* agent env."); what it arrived wrapped in was a crash report about somebody
|
|
167
|
+
* else's bundle, in a container that then restarted and did it again.
|
|
168
|
+
*
|
|
169
|
+
* Exiting is deliberate rather than binding anyway and serving an unhealthy
|
|
170
|
+
* `/health`: a process that stays up tells an orchestrator it started, and a
|
|
171
|
+
* misconfigured deployment that reports itself healthy is worse than one that
|
|
172
|
+
* refuses to run. The non-zero exit is what a supervisor, a `docker run`, and
|
|
173
|
+
* CI all already read.
|
|
174
|
+
*
|
|
175
|
+
* @template T
|
|
176
|
+
* @param {string} fix - What the operator should change, in one sentence.
|
|
177
|
+
* @param {() => T | Promise<T>} work
|
|
178
|
+
* @returns {Promise<T>}
|
|
179
|
+
*/
|
|
180
|
+
async function bootOrExit(fix, work) {
|
|
181
|
+
try {
|
|
182
|
+
return await work();
|
|
183
|
+
} catch (err) {
|
|
184
|
+
if (BUG_ERRORS.some((kind) => err instanceof kind)) throw err;
|
|
185
|
+
console.error(`Cannot start the agent: ${errorText(err)}\n${fix}`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
125
190
|
const env = await resolveAgentEnv();
|
|
126
191
|
|
|
127
192
|
/**
|
|
@@ -152,38 +217,77 @@ const publicUrl = process.env.PUBLIC_URL?.trim();
|
|
|
152
217
|
* Best-effort: if a real migration already created them and this role may not
|
|
153
218
|
* CREATE, it warns and the server starts anyway.
|
|
154
219
|
*/
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
220
|
+
// Read into a const: `env.DATABASE_URL` is a record lookup, so its narrowing
|
|
221
|
+
// does not survive into the callback below.
|
|
222
|
+
const databaseUrl = env.DATABASE_URL;
|
|
223
|
+
if (databaseUrl) {
|
|
224
|
+
await bootOrExit(
|
|
225
|
+
"Check DATABASE_URL: this server has to reach that database at boot to create the tables it owns.",
|
|
226
|
+
async () => {
|
|
227
|
+
await ensureSessionStateSchema({ url: databaseUrl, logger: console });
|
|
228
|
+
// And the durable-run journal's, which is a separate set of tables owned
|
|
229
|
+
// by the same deployment. Without it a project with a `DATABASE_URL`
|
|
230
|
+
// boots claiming durable runs and fails on the first one.
|
|
231
|
+
await ensureWorkflowJournalSchema({ url: databaseUrl, logger: console });
|
|
232
|
+
},
|
|
233
|
+
);
|
|
234
|
+
} else {
|
|
235
|
+
/**
|
|
236
|
+
* Say that this process is the only place the state lives, because the next
|
|
237
|
+
* thing an operator does with a container is run two of them.
|
|
238
|
+
*
|
|
239
|
+
* Session state (slots, the event log) is keyed by session id and held in
|
|
240
|
+
* memory here — the boot line below reports it as `sessionState: { backend:
|
|
241
|
+
* 'memory', durable: false }`, which is true and easy to read as being about
|
|
242
|
+
* restarts alone. It is also about REPLICAS: the browser reconnects with
|
|
243
|
+
* `?sessionId=<id>`, so a reconnect that lands on a different process resumes
|
|
244
|
+
* a session that process has never heard of and the agent's context is gone
|
|
245
|
+
* mid-call. One replica has no such problem, which is exactly why nothing
|
|
246
|
+
* catches this until the deployment grows a second one.
|
|
247
|
+
*/
|
|
248
|
+
console.warn(
|
|
249
|
+
"No DATABASE_URL: session state and durable runs live in THIS process's memory.\n" +
|
|
250
|
+
"One replica is fine. Behind a load balancer, enable sticky sessions so a reconnect " +
|
|
251
|
+
"(the client re-dials with ?sessionId=) reaches the same process — or set DATABASE_URL " +
|
|
252
|
+
"and let every replica share the state.",
|
|
253
|
+
);
|
|
161
254
|
}
|
|
162
255
|
|
|
163
|
-
const server =
|
|
164
|
-
|
|
165
|
-
env,
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
256
|
+
const server = await bootOrExit(
|
|
257
|
+
// The commonest first-run failure, and the one whose stack this replaces: a
|
|
258
|
+
// provider credential that is not there. `.env` is what `aai dev` reads too,
|
|
259
|
+
// so the fix is the same one in both places.
|
|
260
|
+
"Set the missing value in .env, or pass it as a real environment variable (`docker run -e NAME=value`), then start again.",
|
|
261
|
+
() =>
|
|
262
|
+
createAgentServer({
|
|
263
|
+
agent,
|
|
264
|
+
env,
|
|
265
|
+
// Provider credentials may ALSO arrive straight from the environment without
|
|
266
|
+
// being declared, and without becoming ctx.env — the ordinary way to hand
|
|
267
|
+
// ASSEMBLYAI_API_KEY to a container. Anything in `env` still wins.
|
|
268
|
+
providerEnv: withHostCredentialFallback(env),
|
|
269
|
+
clientDir: resolveClientDir(),
|
|
270
|
+
...(publicUrl ? { publicUrl } : {}),
|
|
271
|
+
// Durable workflows need nothing passed here. A `DATABASE_URL` in `env` puts
|
|
272
|
+
// the runs in Postgres and they survive a restart; without one they live in a
|
|
273
|
+
// per-process directory and do not, which is the same trade `aai dev` makes.
|
|
274
|
+
//
|
|
275
|
+
// Two options used to sit here — the compiled workflow surface, carried on the
|
|
276
|
+
// bundle as `__aaiWorkflowCode`/`__aaiStepCode` because a `"use workflow"` body
|
|
277
|
+
// had to go through a compiler at BUILD time. The engine reads the agent's own
|
|
278
|
+
// `workflows` declaration instead, so there is no artifact to hand over.
|
|
279
|
+
}),
|
|
280
|
+
);
|
|
181
281
|
|
|
182
282
|
// Loopback by default: this server has no request authentication of its own,
|
|
183
283
|
// so exposing it is a deliberate act. Set HOST=0.0.0.0 to bind every interface
|
|
184
284
|
// behind your own proxy or auth. An empty HOST means unset, not "everything".
|
|
185
285
|
const host = process.env.HOST?.trim() || undefined;
|
|
186
|
-
|
|
286
|
+
const port = Number(process.env.PORT ?? 3000);
|
|
287
|
+
await bootOrExit(
|
|
288
|
+
`Nothing is listening yet — port ${port} is in use, or this process may not bind it. Set PORT to a free one.`,
|
|
289
|
+
() => server.listen(port, host),
|
|
290
|
+
);
|
|
187
291
|
console.log(`${agent.name} listening on http://${host ?? "127.0.0.1"}:${server.port}`);
|
|
188
292
|
|
|
189
293
|
for (const signal of ["SIGINT", "SIGTERM"]) {
|