@alexkroman1/aai-cli 6.4.0 → 6.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{_agent-vGW5SBnX.mjs → _agent-DpH2pBJd.mjs} +1 -5
- package/dist/{_bundler-BapCUUJN.mjs → _bundler-C2BDwU7r.mjs} +5 -7
- package/dist/{_dev-server-DQ1t9sbw.mjs → _dev-server-LLLWnFBs.mjs} +3 -3
- package/dist/_e2e-test-utils.d.ts +17 -17
- package/dist/{_init-CpISqoR8.mjs → _init-C57bWAQ8.mjs} +2 -2
- package/dist/{_slug-api-D2heILuC.mjs → _slug-api-BxrsJXVI.mjs} +3 -3
- package/dist/_slug-api.d.ts +1 -1
- package/dist/{_api-client-LgLksMhN.mjs → _studio-U3jhKlA3.mjs} +105 -2
- package/dist/_studio.d.ts +11 -0
- package/dist/{_templates-iDGjGiPf.mjs → _templates-Dxb_P2Wz.mjs} +1 -1
- package/dist/bin.d.mts +2 -0
- package/dist/{build-BO1Ni6oJ.mjs → build-Dfza2pRI.mjs} +2 -2
- package/dist/cli.mjs +43 -42
- package/dist/{client-bundler-B8Q9F8gJ.mjs → client-bundler-BEIqgOtd.mjs} +2 -2
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-4I0uNR4a.mjs → delete-vbpjGzqs.mjs} +3 -3
- package/dist/{deploy-DJ3HRcia.mjs → deploy-B8lubiRT.mjs} +3 -4
- package/dist/{dev-C6SWaAZP.mjs → dev-Uq5ujP8a.mjs} +1 -1
- package/dist/{eject-Zrzk5KV3.mjs → eject-31gjtaHF.mjs} +2 -2
- package/dist/{init-BTRGiPQr.mjs → init-l6xfU4xX.mjs} +3 -3
- package/dist/{login-k0Z3VQu9.mjs → login-C71-qz8F.mjs} +1 -1
- package/dist/scaffold/package.json +3 -3
- package/dist/{secret-DzSQx1Ds.mjs → secret-BuMuFR4B.mjs} +2 -2
- package/dist/{storage-C-Do0mIA.mjs → storage-DzRZ-eCw.mjs} +2 -2
- package/dist/{studio-BGVmCA2G.mjs → studio-BCNUpDgP.mjs} +4 -5
- package/dist/templates/retail/resolve.ts +16 -0
- package/dist/templates/retail/store.ts +20 -0
- package/dist/templates/retail/tools/cancel_pending_order.ts +2 -5
- package/dist/templates/retail/tools/exchange_delivered_order_items.ts +2 -5
- package/dist/templates/retail/tools/get_order_details.ts +4 -6
- package/dist/templates/retail/tools/get_user_details.ts +2 -7
- package/dist/templates/retail/tools/modify_pending_order_address.ts +2 -5
- package/dist/templates/retail/tools/modify_pending_order_items.ts +2 -5
- package/dist/templates/retail/tools/modify_pending_order_payment.ts +2 -5
- package/dist/templates/retail/tools/modify_user_address.ts +2 -7
- package/dist/templates/retail/tools/return_delivered_order_items.ts +2 -5
- package/dist/templates/transcription-workflow/agent.test.ts +75 -4
- package/dist/templates/transcription-workflow/api-help.tsx +8 -0
- package/dist/templates/transcription-workflow/client.tsx +149 -11
- package/dist/templates/transcription-workflow/workflows/batch.ts +32 -21
- package/dist/templates/transcription-workflow/workflows/stitch.ts +133 -0
- package/dist/templates/transcription-workflow/workflows/stream.ts +20 -20
- package/dist/templates/transcription-workflow/workflows/sync-api.ts +8 -2
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +71 -86
- package/dist/{test-B6ZBxSpk.mjs → test-9kPKJI-w.mjs} +1 -3
- package/dist/typecheck.mjs +4 -5
- package/dist/{worker-bundler-CIdDhZ0c.mjs → worker-bundler-CGD4r8Kc.mjs} +25 -11
- package/dist/worker-bundler.mjs +1 -1
- package/dist/{workflow-B-O97AqI.mjs → workflow-CFpxOFfQ.mjs} +1 -1
- package/package.json +4 -4
- package/dist/_studio-zAJycZ8j.mjs +0 -94
|
@@ -44,17 +44,28 @@
|
|
|
44
44
|
* app's own upload store and the run carries only its id; each step reads
|
|
45
45
|
* exactly its own window with `readUpload`. Sixty steps therefore move the
|
|
46
46
|
* recording once between them, not sixty times.
|
|
47
|
-
* - **The fan-out is bounded by `
|
|
47
|
+
* - **The fan-out is bounded by `mapConcurrent`, and the bound is not a detail.**
|
|
48
48
|
* The DevKit correlates a journal entry to a step call by the ORDER the call
|
|
49
|
-
* was issued in, so
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
49
|
+
* was issued in, so the primitive keeps a WINDOW over a cursor that only ever
|
|
50
|
+
* hands out the next index — the Nth call issued is segment N-1 however the
|
|
51
|
+
* calls settle. Its module doc carries the argument; what matters here is that
|
|
52
|
+
* there is no barrier, so a slow segment costs only itself.
|
|
53
|
+
* - **The transcript STREAMS as it is produced.** Each segment is emitted the
|
|
54
|
+
* moment it lands (`emit(TRANSCRIPT_STREAM, …)`), so the page renders the
|
|
55
|
+
* answer growing rather than a status line and then everything at once. That is
|
|
56
|
+
* the difference a fan-out can make to a reader and a run output cannot: an
|
|
57
|
+
* `output` exists only when the last segment does.
|
|
54
58
|
*/
|
|
55
59
|
|
|
56
60
|
import { throwFatalStepError } from "@alexkroman1/aai/step-errors";
|
|
57
|
-
import {
|
|
61
|
+
import { emit, mapConcurrent, readUpload, report, uploadInfo } from "@alexkroman1/aai/utils";
|
|
62
|
+
import {
|
|
63
|
+
clock,
|
|
64
|
+
countWords,
|
|
65
|
+
stitchTranscript,
|
|
66
|
+
TRANSCRIPT_STREAM,
|
|
67
|
+
type TranscriptChunk,
|
|
68
|
+
} from "./stitch.ts";
|
|
58
69
|
import { elapsed, timed, transcribeWav } from "./sync-api.ts";
|
|
59
70
|
import {
|
|
60
71
|
bytesPerSecond,
|
|
@@ -117,14 +128,16 @@ export const BYTES_IN_FLIGHT = 640 * 1024 * 1024;
|
|
|
117
128
|
* The widest fan-out, however small the segments are.
|
|
118
129
|
*
|
|
119
130
|
* Because {@link BYTES_IN_FLIGHT} stops being the binding constraint once segments
|
|
120
|
-
* are small — 16 kHz mono would divide out to 173 — and
|
|
121
|
-
* before that helps
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
131
|
+
* are small — 16 kHz mono would divide out to 173 — and the endpoint's own tail
|
|
132
|
+
* takes over before that helps: p95/p50 measured 1.1x at 20 concurrent against
|
|
133
|
+
* 1.5x at 320, with max/p50 reaching 6.7x (5.2s against 35.0s). A `503` carrying
|
|
134
|
+
* `retry-after: 1` is exactly such a straggler.
|
|
135
|
+
*
|
|
136
|
+
* **That tail used to be paid once per ROUND, and is not any more.** `mapConcurrent`
|
|
137
|
+
* was `mapInBatches` — sequential batches of `Promise.all` — so a batch's wall time
|
|
138
|
+
* was its slowest member and a run's was the sum of those. It is a window over a
|
|
139
|
+
* cursor now: a straggler holds up nothing but itself, and the numbers below were
|
|
140
|
+
* measured under the old barrier, so they are if anything pessimistic.
|
|
128
141
|
*
|
|
129
142
|
* 32 is the measured knee over 65 segments (1h37m of 48 kHz stereo), one concurrency
|
|
130
143
|
* per run, through this workflow:
|
|
@@ -142,6 +155,10 @@ export const BYTES_IN_FLIGHT = 640 * 1024 * 1024;
|
|
|
142
155
|
* also inert below a threshold — at 90-second segments, 32 only binds past 48
|
|
143
156
|
* minutes of audio — so on a typical recording the whole fan-out is in flight
|
|
144
157
|
* either way and this number changes nothing.
|
|
158
|
+
*
|
|
159
|
+
* The table above was measured under the old per-round barrier. Re-measuring it is
|
|
160
|
+
* worth doing before this number moves again: the window makes a wide fan-out
|
|
161
|
+
* cheaper at the tail, which if anything argues for a HIGHER knee.
|
|
145
162
|
*/
|
|
146
163
|
export const MAX_SEGMENT_CONCURRENCY = 32;
|
|
147
164
|
|
|
@@ -155,7 +172,7 @@ export const MAX_SEGMENT_CONCURRENCY = 32;
|
|
|
155
172
|
*
|
|
156
173
|
* Safe to call from a workflow BODY: `format` arrives from a journaled step result,
|
|
157
174
|
* so a replay derives the same width from the same bytes — which is what keeps
|
|
158
|
-
* `
|
|
175
|
+
* `mapConcurrent` issuing its calls in the order the journal recorded them.
|
|
159
176
|
*
|
|
160
177
|
* Overshooting stays recoverable whatever this returns: a `503` carries
|
|
161
178
|
* `retry-after` and `toStepError` below honours it, so the run completes having paid
|
|
@@ -178,9 +195,6 @@ export function segmentConcurrency(format: WavFormat): number {
|
|
|
178
195
|
*/
|
|
179
196
|
const HEADER_PROBE_BYTES = 64 * 1024;
|
|
180
197
|
|
|
181
|
-
/** Most words `stitchTranscript` will look back over to find a repeated seam. */
|
|
182
|
-
const MAX_SEAM_WORDS = 40;
|
|
183
|
-
|
|
184
198
|
/**
|
|
185
199
|
* What a finished run reports, whichever flow produced it.
|
|
186
200
|
*
|
|
@@ -201,7 +215,7 @@ export type Transcript = {
|
|
|
201
215
|
transcript: string;
|
|
202
216
|
};
|
|
203
217
|
|
|
204
|
-
/** What one segment's request came back with. */
|
|
218
|
+
/** What one segment's request came back with — the STEP's result, journaled. */
|
|
205
219
|
export type SegmentTranscript = {
|
|
206
220
|
index: number;
|
|
207
221
|
text: string;
|
|
@@ -216,15 +230,18 @@ export type SegmentTranscript = {
|
|
|
216
230
|
export async function transcribeFlow(input: { recording: string }) {
|
|
217
231
|
"use workflow";
|
|
218
232
|
|
|
219
|
-
|
|
220
|
-
|
|
233
|
+
// Both at once: neither needs the other, and issued together they are one
|
|
234
|
+
// round trip instead of two before any audio is read. The ORDER is still a
|
|
235
|
+
// pure function of this line — the two calls go out synchronously, left to
|
|
236
|
+
// right — which is what a replay reproduces.
|
|
237
|
+
const [startedAt, plan] = await Promise.all([startClock(), splitRecording(input.recording)]);
|
|
221
238
|
|
|
222
239
|
// One step per segment, bounded, in an order a replay reproduces exactly.
|
|
223
240
|
// A failed segment fails the RUN, deliberately: every sibling that finished is
|
|
224
241
|
// already journaled, so the resume replays those for free and re-issues only
|
|
225
242
|
// what is missing, where catching here to salvage a partial transcript would
|
|
226
243
|
// return a recording with a silent hole in it and report success.
|
|
227
|
-
const parts = await
|
|
244
|
+
const parts = await mapConcurrent(plan.segments, segmentConcurrency(plan.format), (segment) =>
|
|
228
245
|
transcribeSegment(input.recording, plan.format, segment),
|
|
229
246
|
);
|
|
230
247
|
|
|
@@ -309,6 +326,17 @@ export async function transcribeSegment(
|
|
|
309
326
|
// The LATENCY, which is what says whether the concurrency bound or the endpoint
|
|
310
327
|
// is the thing limiting the run — see `timed`'s doc.
|
|
311
328
|
await report(`Transcribed ${clock(segment.startMs)}–${clock(segment.endMs)} in ${elapsed(ms)}.`);
|
|
329
|
+
// And the WORDS, into their own stream, which is what makes this run's answer
|
|
330
|
+
// streamable rather than only its narration: the page stitches whatever has
|
|
331
|
+
// arrived and renders the transcript growing, minutes before `output` exists.
|
|
332
|
+
// Its own namespace because `report`'s stream carries sentences a page prints
|
|
333
|
+
// verbatim — see `emit`'s doc.
|
|
334
|
+
await emit(TRANSCRIPT_STREAM, {
|
|
335
|
+
index: segment.index,
|
|
336
|
+
startMs: segment.startMs,
|
|
337
|
+
endMs: segment.endMs,
|
|
338
|
+
text,
|
|
339
|
+
} satisfies TranscriptChunk);
|
|
312
340
|
return { index: segment.index, text };
|
|
313
341
|
}
|
|
314
342
|
|
|
@@ -337,7 +365,7 @@ export async function mergeTranscript(
|
|
|
337
365
|
|
|
338
366
|
await report(`Stitching ${parts.length} segment${parts.length === 1 ? "" : "s"} together.`);
|
|
339
367
|
|
|
340
|
-
// `
|
|
368
|
+
// `mapConcurrent` resolves in ITEM order however the calls settled, so this is
|
|
341
369
|
// already ordered — sorted anyway, because the merge is where an ordering
|
|
342
370
|
// mistake would be invisible rather than loud.
|
|
343
371
|
const ordered = [...parts].sort((a, b) => a.index - b.index);
|
|
@@ -358,7 +386,7 @@ export async function mergeTranscript(
|
|
|
358
386
|
};
|
|
359
387
|
}
|
|
360
388
|
|
|
361
|
-
// ----
|
|
389
|
+
// ---- The run's own clock ----------------------------------------------------
|
|
362
390
|
|
|
363
391
|
/**
|
|
364
392
|
* When the run started, as epoch ms.
|
|
@@ -380,71 +408,28 @@ export async function startClock(): Promise<number> {
|
|
|
380
408
|
return Date.now();
|
|
381
409
|
}
|
|
382
410
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
411
|
+
// `clock` and `countWords` are re-exported rather than re-declared: `stream.ts`
|
|
412
|
+
// and `batch.ts` already import them from this module, and the split that let the
|
|
413
|
+
// PAGE stitch a partial transcript should not ripple through every flow.
|
|
414
|
+
export {
|
|
415
|
+
clock,
|
|
416
|
+
countWords,
|
|
417
|
+
stitchChunks,
|
|
418
|
+
stitchTranscript,
|
|
419
|
+
TRANSCRIPT_STREAM,
|
|
420
|
+
type TranscriptChunk,
|
|
421
|
+
} from "./stitch.ts";
|
|
422
|
+
|
|
423
|
+
// ---- I/O helpers ------------------------------------------------------------
|
|
387
424
|
|
|
388
425
|
/**
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
* Segments overlap by `SEGMENT_OVERLAP_SECONDS` (see `wav.ts` for why), so the
|
|
392
|
-
* last few words of one segment are the first few of the next — verbatim when
|
|
393
|
-
* the decoder heard them the same way, which is the common case because it heard
|
|
394
|
-
* the same audio. This finds the longest such run and removes one copy.
|
|
395
|
-
*
|
|
396
|
-
* Comparison is on `seamKey`, not the raw words: the two passes punctuate
|
|
397
|
-
* differently at their own edges (one ends a sentence where the other is
|
|
398
|
-
* mid-clause), so `"today."` and `"today"` are the same word and a raw compare
|
|
399
|
-
* finds no seam at all. The text KEPT is the raw text — only the match is
|
|
400
|
-
* normalized.
|
|
426
|
+
* Run a `wav.ts` helper, turning its "cannot cut this" into a terminal failure.
|
|
401
427
|
*
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
* "the" is not evidence of anything, and requiring the longest run is what stops
|
|
406
|
-
* it counting as one when a longer match is available.
|
|
428
|
+
* Exported for the same reason `countWords` is: `stream.ts` plans with the same
|
|
429
|
+
* `wav.ts` helpers and owes the same classification, and it had this byte for
|
|
430
|
+
* byte.
|
|
407
431
|
*/
|
|
408
|
-
export function
|
|
409
|
-
const merged: string[] = [];
|
|
410
|
-
for (const part of parts) {
|
|
411
|
-
const next = part.split(/\s+/).filter(Boolean);
|
|
412
|
-
if (next.length === 0) continue;
|
|
413
|
-
if (merged.length === 0) {
|
|
414
|
-
merged.push(...next);
|
|
415
|
-
continue;
|
|
416
|
-
}
|
|
417
|
-
merged.push(...next.slice(seamLength(merged, next)));
|
|
418
|
-
}
|
|
419
|
-
return merged.join(" ");
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
/** How many leading words of `next` repeat the tail of `merged`. */
|
|
423
|
-
function seamLength(merged: readonly string[], next: readonly string[]): number {
|
|
424
|
-
const limit = Math.min(MAX_SEAM_WORDS, merged.length, next.length);
|
|
425
|
-
// Longest first, so a short accidental match never wins over a real seam.
|
|
426
|
-
for (let length = limit; length > 0; length--) {
|
|
427
|
-
const tail = merged.slice(merged.length - length);
|
|
428
|
-
if (tail.every((word, at) => seamKey(word) === seamKey(next[at] ?? ""))) return length;
|
|
429
|
-
}
|
|
430
|
-
return 0;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/** Words in a string. Exported so the streaming flow reports the same number. */
|
|
434
|
-
export function countWords(text: string): number {
|
|
435
|
-
return text.split(/\s+/).filter(Boolean).length;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
/** `m:ss` for the progress log — a byte offset means nothing to a reader. */
|
|
439
|
-
export function clock(ms: number): string {
|
|
440
|
-
const seconds = Math.max(0, Math.round(ms / 1000));
|
|
441
|
-
return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, "0")}`;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// ---- I/O helpers ------------------------------------------------------------
|
|
445
|
-
|
|
446
|
-
/** Run a `wav.ts` helper, turning its "cannot cut this" into a terminal failure. */
|
|
447
|
-
function fatalOnUnsupported<T>(read: () => T): T {
|
|
432
|
+
export function fatalOnUnsupported<T>(read: () => T): T {
|
|
448
433
|
try {
|
|
449
434
|
return read();
|
|
450
435
|
} catch (err: unknown) {
|
|
@@ -38,9 +38,7 @@ function resolveVitestCommand(cwd, resolve = createRequire(path.join(cwd, "packa
|
|
|
38
38
|
* Throws on test failure.
|
|
39
39
|
*/
|
|
40
40
|
function runVitest(cwd) {
|
|
41
|
-
|
|
42
|
-
if (existsSync(path.join(cwd, "agent.test.ts"))) testFile = "agent.test.ts";
|
|
43
|
-
else if (existsSync(path.join(cwd, "agent.test.js"))) testFile = "agent.test.js";
|
|
41
|
+
const testFile = ["agent.test.ts", "agent.test.js"].find((name) => existsSync(path.join(cwd, name)));
|
|
44
42
|
if (!testFile) return false;
|
|
45
43
|
const { cmd, args } = resolveVitestCommand(cwd);
|
|
46
44
|
execaSync(cmd, [
|
package/dist/typecheck.mjs
CHANGED
|
@@ -155,12 +155,11 @@ async function typecheckProject(cwd) {
|
|
|
155
155
|
});
|
|
156
156
|
let output = "";
|
|
157
157
|
const keep = (s) => s.length > OUTPUT_CAP ? `…${s.slice(-16e3)}` : s;
|
|
158
|
-
|
|
158
|
+
const append = (chunk) => {
|
|
159
159
|
output = keep(output + chunk.toString());
|
|
160
|
-
}
|
|
161
|
-
child.
|
|
162
|
-
|
|
163
|
-
});
|
|
160
|
+
};
|
|
161
|
+
child.stdout.on("data", append);
|
|
162
|
+
child.stderr.on("data", append);
|
|
164
163
|
child.on("error", reject);
|
|
165
164
|
child.on("close", (code, signal) => {
|
|
166
165
|
if (signal) {
|
|
@@ -145,8 +145,19 @@ var AaiWorkflowBuilder = class extends BaseBuilder {
|
|
|
145
145
|
* repo's `erasableSyntaxOnly`, so the fields are declared the long way too.
|
|
146
146
|
*/
|
|
147
147
|
output;
|
|
148
|
-
|
|
148
|
+
/**
|
|
149
|
+
* The two bundles `build()` reads back.
|
|
150
|
+
*
|
|
151
|
+
* Held as fields because `super()` is also told where to write them: derived
|
|
152
|
+
* a second time inside `build()`, the two spellings of each path were free to
|
|
153
|
+
* disagree, and a `build()` reading a file the builder never wrote is a
|
|
154
|
+
* "produced nothing" failure that names neither path.
|
|
155
|
+
*/
|
|
156
|
+
flowFile;
|
|
157
|
+
stepFile;
|
|
149
158
|
constructor(cwd, outDir) {
|
|
159
|
+
const flowFile = path.join(outDir, "flow.mjs");
|
|
160
|
+
const stepFile = path.join(outDir, "step.mjs");
|
|
150
161
|
super({
|
|
151
162
|
buildTarget: "standalone",
|
|
152
163
|
dirs: [`./${WORKFLOWS_DIR}`],
|
|
@@ -154,31 +165,30 @@ var AaiWorkflowBuilder = class extends BaseBuilder {
|
|
|
154
165
|
projectRoot: cwd,
|
|
155
166
|
moduleSpecifierRoot: cwd,
|
|
156
167
|
externalPackages: [...WDK_EXTERNAL],
|
|
157
|
-
stepsBundlePath:
|
|
158
|
-
workflowsBundlePath:
|
|
168
|
+
stepsBundlePath: stepFile,
|
|
169
|
+
workflowsBundlePath: flowFile,
|
|
159
170
|
webhookBundlePath: path.join(outDir, "webhook.mjs"),
|
|
160
171
|
suppressCreateWorkflowsBundleLogs: true,
|
|
161
172
|
suppressCreateWebhookBundleLogs: true,
|
|
162
173
|
suppressCreateManifestLogs: true
|
|
163
174
|
});
|
|
164
|
-
this.
|
|
175
|
+
this.flowFile = flowFile;
|
|
176
|
+
this.stepFile = stepFile;
|
|
165
177
|
}
|
|
166
178
|
async build() {
|
|
167
179
|
const inputFiles = await this.getInputFiles();
|
|
168
|
-
const flowFile = path.join(this.outDir, "flow.mjs");
|
|
169
|
-
const stepFile = path.join(this.outDir, "step.mjs");
|
|
170
180
|
const { manifest } = await this.createWorkflowsBundle({
|
|
171
181
|
inputFiles,
|
|
172
182
|
format: "esm",
|
|
173
|
-
outfile: flowFile,
|
|
183
|
+
outfile: this.flowFile,
|
|
174
184
|
bundleFinalOutput: false
|
|
175
185
|
});
|
|
176
186
|
await this.createStepsBundle({
|
|
177
187
|
inputFiles,
|
|
178
188
|
format: "esm",
|
|
179
|
-
outfile: stepFile
|
|
189
|
+
outfile: this.stepFile
|
|
180
190
|
});
|
|
181
|
-
const [workflowCode, stepCode] = await Promise.all([fs.readFile(flowFile, "utf-8"), fs.readFile(stepFile, "utf-8")]);
|
|
191
|
+
const [workflowCode, stepCode] = await Promise.all([fs.readFile(this.flowFile, "utf-8"), fs.readFile(this.stepFile, "utf-8")]);
|
|
182
192
|
this.output = {
|
|
183
193
|
workflowCode,
|
|
184
194
|
stepCode: STEP_REQUIRE_SHIM + stepCode,
|
|
@@ -358,8 +368,12 @@ export const __aaiStepCode = ${JSON.stringify(workflows.stepCode)};
|
|
|
358
368
|
*/
|
|
359
369
|
async function buildWorker(cwd, opts = {}) {
|
|
360
370
|
const wrapperPath = path.join(cwd, WRAPPER_ENTRY_REL);
|
|
361
|
-
|
|
362
|
-
|
|
371
|
+
const [, toolFiles, systemPromptFile] = await Promise.all([
|
|
372
|
+
fs.mkdir(path.dirname(wrapperPath), { recursive: true }),
|
|
373
|
+
discoverToolFiles(cwd),
|
|
374
|
+
hasSystemPromptFile(cwd)
|
|
375
|
+
]);
|
|
376
|
+
await fs.writeFile(wrapperPath, wrapperEntrySource(opts.runtime !== false, opts.workflows, toolFiles, systemPromptFile), "utf-8");
|
|
363
377
|
const plugins = [...opts.plugins ?? [], ...opts.workflows ? [workflowClientPlugin(cwd, opts.workflows.inputFiles)] : []];
|
|
364
378
|
let result;
|
|
365
379
|
try {
|
package/dist/worker-bundler.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { a as ok, n as fail } from "./_output-CKkmWs7i.mjs";
|
|
3
3
|
import { n as log } from "./_ui-u7T4YooX.mjs";
|
|
4
|
-
import { n as getServerInfo } from "./_agent-
|
|
4
|
+
import { n as getServerInfo } from "./_agent-DpH2pBJd.mjs";
|
|
5
5
|
import { errorMessage, omitUndefined } from "@alexkroman1/aai/utils";
|
|
6
6
|
import { createWorkflowApiClient } from "@alexkroman1/aai/workflow-api";
|
|
7
7
|
//#region workflow.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alexkroman1/aai-cli",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.5.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"aai": "bin.mjs"
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"p-timeout": "^7.0.1",
|
|
45
45
|
"vite": "^8.2.1",
|
|
46
46
|
"zod": "^4.4.3",
|
|
47
|
-
"@alexkroman1/aai": "6.
|
|
48
|
-
"@alexkroman1/aai-ui": "6.
|
|
47
|
+
"@alexkroman1/aai": "6.5.1",
|
|
48
|
+
"@alexkroman1/aai-ui": "6.5.1"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"playwright": "^1.62.1",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"verdaccio": "^6.9.2",
|
|
54
54
|
"vitest": "^4.1.10",
|
|
55
55
|
"workflow": "4.8.2",
|
|
56
|
-
"aai-templates": "0.3.
|
|
56
|
+
"aai-templates": "0.3.6"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"vitest": "^4.1.10"
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { a as isStringArray, i as checkedResponse, n as apiRequest } from "./_api-client-LgLksMhN.mjs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { MAX_SLUG_LENGTH, PREVIEW_SLUG_SUFFIX, VALID_SLUG_RE, isRecord } from "@alexkroman1/aai/utils";
|
|
5
|
-
import { slugifyName } from "@alexkroman1/aai/slugify";
|
|
6
|
-
import { isLocalOnlyFile, snapshotWorkspaceFiles } from "@alexkroman1/aai/workspace-files";
|
|
7
|
-
//#region _studio.ts
|
|
8
|
-
/**
|
|
9
|
-
* Internals of the studio-workspace commands (`aai list/pull/push/publish`):
|
|
10
|
-
* the local source-file walk and the thin clients for the platform's
|
|
11
|
-
* `/studio/projects` routes. The workspace is the single source of truth —
|
|
12
|
-
* these helpers only move files between a local directory and the project's
|
|
13
|
-
* workspace row; production deploys happen exclusively through the studio's
|
|
14
|
-
* Publish route (which runs the deploy machinery in the project's sandbox).
|
|
15
|
-
*/
|
|
16
|
-
/**
|
|
17
|
-
* Snapshot the local project into a workspace file map.
|
|
18
|
-
*
|
|
19
|
-
* The walk, the caps, the skip rules and the strict UTF-8 decode all come
|
|
20
|
-
* from the SDK (`@alexkroman1/aai/workspace-files`), which is also what the
|
|
21
|
-
* guest's end-of-turn sync uses — the two write the same map from opposite
|
|
22
|
-
* ends, and a disagreement between them is not an error but a file silently
|
|
23
|
-
* dropped here and resurrected there.
|
|
24
|
-
*
|
|
25
|
-
* `isLocalOnlyFile` is the one rule this side adds: `.env` rides the secret
|
|
26
|
-
* routes and lockfiles are install output, so neither belongs in a row.
|
|
27
|
-
*/
|
|
28
|
-
function collectSourceFiles(dir) {
|
|
29
|
-
return snapshotWorkspaceFiles(dir, {
|
|
30
|
-
subject: "Project",
|
|
31
|
-
skipFile: isLocalOnlyFile
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* A studio project name derived from a directory name, or null if unusable.
|
|
36
|
-
*
|
|
37
|
-
* The normalization is the platform's own (`slugifyName`), not a local
|
|
38
|
-
* regex. This used to strip `[^a-z0-9-_]` by hand, which differs from the
|
|
39
|
-
* studio's slugifier on exactly the names people give agents: `Café
|
|
40
|
-
* Ordering` reduced to `caf-ordering` here and `cafe-ordering` there, so the
|
|
41
|
-
* same directory produced a different project depending on whether it was
|
|
42
|
-
* created by `aai push` or by typing the name into the studio.
|
|
43
|
-
*
|
|
44
|
-
* A `-preview` suffix is deliberately unusable. Publishing a project deploys
|
|
45
|
-
* it under the project's own name, so a `*-preview` project would claim a
|
|
46
|
-
* slug the studio's orphan-preview sweep reaps hourly — deleting the agent,
|
|
47
|
-
* its app-database schema, and its secrets on a schedule the user never
|
|
48
|
-
* asked for. Refusing the name is recoverable (rename the directory); losing
|
|
49
|
-
* a published agent to the reaper is not.
|
|
50
|
-
*/
|
|
51
|
-
function projectNameFromDir(dir) {
|
|
52
|
-
const name = slugifyName(path.basename(dir), MAX_SLUG_LENGTH);
|
|
53
|
-
if (name.endsWith(PREVIEW_SLUG_SUFFIX)) return null;
|
|
54
|
-
return VALID_SLUG_RE.test(name) ? name : null;
|
|
55
|
-
}
|
|
56
|
-
/** The shareable studio URL for a project — what every command prints. */
|
|
57
|
-
function studioProjectUrl(serverUrl, project) {
|
|
58
|
-
return `${serverUrl}/studio/chat/${project}`;
|
|
59
|
-
}
|
|
60
|
-
function listStudioProjects(serverUrl, apiKey) {
|
|
61
|
-
return apiRequest(`${serverUrl}/studio/projects`, {
|
|
62
|
-
apiKey,
|
|
63
|
-
action: "list"
|
|
64
|
-
}).then((res) => checkedResponse(res, (value) => isRecord(value) && isStringArray(value.projects), `the studio project list at ${serverUrl}`).projects);
|
|
65
|
-
}
|
|
66
|
-
/** Fetch a project, or null when it doesn't exist (the push existence probe). */
|
|
67
|
-
function fetchStudioProject(serverUrl, apiKey, project) {
|
|
68
|
-
return apiRequest(`${serverUrl}/studio/projects/${encodeURIComponent(project)}`, {
|
|
69
|
-
apiKey,
|
|
70
|
-
action: "pull",
|
|
71
|
-
allow404: true
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
/** `PUT /studio/projects/:project/source` — the atomic whole-tree push. */
|
|
75
|
-
function pushStudioSource(serverUrl, apiKey, project, body) {
|
|
76
|
-
return apiRequest(`${serverUrl}/studio/projects/${encodeURIComponent(project)}/source`, {
|
|
77
|
-
apiKey,
|
|
78
|
-
action: "push",
|
|
79
|
-
method: "PUT",
|
|
80
|
-
body,
|
|
81
|
-
hints: { 409: "The studio has newer changes. Run `aai pull` to fetch them, or `aai push --force` to overwrite." }
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
/** `POST /studio/projects/:project/deploy` — Publish, in the project's sandbox. */
|
|
85
|
-
function publishStudioProject(serverUrl, apiKey, project) {
|
|
86
|
-
return apiRequest(`${serverUrl}/studio/projects/${encodeURIComponent(project)}/deploy`, {
|
|
87
|
-
apiKey,
|
|
88
|
-
action: "publish",
|
|
89
|
-
method: "POST",
|
|
90
|
-
retry: 0
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
//#endregion
|
|
94
|
-
export { publishStudioProject as a, projectNameFromDir as i, fetchStudioProject as n, pushStudioSource as o, listStudioProjects as r, studioProjectUrl as s, collectSourceFiles as t };
|