@alexkroman1/aai-cli 9.1.0 → 10.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/{_bundler-CDuYl5Gb.mjs → _bundler-B31MqYaD.mjs} +7 -33
- package/dist/_bundler.d.ts +3 -41
- package/dist/{_dev-server-BAR8Ya43.mjs → _dev-server-Bo5dSBzQ.mjs} +140 -44
- package/dist/_dev-server.d.ts +1 -8
- package/dist/_dev-vite-config.d.ts +21 -0
- package/dist/{_init-BNotRgV1.mjs → _init-BURbdHNM.mjs} +1 -1
- package/dist/{_slug-api--ZBmIGH-.mjs → _slug-api-CfGQq1Tt.mjs} +1 -1
- package/dist/{_templates-4WcKOjS5.mjs → _templates-0Zr2z6yA.mjs} +37 -2
- package/dist/_templates.d.ts +29 -0
- package/dist/{build-DvDdSwAS.mjs → build-DKC2e9o_.mjs} +1 -2
- package/dist/cli.mjs +46 -22
- package/dist/{delete-8pjH8n8n.mjs → delete-X_CRgCf6.mjs} +1 -1
- package/dist/{deploy-CjU3e-3P.mjs → deploy-CchW5pmw.mjs} +2 -2
- package/dist/{dev-dFzdvwa7.mjs → dev-BfVl1uBA.mjs} +1 -1
- package/dist/{init-DK7GpIF9.mjs → init-BRfoc2EK.mjs} +2 -2
- package/dist/{logs-D2Yhqe2w.mjs → logs-hWnJ66Bl.mjs} +2 -2
- package/dist/scaffold/CLAUDE.md +162 -54
- package/dist/scaffold/package.json +4 -6
- package/dist/scaffold/server.mjs +12 -23
- package/dist/{secret-CwgDLpSy.mjs → secret-B-3T5tvr.mjs} +2 -2
- package/dist/{studio-D3-HWIfk.mjs → studio-wVWNLREn.mjs} +4 -3
- package/dist/templates/call-audit/agent.eval.test.ts +1 -1
- package/dist/templates/call-audit/agent.test.ts +80 -8
- package/dist/templates/call-audit/client.tsx +87 -3
- package/dist/templates/call-audit/workflows/audit.ts +52 -32
- package/dist/templates/call-audit/workflows/ingest.ts +15 -15
- package/dist/templates/call-audit/workflows/media.ts +1 -1
- package/dist/templates/call-audit/workflows/summarize.ts +0 -4
- package/dist/templates/link-digest/agent.eval.test.ts +4 -2
- package/dist/templates/link-digest/agent.test.ts +25 -5
- package/dist/templates/link-digest/client.tsx +65 -7
- package/dist/templates/link-digest/workflows/digest.ts +39 -29
- package/dist/templates/podcast-digest/agent.eval.test.ts +5 -5
- package/dist/templates/podcast-digest/agent.test.ts +108 -2
- package/dist/templates/podcast-digest/client.tsx +89 -8
- package/dist/templates/podcast-digest/workflows/digest.ts +67 -48
- package/dist/templates/podcast-digest/workflows/feeds.ts +1 -4
- package/dist/templates/podcast-digest/workflows/slack.ts +5 -6
- package/dist/templates/recap-workflow/agent.eval.test.ts +72 -8
- package/dist/templates/recap-workflow/agent.test.ts +323 -83
- package/dist/templates/recap-workflow/shared.ts +4 -3
- package/dist/templates/recap-workflow/workflows/recap.ts +322 -86
- package/dist/templates/recap-workflow/workflows/tokens.ts +21 -0
- package/dist/templates/redline/agent.eval.test.ts +1 -1
- package/dist/templates/redline/agent.test.ts +1 -1
- package/dist/templates/redline/client.tsx +83 -3
- package/dist/templates/redline/workflows/redline.ts +26 -23
- package/dist/templates/research-workflow/agent.eval.test.ts +9 -8
- package/dist/templates/research-workflow/agent.test.ts +32 -6
- package/dist/templates/research-workflow/agent.ts +4 -4
- package/dist/templates/research-workflow/shared.ts +4 -3
- package/dist/templates/research-workflow/workflows/prompts.ts +1 -1
- package/dist/templates/research-workflow/workflows/research.ts +35 -38
- package/dist/templates/spoken-summary/agent.eval.test.ts +3 -3
- package/dist/templates/spoken-summary/agent.test.ts +10 -8
- package/dist/templates/spoken-summary/client.tsx +72 -3
- package/dist/templates/spoken-summary/workflows/summarize.ts +25 -21
- package/dist/templates/spoken-summary/workflows/transcribe.ts +15 -18
- package/dist/templates/transcription-workflow/agent.eval.test.ts +1 -1
- package/dist/templates/transcription-workflow/agent.test.ts +720 -16
- package/dist/templates/transcription-workflow/agent.ts +6 -4
- package/dist/templates/transcription-workflow/client.tsx +106 -308
- package/dist/templates/transcription-workflow/recover.test.ts +141 -0
- package/dist/templates/transcription-workflow/recover.ts +130 -0
- package/dist/templates/transcription-workflow/run-panel.tsx +203 -0
- package/dist/templates/transcription-workflow/total-latency.tsx +144 -0
- package/dist/templates/transcription-workflow/workflows/batch.ts +21 -24
- package/dist/templates/transcription-workflow/workflows/downsample.ts +273 -0
- package/dist/templates/transcription-workflow/workflows/normalize.ts +17 -40
- package/dist/templates/transcription-workflow/workflows/stream.ts +311 -38
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +69 -32
- package/dist/templates/transcription-workflow/workflows/wav.ts +15 -4
- package/dist/worker-bundler.d.ts +0 -14
- package/dist/worker-bundler.mjs +149 -1
- package/dist/{workflow-BflATMmU.mjs → workflow-BBu5oEDw.mjs} +84 -10
- package/dist/workflow.d.ts +1 -0
- package/package.json +4 -6
- package/dist/_workflow-scan.d.ts +0 -108
- package/dist/worker-bundler-CCVEDjm1.mjs +0 -650
- package/dist/workflow-bundler.d.ts +0 -138
- package/dist/{_studio-CKrsixd0.mjs → _studio-B1waXMuR.mjs} +1 -1
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* The pure half of the transcription desk: reading a WAV header, deciding where
|
|
4
4
|
* to cut, and writing a header back over each cut.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Nothing in this file is a step or a body, and that is legal — a body is
|
|
7
|
+
* whatever is handed a `ctx`, so an ordinary module can sit beside one. It is a
|
|
8
|
+
* separate
|
|
9
9
|
* file because everything here is a pure function of a journaled value, which is
|
|
10
10
|
* what makes it testable without a run at all: `transcribe.ts`'s spec drives
|
|
11
11
|
* these directly.
|
|
@@ -43,7 +43,18 @@
|
|
|
43
43
|
* so they keep naming the `ffmpeg` line that fixes the file.
|
|
44
44
|
*/
|
|
45
45
|
|
|
46
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* What the sync endpoint will accept in one request.
|
|
48
|
+
*
|
|
49
|
+
* **Measured against the live endpoint, the binding limit is a 30-second WALL
|
|
50
|
+
* CLOCK budget rather than this**, and that budget covers the upload as well as
|
|
51
|
+
* the transcription: a 92-second segment that is 17.66 MB comes back
|
|
52
|
+
* `504 — request exceeded 30.0s` well inside the documented cap. The constant is
|
|
53
|
+
* left alone because it is what the service DOCUMENTS and the discrepancy is
|
|
54
|
+
* unconfirmed; what closes the gap today is `downsample.ts`, which makes each
|
|
55
|
+
* request six times lighter so the deadline stops binding. Revisit both together
|
|
56
|
+
* once the real cap is settled — this number is what `planSegments` cuts to.
|
|
57
|
+
*/
|
|
47
58
|
export const MAX_SEGMENT_SECONDS = 120;
|
|
48
59
|
|
|
49
60
|
/**
|
package/dist/worker-bundler.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type PluginOption } from "vite";
|
|
2
|
-
import { type WorkflowBundleOutput } from "./workflow-bundler.ts";
|
|
3
2
|
/**
|
|
4
3
|
* Options for worker bundling.
|
|
5
4
|
*
|
|
@@ -25,19 +24,6 @@ export type BuildWorkerOptions = {
|
|
|
25
24
|
* rebuild would turn the watch loop from sub-second into multi-second.
|
|
26
25
|
*/
|
|
27
26
|
runtime?: boolean;
|
|
28
|
-
/**
|
|
29
|
-
* The project's compiled workflows, when it declares any.
|
|
30
|
-
*
|
|
31
|
-
* Embedded in the worker as two string exports rather than shipped as extra
|
|
32
|
-
* files, because the guest's `bundle/load` contract is ONE ESM string. See
|
|
33
|
-
* `wrapperEntrySource`.
|
|
34
|
-
*
|
|
35
|
-
* It also switches on the client transform (`workflowClientPlugin`), which is
|
|
36
|
-
* what puts a `workflowId` on the agent's own copy of each body. Passing the
|
|
37
|
-
* strings without it produces a bundle that serves every workflow route and
|
|
38
|
-
* cannot start a run.
|
|
39
|
-
*/
|
|
40
|
-
workflows?: WorkflowBundleOutput | undefined;
|
|
41
27
|
};
|
|
42
28
|
/**
|
|
43
29
|
* Bundle agent.ts into a single ESM string for the sandbox worker.
|
package/dist/worker-bundler.mjs
CHANGED
|
@@ -1,3 +1,151 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { i as errorCode } from "./_utils-B8QmtFhK.mjs";
|
|
3
|
+
import { n as withPreservedNodeEnv } from "./_vite-env-BNveawd1.mjs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import { invariant } from "@alexkroman1/aai/internal";
|
|
7
|
+
import { build } from "vite";
|
|
8
|
+
//#region worker-bundler.ts
|
|
9
|
+
/**
|
|
10
|
+
* Generated wrapper entry, written under `.aai/` for the duration of the
|
|
11
|
+
* build (the CLI's own scratch dir — dot-paths are ignored by the dev
|
|
12
|
+
* watcher, and the studio's workspace materialization never writes there).
|
|
13
|
+
*/
|
|
14
|
+
const WRAPPER_ENTRY_REL = path.join(".aai", "worker-entry.ts");
|
|
15
|
+
/** Extensions a tool module may be authored in (mirrors the SDK's registry). */
|
|
16
|
+
const TOOL_MODULE_EXT_RE = /\.(?:m?ts|tsx)$/;
|
|
17
|
+
/** A co-located spec is not a tool. */
|
|
18
|
+
const TOOL_SPEC_RE = /\.(?:test|spec)\.[^.]+$/;
|
|
19
|
+
/**
|
|
20
|
+
* The project's `tools/` directory, as file names relative to it.
|
|
21
|
+
*
|
|
22
|
+
* **This is the whole of "discovery", and it happens HERE because the guest has
|
|
23
|
+
* no filesystem.** A sandbox is handed one ESM string, so the only place a
|
|
24
|
+
* directory can be turned into modules is where the bundle is assembled — the
|
|
25
|
+
* same lowering eve does (`readdir` → static import list, which the bundler then
|
|
26
|
+
* follows). The names are validated by `toolRegistry` at bundle-evaluation time
|
|
27
|
+
* rather than here, so one implementation owns the rules.
|
|
28
|
+
*
|
|
29
|
+
* Sorted, so the emitted entry is byte-stable for a given directory: an entry
|
|
30
|
+
* that reordered per readdir would change the bundle hash for no reason.
|
|
31
|
+
*/
|
|
32
|
+
async function discoverToolFiles(cwd) {
|
|
33
|
+
const root = path.join(cwd, "tools");
|
|
34
|
+
let entries;
|
|
35
|
+
try {
|
|
36
|
+
entries = await fs.readdir(root, {
|
|
37
|
+
withFileTypes: true,
|
|
38
|
+
recursive: true
|
|
39
|
+
});
|
|
40
|
+
} catch (err) {
|
|
41
|
+
if (errorCode(err) === "ENOENT") return [];
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
return entries.filter((e) => e.isFile() && TOOL_MODULE_EXT_RE.test(e.name) && !TOOL_SPEC_RE.test(e.name)).map((e) => path.relative(root, path.join(e.parentPath, e.name)).split(path.sep).join("/")).sort();
|
|
45
|
+
}
|
|
46
|
+
/** The prose slot: one file, beside `agent.ts`, named by convention. */
|
|
47
|
+
const SYSTEM_PROMPT_FILE = "system-prompt.md";
|
|
48
|
+
/**
|
|
49
|
+
* Whether the project keeps its system prompt in a file.
|
|
50
|
+
*
|
|
51
|
+
* The other half of "a file beside `agent.ts` can BE the thing", and it happens
|
|
52
|
+
* here for the same reason `discoverToolFiles` does — the guest has no
|
|
53
|
+
* filesystem, so the read belongs where the bundle is assembled. What the file
|
|
54
|
+
* MEANS is `withSystemPrompt`'s to decide (the author may have imported and
|
|
55
|
+
* composed it, in which case discovery must not apply it twice); this only
|
|
56
|
+
* answers whether there is one.
|
|
57
|
+
*/
|
|
58
|
+
async function hasSystemPromptFile(cwd) {
|
|
59
|
+
const nested = path.join(cwd, "system-prompt");
|
|
60
|
+
if ((await fs.stat(nested).catch(() => void 0))?.isDirectory() === true) throw new Error(`${nested} is a directory. A system prompt is ONE file — rename it to ${SYSTEM_PROMPT_FILE}, or import the pieces yourself and compose them into \`systemPrompt\`. There is deliberately no concatenation order for a directory.`);
|
|
61
|
+
try {
|
|
62
|
+
return (await fs.stat(path.join(cwd, SYSTEM_PROMPT_FILE))).isFile();
|
|
63
|
+
} catch (err) {
|
|
64
|
+
if (errorCode(err) === "ENOENT") return false;
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function wrapperEntrySource(runtime, toolFiles, systemPromptFile) {
|
|
69
|
+
const toolImports = toolFiles.map((file, i) => `import * as __aaiTool${i} from "../tools/${file}";`).join("\n");
|
|
70
|
+
const toolEntries = toolFiles.map((file, i) => ` ${JSON.stringify(`tools/${file}`)}: __aaiTool${i},`).join("\n");
|
|
71
|
+
return `import def from "../agent.ts";
|
|
72
|
+
import { agentToolsToSchemas, toAgentConfig, toolRegistry, withSystemPrompt, withTools } from "@alexkroman1/aai/manifest";
|
|
73
|
+
${runtime ? `import { createRuntime } from "@alexkroman1/aai-runtime";` : ""}
|
|
74
|
+
${systemPromptFile ? `import __aaiSystemPrompt from "../${SYSTEM_PROMPT_FILE}?raw";` : ""}
|
|
75
|
+
${toolImports}
|
|
76
|
+
// A tool's name is its file name. The map is built here rather than written in
|
|
77
|
+
// agent.ts, so a file that exists is a tool the model can call — there is no
|
|
78
|
+
// registration step to forget.
|
|
79
|
+
//
|
|
80
|
+
// \`system-prompt.md\` arrives the same way, and the \`?raw\` lives HERE rather
|
|
81
|
+
// than in the author's own \`agent.ts\`: it is a Vite convention, and the whole
|
|
82
|
+
// point of generating this entry is that a bundler feature never has to appear
|
|
83
|
+
// in user-authored space.
|
|
84
|
+
const __aaiAgent = ${systemPromptFile ? "withSystemPrompt(" : ""}withTools(
|
|
85
|
+
def,
|
|
86
|
+
toolRegistry({
|
|
87
|
+
${toolEntries}
|
|
88
|
+
}),
|
|
89
|
+
)${systemPromptFile ? ", __aaiSystemPrompt)" : ""};
|
|
90
|
+
export default __aaiAgent;
|
|
91
|
+
export const __aaiConfig = {
|
|
92
|
+
...toAgentConfig(__aaiAgent),
|
|
93
|
+
toolSchemas: agentToolsToSchemas(__aaiAgent.tools ?? {}),
|
|
94
|
+
};
|
|
95
|
+
${runtime ? `export const __aaiCreateRuntime = (opts: Record<string, unknown>) =>
|
|
96
|
+
createRuntime({ ...opts, agent: __aaiAgent });
|
|
97
|
+
` : ""}`;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Bundle agent.ts into a single ESM string for the sandbox worker.
|
|
101
|
+
*
|
|
102
|
+
* Zod is bundled in — zod 4's `Function()` usage is wrapped in try/catch
|
|
103
|
+
* and gracefully degrades in restricted environments like Deno.
|
|
104
|
+
*
|
|
105
|
+
* @internal — build hook for aai-server/the studio; not a supported public
|
|
106
|
+
* API and not covered by semver.
|
|
107
|
+
*/
|
|
108
|
+
async function buildWorker(cwd, opts = {}) {
|
|
109
|
+
const wrapperPath = path.join(cwd, WRAPPER_ENTRY_REL);
|
|
110
|
+
const [, toolFiles, systemPromptFile] = await Promise.all([
|
|
111
|
+
fs.mkdir(path.dirname(wrapperPath), { recursive: true }),
|
|
112
|
+
discoverToolFiles(cwd),
|
|
113
|
+
hasSystemPromptFile(cwd)
|
|
114
|
+
]);
|
|
115
|
+
await fs.writeFile(wrapperPath, wrapperEntrySource(opts.runtime !== false, toolFiles, systemPromptFile), "utf-8");
|
|
116
|
+
const plugins = opts.plugins ?? [];
|
|
117
|
+
let result;
|
|
118
|
+
try {
|
|
119
|
+
result = await withPreservedNodeEnv(() => build({
|
|
120
|
+
root: cwd,
|
|
121
|
+
logLevel: "silent",
|
|
122
|
+
...opts.configFile === false && { configFile: false },
|
|
123
|
+
...plugins.length > 0 && { plugins },
|
|
124
|
+
ssr: { noExternal: true },
|
|
125
|
+
build: {
|
|
126
|
+
ssr: true,
|
|
127
|
+
lib: {
|
|
128
|
+
entry: wrapperPath,
|
|
129
|
+
formats: ["es"],
|
|
130
|
+
fileName: "worker"
|
|
131
|
+
},
|
|
132
|
+
target: "node20",
|
|
133
|
+
minify: opts.minify ? "oxc" : false,
|
|
134
|
+
write: false,
|
|
135
|
+
rollupOptions: { output: {
|
|
136
|
+
entryFileNames: "[name].js",
|
|
137
|
+
codeSplitting: false
|
|
138
|
+
} }
|
|
139
|
+
}
|
|
140
|
+
}));
|
|
141
|
+
} finally {
|
|
142
|
+
await fs.rm(wrapperPath, { force: true }).catch(() => void 0);
|
|
143
|
+
}
|
|
144
|
+
const output = Array.isArray(result) ? result[0] : result;
|
|
145
|
+
invariant(output !== void 0, "bundle.worker.output");
|
|
146
|
+
const chunk = output.output.find((o) => o.type === "chunk" && o.isEntry);
|
|
147
|
+
invariant(chunk !== void 0, "bundle.worker.entry-chunk", () => ({ kinds: output.output.map((o) => o.type) }));
|
|
148
|
+
return chunk.code;
|
|
149
|
+
}
|
|
150
|
+
//#endregion
|
|
3
151
|
export { buildWorker };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as ok, n as fail } from "./_output-CKkmWs7i.mjs";
|
|
2
|
+
import { a as ok, n as fail, t as CliError } from "./_output-CKkmWs7i.mjs";
|
|
3
3
|
import { n as log } from "./_ui-u7T4YooX.mjs";
|
|
4
|
+
import { i as readProjectConfig } from "./_config-CmJOFsAP.mjs";
|
|
4
5
|
import { n as getServerInfo } from "./_agent-BTwZJu4P.mjs";
|
|
5
6
|
import { errorMessage, omitUndefined } from "@alexkroman1/aai/utils";
|
|
6
7
|
import { createWorkflowApiClient } from "@alexkroman1/aai/workflow-api";
|
|
@@ -35,32 +36,105 @@ import { createWorkflowApiClient } from "@alexkroman1/aai/workflow-api";
|
|
|
35
36
|
/** Runs listed when the caller names no limit — a terminal is not a dashboard. */
|
|
36
37
|
const DEFAULT_RUN_LIMIT = 20;
|
|
37
38
|
/**
|
|
39
|
+
* The base URL {@link createWorkflowApiClient} is given for `--agent`.
|
|
40
|
+
*
|
|
41
|
+
* A dev server has no slug: `createServer` mounts the workflow API at
|
|
42
|
+
* `WORKFLOW_API_PREFIX` on the ORIGIN, where the platform serves it under
|
|
43
|
+
* `/:slug`. So targeting one is not a matter of a different origin — it is the
|
|
44
|
+
* absence of the path segment, which nothing in this command could express.
|
|
45
|
+
*
|
|
46
|
+
* Validated rather than interpolated. This value is joined into a request path,
|
|
47
|
+
* and the CLI's standing rule is that anything reaching a URL is checked at the
|
|
48
|
+
* one point it becomes a target (`resolveDeployTarget`'s slug guard is the same
|
|
49
|
+
* rule). It is also the only origin here that `resolveServerUrl` does not
|
|
50
|
+
* produce, so stripping the trailing slash is this function's job — the join
|
|
51
|
+
* site deliberately carries no copy of that.
|
|
52
|
+
*
|
|
53
|
+
* No trust check, deliberately, and the asymmetry with `--server` is the point:
|
|
54
|
+
* `--server` pairs an origin with the user's PLATFORM API KEY, which is why a
|
|
55
|
+
* repo-supplied loopback URL is refused there. Nothing credentialed goes to
|
|
56
|
+
* `--agent` — the workflow API takes the agent's own bearer or none — so there
|
|
57
|
+
* is nothing for a hostile origin to collect, and requiring `aai login` to read
|
|
58
|
+
* runs off a server on your own laptop is the defect this flag exists for.
|
|
59
|
+
*/
|
|
60
|
+
function agentBaseUrl(raw) {
|
|
61
|
+
const url = URL.parse(raw);
|
|
62
|
+
if (url === null) throw new CliError("bad_agent_url", `--agent is not a URL: ${JSON.stringify(raw)}`, HINT_URL);
|
|
63
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") throw new CliError("bad_agent_url", `--agent must be http or https, not ${url.protocol.replace(":", "")}`, HINT_URL);
|
|
64
|
+
return raw.replace(/\/+$/, "");
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
38
67
|
* A client for the agent's workflow API, plus the bearer when one was given.
|
|
39
68
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
69
|
+
* Two ways in. `--agent <url>` names a server the caller is running themselves
|
|
70
|
+
* — `aai dev`, or the scaffold's `npm start` — and takes that URL as the base:
|
|
71
|
+
* no project config, no published slug, and no `ensureApiKey`, none of which
|
|
72
|
+
* such a server has or wants. Otherwise `getServerInfo` turns "this directory"
|
|
73
|
+
* into a platform origin and a published slug.
|
|
74
|
+
*
|
|
75
|
+
* The client is handed the AGENT's base URL and appends the route prefix itself,
|
|
76
|
+
* so the `/workflows` literal is not spelled here — it is the same constant the
|
|
77
|
+
* server matches on.
|
|
45
78
|
*
|
|
46
79
|
* `serverUrl` is joined as-is: `resolveServerUrl` is the single producer of
|
|
47
|
-
* every origin that reaches
|
|
80
|
+
* every origin that reaches THAT branch and strips trailing slashes once, at
|
|
48
81
|
* resolution time, precisely so join sites do not each carry a copy. The copy
|
|
49
82
|
* this used to hold also DISAGREED with it — `/\/$/` takes one slash where the
|
|
50
83
|
* upstream `/\/+$/` takes all — so the two would have differed on the only
|
|
51
84
|
* input either was written for.
|
|
85
|
+
*
|
|
86
|
+
* **The no-deployment failure is raised HERE rather than by
|
|
87
|
+
* `requireDeployedSlug`, and the ordering is why.** `getServerInfo` resolves the
|
|
88
|
+
* API key BEFORE it looks for a slug, so in an undeployed project the first
|
|
89
|
+
* thing a developer running `aai dev` saw was `not_logged_in` — pointing at
|
|
90
|
+
* `aai login` for a command that never sends the key it was asking for, with
|
|
91
|
+
* the real cause two errors away. Reading the project config first puts the
|
|
92
|
+
* cause first and lets the sentence name every way out: publish it, or point at
|
|
93
|
+
* the server already running.
|
|
52
94
|
*/
|
|
53
95
|
async function target(cwd, opts) {
|
|
96
|
+
if (opts.agent !== void 0) {
|
|
97
|
+
const baseUrl = agentBaseUrl(opts.agent);
|
|
98
|
+
return {
|
|
99
|
+
api: createWorkflowApiClient({
|
|
100
|
+
baseUrl,
|
|
101
|
+
...omitUndefined({ token: opts.token })
|
|
102
|
+
}),
|
|
103
|
+
name: baseUrl
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (!(await readProjectConfig(cwd))?.slug) throw new CliError("no_deployment", NO_DEPLOYMENT, HINT_AGENT);
|
|
54
107
|
const { serverUrl, slug } = await getServerInfo(cwd, opts.server);
|
|
55
108
|
return {
|
|
56
109
|
api: createWorkflowApiClient({
|
|
57
110
|
baseUrl: `${serverUrl}/${slug}`,
|
|
58
111
|
...omitUndefined({ token: opts.token })
|
|
59
112
|
}),
|
|
60
|
-
slug
|
|
113
|
+
name: slug
|
|
61
114
|
};
|
|
62
115
|
}
|
|
63
116
|
/**
|
|
117
|
+
* What a project with no deployment is told, and why it names two ways out.
|
|
118
|
+
*
|
|
119
|
+
* `requireDeployedSlug`'s sentence — "run `aai publish` first" — is right for
|
|
120
|
+
* `aai secret` and `aai delete`, which have nothing to talk to until the agent
|
|
121
|
+
* is on the platform. It is wrong here often enough to be a defect: a workflow
|
|
122
|
+
* API is the one agent surface that is fully live under `aai dev`, so the
|
|
123
|
+
* developer being told to publish frequently has the thing they asked about
|
|
124
|
+
* answering on localhost already.
|
|
125
|
+
*/
|
|
126
|
+
const NO_DEPLOYMENT = "This project has no deployed agent, and no --agent URL was given — so there is nothing for `aai workflow` to ask.";
|
|
127
|
+
/**
|
|
128
|
+
* What a malformed `--agent` is told.
|
|
129
|
+
*
|
|
130
|
+
* Its own hint rather than {@link HINT_AGENT}: the caller already knows about
|
|
131
|
+
* the flag — they typed it — so repeating "run `aai publish`" answers a
|
|
132
|
+
* question they did not ask.
|
|
133
|
+
*/
|
|
134
|
+
const HINT_URL = "Pass a full origin, e.g. --agent http://localhost:3000";
|
|
135
|
+
/** How to reach a server you are running yourself. Paired with {@link NO_DEPLOYMENT}. */
|
|
136
|
+
const HINT_AGENT = "Run `aai publish` to deploy it, or pass --agent <url> to target a server you are already running — `--agent http://localhost:3000` for `aai dev`, which needs no login and no slug.";
|
|
137
|
+
/**
|
|
64
138
|
* Run one call, turning a rejection into this command's failure result.
|
|
65
139
|
*
|
|
66
140
|
* The client throws with the AGENT'S own sentence — an unknown workflow names
|
|
@@ -93,11 +167,11 @@ async function attempt(code, call) {
|
|
|
93
167
|
}
|
|
94
168
|
/** `aai workflow list` — what this agent declares. */
|
|
95
169
|
async function executeWorkflowList(cwd, opts) {
|
|
96
|
-
const { api,
|
|
170
|
+
const { api, name } = await target(cwd, opts);
|
|
97
171
|
const res = await attempt("workflow_list_failed", () => api.list());
|
|
98
172
|
if (!res.ok) return res;
|
|
99
173
|
const workflows = res.value;
|
|
100
|
-
if (workflows.length === 0) log.info(`${
|
|
174
|
+
if (workflows.length === 0) log.info(`${name} declares no workflows`);
|
|
101
175
|
else for (const w of workflows) log.info(`${w.name}${w.description ? ` — ${w.description}` : ""}`);
|
|
102
176
|
return ok({ workflows });
|
|
103
177
|
}
|
package/dist/workflow.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ type Run = WorkflowRunSnapshot;
|
|
|
39
39
|
type WorkflowOptions = {
|
|
40
40
|
server?: string | undefined;
|
|
41
41
|
token?: string | undefined;
|
|
42
|
+
agent?: string | undefined;
|
|
42
43
|
};
|
|
43
44
|
/** `aai workflow list` — what this agent declares. */
|
|
44
45
|
export declare function executeWorkflowList(cwd: string, opts: WorkflowOptions): Promise<CommandResult<{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alexkroman1/aai-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"aai": "bin.mjs"
|
|
@@ -33,7 +33,6 @@
|
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@clack/prompts": "^1.7.0",
|
|
36
|
-
"@workflow/builders": "4.1.7",
|
|
37
36
|
"chokidar": "^5.0.0",
|
|
38
37
|
"citty": "^0.2.2",
|
|
39
38
|
"env-paths": "^4.0.0",
|
|
@@ -44,16 +43,15 @@
|
|
|
44
43
|
"p-timeout": "^7.0.1",
|
|
45
44
|
"vite": "^8.2.1",
|
|
46
45
|
"zod": "^4.4.3",
|
|
47
|
-
"@alexkroman1/aai": "
|
|
48
|
-
"@alexkroman1/aai-runtime": "
|
|
49
|
-
"@alexkroman1/aai-ui": "
|
|
46
|
+
"@alexkroman1/aai": "10.0.0",
|
|
47
|
+
"@alexkroman1/aai-runtime": "10.0.0",
|
|
48
|
+
"@alexkroman1/aai-ui": "10.0.0"
|
|
50
49
|
},
|
|
51
50
|
"devDependencies": {
|
|
52
51
|
"playwright": "^1.62.1",
|
|
53
52
|
"tsdown": "^0.22.14",
|
|
54
53
|
"verdaccio": "^6.9.2",
|
|
55
54
|
"vitest": "^4.1.10",
|
|
56
|
-
"workflow": "4.8.2",
|
|
57
55
|
"aai-templates": "0.3.8"
|
|
58
56
|
},
|
|
59
57
|
"peerDependencies": {
|
package/dist/_workflow-scan.d.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* What the BUILT flow bundle carries — the two scans that read it, and the
|
|
3
|
-
* checks over them.
|
|
4
|
-
*
|
|
5
|
-
* Split out of `workflow-bundler.ts` when that file crossed the 500-line cap,
|
|
6
|
-
* along the seam the two scans already share: both read the same artifact after
|
|
7
|
-
* the builder has written it, both attribute a line to a module through
|
|
8
|
-
* esbuild's `// <path>` headers, and neither has anything to do with
|
|
9
|
-
* CONFIGURING the build. One of them fails the build (a `require` the workflow
|
|
10
|
-
* VM cannot answer) and the other warns (a call that replays differently), which
|
|
11
|
-
* is the only real difference between them.
|
|
12
|
-
*
|
|
13
|
-
* Internal: `workflow-bundler.ts` is the surface the studio and the CLI's own
|
|
14
|
-
* build call, and it re-exports nothing from here that they need.
|
|
15
|
-
*
|
|
16
|
-
* @module _workflow-scan
|
|
17
|
-
*/
|
|
18
|
-
/** One `require` the workflow VM cannot answer, and the module it was written for. */
|
|
19
|
-
export type VmRequireSite = {
|
|
20
|
-
/** The module specifier — `node:child_process`. */
|
|
21
|
-
specifier: string;
|
|
22
|
-
/** The bundled file esbuild attributed it to, or `undefined` when it said none. */
|
|
23
|
-
module: string | undefined;
|
|
24
|
-
};
|
|
25
|
-
/**
|
|
26
|
-
* Find the Node builtins a flow bundle would `require` at load.
|
|
27
|
-
*
|
|
28
|
-
* The flow bundle is compiled in a `node:vm` `Script` whose context has
|
|
29
|
-
* `module` and `exports` and **no `require`**, so one of these is a run that
|
|
30
|
-
* dies at replay with `ReferenceError: require is not defined` — never a build
|
|
31
|
-
* failure, and never a symptom before the first run. The WDK's own builder
|
|
32
|
-
* bundles everything for exactly this reason and carries
|
|
33
|
-
* `createNodeModuleErrorPlugin` to reject a builtin import at build time.
|
|
34
|
-
*
|
|
35
|
-
* That plugin has two blind spots this scan covers, and both are the DEPLOYED
|
|
36
|
-
* shape rather than an exotic one:
|
|
37
|
-
*
|
|
38
|
-
* - It reports a violation only when it can point at the import LINE in a
|
|
39
|
-
* first-party file, matched with a single-line regex — so a multi-line
|
|
40
|
-
* `import {\n x,\n} from "pkg"` finds nothing and the builtin is marked
|
|
41
|
-
* external in silence.
|
|
42
|
-
* - It resolves that file against `process.cwd()`, which is not the project
|
|
43
|
-
* being built when the studio builds a workspace, so the read fails and the
|
|
44
|
-
* same silent path is taken.
|
|
45
|
-
*
|
|
46
|
-
* Both were reproduced. What reaches the VM either way is
|
|
47
|
-
* `var import_node_child_process = require("node:child_process");` at the top
|
|
48
|
-
* of the bundle, i.e. every run of every workflow in the project fails, and the
|
|
49
|
-
* stack names a line of generated code inside a dependency.
|
|
50
|
-
*
|
|
51
|
-
* Restricted to builtin specifiers deliberately: those are the only ones this
|
|
52
|
-
* builder leaves external (it marks nothing else so, precisely so nothing can
|
|
53
|
-
* need a `require`), and a narrow set is what keeps the scan from reading the
|
|
54
|
-
* text of a prompt as a violation.
|
|
55
|
-
*
|
|
56
|
-
* @internal
|
|
57
|
-
*/
|
|
58
|
-
export declare function findVmRequires(workflowCode: string): VmRequireSite[];
|
|
59
|
-
/** One replay-unsafe call the flow bundle carries, and where it came from. */
|
|
60
|
-
export type ReplayUnsafeSite = {
|
|
61
|
-
/** The call, as written — `Date.now(`. */
|
|
62
|
-
call: string;
|
|
63
|
-
/** What it does on a replay, and what to do instead. */
|
|
64
|
-
fix: string;
|
|
65
|
-
/** The project file esbuild attributed it to. */
|
|
66
|
-
module: string;
|
|
67
|
-
};
|
|
68
|
-
/**
|
|
69
|
-
* Replay-unsafe calls the flow bundle carries, attributed to the project's OWN
|
|
70
|
-
* `workflows/` files.
|
|
71
|
-
*
|
|
72
|
-
* Attribution is the whole design. The bundle inlines every non-external
|
|
73
|
-
* dependency a workflow module imports — zod, a markdown parser, whatever — and
|
|
74
|
-
* third-party code is full of `Date.now()` on paths a workflow never reaches, so
|
|
75
|
-
* a scan of the bundle's text reports a library and blocks a correct project.
|
|
76
|
-
* esbuild writes a `// <path>` header per module (the same one
|
|
77
|
-
* {@link findVmRequires} reads), so lines can be charged to the file they were
|
|
78
|
-
* written in, and only the project's own workflow sources are read.
|
|
79
|
-
*
|
|
80
|
-
* Scanning the BUNDLE rather than the sources is what makes a `"use step"` body
|
|
81
|
-
* exempt for free: the workflow-mode transform has already removed them, so
|
|
82
|
-
* what is left is the part that really does replay.
|
|
83
|
-
*
|
|
84
|
-
* @internal
|
|
85
|
-
*/
|
|
86
|
-
export declare function findReplayUnsafeCalls(workflowCode: string): ReplayUnsafeSite[];
|
|
87
|
-
/**
|
|
88
|
-
* The warning `aai build` and `aai dev` print for a replay-unsafe call.
|
|
89
|
-
*
|
|
90
|
-
* A WARNING and not a build failure, deliberately. The attribution above makes
|
|
91
|
-
* the scan accurate about which FILE a call is in, and it cannot know whether a
|
|
92
|
-
* plain function in a `workflows/` module is reached from a body (where the
|
|
93
|
-
* rule bites) or only from a step (where it does not) — so the one thing it
|
|
94
|
-
* must not do is refuse a correct project. A silent build was the actual
|
|
95
|
-
* problem; a line naming the file solves it without that risk.
|
|
96
|
-
*/
|
|
97
|
-
export declare function replayWarnings(workflowCode: string): string[];
|
|
98
|
-
/**
|
|
99
|
-
* Fail the build when the flow bundle carries a `require` — see
|
|
100
|
-
* {@link findVmRequires} for what that means and why nothing upstream catches it.
|
|
101
|
-
*
|
|
102
|
-
* The message has to name the MODULE as well as the specifier, because the
|
|
103
|
-
* import that caused it is not in the file an author is looking at: only a
|
|
104
|
-
* `"use step"` body is stripped from this bundle, so a value a `workflows/`
|
|
105
|
-
* module holds at module scope — an exported helper, a constant — keeps its
|
|
106
|
-
* import, and that import's whole graph rides into the VM.
|
|
107
|
-
*/
|
|
108
|
-
export declare function assertNoVmRequires(workflowCode: string): void;
|