@eigenpal/cli 0.6.9 → 0.6.10
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/CHANGELOG.md +6 -0
- package/README.md +2 -2
- package/dist/cli.js +1310 -1274
- package/dist/skill/SKILL.md +60 -57
- package/dist/skill/reference/cli/agents.md +143 -556
- package/dist/skill/reference/cli/init.md +1 -1
- package/dist/skill/reference/cli/runs.md +481 -0
- package/dist/skill/reference/cli/surface.md +135 -0
- package/dist/skill/reference/cli/workflow.md +35 -149
- package/dist/skill/reference/dataset-format.md +1 -1
- package/dist/skill/reference/debugging.md +23 -22
- package/dist/skill/reference/step-exec.md +3 -3
- package/dist/skill/reference/workflow-yaml.md +2 -2
- package/dist/templates/blank/README.md +1 -1
- package/dist/templates/pdf-extraction/README.md +1 -1
- package/dist/templates/text-classification/README.md +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -19715,7 +19715,7 @@ function executionWallClockMs(startedAt, completedAt) {
|
|
|
19715
19715
|
const ms = end - start;
|
|
19716
19716
|
return ms >= 0 ? ms : null;
|
|
19717
19717
|
}
|
|
19718
|
-
var DURABLE_EXECUTION_TYPES, EXECUTION_STATUSES, EXECUTION_PHASES, EXECUTION_PHASE_ORDER, EXECUTION_PHASE_STATUSES, EXECUTION_FAILURE_CATEGORIES, TERMINAL_EXECUTION_STATUSES, TERMINAL_STATUS_SET, EXECUTION_NON_TERMINAL_STATUSES, NON_SUCCESS_TERMINAL_EXECUTION_STATUSES, NON_SUCCESS_TERMINAL_SET, EXECUTION_CONCURRENCY_ACTIVE_STATUSES, EXECUTION_SHARED_CANCEL_REQUESTABLE_STATUSES, EXECUTION_AGENT_CANCEL_REQUESTABLE_STATUSES, EXECUTION_AGENT_QUEUED_STATUSES, EXECUTION_AGENT_REAP_SCAN_STATUSES, EXECUTION_AGENT_METRICS_STATUSES, EXECUTION_EVAL_RUNNING_STATUSES, EXECUTION_FAILED_OR_CANCELLED_STATUSES;
|
|
19718
|
+
var DURABLE_EXECUTION_TYPES, RUNS_TRIGGERED_BY_SYSTEM = "__system__", EXECUTION_STATUSES, EXECUTION_PHASES, EXECUTION_PHASE_ORDER, EXECUTION_PHASE_STATUSES, EXECUTION_FAILURE_CATEGORIES, TERMINAL_EXECUTION_STATUSES, TERMINAL_STATUS_SET, EXECUTION_NON_TERMINAL_STATUSES, NON_SUCCESS_TERMINAL_EXECUTION_STATUSES, NON_SUCCESS_TERMINAL_SET, EXECUTION_CONCURRENCY_ACTIVE_STATUSES, EXECUTION_SHARED_CANCEL_REQUESTABLE_STATUSES, EXECUTION_AGENT_CANCEL_REQUESTABLE_STATUSES, EXECUTION_AGENT_QUEUED_STATUSES, EXECUTION_AGENT_REAP_SCAN_STATUSES, EXECUTION_AGENT_METRICS_STATUSES, EXECUTION_EVAL_RUNNING_STATUSES, EXECUTION_FAILED_OR_CANCELLED_STATUSES;
|
|
19719
19719
|
var init_execution_row = __esm(() => {
|
|
19720
19720
|
DURABLE_EXECUTION_TYPES = ["workflow", "agent"];
|
|
19721
19721
|
EXECUTION_STATUSES = [
|
|
@@ -66958,6 +66958,7 @@ __export(exports_src, {
|
|
|
66958
66958
|
S3_PATH_GRAMMAR: () => S3_PATH_GRAMMAR,
|
|
66959
66959
|
RootSourceManifestSchema: () => RootSourceManifestSchema,
|
|
66960
66960
|
ReleaseTagSchema: () => ReleaseTagSchema,
|
|
66961
|
+
RUNS_TRIGGERED_BY_SYSTEM: () => RUNS_TRIGGERED_BY_SYSTEM,
|
|
66961
66962
|
ProcessorPortSchema: () => ProcessorPortSchema,
|
|
66962
66963
|
ProcessorExecutionContextSchema: () => ProcessorExecutionContextSchema,
|
|
66963
66964
|
ProcessorDefinitionSchema: () => ProcessorDefinitionSchema,
|
|
@@ -188771,7 +188772,7 @@ import { fileURLToPath as fileURLToPath6 } from "url";
|
|
|
188771
188772
|
// package.json
|
|
188772
188773
|
var package_default = {
|
|
188773
188774
|
name: "@eigenpal/cli",
|
|
188774
|
-
version: "0.6.
|
|
188775
|
+
version: "0.6.10",
|
|
188775
188776
|
description: "Eigenpal CLI — author and run document workflows from your terminal. Agent-ready.",
|
|
188776
188777
|
keywords: [
|
|
188777
188778
|
"eigenpal",
|
|
@@ -191301,7 +191302,7 @@ function registerAgentSourceCommands(agent) {
|
|
|
191301
191302
|
withBaseUrl(agent.command("clone")).description("Clone the organization source repository.").option("--out <dir>", "Output directory").option("--tenant-id <tenantId>", "Target tenant id for admin-token source clones").action(action(cloneSource));
|
|
191302
191303
|
withBaseUrl(agent.command("install [packageRef]")).description("Materialize a source package and its workspace dependencies.").option("--out <dir>", "Output directory for an explicit package ref").option("--lockfile <path>", "Lockfile path").option("--frozen-lockfile", "Install exactly from the existing lockfile").option("--remote-url <url>", "Use an explicit organization Git remote URL").action(action(install));
|
|
191303
191304
|
registerSourceInitCommand(agent);
|
|
191304
|
-
withBaseUrl(agent.command("pull")).description("Pull organization source from origin/main with --ff-only. For datasets use agents dataset pull; for run artifacts use
|
|
191305
|
+
withBaseUrl(agent.command("pull")).description("Pull organization source from origin/main with --ff-only. For datasets use agents dataset pull; for run artifacts use runs artifacts fetch.").option("--dir <dir>", "Repository directory").action(action(pullSource));
|
|
191305
191306
|
withBaseUrl(agent.command("commit")).description("Validate changed source packages and commit them.").addOption(new Option("-m, --message <message>", "Commit message").makeOptionMandatory()).option("--dir <dir>", "Repository directory").action(action(commitSource));
|
|
191306
191307
|
withBaseUrl(agent.command("save")).description("Validate, commit if dirty, and push the current source branch.").option("-m, --message <message>", "Commit message when source changes are dirty").option("--dir <dir>", "Repository directory").action(action(saveSource));
|
|
191307
191308
|
withBaseUrl(agent.command("push")).description("Push the current organization source branch and tags.").option("--dir <dir>", "Repository directory").action(action(pushSource));
|
|
@@ -191322,12 +191323,240 @@ function registerGitCommands(program2) {
|
|
|
191322
191323
|
credentialHelper.hidden = true;
|
|
191323
191324
|
}
|
|
191324
191325
|
|
|
191325
|
-
// src/commands/
|
|
191326
|
+
// src/commands/runs.ts
|
|
191327
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
191328
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
191329
|
+
import { promises as fs3, readFileSync as readFileSync7 } from "node:fs";
|
|
191326
191330
|
import path8 from "node:path";
|
|
191327
191331
|
|
|
191328
|
-
// src/
|
|
191329
|
-
|
|
191332
|
+
// src/lib/resolve-workflow.ts
|
|
191333
|
+
var WORKFLOW_ID_PREFIX = "wf_";
|
|
191334
|
+
function looksLikeWorkflowId(value) {
|
|
191335
|
+
return value.startsWith(WORKFLOW_ID_PREFIX);
|
|
191336
|
+
}
|
|
191337
|
+
function levenshtein(a, b2, maxDistance) {
|
|
191338
|
+
if (a === b2)
|
|
191339
|
+
return 0;
|
|
191340
|
+
if (Math.abs(a.length - b2.length) > maxDistance)
|
|
191341
|
+
return maxDistance + 1;
|
|
191342
|
+
if (a.length === 0 || b2.length === 0)
|
|
191343
|
+
return a.length || b2.length;
|
|
191344
|
+
const prev = new Array(b2.length + 1);
|
|
191345
|
+
const curr = new Array(b2.length + 1);
|
|
191346
|
+
for (let j3 = 0;j3 <= b2.length; j3++)
|
|
191347
|
+
prev[j3] = j3;
|
|
191348
|
+
for (let i = 1;i <= a.length; i++) {
|
|
191349
|
+
curr[0] = i;
|
|
191350
|
+
let minRow = curr[0];
|
|
191351
|
+
for (let j3 = 1;j3 <= b2.length; j3++) {
|
|
191352
|
+
const cost = a[i - 1] === b2[j3 - 1] ? 0 : 1;
|
|
191353
|
+
curr[j3] = Math.min(prev[j3] + 1, curr[j3 - 1] + 1, prev[j3 - 1] + cost);
|
|
191354
|
+
if (curr[j3] < minRow)
|
|
191355
|
+
minRow = curr[j3];
|
|
191356
|
+
}
|
|
191357
|
+
if (minRow > maxDistance)
|
|
191358
|
+
return maxDistance + 1;
|
|
191359
|
+
for (let j3 = 0;j3 <= b2.length; j3++)
|
|
191360
|
+
prev[j3] = curr[j3];
|
|
191361
|
+
}
|
|
191362
|
+
return prev[b2.length];
|
|
191363
|
+
}
|
|
191364
|
+
async function findSimilarSlug(client, slug) {
|
|
191365
|
+
try {
|
|
191366
|
+
const res = await client.get("/api/v1/workflows", {
|
|
191367
|
+
search: slug,
|
|
191368
|
+
limit: "20"
|
|
191369
|
+
});
|
|
191370
|
+
const candidates = (res.data ?? []).map((w3) => w3.name).filter((n) => typeof n === "string" && n.length > 0);
|
|
191371
|
+
let best = null;
|
|
191372
|
+
for (const candidate of candidates) {
|
|
191373
|
+
const distance = levenshtein(candidate, slug, 2);
|
|
191374
|
+
if (distance <= 2 && (best === null || distance < best.distance)) {
|
|
191375
|
+
best = { name: candidate, distance };
|
|
191376
|
+
}
|
|
191377
|
+
}
|
|
191378
|
+
return best?.name ?? null;
|
|
191379
|
+
} catch {
|
|
191380
|
+
return null;
|
|
191381
|
+
}
|
|
191382
|
+
}
|
|
191383
|
+
function notFound(idOrSlug, suggestion) {
|
|
191384
|
+
const base = `Workflow "${idOrSlug}" not found on the server.`;
|
|
191385
|
+
const action2 = "Push it first, or run `eigenpal workflow list` to see what's available.";
|
|
191386
|
+
return new Error(suggestion ? `${base} Did you mean "${suggestion}"? ${action2}` : `${base} ${action2}`);
|
|
191387
|
+
}
|
|
191388
|
+
async function resolveWorkflowId(client, idOrSlug) {
|
|
191389
|
+
if (looksLikeWorkflowId(idOrSlug)) {
|
|
191390
|
+
try {
|
|
191391
|
+
const wf = await client.get(`/api/v1/workflows/${idOrSlug}`);
|
|
191392
|
+
return wf.id;
|
|
191393
|
+
} catch {
|
|
191394
|
+
throw notFound(idOrSlug, null);
|
|
191395
|
+
}
|
|
191396
|
+
}
|
|
191397
|
+
const res = await client.get("/api/v1/workflows", { name: idOrSlug });
|
|
191398
|
+
const match = res.data?.[0];
|
|
191399
|
+
if (!match) {
|
|
191400
|
+
const suggestion = await findSimilarSlug(client, idOrSlug);
|
|
191401
|
+
throw notFound(idOrSlug, suggestion);
|
|
191402
|
+
}
|
|
191403
|
+
return match.id;
|
|
191404
|
+
}
|
|
191405
|
+
|
|
191406
|
+
// src/lib/watch.ts
|
|
191407
|
+
var TERMINAL_STATUSES = new Set(["completed", "failed", "cancelled", "rejected"]);
|
|
191408
|
+
var STATUS_BADGES = {
|
|
191409
|
+
pending: "[ ]",
|
|
191410
|
+
running: "[..]",
|
|
191411
|
+
completed: "[ok]",
|
|
191412
|
+
failed: "[x]",
|
|
191413
|
+
cancelled: "[-]",
|
|
191414
|
+
skipped: "[s]"
|
|
191415
|
+
};
|
|
191416
|
+
function statusBadge(status) {
|
|
191417
|
+
return STATUS_BADGES[status] ?? `[${status.slice(0, 3)}]`;
|
|
191418
|
+
}
|
|
191419
|
+
function durationSuffix(ms) {
|
|
191420
|
+
const formatted = formatDuration(ms);
|
|
191421
|
+
return formatted ? ` ${formatted}` : "";
|
|
191422
|
+
}
|
|
191423
|
+
function renderFrame(snapshot) {
|
|
191424
|
+
const lines = [
|
|
191425
|
+
`${statusBadge(snapshot.status)} ${snapshot.id}${durationSuffix(snapshot.durationMs)}`
|
|
191426
|
+
];
|
|
191427
|
+
for (const step of snapshot.stepExecutions ?? []) {
|
|
191428
|
+
const errSuffix = step.error ? ` err: ${step.error.slice(0, 80)}` : "";
|
|
191429
|
+
lines.push(` ${statusBadge(step.status)} ${step.stepName}${durationSuffix(step.durationMs)}${errSuffix}`);
|
|
191430
|
+
if (step.outputPreview) {
|
|
191431
|
+
lines.push(` ↳ ${step.outputPreview}`);
|
|
191432
|
+
}
|
|
191433
|
+
}
|
|
191434
|
+
if (snapshot.error && (snapshot.stepExecutions ?? []).length === 0) {
|
|
191435
|
+
lines.push(` err: ${snapshot.error.slice(0, 200)}`);
|
|
191436
|
+
}
|
|
191437
|
+
return lines.join(`
|
|
191438
|
+
`);
|
|
191439
|
+
}
|
|
191440
|
+
var ANSI_CURSOR_UP = "\x1B[A";
|
|
191441
|
+
var ANSI_CLEAR_LINE = "\x1B[2K\r";
|
|
191442
|
+
function makeAnsiRenderer() {
|
|
191443
|
+
const isTty = Boolean(process.stderr.isTTY);
|
|
191444
|
+
let previousLineCount = 0;
|
|
191445
|
+
return (frame) => {
|
|
191446
|
+
let buf = "";
|
|
191447
|
+
if (isTty) {
|
|
191448
|
+
for (let i = 0;i < previousLineCount; i++) {
|
|
191449
|
+
buf += i === 0 ? ANSI_CLEAR_LINE : `${ANSI_CURSOR_UP}${ANSI_CLEAR_LINE}`;
|
|
191450
|
+
}
|
|
191451
|
+
}
|
|
191452
|
+
buf += frame;
|
|
191453
|
+
if (!frame.endsWith(`
|
|
191454
|
+
`))
|
|
191455
|
+
buf += `
|
|
191456
|
+
`;
|
|
191457
|
+
if (!isTty && previousLineCount > 0)
|
|
191458
|
+
buf += `
|
|
191459
|
+
`;
|
|
191460
|
+
process.stderr.write(buf);
|
|
191461
|
+
previousLineCount = frame.split(`
|
|
191462
|
+
`).length;
|
|
191463
|
+
};
|
|
191464
|
+
}
|
|
191465
|
+
function defaultSleep(ms) {
|
|
191466
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
191467
|
+
}
|
|
191468
|
+
function snapshotsDiffer(a, b2) {
|
|
191469
|
+
if (a.status !== b2.status)
|
|
191470
|
+
return true;
|
|
191471
|
+
const aSteps = a.stepExecutions ?? [];
|
|
191472
|
+
const bSteps = b2.stepExecutions ?? [];
|
|
191473
|
+
if (aSteps.length !== bSteps.length)
|
|
191474
|
+
return true;
|
|
191475
|
+
for (let i = 0;i < aSteps.length; i++) {
|
|
191476
|
+
if (aSteps[i].stepName !== bSteps[i].stepName || aSteps[i].status !== bSteps[i].status) {
|
|
191477
|
+
return true;
|
|
191478
|
+
}
|
|
191479
|
+
}
|
|
191480
|
+
return false;
|
|
191481
|
+
}
|
|
191482
|
+
function defaultShouldAbort(err) {
|
|
191483
|
+
return err instanceof ApiError && err.status >= 400 && err.status < 500;
|
|
191484
|
+
}
|
|
191485
|
+
async function watchExecution(opts) {
|
|
191486
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
191487
|
+
const now = opts.now ?? (() => Date.now());
|
|
191488
|
+
const render = opts.render ?? makeAnsiRenderer();
|
|
191489
|
+
const maxMs = opts.maxMs ?? 30 * 60 * 1000;
|
|
191490
|
+
const transitionInterval = opts.transitionIntervalMs ?? 2000;
|
|
191491
|
+
const steadyInterval = opts.steadyIntervalMs ?? 5000;
|
|
191492
|
+
const shouldAbort = opts.shouldAbort ?? defaultShouldAbort;
|
|
191493
|
+
const start = now();
|
|
191494
|
+
let previous;
|
|
191495
|
+
let lastFetched;
|
|
191496
|
+
while (true) {
|
|
191497
|
+
let current;
|
|
191498
|
+
try {
|
|
191499
|
+
current = await opts.fetch();
|
|
191500
|
+
} catch (err) {
|
|
191501
|
+
if (shouldAbort(err))
|
|
191502
|
+
throw err;
|
|
191503
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
191504
|
+
render(`(network) ${message.slice(0, 120)}`);
|
|
191505
|
+
if (lastFetched && now() - start >= maxMs) {
|
|
191506
|
+
return { final: lastFetched, detached: true };
|
|
191507
|
+
}
|
|
191508
|
+
await sleep(steadyInterval);
|
|
191509
|
+
continue;
|
|
191510
|
+
}
|
|
191511
|
+
lastFetched = current;
|
|
191512
|
+
render(renderFrame(current));
|
|
191513
|
+
if (TERMINAL_STATUSES.has(current.status)) {
|
|
191514
|
+
return { final: current, detached: false };
|
|
191515
|
+
}
|
|
191516
|
+
if (now() - start >= maxMs) {
|
|
191517
|
+
return { final: current, detached: true };
|
|
191518
|
+
}
|
|
191519
|
+
const transitioned = previous == null || snapshotsDiffer(previous, current);
|
|
191520
|
+
previous = current;
|
|
191521
|
+
await sleep(transitioned ? transitionInterval : steadyInterval);
|
|
191522
|
+
}
|
|
191523
|
+
}
|
|
191524
|
+
|
|
191525
|
+
// src/commands/agents/run-form-data.ts
|
|
191526
|
+
import { promises as fs } from "node:fs";
|
|
191330
191527
|
import path6 from "node:path";
|
|
191528
|
+
async function buildAgentExecutionRunFormData(inputFile, inputJson) {
|
|
191529
|
+
const form = new FormData;
|
|
191530
|
+
for (const spec of parseAgentInputFileSpecs(inputFile)) {
|
|
191531
|
+
const data = await fs.readFile(spec.filePath);
|
|
191532
|
+
form.append(spec.fieldName, new Blob([data]), path6.basename(spec.filePath));
|
|
191533
|
+
}
|
|
191534
|
+
if (inputJson)
|
|
191535
|
+
form.append("_json", inputJson);
|
|
191536
|
+
return form;
|
|
191537
|
+
}
|
|
191538
|
+
function parseAgentInputFileSpecs(inputFile) {
|
|
191539
|
+
const values = Array.isArray(inputFile) ? inputFile : [inputFile];
|
|
191540
|
+
return values.map((value) => {
|
|
191541
|
+
const separatorIndex = value.indexOf("=");
|
|
191542
|
+
if (separatorIndex === -1) {
|
|
191543
|
+
return { fieldName: "file", filePath: value };
|
|
191544
|
+
}
|
|
191545
|
+
const fieldName = value.slice(0, separatorIndex).trim();
|
|
191546
|
+
const filePath = value.slice(separatorIndex + 1).trim();
|
|
191547
|
+
if (!fieldName || !filePath) {
|
|
191548
|
+
throw new Error("--input-file must be <field=path> or a bare path");
|
|
191549
|
+
}
|
|
191550
|
+
if (fieldName === "_json" || fieldName === "_metadata" || fieldName === "input") {
|
|
191551
|
+
throw new Error(`--input-file field "${fieldName}" is reserved`);
|
|
191552
|
+
}
|
|
191553
|
+
return { fieldName, filePath };
|
|
191554
|
+
});
|
|
191555
|
+
}
|
|
191556
|
+
|
|
191557
|
+
// src/commands/agents/shared.ts
|
|
191558
|
+
import { existsSync as existsSync6, promises as fs2 } from "node:fs";
|
|
191559
|
+
import path7 from "node:path";
|
|
191331
191560
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
191332
191561
|
var PACKAGE_MANIFEST = "eigenpal.yaml";
|
|
191333
191562
|
var DATASET_DIR = "dataset";
|
|
@@ -191351,17 +191580,17 @@ async function readFilesUnder(dir) {
|
|
|
191351
191580
|
return [];
|
|
191352
191581
|
const out = [];
|
|
191353
191582
|
async function walk(current) {
|
|
191354
|
-
for (const entry of await
|
|
191583
|
+
for (const entry of await fs2.readdir(current, { withFileTypes: true })) {
|
|
191355
191584
|
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
191356
191585
|
continue;
|
|
191357
|
-
const absolute =
|
|
191586
|
+
const absolute = path7.join(current, entry.name);
|
|
191358
191587
|
if (entry.isDirectory()) {
|
|
191359
191588
|
await walk(absolute);
|
|
191360
191589
|
} else if (entry.isFile()) {
|
|
191361
|
-
const relative =
|
|
191590
|
+
const relative = path7.relative(dir, absolute).split(path7.sep).join("/");
|
|
191362
191591
|
out.push({
|
|
191363
191592
|
path: relative,
|
|
191364
|
-
contentBase64: (await
|
|
191593
|
+
contentBase64: (await fs2.readFile(absolute)).toString("base64")
|
|
191365
191594
|
});
|
|
191366
191595
|
}
|
|
191367
191596
|
}
|
|
@@ -191370,13 +191599,13 @@ async function readFilesUnder(dir) {
|
|
|
191370
191599
|
return out;
|
|
191371
191600
|
}
|
|
191372
191601
|
async function writeBase64File(file2, contentBase64) {
|
|
191373
|
-
await
|
|
191374
|
-
await
|
|
191602
|
+
await fs2.mkdir(path7.dirname(file2), { recursive: true });
|
|
191603
|
+
await fs2.writeFile(file2, Buffer.from(contentBase64, "base64"));
|
|
191375
191604
|
}
|
|
191376
191605
|
async function pollRun(client, executionId, interval, maxWait) {
|
|
191377
191606
|
const started = Date.now();
|
|
191378
191607
|
for (;; ) {
|
|
191379
|
-
const payload = await client.get(`/api/v1/
|
|
191608
|
+
const payload = await client.get(`/api/v1/runs/${encodeURIComponent(executionId)}?include=detail`);
|
|
191380
191609
|
if (isTerminal(payload.run?.status))
|
|
191381
191610
|
return payload;
|
|
191382
191611
|
if (Date.now() - started > maxWait * 1000) {
|
|
@@ -191470,44 +191699,1037 @@ function toCsv(rows, columns) {
|
|
|
191470
191699
|
`);
|
|
191471
191700
|
}
|
|
191472
191701
|
|
|
191702
|
+
// src/commands/agents/target.ts
|
|
191703
|
+
init_src();
|
|
191704
|
+
function parseAgentTarget(target) {
|
|
191705
|
+
const [rawTarget, rawRef, extra] = target.split("@");
|
|
191706
|
+
if (extra !== undefined)
|
|
191707
|
+
throw new Error("Agent target must be <slug>[@ref].");
|
|
191708
|
+
const normalizedTarget = rawTarget.includes(".") ? target : `agents.${rawTarget}${rawRef !== undefined ? `@${rawRef}` : ""}`;
|
|
191709
|
+
const parsed = parseAutomationTarget(normalizedTarget);
|
|
191710
|
+
if (parsed.type !== "agents") {
|
|
191711
|
+
throw new Error("Only agent targets are supported in this release. Use agents.<slug>[@ref].");
|
|
191712
|
+
}
|
|
191713
|
+
return {
|
|
191714
|
+
packageName: parsed.packageName,
|
|
191715
|
+
slug: parsed.slug,
|
|
191716
|
+
sourceRef: rawRef !== undefined ? parsed.ref : undefined
|
|
191717
|
+
};
|
|
191718
|
+
}
|
|
191719
|
+
|
|
191720
|
+
// src/commands/runs.ts
|
|
191721
|
+
function registerRunsCommands(program2) {
|
|
191722
|
+
const runs = program2.command("runs").description("Inspect, watch, and manage workflow, agent, and eval runs.").action(() => {
|
|
191723
|
+
process.stderr.write("`eigenpal runs` requires a subcommand. Run `eigenpal runs --help`.\n");
|
|
191724
|
+
process.exit(2);
|
|
191725
|
+
});
|
|
191726
|
+
const listRunsCmd = addJsonFlag(withPagination(withBaseUrl(runs.command("list [source]")), 50)).alias("ls").description("List runs across workflows and agents, optionally scoped to one source.").option("--type <type>", "Filter by run type: workflow|agent").option("--status <status>", "Filter by run status").option("--source-ref <ref>", "Filter agent runs by source ref").option("--batch-id <id>", "Filter eval/experiment runs by batch id").option("--example-id <id>", "Filter eval runs by example id").option("--example-id-contains <text>", "Filter eval runs by example id substring").option("--from <date>", "Created at or after this ISO timestamp or relative date").option("--to <date>", "Created before this ISO timestamp or relative date").option("--created-after <date>", "Alias for --from").option("--created-before <date>", "Alias for --to").option("--completed-after <date>", "Completed at or after this ISO timestamp or relative date").option("--completed-before <date>", "Completed before this ISO timestamp or relative date").option("--compact", "Render compact run rows").option("--sort <field>", "Sort field; only createdAt is supported").option("--order <asc|desc>", "Sort order; only desc is supported");
|
|
191727
|
+
listRunsCmd.action(action(listRuns));
|
|
191728
|
+
addJsonFlag(withBaseUrl(runs.command("get <run-id>"))).description("Get one run.").option("--step <name>", "For workflow runs, show only this step (or comma-separated list)").option("--include <parts>", "Comma-separated extra parts or workflow step fields: feedback,expected,files,trace,issues,input,output,error,duration,inputRef", "feedback").action(action(getRun));
|
|
191729
|
+
addJsonFlag(withBaseUrl(runs.command("compare <reference-run-id> <run-id>"))).description("Compare one run against another run. PDF/DOCX text comparison uses pdftotext/python3 and reports byte fallbacks.").option("--baseline", "Compare actual outputs from both runs instead of expected artifacts").option("--step <name>", "For workflow runs, restrict comparison to one step").option("--out <dir>", "Write comparison artifacts to this directory").option("--normalize-dates", "Normalize YYYYMMDD and YYYY-MM-DD tokens in filenames/text").option("--fail-on-diff", "Exit 1 when comparison status is fail").action(action(compareRun));
|
|
191730
|
+
addJsonFlag(withBaseUrl(runs.command("rerun <run-id>"))).description("Create a new run from a previous run's stored input snapshot.").option("--source-ref <ref>", "Source ref for the new run: latest (default) or original. Workflow runs support only latest/original.").option("--wait", "Poll until the rerun reaches a terminal status").option("--interval <seconds>", "Polling interval in seconds", intArg, 2).option("--max-wait <seconds>", "Maximum wait before exiting 2", intArg, 1800).action(action(rerunRun));
|
|
191731
|
+
const artifacts = runs.command("artifacts").description("Inspect agent run artifacts.").action(() => {
|
|
191732
|
+
process.stderr.write("`eigenpal runs artifacts` requires a subcommand. Run `eigenpal runs artifacts --help`.\n");
|
|
191733
|
+
process.exit(2);
|
|
191734
|
+
});
|
|
191735
|
+
addJsonFlag(withBaseUrl(artifacts.command("list <run-id>"))).description("List available agent run artifacts without downloading them.").action(action(listRunArtifacts));
|
|
191736
|
+
withBaseUrl(artifacts.command("fetch <run-id>")).description("Download agent run artifacts by canonical artifact path.").option("--out <dir>", "Output directory").option("--include <parts>", "Comma-separated parts: output,input,metadata,issues,trace,lockfile,expected,all", "all").option("--path <path>", "Fetch one exact artifact path from `artifacts list`; repeatable", collectRepeated, []).option("--json", "Output a JSON summary of written artifacts").action(action(fetchRunArtifacts));
|
|
191737
|
+
withBaseUrl(runs.command("trace <run-id>")).description("Print raw trace.jsonl for a run, or write it with --out.").option("--out <file>", "Output file path").action(action(traceRun));
|
|
191738
|
+
registerRunFeedbackCommands(runs);
|
|
191739
|
+
registerRunExpectedCommands(runs);
|
|
191740
|
+
addJsonFlag(withBaseUrl(runs.command("watch <run-id>"))).description("Watch a run until it reaches a terminal status.").option("--interval <seconds>", "Polling interval in seconds", intArg, 2).option("--max-wait <seconds>", "Maximum wait before exiting 2", intArg, 1800).action(action(watchRunCommand));
|
|
191741
|
+
addJsonFlag(withBaseUrl(runs.command("resume <run-id>"))).description("Resume a workflow run that is waiting for approval.").action(action(resumeRun));
|
|
191742
|
+
addJsonFlag(withBaseUrl(runs.command("cancel <run-id>"))).description("Cancel a run.").option("--yes", "Required in non-interactive environments").action(action(cancelRun));
|
|
191743
|
+
}
|
|
191744
|
+
function registerRunFeedbackCommands(runs) {
|
|
191745
|
+
const feedback = runs.command("feedback").description("Update or clear feedback attached to a run.").action(() => {
|
|
191746
|
+
process.stderr.write("`eigenpal runs feedback` requires a subcommand. Run `eigenpal runs feedback --help`.\n");
|
|
191747
|
+
process.exit(2);
|
|
191748
|
+
});
|
|
191749
|
+
addJsonFlag(withBaseUrl(feedback.command("update <run-id>"))).description("Edit feedback state, rating, message, or expected JSON for a run.").option("--status <open|resolved|ignored>", "Set feedback status").option("--rating <pass|fail|partial|none>", "Set feedback rating").option("--message <text>", "Set feedback message body").option("--message-file <path>", "Read feedback message body from a file").option("--expected-json <json>", "Set structured expected JSON").option("--expected-json-file <path>", "Read structured expected JSON from a file").option("--clear-message", "Clear the feedback message body").option("--clear-rating", "Clear feedback rating").option("--clear-expected-json", "Delete structured expected JSON").action(action(updateRunFeedback));
|
|
191750
|
+
addJsonFlag(withBaseUrl(feedback.command("resolve <run-id>"))).description("Mark run feedback as resolved.").option("--message <text>", "Set feedback message body").option("--message-file <path>", "Read feedback message body from a file").action(action((runId, opts) => updateRunFeedback(runId, { ...opts, status: "resolved" })));
|
|
191751
|
+
addJsonFlag(withBaseUrl(feedback.command("clear <run-id>"))).description("Delete feedback, expected.json, and expected files for a run.").option("--yes", "Required in non-interactive environments").action(action(clearRunFeedback));
|
|
191752
|
+
}
|
|
191753
|
+
function registerRunExpectedCommands(runs) {
|
|
191754
|
+
const expected = runs.command("expected").description("Manage expected artifacts attached to a run.").action(() => {
|
|
191755
|
+
process.stderr.write("`eigenpal runs expected` requires a subcommand. Run `eigenpal runs expected --help`.\n");
|
|
191756
|
+
process.exit(2);
|
|
191757
|
+
});
|
|
191758
|
+
addJsonFlag(withBaseUrl(expected.command("list <run-id>"))).description("List expected JSON and files attached to a run.").action(action(listRunExpected));
|
|
191759
|
+
withBaseUrl(expected.command("pull <run-id>")).description("Download expected JSON and files attached to a run.").option("--out <dir>", "Output directory").action(action(pullRunExpected));
|
|
191760
|
+
addJsonFlag(withBaseUrl(expected.command("upload <run-id> <file>"))).description("Upload a local file as an expected artifact.").option("--name <name>", "Expected artifact name").action(action(uploadRunExpected));
|
|
191761
|
+
addJsonFlag(withBaseUrl(expected.command("copy-output <run-id> <output-file>"))).description("Copy a generated output file into expected artifacts.").option("--name <name>", "Expected artifact name").action(action(copyOutputToExpected));
|
|
191762
|
+
addJsonFlag(withBaseUrl(expected.command("rename <run-id> <old-name> <new-name>"))).description("Rename an expected artifact.").action(action(renameRunExpected));
|
|
191763
|
+
addJsonFlag(withBaseUrl(expected.command("delete <run-id> <name>"))).description("Delete an expected artifact.").option("--yes", "Required in non-interactive environments").action(action(deleteRunExpected));
|
|
191764
|
+
}
|
|
191765
|
+
async function runExecution(agentId, opts) {
|
|
191766
|
+
const client = buildClient(opts);
|
|
191767
|
+
const runPath = `/api/v1/agents/${encodeURIComponent(agentId)}/run${opts.sourceRef ? `?sourceRef=${encodeURIComponent(opts.sourceRef)}` : ""}`;
|
|
191768
|
+
let payload;
|
|
191769
|
+
if (opts.inputFile && opts.inputFile.length > 0) {
|
|
191770
|
+
const form = await buildAgentExecutionRunFormData(opts.inputFile, opts.inputJson);
|
|
191771
|
+
payload = await client.postFormData(runPath, form);
|
|
191772
|
+
} else {
|
|
191773
|
+
payload = await client.post(runPath, {
|
|
191774
|
+
input: opts.inputJson ? JSON.parse(opts.inputJson) : {},
|
|
191775
|
+
...opts.sourceRef ? { sourceRef: opts.sourceRef } : {}
|
|
191776
|
+
});
|
|
191777
|
+
}
|
|
191778
|
+
const runId = String(payload.runId ?? "");
|
|
191779
|
+
let waitedForTerminalRun = false;
|
|
191780
|
+
if (opts.wait && runId) {
|
|
191781
|
+
payload = await pollRun(client, runId, opts.interval ?? 2, opts.maxWait ?? 1800);
|
|
191782
|
+
waitedForTerminalRun = true;
|
|
191783
|
+
}
|
|
191784
|
+
renderRunPayload(payload, opts);
|
|
191785
|
+
if (waitedForTerminalRun)
|
|
191786
|
+
setExitCodeForFailedTerminalRun(payload);
|
|
191787
|
+
}
|
|
191788
|
+
async function runExample(agentId, opts) {
|
|
191789
|
+
const client = buildClient(opts);
|
|
191790
|
+
const started = await client.post(`/api/v1/agents/${encodeURIComponent(agentId)}/experiments`, {
|
|
191791
|
+
exampleId: opts.example,
|
|
191792
|
+
...opts.sourceRef ? { sourceRef: opts.sourceRef } : {}
|
|
191793
|
+
});
|
|
191794
|
+
const runId = started.runs?.[0]?.runId;
|
|
191795
|
+
if (opts.wait && runId) {
|
|
191796
|
+
const payload2 = await pollRun(client, runId, opts.interval, opts.maxWait);
|
|
191797
|
+
renderRunPayload(payload2, opts);
|
|
191798
|
+
setExitCodeForFailedTerminalRun(payload2);
|
|
191799
|
+
return;
|
|
191800
|
+
}
|
|
191801
|
+
const payload = {
|
|
191802
|
+
runId,
|
|
191803
|
+
exampleId: opts.example,
|
|
191804
|
+
batchId: started.batchId,
|
|
191805
|
+
status: "pending"
|
|
191806
|
+
};
|
|
191807
|
+
if (opts.json)
|
|
191808
|
+
return printJson(payload);
|
|
191809
|
+
success2(`Run ${runId ?? ""} queued for example ${opts.example}`);
|
|
191810
|
+
}
|
|
191811
|
+
async function getRun(executionId, opts) {
|
|
191812
|
+
const client = buildClient(opts);
|
|
191813
|
+
const include = opts.include ? `detail,${opts.include}` : "detail";
|
|
191814
|
+
const payload = await client.get(`/api/v1/runs/${encodeURIComponent(executionId)}`, {
|
|
191815
|
+
include
|
|
191816
|
+
});
|
|
191817
|
+
const run = payload.run;
|
|
191818
|
+
if (!run)
|
|
191819
|
+
return renderRunPayload(payload, opts);
|
|
191820
|
+
if (opts.step && !isWorkflowRun(run)) {
|
|
191821
|
+
throw new Error("Agent runs do not have workflow steps. Use `eigenpal runs get <run-id> --json` to inspect the agent run payload.");
|
|
191822
|
+
}
|
|
191823
|
+
if (isWorkflowRun(run)) {
|
|
191824
|
+
const steps = filterWorkflowSteps(run.stepExecutions, opts.step);
|
|
191825
|
+
const projectionIncludes = workflowStepProjectionIncludes(opts.include);
|
|
191826
|
+
if (opts.step) {
|
|
191827
|
+
return printJson({
|
|
191828
|
+
...payload,
|
|
191829
|
+
run: { ...run, stepExecutions: projectWorkflowSteps(steps, projectionIncludes) }
|
|
191830
|
+
});
|
|
191831
|
+
}
|
|
191832
|
+
if (opts.json) {
|
|
191833
|
+
return printJson(payload);
|
|
191834
|
+
}
|
|
191835
|
+
console.log(renderFrame(toExecutionSnapshot(run, executionId)));
|
|
191836
|
+
process.stderr.write(`${ui.dim(`use --json for the full payload, then pipe to jq for field projection`)}
|
|
191837
|
+
`);
|
|
191838
|
+
return;
|
|
191839
|
+
}
|
|
191840
|
+
renderRunPayload(payload, opts);
|
|
191841
|
+
}
|
|
191842
|
+
function isWorkflowRun(run) {
|
|
191843
|
+
return Array.isArray(run.stepExecutions);
|
|
191844
|
+
}
|
|
191845
|
+
function isAgentRun(run) {
|
|
191846
|
+
return run.type === "agent";
|
|
191847
|
+
}
|
|
191848
|
+
function ensureAgentRun(run, feature) {
|
|
191849
|
+
if (isAgentRun(run))
|
|
191850
|
+
return;
|
|
191851
|
+
throw new Error(`${feature} is only available for agent runs. Workflow runs do not have agent artifacts, feedback, or expected files.`);
|
|
191852
|
+
}
|
|
191853
|
+
async function getRunForCompatibilityCheck(client, executionId) {
|
|
191854
|
+
const payload = await client.get(`/api/v1/runs/${encodeURIComponent(executionId)}`, {
|
|
191855
|
+
include: "detail"
|
|
191856
|
+
});
|
|
191857
|
+
if (!payload.run)
|
|
191858
|
+
throw new Error(`Run ${executionId} not found`);
|
|
191859
|
+
return payload.run;
|
|
191860
|
+
}
|
|
191861
|
+
async function ensureAgentRunById(client, executionId, feature) {
|
|
191862
|
+
ensureAgentRun(await getRunForCompatibilityCheck(client, executionId), feature);
|
|
191863
|
+
}
|
|
191864
|
+
function workflowSteps(value) {
|
|
191865
|
+
return Array.isArray(value) ? value : [];
|
|
191866
|
+
}
|
|
191867
|
+
function filterWorkflowSteps(value, stepFilter) {
|
|
191868
|
+
const steps = workflowSteps(value);
|
|
191869
|
+
if (!stepFilter)
|
|
191870
|
+
return steps;
|
|
191871
|
+
const wanted = new Set(stepFilter.split(",").map((step) => step.trim()).filter(Boolean));
|
|
191872
|
+
return steps.filter((step) => typeof step.stepName === "string" && wanted.has(step.stepName));
|
|
191873
|
+
}
|
|
191874
|
+
function projectWorkflowSteps(steps, includes) {
|
|
191875
|
+
return steps.map((step) => projectWorkflowStep(step, includes));
|
|
191876
|
+
}
|
|
191877
|
+
function workflowStepProjectionIncludes(includes) {
|
|
191878
|
+
if (!includes)
|
|
191879
|
+
return;
|
|
191880
|
+
const parts = new Set(includes.split(",").map((part) => part.trim()));
|
|
191881
|
+
const workflowFields = [
|
|
191882
|
+
"input",
|
|
191883
|
+
"config",
|
|
191884
|
+
"resolvedConfig",
|
|
191885
|
+
"inputRef",
|
|
191886
|
+
"inputData",
|
|
191887
|
+
"output",
|
|
191888
|
+
"outputData",
|
|
191889
|
+
"error",
|
|
191890
|
+
"duration"
|
|
191891
|
+
];
|
|
191892
|
+
return workflowFields.some((field) => parts.has(field)) ? includes : undefined;
|
|
191893
|
+
}
|
|
191894
|
+
function projectWorkflowStep(step, includes) {
|
|
191895
|
+
if (!includes)
|
|
191896
|
+
return step;
|
|
191897
|
+
const wanted = new Set(includes.split(",").map((part) => part.trim()));
|
|
191898
|
+
const out = { stepName: step.stepName, status: step.status };
|
|
191899
|
+
if (wanted.has("input") || wanted.has("config") || wanted.has("resolvedConfig")) {
|
|
191900
|
+
out.input = step.resolvedConfig;
|
|
191901
|
+
}
|
|
191902
|
+
if (wanted.has("inputRef") || wanted.has("inputData"))
|
|
191903
|
+
out.inputRef = step.inputData;
|
|
191904
|
+
if (wanted.has("output") || wanted.has("outputData"))
|
|
191905
|
+
out.output = step.outputData;
|
|
191906
|
+
if (wanted.has("error"))
|
|
191907
|
+
out.error = step.error;
|
|
191908
|
+
if (wanted.has("duration"))
|
|
191909
|
+
out.durationMs = step.durationMs;
|
|
191910
|
+
if ("overrideMode" in step)
|
|
191911
|
+
out.overrideMode = step.overrideMode;
|
|
191912
|
+
return out;
|
|
191913
|
+
}
|
|
191914
|
+
function toExecutionSnapshot(run, fallbackId) {
|
|
191915
|
+
return {
|
|
191916
|
+
id: String(run.id ?? run.executionId ?? fallbackId),
|
|
191917
|
+
status: String(run.status ?? "unknown"),
|
|
191918
|
+
startedAt: typeof run.startedAt === "string" ? run.startedAt : null,
|
|
191919
|
+
completedAt: typeof run.completedAt === "string" ? run.completedAt : null,
|
|
191920
|
+
durationMs: typeof run.durationMs === "number" ? run.durationMs : null,
|
|
191921
|
+
stepExecutions: workflowSteps(run.stepExecutions).map((step) => ({
|
|
191922
|
+
stepName: String(step.stepName ?? ""),
|
|
191923
|
+
status: String(step.status ?? "unknown"),
|
|
191924
|
+
durationMs: typeof step.durationMs === "number" ? step.durationMs : null,
|
|
191925
|
+
error: typeof step.error === "string" ? step.error : null,
|
|
191926
|
+
outputPreview: previewOutput(step.outputData)
|
|
191927
|
+
})),
|
|
191928
|
+
error: typeof run.error === "string" ? run.error : null
|
|
191929
|
+
};
|
|
191930
|
+
}
|
|
191931
|
+
var PREVIEW_MAX = 120;
|
|
191932
|
+
function previewOutput(value) {
|
|
191933
|
+
if (value == null)
|
|
191934
|
+
return null;
|
|
191935
|
+
if (typeof value === "string") {
|
|
191936
|
+
if (value.length === 0)
|
|
191937
|
+
return null;
|
|
191938
|
+
return value.length > PREVIEW_MAX ? `${value.slice(0, PREVIEW_MAX - 3)}...` : value;
|
|
191939
|
+
}
|
|
191940
|
+
let json2;
|
|
191941
|
+
try {
|
|
191942
|
+
json2 = JSON.stringify(value);
|
|
191943
|
+
} catch {
|
|
191944
|
+
return null;
|
|
191945
|
+
}
|
|
191946
|
+
if (!json2 || json2 === "{}" || json2 === "[]" || json2 === "null")
|
|
191947
|
+
return null;
|
|
191948
|
+
const flat = json2.replace(/\s+/g, " ");
|
|
191949
|
+
return flat.length > PREVIEW_MAX ? `${flat.slice(0, PREVIEW_MAX - 3)}...` : flat;
|
|
191950
|
+
}
|
|
191951
|
+
async function rerunRun(executionId, opts) {
|
|
191952
|
+
const client = buildClient(opts);
|
|
191953
|
+
const sourceRef = await resolveRerunSourceRef(client, executionId, opts.sourceRef);
|
|
191954
|
+
let payload = await client.post(`/api/v1/runs/${encodeURIComponent(executionId)}/rerun`, sourceRef ? { sourceRef } : {});
|
|
191955
|
+
const rerunId = String(payload.runId ?? payload.executionId ?? "");
|
|
191956
|
+
if (opts.wait && rerunId) {
|
|
191957
|
+
payload = await pollRun(client, rerunId, opts.interval, opts.maxWait);
|
|
191958
|
+
renderRunPayload(payload, opts);
|
|
191959
|
+
setExitCodeForFailedTerminalRun(payload);
|
|
191960
|
+
return;
|
|
191961
|
+
}
|
|
191962
|
+
if (opts.json)
|
|
191963
|
+
return printJson(payload);
|
|
191964
|
+
success2(`Started rerun ${ui.bold(rerunId)} from ${executionId}`);
|
|
191965
|
+
}
|
|
191966
|
+
async function resolveRerunSourceRef(client, executionId, requested) {
|
|
191967
|
+
if (!requested)
|
|
191968
|
+
return;
|
|
191969
|
+
if (requested !== "original")
|
|
191970
|
+
return requested;
|
|
191971
|
+
const payload = await client.get(`/api/v1/runs/${encodeURIComponent(executionId)}`, {
|
|
191972
|
+
include: "detail"
|
|
191973
|
+
});
|
|
191974
|
+
const run = payload.run;
|
|
191975
|
+
if (!run)
|
|
191976
|
+
throw new Error(`Run ${executionId} not found`);
|
|
191977
|
+
const resolved = stringOrNull(run.resolvedGitRef);
|
|
191978
|
+
const requestedSource = stringOrNull(run.requestedSourceRef);
|
|
191979
|
+
const original = resolved ?? requestedSource;
|
|
191980
|
+
if (!original) {
|
|
191981
|
+
throw new Error(`Run ${executionId} does not have an original source ref to reuse`);
|
|
191982
|
+
}
|
|
191983
|
+
return original;
|
|
191984
|
+
}
|
|
191985
|
+
async function fetchRunArtifacts(executionId, opts) {
|
|
191986
|
+
const client = buildClient(opts);
|
|
191987
|
+
const payload = await client.get(`/api/v1/runs/${encodeURIComponent(executionId)}`, {
|
|
191988
|
+
include: "detail,expected,files,input,output,issues,trace,lockfile,metadata"
|
|
191989
|
+
});
|
|
191990
|
+
const run = payload.run;
|
|
191991
|
+
if (!run)
|
|
191992
|
+
throw new Error(`Run ${executionId} not found`);
|
|
191993
|
+
ensureAgentRun(run, "Artifact download");
|
|
191994
|
+
const out = path8.resolve(opts.out ?? path8.join(".eigenpal", "artifacts", "runs", executionId));
|
|
191995
|
+
await fs3.mkdir(out, { recursive: true });
|
|
191996
|
+
const written = [];
|
|
191997
|
+
const skipped = [];
|
|
191998
|
+
const selectedPaths = opts.path && opts.path.length > 0 ? opts.path : runArtifactInventory(run).filter((artifact) => artifactIncluded(artifact, opts.include)).map((artifact) => artifact.path);
|
|
191999
|
+
for (const artifactPath of selectedPaths) {
|
|
192000
|
+
const normalized = normalizeArtifactPath(artifactPath);
|
|
192001
|
+
if (!normalized) {
|
|
192002
|
+
skipped.push(artifactPath);
|
|
192003
|
+
continue;
|
|
192004
|
+
}
|
|
192005
|
+
const result = await writeRunArtifact(client, executionId, out, normalized, run);
|
|
192006
|
+
if (result)
|
|
192007
|
+
written.push(result);
|
|
192008
|
+
else
|
|
192009
|
+
skipped.push(normalized);
|
|
192010
|
+
}
|
|
192011
|
+
const summary = {
|
|
192012
|
+
runId: executionId,
|
|
192013
|
+
out,
|
|
192014
|
+
written,
|
|
192015
|
+
skipped,
|
|
192016
|
+
counts: {
|
|
192017
|
+
written: written.length,
|
|
192018
|
+
skipped: skipped.length
|
|
192019
|
+
}
|
|
192020
|
+
};
|
|
192021
|
+
if (opts.json)
|
|
192022
|
+
return printJson(summary);
|
|
192023
|
+
success2(`Fetched run artifacts for ${executionId} to ${out}`);
|
|
192024
|
+
dim(`Wrote ${written.length} artifact${written.length === 1 ? "" : "s"}${skipped.length ? `; skipped missing ${skipped.join(", ")}` : ""}`);
|
|
192025
|
+
}
|
|
192026
|
+
async function listRunArtifacts(executionId, opts) {
|
|
192027
|
+
const client = buildClient(opts);
|
|
192028
|
+
const payload = await client.get(`/api/v1/runs/${encodeURIComponent(executionId)}`, {
|
|
192029
|
+
include: "detail,expected,files,input,output,issues,trace,lockfile,metadata"
|
|
192030
|
+
});
|
|
192031
|
+
const run = payload.run;
|
|
192032
|
+
if (!run)
|
|
192033
|
+
throw new Error(`Run ${executionId} not found`);
|
|
192034
|
+
ensureAgentRun(run, "Artifact inventory");
|
|
192035
|
+
const artifacts = runArtifactInventory(run);
|
|
192036
|
+
if (opts.json)
|
|
192037
|
+
return printJson({ runId: executionId, artifacts });
|
|
192038
|
+
console.log(table(artifacts, [
|
|
192039
|
+
{ key: "kind", header: "KIND" },
|
|
192040
|
+
{ key: "name", header: "NAME" },
|
|
192041
|
+
{ key: "path", header: "PATH" },
|
|
192042
|
+
{ key: "present", header: "PRESENT" }
|
|
192043
|
+
]));
|
|
192044
|
+
}
|
|
192045
|
+
function artifactIncluded(artifact, include) {
|
|
192046
|
+
const parts = new Set(include.split(",").map((part) => part.trim()).filter(Boolean));
|
|
192047
|
+
if (parts.has("all"))
|
|
192048
|
+
return true;
|
|
192049
|
+
if (parts.has(artifact.kind))
|
|
192050
|
+
return true;
|
|
192051
|
+
if (parts.has("files") && (artifact.kind === "input" || artifact.kind === "output"))
|
|
192052
|
+
return true;
|
|
192053
|
+
if (parts.has("metadata") && artifact.path === "run.json")
|
|
192054
|
+
return true;
|
|
192055
|
+
return false;
|
|
192056
|
+
}
|
|
192057
|
+
function normalizeArtifactPath(artifactPath) {
|
|
192058
|
+
const parts = artifactPath.split("/");
|
|
192059
|
+
if (artifactPath.startsWith("/") || parts.some((part) => part === "" || part === "." || part === "..")) {
|
|
192060
|
+
return null;
|
|
192061
|
+
}
|
|
192062
|
+
return artifactPath;
|
|
192063
|
+
}
|
|
192064
|
+
async function writeJsonRunArtifact(out, artifactPath, value) {
|
|
192065
|
+
await fs3.mkdir(path8.dirname(path8.join(out, artifactPath)), { recursive: true });
|
|
192066
|
+
await fs3.writeFile(path8.join(out, artifactPath), JSON.stringify(value, null, 2));
|
|
192067
|
+
}
|
|
192068
|
+
async function writeDownloadedRunArtifact(client, executionId, out, artifactPath, routePrefix) {
|
|
192069
|
+
const routePath = routePrefix === "files" ? artifactPath : artifactPath.slice("expected/".length);
|
|
192070
|
+
const routeBase = routePrefix === "files" ? "artifact" : "expected";
|
|
192071
|
+
const response = await client.getStream(`/api/v1/runs/${encodeURIComponent(executionId)}/${routeBase}/${encodeRunArtifactPath(routePath)}`);
|
|
192072
|
+
const outPath = path8.join(out, artifactPath);
|
|
192073
|
+
await fs3.mkdir(path8.dirname(outPath), { recursive: true });
|
|
192074
|
+
await fs3.writeFile(outPath, Buffer.from(await response.arrayBuffer()));
|
|
192075
|
+
}
|
|
192076
|
+
async function writeRunArtifact(client, executionId, out, artifactPath, run) {
|
|
192077
|
+
if (artifactPath === "run.json") {
|
|
192078
|
+
await writeJsonRunArtifact(out, artifactPath, run);
|
|
192079
|
+
return artifactPath;
|
|
192080
|
+
}
|
|
192081
|
+
if (artifactPath === "input.json") {
|
|
192082
|
+
if (run.inputJson == null)
|
|
192083
|
+
return null;
|
|
192084
|
+
await writeJsonRunArtifact(out, artifactPath, run.inputJson);
|
|
192085
|
+
return artifactPath;
|
|
192086
|
+
}
|
|
192087
|
+
if (artifactPath === "metadata.json") {
|
|
192088
|
+
if (run.metadata == null)
|
|
192089
|
+
return null;
|
|
192090
|
+
await writeJsonRunArtifact(out, artifactPath, run.metadata);
|
|
192091
|
+
return artifactPath;
|
|
192092
|
+
}
|
|
192093
|
+
if (artifactPath === "expected.json") {
|
|
192094
|
+
if (run.expected == null)
|
|
192095
|
+
return null;
|
|
192096
|
+
await writeJsonRunArtifact(out, artifactPath, run.expected);
|
|
192097
|
+
return artifactPath;
|
|
192098
|
+
}
|
|
192099
|
+
if (artifactPath.startsWith("expected/")) {
|
|
192100
|
+
await writeDownloadedRunArtifact(client, executionId, out, artifactPath, "expected");
|
|
192101
|
+
return artifactPath;
|
|
192102
|
+
}
|
|
192103
|
+
await writeDownloadedRunArtifact(client, executionId, out, artifactPath, "files");
|
|
192104
|
+
return artifactPath;
|
|
192105
|
+
}
|
|
192106
|
+
async function traceRun(executionId, opts) {
|
|
192107
|
+
const client = buildClient(opts);
|
|
192108
|
+
await ensureAgentRunById(client, executionId, "Trace download");
|
|
192109
|
+
const text = await downloadTraceText(client, executionId);
|
|
192110
|
+
if (!opts.out) {
|
|
192111
|
+
process.stdout.write(text);
|
|
192112
|
+
return;
|
|
192113
|
+
}
|
|
192114
|
+
const out = path8.resolve(opts.out);
|
|
192115
|
+
await fs3.mkdir(path8.dirname(out), { recursive: true });
|
|
192116
|
+
await fs3.writeFile(out, text);
|
|
192117
|
+
success2(`Downloaded trace for ${executionId} to ${ui.bold(out)}`);
|
|
192118
|
+
}
|
|
192119
|
+
async function compareRun(referenceId, executionId, opts) {
|
|
192120
|
+
const client = buildClient(opts);
|
|
192121
|
+
const targetPayload = await client.get(`/api/v1/runs/${encodeURIComponent(executionId)}`, {
|
|
192122
|
+
include: "detail,files,output"
|
|
192123
|
+
});
|
|
192124
|
+
const target = targetPayload.run;
|
|
192125
|
+
if (!target)
|
|
192126
|
+
throw new Error(`Run ${executionId} not found`);
|
|
192127
|
+
const mode = opts.baseline ? "baseline" : "expected";
|
|
192128
|
+
const reference = (await client.get(`/api/v1/runs/${encodeURIComponent(referenceId)}`, {
|
|
192129
|
+
include: mode === "baseline" ? "detail,files,output" : "detail,expected"
|
|
192130
|
+
})).run;
|
|
192131
|
+
if (!reference)
|
|
192132
|
+
throw new Error(`Reference run ${referenceId} not found`);
|
|
192133
|
+
if (isWorkflowRun(reference) && isWorkflowRun(target)) {
|
|
192134
|
+
const report2 = compareWorkflowRuns(referenceId, reference, executionId, target, opts.step);
|
|
192135
|
+
if (opts.json) {
|
|
192136
|
+
printJson(report2);
|
|
192137
|
+
} else {
|
|
192138
|
+
renderWorkflowComparisonReport(report2);
|
|
192139
|
+
}
|
|
192140
|
+
if (opts.failOnDiff && report2.status === "fail")
|
|
192141
|
+
process.exit(1);
|
|
192142
|
+
return;
|
|
192143
|
+
}
|
|
192144
|
+
if (opts.step) {
|
|
192145
|
+
throw new Error("`--step` is only supported when both runs are workflow runs. Agent runs do not have workflow steps.");
|
|
192146
|
+
}
|
|
192147
|
+
if (isWorkflowRun(reference) || isWorkflowRun(target)) {
|
|
192148
|
+
throw new Error("Mixed workflow/agent comparisons are not supported. Compare two workflow runs or two agent runs.");
|
|
192149
|
+
}
|
|
192150
|
+
const out = path8.resolve(opts.out ?? path8.join(".eigenpal", "artifacts", "comparisons", `${referenceId}..${executionId}`));
|
|
192151
|
+
await fs3.mkdir(out, { recursive: true });
|
|
192152
|
+
const warnings = [];
|
|
192153
|
+
if (mode === "expected" && reference.expected == null && fileNames(reference.expectedFiles).length === 0) {
|
|
192154
|
+
warnings.push(`Reference run ${referenceId} has no expected JSON or expected files; comparison has no baseline artifacts. Use --baseline to compare actual outputs.`);
|
|
192155
|
+
}
|
|
192156
|
+
const jsonDiffs = diffJson(mode === "baseline" ? reference.output : reference.expected, target.output);
|
|
192157
|
+
const expectedFiles = mode === "baseline" ? fileNames(reference.resultFiles).filter((name) => name !== "issues.md" && name !== "trace.jsonl") : fileNames(reference.expectedFiles);
|
|
192158
|
+
const outputFiles = fileNames(target.resultFiles).filter((name) => name !== "issues.md" && name !== "trace.jsonl");
|
|
192159
|
+
const inventory = compareFileInventory(expectedFiles, outputFiles, Boolean(opts.normalizeDates));
|
|
192160
|
+
const textDifferences = await compareMatchedFileText(client, { runId: referenceId, kind: mode === "baseline" ? "output" : "expected" }, { runId: executionId, kind: "output" }, out, inventory.matched, Boolean(opts.normalizeDates));
|
|
192161
|
+
const report = {
|
|
192162
|
+
status: jsonDiffs.length === 0 && inventory.missing.length === 0 && inventory.extra.length === 0 && textDifferences.every((diff) => diff.status === "match" || diff.status === "binary-match") ? "pass" : "fail",
|
|
192163
|
+
runId: executionId,
|
|
192164
|
+
comparedWithRunId: referenceId,
|
|
192165
|
+
mode,
|
|
192166
|
+
warnings,
|
|
192167
|
+
jsonDifferences: jsonDiffs,
|
|
192168
|
+
matchedFiles: inventory.matched,
|
|
192169
|
+
missingFiles: inventory.missing,
|
|
192170
|
+
extraFiles: inventory.extra,
|
|
192171
|
+
textDifferences
|
|
192172
|
+
};
|
|
192173
|
+
await fs3.writeFile(path8.join(out, "comparison.json"), JSON.stringify(report, null, 2));
|
|
192174
|
+
if (opts.json) {
|
|
192175
|
+
printJson(report);
|
|
192176
|
+
} else {
|
|
192177
|
+
renderComparisonReport(report);
|
|
192178
|
+
}
|
|
192179
|
+
dim(`Comparison artifacts written to ${ui.bold(out)}`);
|
|
192180
|
+
if (opts.failOnDiff && report.status === "fail")
|
|
192181
|
+
process.exit(1);
|
|
192182
|
+
}
|
|
192183
|
+
function compareWorkflowRuns(referenceId, reference, targetId, target, stepFilter) {
|
|
192184
|
+
const referenceSteps = workflowSteps(reference.stepExecutions);
|
|
192185
|
+
const targetSteps = workflowSteps(target.stepExecutions);
|
|
192186
|
+
const stepNames = new Set;
|
|
192187
|
+
for (const step of referenceSteps)
|
|
192188
|
+
if (step.stepName)
|
|
192189
|
+
stepNames.add(step.stepName);
|
|
192190
|
+
for (const step of targetSteps)
|
|
192191
|
+
if (step.stepName)
|
|
192192
|
+
stepNames.add(step.stepName);
|
|
192193
|
+
const wanted = stepFilter ? new Set(stepFilter.split(",").map((step) => step.trim()).filter(Boolean)) : null;
|
|
192194
|
+
const steps = [...stepNames].sort().filter((stepName) => !wanted || wanted.has(stepName)).map((stepName) => {
|
|
192195
|
+
const referenceStep = referenceSteps.find((step) => step.stepName === stepName);
|
|
192196
|
+
const targetStep = targetSteps.find((step) => step.stepName === stepName);
|
|
192197
|
+
const referenceDuration = typeof referenceStep?.durationMs === "number" ? referenceStep.durationMs : null;
|
|
192198
|
+
const targetDuration = typeof targetStep?.durationMs === "number" ? targetStep.durationMs : null;
|
|
192199
|
+
return {
|
|
192200
|
+
stepName,
|
|
192201
|
+
referenceStatus: String(referenceStep?.status ?? "-"),
|
|
192202
|
+
targetStatus: String(targetStep?.status ?? "-"),
|
|
192203
|
+
durationDeltaMs: referenceDuration != null && targetDuration != null ? targetDuration - referenceDuration : null,
|
|
192204
|
+
outputState: compareOutputs(referenceStep?.outputData, targetStep?.outputData)
|
|
192205
|
+
};
|
|
192206
|
+
});
|
|
192207
|
+
const finalOutputState = compareOutputs(reference.output, target.output);
|
|
192208
|
+
const status = finalOutputState === "identical" && steps.every((step) => step.referenceStatus === step.targetStatus && (step.outputState === "identical" || step.outputState === "-")) ? "pass" : "fail";
|
|
192209
|
+
return {
|
|
192210
|
+
status,
|
|
192211
|
+
runId: targetId,
|
|
192212
|
+
comparedWithRunId: referenceId,
|
|
192213
|
+
steps,
|
|
192214
|
+
finalOutputState
|
|
192215
|
+
};
|
|
192216
|
+
}
|
|
192217
|
+
function renderWorkflowComparisonReport(report) {
|
|
192218
|
+
console.log(`${ui.dim("A =")} ${ui.bold(report.comparedWithRunId)} ${ui.dim("B =")} ${ui.bold(report.runId)}`);
|
|
192219
|
+
console.log("");
|
|
192220
|
+
console.log(ui.dim(`step A status B status Δ duration output`));
|
|
192221
|
+
console.log(ui.dim(`------------------------ --------- --------- ---------- ------`));
|
|
192222
|
+
for (const step of report.steps) {
|
|
192223
|
+
const delta = step.durationDeltaMs == null ? "-" : `${step.durationDeltaMs >= 0 ? "+" : ""}${step.durationDeltaMs}ms`;
|
|
192224
|
+
console.log(`${step.stepName.padEnd(28)} ${padColored(colorStatus(step.referenceStatus), step.referenceStatus, 11)} ${padColored(colorStatus(step.targetStatus), step.targetStatus, 11)} ${delta.padEnd(12)} ${colorOutputState(step.outputState)}`);
|
|
192225
|
+
}
|
|
192226
|
+
console.log("");
|
|
192227
|
+
console.log(`${ui.dim("final output:")} ${colorOutputState(report.finalOutputState)}`);
|
|
192228
|
+
}
|
|
192229
|
+
function padColored(colored, plain, width) {
|
|
192230
|
+
const pad = Math.max(0, width - plain.length);
|
|
192231
|
+
return colored + " ".repeat(pad);
|
|
192232
|
+
}
|
|
192233
|
+
function colorStatus(status) {
|
|
192234
|
+
if (!status)
|
|
192235
|
+
return "-";
|
|
192236
|
+
if (status === "completed")
|
|
192237
|
+
return ui.ok(status);
|
|
192238
|
+
if (status === "failed" || status === "cancelled")
|
|
192239
|
+
return ui.err(status);
|
|
192240
|
+
if (status === "running" || status === "pending")
|
|
192241
|
+
return ui.info(status);
|
|
192242
|
+
return status;
|
|
192243
|
+
}
|
|
192244
|
+
function colorOutputState(state) {
|
|
192245
|
+
if (state === "identical")
|
|
192246
|
+
return ui.ok(state);
|
|
192247
|
+
if (state === "-")
|
|
192248
|
+
return ui.dim(state);
|
|
192249
|
+
if (state.startsWith("differs"))
|
|
192250
|
+
return ui.warn(state);
|
|
192251
|
+
return state;
|
|
192252
|
+
}
|
|
192253
|
+
function compareOutputs(reference, target) {
|
|
192254
|
+
if (reference === undefined && target === undefined)
|
|
192255
|
+
return "-";
|
|
192256
|
+
if (reference === undefined)
|
|
192257
|
+
return "B only";
|
|
192258
|
+
if (target === undefined)
|
|
192259
|
+
return "A only";
|
|
192260
|
+
const referenceJson = JSON.stringify(reference);
|
|
192261
|
+
const targetJson = JSON.stringify(target);
|
|
192262
|
+
if (referenceJson === targetJson)
|
|
192263
|
+
return "identical";
|
|
192264
|
+
return `differs (A=${referenceJson.length}b, B=${targetJson.length}b)`;
|
|
192265
|
+
}
|
|
192266
|
+
async function listRuns(source, opts) {
|
|
192267
|
+
const client = buildClient(opts);
|
|
192268
|
+
const parsedAgentTarget = source && (opts.type === "agent" || source.startsWith("agents.") || source.includes("@")) ? parseAgentTarget(source) : null;
|
|
192269
|
+
const resolvedWorkflowSource = source && opts.type === "workflow" ? await resolveWorkflowId(client, source) : undefined;
|
|
192270
|
+
const params = {
|
|
192271
|
+
...buildRunListParams(opts),
|
|
192272
|
+
...parsedAgentTarget ? { type: opts.type ?? "agent", source: parsedAgentTarget.slug } : resolvedWorkflowSource ? { source: resolvedWorkflowSource } : source ? { source } : {},
|
|
192273
|
+
...parsedAgentTarget?.sourceRef ? { sourceRef: parsedAgentTarget.sourceRef } : {}
|
|
192274
|
+
};
|
|
192275
|
+
const payload = await client.get("/api/v1/runs", params);
|
|
192276
|
+
const rows = opts.compact ? payload.runs.map(compactRunRow) : payload.runs;
|
|
192277
|
+
if (opts.json)
|
|
192278
|
+
return printJson({ ...payload, runs: rows });
|
|
192279
|
+
if (payload.scanLimited) {
|
|
192280
|
+
warn("Feedback/expected filters scanned only the first matching window. Increase --scan-limit or narrow DB filters for a fuller result.");
|
|
192281
|
+
}
|
|
192282
|
+
if (payload.noResolvedAnchor) {
|
|
192283
|
+
warn("No resolved feedback anchor was found in the scan window; --since-last-resolved returned no rows.");
|
|
192284
|
+
}
|
|
192285
|
+
console.log(table(rows, [
|
|
192286
|
+
{ key: "id", header: "ID" },
|
|
192287
|
+
{ key: "type", header: "TYPE" },
|
|
192288
|
+
{ key: "status", header: "STATUS" },
|
|
192289
|
+
{
|
|
192290
|
+
key: "sourceName",
|
|
192291
|
+
header: "SOURCE",
|
|
192292
|
+
format: (value, row) => String(value ?? row.sourceId ?? "")
|
|
192293
|
+
},
|
|
192294
|
+
{ key: "exampleId", header: "EXAMPLE" },
|
|
192295
|
+
{
|
|
192296
|
+
key: "feedback",
|
|
192297
|
+
header: "FEEDBACK",
|
|
192298
|
+
format: (value) => value && typeof value === "object" ? `${String(value.rating ?? "")}/${String(value.status ?? "")}` : ""
|
|
192299
|
+
},
|
|
192300
|
+
{ key: "createdAt", header: "CREATED", format: formatTimestamp }
|
|
192301
|
+
]));
|
|
192302
|
+
}
|
|
192303
|
+
function buildRunListParams(opts) {
|
|
192304
|
+
return compactParams({
|
|
192305
|
+
type: "type" in opts ? opts.type : undefined,
|
|
192306
|
+
sourceRef: "sourceRef" in opts ? opts.sourceRef : undefined,
|
|
192307
|
+
status: "status" in opts ? opts.status : undefined,
|
|
192308
|
+
batchId: "batchId" in opts ? opts.batchId : undefined,
|
|
192309
|
+
exampleId: "exampleId" in opts ? opts.exampleId : undefined,
|
|
192310
|
+
exampleIdContains: "exampleIdContains" in opts ? opts.exampleIdContains : undefined,
|
|
192311
|
+
from: "from" in opts ? opts.from : undefined,
|
|
192312
|
+
to: "to" in opts ? opts.to : undefined,
|
|
192313
|
+
createdAfter: "createdAfter" in opts ? opts.createdAfter : undefined,
|
|
192314
|
+
createdBefore: "createdBefore" in opts ? opts.createdBefore : undefined,
|
|
192315
|
+
completedAfter: "completedAfter" in opts ? opts.completedAfter : undefined,
|
|
192316
|
+
completedBefore: "completedBefore" in opts ? opts.completedBefore : undefined,
|
|
192317
|
+
limit: "limit" in opts ? opts.limit : undefined,
|
|
192318
|
+
offset: "offset" in opts ? opts.offset : undefined,
|
|
192319
|
+
sort: "sort" in opts ? opts.sort : undefined,
|
|
192320
|
+
order: "order" in opts ? opts.order : undefined
|
|
192321
|
+
});
|
|
192322
|
+
}
|
|
192323
|
+
async function updateRunFeedback(executionId, opts) {
|
|
192324
|
+
const client = buildClient(opts);
|
|
192325
|
+
await ensureAgentRunById(client, executionId, "Feedback");
|
|
192326
|
+
const body = {};
|
|
192327
|
+
if (opts.status)
|
|
192328
|
+
body.status = opts.status;
|
|
192329
|
+
if (opts.rating)
|
|
192330
|
+
body.rating = opts.rating === "none" ? null : opts.rating;
|
|
192331
|
+
if (opts.clearRating)
|
|
192332
|
+
body.rating = null;
|
|
192333
|
+
if (opts.messageFile)
|
|
192334
|
+
body.body = await fs3.readFile(opts.messageFile, "utf-8");
|
|
192335
|
+
if (opts.message !== undefined)
|
|
192336
|
+
body.body = opts.message;
|
|
192337
|
+
if (opts.clearMessage)
|
|
192338
|
+
body.body = "";
|
|
192339
|
+
if (opts.expectedJsonFile)
|
|
192340
|
+
body.expected = JSON.parse(await fs3.readFile(opts.expectedJsonFile, "utf-8"));
|
|
192341
|
+
if (opts.expectedJson !== undefined)
|
|
192342
|
+
body.expected = JSON.parse(opts.expectedJson);
|
|
192343
|
+
if (opts.clearExpectedJson)
|
|
192344
|
+
body.expected = null;
|
|
192345
|
+
const payload = await client.patch(`/api/v1/runs/${encodeURIComponent(executionId)}/feedback`, body);
|
|
192346
|
+
renderGeneric(payload, opts, `Updated feedback for ${executionId}`);
|
|
192347
|
+
}
|
|
192348
|
+
function compactRunRow(run) {
|
|
192349
|
+
const feedback = run.feedback && typeof run.feedback === "object" ? run.feedback : null;
|
|
192350
|
+
return {
|
|
192351
|
+
id: run.id,
|
|
192352
|
+
status: run.status,
|
|
192353
|
+
exampleId: run.exampleId,
|
|
192354
|
+
feedback: feedback ? {
|
|
192355
|
+
rating: feedback.rating ?? null,
|
|
192356
|
+
status: feedback.status ?? null,
|
|
192357
|
+
updatedAt: feedback.updatedAt ?? null
|
|
192358
|
+
} : null,
|
|
192359
|
+
hasExpectedJson: run.expected != null,
|
|
192360
|
+
expectedFileCount: Array.isArray(run.expectedFiles) ? run.expectedFiles.length : undefined,
|
|
192361
|
+
createdAt: run.createdAt,
|
|
192362
|
+
completedAt: run.completedAt
|
|
192363
|
+
};
|
|
192364
|
+
}
|
|
192365
|
+
async function clearRunFeedback(executionId, opts) {
|
|
192366
|
+
if (!(opts.yes || await confirmTyped(executionId, "clear feedback artifacts"))) {
|
|
192367
|
+
throw new Error("Clear cancelled");
|
|
192368
|
+
}
|
|
192369
|
+
const client = buildClient(opts);
|
|
192370
|
+
await ensureAgentRunById(client, executionId, "Feedback");
|
|
192371
|
+
const payload = await client.delete(`/api/v1/runs/${encodeURIComponent(executionId)}/feedback`);
|
|
192372
|
+
renderGeneric(payload, opts, `Cleared feedback for ${executionId}`);
|
|
192373
|
+
}
|
|
192374
|
+
async function listRunExpected(executionId, opts) {
|
|
192375
|
+
const client = buildClient(opts);
|
|
192376
|
+
await ensureAgentRunById(client, executionId, "Expected artifacts");
|
|
192377
|
+
const payload = await client.get(`/api/v1/runs/${encodeURIComponent(executionId)}/expected`);
|
|
192378
|
+
if (opts.json)
|
|
192379
|
+
return printJson(payload);
|
|
192380
|
+
console.log(table(payload.files ?? [], [{ key: "name", header: "NAME" }]));
|
|
192381
|
+
if (payload.expected != null)
|
|
192382
|
+
dim("expected.json present");
|
|
192383
|
+
}
|
|
192384
|
+
async function pullRunExpected(executionId, opts) {
|
|
192385
|
+
const client = buildClient(opts);
|
|
192386
|
+
await ensureAgentRunById(client, executionId, "Expected artifacts");
|
|
192387
|
+
const payload = await client.get(`/api/v1/runs/${encodeURIComponent(executionId)}/expected`);
|
|
192388
|
+
const out = path8.resolve(opts.out ?? path8.join(executionId, "expected"));
|
|
192389
|
+
await fs3.mkdir(out, { recursive: true });
|
|
192390
|
+
if (payload.expected != null) {
|
|
192391
|
+
await fs3.writeFile(path8.join(out, "expected.json"), JSON.stringify(payload.expected, null, 2));
|
|
192392
|
+
}
|
|
192393
|
+
await downloadExpectedFiles(client, executionId, out, payload.files);
|
|
192394
|
+
success2(`Pulled expected artifacts for ${executionId} to ${out}`);
|
|
192395
|
+
}
|
|
192396
|
+
async function downloadExpectedFiles(client, executionId, out, files) {
|
|
192397
|
+
const rows = Array.isArray(files) ? files : [];
|
|
192398
|
+
const written = await Promise.all(rows.map(async (file2) => {
|
|
192399
|
+
const name = String(file2.name ?? "");
|
|
192400
|
+
if (!name || name.includes("/") || name.includes(".."))
|
|
192401
|
+
return null;
|
|
192402
|
+
const response = await client.getStream(`/api/v1/runs/${encodeURIComponent(executionId)}/expected/${encodeURIComponent(name)}`);
|
|
192403
|
+
await fs3.mkdir(out, { recursive: true });
|
|
192404
|
+
await fs3.writeFile(path8.join(out, name), Buffer.from(await response.arrayBuffer()));
|
|
192405
|
+
return name;
|
|
192406
|
+
}));
|
|
192407
|
+
return written.filter((name) => Boolean(name));
|
|
192408
|
+
}
|
|
192409
|
+
async function uploadRunExpected(executionId, file2, opts) {
|
|
192410
|
+
const client = buildClient(opts);
|
|
192411
|
+
await ensureAgentRunById(client, executionId, "Expected artifacts");
|
|
192412
|
+
const form = new FormData;
|
|
192413
|
+
const data = await fs3.readFile(file2);
|
|
192414
|
+
form.append("file", new Blob([data]), path8.basename(file2));
|
|
192415
|
+
if (opts.name)
|
|
192416
|
+
form.append("name", opts.name);
|
|
192417
|
+
const payload = await client.postFormData(`/api/v1/runs/${encodeURIComponent(executionId)}/expected`, form);
|
|
192418
|
+
renderGeneric(payload, opts, `Uploaded expected file for ${executionId}`);
|
|
192419
|
+
}
|
|
192420
|
+
async function copyOutputToExpected(executionId, outputFile, opts) {
|
|
192421
|
+
const client = buildClient(opts);
|
|
192422
|
+
await ensureAgentRunById(client, executionId, "Expected artifacts");
|
|
192423
|
+
const payload = await client.post(`/api/v1/runs/${encodeURIComponent(executionId)}/expected`, {
|
|
192424
|
+
outputFileName: outputFile,
|
|
192425
|
+
...opts.name ? { expectedName: opts.name } : {}
|
|
192426
|
+
});
|
|
192427
|
+
renderGeneric(payload, opts, `Copied output file to expected for ${executionId}`);
|
|
192428
|
+
}
|
|
192429
|
+
async function renameRunExpected(executionId, oldName, newName, opts) {
|
|
192430
|
+
const client = buildClient(opts);
|
|
192431
|
+
await ensureAgentRunById(client, executionId, "Expected artifacts");
|
|
192432
|
+
const payload = await client.patch(`/api/v1/runs/${encodeURIComponent(executionId)}/expected/${encodeURIComponent(oldName)}`, { name: newName });
|
|
192433
|
+
renderGeneric(payload, opts, `Renamed expected file for ${executionId}`);
|
|
192434
|
+
}
|
|
192435
|
+
async function deleteRunExpected(executionId, name, opts) {
|
|
192436
|
+
if (!(opts.yes || await confirmTyped(name, "delete expected file"))) {
|
|
192437
|
+
throw new Error("Delete cancelled");
|
|
192438
|
+
}
|
|
192439
|
+
const client = buildClient(opts);
|
|
192440
|
+
await ensureAgentRunById(client, executionId, "Expected artifacts");
|
|
192441
|
+
await client.delete(`/api/v1/runs/${encodeURIComponent(executionId)}/expected/${encodeURIComponent(name)}`);
|
|
192442
|
+
renderGeneric({ ok: true }, opts, `Deleted expected file ${name}`);
|
|
192443
|
+
}
|
|
192444
|
+
async function watchRunCommand(executionId, opts) {
|
|
192445
|
+
const client = buildClient(opts);
|
|
192446
|
+
if (opts.json) {
|
|
192447
|
+
const payload = await pollRun(client, executionId, opts.interval, opts.maxWait);
|
|
192448
|
+
renderRunPayload(payload, opts);
|
|
192449
|
+
setExitCodeForFailedTerminalRun(payload);
|
|
192450
|
+
return;
|
|
192451
|
+
}
|
|
192452
|
+
const result = await watchExecution({
|
|
192453
|
+
fetch: async () => {
|
|
192454
|
+
const payload = await client.get(`/api/v1/runs/${encodeURIComponent(executionId)}`, {
|
|
192455
|
+
include: "detail"
|
|
192456
|
+
});
|
|
192457
|
+
return toExecutionSnapshot(payload.run ?? {}, executionId);
|
|
192458
|
+
},
|
|
192459
|
+
maxMs: opts.maxWait * 1000,
|
|
192460
|
+
transitionIntervalMs: opts.interval * 1000,
|
|
192461
|
+
steadyIntervalMs: Math.max(opts.interval, 5) * 1000
|
|
192462
|
+
});
|
|
192463
|
+
if (result.detached) {
|
|
192464
|
+
process.stderr.write(`
|
|
192465
|
+
Detached after ${opts.maxWait}s without reaching terminal status. Re-run \`eigenpal runs watch ${executionId}\` to resume.
|
|
192466
|
+
`);
|
|
192467
|
+
process.exit(2);
|
|
192468
|
+
}
|
|
192469
|
+
if (result.final.status === "failed" || result.final.status === "cancelled") {
|
|
192470
|
+
process.exitCode = 1;
|
|
192471
|
+
}
|
|
192472
|
+
}
|
|
192473
|
+
async function cancelRun(executionId, opts) {
|
|
192474
|
+
if (!(opts.yes || process.stdin.isTTY))
|
|
192475
|
+
throw new Error("Pass --yes to cancel in non-interactive mode");
|
|
192476
|
+
const client = buildClient(opts);
|
|
192477
|
+
const payload = await client.post(`/api/v1/runs/${encodeURIComponent(executionId)}/cancel`, {});
|
|
192478
|
+
renderRunPayload(payload, opts);
|
|
192479
|
+
}
|
|
192480
|
+
async function resumeRun(executionId, opts) {
|
|
192481
|
+
const client = buildClient(opts);
|
|
192482
|
+
const payload = await client.post(`/api/v1/runs/${encodeURIComponent(executionId)}/resume`, {});
|
|
192483
|
+
renderRunPayload(payload, opts);
|
|
192484
|
+
}
|
|
192485
|
+
async function downloadTraceText(client, executionId) {
|
|
192486
|
+
const response = await client.getStream(`/api/v1/runs/${encodeURIComponent(executionId)}/artifact/trace.jsonl`);
|
|
192487
|
+
return Buffer.from(await response.arrayBuffer()).toString("utf-8");
|
|
192488
|
+
}
|
|
192489
|
+
function diffJson(expected, actual, basePath = "$") {
|
|
192490
|
+
if (expected === undefined || expected === null)
|
|
192491
|
+
return [];
|
|
192492
|
+
if (Object.is(expected, actual))
|
|
192493
|
+
return [];
|
|
192494
|
+
if (expected && actual && typeof expected === "object" && typeof actual === "object" && !Array.isArray(expected) && !Array.isArray(actual)) {
|
|
192495
|
+
const keys = new Set([...Object.keys(expected), ...Object.keys(actual)]);
|
|
192496
|
+
return [...keys].flatMap((key) => {
|
|
192497
|
+
const next = `${basePath}.${key}`;
|
|
192498
|
+
if (!(key in actual))
|
|
192499
|
+
return [{ path: next, type: "missing" }];
|
|
192500
|
+
if (!(key in expected))
|
|
192501
|
+
return [{ path: next, type: "extra" }];
|
|
192502
|
+
return diffJson(expected[key], actual[key], next);
|
|
192503
|
+
});
|
|
192504
|
+
}
|
|
192505
|
+
if (Array.isArray(expected) && Array.isArray(actual)) {
|
|
192506
|
+
const diffs = [];
|
|
192507
|
+
const max = Math.max(expected.length, actual.length);
|
|
192508
|
+
for (let index = 0;index < max; index += 1) {
|
|
192509
|
+
const next = `${basePath}[${index}]`;
|
|
192510
|
+
if (index >= actual.length)
|
|
192511
|
+
diffs.push({ path: next, type: "missing" });
|
|
192512
|
+
else if (index >= expected.length)
|
|
192513
|
+
diffs.push({ path: next, type: "extra" });
|
|
192514
|
+
else
|
|
192515
|
+
diffs.push(...diffJson(expected[index], actual[index], next));
|
|
192516
|
+
}
|
|
192517
|
+
return diffs;
|
|
192518
|
+
}
|
|
192519
|
+
return [
|
|
192520
|
+
{
|
|
192521
|
+
path: basePath,
|
|
192522
|
+
type: "changed",
|
|
192523
|
+
expected: JSON.stringify(expected),
|
|
192524
|
+
actual: JSON.stringify(actual)
|
|
192525
|
+
}
|
|
192526
|
+
];
|
|
192527
|
+
}
|
|
192528
|
+
function fileNames(files) {
|
|
192529
|
+
return (Array.isArray(files) ? files : []).map((file2) => String(file2.name ?? "")).filter(Boolean).sort();
|
|
192530
|
+
}
|
|
192531
|
+
function stringOrNull(value) {
|
|
192532
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
192533
|
+
}
|
|
192534
|
+
function runArtifactInventory(run) {
|
|
192535
|
+
const rows = [
|
|
192536
|
+
{ kind: "metadata", name: "run.json", path: "run.json", present: "yes" }
|
|
192537
|
+
];
|
|
192538
|
+
if (run.inputJson != null) {
|
|
192539
|
+
rows.push({ kind: "input", name: "input.json", path: "input.json", present: "yes" });
|
|
192540
|
+
}
|
|
192541
|
+
if (run.metadata != null) {
|
|
192542
|
+
rows.push({ kind: "metadata", name: "metadata.json", path: "metadata.json", present: "yes" });
|
|
192543
|
+
}
|
|
192544
|
+
if (run.expected != null) {
|
|
192545
|
+
rows.push({ kind: "expected", name: "expected.json", path: "expected.json", present: "yes" });
|
|
192546
|
+
}
|
|
192547
|
+
for (const name of fileNames(run.inputFiles)) {
|
|
192548
|
+
rows.push({ kind: "input", name, path: `input/${name}`, present: "yes" });
|
|
192549
|
+
}
|
|
192550
|
+
for (const name of fileNames(run.resultFiles).filter((name2) => name2 !== "issues.md" && name2 !== "trace.jsonl")) {
|
|
192551
|
+
rows.push({ kind: "output", name, path: `output/${name}`, present: "yes" });
|
|
192552
|
+
}
|
|
192553
|
+
for (const name of fileNames(run.expectedFiles)) {
|
|
192554
|
+
rows.push({ kind: "expected", name, path: `expected/${name}`, present: "yes" });
|
|
192555
|
+
}
|
|
192556
|
+
for (const name of fileNames(run.issueFiles)) {
|
|
192557
|
+
rows.push({ kind: "issues", name, path: name, present: "yes" });
|
|
192558
|
+
}
|
|
192559
|
+
for (const name of fileNames(run.traceFiles)) {
|
|
192560
|
+
rows.push({ kind: "trace", name, path: name, present: "yes" });
|
|
192561
|
+
}
|
|
192562
|
+
for (const name of fileNames(run.lockfileFiles)) {
|
|
192563
|
+
rows.push({ kind: "lockfile", name, path: name, present: "yes" });
|
|
192564
|
+
}
|
|
192565
|
+
return rows.sort((a, b2) => a.path.localeCompare(b2.path));
|
|
192566
|
+
}
|
|
192567
|
+
function normalizeGeneratedTokens(value) {
|
|
192568
|
+
return value.replace(/\d{4}-\d{2}-\d{2}/g, "<date>").replace(/\d{8}/g, "<date>");
|
|
192569
|
+
}
|
|
192570
|
+
function compareFileInventory(expected, output, normalizeDates) {
|
|
192571
|
+
const normalize = (value) => normalizeDates ? normalizeGeneratedTokens(value) : value;
|
|
192572
|
+
const outputByNormalized = new Map(output.map((name) => [normalize(name), name]));
|
|
192573
|
+
const expectedByNormalized = new Map(expected.map((name) => [normalize(name), name]));
|
|
192574
|
+
const matched = expected.map((expectedName2) => {
|
|
192575
|
+
const outputName = outputByNormalized.get(normalize(expectedName2));
|
|
192576
|
+
return outputName ? { expected: expectedName2, output: outputName } : null;
|
|
192577
|
+
}).filter((match) => Boolean(match));
|
|
192578
|
+
return {
|
|
192579
|
+
matched,
|
|
192580
|
+
missing: expected.filter((name) => !outputByNormalized.has(normalize(name))),
|
|
192581
|
+
extra: output.filter((name) => !expectedByNormalized.has(normalize(name)))
|
|
192582
|
+
};
|
|
192583
|
+
}
|
|
192584
|
+
async function compareMatchedFileText(client, left, right, out, matched, normalizeDates) {
|
|
192585
|
+
const expectedDir = path8.join(out, "expected");
|
|
192586
|
+
const outputDir = path8.join(out, "output");
|
|
192587
|
+
await fs3.mkdir(expectedDir, { recursive: true });
|
|
192588
|
+
await fs3.mkdir(outputDir, { recursive: true });
|
|
192589
|
+
const diffs = [];
|
|
192590
|
+
for (const pair of matched) {
|
|
192591
|
+
const expectedPath = path8.join(expectedDir, pair.expected);
|
|
192592
|
+
const outputPath = path8.join(outputDir, pair.output);
|
|
192593
|
+
await downloadStreamToFile(client, runFileUrl(left, pair.expected), expectedPath);
|
|
192594
|
+
await downloadStreamToFile(client, runFileUrl(right, pair.output), outputPath);
|
|
192595
|
+
const expectedText = extractComparableText(expectedPath);
|
|
192596
|
+
const outputText = extractComparableText(outputPath);
|
|
192597
|
+
if (expectedText.text == null || outputText.text == null) {
|
|
192598
|
+
const [expectedBuffer, outputBuffer] = await Promise.all([
|
|
192599
|
+
fs3.readFile(expectedPath),
|
|
192600
|
+
fs3.readFile(outputPath)
|
|
192601
|
+
]);
|
|
192602
|
+
const expectedHash = createHash2("sha256").update(expectedBuffer).digest("hex");
|
|
192603
|
+
const outputHash = createHash2("sha256").update(outputBuffer).digest("hex");
|
|
192604
|
+
diffs.push({
|
|
192605
|
+
...pair,
|
|
192606
|
+
status: expectedBuffer.length === outputBuffer.length && expectedHash === outputHash ? "binary-match" : "binary-different",
|
|
192607
|
+
expectedBytes: expectedBuffer.length,
|
|
192608
|
+
outputBytes: outputBuffer.length,
|
|
192609
|
+
expectedSha256: expectedHash,
|
|
192610
|
+
outputSha256: outputHash,
|
|
192611
|
+
textExtraction: {
|
|
192612
|
+
expected: expectedText.reason ?? null,
|
|
192613
|
+
output: outputText.reason ?? null
|
|
192614
|
+
}
|
|
192615
|
+
});
|
|
192616
|
+
continue;
|
|
192617
|
+
}
|
|
192618
|
+
const normalize = (value) => normalizeDates ? normalizeGeneratedTokens(value).trim() : value.trim();
|
|
192619
|
+
diffs.push({
|
|
192620
|
+
...pair,
|
|
192621
|
+
status: normalize(expectedText.text) === normalize(outputText.text) ? "match" : "text-different"
|
|
192622
|
+
});
|
|
192623
|
+
}
|
|
192624
|
+
return diffs;
|
|
192625
|
+
}
|
|
192626
|
+
function runFileUrl(side, name) {
|
|
192627
|
+
const runId = encodeURIComponent(side.runId);
|
|
192628
|
+
const filename = encodeURIComponent(name);
|
|
192629
|
+
return side.kind === "expected" ? `/api/v1/runs/${runId}/expected/${filename}` : `/api/v1/runs/${runId}/artifact/output/${filename}`;
|
|
192630
|
+
}
|
|
192631
|
+
function encodeRunArtifactPath(artifactPath) {
|
|
192632
|
+
return artifactPath.split("/").map(encodeURIComponent).join("/");
|
|
192633
|
+
}
|
|
192634
|
+
async function downloadStreamToFile(client, url2, target) {
|
|
192635
|
+
await fs3.mkdir(path8.dirname(target), { recursive: true });
|
|
192636
|
+
const response = await client.getStream(url2);
|
|
192637
|
+
await fs3.writeFile(target, Buffer.from(await response.arrayBuffer()));
|
|
192638
|
+
}
|
|
192639
|
+
function extractComparableText(file2) {
|
|
192640
|
+
const ext = path8.extname(file2).toLowerCase();
|
|
192641
|
+
if ([".txt", ".md", ".json", ".csv", ".xml"].includes(ext)) {
|
|
192642
|
+
return { text: readFileSync7(file2, "utf8") };
|
|
192643
|
+
}
|
|
192644
|
+
if (ext === ".pdf") {
|
|
192645
|
+
const result = spawnSync4("pdftotext", [file2, "-"], { encoding: "utf8" });
|
|
192646
|
+
return result.status === 0 ? { text: result.stdout } : { text: null, reason: textExtractionFallbackReason("pdftotext", result) };
|
|
192647
|
+
}
|
|
192648
|
+
if (ext === ".docx") {
|
|
192649
|
+
const script = "import re,sys,zipfile; z=zipfile.ZipFile(sys.argv[1]); x=z.read('word/document.xml').decode('utf-8','ignore'); print(re.sub('<[^>]+>',' ',x))";
|
|
192650
|
+
const result = spawnSync4("python3", ["-c", script, file2], { encoding: "utf8" });
|
|
192651
|
+
return result.status === 0 ? { text: result.stdout } : { text: null, reason: textExtractionFallbackReason("python3", result) };
|
|
192652
|
+
}
|
|
192653
|
+
return { text: null, reason: `No text extractor for ${ext || "extensionless"} files` };
|
|
192654
|
+
}
|
|
192655
|
+
function textExtractionFallbackReason(tool, result) {
|
|
192656
|
+
if (result.error)
|
|
192657
|
+
return `${tool} unavailable; compared bytes instead`;
|
|
192658
|
+
return `${tool} could not extract text; compared bytes instead`;
|
|
192659
|
+
}
|
|
192660
|
+
function renderComparisonReport(report) {
|
|
192661
|
+
const header = `Comparison ${report.status}`;
|
|
192662
|
+
if (report.status === "pass") {
|
|
192663
|
+
success2(header);
|
|
192664
|
+
} else {
|
|
192665
|
+
error51(header);
|
|
192666
|
+
}
|
|
192667
|
+
for (const warning of report.warnings ?? []) {
|
|
192668
|
+
dim(`warning: ${warning}`);
|
|
192669
|
+
}
|
|
192670
|
+
console.log(table([
|
|
192671
|
+
{ item: "JSON differences", count: report.jsonDifferences.length },
|
|
192672
|
+
{ item: "Matched files", count: report.matchedFiles.length },
|
|
192673
|
+
{ item: "Missing files", count: report.missingFiles.length },
|
|
192674
|
+
{ item: "Extra files", count: report.extraFiles.length },
|
|
192675
|
+
{
|
|
192676
|
+
item: "Text differences",
|
|
192677
|
+
count: report.textDifferences.filter((diff) => diff.status === "text-different").length
|
|
192678
|
+
}
|
|
192679
|
+
], [
|
|
192680
|
+
{ key: "item", header: "ITEM" },
|
|
192681
|
+
{ key: "count", header: "COUNT" }
|
|
192682
|
+
]));
|
|
192683
|
+
for (const diff of report.textDifferences) {
|
|
192684
|
+
const textExtraction = diff.textExtraction;
|
|
192685
|
+
const reasons = [textExtraction?.expected, textExtraction?.output].filter((value) => typeof value === "string" && value.length > 0).filter((value, index, values) => values.indexOf(value) === index);
|
|
192686
|
+
for (const reason of reasons) {
|
|
192687
|
+
dim(`warning: ${String(diff.expected)} vs ${String(diff.output)}: ${reason}`);
|
|
192688
|
+
}
|
|
192689
|
+
}
|
|
192690
|
+
}
|
|
192691
|
+
|
|
192692
|
+
// src/commands/agents/dataset.ts
|
|
192693
|
+
import path10 from "node:path";
|
|
192694
|
+
|
|
191473
192695
|
// src/commands/agents/validation.ts
|
|
191474
192696
|
init_src();
|
|
191475
192697
|
var import_yaml3 = __toESM(require_dist4(), 1);
|
|
191476
|
-
import { existsSync as existsSync7, promises as
|
|
191477
|
-
import
|
|
192698
|
+
import { existsSync as existsSync7, promises as fs4 } from "node:fs";
|
|
192699
|
+
import path9 from "node:path";
|
|
191478
192700
|
async function validateAgentProject(root) {
|
|
191479
192701
|
const errors3 = [];
|
|
191480
192702
|
const warnings = [];
|
|
191481
192703
|
for (const [oldName] of LEGACY_LAYOUTS) {
|
|
191482
|
-
if (existsSync7(
|
|
192704
|
+
if (existsSync7(path9.join(root, oldName))) {
|
|
191483
192705
|
errors3.push(`Legacy layout ${oldName}/ is removed; use Git source under agents/<slug>/`);
|
|
191484
192706
|
}
|
|
191485
192707
|
}
|
|
191486
|
-
if (existsSync7(
|
|
192708
|
+
if (existsSync7(path9.join(root, "agent.yaml"))) {
|
|
191487
192709
|
errors3.push("Legacy agent.yaml layout is removed; use eigenpal.yaml in a Git-backed package");
|
|
191488
192710
|
}
|
|
191489
|
-
if (existsSync7(
|
|
192711
|
+
if (existsSync7(path9.join(root, "agent"))) {
|
|
191490
192712
|
errors3.push("Legacy agent/ directory is removed; use Git source (eigenpal agents clone)");
|
|
191491
192713
|
}
|
|
191492
|
-
const manifestPath =
|
|
192714
|
+
const manifestPath = path9.join(root, PACKAGE_MANIFEST);
|
|
191493
192715
|
if (!existsSync7(manifestPath)) {
|
|
191494
192716
|
errors3.push(`Missing ${PACKAGE_MANIFEST}`);
|
|
191495
192717
|
} else {
|
|
191496
192718
|
try {
|
|
191497
|
-
SourcePackageManifestSchema.parse(import_yaml3.default.parse(await
|
|
192719
|
+
SourcePackageManifestSchema.parse(import_yaml3.default.parse(await fs4.readFile(manifestPath, "utf8")));
|
|
191498
192720
|
} catch (err) {
|
|
191499
192721
|
errors3.push(err instanceof Error ? err.message : String(err));
|
|
191500
192722
|
}
|
|
191501
192723
|
}
|
|
191502
|
-
if (!existsSync7(
|
|
192724
|
+
if (!existsSync7(path9.join(root, "AGENT.md")))
|
|
191503
192725
|
warnings.push("Missing AGENT.md");
|
|
191504
|
-
if (!existsSync7(
|
|
192726
|
+
if (!existsSync7(path9.join(root, DATASET_DIR)))
|
|
191505
192727
|
warnings.push(`Missing ${DATASET_DIR}/`);
|
|
191506
192728
|
for (const filename of SCHEMA_FILENAMES) {
|
|
191507
|
-
const schemaPath =
|
|
192729
|
+
const schemaPath = path9.join(root, filename);
|
|
191508
192730
|
if (!existsSync7(schemaPath))
|
|
191509
192731
|
continue;
|
|
191510
|
-
const validation2 = validateWorkspaceSchema(await
|
|
192732
|
+
const validation2 = validateWorkspaceSchema(await fs4.readFile(schemaPath, "utf8"), filename);
|
|
191511
192733
|
for (const issue2 of validation2.errors) {
|
|
191512
192734
|
errors3.push(`${filename}: ${issue2}`);
|
|
191513
192735
|
}
|
|
@@ -191519,7 +192741,7 @@ async function validateDatasetDir(dir, opts = {}) {
|
|
|
191519
192741
|
if (!existsSync7(dir))
|
|
191520
192742
|
errors3.push(`Missing dataset directory: ${dir}`);
|
|
191521
192743
|
for (const [oldName, newName] of LEGACY_LAYOUTS) {
|
|
191522
|
-
if (
|
|
192744
|
+
if (path9.basename(dir) === oldName) {
|
|
191523
192745
|
errors3.push(`Use ${newName}/ instead of legacy ${oldName}/`);
|
|
191524
192746
|
}
|
|
191525
192747
|
}
|
|
@@ -191530,28 +192752,28 @@ async function validateDatasetDir(dir, opts = {}) {
|
|
|
191530
192752
|
const outputSchema = await loadOptionalWorkspaceSchema(agentDir, "output-schema.json", errors3);
|
|
191531
192753
|
if (errors3.length > 0)
|
|
191532
192754
|
return { valid: false, errors: errors3 };
|
|
191533
|
-
const entries = await
|
|
192755
|
+
const entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
191534
192756
|
const examples = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
191535
192757
|
for (const example of examples) {
|
|
191536
|
-
const exampleRoot =
|
|
192758
|
+
const exampleRoot = path9.join(dir, example);
|
|
191537
192759
|
if (inputSchema) {
|
|
191538
192760
|
errors3.push(...await validateAgentDatasetInputExample(example, exampleRoot, inputSchema));
|
|
191539
192761
|
} else {
|
|
191540
|
-
await validateJsonObjectIfPresent(
|
|
192762
|
+
await validateJsonObjectIfPresent(path9.join(exampleRoot, AGENT_EXAMPLE_INPUT_JSON), `${example}/${AGENT_EXAMPLE_INPUT_JSON}`, errors3);
|
|
191541
192763
|
}
|
|
191542
192764
|
if (outputSchema) {
|
|
191543
192765
|
errors3.push(...await validateAgentDatasetExpectedExample(example, exampleRoot, outputSchema));
|
|
191544
192766
|
} else {
|
|
191545
|
-
await validateJsonObjectIfPresent(
|
|
192767
|
+
await validateJsonObjectIfPresent(path9.join(exampleRoot, AGENT_EXAMPLE_EXPECTED_JSON), `${example}/${AGENT_EXAMPLE_EXPECTED_JSON}`, errors3);
|
|
191546
192768
|
}
|
|
191547
192769
|
}
|
|
191548
192770
|
return { valid: errors3.length === 0, errors: errors3 };
|
|
191549
192771
|
}
|
|
191550
192772
|
async function loadOptionalWorkspaceSchema(agentDir, filename, errors3) {
|
|
191551
|
-
const schemaPath =
|
|
192773
|
+
const schemaPath = path9.join(agentDir, filename);
|
|
191552
192774
|
if (!existsSync7(schemaPath))
|
|
191553
192775
|
return null;
|
|
191554
|
-
const raw = await
|
|
192776
|
+
const raw = await fs4.readFile(schemaPath, "utf8");
|
|
191555
192777
|
const validation2 = validateWorkspaceSchema(raw, filename);
|
|
191556
192778
|
for (const issue2 of validation2.errors)
|
|
191557
192779
|
errors3.push(`${filename}: ${issue2}`);
|
|
@@ -191586,8 +192808,8 @@ async function validateAgentDatasetInputExample(example, exampleRoot, inputSchem
|
|
|
191586
192808
|
const errors3 = [];
|
|
191587
192809
|
const props = schemaProperties(inputSchema);
|
|
191588
192810
|
const required2 = schemaRequired(inputSchema);
|
|
191589
|
-
const inputDir =
|
|
191590
|
-
const inputFiles = existsSync7(inputDir) ? (await
|
|
192811
|
+
const inputDir = path9.join(exampleRoot, "input");
|
|
192812
|
+
const inputFiles = existsSync7(inputDir) ? (await fs4.readdir(inputDir, { withFileTypes: true })).filter((entry) => entry.isFile()).map((entry) => entry.name) : [];
|
|
191591
192813
|
const matchedFiles = new Set;
|
|
191592
192814
|
for (const [fieldName, propSchema] of Object.entries(props)) {
|
|
191593
192815
|
if (!isFileField(propSchema))
|
|
@@ -191600,7 +192822,7 @@ async function validateAgentDatasetInputExample(example, exampleRoot, inputSchem
|
|
|
191600
192822
|
}
|
|
191601
192823
|
}
|
|
191602
192824
|
const dataFields = Object.entries(props).filter(([, propSchema]) => !isFileField(propSchema)).map(([fieldName]) => fieldName);
|
|
191603
|
-
const inputJsonPath =
|
|
192825
|
+
const inputJsonPath = path9.join(exampleRoot, AGENT_EXAMPLE_INPUT_JSON);
|
|
191604
192826
|
let inputData = null;
|
|
191605
192827
|
if (existsSync7(inputJsonPath)) {
|
|
191606
192828
|
inputData = await readJsonObject(inputJsonPath, `${example}/${AGENT_EXAMPLE_INPUT_JSON}`, errors3);
|
|
@@ -191640,9 +192862,9 @@ async function validateAgentDatasetInputExample(example, exampleRoot, inputSchem
|
|
|
191640
192862
|
async function validateAgentDatasetExpectedExample(example, exampleRoot, outputSchema) {
|
|
191641
192863
|
const errors3 = [];
|
|
191642
192864
|
const props = schemaProperties(outputSchema);
|
|
191643
|
-
const expectedPath =
|
|
191644
|
-
const expectedDir =
|
|
191645
|
-
const goldenFiles = existsSync7(expectedDir) ? (await
|
|
192865
|
+
const expectedPath = path9.join(exampleRoot, AGENT_EXAMPLE_EXPECTED_JSON);
|
|
192866
|
+
const expectedDir = path9.join(exampleRoot, "expected");
|
|
192867
|
+
const goldenFiles = existsSync7(expectedDir) ? (await fs4.readdir(expectedDir, { withFileTypes: true })).filter((entry) => entry.isFile() && !entry.name.startsWith(".")).map((entry) => entry.name) : [];
|
|
191646
192868
|
let expected = null;
|
|
191647
192869
|
if (existsSync7(expectedPath)) {
|
|
191648
192870
|
expected = await readJsonObject(expectedPath, `${example}/${AGENT_EXAMPLE_EXPECTED_JSON}`, errors3);
|
|
@@ -191690,7 +192912,7 @@ async function validateJsonObjectIfPresent(filePath, label, errors3) {
|
|
|
191690
192912
|
}
|
|
191691
192913
|
async function readJsonObject(filePath, label, errors3) {
|
|
191692
192914
|
try {
|
|
191693
|
-
const parsed = JSON.parse(await
|
|
192915
|
+
const parsed = JSON.parse(await fs4.readFile(filePath, "utf8"));
|
|
191694
192916
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
191695
192917
|
errors3.push(`${label}: must be a JSON object`);
|
|
191696
192918
|
return null;
|
|
@@ -191754,7 +192976,7 @@ async function pushDataset(agentId, opts) {
|
|
|
191754
192976
|
if (opts.mode === "replace" && !(opts.yes || await confirmTyped(agentId, "replace dataset"))) {
|
|
191755
192977
|
throw new Error("Dataset replace aborted");
|
|
191756
192978
|
}
|
|
191757
|
-
const files = await readFilesUnder(
|
|
192979
|
+
const files = await readFilesUnder(path10.resolve(opts.file));
|
|
191758
192980
|
const client = buildClient(opts);
|
|
191759
192981
|
const payload = await client.post(`/api/v1/agents/${encodeURIComponent(agentId)}/dataset?mode=${opts.mode}`, { files });
|
|
191760
192982
|
if (opts.json)
|
|
@@ -191766,15 +192988,15 @@ async function pullDataset(agentId, opts) {
|
|
|
191766
192988
|
const payload = await client.get(`/api/v1/agents/${encodeURIComponent(agentId)}/dataset`, {
|
|
191767
192989
|
include: "files"
|
|
191768
192990
|
});
|
|
191769
|
-
const out =
|
|
192991
|
+
const out = path10.resolve(opts.out ?? DATASET_DIR);
|
|
191770
192992
|
for (const file2 of payload.files) {
|
|
191771
|
-
await writeBase64File(
|
|
192993
|
+
await writeBase64File(path10.join(out, file2.path), file2.contentBase64);
|
|
191772
192994
|
}
|
|
191773
192995
|
success2(`Pulled dataset to ${ui.bold(out)}`);
|
|
191774
192996
|
}
|
|
191775
192997
|
async function validateDatasetCommand(dir = DATASET_DIR, opts) {
|
|
191776
|
-
const result = await validateDatasetDir(
|
|
191777
|
-
agentDir:
|
|
192998
|
+
const result = await validateDatasetDir(path10.resolve(dir), {
|
|
192999
|
+
agentDir: path10.resolve(opts.agentDir ?? ".")
|
|
191778
193000
|
});
|
|
191779
193001
|
if (opts.json)
|
|
191780
193002
|
return printJson(result);
|
|
@@ -191788,28 +193010,8 @@ async function validateDatasetCommand(dir = DATASET_DIR, opts) {
|
|
|
191788
193010
|
// src/commands/agents/env.ts
|
|
191789
193011
|
init_src();
|
|
191790
193012
|
var import_yaml4 = __toESM(require_dist4(), 1);
|
|
191791
|
-
import { existsSync as existsSync8, promises as
|
|
191792
|
-
import
|
|
191793
|
-
|
|
191794
|
-
// src/commands/agents/target.ts
|
|
191795
|
-
init_src();
|
|
191796
|
-
function parseAgentTarget(target) {
|
|
191797
|
-
const [rawTarget, rawRef, extra] = target.split("@");
|
|
191798
|
-
if (extra !== undefined)
|
|
191799
|
-
throw new Error("Agent target must be <slug>[@ref].");
|
|
191800
|
-
const normalizedTarget = rawTarget.includes(".") ? target : `agents.${rawTarget}${rawRef !== undefined ? `@${rawRef}` : ""}`;
|
|
191801
|
-
const parsed = parseAutomationTarget(normalizedTarget);
|
|
191802
|
-
if (parsed.type !== "agents") {
|
|
191803
|
-
throw new Error("Only agent targets are supported in this release. Use agents.<slug>[@ref].");
|
|
191804
|
-
}
|
|
191805
|
-
return {
|
|
191806
|
-
packageName: parsed.packageName,
|
|
191807
|
-
slug: parsed.slug,
|
|
191808
|
-
sourceRef: rawRef !== undefined ? parsed.ref : undefined
|
|
191809
|
-
};
|
|
191810
|
-
}
|
|
191811
|
-
|
|
191812
|
-
// src/commands/agents/env.ts
|
|
193013
|
+
import { existsSync as existsSync8, promises as fs5, readFileSync as readFileSync8 } from "node:fs";
|
|
193014
|
+
import path11 from "node:path";
|
|
191813
193015
|
function registerEnvCommands(agent) {
|
|
191814
193016
|
const env2 = agent.command("env").description("Export encrypted source secrets for a local installed agent package.").action(() => {
|
|
191815
193017
|
process.stderr.write("`eigenpal agents env` requires a subcommand. Run `eigenpal agents env --help`.\n");
|
|
@@ -191830,7 +193032,7 @@ async function pullAgentEnv(target, opts) {
|
|
|
191830
193032
|
throw new Error("--format must be shell or dotenv");
|
|
191831
193033
|
}
|
|
191832
193034
|
const client = buildClient(opts);
|
|
191833
|
-
const requests = target ? await collectEncryptedSecretsForTarget(client, target) : await collectEncryptedSecrets(
|
|
193035
|
+
const requests = target ? await collectEncryptedSecretsForTarget(client, target) : await collectEncryptedSecrets(path11.resolve(opts.dir ?? "."));
|
|
191834
193036
|
if (requests.length === 0)
|
|
191835
193037
|
return;
|
|
191836
193038
|
const payload = await client.post("/api/v1/source/secrets/decrypt", {
|
|
@@ -191893,14 +193095,14 @@ async function readOptionalSourceFile(client, query) {
|
|
|
191893
193095
|
}
|
|
191894
193096
|
}
|
|
191895
193097
|
async function collectEncryptedSecrets(root) {
|
|
191896
|
-
const roots = [root, ...await listModulePackageRoots(
|
|
193098
|
+
const roots = [root, ...await listModulePackageRoots(path11.join(root, "eigenpal_modules"))];
|
|
191897
193099
|
const seen = new Set;
|
|
191898
193100
|
const requests = [];
|
|
191899
193101
|
for (const packageRoot of roots) {
|
|
191900
|
-
const secretsPath =
|
|
193102
|
+
const secretsPath = path11.join(packageRoot, SOURCE_SECRETS_FILENAME);
|
|
191901
193103
|
if (!existsSync8(secretsPath))
|
|
191902
193104
|
continue;
|
|
191903
|
-
const parsed = SourceSecretsFileSchema.parse(import_yaml4.default.parse(await
|
|
193105
|
+
const parsed = SourceSecretsFileSchema.parse(import_yaml4.default.parse(await fs5.readFile(secretsPath, "utf8")));
|
|
191904
193106
|
const sourcePath = sourcePathForInstalledPackage(root, packageRoot);
|
|
191905
193107
|
for (const [secretName, entry] of Object.entries(parsed.secrets)) {
|
|
191906
193108
|
if (!entry.encrypted)
|
|
@@ -191923,14 +193125,14 @@ async function listModulePackageRoots(modulesRoot) {
|
|
|
191923
193125
|
if (!existsSync8(modulesRoot))
|
|
191924
193126
|
return [];
|
|
191925
193127
|
const result = [];
|
|
191926
|
-
for (const type of await
|
|
191927
|
-
const typeRoot =
|
|
191928
|
-
const typeStat = await
|
|
193128
|
+
for (const type of await fs5.readdir(modulesRoot)) {
|
|
193129
|
+
const typeRoot = path11.join(modulesRoot, type);
|
|
193130
|
+
const typeStat = await fs5.stat(typeRoot).catch(() => null);
|
|
191929
193131
|
if (!typeStat?.isDirectory())
|
|
191930
193132
|
continue;
|
|
191931
|
-
for (const slug of await
|
|
191932
|
-
const packageRoot =
|
|
191933
|
-
const packageStat = await
|
|
193133
|
+
for (const slug of await fs5.readdir(typeRoot)) {
|
|
193134
|
+
const packageRoot = path11.join(typeRoot, slug);
|
|
193135
|
+
const packageStat = await fs5.stat(packageRoot).catch(() => null);
|
|
191934
193136
|
if (packageStat?.isDirectory())
|
|
191935
193137
|
result.push(packageRoot);
|
|
191936
193138
|
}
|
|
@@ -191938,16 +193140,16 @@ async function listModulePackageRoots(modulesRoot) {
|
|
|
191938
193140
|
return result;
|
|
191939
193141
|
}
|
|
191940
193142
|
function sourcePathForInstalledPackage(root, packageRoot) {
|
|
191941
|
-
const sourceRelative =
|
|
193143
|
+
const sourceRelative = path11.relative(root, packageRoot).replace(/\\/g, "/");
|
|
191942
193144
|
if (!sourceRelative.startsWith("..") && SourcePackagePathSchema.safeParse(sourceRelative).success) {
|
|
191943
193145
|
return sourceRelative;
|
|
191944
193146
|
}
|
|
191945
|
-
const relative =
|
|
193147
|
+
const relative = path11.relative(path11.join(root, "eigenpal_modules"), packageRoot).replace(/\\/g, "/");
|
|
191946
193148
|
if (!relative.startsWith(".."))
|
|
191947
193149
|
return relative;
|
|
191948
|
-
const lockfilePath =
|
|
193150
|
+
const lockfilePath = path11.join(root, ".eigenpal", "eigenpal.lock");
|
|
191949
193151
|
if (packageRoot === root && existsSync8(lockfilePath)) {
|
|
191950
|
-
const lockfile = JSON.parse(
|
|
193152
|
+
const lockfile = JSON.parse(readFileSync8(lockfilePath, "utf8"));
|
|
191951
193153
|
if (typeof lockfile.root?.packagePath === "string")
|
|
191952
193154
|
return lockfile.root.packagePath;
|
|
191953
193155
|
}
|
|
@@ -191961,7 +193163,7 @@ function dotenvQuote(value) {
|
|
|
191961
193163
|
}
|
|
191962
193164
|
|
|
191963
193165
|
// src/commands/agents/experiments.ts
|
|
191964
|
-
import { promises as
|
|
193166
|
+
import { promises as fs6 } from "node:fs";
|
|
191965
193167
|
function registerExperimentCommands(agent) {
|
|
191966
193168
|
const experiment = agent.command("experiment").description("Run and inspect batches of agent executions.").action(() => {
|
|
191967
193169
|
process.stderr.write("`eigenpal agents experiment` requires a subcommand. Run `eigenpal agents experiment --help`.\n");
|
|
@@ -192000,7 +193202,7 @@ async function experimentResults(agentId, batchId, opts) {
|
|
|
192000
193202
|
const payload = await client.get(`/api/v1/agents/${encodeURIComponent(agentId)}/experiments/${encodeURIComponent(selected)}`);
|
|
192001
193203
|
const content = opts.format === "json" ? JSON.stringify(payload, null, 2) : toCsv(payload.runs, ["id", "status", "exampleId"]);
|
|
192002
193204
|
if (opts.out) {
|
|
192003
|
-
await
|
|
193205
|
+
await fs6.writeFile(opts.out, content);
|
|
192004
193206
|
success2(`Wrote ${opts.out}`);
|
|
192005
193207
|
} else {
|
|
192006
193208
|
process.stdout.write(`${content}
|
|
@@ -192037,9 +193239,9 @@ async function cancelExperiment(agentId, batchId, opts) {
|
|
|
192037
193239
|
}
|
|
192038
193240
|
|
|
192039
193241
|
// src/commands/agents/files.ts
|
|
192040
|
-
import { createHash as
|
|
192041
|
-
import { promises as
|
|
192042
|
-
import
|
|
193242
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
193243
|
+
import { promises as fs7 } from "node:fs";
|
|
193244
|
+
import path12 from "node:path";
|
|
192043
193245
|
function registerAgentFileCommands(agent) {
|
|
192044
193246
|
const file2 = agent.command("file").description("List, download, and compare individual live agent files.").action(() => {
|
|
192045
193247
|
process.stderr.write("`eigenpal agents file` requires a subcommand. Run `eigenpal agents file --help`.\n");
|
|
@@ -192065,19 +193267,19 @@ async function getAgentFile(agentId, remotePath, opts) {
|
|
|
192065
193267
|
});
|
|
192066
193268
|
if (opts.json)
|
|
192067
193269
|
return printJson(payload);
|
|
192068
|
-
const out =
|
|
193270
|
+
const out = path12.resolve(opts.out ?? payload.path);
|
|
192069
193271
|
await writeBase64File(out, payload.contentBase64);
|
|
192070
193272
|
success2(`Downloaded ${payload.path} to ${ui.bold(out)}`);
|
|
192071
193273
|
}
|
|
192072
193274
|
async function diffAgentFile(agentId, remotePath, localPath, opts) {
|
|
192073
|
-
const report = await buildAgentFileDiff(agentId, remotePath,
|
|
193275
|
+
const report = await buildAgentFileDiff(agentId, remotePath, path12.resolve(localPath), opts);
|
|
192074
193276
|
if (opts.json)
|
|
192075
193277
|
return printJson(report);
|
|
192076
193278
|
renderAgentFileDiff(report);
|
|
192077
193279
|
}
|
|
192078
193280
|
async function buildAgentFileDiff(agentId, remotePath, localPath, opts) {
|
|
192079
193281
|
const client = buildClient(opts);
|
|
192080
|
-
const local = await
|
|
193282
|
+
const local = await fs7.readFile(localPath);
|
|
192081
193283
|
let remote = null;
|
|
192082
193284
|
let serverPath = remotePath;
|
|
192083
193285
|
try {
|
|
@@ -192090,8 +193292,8 @@ async function buildAgentFileDiff(agentId, remotePath, localPath, opts) {
|
|
|
192090
193292
|
if (!(err instanceof ApiError) || err.status !== 404)
|
|
192091
193293
|
throw err;
|
|
192092
193294
|
}
|
|
192093
|
-
const localHash =
|
|
192094
|
-
const remoteHash = remote ?
|
|
193295
|
+
const localHash = createHash3("sha256").update(local).digest("hex");
|
|
193296
|
+
const remoteHash = remote ? createHash3("sha256").update(remote).digest("hex") : null;
|
|
192095
193297
|
const status = !remote ? "remote-missing" : localHash === remoteHash ? "match" : "different";
|
|
192096
193298
|
return {
|
|
192097
193299
|
agentId,
|
|
@@ -192145,713 +193347,6 @@ function buildTextDiffPreview(remoteText, localText) {
|
|
|
192145
193347
|
return preview;
|
|
192146
193348
|
}
|
|
192147
193349
|
|
|
192148
|
-
// src/commands/agents/runs.ts
|
|
192149
|
-
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
192150
|
-
import { createHash as createHash3 } from "node:crypto";
|
|
192151
|
-
import { promises as fs7, readFileSync as readFileSync8 } from "node:fs";
|
|
192152
|
-
import path12 from "node:path";
|
|
192153
|
-
|
|
192154
|
-
// src/commands/agents/run-form-data.ts
|
|
192155
|
-
import { promises as fs6 } from "node:fs";
|
|
192156
|
-
import path11 from "node:path";
|
|
192157
|
-
async function buildAgentExecutionRunFormData(inputFile, inputJson) {
|
|
192158
|
-
const form = new FormData;
|
|
192159
|
-
for (const spec of parseAgentInputFileSpecs(inputFile)) {
|
|
192160
|
-
const data = await fs6.readFile(spec.filePath);
|
|
192161
|
-
form.append(spec.fieldName, new Blob([data]), path11.basename(spec.filePath));
|
|
192162
|
-
}
|
|
192163
|
-
if (inputJson)
|
|
192164
|
-
form.append("_json", inputJson);
|
|
192165
|
-
return form;
|
|
192166
|
-
}
|
|
192167
|
-
function parseAgentInputFileSpecs(inputFile) {
|
|
192168
|
-
const values = Array.isArray(inputFile) ? inputFile : [inputFile];
|
|
192169
|
-
return values.map((value) => {
|
|
192170
|
-
const separatorIndex = value.indexOf("=");
|
|
192171
|
-
if (separatorIndex === -1) {
|
|
192172
|
-
return { fieldName: "file", filePath: value };
|
|
192173
|
-
}
|
|
192174
|
-
const fieldName = value.slice(0, separatorIndex).trim();
|
|
192175
|
-
const filePath = value.slice(separatorIndex + 1).trim();
|
|
192176
|
-
if (!fieldName || !filePath) {
|
|
192177
|
-
throw new Error("--input-file must be <field=path> or a bare path");
|
|
192178
|
-
}
|
|
192179
|
-
if (fieldName === "_json" || fieldName === "_metadata" || fieldName === "input") {
|
|
192180
|
-
throw new Error(`--input-file field "${fieldName}" is reserved`);
|
|
192181
|
-
}
|
|
192182
|
-
return { fieldName, filePath };
|
|
192183
|
-
});
|
|
192184
|
-
}
|
|
192185
|
-
|
|
192186
|
-
// src/commands/agents/runs.ts
|
|
192187
|
-
function registerRunCommands(agent) {
|
|
192188
|
-
const runs = agent.command("runs").description("Inspect, watch, and manage agent runs.").action(() => {
|
|
192189
|
-
process.stderr.write("`eigenpal agents runs` requires a subcommand. Run `eigenpal agents runs --help`.\n");
|
|
192190
|
-
process.exit(2);
|
|
192191
|
-
});
|
|
192192
|
-
const listRunsCmd = addJsonFlag(withPagination(withBaseUrl(runs.command("list <target>")), 50)).description("List runs for an agent target; unqualified targets include all source refs.").option("--status <status>", "Filter by run status").option("--include <items>", "Comma-separated include list").option("--compact", "Render compact run rows").option("--sort <field>", "Sort field").option("--order <asc|desc>", "Sort order");
|
|
192193
|
-
listRunsCmd.action(action(listRunsTarget));
|
|
192194
|
-
addJsonFlag(withBaseUrl(runs.command("get <run-id>"))).description("Get one agent run.").option("--include <parts>", "Comma-separated extra parts: feedback,expected,files,trace,issues", "feedback").action(action(getRun));
|
|
192195
|
-
addJsonFlag(withBaseUrl(runs.command("compare <reference-run-id> <run-id>"))).description("Compare one run against another run. PDF/DOCX text comparison uses pdftotext/python3 and reports byte fallbacks.").option("--baseline", "Compare actual outputs from both runs instead of expected artifacts").option("--out <dir>", "Write comparison artifacts to this directory").option("--normalize-dates", "Normalize YYYYMMDD and YYYY-MM-DD tokens in filenames/text").option("--fail-on-diff", "Exit 1 when comparison status is fail").action(action(compareRun));
|
|
192196
|
-
const artifacts = runs.command("artifacts").description("Inspect run artifact inventory.").action(() => {
|
|
192197
|
-
process.stderr.write("`eigenpal agents runs artifacts` requires a subcommand. Run `eigenpal agents runs artifacts --help`.\n");
|
|
192198
|
-
process.exit(2);
|
|
192199
|
-
});
|
|
192200
|
-
addJsonFlag(withBaseUrl(artifacts.command("list <run-id>"))).description("List available run artifacts without downloading them.").action(action(listRunArtifacts));
|
|
192201
|
-
withBaseUrl(artifacts.command("fetch <run-id>")).description("Download run artifacts by canonical artifact path.").option("--out <dir>", "Output directory").option("--include <parts>", "Comma-separated parts: output,input,metadata,issues,trace,lockfile,expected,all", "all").option("--path <path>", "Fetch one exact artifact path from `artifacts list`; repeatable", collectRepeated, []).option("--json", "Output a JSON summary of written artifacts").action(action(fetchRunArtifacts));
|
|
192202
|
-
withBaseUrl(runs.command("trace <run-id>")).description("Print raw trace.jsonl for a run, or write it with --out.").option("--out <file>", "Output file path").action(action(traceRun));
|
|
192203
|
-
registerRunFeedbackCommands(runs);
|
|
192204
|
-
registerRunExpectedCommands(runs);
|
|
192205
|
-
addJsonFlag(withBaseUrl(runs.command("watch <run-id>"))).description("Watch a run until it reaches a terminal status.").option("--interval <seconds>", "Polling interval in seconds", intArg, 2).option("--max-wait <seconds>", "Maximum wait before exiting 2", intArg, 1800).action(action(watchRunCommand));
|
|
192206
|
-
addJsonFlag(withBaseUrl(runs.command("cancel <run-id>"))).description("Cancel an agent run.").option("--yes", "Required in non-interactive environments").action(action(cancelRun));
|
|
192207
|
-
}
|
|
192208
|
-
function registerRunFeedbackCommands(runs) {
|
|
192209
|
-
const feedback = runs.command("feedback").description("Update or clear feedback attached to an agent run.").action(() => {
|
|
192210
|
-
process.stderr.write("`eigenpal agents runs feedback` requires a subcommand. Run `eigenpal agents runs feedback --help`.\n");
|
|
192211
|
-
process.exit(2);
|
|
192212
|
-
});
|
|
192213
|
-
addJsonFlag(withBaseUrl(feedback.command("update <run-id>"))).description("Edit feedback state, rating, message, or expected JSON for a run.").option("--status <open|resolved|ignored>", "Set feedback status").option("--rating <pass|fail|partial|none>", "Set feedback rating").option("--message <text>", "Set feedback message body").option("--message-file <path>", "Read feedback message body from a file").option("--expected-json <json>", "Set structured expected JSON").option("--expected-json-file <path>", "Read structured expected JSON from a file").option("--clear-message", "Clear the feedback message body").option("--clear-rating", "Clear feedback rating").option("--clear-expected-json", "Delete structured expected JSON").action(action(updateRunFeedback));
|
|
192214
|
-
addJsonFlag(withBaseUrl(feedback.command("resolve <run-id>"))).description("Mark run feedback as resolved.").option("--message <text>", "Set feedback message body").option("--message-file <path>", "Read feedback message body from a file").action(action((runId, opts) => updateRunFeedback(runId, { ...opts, status: "resolved" })));
|
|
192215
|
-
addJsonFlag(withBaseUrl(feedback.command("clear <run-id>"))).description("Delete feedback, expected.json, and expected files for a run.").option("--yes", "Required in non-interactive environments").action(action(clearRunFeedback));
|
|
192216
|
-
}
|
|
192217
|
-
function registerRunExpectedCommands(runs) {
|
|
192218
|
-
const expected = runs.command("expected").description("Manage expected artifacts attached to an agent run.").action(() => {
|
|
192219
|
-
process.stderr.write("`eigenpal agents runs expected` requires a subcommand. Run `eigenpal agents runs expected --help`.\n");
|
|
192220
|
-
process.exit(2);
|
|
192221
|
-
});
|
|
192222
|
-
addJsonFlag(withBaseUrl(expected.command("list <run-id>"))).description("List expected JSON and files attached to a run.").action(action(listRunExpected));
|
|
192223
|
-
withBaseUrl(expected.command("pull <run-id>")).description("Download expected JSON and files attached to a run.").option("--out <dir>", "Output directory").action(action(pullRunExpected));
|
|
192224
|
-
addJsonFlag(withBaseUrl(expected.command("upload <run-id> <file>"))).description("Upload a local file as an expected artifact.").option("--name <name>", "Expected artifact name").action(action(uploadRunExpected));
|
|
192225
|
-
addJsonFlag(withBaseUrl(expected.command("copy-output <run-id> <output-file>"))).description("Copy a generated output file into expected artifacts.").option("--name <name>", "Expected artifact name").action(action(copyOutputToExpected));
|
|
192226
|
-
addJsonFlag(withBaseUrl(expected.command("rename <run-id> <old-name> <new-name>"))).description("Rename an expected artifact.").action(action(renameRunExpected));
|
|
192227
|
-
addJsonFlag(withBaseUrl(expected.command("delete <run-id> <name>"))).description("Delete an expected artifact.").option("--yes", "Required in non-interactive environments").action(action(deleteRunExpected));
|
|
192228
|
-
}
|
|
192229
|
-
async function listRunsTarget(target, opts) {
|
|
192230
|
-
const parsed = parseAgentTarget(target);
|
|
192231
|
-
return listRuns(parsed.slug, { ...opts, sourceRef: parsed.sourceRef });
|
|
192232
|
-
}
|
|
192233
|
-
async function runExecution(agentId, opts) {
|
|
192234
|
-
const client = buildClient(opts);
|
|
192235
|
-
const runPath = `/api/v1/agents/${encodeURIComponent(agentId)}/run${opts.sourceRef ? `?sourceRef=${encodeURIComponent(opts.sourceRef)}` : ""}`;
|
|
192236
|
-
let payload;
|
|
192237
|
-
if (opts.inputFile && opts.inputFile.length > 0) {
|
|
192238
|
-
const form = await buildAgentExecutionRunFormData(opts.inputFile, opts.inputJson);
|
|
192239
|
-
payload = await client.postFormData(runPath, form);
|
|
192240
|
-
} else {
|
|
192241
|
-
payload = await client.post(runPath, {
|
|
192242
|
-
input: opts.inputJson ? JSON.parse(opts.inputJson) : {},
|
|
192243
|
-
...opts.sourceRef ? { sourceRef: opts.sourceRef } : {}
|
|
192244
|
-
});
|
|
192245
|
-
}
|
|
192246
|
-
const runId = String(payload.runId ?? "");
|
|
192247
|
-
let waitedForTerminalRun = false;
|
|
192248
|
-
if (opts.wait && runId) {
|
|
192249
|
-
payload = await pollRun(client, runId, opts.interval ?? 2, opts.maxWait ?? 1800);
|
|
192250
|
-
waitedForTerminalRun = true;
|
|
192251
|
-
}
|
|
192252
|
-
renderRunPayload(payload, opts);
|
|
192253
|
-
if (waitedForTerminalRun)
|
|
192254
|
-
setExitCodeForFailedTerminalRun(payload);
|
|
192255
|
-
}
|
|
192256
|
-
async function runExample(agentId, opts) {
|
|
192257
|
-
const client = buildClient(opts);
|
|
192258
|
-
const started = await client.post(`/api/v1/agents/${encodeURIComponent(agentId)}/experiments`, {
|
|
192259
|
-
exampleId: opts.example,
|
|
192260
|
-
...opts.sourceRef ? { sourceRef: opts.sourceRef } : {}
|
|
192261
|
-
});
|
|
192262
|
-
const runId = started.runs?.[0]?.runId;
|
|
192263
|
-
if (opts.wait && runId) {
|
|
192264
|
-
const payload2 = await pollRun(client, runId, opts.interval, opts.maxWait);
|
|
192265
|
-
renderRunPayload(payload2, opts);
|
|
192266
|
-
setExitCodeForFailedTerminalRun(payload2);
|
|
192267
|
-
return;
|
|
192268
|
-
}
|
|
192269
|
-
const payload = {
|
|
192270
|
-
runId,
|
|
192271
|
-
exampleId: opts.example,
|
|
192272
|
-
batchId: started.batchId,
|
|
192273
|
-
status: "pending"
|
|
192274
|
-
};
|
|
192275
|
-
if (opts.json)
|
|
192276
|
-
return printJson(payload);
|
|
192277
|
-
success2(`Run ${runId ?? ""} queued for example ${opts.example}`);
|
|
192278
|
-
}
|
|
192279
|
-
async function getRun(executionId, opts) {
|
|
192280
|
-
const client = buildClient(opts);
|
|
192281
|
-
const payload = await client.get(`/api/v1/agents/runs/${encodeURIComponent(executionId)}`, {
|
|
192282
|
-
...opts.include ? { include: opts.include } : {}
|
|
192283
|
-
});
|
|
192284
|
-
renderRunPayload(payload, opts);
|
|
192285
|
-
}
|
|
192286
|
-
async function rerunRun(executionId, opts) {
|
|
192287
|
-
const client = buildClient(opts);
|
|
192288
|
-
const sourceRef = await resolveRerunSourceRef(client, executionId, opts.sourceRef);
|
|
192289
|
-
let payload = await client.post(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/rerun`, sourceRef ? { sourceRef } : {});
|
|
192290
|
-
const rerunId = String(payload.runId ?? "");
|
|
192291
|
-
if (opts.wait && rerunId) {
|
|
192292
|
-
payload = await pollRun(client, rerunId, opts.interval, opts.maxWait);
|
|
192293
|
-
renderRunPayload(payload, opts);
|
|
192294
|
-
setExitCodeForFailedTerminalRun(payload);
|
|
192295
|
-
return;
|
|
192296
|
-
}
|
|
192297
|
-
if (opts.json)
|
|
192298
|
-
return printJson(payload);
|
|
192299
|
-
success2(`Started rerun ${ui.bold(rerunId)} from ${executionId}`);
|
|
192300
|
-
}
|
|
192301
|
-
async function resolveRerunSourceRef(client, executionId, requested) {
|
|
192302
|
-
if (!requested)
|
|
192303
|
-
return;
|
|
192304
|
-
if (requested !== "original")
|
|
192305
|
-
return requested;
|
|
192306
|
-
const payload = await client.get(`/api/v1/agents/runs/${encodeURIComponent(executionId)}`);
|
|
192307
|
-
const run = payload.run;
|
|
192308
|
-
if (!run)
|
|
192309
|
-
throw new Error(`Run ${executionId} not found`);
|
|
192310
|
-
const resolved = stringOrNull(run.resolvedGitRef);
|
|
192311
|
-
const requestedSource = stringOrNull(run.requestedSourceRef);
|
|
192312
|
-
const original = resolved ?? requestedSource;
|
|
192313
|
-
if (!original) {
|
|
192314
|
-
throw new Error(`Run ${executionId} does not have an original source ref to reuse`);
|
|
192315
|
-
}
|
|
192316
|
-
return original;
|
|
192317
|
-
}
|
|
192318
|
-
async function fetchRunArtifacts(executionId, opts) {
|
|
192319
|
-
const client = buildClient(opts);
|
|
192320
|
-
const payload = await client.get(`/api/v1/agents/runs/${encodeURIComponent(executionId)}`, {
|
|
192321
|
-
include: "expected,files,input,output,issues,trace,lockfile,metadata"
|
|
192322
|
-
});
|
|
192323
|
-
const run = payload.run;
|
|
192324
|
-
if (!run)
|
|
192325
|
-
throw new Error(`Run ${executionId} not found`);
|
|
192326
|
-
const out = path12.resolve(opts.out ?? path12.join(".eigenpal", "artifacts", "runs", executionId));
|
|
192327
|
-
await fs7.mkdir(out, { recursive: true });
|
|
192328
|
-
const written = [];
|
|
192329
|
-
const skipped = [];
|
|
192330
|
-
const selectedPaths = opts.path && opts.path.length > 0 ? opts.path : runArtifactInventory(run).filter((artifact) => artifactIncluded(artifact, opts.include)).map((artifact) => artifact.path);
|
|
192331
|
-
for (const artifactPath of selectedPaths) {
|
|
192332
|
-
const normalized = normalizeArtifactPath(artifactPath);
|
|
192333
|
-
if (!normalized) {
|
|
192334
|
-
skipped.push(artifactPath);
|
|
192335
|
-
continue;
|
|
192336
|
-
}
|
|
192337
|
-
const result = await writeRunArtifact(client, executionId, out, normalized, run);
|
|
192338
|
-
if (result)
|
|
192339
|
-
written.push(result);
|
|
192340
|
-
else
|
|
192341
|
-
skipped.push(normalized);
|
|
192342
|
-
}
|
|
192343
|
-
const summary = {
|
|
192344
|
-
runId: executionId,
|
|
192345
|
-
out,
|
|
192346
|
-
written,
|
|
192347
|
-
skipped,
|
|
192348
|
-
counts: {
|
|
192349
|
-
written: written.length,
|
|
192350
|
-
skipped: skipped.length
|
|
192351
|
-
}
|
|
192352
|
-
};
|
|
192353
|
-
if (opts.json)
|
|
192354
|
-
return printJson(summary);
|
|
192355
|
-
success2(`Fetched run artifacts for ${executionId} to ${out}`);
|
|
192356
|
-
dim(`Wrote ${written.length} artifact${written.length === 1 ? "" : "s"}${skipped.length ? `; skipped missing ${skipped.join(", ")}` : ""}`);
|
|
192357
|
-
}
|
|
192358
|
-
async function listRunArtifacts(executionId, opts) {
|
|
192359
|
-
const client = buildClient(opts);
|
|
192360
|
-
const payload = await client.get(`/api/v1/agents/runs/${encodeURIComponent(executionId)}`, {
|
|
192361
|
-
include: "expected,files,input,output,issues,trace,lockfile,metadata"
|
|
192362
|
-
});
|
|
192363
|
-
const run = payload.run;
|
|
192364
|
-
if (!run)
|
|
192365
|
-
throw new Error(`Run ${executionId} not found`);
|
|
192366
|
-
const artifacts = runArtifactInventory(run);
|
|
192367
|
-
if (opts.json)
|
|
192368
|
-
return printJson({ runId: executionId, artifacts });
|
|
192369
|
-
console.log(table(artifacts, [
|
|
192370
|
-
{ key: "kind", header: "KIND" },
|
|
192371
|
-
{ key: "name", header: "NAME" },
|
|
192372
|
-
{ key: "path", header: "PATH" },
|
|
192373
|
-
{ key: "present", header: "PRESENT" }
|
|
192374
|
-
]));
|
|
192375
|
-
}
|
|
192376
|
-
function artifactIncluded(artifact, include) {
|
|
192377
|
-
const parts = new Set(include.split(",").map((part) => part.trim()).filter(Boolean));
|
|
192378
|
-
if (parts.has("all"))
|
|
192379
|
-
return true;
|
|
192380
|
-
if (parts.has(artifact.kind))
|
|
192381
|
-
return true;
|
|
192382
|
-
if (parts.has("files") && (artifact.kind === "input" || artifact.kind === "output"))
|
|
192383
|
-
return true;
|
|
192384
|
-
if (parts.has("metadata") && artifact.path === "run.json")
|
|
192385
|
-
return true;
|
|
192386
|
-
return false;
|
|
192387
|
-
}
|
|
192388
|
-
function normalizeArtifactPath(artifactPath) {
|
|
192389
|
-
const parts = artifactPath.split("/");
|
|
192390
|
-
if (artifactPath.startsWith("/") || parts.some((part) => part === "" || part === "." || part === "..")) {
|
|
192391
|
-
return null;
|
|
192392
|
-
}
|
|
192393
|
-
return artifactPath;
|
|
192394
|
-
}
|
|
192395
|
-
async function writeJsonRunArtifact(out, artifactPath, value) {
|
|
192396
|
-
await fs7.mkdir(path12.dirname(path12.join(out, artifactPath)), { recursive: true });
|
|
192397
|
-
await fs7.writeFile(path12.join(out, artifactPath), JSON.stringify(value, null, 2));
|
|
192398
|
-
}
|
|
192399
|
-
async function writeDownloadedRunArtifact(client, executionId, out, artifactPath, routePrefix) {
|
|
192400
|
-
const routePath = routePrefix === "files" ? artifactPath : artifactPath.slice("expected/".length);
|
|
192401
|
-
const response = await client.getStream(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/${routePrefix}/${encodeRunArtifactPath(routePath)}`);
|
|
192402
|
-
const outPath = path12.join(out, artifactPath);
|
|
192403
|
-
await fs7.mkdir(path12.dirname(outPath), { recursive: true });
|
|
192404
|
-
await fs7.writeFile(outPath, Buffer.from(await response.arrayBuffer()));
|
|
192405
|
-
}
|
|
192406
|
-
async function writeRunArtifact(client, executionId, out, artifactPath, run) {
|
|
192407
|
-
if (artifactPath === "run.json") {
|
|
192408
|
-
await writeJsonRunArtifact(out, artifactPath, run);
|
|
192409
|
-
return artifactPath;
|
|
192410
|
-
}
|
|
192411
|
-
if (artifactPath === "input.json") {
|
|
192412
|
-
if (run.inputJson == null)
|
|
192413
|
-
return null;
|
|
192414
|
-
await writeJsonRunArtifact(out, artifactPath, run.inputJson);
|
|
192415
|
-
return artifactPath;
|
|
192416
|
-
}
|
|
192417
|
-
if (artifactPath === "metadata.json") {
|
|
192418
|
-
if (run.metadata == null)
|
|
192419
|
-
return null;
|
|
192420
|
-
await writeJsonRunArtifact(out, artifactPath, run.metadata);
|
|
192421
|
-
return artifactPath;
|
|
192422
|
-
}
|
|
192423
|
-
if (artifactPath === "expected.json") {
|
|
192424
|
-
if (run.expected == null)
|
|
192425
|
-
return null;
|
|
192426
|
-
await writeJsonRunArtifact(out, artifactPath, run.expected);
|
|
192427
|
-
return artifactPath;
|
|
192428
|
-
}
|
|
192429
|
-
if (artifactPath.startsWith("expected/")) {
|
|
192430
|
-
await writeDownloadedRunArtifact(client, executionId, out, artifactPath, "expected");
|
|
192431
|
-
return artifactPath;
|
|
192432
|
-
}
|
|
192433
|
-
await writeDownloadedRunArtifact(client, executionId, out, artifactPath, "files");
|
|
192434
|
-
return artifactPath;
|
|
192435
|
-
}
|
|
192436
|
-
async function traceRun(executionId, opts) {
|
|
192437
|
-
const client = buildClient(opts);
|
|
192438
|
-
const text = await downloadTraceText(client, executionId);
|
|
192439
|
-
if (!opts.out) {
|
|
192440
|
-
process.stdout.write(text);
|
|
192441
|
-
return;
|
|
192442
|
-
}
|
|
192443
|
-
const out = path12.resolve(opts.out);
|
|
192444
|
-
await fs7.mkdir(path12.dirname(out), { recursive: true });
|
|
192445
|
-
await fs7.writeFile(out, text);
|
|
192446
|
-
success2(`Downloaded trace for ${executionId} to ${ui.bold(out)}`);
|
|
192447
|
-
}
|
|
192448
|
-
async function compareRun(referenceId, executionId, opts) {
|
|
192449
|
-
const client = buildClient(opts);
|
|
192450
|
-
const targetPayload = await client.get(`/api/v1/agents/runs/${encodeURIComponent(executionId)}`, { include: "files,output" });
|
|
192451
|
-
const target = targetPayload.run;
|
|
192452
|
-
if (!target)
|
|
192453
|
-
throw new Error(`Run ${executionId} not found`);
|
|
192454
|
-
const mode = opts.baseline ? "baseline" : "expected";
|
|
192455
|
-
const reference = (await client.get(`/api/v1/agents/runs/${encodeURIComponent(referenceId)}`, {
|
|
192456
|
-
include: mode === "baseline" ? "files,output" : "expected"
|
|
192457
|
-
})).run;
|
|
192458
|
-
if (!reference)
|
|
192459
|
-
throw new Error(`Reference run ${referenceId} not found`);
|
|
192460
|
-
const out = path12.resolve(opts.out ?? path12.join(".eigenpal", "artifacts", "comparisons", `${referenceId}..${executionId}`));
|
|
192461
|
-
await fs7.mkdir(out, { recursive: true });
|
|
192462
|
-
const warnings = [];
|
|
192463
|
-
if (mode === "expected" && reference.expected == null && fileNames(reference.expectedFiles).length === 0) {
|
|
192464
|
-
warnings.push(`Reference run ${referenceId} has no expected JSON or expected files; comparison has no baseline artifacts. Use --baseline to compare actual outputs.`);
|
|
192465
|
-
}
|
|
192466
|
-
const jsonDiffs = diffJson(mode === "baseline" ? reference.output : reference.expected, target.output);
|
|
192467
|
-
const expectedFiles = mode === "baseline" ? fileNames(reference.resultFiles).filter((name) => name !== "issues.md" && name !== "trace.jsonl") : fileNames(reference.expectedFiles);
|
|
192468
|
-
const outputFiles = fileNames(target.resultFiles).filter((name) => name !== "issues.md" && name !== "trace.jsonl");
|
|
192469
|
-
const inventory = compareFileInventory(expectedFiles, outputFiles, Boolean(opts.normalizeDates));
|
|
192470
|
-
const textDifferences = await compareMatchedFileText(client, { runId: referenceId, kind: mode === "baseline" ? "output" : "expected" }, { runId: executionId, kind: "output" }, out, inventory.matched, Boolean(opts.normalizeDates));
|
|
192471
|
-
const report = {
|
|
192472
|
-
status: jsonDiffs.length === 0 && inventory.missing.length === 0 && inventory.extra.length === 0 && textDifferences.every((diff) => diff.status === "match" || diff.status === "binary-match") ? "pass" : "fail",
|
|
192473
|
-
runId: executionId,
|
|
192474
|
-
comparedWithRunId: referenceId,
|
|
192475
|
-
mode,
|
|
192476
|
-
warnings,
|
|
192477
|
-
jsonDifferences: jsonDiffs,
|
|
192478
|
-
matchedFiles: inventory.matched,
|
|
192479
|
-
missingFiles: inventory.missing,
|
|
192480
|
-
extraFiles: inventory.extra,
|
|
192481
|
-
textDifferences
|
|
192482
|
-
};
|
|
192483
|
-
await fs7.writeFile(path12.join(out, "comparison.json"), JSON.stringify(report, null, 2));
|
|
192484
|
-
if (opts.json) {
|
|
192485
|
-
printJson(report);
|
|
192486
|
-
} else {
|
|
192487
|
-
renderComparisonReport(report);
|
|
192488
|
-
}
|
|
192489
|
-
dim(`Comparison artifacts written to ${ui.bold(out)}`);
|
|
192490
|
-
if (opts.failOnDiff && report.status === "fail")
|
|
192491
|
-
process.exit(1);
|
|
192492
|
-
}
|
|
192493
|
-
async function listRuns(agentId, opts) {
|
|
192494
|
-
const client = buildClient(opts);
|
|
192495
|
-
const params = buildRunListParams(opts);
|
|
192496
|
-
const payload = await client.get(`/api/v1/agents/${encodeURIComponent(agentId)}/runs`, params);
|
|
192497
|
-
const rows = opts.compact ? payload.runs.map(compactRunRow) : payload.runs;
|
|
192498
|
-
if (opts.json)
|
|
192499
|
-
return printJson({ ...payload, runs: rows });
|
|
192500
|
-
if (payload.scanLimited) {
|
|
192501
|
-
warn("Feedback/expected filters scanned only the first matching window. Increase --scan-limit or narrow DB filters for a fuller result.");
|
|
192502
|
-
}
|
|
192503
|
-
if (payload.noResolvedAnchor) {
|
|
192504
|
-
warn("No resolved feedback anchor was found in the scan window; --since-last-resolved returned no rows.");
|
|
192505
|
-
}
|
|
192506
|
-
console.log(table(rows, [
|
|
192507
|
-
{ key: "id", header: "ID" },
|
|
192508
|
-
{ key: "status", header: "STATUS" },
|
|
192509
|
-
{ key: "exampleId", header: "EXAMPLE" },
|
|
192510
|
-
{
|
|
192511
|
-
key: "feedback",
|
|
192512
|
-
header: "FEEDBACK",
|
|
192513
|
-
format: (value) => value && typeof value === "object" ? `${String(value.rating ?? "")}/${String(value.status ?? "")}` : ""
|
|
192514
|
-
},
|
|
192515
|
-
{ key: "createdAt", header: "CREATED", format: formatTimestamp }
|
|
192516
|
-
]));
|
|
192517
|
-
}
|
|
192518
|
-
function buildRunListParams(opts) {
|
|
192519
|
-
return compactParams({
|
|
192520
|
-
...opts,
|
|
192521
|
-
feedback: undefined,
|
|
192522
|
-
noFeedback: opts.noFeedback ?? (opts.feedback === false ? true : undefined)
|
|
192523
|
-
});
|
|
192524
|
-
}
|
|
192525
|
-
async function updateRunFeedback(executionId, opts) {
|
|
192526
|
-
const client = buildClient(opts);
|
|
192527
|
-
const body = {};
|
|
192528
|
-
if (opts.status)
|
|
192529
|
-
body.status = opts.status;
|
|
192530
|
-
if (opts.rating)
|
|
192531
|
-
body.rating = opts.rating === "none" ? null : opts.rating;
|
|
192532
|
-
if (opts.clearRating)
|
|
192533
|
-
body.rating = null;
|
|
192534
|
-
if (opts.messageFile)
|
|
192535
|
-
body.body = await fs7.readFile(opts.messageFile, "utf-8");
|
|
192536
|
-
if (opts.message !== undefined)
|
|
192537
|
-
body.body = opts.message;
|
|
192538
|
-
if (opts.clearMessage)
|
|
192539
|
-
body.body = "";
|
|
192540
|
-
if (opts.expectedJsonFile)
|
|
192541
|
-
body.expected = JSON.parse(await fs7.readFile(opts.expectedJsonFile, "utf-8"));
|
|
192542
|
-
if (opts.expectedJson !== undefined)
|
|
192543
|
-
body.expected = JSON.parse(opts.expectedJson);
|
|
192544
|
-
if (opts.clearExpectedJson)
|
|
192545
|
-
body.expected = null;
|
|
192546
|
-
const payload = await client.patch(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/feedback`, body);
|
|
192547
|
-
renderGeneric(payload, opts, `Updated feedback for ${executionId}`);
|
|
192548
|
-
}
|
|
192549
|
-
function compactRunRow(run) {
|
|
192550
|
-
const feedback = run.feedback && typeof run.feedback === "object" ? run.feedback : null;
|
|
192551
|
-
return {
|
|
192552
|
-
id: run.id,
|
|
192553
|
-
status: run.status,
|
|
192554
|
-
exampleId: run.exampleId,
|
|
192555
|
-
feedback: feedback ? {
|
|
192556
|
-
rating: feedback.rating ?? null,
|
|
192557
|
-
status: feedback.status ?? null,
|
|
192558
|
-
updatedAt: feedback.updatedAt ?? null
|
|
192559
|
-
} : null,
|
|
192560
|
-
hasExpectedJson: run.expected != null,
|
|
192561
|
-
expectedFileCount: Array.isArray(run.expectedFiles) ? run.expectedFiles.length : undefined,
|
|
192562
|
-
createdAt: run.createdAt,
|
|
192563
|
-
completedAt: run.completedAt
|
|
192564
|
-
};
|
|
192565
|
-
}
|
|
192566
|
-
async function clearRunFeedback(executionId, opts) {
|
|
192567
|
-
if (!(opts.yes || await confirmTyped(executionId, "clear feedback artifacts"))) {
|
|
192568
|
-
throw new Error("Clear cancelled");
|
|
192569
|
-
}
|
|
192570
|
-
const client = buildClient(opts);
|
|
192571
|
-
const payload = await client.delete(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/feedback`);
|
|
192572
|
-
renderGeneric(payload, opts, `Cleared feedback for ${executionId}`);
|
|
192573
|
-
}
|
|
192574
|
-
async function listRunExpected(executionId, opts) {
|
|
192575
|
-
const client = buildClient(opts);
|
|
192576
|
-
const payload = await client.get(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/expected`);
|
|
192577
|
-
if (opts.json)
|
|
192578
|
-
return printJson(payload);
|
|
192579
|
-
console.log(table(payload.files ?? [], [{ key: "name", header: "NAME" }]));
|
|
192580
|
-
if (payload.expected != null)
|
|
192581
|
-
dim("expected.json present");
|
|
192582
|
-
}
|
|
192583
|
-
async function pullRunExpected(executionId, opts) {
|
|
192584
|
-
const client = buildClient(opts);
|
|
192585
|
-
const payload = await client.get(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/expected`);
|
|
192586
|
-
const out = path12.resolve(opts.out ?? path12.join(executionId, "expected"));
|
|
192587
|
-
await fs7.mkdir(out, { recursive: true });
|
|
192588
|
-
if (payload.expected != null) {
|
|
192589
|
-
await fs7.writeFile(path12.join(out, "expected.json"), JSON.stringify(payload.expected, null, 2));
|
|
192590
|
-
}
|
|
192591
|
-
await downloadExpectedFiles(client, executionId, out, payload.files);
|
|
192592
|
-
success2(`Pulled expected artifacts for ${executionId} to ${out}`);
|
|
192593
|
-
}
|
|
192594
|
-
async function downloadExpectedFiles(client, executionId, out, files) {
|
|
192595
|
-
const rows = Array.isArray(files) ? files : [];
|
|
192596
|
-
const written = await Promise.all(rows.map(async (file2) => {
|
|
192597
|
-
const name = String(file2.name ?? "");
|
|
192598
|
-
if (!name || name.includes("/") || name.includes(".."))
|
|
192599
|
-
return null;
|
|
192600
|
-
const response = await client.getStream(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/expected/${encodeURIComponent(name)}`);
|
|
192601
|
-
await fs7.mkdir(out, { recursive: true });
|
|
192602
|
-
await fs7.writeFile(path12.join(out, name), Buffer.from(await response.arrayBuffer()));
|
|
192603
|
-
return name;
|
|
192604
|
-
}));
|
|
192605
|
-
return written.filter((name) => Boolean(name));
|
|
192606
|
-
}
|
|
192607
|
-
async function uploadRunExpected(executionId, file2, opts) {
|
|
192608
|
-
const client = buildClient(opts);
|
|
192609
|
-
const form = new FormData;
|
|
192610
|
-
const data = await fs7.readFile(file2);
|
|
192611
|
-
form.append("file", new Blob([data]), path12.basename(file2));
|
|
192612
|
-
if (opts.name)
|
|
192613
|
-
form.append("name", opts.name);
|
|
192614
|
-
const payload = await client.postFormData(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/expected`, form);
|
|
192615
|
-
renderGeneric(payload, opts, `Uploaded expected file for ${executionId}`);
|
|
192616
|
-
}
|
|
192617
|
-
async function copyOutputToExpected(executionId, outputFile, opts) {
|
|
192618
|
-
const client = buildClient(opts);
|
|
192619
|
-
const payload = await client.post(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/expected`, { outputFileName: outputFile, ...opts.name ? { expectedName: opts.name } : {} });
|
|
192620
|
-
renderGeneric(payload, opts, `Copied output file to expected for ${executionId}`);
|
|
192621
|
-
}
|
|
192622
|
-
async function renameRunExpected(executionId, oldName, newName, opts) {
|
|
192623
|
-
const client = buildClient(opts);
|
|
192624
|
-
const payload = await client.patch(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/expected/${encodeURIComponent(oldName)}`, { name: newName });
|
|
192625
|
-
renderGeneric(payload, opts, `Renamed expected file for ${executionId}`);
|
|
192626
|
-
}
|
|
192627
|
-
async function deleteRunExpected(executionId, name, opts) {
|
|
192628
|
-
if (!(opts.yes || await confirmTyped(name, "delete expected file"))) {
|
|
192629
|
-
throw new Error("Delete cancelled");
|
|
192630
|
-
}
|
|
192631
|
-
const client = buildClient(opts);
|
|
192632
|
-
await client.delete(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/expected/${encodeURIComponent(name)}`);
|
|
192633
|
-
renderGeneric({ ok: true }, opts, `Deleted expected file ${name}`);
|
|
192634
|
-
}
|
|
192635
|
-
async function watchRunCommand(executionId, opts) {
|
|
192636
|
-
const client = buildClient(opts);
|
|
192637
|
-
const payload = await pollRun(client, executionId, opts.interval, opts.maxWait);
|
|
192638
|
-
renderRunPayload(payload, opts);
|
|
192639
|
-
setExitCodeForFailedTerminalRun(payload);
|
|
192640
|
-
}
|
|
192641
|
-
async function cancelRun(executionId, opts) {
|
|
192642
|
-
if (!(opts.yes || process.stdin.isTTY))
|
|
192643
|
-
throw new Error("Pass --yes to cancel in non-interactive mode");
|
|
192644
|
-
const client = buildClient(opts);
|
|
192645
|
-
const payload = await client.post(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/cancel`, {});
|
|
192646
|
-
renderRunPayload(payload, opts);
|
|
192647
|
-
}
|
|
192648
|
-
async function downloadTraceText(client, executionId) {
|
|
192649
|
-
const response = await client.getStream(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/files/trace.jsonl`);
|
|
192650
|
-
return Buffer.from(await response.arrayBuffer()).toString("utf-8");
|
|
192651
|
-
}
|
|
192652
|
-
function diffJson(expected, actual, basePath = "$") {
|
|
192653
|
-
if (expected === undefined || expected === null)
|
|
192654
|
-
return [];
|
|
192655
|
-
if (Object.is(expected, actual))
|
|
192656
|
-
return [];
|
|
192657
|
-
if (expected && actual && typeof expected === "object" && typeof actual === "object" && !Array.isArray(expected) && !Array.isArray(actual)) {
|
|
192658
|
-
const keys = new Set([...Object.keys(expected), ...Object.keys(actual)]);
|
|
192659
|
-
return [...keys].flatMap((key) => {
|
|
192660
|
-
const next = `${basePath}.${key}`;
|
|
192661
|
-
if (!(key in actual))
|
|
192662
|
-
return [{ path: next, type: "missing" }];
|
|
192663
|
-
if (!(key in expected))
|
|
192664
|
-
return [{ path: next, type: "extra" }];
|
|
192665
|
-
return diffJson(expected[key], actual[key], next);
|
|
192666
|
-
});
|
|
192667
|
-
}
|
|
192668
|
-
if (Array.isArray(expected) && Array.isArray(actual)) {
|
|
192669
|
-
const diffs = [];
|
|
192670
|
-
const max = Math.max(expected.length, actual.length);
|
|
192671
|
-
for (let index = 0;index < max; index += 1) {
|
|
192672
|
-
const next = `${basePath}[${index}]`;
|
|
192673
|
-
if (index >= actual.length)
|
|
192674
|
-
diffs.push({ path: next, type: "missing" });
|
|
192675
|
-
else if (index >= expected.length)
|
|
192676
|
-
diffs.push({ path: next, type: "extra" });
|
|
192677
|
-
else
|
|
192678
|
-
diffs.push(...diffJson(expected[index], actual[index], next));
|
|
192679
|
-
}
|
|
192680
|
-
return diffs;
|
|
192681
|
-
}
|
|
192682
|
-
return [
|
|
192683
|
-
{
|
|
192684
|
-
path: basePath,
|
|
192685
|
-
type: "changed",
|
|
192686
|
-
expected: JSON.stringify(expected),
|
|
192687
|
-
actual: JSON.stringify(actual)
|
|
192688
|
-
}
|
|
192689
|
-
];
|
|
192690
|
-
}
|
|
192691
|
-
function fileNames(files) {
|
|
192692
|
-
return (Array.isArray(files) ? files : []).map((file2) => String(file2.name ?? "")).filter(Boolean).sort();
|
|
192693
|
-
}
|
|
192694
|
-
function stringOrNull(value) {
|
|
192695
|
-
return typeof value === "string" && value.length > 0 ? value : null;
|
|
192696
|
-
}
|
|
192697
|
-
function runArtifactInventory(run) {
|
|
192698
|
-
const rows = [
|
|
192699
|
-
{ kind: "metadata", name: "run.json", path: "run.json", present: "yes" }
|
|
192700
|
-
];
|
|
192701
|
-
if (run.inputJson != null) {
|
|
192702
|
-
rows.push({ kind: "input", name: "input.json", path: "input.json", present: "yes" });
|
|
192703
|
-
}
|
|
192704
|
-
if (run.metadata != null) {
|
|
192705
|
-
rows.push({ kind: "metadata", name: "metadata.json", path: "metadata.json", present: "yes" });
|
|
192706
|
-
}
|
|
192707
|
-
if (run.expected != null) {
|
|
192708
|
-
rows.push({ kind: "expected", name: "expected.json", path: "expected.json", present: "yes" });
|
|
192709
|
-
}
|
|
192710
|
-
for (const name of fileNames(run.inputFiles)) {
|
|
192711
|
-
rows.push({ kind: "input", name, path: `input/${name}`, present: "yes" });
|
|
192712
|
-
}
|
|
192713
|
-
for (const name of fileNames(run.resultFiles).filter((name2) => name2 !== "issues.md" && name2 !== "trace.jsonl")) {
|
|
192714
|
-
rows.push({ kind: "output", name, path: `output/${name}`, present: "yes" });
|
|
192715
|
-
}
|
|
192716
|
-
for (const name of fileNames(run.expectedFiles)) {
|
|
192717
|
-
rows.push({ kind: "expected", name, path: `expected/${name}`, present: "yes" });
|
|
192718
|
-
}
|
|
192719
|
-
for (const name of fileNames(run.issueFiles)) {
|
|
192720
|
-
rows.push({ kind: "issues", name, path: name, present: "yes" });
|
|
192721
|
-
}
|
|
192722
|
-
for (const name of fileNames(run.traceFiles)) {
|
|
192723
|
-
rows.push({ kind: "trace", name, path: name, present: "yes" });
|
|
192724
|
-
}
|
|
192725
|
-
for (const name of fileNames(run.lockfileFiles)) {
|
|
192726
|
-
rows.push({ kind: "lockfile", name, path: name, present: "yes" });
|
|
192727
|
-
}
|
|
192728
|
-
return rows.sort((a, b2) => a.path.localeCompare(b2.path));
|
|
192729
|
-
}
|
|
192730
|
-
function normalizeGeneratedTokens(value) {
|
|
192731
|
-
return value.replace(/\d{4}-\d{2}-\d{2}/g, "<date>").replace(/\d{8}/g, "<date>");
|
|
192732
|
-
}
|
|
192733
|
-
function compareFileInventory(expected, output, normalizeDates) {
|
|
192734
|
-
const normalize = (value) => normalizeDates ? normalizeGeneratedTokens(value) : value;
|
|
192735
|
-
const outputByNormalized = new Map(output.map((name) => [normalize(name), name]));
|
|
192736
|
-
const expectedByNormalized = new Map(expected.map((name) => [normalize(name), name]));
|
|
192737
|
-
const matched = expected.map((expectedName2) => {
|
|
192738
|
-
const outputName = outputByNormalized.get(normalize(expectedName2));
|
|
192739
|
-
return outputName ? { expected: expectedName2, output: outputName } : null;
|
|
192740
|
-
}).filter((match) => Boolean(match));
|
|
192741
|
-
return {
|
|
192742
|
-
matched,
|
|
192743
|
-
missing: expected.filter((name) => !outputByNormalized.has(normalize(name))),
|
|
192744
|
-
extra: output.filter((name) => !expectedByNormalized.has(normalize(name)))
|
|
192745
|
-
};
|
|
192746
|
-
}
|
|
192747
|
-
async function compareMatchedFileText(client, left, right, out, matched, normalizeDates) {
|
|
192748
|
-
const expectedDir = path12.join(out, "expected");
|
|
192749
|
-
const outputDir = path12.join(out, "output");
|
|
192750
|
-
await fs7.mkdir(expectedDir, { recursive: true });
|
|
192751
|
-
await fs7.mkdir(outputDir, { recursive: true });
|
|
192752
|
-
const diffs = [];
|
|
192753
|
-
for (const pair of matched) {
|
|
192754
|
-
const expectedPath = path12.join(expectedDir, pair.expected);
|
|
192755
|
-
const outputPath = path12.join(outputDir, pair.output);
|
|
192756
|
-
await downloadStreamToFile(client, runFileUrl(left, pair.expected), expectedPath);
|
|
192757
|
-
await downloadStreamToFile(client, runFileUrl(right, pair.output), outputPath);
|
|
192758
|
-
const expectedText = extractComparableText(expectedPath);
|
|
192759
|
-
const outputText = extractComparableText(outputPath);
|
|
192760
|
-
if (expectedText.text == null || outputText.text == null) {
|
|
192761
|
-
const [expectedBuffer, outputBuffer] = await Promise.all([
|
|
192762
|
-
fs7.readFile(expectedPath),
|
|
192763
|
-
fs7.readFile(outputPath)
|
|
192764
|
-
]);
|
|
192765
|
-
const expectedHash = createHash3("sha256").update(expectedBuffer).digest("hex");
|
|
192766
|
-
const outputHash = createHash3("sha256").update(outputBuffer).digest("hex");
|
|
192767
|
-
diffs.push({
|
|
192768
|
-
...pair,
|
|
192769
|
-
status: expectedBuffer.length === outputBuffer.length && expectedHash === outputHash ? "binary-match" : "binary-different",
|
|
192770
|
-
expectedBytes: expectedBuffer.length,
|
|
192771
|
-
outputBytes: outputBuffer.length,
|
|
192772
|
-
expectedSha256: expectedHash,
|
|
192773
|
-
outputSha256: outputHash,
|
|
192774
|
-
textExtraction: {
|
|
192775
|
-
expected: expectedText.reason ?? null,
|
|
192776
|
-
output: outputText.reason ?? null
|
|
192777
|
-
}
|
|
192778
|
-
});
|
|
192779
|
-
continue;
|
|
192780
|
-
}
|
|
192781
|
-
const normalize = (value) => normalizeDates ? normalizeGeneratedTokens(value).trim() : value.trim();
|
|
192782
|
-
diffs.push({
|
|
192783
|
-
...pair,
|
|
192784
|
-
status: normalize(expectedText.text) === normalize(outputText.text) ? "match" : "text-different"
|
|
192785
|
-
});
|
|
192786
|
-
}
|
|
192787
|
-
return diffs;
|
|
192788
|
-
}
|
|
192789
|
-
function runFileUrl(side, name) {
|
|
192790
|
-
const runId = encodeURIComponent(side.runId);
|
|
192791
|
-
const filename = encodeURIComponent(name);
|
|
192792
|
-
return side.kind === "expected" ? `/api/v1/agents/runs/${runId}/expected/${filename}` : `/api/v1/agents/runs/${runId}/files/output/${filename}`;
|
|
192793
|
-
}
|
|
192794
|
-
function encodeRunArtifactPath(artifactPath) {
|
|
192795
|
-
return artifactPath.split("/").map(encodeURIComponent).join("/");
|
|
192796
|
-
}
|
|
192797
|
-
async function downloadStreamToFile(client, url2, target) {
|
|
192798
|
-
await fs7.mkdir(path12.dirname(target), { recursive: true });
|
|
192799
|
-
const response = await client.getStream(url2);
|
|
192800
|
-
await fs7.writeFile(target, Buffer.from(await response.arrayBuffer()));
|
|
192801
|
-
}
|
|
192802
|
-
function extractComparableText(file2) {
|
|
192803
|
-
const ext = path12.extname(file2).toLowerCase();
|
|
192804
|
-
if ([".txt", ".md", ".json", ".csv", ".xml"].includes(ext)) {
|
|
192805
|
-
return { text: readFileSync8(file2, "utf8") };
|
|
192806
|
-
}
|
|
192807
|
-
if (ext === ".pdf") {
|
|
192808
|
-
const result = spawnSync4("pdftotext", [file2, "-"], { encoding: "utf8" });
|
|
192809
|
-
return result.status === 0 ? { text: result.stdout } : { text: null, reason: textExtractionFallbackReason("pdftotext", result) };
|
|
192810
|
-
}
|
|
192811
|
-
if (ext === ".docx") {
|
|
192812
|
-
const script = "import re,sys,zipfile; z=zipfile.ZipFile(sys.argv[1]); x=z.read('word/document.xml').decode('utf-8','ignore'); print(re.sub('<[^>]+>',' ',x))";
|
|
192813
|
-
const result = spawnSync4("python3", ["-c", script, file2], { encoding: "utf8" });
|
|
192814
|
-
return result.status === 0 ? { text: result.stdout } : { text: null, reason: textExtractionFallbackReason("python3", result) };
|
|
192815
|
-
}
|
|
192816
|
-
return { text: null, reason: `No text extractor for ${ext || "extensionless"} files` };
|
|
192817
|
-
}
|
|
192818
|
-
function textExtractionFallbackReason(tool, result) {
|
|
192819
|
-
if (result.error)
|
|
192820
|
-
return `${tool} unavailable; compared bytes instead`;
|
|
192821
|
-
return `${tool} could not extract text; compared bytes instead`;
|
|
192822
|
-
}
|
|
192823
|
-
function renderComparisonReport(report) {
|
|
192824
|
-
const header = `Comparison ${report.status}`;
|
|
192825
|
-
if (report.status === "pass") {
|
|
192826
|
-
success2(header);
|
|
192827
|
-
} else {
|
|
192828
|
-
error51(header);
|
|
192829
|
-
}
|
|
192830
|
-
for (const warning of report.warnings ?? []) {
|
|
192831
|
-
dim(`warning: ${warning}`);
|
|
192832
|
-
}
|
|
192833
|
-
console.log(table([
|
|
192834
|
-
{ item: "JSON differences", count: report.jsonDifferences.length },
|
|
192835
|
-
{ item: "Matched files", count: report.matchedFiles.length },
|
|
192836
|
-
{ item: "Missing files", count: report.missingFiles.length },
|
|
192837
|
-
{ item: "Extra files", count: report.extraFiles.length },
|
|
192838
|
-
{
|
|
192839
|
-
item: "Text differences",
|
|
192840
|
-
count: report.textDifferences.filter((diff) => diff.status === "text-different").length
|
|
192841
|
-
}
|
|
192842
|
-
], [
|
|
192843
|
-
{ key: "item", header: "ITEM" },
|
|
192844
|
-
{ key: "count", header: "COUNT" }
|
|
192845
|
-
]));
|
|
192846
|
-
for (const diff of report.textDifferences) {
|
|
192847
|
-
const textExtraction = diff.textExtraction;
|
|
192848
|
-
const reasons = [textExtraction?.expected, textExtraction?.output].filter((value) => typeof value === "string" && value.length > 0).filter((value, index, values) => values.indexOf(value) === index);
|
|
192849
|
-
for (const reason of reasons) {
|
|
192850
|
-
dim(`warning: ${String(diff.expected)} vs ${String(diff.output)}: ${reason}`);
|
|
192851
|
-
}
|
|
192852
|
-
}
|
|
192853
|
-
}
|
|
192854
|
-
|
|
192855
193350
|
// src/commands/agents/sessions.ts
|
|
192856
193351
|
function registerSessionCommands(agent) {
|
|
192857
193352
|
const session = agent.command("session").description("Manage agent builder sessions.").action(() => {
|
|
@@ -192900,18 +193395,16 @@ async function stopSession(sessionId, opts) {
|
|
|
192900
193395
|
|
|
192901
193396
|
// src/commands/agents/index.ts
|
|
192902
193397
|
function registerAgentCommands(program2) {
|
|
192903
|
-
const agent = program2.command("agents").
|
|
193398
|
+
const agent = program2.command("agents").description("Manage Eigenpal agents: Git source, datasets, runs, experiments, sessions, and releases.").action(() => {
|
|
192904
193399
|
process.stderr.write("`eigenpal agents` requires a subcommand. Run `eigenpal agents --help`.\n");
|
|
192905
193400
|
process.exit(2);
|
|
192906
193401
|
});
|
|
192907
193402
|
addJsonFlag(withBaseUrl(agent.command("run <target>"))).description("Run an agent target, e.g. agents.invoice-agent@latest.").option("--input-json <json>", "JSON input object").option("--input-file <field=path>", 'Input file to upload as multipart form-data. Repeat for multiple files; bare paths use field "file".', collectRepeated, []).option("--example <name>", "Run a persisted dataset example by name").option("--wait", "Poll until the run reaches a terminal status").option("--interval <seconds>", "Polling interval in seconds", intArg, 2).option("--max-wait <seconds>", "Maximum wait before exiting 2", intArg, 1800).action(action(runTarget));
|
|
192908
|
-
addJsonFlag(withBaseUrl(agent.command("rerun <run-id>"))).description("Create a new run from a previous run's stored input snapshot.").option("--source-ref <ref>", 'Source ref for the new run (default: latest). Use "original" to reproduce the previous resolved version.').option("--wait", "Poll until the rerun reaches a terminal status").option("--interval <seconds>", "Polling interval in seconds", intArg, 2).option("--max-wait <seconds>", "Maximum wait before exiting 2", intArg, 1800).action(action(rerunRun));
|
|
192909
193403
|
addJsonFlag(withPagination(withBaseUrl(agent.command("list")), 50)).description("List agents.").option("--search <q>", "Search by slug, name, or description").action(action(listAgents));
|
|
192910
193404
|
registerAgentFileCommands(agent);
|
|
192911
193405
|
addJsonFlag(agent.command("validate [dir]")).description("Validate a local agent package (layout, manifest, schemas, and Git source rules).").action(action(validateAgentCommand));
|
|
192912
193406
|
registerAgentSourceCommands(agent);
|
|
192913
193407
|
registerDatasetCommands(agent);
|
|
192914
|
-
registerRunCommands(agent);
|
|
192915
193408
|
registerExperimentCommands(agent);
|
|
192916
193409
|
registerSessionCommands(agent);
|
|
192917
193410
|
registerEnvCommands(agent);
|
|
@@ -193487,7 +193980,7 @@ async function init(name, opts) {
|
|
|
193487
193980
|
info("Next steps:");
|
|
193488
193981
|
dim(` cd ${targetDisplay}`);
|
|
193489
193982
|
dim(` eigenpal workflow validate # all three local checks`);
|
|
193490
|
-
dim(` eigenpal workflow
|
|
193983
|
+
dim(` eigenpal workflow run ${name} sample`);
|
|
193491
193984
|
}
|
|
193492
193985
|
function assertSafeTargetDir(target, dir) {
|
|
193493
193986
|
if (dir !== undefined && !dir.startsWith("/") && dir.split(/[/\\]/).some((s) => s === "..")) {
|
|
@@ -194858,80 +195351,6 @@ function formatEigenpalDirIfAvailable(dir) {
|
|
|
194858
195351
|
});
|
|
194859
195352
|
}
|
|
194860
195353
|
|
|
194861
|
-
// src/lib/resolve-workflow.ts
|
|
194862
|
-
var WORKFLOW_ID_PREFIX = "wf_";
|
|
194863
|
-
function looksLikeWorkflowId(value) {
|
|
194864
|
-
return value.startsWith(WORKFLOW_ID_PREFIX);
|
|
194865
|
-
}
|
|
194866
|
-
function levenshtein(a, b2, maxDistance) {
|
|
194867
|
-
if (a === b2)
|
|
194868
|
-
return 0;
|
|
194869
|
-
if (Math.abs(a.length - b2.length) > maxDistance)
|
|
194870
|
-
return maxDistance + 1;
|
|
194871
|
-
if (a.length === 0 || b2.length === 0)
|
|
194872
|
-
return a.length || b2.length;
|
|
194873
|
-
const prev = new Array(b2.length + 1);
|
|
194874
|
-
const curr = new Array(b2.length + 1);
|
|
194875
|
-
for (let j3 = 0;j3 <= b2.length; j3++)
|
|
194876
|
-
prev[j3] = j3;
|
|
194877
|
-
for (let i2 = 1;i2 <= a.length; i2++) {
|
|
194878
|
-
curr[0] = i2;
|
|
194879
|
-
let minRow = curr[0];
|
|
194880
|
-
for (let j3 = 1;j3 <= b2.length; j3++) {
|
|
194881
|
-
const cost = a[i2 - 1] === b2[j3 - 1] ? 0 : 1;
|
|
194882
|
-
curr[j3] = Math.min(prev[j3] + 1, curr[j3 - 1] + 1, prev[j3 - 1] + cost);
|
|
194883
|
-
if (curr[j3] < minRow)
|
|
194884
|
-
minRow = curr[j3];
|
|
194885
|
-
}
|
|
194886
|
-
if (minRow > maxDistance)
|
|
194887
|
-
return maxDistance + 1;
|
|
194888
|
-
for (let j3 = 0;j3 <= b2.length; j3++)
|
|
194889
|
-
prev[j3] = curr[j3];
|
|
194890
|
-
}
|
|
194891
|
-
return prev[b2.length];
|
|
194892
|
-
}
|
|
194893
|
-
async function findSimilarSlug(client, slug) {
|
|
194894
|
-
try {
|
|
194895
|
-
const res = await client.get("/api/v1/workflows", {
|
|
194896
|
-
search: slug,
|
|
194897
|
-
limit: "20"
|
|
194898
|
-
});
|
|
194899
|
-
const candidates = (res.data ?? []).map((w3) => w3.name).filter((n) => typeof n === "string" && n.length > 0);
|
|
194900
|
-
let best = null;
|
|
194901
|
-
for (const candidate of candidates) {
|
|
194902
|
-
const distance = levenshtein(candidate, slug, 2);
|
|
194903
|
-
if (distance <= 2 && (best === null || distance < best.distance)) {
|
|
194904
|
-
best = { name: candidate, distance };
|
|
194905
|
-
}
|
|
194906
|
-
}
|
|
194907
|
-
return best?.name ?? null;
|
|
194908
|
-
} catch {
|
|
194909
|
-
return null;
|
|
194910
|
-
}
|
|
194911
|
-
}
|
|
194912
|
-
function notFound(idOrSlug, suggestion) {
|
|
194913
|
-
const base = `Workflow "${idOrSlug}" not found on the server.`;
|
|
194914
|
-
const action2 = "Push it first, or run `eigenpal workflow list` to see what's available.";
|
|
194915
|
-
return new Error(suggestion ? `${base} Did you mean "${suggestion}"? ${action2}` : `${base} ${action2}`);
|
|
194916
|
-
}
|
|
194917
|
-
async function resolveWorkflowId(client, idOrSlug) {
|
|
194918
|
-
if (looksLikeWorkflowId(idOrSlug)) {
|
|
194919
|
-
try {
|
|
194920
|
-
const wf = await client.get(`/api/v1/workflows/${idOrSlug}`);
|
|
194921
|
-
return wf.id;
|
|
194922
|
-
} catch {
|
|
194923
|
-
throw notFound(idOrSlug, null);
|
|
194924
|
-
}
|
|
194925
|
-
}
|
|
194926
|
-
const res = await client.get("/api/v1/workflows", { name: idOrSlug });
|
|
194927
|
-
const match = res.data?.[0];
|
|
194928
|
-
if (!match) {
|
|
194929
|
-
const suggestion = await findSimilarSlug(client, idOrSlug);
|
|
194930
|
-
throw notFound(idOrSlug, suggestion);
|
|
194931
|
-
}
|
|
194932
|
-
return match.id;
|
|
194933
|
-
}
|
|
194934
|
-
|
|
194935
195354
|
// src/commands/workflow/clear.ts
|
|
194936
195355
|
import { existsSync as existsSync14, readdirSync as readdirSync5, rmSync as rmSync3, statSync as statSync4 } from "fs";
|
|
194937
195356
|
import { basename, join as join7 } from "path";
|
|
@@ -195225,125 +195644,6 @@ function registerEvaluatorTypeCommands(parent) {
|
|
|
195225
195644
|
});
|
|
195226
195645
|
}
|
|
195227
195646
|
|
|
195228
|
-
// src/lib/watch.ts
|
|
195229
|
-
var TERMINAL_STATUSES = new Set(["completed", "failed", "cancelled", "rejected"]);
|
|
195230
|
-
var STATUS_BADGES = {
|
|
195231
|
-
pending: "[ ]",
|
|
195232
|
-
running: "[..]",
|
|
195233
|
-
completed: "[ok]",
|
|
195234
|
-
failed: "[x]",
|
|
195235
|
-
cancelled: "[-]",
|
|
195236
|
-
skipped: "[s]"
|
|
195237
|
-
};
|
|
195238
|
-
function statusBadge(status2) {
|
|
195239
|
-
return STATUS_BADGES[status2] ?? `[${status2.slice(0, 3)}]`;
|
|
195240
|
-
}
|
|
195241
|
-
function durationSuffix(ms) {
|
|
195242
|
-
const formatted = formatDuration(ms);
|
|
195243
|
-
return formatted ? ` ${formatted}` : "";
|
|
195244
|
-
}
|
|
195245
|
-
function renderFrame(snapshot) {
|
|
195246
|
-
const lines = [
|
|
195247
|
-
`${statusBadge(snapshot.status)} ${snapshot.id}${durationSuffix(snapshot.durationMs)}`
|
|
195248
|
-
];
|
|
195249
|
-
for (const step of snapshot.stepExecutions ?? []) {
|
|
195250
|
-
const errSuffix = step.error ? ` err: ${step.error.slice(0, 80)}` : "";
|
|
195251
|
-
lines.push(` ${statusBadge(step.status)} ${step.stepName}${durationSuffix(step.durationMs)}${errSuffix}`);
|
|
195252
|
-
if (step.outputPreview) {
|
|
195253
|
-
lines.push(` ↳ ${step.outputPreview}`);
|
|
195254
|
-
}
|
|
195255
|
-
}
|
|
195256
|
-
if (snapshot.error && (snapshot.stepExecutions ?? []).length === 0) {
|
|
195257
|
-
lines.push(` err: ${snapshot.error.slice(0, 200)}`);
|
|
195258
|
-
}
|
|
195259
|
-
return lines.join(`
|
|
195260
|
-
`);
|
|
195261
|
-
}
|
|
195262
|
-
var ANSI_CURSOR_UP = "\x1B[A";
|
|
195263
|
-
var ANSI_CLEAR_LINE = "\x1B[2K\r";
|
|
195264
|
-
function makeAnsiRenderer() {
|
|
195265
|
-
const isTty = Boolean(process.stderr.isTTY);
|
|
195266
|
-
let previousLineCount = 0;
|
|
195267
|
-
return (frame) => {
|
|
195268
|
-
let buf = "";
|
|
195269
|
-
if (isTty) {
|
|
195270
|
-
for (let i2 = 0;i2 < previousLineCount; i2++) {
|
|
195271
|
-
buf += i2 === 0 ? ANSI_CLEAR_LINE : `${ANSI_CURSOR_UP}${ANSI_CLEAR_LINE}`;
|
|
195272
|
-
}
|
|
195273
|
-
}
|
|
195274
|
-
buf += frame;
|
|
195275
|
-
if (!frame.endsWith(`
|
|
195276
|
-
`))
|
|
195277
|
-
buf += `
|
|
195278
|
-
`;
|
|
195279
|
-
if (!isTty && previousLineCount > 0)
|
|
195280
|
-
buf += `
|
|
195281
|
-
`;
|
|
195282
|
-
process.stderr.write(buf);
|
|
195283
|
-
previousLineCount = frame.split(`
|
|
195284
|
-
`).length;
|
|
195285
|
-
};
|
|
195286
|
-
}
|
|
195287
|
-
function defaultSleep(ms) {
|
|
195288
|
-
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
195289
|
-
}
|
|
195290
|
-
function snapshotsDiffer(a, b2) {
|
|
195291
|
-
if (a.status !== b2.status)
|
|
195292
|
-
return true;
|
|
195293
|
-
const aSteps = a.stepExecutions ?? [];
|
|
195294
|
-
const bSteps = b2.stepExecutions ?? [];
|
|
195295
|
-
if (aSteps.length !== bSteps.length)
|
|
195296
|
-
return true;
|
|
195297
|
-
for (let i2 = 0;i2 < aSteps.length; i2++) {
|
|
195298
|
-
if (aSteps[i2].stepName !== bSteps[i2].stepName || aSteps[i2].status !== bSteps[i2].status) {
|
|
195299
|
-
return true;
|
|
195300
|
-
}
|
|
195301
|
-
}
|
|
195302
|
-
return false;
|
|
195303
|
-
}
|
|
195304
|
-
function defaultShouldAbort(err2) {
|
|
195305
|
-
return err2 instanceof ApiError && err2.status >= 400 && err2.status < 500;
|
|
195306
|
-
}
|
|
195307
|
-
async function watchExecution(opts) {
|
|
195308
|
-
const sleep = opts.sleep ?? defaultSleep;
|
|
195309
|
-
const now = opts.now ?? (() => Date.now());
|
|
195310
|
-
const render = opts.render ?? makeAnsiRenderer();
|
|
195311
|
-
const maxMs = opts.maxMs ?? 30 * 60 * 1000;
|
|
195312
|
-
const transitionInterval = opts.transitionIntervalMs ?? 2000;
|
|
195313
|
-
const steadyInterval = opts.steadyIntervalMs ?? 5000;
|
|
195314
|
-
const shouldAbort = opts.shouldAbort ?? defaultShouldAbort;
|
|
195315
|
-
const start = now();
|
|
195316
|
-
let previous;
|
|
195317
|
-
let lastFetched;
|
|
195318
|
-
while (true) {
|
|
195319
|
-
let current;
|
|
195320
|
-
try {
|
|
195321
|
-
current = await opts.fetch();
|
|
195322
|
-
} catch (err2) {
|
|
195323
|
-
if (shouldAbort(err2))
|
|
195324
|
-
throw err2;
|
|
195325
|
-
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
195326
|
-
render(`(network) ${message.slice(0, 120)}`);
|
|
195327
|
-
if (lastFetched && now() - start >= maxMs) {
|
|
195328
|
-
return { final: lastFetched, detached: true };
|
|
195329
|
-
}
|
|
195330
|
-
await sleep(steadyInterval);
|
|
195331
|
-
continue;
|
|
195332
|
-
}
|
|
195333
|
-
lastFetched = current;
|
|
195334
|
-
render(renderFrame(current));
|
|
195335
|
-
if (TERMINAL_STATUSES.has(current.status)) {
|
|
195336
|
-
return { final: current, detached: false };
|
|
195337
|
-
}
|
|
195338
|
-
if (now() - start >= maxMs) {
|
|
195339
|
-
return { final: current, detached: true };
|
|
195340
|
-
}
|
|
195341
|
-
const transitioned = previous == null || snapshotsDiffer(previous, current);
|
|
195342
|
-
previous = current;
|
|
195343
|
-
await sleep(transitioned ? transitionInterval : steadyInterval);
|
|
195344
|
-
}
|
|
195345
|
-
}
|
|
195346
|
-
|
|
195347
195647
|
// src/commands/workflow/exec.ts
|
|
195348
195648
|
import { join as join11 } from "path";
|
|
195349
195649
|
|
|
@@ -195532,7 +195832,8 @@ var DEFAULT_CONCURRENCY = 3;
|
|
|
195532
195832
|
async function pollExecution(client, executionId) {
|
|
195533
195833
|
const start = Date.now();
|
|
195534
195834
|
while (Date.now() - start < MAX_POLL_MS) {
|
|
195535
|
-
const
|
|
195835
|
+
const payload = await client.get(`/api/v1/runs/${executionId}?include=detail`);
|
|
195836
|
+
const res = "run" in payload && payload.run ? payload.run : payload;
|
|
195536
195837
|
if (["completed", "failed", "cancelled", "rejected"].includes(res.status))
|
|
195537
195838
|
return res;
|
|
195538
195839
|
await new Promise((r5) => setTimeout(r5, POLL_INTERVAL_MS));
|
|
@@ -195610,7 +195911,8 @@ async function runExec(client, dir, workflowIdOrSlug, exampleNames, options8 = {
|
|
|
195610
195911
|
else
|
|
195611
195912
|
failed++;
|
|
195612
195913
|
try {
|
|
195613
|
-
const
|
|
195914
|
+
const artifactPayload = await client.get(`/api/v1/runs/${executionId}?include=detail`);
|
|
195915
|
+
const artifact = "run" in artifactPayload && artifactPayload.run ? artifactPayload.run : artifactPayload;
|
|
195614
195916
|
const artifactDir = await writeExecutionArtifacts(client, exampleDir, artifact);
|
|
195615
195917
|
if (!interactive)
|
|
195616
195918
|
console.log(ui.dim(` Artifacts: ${artifactDir}`));
|
|
@@ -195637,14 +195939,13 @@ ${ui.bold("Results:")} ${ui.ok(String(passed))} passed, ${failed > 0 ? ui.err(St
|
|
|
195637
195939
|
}
|
|
195638
195940
|
|
|
195639
195941
|
// src/commands/workflow/execution.ts
|
|
195640
|
-
function
|
|
195641
|
-
const
|
|
195642
|
-
const runCmd = execution.command("run <workflow-id> [examples...]").description("Run a saved workflow against local dataset examples.").option("--dir <dir>", "Local eigenpal directory", undefined).option("--concurrency <n>", "Max examples to run in parallel (default: 3)", intArg).addHelpText("after", `
|
|
195942
|
+
function registerWorkflowRunCommand(parent) {
|
|
195943
|
+
const runCmd = parent.command("run <workflow-id> [examples...]").description("Run a saved workflow against local dataset examples.").option("--dir <dir>", "Local eigenpal directory", undefined).option("--concurrency <n>", "Max examples to run in parallel (default: 3)", intArg).addHelpText("after", `
|
|
195643
195944
|
Examples:
|
|
195644
|
-
$ eigenpal workflow
|
|
195645
|
-
$ eigenpal workflow
|
|
195646
|
-
$ eigenpal workflow
|
|
195647
|
-
$ eigenpal workflow
|
|
195945
|
+
$ eigenpal workflow run wf_abc123 # all examples
|
|
195946
|
+
$ eigenpal workflow run wf_abc123 sample-1 sample-2
|
|
195947
|
+
$ eigenpal workflow run wf_abc123 --concurrency 5
|
|
195948
|
+
$ eigenpal workflow run wf_abc123 sample --json | jq '.passed'
|
|
195648
195949
|
|
|
195649
195950
|
Reads examples from ./dataset/examples/<example>/ and writes per-run
|
|
195650
195951
|
artifacts to ./dataset/examples/<example>/executions/<timestamp>/.
|
|
@@ -195671,272 +195972,6 @@ Exits 1 when any example fails.
|
|
|
195671
195972
|
formatEigenpalDirIfAvailable(config2.dir);
|
|
195672
195973
|
}
|
|
195673
195974
|
});
|
|
195674
|
-
const getCmd = execution.command("get <executionId>").description("Fetch a single execution payload. Optionally narrow to one step.").option("--step <name>", "Show only this step (or comma-separated list)").option("--include <kinds>", "Comma-separated subset of input,output,error,duration. `input` projects the resolved templated config (= what the processor actually received). `inputRef` returns the minimal predecessor-id reference instead.", "input,output,error,duration").addHelpText("after", "\nFor field projection, pipe `--json` through real jq:\n" + ` $ eigenpal workflow execution get exec_… --json | jq '.status'
|
|
195675
|
-
` + ` $ eigenpal workflow execution get exec_… --json | jq '.stepExecutions[] | select(.status=="failed")'
|
|
195676
|
-
`);
|
|
195677
|
-
addJsonFlag(withBaseUrl(getCmd)).action(getExecution);
|
|
195678
|
-
const listCmd = execution.command("list <workflow-id>").description("List recent executions for a workflow.").option("--status <status>", "Filter by status: pending|running|completed|failed|cancelled");
|
|
195679
|
-
addJsonFlag(withBaseUrl(withPagination(listCmd))).action(listExecutions);
|
|
195680
|
-
const watchCmd = execution.command("watch <executionId>").description("Stream live execution status until terminal or 30-min detach.").option("--max-wait <seconds>", "Detach after N seconds (default 1800 = 30 min)", intArg, 30 * 60);
|
|
195681
|
-
withBaseUrl(watchCmd).action(watchCommand);
|
|
195682
|
-
const compareCmd = execution.command("compare <executionA> <executionB>").description("Diff two executions side-by-side, per step.").option("--step <name>", "Restrict comparison to one step");
|
|
195683
|
-
withBaseUrl(compareCmd).action(compareExecutions);
|
|
195684
|
-
const cancelCmd = execution.command("cancel <executionId>").description("Cancel an execution. Idempotent on already-terminal runs.").option("--yes", "Required for non-TTY shells (CI, pipes). Acts immediately, no prompt.").addHelpText("after", `
|
|
195685
|
-
Examples:
|
|
195686
|
-
$ eigenpal workflow execution cancel exec_123abc
|
|
195687
|
-
$ eigenpal workflow execution cancel exec_123abc --yes # required in CI
|
|
195688
|
-
$ eigenpal workflow execution cancel exec_123abc --json # raw server payload
|
|
195689
|
-
|
|
195690
|
-
Behavior:
|
|
195691
|
-
- created/pending: transitions straight to cancelled.
|
|
195692
|
-
- running/waiting: stamps cancelRequestedAt; the worker observes it
|
|
195693
|
-
between step transitions and exits cleanly.
|
|
195694
|
-
- already terminal (completed/failed/cancelled): no-op, exits 0 with an
|
|
195695
|
-
info line. Idempotent — safe to retry.
|
|
195696
|
-
`);
|
|
195697
|
-
addJsonFlag(withBaseUrl(cancelCmd)).action(async (executionId, opts) => {
|
|
195698
|
-
const config2 = resolveConfig(opts);
|
|
195699
|
-
requireApiKey(config2);
|
|
195700
|
-
const client = new ApiClient(config2);
|
|
195701
|
-
try {
|
|
195702
|
-
await cancelExecution(client, executionId, opts);
|
|
195703
|
-
} catch (err2) {
|
|
195704
|
-
error51(err2 instanceof Error ? err2.message : String(err2));
|
|
195705
|
-
process.exit(1);
|
|
195706
|
-
}
|
|
195707
|
-
});
|
|
195708
|
-
}
|
|
195709
|
-
async function cancelExecution(client, executionId, opts) {
|
|
195710
|
-
if (!opts.yes && !isTTY()) {
|
|
195711
|
-
throw new Error("cancel is destructive and requires --yes when run non-interactively");
|
|
195712
|
-
}
|
|
195713
|
-
const result = await client.post(`/api/v1/workflows/executions/${executionId}/cancel`);
|
|
195714
|
-
if (opts.json) {
|
|
195715
|
-
console.log(JSON.stringify(result, null, 2));
|
|
195716
|
-
return;
|
|
195717
|
-
}
|
|
195718
|
-
const status2 = result.status;
|
|
195719
|
-
if (status2 === "already-terminal") {
|
|
195720
|
-
info(`already terminal: ${result.wasStatus ?? "unknown"}`);
|
|
195721
|
-
return;
|
|
195722
|
-
}
|
|
195723
|
-
if (status2 === "cancelled" || status2 === "cancellation-requested") {
|
|
195724
|
-
success2("cancellation requested");
|
|
195725
|
-
return;
|
|
195726
|
-
}
|
|
195727
|
-
info(`server returned status: ${String(status2 ?? "unknown")}`);
|
|
195728
|
-
}
|
|
195729
|
-
async function getExecution(executionId, opts) {
|
|
195730
|
-
const config2 = resolveConfig(opts);
|
|
195731
|
-
requireApiKey(config2);
|
|
195732
|
-
const client = new ApiClient(config2);
|
|
195733
|
-
try {
|
|
195734
|
-
const result = await client.get(`/api/v1/workflows/executions/${executionId}?includeSteps=true`);
|
|
195735
|
-
if (opts.step) {
|
|
195736
|
-
const wanted = new Set(opts.step.split(",").map((s5) => s5.trim()));
|
|
195737
|
-
const filtered = (result.stepExecutions ?? []).filter((s5) => wanted.has(s5.stepName));
|
|
195738
|
-
console.log(JSON.stringify({ stepExecutions: filtered.map((s5) => filterIncludes(s5, opts.include)) }, null, 2));
|
|
195739
|
-
return;
|
|
195740
|
-
}
|
|
195741
|
-
if (opts.json) {
|
|
195742
|
-
console.log(JSON.stringify({
|
|
195743
|
-
...result,
|
|
195744
|
-
stepExecutions: (result.stepExecutions ?? []).map((s5) => filterIncludes(s5, opts.include))
|
|
195745
|
-
}, null, 2));
|
|
195746
|
-
return;
|
|
195747
|
-
}
|
|
195748
|
-
const snapshot = {
|
|
195749
|
-
id: result.id ?? executionId,
|
|
195750
|
-
status: result.status ?? "unknown",
|
|
195751
|
-
startedAt: result.startedAt ?? null,
|
|
195752
|
-
completedAt: result.completedAt ?? null,
|
|
195753
|
-
durationMs: result.durationMs ?? null,
|
|
195754
|
-
stepExecutions: (result.stepExecutions ?? []).map((s5) => ({
|
|
195755
|
-
stepName: s5.stepName,
|
|
195756
|
-
status: s5.status,
|
|
195757
|
-
durationMs: s5.durationMs,
|
|
195758
|
-
error: s5.error,
|
|
195759
|
-
outputPreview: previewOutput(s5.outputData)
|
|
195760
|
-
})),
|
|
195761
|
-
error: result.error ?? null
|
|
195762
|
-
};
|
|
195763
|
-
console.log(renderFrame(snapshot));
|
|
195764
|
-
process.stderr.write(`${ui.dim(`use --json for the full payload, then pipe to jq for field projection`)}
|
|
195765
|
-
`);
|
|
195766
|
-
} catch (err2) {
|
|
195767
|
-
error51(err2 instanceof Error ? err2.message : String(err2));
|
|
195768
|
-
process.exit(1);
|
|
195769
|
-
}
|
|
195770
|
-
}
|
|
195771
|
-
async function listExecutions(workflow2, opts) {
|
|
195772
|
-
const config2 = resolveConfig(opts);
|
|
195773
|
-
requireApiKey(config2);
|
|
195774
|
-
const client = new ApiClient(config2);
|
|
195775
|
-
const workflowId = await resolveWorkflowId(client, workflow2);
|
|
195776
|
-
const params = {
|
|
195777
|
-
limit: String(opts.limit),
|
|
195778
|
-
offset: String(opts.offset),
|
|
195779
|
-
workflowId
|
|
195780
|
-
};
|
|
195781
|
-
if (opts.status)
|
|
195782
|
-
params.status = opts.status;
|
|
195783
|
-
try {
|
|
195784
|
-
const raw = await client.get(`/api/v1/workflows/${workflowId}/executions`, params);
|
|
195785
|
-
renderListResult(raw, [
|
|
195786
|
-
{ key: "id", header: "id" },
|
|
195787
|
-
{ key: "status", header: "status" },
|
|
195788
|
-
{ key: "startedAt", header: "startedAt", format: formatTimestamp },
|
|
195789
|
-
{
|
|
195790
|
-
key: "durationMs",
|
|
195791
|
-
header: "duration",
|
|
195792
|
-
align: "right",
|
|
195793
|
-
format: (v11) => typeof v11 === "number" ? formatDuration(v11) : "-"
|
|
195794
|
-
}
|
|
195795
|
-
], { json: opts.json, entityLabel: "execution" });
|
|
195796
|
-
} catch (err2) {
|
|
195797
|
-
error51(err2 instanceof Error ? err2.message : String(err2));
|
|
195798
|
-
process.exit(1);
|
|
195799
|
-
}
|
|
195800
|
-
}
|
|
195801
|
-
async function watchCommand(executionId, opts) {
|
|
195802
|
-
const config2 = resolveConfig(opts);
|
|
195803
|
-
requireApiKey(config2);
|
|
195804
|
-
const client = new ApiClient(config2);
|
|
195805
|
-
try {
|
|
195806
|
-
const result = await watchExecution({
|
|
195807
|
-
fetch: () => client.get(`/api/v1/workflows/executions/${executionId}?includeSteps=true`),
|
|
195808
|
-
maxMs: opts.maxWait * 1000
|
|
195809
|
-
});
|
|
195810
|
-
if (result.detached) {
|
|
195811
|
-
process.stderr.write(`
|
|
195812
|
-
Detached after ${opts.maxWait}s without reaching terminal status. Re-run \`eigenpal workflow execution watch ${executionId}\` to resume.
|
|
195813
|
-
`);
|
|
195814
|
-
process.exit(2);
|
|
195815
|
-
}
|
|
195816
|
-
if (result.final.status === "failed" || result.final.status === "cancelled") {
|
|
195817
|
-
process.exit(1);
|
|
195818
|
-
}
|
|
195819
|
-
} catch (err2) {
|
|
195820
|
-
error51(err2 instanceof Error ? err2.message : String(err2));
|
|
195821
|
-
process.exit(1);
|
|
195822
|
-
}
|
|
195823
|
-
}
|
|
195824
|
-
async function compareExecutions(idA, idB, opts) {
|
|
195825
|
-
const config2 = resolveConfig(opts);
|
|
195826
|
-
requireApiKey(config2);
|
|
195827
|
-
const client = new ApiClient(config2);
|
|
195828
|
-
try {
|
|
195829
|
-
const [a5, b8] = await Promise.all([
|
|
195830
|
-
client.get(`/api/v1/workflows/executions/${idA}?includeSteps=true`),
|
|
195831
|
-
client.get(`/api/v1/workflows/executions/${idB}?includeSteps=true`)
|
|
195832
|
-
]);
|
|
195833
|
-
const stepNames = new Set;
|
|
195834
|
-
for (const s5 of a5.stepExecutions ?? [])
|
|
195835
|
-
stepNames.add(s5.stepName);
|
|
195836
|
-
for (const s5 of b8.stepExecutions ?? [])
|
|
195837
|
-
stepNames.add(s5.stepName);
|
|
195838
|
-
const ordered = Array.from(stepNames).sort();
|
|
195839
|
-
const targetSteps = opts.step ? ordered.filter((n5) => n5 === opts.step) : ordered;
|
|
195840
|
-
console.log(`${ui.dim("A =")} ${ui.bold(idA)} ${ui.dim("status=")}${colorStatus(a5.status)} ${ui.dim("durationMs=")}${a5.durationMs ?? "-"}`);
|
|
195841
|
-
console.log(`${ui.dim("B =")} ${ui.bold(idB)} ${ui.dim("status=")}${colorStatus(b8.status)} ${ui.dim("durationMs=")}${b8.durationMs ?? "-"}`);
|
|
195842
|
-
console.log("");
|
|
195843
|
-
console.log(ui.dim(`step A status B status Δ duration output`));
|
|
195844
|
-
console.log(ui.dim(`──────────────────────── ───────── ───────── ────────── ──────`));
|
|
195845
|
-
for (const stepName of targetSteps) {
|
|
195846
|
-
const aStep = (a5.stepExecutions ?? []).find((s5) => s5.stepName === stepName);
|
|
195847
|
-
const bStep = (b8.stepExecutions ?? []).find((s5) => s5.stepName === stepName);
|
|
195848
|
-
const aStatus = aStep?.status ?? "—";
|
|
195849
|
-
const bStatus = bStep?.status ?? "—";
|
|
195850
|
-
const aDur = aStep?.durationMs ?? null;
|
|
195851
|
-
const bDur = bStep?.durationMs ?? null;
|
|
195852
|
-
const delta = aDur != null && bDur != null ? `${bDur - aDur >= 0 ? "+" : ""}${bDur - aDur}ms` : "—";
|
|
195853
|
-
const outputState = compareOutputs(aStep?.output, bStep?.output);
|
|
195854
|
-
console.log(`${stepName.padEnd(28)} ${padColored(colorStatus(aStatus), aStatus, 11)} ${padColored(colorStatus(bStatus), bStatus, 11)} ${delta.padEnd(12)} ${colorOutputState(outputState)}`);
|
|
195855
|
-
}
|
|
195856
|
-
const finalState = compareOutputs(a5.output, b8.output);
|
|
195857
|
-
console.log("");
|
|
195858
|
-
console.log(`${ui.dim("final output:")} ${colorOutputState(finalState)}`);
|
|
195859
|
-
} catch (err2) {
|
|
195860
|
-
error51(err2 instanceof Error ? err2.message : String(err2));
|
|
195861
|
-
process.exit(1);
|
|
195862
|
-
}
|
|
195863
|
-
}
|
|
195864
|
-
function padColored(colored, plain, width) {
|
|
195865
|
-
const pad = Math.max(0, width - plain.length);
|
|
195866
|
-
return colored + " ".repeat(pad);
|
|
195867
|
-
}
|
|
195868
|
-
function colorStatus(status2) {
|
|
195869
|
-
if (!status2)
|
|
195870
|
-
return "—";
|
|
195871
|
-
if (status2 === "completed")
|
|
195872
|
-
return ui.ok(status2);
|
|
195873
|
-
if (status2 === "failed" || status2 === "cancelled")
|
|
195874
|
-
return ui.err(status2);
|
|
195875
|
-
if (status2 === "running" || status2 === "pending")
|
|
195876
|
-
return ui.info(status2);
|
|
195877
|
-
return status2;
|
|
195878
|
-
}
|
|
195879
|
-
function colorOutputState(state) {
|
|
195880
|
-
if (state === "identical")
|
|
195881
|
-
return ui.ok(state);
|
|
195882
|
-
if (state === "—")
|
|
195883
|
-
return ui.dim(state);
|
|
195884
|
-
if (state.startsWith("differs"))
|
|
195885
|
-
return ui.warn(state);
|
|
195886
|
-
return state;
|
|
195887
|
-
}
|
|
195888
|
-
function compareOutputs(a5, b8) {
|
|
195889
|
-
if (a5 === undefined && b8 === undefined)
|
|
195890
|
-
return "—";
|
|
195891
|
-
if (a5 === undefined)
|
|
195892
|
-
return "B only";
|
|
195893
|
-
if (b8 === undefined)
|
|
195894
|
-
return "A only";
|
|
195895
|
-
const aJson = JSON.stringify(a5);
|
|
195896
|
-
const bJson = JSON.stringify(b8);
|
|
195897
|
-
if (aJson === bJson)
|
|
195898
|
-
return "identical";
|
|
195899
|
-
return `differs (A=${aJson.length}b, B=${bJson.length}b)`;
|
|
195900
|
-
}
|
|
195901
|
-
var PREVIEW_MAX = 120;
|
|
195902
|
-
function previewOutput(value) {
|
|
195903
|
-
if (value == null)
|
|
195904
|
-
return null;
|
|
195905
|
-
if (typeof value === "string") {
|
|
195906
|
-
if (value.length === 0)
|
|
195907
|
-
return null;
|
|
195908
|
-
return value.length > PREVIEW_MAX ? `${value.slice(0, PREVIEW_MAX - 1)}…` : value;
|
|
195909
|
-
}
|
|
195910
|
-
let json2;
|
|
195911
|
-
try {
|
|
195912
|
-
json2 = JSON.stringify(value);
|
|
195913
|
-
} catch {
|
|
195914
|
-
return null;
|
|
195915
|
-
}
|
|
195916
|
-
if (!json2 || json2 === "{}" || json2 === "[]" || json2 === "null")
|
|
195917
|
-
return null;
|
|
195918
|
-
const flat = json2.replace(/\s+/g, " ");
|
|
195919
|
-
return flat.length > PREVIEW_MAX ? `${flat.slice(0, PREVIEW_MAX - 1)}…` : flat;
|
|
195920
|
-
}
|
|
195921
|
-
function filterIncludes(step, includes) {
|
|
195922
|
-
if (!includes)
|
|
195923
|
-
return step;
|
|
195924
|
-
const wanted = new Set(includes.split(",").map((s5) => s5.trim()));
|
|
195925
|
-
const out = { stepName: step.stepName, status: step.status };
|
|
195926
|
-
if (wanted.has("input") || wanted.has("config") || wanted.has("resolvedConfig")) {
|
|
195927
|
-
out.input = step.resolvedConfig;
|
|
195928
|
-
}
|
|
195929
|
-
if (wanted.has("inputRef") || wanted.has("inputData"))
|
|
195930
|
-
out.inputRef = step.inputData;
|
|
195931
|
-
if (wanted.has("output") || wanted.has("outputData"))
|
|
195932
|
-
out.output = step.outputData;
|
|
195933
|
-
if (wanted.has("error"))
|
|
195934
|
-
out.error = step.error;
|
|
195935
|
-
if (wanted.has("duration"))
|
|
195936
|
-
out.durationMs = step.durationMs;
|
|
195937
|
-
if ("overrideMode" in step)
|
|
195938
|
-
out.overrideMode = step.overrideMode;
|
|
195939
|
-
return out;
|
|
195940
195975
|
}
|
|
195941
195976
|
|
|
195942
195977
|
// src/commands/workflow/experiment-compare.ts
|
|
@@ -196226,13 +196261,13 @@ function exitUnsupported(type) {
|
|
|
196226
196261
|
` + ` redesign in EIG-104.
|
|
196227
196262
|
|
|
196228
196263
|
` + ` To run a single step today, push the workflow and run it via:
|
|
196229
|
-
` + ` $ eigenpal workflow
|
|
196264
|
+
` + ` $ eigenpal workflow run <workflow-id>
|
|
196230
196265
|
` + ` $ eigenpal workflow experiment run <workflow-id> --example-id <name>`);
|
|
196231
196266
|
process.exit(UNSUPPORTED_TYPE_EXIT);
|
|
196232
196267
|
}
|
|
196233
196268
|
function registerStepExecCommands(workflow2) {
|
|
196234
196269
|
const step = workflow2.command("step").description("Run a single workflow step (placeholder; see EIG-104).");
|
|
196235
|
-
step.command("exec <type>").description("DISABLED — local mimic runners removed pending server-side redesign (EIG-104). " + "Use `workflow
|
|
196270
|
+
step.command("exec <type>").description("DISABLED — local mimic runners removed pending server-side redesign (EIG-104). " + "Use `workflow run` or `workflow experiment run` instead.").option("--config-json <json>", "(unused; kept for back-compat parsing)").option("--config-file <path>", "(unused)").option("--inputs <pairs...>", "(unused)").option("--output-schema <path>", "(unused)").action(action(async (rawType) => {
|
|
196236
196271
|
const known = listStepTypes();
|
|
196237
196272
|
if (!known.includes(rawType)) {
|
|
196238
196273
|
error51(`Unknown step type: ${rawType}. Run \`eigenpal workflow step-type list\`.`);
|
|
@@ -197138,7 +197173,7 @@ YAML's \`name:\` field). Both:
|
|
|
197138
197173
|
registerEvaluatorsCommands(workflow2);
|
|
197139
197174
|
registerDatasetCommands2(workflow2);
|
|
197140
197175
|
registerExperimentCommands2(workflow2);
|
|
197141
|
-
|
|
197176
|
+
registerWorkflowRunCommand(workflow2);
|
|
197142
197177
|
registerVersionsCommands(workflow2);
|
|
197143
197178
|
registerStepTypeCommands(workflow2);
|
|
197144
197179
|
registerEvaluatorTypeCommands(workflow2);
|
|
@@ -197962,7 +197997,7 @@ ${ui.bold(`✗ ${failedExecs.length}/${totalCount} did not pass`)}
|
|
|
197962
197997
|
const reason = (e8.error ?? "(no error message — fetch full execution)").replace(/\s+/g, " ").slice(0, 200);
|
|
197963
197998
|
process.stderr.write(` ${ui.bold(label2)} ${ui.dim(`[${e8.status}]`)} ${reason}
|
|
197964
197999
|
`);
|
|
197965
|
-
process.stderr.write(` ${ui.dim(`eigenpal
|
|
198000
|
+
process.stderr.write(` ${ui.dim(`eigenpal runs get ${e8.id}`)}
|
|
197966
198001
|
`);
|
|
197967
198002
|
}
|
|
197968
198003
|
process.stderr.write(`
|
|
@@ -198336,7 +198371,7 @@ var HELP_GROUPS = [
|
|
|
198336
198371
|
},
|
|
198337
198372
|
{
|
|
198338
198373
|
title: "Workflows",
|
|
198339
|
-
description: "Definition, evaluators, dataset, experiments,
|
|
198374
|
+
description: "Definition, evaluators, dataset, experiments, and local runs",
|
|
198340
198375
|
commands: ["workflow"]
|
|
198341
198376
|
},
|
|
198342
198377
|
{
|
|
@@ -198507,7 +198542,7 @@ program2.name("eigenpal").description("Eigenpal CLI — AI workflows, evals, and
|
|
|
198507
198542
|
setQuiet(true);
|
|
198508
198543
|
});
|
|
198509
198544
|
program2.command("status").description("One-shot dashboard: server, active tenant, user, key id, workflow count. Pair with `--json` for scripting.").option("--base-url <url>", "Server base URL").option("--json", "Emit machine-readable JSON instead of human-readable text").action(action(async (opts) => status(opts)));
|
|
198510
|
-
var initCmd = program2.command("init [name]").description("Scaffold a new workflow project. Without `[name]`, scaffolds into the current directory using the cwd basename as the workflow name. With `[name]`, creates `./<name>/` and uses that as the slug. The flat layout matches what `workflow
|
|
198545
|
+
var initCmd = program2.command("init [name]").description("Scaffold a new workflow project. Without `[name]`, scaffolds into the current directory using the cwd basename as the workflow name. With `[name]`, creates `./<name>/` and uses that as the slug. The flat layout matches what `workflow run <slug>` already discovers — no manual file moves.").option("--template <name>", "Skip the picker; use this template").option("--dir <dir>", "Target directory (default: cwd if `[name]` omitted, else ./<name>)").option("--yes", "Non-interactive: pick the default template (blank)").action(async (name, opts) => {
|
|
198511
198546
|
try {
|
|
198512
198547
|
if (name) {
|
|
198513
198548
|
await init(name, opts);
|
|
@@ -198554,6 +198589,7 @@ authCmd.command("use [profile]").description("Switch the active profile (persist
|
|
|
198554
198589
|
});
|
|
198555
198590
|
registerWorkflowCommands(program2);
|
|
198556
198591
|
registerAgentCommands(program2);
|
|
198592
|
+
registerRunsCommands(program2);
|
|
198557
198593
|
registerGitCommands(program2);
|
|
198558
198594
|
program2.command("completion <shell>").description("Print a shell completion script to stdout (supported shells: bash, zsh, fish). Pipe into the appropriate completion file for your shell — see the script header for install instructions.").action(async (shell) => {
|
|
198559
198595
|
try {
|