@alexkroman1/aai-cli 5.11.0 → 5.12.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-DS2PUJcl.mjs → _agent-JGaAJ5j6.mjs} +3 -2
- package/dist/_agent.d.ts +1 -0
- package/dist/{_bundler-DC17suWN.mjs → _bundler-BvfPzf8J.mjs} +28 -9
- package/dist/_bundler.d.ts +16 -2
- package/dist/{_config-DMyolIk9.mjs → _config-riTmrwxX.mjs} +2 -2
- package/dist/_deploy.d.ts +0 -10
- package/dist/_dev-restart.d.ts +59 -0
- package/dist/{_dev-server-B3ivqyEd.mjs → _dev-server-BgXXSijx.mjs} +178 -89
- package/dist/_dev-server.d.ts +17 -0
- package/dist/{_init-5hHkHe-V.mjs → _init-Ca5Eg5pn.mjs} +2 -2
- package/dist/_preflight-BtfaYtbE.mjs +46 -0
- package/dist/_preflight.d.ts +57 -0
- package/dist/_slug-api-DXm7s9X5.mjs +50 -0
- package/dist/_slug-api.d.ts +16 -0
- package/dist/_studio-CXoY_Hsg.mjs +94 -0
- package/dist/_studio.d.ts +19 -16
- package/dist/{_templates-DmdG-voA.mjs → _templates-CKYDgGyY.mjs} +1 -1
- package/dist/{_typecheck-gate-DB-PY0A3.mjs → _typecheck-gate-4v9UBZHh.mjs} +1 -1
- package/dist/{_ui-DfwfDbT-.mjs → _ui-u7T4YooX.mjs} +5 -1
- package/dist/_ui.d.ts +2 -0
- package/dist/{build-CACFbdQ4.mjs → build-BTOyN3H5.mjs} +4 -4
- package/dist/cli.d.ts +2 -1
- package/dist/cli.mjs +69 -33
- package/dist/{delete-DEZ7u3u4.mjs → delete-D3gZdSs-.mjs} +3 -3
- package/dist/{deploy-Ch0d_jje.mjs → deploy-Df6e7YFn.mjs} +21 -21
- package/dist/deploy.d.ts +0 -2
- package/dist/{dev-CdeRcYiQ.mjs → dev-DqIUtFfS.mjs} +2 -2
- package/dist/{eject-DSrAp8PL.mjs → eject-DSldO60O.mjs} +2 -2
- package/dist/{init-BBAoRI_f.mjs → init-B-04lRSV.mjs} +4 -4
- package/dist/{login-BeFUiU6M.mjs → login-CtimAsZk.mjs} +4 -15
- package/dist/login.d.ts +0 -9
- package/dist/project-config.d.ts +23 -0
- package/dist/project-config.mjs +3 -0
- package/dist/scaffold/package.json +3 -3
- package/dist/{secret-4_dYyrpA.mjs → secret-LnFdg8SB.mjs} +7 -7
- package/dist/{storage-BTfErOOW.mjs → storage-Cjj3Pv6i.mjs} +2 -2
- package/dist/{studio-DPDFKZ9i.mjs → studio-Rl5Po2M6.mjs} +10 -177
- package/dist/{test-C-V98oC-.mjs → test-BtOzTcXE.mjs} +1 -1
- package/package.json +8 -3
- package/dist/_slug-api-fRNNR8tz.mjs +0 -27
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as serverOrigin, i as readProjectConfig, n as ensureApiKey, r as readGlobalConfig, t as approveServer } from "./_config-
|
|
2
|
+
import { a as serverOrigin, i as readProjectConfig, n as ensureApiKey, r as readGlobalConfig, t as approveServer } from "./_config-riTmrwxX.mjs";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -107,7 +107,8 @@ async function getServerInfo(cwd, explicitServer) {
|
|
|
107
107
|
return {
|
|
108
108
|
serverUrl,
|
|
109
109
|
slug: config.slug,
|
|
110
|
-
apiKey
|
|
110
|
+
apiKey,
|
|
111
|
+
studioProject: config.studioProject
|
|
111
112
|
};
|
|
112
113
|
}
|
|
113
114
|
//#endregion
|
package/dist/_agent.d.ts
CHANGED
|
@@ -5,16 +5,16 @@ import { buildWorker } from "./worker-bundler.mjs";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
8
|
-
import { hash } from "node:crypto";
|
|
9
8
|
import { tmpdir } from "node:os";
|
|
9
|
+
import { hash } from "node:crypto";
|
|
10
10
|
//#region _bundler.ts
|
|
11
11
|
/**
|
|
12
12
|
* Bundle an agent directory: build agent.ts into worker ESM + client files.
|
|
13
13
|
*
|
|
14
14
|
* agent.ts is the single entry point: `export default agent({...})`. The
|
|
15
15
|
* worker self-describes (it exports `__aaiConfig` — see `worker-bundler.ts`),
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* which is what `evalWorkerConfig` reads back: the platform stores no agent
|
|
17
|
+
* config and evaluates nothing, so every read of one happens here.
|
|
18
18
|
*/
|
|
19
19
|
async function buildAgentBundle(cwd, opts = {}) {
|
|
20
20
|
const [worker, clientFiles] = await Promise.all([buildWorker(cwd, opts), buildClient(cwd)]);
|
|
@@ -42,21 +42,40 @@ async function buildAgentBundle(cwd, opts = {}) {
|
|
|
42
42
|
* experimental.
|
|
43
43
|
*/
|
|
44
44
|
async function evalWorkerBundle(code) {
|
|
45
|
+
const mod = await importWorkerModule(code);
|
|
46
|
+
const agentDef = mod.default ?? mod;
|
|
47
|
+
validateAgentExport(agentDef);
|
|
48
|
+
return agentDef;
|
|
49
|
+
}
|
|
50
|
+
/** Import a built worker from a temp file. See {@link evalWorkerBundle}. */
|
|
51
|
+
async function importWorkerModule(code) {
|
|
45
52
|
const dir = await mkdtemp(path.join(tmpdir(), "aai-worker-"));
|
|
46
53
|
const file = path.join(dir, "worker.mjs");
|
|
47
|
-
let mod;
|
|
48
54
|
try {
|
|
49
55
|
await writeFile(file, code, "utf-8");
|
|
50
|
-
|
|
56
|
+
return await import(pathToFileURL(file).href);
|
|
51
57
|
} finally {
|
|
52
58
|
await rm(dir, {
|
|
53
59
|
recursive: true,
|
|
54
60
|
force: true
|
|
55
61
|
}).catch(() => void 0);
|
|
56
62
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The config a built worker describes about itself — its `__aaiConfig`
|
|
66
|
+
* export, generated by `buildWorker`'s wrapper entry (`toAgentConfig(def)`
|
|
67
|
+
* plus the tool schemas).
|
|
68
|
+
*
|
|
69
|
+
* Importing it is also the deploy's SMOKE TEST: a bundle whose top level
|
|
70
|
+
* throws fails here, in the directory that owns it, rather than as a sandbox
|
|
71
|
+
* that never becomes ready after the upload. The platform performs no such
|
|
72
|
+
* check — it evaluates nothing (see `_preflight.ts`).
|
|
73
|
+
*
|
|
74
|
+
* Returns undefined for a bundle built by a CLI old enough not to emit the
|
|
75
|
+
* export; the caller treats that as "nothing to preflight", never an error.
|
|
76
|
+
*/
|
|
77
|
+
async function evalWorkerConfig(code) {
|
|
78
|
+
return (await importWorkerModule(code)).__aaiConfig;
|
|
60
79
|
}
|
|
61
80
|
/**
|
|
62
81
|
* Memoizing wrapper around `evalWorkerBundle` for long-lived callers (the
|
|
@@ -79,4 +98,4 @@ function createWorkerEvaluator() {
|
|
|
79
98
|
};
|
|
80
99
|
}
|
|
81
100
|
//#endregion
|
|
82
|
-
export { createWorkerEvaluator as n, evalWorkerBundle as r, buildAgentBundle as t };
|
|
101
|
+
export { evalWorkerConfig as i, createWorkerEvaluator as n, evalWorkerBundle as r, buildAgentBundle as t };
|
package/dist/_bundler.d.ts
CHANGED
|
@@ -12,8 +12,8 @@ export type DirectoryBundleOutput = {
|
|
|
12
12
|
*
|
|
13
13
|
* agent.ts is the single entry point: `export default agent({...})`. The
|
|
14
14
|
* worker self-describes (it exports `__aaiConfig` — see `worker-bundler.ts`),
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* which is what `evalWorkerConfig` reads back: the platform stores no agent
|
|
16
|
+
* config and evaluates nothing, so every read of one happens here.
|
|
17
17
|
*/
|
|
18
18
|
export declare function buildAgentBundle(cwd: string, opts?: BuildWorkerOptions): Promise<DirectoryBundleOutput>;
|
|
19
19
|
/**
|
|
@@ -35,6 +35,20 @@ export declare function buildAgentBundle(cwd: string, opts?: BuildWorkerOptions)
|
|
|
35
35
|
* experimental.
|
|
36
36
|
*/
|
|
37
37
|
export declare function evalWorkerBundle(code: string): Promise<AgentDef>;
|
|
38
|
+
/**
|
|
39
|
+
* The config a built worker describes about itself — its `__aaiConfig`
|
|
40
|
+
* export, generated by `buildWorker`'s wrapper entry (`toAgentConfig(def)`
|
|
41
|
+
* plus the tool schemas).
|
|
42
|
+
*
|
|
43
|
+
* Importing it is also the deploy's SMOKE TEST: a bundle whose top level
|
|
44
|
+
* throws fails here, in the directory that owns it, rather than as a sandbox
|
|
45
|
+
* that never becomes ready after the upload. The platform performs no such
|
|
46
|
+
* check — it evaluates nothing (see `_preflight.ts`).
|
|
47
|
+
*
|
|
48
|
+
* Returns undefined for a bundle built by a CLI old enough not to emit the
|
|
49
|
+
* export; the caller treats that as "nothing to preflight", never an error.
|
|
50
|
+
*/
|
|
51
|
+
export declare function evalWorkerConfig(code: string): Promise<unknown>;
|
|
38
52
|
/**
|
|
39
53
|
* Memoizing wrapper around `evalWorkerBundle` for long-lived callers (the
|
|
40
54
|
* dev server): byte-identical worker code returns the previously evaluated
|
|
@@ -4,8 +4,8 @@ import { a as errorMessage, c as readJson, d as writeJson } from "./_utils-8KKw-
|
|
|
4
4
|
import { mkdtempSync } from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import fs from "node:fs/promises";
|
|
7
|
-
import { setTimeout } from "node:timers/promises";
|
|
8
7
|
import { tmpdir } from "node:os";
|
|
8
|
+
import { setTimeout } from "node:timers/promises";
|
|
9
9
|
import envPaths from "env-paths";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
//#region _config.ts
|
|
@@ -239,4 +239,4 @@ async function ensureApiKey(configDir) {
|
|
|
239
239
|
throw new CliError("not_logged_in", "You're not logged in.", "Run `aai login` to link your account. Non-interactive setups can point AAI_CONFIG_DIR at a config dir holding a logged-in key.");
|
|
240
240
|
}
|
|
241
241
|
//#endregion
|
|
242
|
-
export { serverOrigin as a,
|
|
242
|
+
export { serverOrigin as a, writeGlobalConfig as c, readProjectConfig as i, writeProjectConfig as l, ensureApiKey as n, updateGlobalConfig as o, readGlobalConfig as r, updateProjectConfig as s, approveServer as t };
|
package/dist/_deploy.d.ts
CHANGED
|
@@ -7,14 +7,6 @@ export type DeployOpts = {
|
|
|
7
7
|
/** Existing slug for redeployment. Omit for first deploy — server generates one. */
|
|
8
8
|
slug?: string;
|
|
9
9
|
apiKey: string;
|
|
10
|
-
/**
|
|
11
|
-
* Ask the server to WARN (in `warnings`) instead of rejecting when the
|
|
12
|
-
* agent's providers are missing credentials — `aai deploy
|
|
13
|
-
* --allow-missing-secrets`, for setting them post-deploy with
|
|
14
|
-
* `aai secret put` (the studio's publish flow relies on this: its Secrets
|
|
15
|
-
* panel needs a deployed slug to attach secrets to).
|
|
16
|
-
*/
|
|
17
|
-
allowMissingSecrets?: boolean;
|
|
18
10
|
/**
|
|
19
11
|
* Ask the server to permit a `-preview`-suffixed slug (`aai deploy
|
|
20
12
|
* --allow-preview-slug`). That suffix is reserved for the studio's
|
|
@@ -29,7 +21,5 @@ export type DeployOpts = {
|
|
|
29
21
|
};
|
|
30
22
|
export type DeployResult = {
|
|
31
23
|
slug: string;
|
|
32
|
-
/** Server-side deploy warnings (e.g. the missing-credential preflight). */
|
|
33
|
-
warnings?: string[];
|
|
34
24
|
};
|
|
35
25
|
export declare function runDeploy(opts: DeployOpts): Promise<DeployResult>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Restart supervision for `aai dev`.
|
|
3
|
+
*
|
|
4
|
+
* The dev server's subtlest logic is not the wiring (chokidar, Vite, the
|
|
5
|
+
* bundler) but the small state machine that decides WHEN to rebuild and in
|
|
6
|
+
* what order to swap servers: an edit saved mid-boot must be queued rather
|
|
7
|
+
* than race the initial build, a failed build must leave the old server
|
|
8
|
+
* serving, a `listen` that loses the port race must retry, and teardown must
|
|
9
|
+
* be idempotent and win every race against an in-flight rebuild.
|
|
10
|
+
*
|
|
11
|
+
* That machine lives here, behind injected `build`/`listen`/`close`
|
|
12
|
+
* operations, so it can be exercised directly — no file watcher, no bundler,
|
|
13
|
+
* no debounce, no module mocks. `_dev-server.ts` supplies the real operations.
|
|
14
|
+
*
|
|
15
|
+
* The supervised server is opaque (`S`): the supervisor never touches it
|
|
16
|
+
* except through `ops`, so a test can use a plain label.
|
|
17
|
+
*/
|
|
18
|
+
export type NotifyLevel = "error" | "warn" | "info" | "success";
|
|
19
|
+
export type RestartOps<S> = {
|
|
20
|
+
/** Build a replacement server. Rejections leave the current one serving. */
|
|
21
|
+
build(): Promise<S>;
|
|
22
|
+
/** Bind the port. Rejections are retried — see {@link LISTEN_ATTEMPTS}. */
|
|
23
|
+
listen(server: S): Promise<void>;
|
|
24
|
+
/** Release the port and any per-server resources. */
|
|
25
|
+
close(server: S): Promise<void>;
|
|
26
|
+
/** User-facing progress and failures. */
|
|
27
|
+
notify(level: NotifyLevel, message: string): void;
|
|
28
|
+
/**
|
|
29
|
+
* Extra teardown (file watcher, Vite) run once by {@link
|
|
30
|
+
* RestartSupervisor.close}, before the current server closes. Failures are
|
|
31
|
+
* swallowed so one leak can't strand the others.
|
|
32
|
+
*/
|
|
33
|
+
teardown?: () => Promise<void>;
|
|
34
|
+
/** Injectable so retry specs don't sleep real wall-clock. */
|
|
35
|
+
sleep?: (ms: number) => Promise<void>;
|
|
36
|
+
};
|
|
37
|
+
export type RestartSupervisor<S> = {
|
|
38
|
+
/**
|
|
39
|
+
* A change was detected. Queues instead of racing when a restart (or the
|
|
40
|
+
* initial boot) is still in flight.
|
|
41
|
+
*/
|
|
42
|
+
request(): void;
|
|
43
|
+
/**
|
|
44
|
+
* Startup finished with `server`. Releases the queue and runs the one
|
|
45
|
+
* restart an edit saved during boot asked for.
|
|
46
|
+
*/
|
|
47
|
+
adopt(server: S): void;
|
|
48
|
+
/** The server currently serving, or `undefined` before {@link adopt}. */
|
|
49
|
+
current(): S | undefined;
|
|
50
|
+
/** Idempotent teardown. Concurrent callers join the in-flight run. */
|
|
51
|
+
close(): Promise<void>;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Create a {@link RestartSupervisor}. It starts in the "restarting" state:
|
|
55
|
+
* callers install their watcher and call {@link RestartSupervisor.request}
|
|
56
|
+
* freely while the initial build runs, then {@link RestartSupervisor.adopt}
|
|
57
|
+
* the built server (or {@link RestartSupervisor.close} on startup failure).
|
|
58
|
+
*/
|
|
59
|
+
export declare function createRestartSupervisor<S>(ops: RestartOps<S>): RestartSupervisor<S>;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as log, r as notify } from "./_ui-
|
|
2
|
+
import { i as outputSilenced, n as log, r as notify } from "./_ui-u7T4YooX.mjs";
|
|
3
3
|
import { a as errorMessage, r as errorCode } from "./_utils-8KKw-bzi.mjs";
|
|
4
4
|
import { n as fallbackHtmlPlugin } from "./client-bundler-DONm-khu.mjs";
|
|
5
5
|
import { buildWorker } from "./worker-bundler.mjs";
|
|
6
|
-
import { n as
|
|
7
|
-
import { n as
|
|
6
|
+
import { n as ensureApiKey } from "./_config-riTmrwxX.mjs";
|
|
7
|
+
import { n as createWorkerEvaluator } from "./_bundler-BvfPzf8J.mjs";
|
|
8
8
|
import { t as resolveServerEnv } from "./_server-common-DX8Bfrf5.mjs";
|
|
9
9
|
import { existsSync } from "node:fs";
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import { setTimeout } from "node:timers/promises";
|
|
12
|
-
import { createRuntime, createServer, requiredProviderEnvVars, withHostCredentialFallback } from "@alexkroman1/aai/runtime";
|
|
12
|
+
import { consoleLogger, createRuntime, createServer, requiredProviderEnvVars, withHostCredentialFallback } from "@alexkroman1/aai/runtime";
|
|
13
13
|
import { defaultClientDir } from "@alexkroman1/aai-ui/client-dir";
|
|
14
14
|
import { watch } from "chokidar";
|
|
15
15
|
import getPort, { portNumbers } from "get-port";
|
|
@@ -65,6 +65,130 @@ function devWatchEnabled() {
|
|
|
65
65
|
return /^(1|true|yes|on)$/i.test(process.env.AAI_DEV_WATCH?.trim() ?? "");
|
|
66
66
|
}
|
|
67
67
|
//#endregion
|
|
68
|
+
//#region _dev-restart.ts
|
|
69
|
+
/**
|
|
70
|
+
* Restart supervision for `aai dev`.
|
|
71
|
+
*
|
|
72
|
+
* The dev server's subtlest logic is not the wiring (chokidar, Vite, the
|
|
73
|
+
* bundler) but the small state machine that decides WHEN to rebuild and in
|
|
74
|
+
* what order to swap servers: an edit saved mid-boot must be queued rather
|
|
75
|
+
* than race the initial build, a failed build must leave the old server
|
|
76
|
+
* serving, a `listen` that loses the port race must retry, and teardown must
|
|
77
|
+
* be idempotent and win every race against an in-flight rebuild.
|
|
78
|
+
*
|
|
79
|
+
* That machine lives here, behind injected `build`/`listen`/`close`
|
|
80
|
+
* operations, so it can be exercised directly — no file watcher, no bundler,
|
|
81
|
+
* no debounce, no module mocks. `_dev-server.ts` supplies the real operations.
|
|
82
|
+
*
|
|
83
|
+
* The supervised server is opaque (`S`): the supervisor never touches it
|
|
84
|
+
* except through `ops`, so a test can use a plain label.
|
|
85
|
+
*/
|
|
86
|
+
/** Attempts to bind the port during the close→listen swap. */
|
|
87
|
+
const LISTEN_ATTEMPTS = 3;
|
|
88
|
+
/** Backoff between those attempts. */
|
|
89
|
+
const LISTEN_RETRY_DELAY_MS = 250;
|
|
90
|
+
/**
|
|
91
|
+
* Create a {@link RestartSupervisor}. It starts in the "restarting" state:
|
|
92
|
+
* callers install their watcher and call {@link RestartSupervisor.request}
|
|
93
|
+
* freely while the initial build runs, then {@link RestartSupervisor.adopt}
|
|
94
|
+
* the built server (or {@link RestartSupervisor.close} on startup failure).
|
|
95
|
+
*/
|
|
96
|
+
function createRestartSupervisor(ops) {
|
|
97
|
+
const wait = ops.sleep ?? setTimeout;
|
|
98
|
+
let restarting = true;
|
|
99
|
+
let pendingRestart = false;
|
|
100
|
+
let closed = false;
|
|
101
|
+
let current;
|
|
102
|
+
let cleanupPromise;
|
|
103
|
+
function request() {
|
|
104
|
+
if (restarting) {
|
|
105
|
+
pendingRestart = true;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
restarting = true;
|
|
109
|
+
restart().catch((err) => {
|
|
110
|
+
ops.notify("error", `Restart failed: ${errorMessage(err)}`);
|
|
111
|
+
}).finally(() => {
|
|
112
|
+
restarting = false;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
async function restart() {
|
|
116
|
+
do {
|
|
117
|
+
pendingRestart = false;
|
|
118
|
+
await restartOnce();
|
|
119
|
+
} while (pendingRestart && !closed);
|
|
120
|
+
}
|
|
121
|
+
async function restartOnce() {
|
|
122
|
+
let newServer;
|
|
123
|
+
try {
|
|
124
|
+
newServer = await ops.build();
|
|
125
|
+
} catch (err) {
|
|
126
|
+
ops.notify("error", `Restart failed: ${errorMessage(err)} (previous server still running)`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (closed) {
|
|
130
|
+
await closeQuietly(newServer);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (current !== void 0) {
|
|
134
|
+
const old = current;
|
|
135
|
+
current = void 0;
|
|
136
|
+
await closeQuietly(old);
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
await listenWithRetry(newServer);
|
|
140
|
+
} catch (err) {
|
|
141
|
+
ops.notify("error", `Restart failed: ${errorMessage(err)} — dev server is down; save a file to retry.`);
|
|
142
|
+
await closeQuietly(newServer);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
current = newServer;
|
|
146
|
+
if (closed) {
|
|
147
|
+
await closeQuietly(newServer);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
ops.notify("success", "Restarted");
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Listen with a few short-backoff retries. During the close→listen swap the
|
|
154
|
+
* port is momentarily free, so another process can snatch it (or the OS can
|
|
155
|
+
* hold it in TIME_WAIT); one blind attempt would leave the dev server down
|
|
156
|
+
* until the next file change.
|
|
157
|
+
*/
|
|
158
|
+
async function listenWithRetry(server) {
|
|
159
|
+
for (let attempt = 1;; attempt++) try {
|
|
160
|
+
await ops.listen(server);
|
|
161
|
+
return;
|
|
162
|
+
} catch (err) {
|
|
163
|
+
if (attempt >= LISTEN_ATTEMPTS || closed) throw err;
|
|
164
|
+
await wait(LISTEN_RETRY_DELAY_MS);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/** Best-effort close: a synchronous throw is swallowed alongside a rejection. */
|
|
168
|
+
async function closeQuietly(server) {
|
|
169
|
+
try {
|
|
170
|
+
await ops.close(server);
|
|
171
|
+
} catch {}
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
request,
|
|
175
|
+
adopt(server) {
|
|
176
|
+
current = server;
|
|
177
|
+
restarting = false;
|
|
178
|
+
if (pendingRestart) request();
|
|
179
|
+
},
|
|
180
|
+
current: () => current,
|
|
181
|
+
close() {
|
|
182
|
+
cleanupPromise ??= (async () => {
|
|
183
|
+
closed = true;
|
|
184
|
+
await ops.teardown?.().catch(() => void 0);
|
|
185
|
+
if (current !== void 0) await closeQuietly(current);
|
|
186
|
+
})();
|
|
187
|
+
return cleanupPromise;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
//#endregion
|
|
68
192
|
//#region _dev-server.ts
|
|
69
193
|
/**
|
|
70
194
|
* Dev server for directory-based agents.
|
|
@@ -143,6 +267,33 @@ function isIgnoredPath(dir, filePath) {
|
|
|
143
267
|
});
|
|
144
268
|
}
|
|
145
269
|
/**
|
|
270
|
+
* The logger the dev server's runtime writes through.
|
|
271
|
+
*
|
|
272
|
+
* The SDK's default logger is console-backed and `console.log` is STDOUT, so
|
|
273
|
+
* in JSON mode the runtime's own diagnostics — the multi-line "Session mode
|
|
274
|
+
* resolved" dump at startup, every later warning — landed on stdout ahead of
|
|
275
|
+
* the single result line `aai dev` promises there. JSON mode is AUTO-DETECTED
|
|
276
|
+
* on a pipe, so that is the normal case rather than an opt-in one:
|
|
277
|
+
* `aai dev > dev.log`, a process supervisor, a container. It is the same
|
|
278
|
+
* hazard `notify` exists for, one layer down: `silenceOutput()` only reaches
|
|
279
|
+
* this CLI's own `log`, and the runtime is not using it.
|
|
280
|
+
*
|
|
281
|
+
* Human mode keeps the console logger exactly as it was — a TTY has nothing
|
|
282
|
+
* to parse, and stdout is where people are already reading these.
|
|
283
|
+
*/
|
|
284
|
+
function createDevLogger(silenced) {
|
|
285
|
+
if (!silenced) return consoleLogger;
|
|
286
|
+
const write = (msg, ctx) => {
|
|
287
|
+
process.stderr.write(`${msg}${ctx === void 0 ? "" : ` ${JSON.stringify(ctx)}`}\n`);
|
|
288
|
+
};
|
|
289
|
+
return {
|
|
290
|
+
info: write,
|
|
291
|
+
warn: write,
|
|
292
|
+
error: write,
|
|
293
|
+
debug: () => void 0
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
146
297
|
* Watch the agent directory for changes and call `onChange` when detected.
|
|
147
298
|
* Debounces to avoid rapid restarts. Uses chokidar for reliable recursive
|
|
148
299
|
* watching across platforms (raw `fs.watch` misses events on Linux).
|
|
@@ -208,6 +359,7 @@ async function startDevServer(opts) {
|
|
|
208
359
|
const vitePort = port;
|
|
209
360
|
const clientDirOpt = hasClient ? {} : { clientDir: defaultClientDir() };
|
|
210
361
|
const evaluateWorker = createWorkerEvaluator();
|
|
362
|
+
const devLogger = createDevLogger(outputSilenced());
|
|
211
363
|
/** Full build sequence, shared by initial startup and every restart. */
|
|
212
364
|
async function buildServer() {
|
|
213
365
|
const agentDef = await loadAgentDef(cwd, evaluateWorker);
|
|
@@ -217,7 +369,8 @@ async function startDevServer(opts) {
|
|
|
217
369
|
runtime: createRuntime({
|
|
218
370
|
agent: agentDef,
|
|
219
371
|
env,
|
|
220
|
-
providerEnv
|
|
372
|
+
providerEnv,
|
|
373
|
+
logger: devLogger
|
|
221
374
|
}),
|
|
222
375
|
name: agentDef.name,
|
|
223
376
|
env: hostModeEnv(providerEnv),
|
|
@@ -226,27 +379,24 @@ async function startDevServer(opts) {
|
|
|
226
379
|
...clientDirOpt
|
|
227
380
|
});
|
|
228
381
|
}
|
|
229
|
-
let restarting = true;
|
|
230
|
-
let pendingRestart = false;
|
|
231
|
-
let closed = false;
|
|
232
|
-
let currentServer;
|
|
233
|
-
function kickRestart() {
|
|
234
|
-
if (restarting) {
|
|
235
|
-
pendingRestart = true;
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
restarting = true;
|
|
239
|
-
restart().catch((err) => {
|
|
240
|
-
notify("error", `Restart failed: ${errorMessage(err)}`);
|
|
241
|
-
}).finally(() => {
|
|
242
|
-
restarting = false;
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
const watcher = devWatchEnabled() ? watchDirectory(cwd, kickRestart) : void 0;
|
|
246
382
|
let viteServer;
|
|
383
|
+
let watcher;
|
|
384
|
+
const supervisor = createRestartSupervisor({
|
|
385
|
+
build: buildServer,
|
|
386
|
+
listen: (server) => server.listen(backendPort, devBindHost()),
|
|
387
|
+
close: (server) => server.close(),
|
|
388
|
+
notify,
|
|
389
|
+
teardown: async () => {
|
|
390
|
+
await watcher?.close().catch(() => void 0);
|
|
391
|
+
await viteServer?.close().catch(() => void 0);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
watcher = devWatchEnabled() ? watchDirectory(cwd, supervisor.request) : void 0;
|
|
395
|
+
let boundServer;
|
|
247
396
|
try {
|
|
248
|
-
|
|
249
|
-
await
|
|
397
|
+
const initialServer = await buildServer();
|
|
398
|
+
await initialServer.listen(backendPort, devBindHost());
|
|
399
|
+
boundServer = initialServer;
|
|
250
400
|
if (hasClient) {
|
|
251
401
|
const { createServer: createViteServer } = await import("vite");
|
|
252
402
|
viteServer = await createViteServer(viteDevConfig(cwd, vitePort, backendPort));
|
|
@@ -255,74 +405,13 @@ async function startDevServer(opts) {
|
|
|
255
405
|
notify("error", `Vite dev server error: ${errorMessage(err)}`);
|
|
256
406
|
});
|
|
257
407
|
}
|
|
408
|
+
supervisor.adopt(initialServer);
|
|
258
409
|
} catch (err) {
|
|
259
|
-
await
|
|
260
|
-
await
|
|
410
|
+
await supervisor.close();
|
|
411
|
+
await boundServer?.close().catch(() => void 0);
|
|
261
412
|
throw err;
|
|
262
413
|
}
|
|
263
|
-
|
|
264
|
-
if (pendingRestart) kickRestart();
|
|
265
|
-
async function restart() {
|
|
266
|
-
do {
|
|
267
|
-
pendingRestart = false;
|
|
268
|
-
await restartOnce();
|
|
269
|
-
} while (pendingRestart && !closed);
|
|
270
|
-
}
|
|
271
|
-
async function restartOnce() {
|
|
272
|
-
let newServer;
|
|
273
|
-
try {
|
|
274
|
-
newServer = await buildServer();
|
|
275
|
-
} catch (err) {
|
|
276
|
-
notify("error", `Restart failed: ${errorMessage(err)} (previous server still running)`);
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
if (closed) {
|
|
280
|
-
await newServer.close().catch(() => void 0);
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
try {
|
|
284
|
-
await currentServer.close();
|
|
285
|
-
} catch {}
|
|
286
|
-
try {
|
|
287
|
-
await listenWithRetry(newServer);
|
|
288
|
-
currentServer = newServer;
|
|
289
|
-
if (closed) {
|
|
290
|
-
await newServer.close().catch(() => void 0);
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
notify("success", "Restarted");
|
|
294
|
-
} catch (err) {
|
|
295
|
-
notify("error", `Restart failed: ${errorMessage(err)} — dev server is down; save a file to retry.`);
|
|
296
|
-
await newServer.close().catch(() => void 0);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Listen with a few short-backoff retries. During the close→listen swap the
|
|
301
|
-
* port is momentarily free, so another process can snatch it (or the OS can
|
|
302
|
-
* hold it in TIME_WAIT); one blind attempt would leave the dev server down
|
|
303
|
-
* until the next file change.
|
|
304
|
-
*/
|
|
305
|
-
async function listenWithRetry(server) {
|
|
306
|
-
const LISTEN_ATTEMPTS = 3;
|
|
307
|
-
const LISTEN_RETRY_DELAY_MS = 250;
|
|
308
|
-
for (let attempt = 1;; attempt++) try {
|
|
309
|
-
await server.listen(backendPort, devBindHost());
|
|
310
|
-
return;
|
|
311
|
-
} catch (err) {
|
|
312
|
-
if (attempt >= LISTEN_ATTEMPTS || closed) throw err;
|
|
313
|
-
await setTimeout(LISTEN_RETRY_DELAY_MS);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
let cleanupPromise;
|
|
317
|
-
return () => {
|
|
318
|
-
cleanupPromise ??= (async () => {
|
|
319
|
-
closed = true;
|
|
320
|
-
await watcher?.close().catch(() => void 0);
|
|
321
|
-
await viteServer?.close().catch(() => void 0);
|
|
322
|
-
await currentServer.close().catch(() => void 0);
|
|
323
|
-
})();
|
|
324
|
-
return cleanupPromise;
|
|
325
|
-
};
|
|
414
|
+
return supervisor.close;
|
|
326
415
|
}
|
|
327
416
|
//#endregion
|
|
328
417
|
export { startDevServer };
|
package/dist/_dev-server.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* client SPA HMR.
|
|
8
8
|
*/
|
|
9
9
|
import type { AgentDef } from "@alexkroman1/aai";
|
|
10
|
+
import { type Logger } from "@alexkroman1/aai/runtime";
|
|
10
11
|
import { type FSWatcher } from "chokidar";
|
|
11
12
|
/**
|
|
12
13
|
* Warnings about the agent's credentials, computed against the `.env`-derived
|
|
@@ -46,6 +47,22 @@ export declare function loadAgentDef(cwd: string, evaluate: (code: string) => Pr
|
|
|
46
47
|
* restart the server with the new values.
|
|
47
48
|
*/
|
|
48
49
|
export declare function isIgnoredPath(dir: string, filePath: string): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* The logger the dev server's runtime writes through.
|
|
52
|
+
*
|
|
53
|
+
* The SDK's default logger is console-backed and `console.log` is STDOUT, so
|
|
54
|
+
* in JSON mode the runtime's own diagnostics — the multi-line "Session mode
|
|
55
|
+
* resolved" dump at startup, every later warning — landed on stdout ahead of
|
|
56
|
+
* the single result line `aai dev` promises there. JSON mode is AUTO-DETECTED
|
|
57
|
+
* on a pipe, so that is the normal case rather than an opt-in one:
|
|
58
|
+
* `aai dev > dev.log`, a process supervisor, a container. It is the same
|
|
59
|
+
* hazard `notify` exists for, one layer down: `silenceOutput()` only reaches
|
|
60
|
+
* this CLI's own `log`, and the runtime is not using it.
|
|
61
|
+
*
|
|
62
|
+
* Human mode keeps the console logger exactly as it was — a TTY has nothing
|
|
63
|
+
* to parse, and stdout is where people are already reading these.
|
|
64
|
+
*/
|
|
65
|
+
export declare function createDevLogger(silenced: boolean): Logger;
|
|
49
66
|
/**
|
|
50
67
|
* Watch the agent directory for changes and call `onChange` when detected.
|
|
51
68
|
* Debounces to avoid rapid restarts. Uses chokidar for reliable recursive
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { c as readJson, d as writeJson, s as isEexist } from "./_utils-8KKw-bzi.mjs";
|
|
3
|
-
import { r as isDevMode, t as getMonorepoRoot } from "./_agent-
|
|
4
|
-
import { REPO_URL, downloadAndMergeTemplate } from "./_templates-
|
|
3
|
+
import { r as isDevMode, t as getMonorepoRoot } from "./_agent-JGaAJ5j6.mjs";
|
|
4
|
+
import { REPO_URL, downloadAndMergeTemplate } from "./_templates-CKYDgGyY.mjs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import fs from "node:fs/promises";
|
|
7
7
|
//#region _init.ts
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { requiredProviderEnvVars } from "@alexkroman1/aai/runtime";
|
|
3
|
+
//#region _preflight.ts
|
|
4
|
+
/**
|
|
5
|
+
* The deploy-time credential preflight — the classic dev/prod failure, caught
|
|
6
|
+
* before the upload instead of at the deployed agent's first session.
|
|
7
|
+
*
|
|
8
|
+
* An agent that ran locally on shell-exported provider keys dies after deploy
|
|
9
|
+
* with what looks like a provider outage: `aai dev` falls back to the shell
|
|
10
|
+
* (`withHostCredentialFallback`), and the platform never will. Naming the key
|
|
11
|
+
* here turns that into one line of output while the developer is still in the
|
|
12
|
+
* directory that owns the `.env`.
|
|
13
|
+
*
|
|
14
|
+
* **This lives in the CLI because the agent's config does.** The platform
|
|
15
|
+
* stores no description of a bundle and never evaluates one (see "The
|
|
16
|
+
* platform stores no agent config" in packages/aai-server/CLAUDE.md), so the
|
|
17
|
+
* only place that knows an agent needs `CARTESIA_API_KEY` is the process that
|
|
18
|
+
* just built it.
|
|
19
|
+
*
|
|
20
|
+
* **It WARNS, and must not be turned into a hard failure.** The CLI sees the
|
|
21
|
+
* env it is about to upload; it cannot see what is already stored against the
|
|
22
|
+
* slug from an earlier `aai secret put`, so a key the platform holds looks
|
|
23
|
+
* missing from here. The server-side check this replaced could see both and
|
|
24
|
+
* so could reject — moving the config to where it is authored costs that
|
|
25
|
+
* accuracy, and a false rejection is worse than a false warning: it blocks a
|
|
26
|
+
* deploy that would have worked.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Env var names the agent needs that the env being uploaded doesn't supply.
|
|
30
|
+
* Empty values count as missing — an empty credential authenticates nothing.
|
|
31
|
+
*
|
|
32
|
+
* Two sources: provider credentials derived from the stt/llm/tts/s2s
|
|
33
|
+
* descriptors (the same registry-backed derivation the runtime resolves keys
|
|
34
|
+
* with), and the agent's own declared `requiredEnv` — an `agent()` field for
|
|
35
|
+
* custom keys tools read from `ctx.env`, which no static derivation can see.
|
|
36
|
+
*/
|
|
37
|
+
function missingCredentials(config, env) {
|
|
38
|
+
return [.../* @__PURE__ */ new Set([...requiredProviderEnvVars(config), ...config.requiredEnv ?? []])].filter((name) => !env[name]);
|
|
39
|
+
}
|
|
40
|
+
/** One line naming the missing keys and what to do about them. */
|
|
41
|
+
function missingCredentialMessage(missing) {
|
|
42
|
+
const plural = missing.length > 1;
|
|
43
|
+
return `Missing credential${plural ? "s" : ""} the agent needs to start: ${missing.join(", ")}. Declare ${plural ? "them" : "it"} in .env and redeploy (already set on the platform with \`aai secret put\`? then this is already handled).`;
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { missingCredentialMessage, missingCredentials };
|