@eigenpal/cli 0.6.8 → 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 +1362 -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
|
@@ -19690,6 +19690,15 @@ var init_otel = __esm(() => {
|
|
|
19690
19690
|
});
|
|
19691
19691
|
|
|
19692
19692
|
// ../types/src/execution-row.ts
|
|
19693
|
+
function executionPhaseDurationMs(startedAt, completedAt) {
|
|
19694
|
+
return executionWallClockMs(startedAt, completedAt);
|
|
19695
|
+
}
|
|
19696
|
+
function publicExecutionFailure(failure) {
|
|
19697
|
+
if (!failure)
|
|
19698
|
+
return;
|
|
19699
|
+
const { phase, code, category, provider, retryable, userMessage, occurredAt } = failure;
|
|
19700
|
+
return { phase, code, category, provider, retryable, userMessage, occurredAt };
|
|
19701
|
+
}
|
|
19693
19702
|
function isTerminalExecutionStatus(status) {
|
|
19694
19703
|
return status != null && TERMINAL_STATUS_SET.has(status);
|
|
19695
19704
|
}
|
|
@@ -19706,7 +19715,7 @@ function executionWallClockMs(startedAt, completedAt) {
|
|
|
19706
19715
|
const ms = end - start;
|
|
19707
19716
|
return ms >= 0 ? ms : null;
|
|
19708
19717
|
}
|
|
19709
|
-
var DURABLE_EXECUTION_TYPES, EXECUTION_STATUSES, 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;
|
|
19710
19719
|
var init_execution_row = __esm(() => {
|
|
19711
19720
|
DURABLE_EXECUTION_TYPES = ["workflow", "agent"];
|
|
19712
19721
|
EXECUTION_STATUSES = [
|
|
@@ -19720,6 +19729,43 @@ var init_execution_row = __esm(() => {
|
|
|
19720
19729
|
"cancelled",
|
|
19721
19730
|
"rejected"
|
|
19722
19731
|
];
|
|
19732
|
+
EXECUTION_PHASES = [
|
|
19733
|
+
"queued",
|
|
19734
|
+
"starting",
|
|
19735
|
+
"preparing_inputs",
|
|
19736
|
+
"preparing_environment",
|
|
19737
|
+
"installing_dependencies",
|
|
19738
|
+
"running",
|
|
19739
|
+
"collecting_outputs",
|
|
19740
|
+
"finalizing"
|
|
19741
|
+
];
|
|
19742
|
+
EXECUTION_PHASE_ORDER = {
|
|
19743
|
+
queued: 10,
|
|
19744
|
+
starting: 20,
|
|
19745
|
+
preparing_inputs: 30,
|
|
19746
|
+
preparing_environment: 40,
|
|
19747
|
+
installing_dependencies: 50,
|
|
19748
|
+
running: 60,
|
|
19749
|
+
collecting_outputs: 70,
|
|
19750
|
+
finalizing: 80
|
|
19751
|
+
};
|
|
19752
|
+
EXECUTION_PHASE_STATUSES = [
|
|
19753
|
+
"pending",
|
|
19754
|
+
"running",
|
|
19755
|
+
"completed",
|
|
19756
|
+
"failed",
|
|
19757
|
+
"skipped",
|
|
19758
|
+
"cancelled"
|
|
19759
|
+
];
|
|
19760
|
+
EXECUTION_FAILURE_CATEGORIES = [
|
|
19761
|
+
"input",
|
|
19762
|
+
"source",
|
|
19763
|
+
"environment",
|
|
19764
|
+
"runtime",
|
|
19765
|
+
"output",
|
|
19766
|
+
"finalization",
|
|
19767
|
+
"system"
|
|
19768
|
+
];
|
|
19723
19769
|
TERMINAL_EXECUTION_STATUSES = [
|
|
19724
19770
|
"completed",
|
|
19725
19771
|
"failed",
|
|
@@ -66739,6 +66785,7 @@ __export(exports_src, {
|
|
|
66739
66785
|
rootS3Path: () => rootS3Path,
|
|
66740
66786
|
resolveEffectiveMimeType: () => resolveEffectiveMimeType,
|
|
66741
66787
|
requiresSpecializedParser: () => requiresSpecializedParser,
|
|
66788
|
+
publicExecutionFailure: () => publicExecutionFailure,
|
|
66742
66789
|
projectAgentInputFiles: () => projectAgentInputFiles,
|
|
66743
66790
|
pathToDottedPackageName: () => pathToDottedPackageName,
|
|
66744
66791
|
parseStepType: () => parseStepType,
|
|
@@ -66791,6 +66838,7 @@ __export(exports_src, {
|
|
|
66791
66838
|
fieldStemFromGoldenName: () => fieldStemFromGoldenName,
|
|
66792
66839
|
extractFieldMetadata: () => extractFieldMetadata,
|
|
66793
66840
|
executionWallClockMs: () => executionWallClockMs,
|
|
66841
|
+
executionPhaseDurationMs: () => executionPhaseDurationMs,
|
|
66794
66842
|
evalInputKey: () => evalInputKey,
|
|
66795
66843
|
evalExpectedKey: () => evalExpectedKey,
|
|
66796
66844
|
errorHintFor: () => errorHintFor,
|
|
@@ -66910,6 +66958,7 @@ __export(exports_src, {
|
|
|
66910
66958
|
S3_PATH_GRAMMAR: () => S3_PATH_GRAMMAR,
|
|
66911
66959
|
RootSourceManifestSchema: () => RootSourceManifestSchema,
|
|
66912
66960
|
ReleaseTagSchema: () => ReleaseTagSchema,
|
|
66961
|
+
RUNS_TRIGGERED_BY_SYSTEM: () => RUNS_TRIGGERED_BY_SYSTEM,
|
|
66913
66962
|
ProcessorPortSchema: () => ProcessorPortSchema,
|
|
66914
66963
|
ProcessorExecutionContextSchema: () => ProcessorExecutionContextSchema,
|
|
66915
66964
|
ProcessorDefinitionSchema: () => ProcessorDefinitionSchema,
|
|
@@ -66996,7 +67045,11 @@ __export(exports_src, {
|
|
|
66996
67045
|
EXECUTION_TYPES: () => EXECUTION_TYPES,
|
|
66997
67046
|
EXECUTION_STATUSES: () => EXECUTION_STATUSES,
|
|
66998
67047
|
EXECUTION_SHARED_CANCEL_REQUESTABLE_STATUSES: () => EXECUTION_SHARED_CANCEL_REQUESTABLE_STATUSES,
|
|
67048
|
+
EXECUTION_PHASE_STATUSES: () => EXECUTION_PHASE_STATUSES,
|
|
67049
|
+
EXECUTION_PHASE_ORDER: () => EXECUTION_PHASE_ORDER,
|
|
67050
|
+
EXECUTION_PHASES: () => EXECUTION_PHASES,
|
|
66999
67051
|
EXECUTION_NON_TERMINAL_STATUSES: () => EXECUTION_NON_TERMINAL_STATUSES,
|
|
67052
|
+
EXECUTION_FAILURE_CATEGORIES: () => EXECUTION_FAILURE_CATEGORIES,
|
|
67000
67053
|
EXECUTION_FAILED_OR_CANCELLED_STATUSES: () => EXECUTION_FAILED_OR_CANCELLED_STATUSES,
|
|
67001
67054
|
EXECUTION_EVAL_RUNNING_STATUSES: () => EXECUTION_EVAL_RUNNING_STATUSES,
|
|
67002
67055
|
EXECUTION_CONCURRENCY_ACTIVE_STATUSES: () => EXECUTION_CONCURRENCY_ACTIVE_STATUSES,
|
|
@@ -188719,7 +188772,7 @@ import { fileURLToPath as fileURLToPath6 } from "url";
|
|
|
188719
188772
|
// package.json
|
|
188720
188773
|
var package_default = {
|
|
188721
188774
|
name: "@eigenpal/cli",
|
|
188722
|
-
version: "0.6.
|
|
188775
|
+
version: "0.6.10",
|
|
188723
188776
|
description: "Eigenpal CLI — author and run document workflows from your terminal. Agent-ready.",
|
|
188724
188777
|
keywords: [
|
|
188725
188778
|
"eigenpal",
|
|
@@ -191249,7 +191302,7 @@ function registerAgentSourceCommands(agent) {
|
|
|
191249
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));
|
|
191250
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));
|
|
191251
191304
|
registerSourceInitCommand(agent);
|
|
191252
|
-
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));
|
|
191253
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));
|
|
191254
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));
|
|
191255
191308
|
withBaseUrl(agent.command("push")).description("Push the current organization source branch and tags.").option("--dir <dir>", "Repository directory").action(action(pushSource));
|
|
@@ -191270,12 +191323,240 @@ function registerGitCommands(program2) {
|
|
|
191270
191323
|
credentialHelper.hidden = true;
|
|
191271
191324
|
}
|
|
191272
191325
|
|
|
191273
|
-
// 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";
|
|
191274
191330
|
import path8 from "node:path";
|
|
191275
191331
|
|
|
191276
|
-
// src/
|
|
191277
|
-
|
|
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";
|
|
191278
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";
|
|
191279
191560
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
191280
191561
|
var PACKAGE_MANIFEST = "eigenpal.yaml";
|
|
191281
191562
|
var DATASET_DIR = "dataset";
|
|
@@ -191299,17 +191580,17 @@ async function readFilesUnder(dir) {
|
|
|
191299
191580
|
return [];
|
|
191300
191581
|
const out = [];
|
|
191301
191582
|
async function walk(current) {
|
|
191302
|
-
for (const entry of await
|
|
191583
|
+
for (const entry of await fs2.readdir(current, { withFileTypes: true })) {
|
|
191303
191584
|
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
191304
191585
|
continue;
|
|
191305
|
-
const absolute =
|
|
191586
|
+
const absolute = path7.join(current, entry.name);
|
|
191306
191587
|
if (entry.isDirectory()) {
|
|
191307
191588
|
await walk(absolute);
|
|
191308
191589
|
} else if (entry.isFile()) {
|
|
191309
|
-
const relative =
|
|
191590
|
+
const relative = path7.relative(dir, absolute).split(path7.sep).join("/");
|
|
191310
191591
|
out.push({
|
|
191311
191592
|
path: relative,
|
|
191312
|
-
contentBase64: (await
|
|
191593
|
+
contentBase64: (await fs2.readFile(absolute)).toString("base64")
|
|
191313
191594
|
});
|
|
191314
191595
|
}
|
|
191315
191596
|
}
|
|
@@ -191318,13 +191599,13 @@ async function readFilesUnder(dir) {
|
|
|
191318
191599
|
return out;
|
|
191319
191600
|
}
|
|
191320
191601
|
async function writeBase64File(file2, contentBase64) {
|
|
191321
|
-
await
|
|
191322
|
-
await
|
|
191602
|
+
await fs2.mkdir(path7.dirname(file2), { recursive: true });
|
|
191603
|
+
await fs2.writeFile(file2, Buffer.from(contentBase64, "base64"));
|
|
191323
191604
|
}
|
|
191324
191605
|
async function pollRun(client, executionId, interval, maxWait) {
|
|
191325
191606
|
const started = Date.now();
|
|
191326
191607
|
for (;; ) {
|
|
191327
|
-
const payload = await client.get(`/api/v1/
|
|
191608
|
+
const payload = await client.get(`/api/v1/runs/${encodeURIComponent(executionId)}?include=detail`);
|
|
191328
191609
|
if (isTerminal(payload.run?.status))
|
|
191329
191610
|
return payload;
|
|
191330
191611
|
if (Date.now() - started > maxWait * 1000) {
|
|
@@ -191418,44 +191699,1037 @@ function toCsv(rows, columns) {
|
|
|
191418
191699
|
`);
|
|
191419
191700
|
}
|
|
191420
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
|
+
|
|
191421
192695
|
// src/commands/agents/validation.ts
|
|
191422
192696
|
init_src();
|
|
191423
192697
|
var import_yaml3 = __toESM(require_dist4(), 1);
|
|
191424
|
-
import { existsSync as existsSync7, promises as
|
|
191425
|
-
import
|
|
192698
|
+
import { existsSync as existsSync7, promises as fs4 } from "node:fs";
|
|
192699
|
+
import path9 from "node:path";
|
|
191426
192700
|
async function validateAgentProject(root) {
|
|
191427
192701
|
const errors3 = [];
|
|
191428
192702
|
const warnings = [];
|
|
191429
192703
|
for (const [oldName] of LEGACY_LAYOUTS) {
|
|
191430
|
-
if (existsSync7(
|
|
192704
|
+
if (existsSync7(path9.join(root, oldName))) {
|
|
191431
192705
|
errors3.push(`Legacy layout ${oldName}/ is removed; use Git source under agents/<slug>/`);
|
|
191432
192706
|
}
|
|
191433
192707
|
}
|
|
191434
|
-
if (existsSync7(
|
|
192708
|
+
if (existsSync7(path9.join(root, "agent.yaml"))) {
|
|
191435
192709
|
errors3.push("Legacy agent.yaml layout is removed; use eigenpal.yaml in a Git-backed package");
|
|
191436
192710
|
}
|
|
191437
|
-
if (existsSync7(
|
|
192711
|
+
if (existsSync7(path9.join(root, "agent"))) {
|
|
191438
192712
|
errors3.push("Legacy agent/ directory is removed; use Git source (eigenpal agents clone)");
|
|
191439
192713
|
}
|
|
191440
|
-
const manifestPath =
|
|
192714
|
+
const manifestPath = path9.join(root, PACKAGE_MANIFEST);
|
|
191441
192715
|
if (!existsSync7(manifestPath)) {
|
|
191442
192716
|
errors3.push(`Missing ${PACKAGE_MANIFEST}`);
|
|
191443
192717
|
} else {
|
|
191444
192718
|
try {
|
|
191445
|
-
SourcePackageManifestSchema.parse(import_yaml3.default.parse(await
|
|
192719
|
+
SourcePackageManifestSchema.parse(import_yaml3.default.parse(await fs4.readFile(manifestPath, "utf8")));
|
|
191446
192720
|
} catch (err) {
|
|
191447
192721
|
errors3.push(err instanceof Error ? err.message : String(err));
|
|
191448
192722
|
}
|
|
191449
192723
|
}
|
|
191450
|
-
if (!existsSync7(
|
|
192724
|
+
if (!existsSync7(path9.join(root, "AGENT.md")))
|
|
191451
192725
|
warnings.push("Missing AGENT.md");
|
|
191452
|
-
if (!existsSync7(
|
|
192726
|
+
if (!existsSync7(path9.join(root, DATASET_DIR)))
|
|
191453
192727
|
warnings.push(`Missing ${DATASET_DIR}/`);
|
|
191454
192728
|
for (const filename of SCHEMA_FILENAMES) {
|
|
191455
|
-
const schemaPath =
|
|
192729
|
+
const schemaPath = path9.join(root, filename);
|
|
191456
192730
|
if (!existsSync7(schemaPath))
|
|
191457
192731
|
continue;
|
|
191458
|
-
const validation2 = validateWorkspaceSchema(await
|
|
192732
|
+
const validation2 = validateWorkspaceSchema(await fs4.readFile(schemaPath, "utf8"), filename);
|
|
191459
192733
|
for (const issue2 of validation2.errors) {
|
|
191460
192734
|
errors3.push(`${filename}: ${issue2}`);
|
|
191461
192735
|
}
|
|
@@ -191467,7 +192741,7 @@ async function validateDatasetDir(dir, opts = {}) {
|
|
|
191467
192741
|
if (!existsSync7(dir))
|
|
191468
192742
|
errors3.push(`Missing dataset directory: ${dir}`);
|
|
191469
192743
|
for (const [oldName, newName] of LEGACY_LAYOUTS) {
|
|
191470
|
-
if (
|
|
192744
|
+
if (path9.basename(dir) === oldName) {
|
|
191471
192745
|
errors3.push(`Use ${newName}/ instead of legacy ${oldName}/`);
|
|
191472
192746
|
}
|
|
191473
192747
|
}
|
|
@@ -191478,28 +192752,28 @@ async function validateDatasetDir(dir, opts = {}) {
|
|
|
191478
192752
|
const outputSchema = await loadOptionalWorkspaceSchema(agentDir, "output-schema.json", errors3);
|
|
191479
192753
|
if (errors3.length > 0)
|
|
191480
192754
|
return { valid: false, errors: errors3 };
|
|
191481
|
-
const entries = await
|
|
192755
|
+
const entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
191482
192756
|
const examples = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
191483
192757
|
for (const example of examples) {
|
|
191484
|
-
const exampleRoot =
|
|
192758
|
+
const exampleRoot = path9.join(dir, example);
|
|
191485
192759
|
if (inputSchema) {
|
|
191486
192760
|
errors3.push(...await validateAgentDatasetInputExample(example, exampleRoot, inputSchema));
|
|
191487
192761
|
} else {
|
|
191488
|
-
await validateJsonObjectIfPresent(
|
|
192762
|
+
await validateJsonObjectIfPresent(path9.join(exampleRoot, AGENT_EXAMPLE_INPUT_JSON), `${example}/${AGENT_EXAMPLE_INPUT_JSON}`, errors3);
|
|
191489
192763
|
}
|
|
191490
192764
|
if (outputSchema) {
|
|
191491
192765
|
errors3.push(...await validateAgentDatasetExpectedExample(example, exampleRoot, outputSchema));
|
|
191492
192766
|
} else {
|
|
191493
|
-
await validateJsonObjectIfPresent(
|
|
192767
|
+
await validateJsonObjectIfPresent(path9.join(exampleRoot, AGENT_EXAMPLE_EXPECTED_JSON), `${example}/${AGENT_EXAMPLE_EXPECTED_JSON}`, errors3);
|
|
191494
192768
|
}
|
|
191495
192769
|
}
|
|
191496
192770
|
return { valid: errors3.length === 0, errors: errors3 };
|
|
191497
192771
|
}
|
|
191498
192772
|
async function loadOptionalWorkspaceSchema(agentDir, filename, errors3) {
|
|
191499
|
-
const schemaPath =
|
|
192773
|
+
const schemaPath = path9.join(agentDir, filename);
|
|
191500
192774
|
if (!existsSync7(schemaPath))
|
|
191501
192775
|
return null;
|
|
191502
|
-
const raw = await
|
|
192776
|
+
const raw = await fs4.readFile(schemaPath, "utf8");
|
|
191503
192777
|
const validation2 = validateWorkspaceSchema(raw, filename);
|
|
191504
192778
|
for (const issue2 of validation2.errors)
|
|
191505
192779
|
errors3.push(`${filename}: ${issue2}`);
|
|
@@ -191534,8 +192808,8 @@ async function validateAgentDatasetInputExample(example, exampleRoot, inputSchem
|
|
|
191534
192808
|
const errors3 = [];
|
|
191535
192809
|
const props = schemaProperties(inputSchema);
|
|
191536
192810
|
const required2 = schemaRequired(inputSchema);
|
|
191537
|
-
const inputDir =
|
|
191538
|
-
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) : [];
|
|
191539
192813
|
const matchedFiles = new Set;
|
|
191540
192814
|
for (const [fieldName, propSchema] of Object.entries(props)) {
|
|
191541
192815
|
if (!isFileField(propSchema))
|
|
@@ -191548,7 +192822,7 @@ async function validateAgentDatasetInputExample(example, exampleRoot, inputSchem
|
|
|
191548
192822
|
}
|
|
191549
192823
|
}
|
|
191550
192824
|
const dataFields = Object.entries(props).filter(([, propSchema]) => !isFileField(propSchema)).map(([fieldName]) => fieldName);
|
|
191551
|
-
const inputJsonPath =
|
|
192825
|
+
const inputJsonPath = path9.join(exampleRoot, AGENT_EXAMPLE_INPUT_JSON);
|
|
191552
192826
|
let inputData = null;
|
|
191553
192827
|
if (existsSync7(inputJsonPath)) {
|
|
191554
192828
|
inputData = await readJsonObject(inputJsonPath, `${example}/${AGENT_EXAMPLE_INPUT_JSON}`, errors3);
|
|
@@ -191588,9 +192862,9 @@ async function validateAgentDatasetInputExample(example, exampleRoot, inputSchem
|
|
|
191588
192862
|
async function validateAgentDatasetExpectedExample(example, exampleRoot, outputSchema) {
|
|
191589
192863
|
const errors3 = [];
|
|
191590
192864
|
const props = schemaProperties(outputSchema);
|
|
191591
|
-
const expectedPath =
|
|
191592
|
-
const expectedDir =
|
|
191593
|
-
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) : [];
|
|
191594
192868
|
let expected = null;
|
|
191595
192869
|
if (existsSync7(expectedPath)) {
|
|
191596
192870
|
expected = await readJsonObject(expectedPath, `${example}/${AGENT_EXAMPLE_EXPECTED_JSON}`, errors3);
|
|
@@ -191638,7 +192912,7 @@ async function validateJsonObjectIfPresent(filePath, label, errors3) {
|
|
|
191638
192912
|
}
|
|
191639
192913
|
async function readJsonObject(filePath, label, errors3) {
|
|
191640
192914
|
try {
|
|
191641
|
-
const parsed = JSON.parse(await
|
|
192915
|
+
const parsed = JSON.parse(await fs4.readFile(filePath, "utf8"));
|
|
191642
192916
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
191643
192917
|
errors3.push(`${label}: must be a JSON object`);
|
|
191644
192918
|
return null;
|
|
@@ -191702,7 +192976,7 @@ async function pushDataset(agentId, opts) {
|
|
|
191702
192976
|
if (opts.mode === "replace" && !(opts.yes || await confirmTyped(agentId, "replace dataset"))) {
|
|
191703
192977
|
throw new Error("Dataset replace aborted");
|
|
191704
192978
|
}
|
|
191705
|
-
const files = await readFilesUnder(
|
|
192979
|
+
const files = await readFilesUnder(path10.resolve(opts.file));
|
|
191706
192980
|
const client = buildClient(opts);
|
|
191707
192981
|
const payload = await client.post(`/api/v1/agents/${encodeURIComponent(agentId)}/dataset?mode=${opts.mode}`, { files });
|
|
191708
192982
|
if (opts.json)
|
|
@@ -191714,15 +192988,15 @@ async function pullDataset(agentId, opts) {
|
|
|
191714
192988
|
const payload = await client.get(`/api/v1/agents/${encodeURIComponent(agentId)}/dataset`, {
|
|
191715
192989
|
include: "files"
|
|
191716
192990
|
});
|
|
191717
|
-
const out =
|
|
192991
|
+
const out = path10.resolve(opts.out ?? DATASET_DIR);
|
|
191718
192992
|
for (const file2 of payload.files) {
|
|
191719
|
-
await writeBase64File(
|
|
192993
|
+
await writeBase64File(path10.join(out, file2.path), file2.contentBase64);
|
|
191720
192994
|
}
|
|
191721
192995
|
success2(`Pulled dataset to ${ui.bold(out)}`);
|
|
191722
192996
|
}
|
|
191723
192997
|
async function validateDatasetCommand(dir = DATASET_DIR, opts) {
|
|
191724
|
-
const result = await validateDatasetDir(
|
|
191725
|
-
agentDir:
|
|
192998
|
+
const result = await validateDatasetDir(path10.resolve(dir), {
|
|
192999
|
+
agentDir: path10.resolve(opts.agentDir ?? ".")
|
|
191726
193000
|
});
|
|
191727
193001
|
if (opts.json)
|
|
191728
193002
|
return printJson(result);
|
|
@@ -191736,28 +193010,8 @@ async function validateDatasetCommand(dir = DATASET_DIR, opts) {
|
|
|
191736
193010
|
// src/commands/agents/env.ts
|
|
191737
193011
|
init_src();
|
|
191738
193012
|
var import_yaml4 = __toESM(require_dist4(), 1);
|
|
191739
|
-
import { existsSync as existsSync8, promises as
|
|
191740
|
-
import
|
|
191741
|
-
|
|
191742
|
-
// src/commands/agents/target.ts
|
|
191743
|
-
init_src();
|
|
191744
|
-
function parseAgentTarget(target) {
|
|
191745
|
-
const [rawTarget, rawRef, extra] = target.split("@");
|
|
191746
|
-
if (extra !== undefined)
|
|
191747
|
-
throw new Error("Agent target must be <slug>[@ref].");
|
|
191748
|
-
const normalizedTarget = rawTarget.includes(".") ? target : `agents.${rawTarget}${rawRef !== undefined ? `@${rawRef}` : ""}`;
|
|
191749
|
-
const parsed = parseAutomationTarget(normalizedTarget);
|
|
191750
|
-
if (parsed.type !== "agents") {
|
|
191751
|
-
throw new Error("Only agent targets are supported in this release. Use agents.<slug>[@ref].");
|
|
191752
|
-
}
|
|
191753
|
-
return {
|
|
191754
|
-
packageName: parsed.packageName,
|
|
191755
|
-
slug: parsed.slug,
|
|
191756
|
-
sourceRef: rawRef !== undefined ? parsed.ref : undefined
|
|
191757
|
-
};
|
|
191758
|
-
}
|
|
191759
|
-
|
|
191760
|
-
// 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";
|
|
191761
193015
|
function registerEnvCommands(agent) {
|
|
191762
193016
|
const env2 = agent.command("env").description("Export encrypted source secrets for a local installed agent package.").action(() => {
|
|
191763
193017
|
process.stderr.write("`eigenpal agents env` requires a subcommand. Run `eigenpal agents env --help`.\n");
|
|
@@ -191778,7 +193032,7 @@ async function pullAgentEnv(target, opts) {
|
|
|
191778
193032
|
throw new Error("--format must be shell or dotenv");
|
|
191779
193033
|
}
|
|
191780
193034
|
const client = buildClient(opts);
|
|
191781
|
-
const requests = target ? await collectEncryptedSecretsForTarget(client, target) : await collectEncryptedSecrets(
|
|
193035
|
+
const requests = target ? await collectEncryptedSecretsForTarget(client, target) : await collectEncryptedSecrets(path11.resolve(opts.dir ?? "."));
|
|
191782
193036
|
if (requests.length === 0)
|
|
191783
193037
|
return;
|
|
191784
193038
|
const payload = await client.post("/api/v1/source/secrets/decrypt", {
|
|
@@ -191841,14 +193095,14 @@ async function readOptionalSourceFile(client, query) {
|
|
|
191841
193095
|
}
|
|
191842
193096
|
}
|
|
191843
193097
|
async function collectEncryptedSecrets(root) {
|
|
191844
|
-
const roots = [root, ...await listModulePackageRoots(
|
|
193098
|
+
const roots = [root, ...await listModulePackageRoots(path11.join(root, "eigenpal_modules"))];
|
|
191845
193099
|
const seen = new Set;
|
|
191846
193100
|
const requests = [];
|
|
191847
193101
|
for (const packageRoot of roots) {
|
|
191848
|
-
const secretsPath =
|
|
193102
|
+
const secretsPath = path11.join(packageRoot, SOURCE_SECRETS_FILENAME);
|
|
191849
193103
|
if (!existsSync8(secretsPath))
|
|
191850
193104
|
continue;
|
|
191851
|
-
const parsed = SourceSecretsFileSchema.parse(import_yaml4.default.parse(await
|
|
193105
|
+
const parsed = SourceSecretsFileSchema.parse(import_yaml4.default.parse(await fs5.readFile(secretsPath, "utf8")));
|
|
191852
193106
|
const sourcePath = sourcePathForInstalledPackage(root, packageRoot);
|
|
191853
193107
|
for (const [secretName, entry] of Object.entries(parsed.secrets)) {
|
|
191854
193108
|
if (!entry.encrypted)
|
|
@@ -191871,14 +193125,14 @@ async function listModulePackageRoots(modulesRoot) {
|
|
|
191871
193125
|
if (!existsSync8(modulesRoot))
|
|
191872
193126
|
return [];
|
|
191873
193127
|
const result = [];
|
|
191874
|
-
for (const type of await
|
|
191875
|
-
const typeRoot =
|
|
191876
|
-
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);
|
|
191877
193131
|
if (!typeStat?.isDirectory())
|
|
191878
193132
|
continue;
|
|
191879
|
-
for (const slug of await
|
|
191880
|
-
const packageRoot =
|
|
191881
|
-
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);
|
|
191882
193136
|
if (packageStat?.isDirectory())
|
|
191883
193137
|
result.push(packageRoot);
|
|
191884
193138
|
}
|
|
@@ -191886,16 +193140,16 @@ async function listModulePackageRoots(modulesRoot) {
|
|
|
191886
193140
|
return result;
|
|
191887
193141
|
}
|
|
191888
193142
|
function sourcePathForInstalledPackage(root, packageRoot) {
|
|
191889
|
-
const sourceRelative =
|
|
193143
|
+
const sourceRelative = path11.relative(root, packageRoot).replace(/\\/g, "/");
|
|
191890
193144
|
if (!sourceRelative.startsWith("..") && SourcePackagePathSchema.safeParse(sourceRelative).success) {
|
|
191891
193145
|
return sourceRelative;
|
|
191892
193146
|
}
|
|
191893
|
-
const relative =
|
|
193147
|
+
const relative = path11.relative(path11.join(root, "eigenpal_modules"), packageRoot).replace(/\\/g, "/");
|
|
191894
193148
|
if (!relative.startsWith(".."))
|
|
191895
193149
|
return relative;
|
|
191896
|
-
const lockfilePath =
|
|
193150
|
+
const lockfilePath = path11.join(root, ".eigenpal", "eigenpal.lock");
|
|
191897
193151
|
if (packageRoot === root && existsSync8(lockfilePath)) {
|
|
191898
|
-
const lockfile = JSON.parse(
|
|
193152
|
+
const lockfile = JSON.parse(readFileSync8(lockfilePath, "utf8"));
|
|
191899
193153
|
if (typeof lockfile.root?.packagePath === "string")
|
|
191900
193154
|
return lockfile.root.packagePath;
|
|
191901
193155
|
}
|
|
@@ -191909,7 +193163,7 @@ function dotenvQuote(value) {
|
|
|
191909
193163
|
}
|
|
191910
193164
|
|
|
191911
193165
|
// src/commands/agents/experiments.ts
|
|
191912
|
-
import { promises as
|
|
193166
|
+
import { promises as fs6 } from "node:fs";
|
|
191913
193167
|
function registerExperimentCommands(agent) {
|
|
191914
193168
|
const experiment = agent.command("experiment").description("Run and inspect batches of agent executions.").action(() => {
|
|
191915
193169
|
process.stderr.write("`eigenpal agents experiment` requires a subcommand. Run `eigenpal agents experiment --help`.\n");
|
|
@@ -191948,7 +193202,7 @@ async function experimentResults(agentId, batchId, opts) {
|
|
|
191948
193202
|
const payload = await client.get(`/api/v1/agents/${encodeURIComponent(agentId)}/experiments/${encodeURIComponent(selected)}`);
|
|
191949
193203
|
const content = opts.format === "json" ? JSON.stringify(payload, null, 2) : toCsv(payload.runs, ["id", "status", "exampleId"]);
|
|
191950
193204
|
if (opts.out) {
|
|
191951
|
-
await
|
|
193205
|
+
await fs6.writeFile(opts.out, content);
|
|
191952
193206
|
success2(`Wrote ${opts.out}`);
|
|
191953
193207
|
} else {
|
|
191954
193208
|
process.stdout.write(`${content}
|
|
@@ -191985,9 +193239,9 @@ async function cancelExperiment(agentId, batchId, opts) {
|
|
|
191985
193239
|
}
|
|
191986
193240
|
|
|
191987
193241
|
// src/commands/agents/files.ts
|
|
191988
|
-
import { createHash as
|
|
191989
|
-
import { promises as
|
|
191990
|
-
import
|
|
193242
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
193243
|
+
import { promises as fs7 } from "node:fs";
|
|
193244
|
+
import path12 from "node:path";
|
|
191991
193245
|
function registerAgentFileCommands(agent) {
|
|
191992
193246
|
const file2 = agent.command("file").description("List, download, and compare individual live agent files.").action(() => {
|
|
191993
193247
|
process.stderr.write("`eigenpal agents file` requires a subcommand. Run `eigenpal agents file --help`.\n");
|
|
@@ -192013,19 +193267,19 @@ async function getAgentFile(agentId, remotePath, opts) {
|
|
|
192013
193267
|
});
|
|
192014
193268
|
if (opts.json)
|
|
192015
193269
|
return printJson(payload);
|
|
192016
|
-
const out =
|
|
193270
|
+
const out = path12.resolve(opts.out ?? payload.path);
|
|
192017
193271
|
await writeBase64File(out, payload.contentBase64);
|
|
192018
193272
|
success2(`Downloaded ${payload.path} to ${ui.bold(out)}`);
|
|
192019
193273
|
}
|
|
192020
193274
|
async function diffAgentFile(agentId, remotePath, localPath, opts) {
|
|
192021
|
-
const report = await buildAgentFileDiff(agentId, remotePath,
|
|
193275
|
+
const report = await buildAgentFileDiff(agentId, remotePath, path12.resolve(localPath), opts);
|
|
192022
193276
|
if (opts.json)
|
|
192023
193277
|
return printJson(report);
|
|
192024
193278
|
renderAgentFileDiff(report);
|
|
192025
193279
|
}
|
|
192026
193280
|
async function buildAgentFileDiff(agentId, remotePath, localPath, opts) {
|
|
192027
193281
|
const client = buildClient(opts);
|
|
192028
|
-
const local = await
|
|
193282
|
+
const local = await fs7.readFile(localPath);
|
|
192029
193283
|
let remote = null;
|
|
192030
193284
|
let serverPath = remotePath;
|
|
192031
193285
|
try {
|
|
@@ -192038,8 +193292,8 @@ async function buildAgentFileDiff(agentId, remotePath, localPath, opts) {
|
|
|
192038
193292
|
if (!(err instanceof ApiError) || err.status !== 404)
|
|
192039
193293
|
throw err;
|
|
192040
193294
|
}
|
|
192041
|
-
const localHash =
|
|
192042
|
-
const remoteHash = remote ?
|
|
193295
|
+
const localHash = createHash3("sha256").update(local).digest("hex");
|
|
193296
|
+
const remoteHash = remote ? createHash3("sha256").update(remote).digest("hex") : null;
|
|
192043
193297
|
const status = !remote ? "remote-missing" : localHash === remoteHash ? "match" : "different";
|
|
192044
193298
|
return {
|
|
192045
193299
|
agentId,
|
|
@@ -192093,713 +193347,6 @@ function buildTextDiffPreview(remoteText, localText) {
|
|
|
192093
193347
|
return preview;
|
|
192094
193348
|
}
|
|
192095
193349
|
|
|
192096
|
-
// src/commands/agents/runs.ts
|
|
192097
|
-
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
192098
|
-
import { createHash as createHash3 } from "node:crypto";
|
|
192099
|
-
import { promises as fs7, readFileSync as readFileSync8 } from "node:fs";
|
|
192100
|
-
import path12 from "node:path";
|
|
192101
|
-
|
|
192102
|
-
// src/commands/agents/run-form-data.ts
|
|
192103
|
-
import { promises as fs6 } from "node:fs";
|
|
192104
|
-
import path11 from "node:path";
|
|
192105
|
-
async function buildAgentExecutionRunFormData(inputFile, inputJson) {
|
|
192106
|
-
const form = new FormData;
|
|
192107
|
-
for (const spec of parseAgentInputFileSpecs(inputFile)) {
|
|
192108
|
-
const data = await fs6.readFile(spec.filePath);
|
|
192109
|
-
form.append(spec.fieldName, new Blob([data]), path11.basename(spec.filePath));
|
|
192110
|
-
}
|
|
192111
|
-
if (inputJson)
|
|
192112
|
-
form.append("_json", inputJson);
|
|
192113
|
-
return form;
|
|
192114
|
-
}
|
|
192115
|
-
function parseAgentInputFileSpecs(inputFile) {
|
|
192116
|
-
const values = Array.isArray(inputFile) ? inputFile : [inputFile];
|
|
192117
|
-
return values.map((value) => {
|
|
192118
|
-
const separatorIndex = value.indexOf("=");
|
|
192119
|
-
if (separatorIndex === -1) {
|
|
192120
|
-
return { fieldName: "file", filePath: value };
|
|
192121
|
-
}
|
|
192122
|
-
const fieldName = value.slice(0, separatorIndex).trim();
|
|
192123
|
-
const filePath = value.slice(separatorIndex + 1).trim();
|
|
192124
|
-
if (!fieldName || !filePath) {
|
|
192125
|
-
throw new Error("--input-file must be <field=path> or a bare path");
|
|
192126
|
-
}
|
|
192127
|
-
if (fieldName === "_json" || fieldName === "_metadata" || fieldName === "input") {
|
|
192128
|
-
throw new Error(`--input-file field "${fieldName}" is reserved`);
|
|
192129
|
-
}
|
|
192130
|
-
return { fieldName, filePath };
|
|
192131
|
-
});
|
|
192132
|
-
}
|
|
192133
|
-
|
|
192134
|
-
// src/commands/agents/runs.ts
|
|
192135
|
-
function registerRunCommands(agent) {
|
|
192136
|
-
const runs = agent.command("runs").description("Inspect, watch, and manage agent runs.").action(() => {
|
|
192137
|
-
process.stderr.write("`eigenpal agents runs` requires a subcommand. Run `eigenpal agents runs --help`.\n");
|
|
192138
|
-
process.exit(2);
|
|
192139
|
-
});
|
|
192140
|
-
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");
|
|
192141
|
-
listRunsCmd.action(action(listRunsTarget));
|
|
192142
|
-
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));
|
|
192143
|
-
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));
|
|
192144
|
-
const artifacts = runs.command("artifacts").description("Inspect run artifact inventory.").action(() => {
|
|
192145
|
-
process.stderr.write("`eigenpal agents runs artifacts` requires a subcommand. Run `eigenpal agents runs artifacts --help`.\n");
|
|
192146
|
-
process.exit(2);
|
|
192147
|
-
});
|
|
192148
|
-
addJsonFlag(withBaseUrl(artifacts.command("list <run-id>"))).description("List available run artifacts without downloading them.").action(action(listRunArtifacts));
|
|
192149
|
-
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));
|
|
192150
|
-
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));
|
|
192151
|
-
registerRunFeedbackCommands(runs);
|
|
192152
|
-
registerRunExpectedCommands(runs);
|
|
192153
|
-
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));
|
|
192154
|
-
addJsonFlag(withBaseUrl(runs.command("cancel <run-id>"))).description("Cancel an agent run.").option("--yes", "Required in non-interactive environments").action(action(cancelRun));
|
|
192155
|
-
}
|
|
192156
|
-
function registerRunFeedbackCommands(runs) {
|
|
192157
|
-
const feedback = runs.command("feedback").description("Update or clear feedback attached to an agent run.").action(() => {
|
|
192158
|
-
process.stderr.write("`eigenpal agents runs feedback` requires a subcommand. Run `eigenpal agents runs feedback --help`.\n");
|
|
192159
|
-
process.exit(2);
|
|
192160
|
-
});
|
|
192161
|
-
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));
|
|
192162
|
-
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" })));
|
|
192163
|
-
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));
|
|
192164
|
-
}
|
|
192165
|
-
function registerRunExpectedCommands(runs) {
|
|
192166
|
-
const expected = runs.command("expected").description("Manage expected artifacts attached to an agent run.").action(() => {
|
|
192167
|
-
process.stderr.write("`eigenpal agents runs expected` requires a subcommand. Run `eigenpal agents runs expected --help`.\n");
|
|
192168
|
-
process.exit(2);
|
|
192169
|
-
});
|
|
192170
|
-
addJsonFlag(withBaseUrl(expected.command("list <run-id>"))).description("List expected JSON and files attached to a run.").action(action(listRunExpected));
|
|
192171
|
-
withBaseUrl(expected.command("pull <run-id>")).description("Download expected JSON and files attached to a run.").option("--out <dir>", "Output directory").action(action(pullRunExpected));
|
|
192172
|
-
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));
|
|
192173
|
-
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));
|
|
192174
|
-
addJsonFlag(withBaseUrl(expected.command("rename <run-id> <old-name> <new-name>"))).description("Rename an expected artifact.").action(action(renameRunExpected));
|
|
192175
|
-
addJsonFlag(withBaseUrl(expected.command("delete <run-id> <name>"))).description("Delete an expected artifact.").option("--yes", "Required in non-interactive environments").action(action(deleteRunExpected));
|
|
192176
|
-
}
|
|
192177
|
-
async function listRunsTarget(target, opts) {
|
|
192178
|
-
const parsed = parseAgentTarget(target);
|
|
192179
|
-
return listRuns(parsed.slug, { ...opts, sourceRef: parsed.sourceRef });
|
|
192180
|
-
}
|
|
192181
|
-
async function runExecution(agentId, opts) {
|
|
192182
|
-
const client = buildClient(opts);
|
|
192183
|
-
const runPath = `/api/v1/agents/${encodeURIComponent(agentId)}/run${opts.sourceRef ? `?sourceRef=${encodeURIComponent(opts.sourceRef)}` : ""}`;
|
|
192184
|
-
let payload;
|
|
192185
|
-
if (opts.inputFile && opts.inputFile.length > 0) {
|
|
192186
|
-
const form = await buildAgentExecutionRunFormData(opts.inputFile, opts.inputJson);
|
|
192187
|
-
payload = await client.postFormData(runPath, form);
|
|
192188
|
-
} else {
|
|
192189
|
-
payload = await client.post(runPath, {
|
|
192190
|
-
input: opts.inputJson ? JSON.parse(opts.inputJson) : {},
|
|
192191
|
-
...opts.sourceRef ? { sourceRef: opts.sourceRef } : {}
|
|
192192
|
-
});
|
|
192193
|
-
}
|
|
192194
|
-
const runId = String(payload.runId ?? "");
|
|
192195
|
-
let waitedForTerminalRun = false;
|
|
192196
|
-
if (opts.wait && runId) {
|
|
192197
|
-
payload = await pollRun(client, runId, opts.interval ?? 2, opts.maxWait ?? 1800);
|
|
192198
|
-
waitedForTerminalRun = true;
|
|
192199
|
-
}
|
|
192200
|
-
renderRunPayload(payload, opts);
|
|
192201
|
-
if (waitedForTerminalRun)
|
|
192202
|
-
setExitCodeForFailedTerminalRun(payload);
|
|
192203
|
-
}
|
|
192204
|
-
async function runExample(agentId, opts) {
|
|
192205
|
-
const client = buildClient(opts);
|
|
192206
|
-
const started = await client.post(`/api/v1/agents/${encodeURIComponent(agentId)}/experiments`, {
|
|
192207
|
-
exampleId: opts.example,
|
|
192208
|
-
...opts.sourceRef ? { sourceRef: opts.sourceRef } : {}
|
|
192209
|
-
});
|
|
192210
|
-
const runId = started.runs?.[0]?.runId;
|
|
192211
|
-
if (opts.wait && runId) {
|
|
192212
|
-
const payload2 = await pollRun(client, runId, opts.interval, opts.maxWait);
|
|
192213
|
-
renderRunPayload(payload2, opts);
|
|
192214
|
-
setExitCodeForFailedTerminalRun(payload2);
|
|
192215
|
-
return;
|
|
192216
|
-
}
|
|
192217
|
-
const payload = {
|
|
192218
|
-
runId,
|
|
192219
|
-
exampleId: opts.example,
|
|
192220
|
-
batchId: started.batchId,
|
|
192221
|
-
status: "pending"
|
|
192222
|
-
};
|
|
192223
|
-
if (opts.json)
|
|
192224
|
-
return printJson(payload);
|
|
192225
|
-
success2(`Run ${runId ?? ""} queued for example ${opts.example}`);
|
|
192226
|
-
}
|
|
192227
|
-
async function getRun(executionId, opts) {
|
|
192228
|
-
const client = buildClient(opts);
|
|
192229
|
-
const payload = await client.get(`/api/v1/agents/runs/${encodeURIComponent(executionId)}`, {
|
|
192230
|
-
...opts.include ? { include: opts.include } : {}
|
|
192231
|
-
});
|
|
192232
|
-
renderRunPayload(payload, opts);
|
|
192233
|
-
}
|
|
192234
|
-
async function rerunRun(executionId, opts) {
|
|
192235
|
-
const client = buildClient(opts);
|
|
192236
|
-
const sourceRef = await resolveRerunSourceRef(client, executionId, opts.sourceRef);
|
|
192237
|
-
let payload = await client.post(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/rerun`, sourceRef ? { sourceRef } : {});
|
|
192238
|
-
const rerunId = String(payload.runId ?? "");
|
|
192239
|
-
if (opts.wait && rerunId) {
|
|
192240
|
-
payload = await pollRun(client, rerunId, opts.interval, opts.maxWait);
|
|
192241
|
-
renderRunPayload(payload, opts);
|
|
192242
|
-
setExitCodeForFailedTerminalRun(payload);
|
|
192243
|
-
return;
|
|
192244
|
-
}
|
|
192245
|
-
if (opts.json)
|
|
192246
|
-
return printJson(payload);
|
|
192247
|
-
success2(`Started rerun ${ui.bold(rerunId)} from ${executionId}`);
|
|
192248
|
-
}
|
|
192249
|
-
async function resolveRerunSourceRef(client, executionId, requested) {
|
|
192250
|
-
if (!requested)
|
|
192251
|
-
return;
|
|
192252
|
-
if (requested !== "original")
|
|
192253
|
-
return requested;
|
|
192254
|
-
const payload = await client.get(`/api/v1/agents/runs/${encodeURIComponent(executionId)}`);
|
|
192255
|
-
const run = payload.run;
|
|
192256
|
-
if (!run)
|
|
192257
|
-
throw new Error(`Run ${executionId} not found`);
|
|
192258
|
-
const resolved = stringOrNull(run.resolvedGitRef);
|
|
192259
|
-
const requestedSource = stringOrNull(run.requestedSourceRef);
|
|
192260
|
-
const original = resolved ?? requestedSource;
|
|
192261
|
-
if (!original) {
|
|
192262
|
-
throw new Error(`Run ${executionId} does not have an original source ref to reuse`);
|
|
192263
|
-
}
|
|
192264
|
-
return original;
|
|
192265
|
-
}
|
|
192266
|
-
async function fetchRunArtifacts(executionId, opts) {
|
|
192267
|
-
const client = buildClient(opts);
|
|
192268
|
-
const payload = await client.get(`/api/v1/agents/runs/${encodeURIComponent(executionId)}`, {
|
|
192269
|
-
include: "expected,files,input,output,issues,trace,lockfile,metadata"
|
|
192270
|
-
});
|
|
192271
|
-
const run = payload.run;
|
|
192272
|
-
if (!run)
|
|
192273
|
-
throw new Error(`Run ${executionId} not found`);
|
|
192274
|
-
const out = path12.resolve(opts.out ?? path12.join(".eigenpal", "artifacts", "runs", executionId));
|
|
192275
|
-
await fs7.mkdir(out, { recursive: true });
|
|
192276
|
-
const written = [];
|
|
192277
|
-
const skipped = [];
|
|
192278
|
-
const selectedPaths = opts.path && opts.path.length > 0 ? opts.path : runArtifactInventory(run).filter((artifact) => artifactIncluded(artifact, opts.include)).map((artifact) => artifact.path);
|
|
192279
|
-
for (const artifactPath of selectedPaths) {
|
|
192280
|
-
const normalized = normalizeArtifactPath(artifactPath);
|
|
192281
|
-
if (!normalized) {
|
|
192282
|
-
skipped.push(artifactPath);
|
|
192283
|
-
continue;
|
|
192284
|
-
}
|
|
192285
|
-
const result = await writeRunArtifact(client, executionId, out, normalized, run);
|
|
192286
|
-
if (result)
|
|
192287
|
-
written.push(result);
|
|
192288
|
-
else
|
|
192289
|
-
skipped.push(normalized);
|
|
192290
|
-
}
|
|
192291
|
-
const summary = {
|
|
192292
|
-
runId: executionId,
|
|
192293
|
-
out,
|
|
192294
|
-
written,
|
|
192295
|
-
skipped,
|
|
192296
|
-
counts: {
|
|
192297
|
-
written: written.length,
|
|
192298
|
-
skipped: skipped.length
|
|
192299
|
-
}
|
|
192300
|
-
};
|
|
192301
|
-
if (opts.json)
|
|
192302
|
-
return printJson(summary);
|
|
192303
|
-
success2(`Fetched run artifacts for ${executionId} to ${out}`);
|
|
192304
|
-
dim(`Wrote ${written.length} artifact${written.length === 1 ? "" : "s"}${skipped.length ? `; skipped missing ${skipped.join(", ")}` : ""}`);
|
|
192305
|
-
}
|
|
192306
|
-
async function listRunArtifacts(executionId, opts) {
|
|
192307
|
-
const client = buildClient(opts);
|
|
192308
|
-
const payload = await client.get(`/api/v1/agents/runs/${encodeURIComponent(executionId)}`, {
|
|
192309
|
-
include: "expected,files,input,output,issues,trace,lockfile,metadata"
|
|
192310
|
-
});
|
|
192311
|
-
const run = payload.run;
|
|
192312
|
-
if (!run)
|
|
192313
|
-
throw new Error(`Run ${executionId} not found`);
|
|
192314
|
-
const artifacts = runArtifactInventory(run);
|
|
192315
|
-
if (opts.json)
|
|
192316
|
-
return printJson({ runId: executionId, artifacts });
|
|
192317
|
-
console.log(table(artifacts, [
|
|
192318
|
-
{ key: "kind", header: "KIND" },
|
|
192319
|
-
{ key: "name", header: "NAME" },
|
|
192320
|
-
{ key: "path", header: "PATH" },
|
|
192321
|
-
{ key: "present", header: "PRESENT" }
|
|
192322
|
-
]));
|
|
192323
|
-
}
|
|
192324
|
-
function artifactIncluded(artifact, include) {
|
|
192325
|
-
const parts = new Set(include.split(",").map((part) => part.trim()).filter(Boolean));
|
|
192326
|
-
if (parts.has("all"))
|
|
192327
|
-
return true;
|
|
192328
|
-
if (parts.has(artifact.kind))
|
|
192329
|
-
return true;
|
|
192330
|
-
if (parts.has("files") && (artifact.kind === "input" || artifact.kind === "output"))
|
|
192331
|
-
return true;
|
|
192332
|
-
if (parts.has("metadata") && artifact.path === "run.json")
|
|
192333
|
-
return true;
|
|
192334
|
-
return false;
|
|
192335
|
-
}
|
|
192336
|
-
function normalizeArtifactPath(artifactPath) {
|
|
192337
|
-
const parts = artifactPath.split("/");
|
|
192338
|
-
if (artifactPath.startsWith("/") || parts.some((part) => part === "" || part === "." || part === "..")) {
|
|
192339
|
-
return null;
|
|
192340
|
-
}
|
|
192341
|
-
return artifactPath;
|
|
192342
|
-
}
|
|
192343
|
-
async function writeJsonRunArtifact(out, artifactPath, value) {
|
|
192344
|
-
await fs7.mkdir(path12.dirname(path12.join(out, artifactPath)), { recursive: true });
|
|
192345
|
-
await fs7.writeFile(path12.join(out, artifactPath), JSON.stringify(value, null, 2));
|
|
192346
|
-
}
|
|
192347
|
-
async function writeDownloadedRunArtifact(client, executionId, out, artifactPath, routePrefix) {
|
|
192348
|
-
const routePath = routePrefix === "files" ? artifactPath : artifactPath.slice("expected/".length);
|
|
192349
|
-
const response = await client.getStream(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/${routePrefix}/${encodeRunArtifactPath(routePath)}`);
|
|
192350
|
-
const outPath = path12.join(out, artifactPath);
|
|
192351
|
-
await fs7.mkdir(path12.dirname(outPath), { recursive: true });
|
|
192352
|
-
await fs7.writeFile(outPath, Buffer.from(await response.arrayBuffer()));
|
|
192353
|
-
}
|
|
192354
|
-
async function writeRunArtifact(client, executionId, out, artifactPath, run) {
|
|
192355
|
-
if (artifactPath === "run.json") {
|
|
192356
|
-
await writeJsonRunArtifact(out, artifactPath, run);
|
|
192357
|
-
return artifactPath;
|
|
192358
|
-
}
|
|
192359
|
-
if (artifactPath === "input.json") {
|
|
192360
|
-
if (run.inputJson == null)
|
|
192361
|
-
return null;
|
|
192362
|
-
await writeJsonRunArtifact(out, artifactPath, run.inputJson);
|
|
192363
|
-
return artifactPath;
|
|
192364
|
-
}
|
|
192365
|
-
if (artifactPath === "metadata.json") {
|
|
192366
|
-
if (run.metadata == null)
|
|
192367
|
-
return null;
|
|
192368
|
-
await writeJsonRunArtifact(out, artifactPath, run.metadata);
|
|
192369
|
-
return artifactPath;
|
|
192370
|
-
}
|
|
192371
|
-
if (artifactPath === "expected.json") {
|
|
192372
|
-
if (run.expected == null)
|
|
192373
|
-
return null;
|
|
192374
|
-
await writeJsonRunArtifact(out, artifactPath, run.expected);
|
|
192375
|
-
return artifactPath;
|
|
192376
|
-
}
|
|
192377
|
-
if (artifactPath.startsWith("expected/")) {
|
|
192378
|
-
await writeDownloadedRunArtifact(client, executionId, out, artifactPath, "expected");
|
|
192379
|
-
return artifactPath;
|
|
192380
|
-
}
|
|
192381
|
-
await writeDownloadedRunArtifact(client, executionId, out, artifactPath, "files");
|
|
192382
|
-
return artifactPath;
|
|
192383
|
-
}
|
|
192384
|
-
async function traceRun(executionId, opts) {
|
|
192385
|
-
const client = buildClient(opts);
|
|
192386
|
-
const text = await downloadTraceText(client, executionId);
|
|
192387
|
-
if (!opts.out) {
|
|
192388
|
-
process.stdout.write(text);
|
|
192389
|
-
return;
|
|
192390
|
-
}
|
|
192391
|
-
const out = path12.resolve(opts.out);
|
|
192392
|
-
await fs7.mkdir(path12.dirname(out), { recursive: true });
|
|
192393
|
-
await fs7.writeFile(out, text);
|
|
192394
|
-
success2(`Downloaded trace for ${executionId} to ${ui.bold(out)}`);
|
|
192395
|
-
}
|
|
192396
|
-
async function compareRun(referenceId, executionId, opts) {
|
|
192397
|
-
const client = buildClient(opts);
|
|
192398
|
-
const targetPayload = await client.get(`/api/v1/agents/runs/${encodeURIComponent(executionId)}`, { include: "files,output" });
|
|
192399
|
-
const target = targetPayload.run;
|
|
192400
|
-
if (!target)
|
|
192401
|
-
throw new Error(`Run ${executionId} not found`);
|
|
192402
|
-
const mode = opts.baseline ? "baseline" : "expected";
|
|
192403
|
-
const reference = (await client.get(`/api/v1/agents/runs/${encodeURIComponent(referenceId)}`, {
|
|
192404
|
-
include: mode === "baseline" ? "files,output" : "expected"
|
|
192405
|
-
})).run;
|
|
192406
|
-
if (!reference)
|
|
192407
|
-
throw new Error(`Reference run ${referenceId} not found`);
|
|
192408
|
-
const out = path12.resolve(opts.out ?? path12.join(".eigenpal", "artifacts", "comparisons", `${referenceId}..${executionId}`));
|
|
192409
|
-
await fs7.mkdir(out, { recursive: true });
|
|
192410
|
-
const warnings = [];
|
|
192411
|
-
if (mode === "expected" && reference.expected == null && fileNames(reference.expectedFiles).length === 0) {
|
|
192412
|
-
warnings.push(`Reference run ${referenceId} has no expected JSON or expected files; comparison has no baseline artifacts. Use --baseline to compare actual outputs.`);
|
|
192413
|
-
}
|
|
192414
|
-
const jsonDiffs = diffJson(mode === "baseline" ? reference.output : reference.expected, target.output);
|
|
192415
|
-
const expectedFiles = mode === "baseline" ? fileNames(reference.resultFiles).filter((name) => name !== "issues.md" && name !== "trace.jsonl") : fileNames(reference.expectedFiles);
|
|
192416
|
-
const outputFiles = fileNames(target.resultFiles).filter((name) => name !== "issues.md" && name !== "trace.jsonl");
|
|
192417
|
-
const inventory = compareFileInventory(expectedFiles, outputFiles, Boolean(opts.normalizeDates));
|
|
192418
|
-
const textDifferences = await compareMatchedFileText(client, { runId: referenceId, kind: mode === "baseline" ? "output" : "expected" }, { runId: executionId, kind: "output" }, out, inventory.matched, Boolean(opts.normalizeDates));
|
|
192419
|
-
const report = {
|
|
192420
|
-
status: jsonDiffs.length === 0 && inventory.missing.length === 0 && inventory.extra.length === 0 && textDifferences.every((diff) => diff.status === "match" || diff.status === "binary-match") ? "pass" : "fail",
|
|
192421
|
-
runId: executionId,
|
|
192422
|
-
comparedWithRunId: referenceId,
|
|
192423
|
-
mode,
|
|
192424
|
-
warnings,
|
|
192425
|
-
jsonDifferences: jsonDiffs,
|
|
192426
|
-
matchedFiles: inventory.matched,
|
|
192427
|
-
missingFiles: inventory.missing,
|
|
192428
|
-
extraFiles: inventory.extra,
|
|
192429
|
-
textDifferences
|
|
192430
|
-
};
|
|
192431
|
-
await fs7.writeFile(path12.join(out, "comparison.json"), JSON.stringify(report, null, 2));
|
|
192432
|
-
if (opts.json) {
|
|
192433
|
-
printJson(report);
|
|
192434
|
-
} else {
|
|
192435
|
-
renderComparisonReport(report);
|
|
192436
|
-
}
|
|
192437
|
-
dim(`Comparison artifacts written to ${ui.bold(out)}`);
|
|
192438
|
-
if (opts.failOnDiff && report.status === "fail")
|
|
192439
|
-
process.exit(1);
|
|
192440
|
-
}
|
|
192441
|
-
async function listRuns(agentId, opts) {
|
|
192442
|
-
const client = buildClient(opts);
|
|
192443
|
-
const params = buildRunListParams(opts);
|
|
192444
|
-
const payload = await client.get(`/api/v1/agents/${encodeURIComponent(agentId)}/runs`, params);
|
|
192445
|
-
const rows = opts.compact ? payload.runs.map(compactRunRow) : payload.runs;
|
|
192446
|
-
if (opts.json)
|
|
192447
|
-
return printJson({ ...payload, runs: rows });
|
|
192448
|
-
if (payload.scanLimited) {
|
|
192449
|
-
warn("Feedback/expected filters scanned only the first matching window. Increase --scan-limit or narrow DB filters for a fuller result.");
|
|
192450
|
-
}
|
|
192451
|
-
if (payload.noResolvedAnchor) {
|
|
192452
|
-
warn("No resolved feedback anchor was found in the scan window; --since-last-resolved returned no rows.");
|
|
192453
|
-
}
|
|
192454
|
-
console.log(table(rows, [
|
|
192455
|
-
{ key: "id", header: "ID" },
|
|
192456
|
-
{ key: "status", header: "STATUS" },
|
|
192457
|
-
{ key: "exampleId", header: "EXAMPLE" },
|
|
192458
|
-
{
|
|
192459
|
-
key: "feedback",
|
|
192460
|
-
header: "FEEDBACK",
|
|
192461
|
-
format: (value) => value && typeof value === "object" ? `${String(value.rating ?? "")}/${String(value.status ?? "")}` : ""
|
|
192462
|
-
},
|
|
192463
|
-
{ key: "createdAt", header: "CREATED", format: formatTimestamp }
|
|
192464
|
-
]));
|
|
192465
|
-
}
|
|
192466
|
-
function buildRunListParams(opts) {
|
|
192467
|
-
return compactParams({
|
|
192468
|
-
...opts,
|
|
192469
|
-
feedback: undefined,
|
|
192470
|
-
noFeedback: opts.noFeedback ?? (opts.feedback === false ? true : undefined)
|
|
192471
|
-
});
|
|
192472
|
-
}
|
|
192473
|
-
async function updateRunFeedback(executionId, opts) {
|
|
192474
|
-
const client = buildClient(opts);
|
|
192475
|
-
const body = {};
|
|
192476
|
-
if (opts.status)
|
|
192477
|
-
body.status = opts.status;
|
|
192478
|
-
if (opts.rating)
|
|
192479
|
-
body.rating = opts.rating === "none" ? null : opts.rating;
|
|
192480
|
-
if (opts.clearRating)
|
|
192481
|
-
body.rating = null;
|
|
192482
|
-
if (opts.messageFile)
|
|
192483
|
-
body.body = await fs7.readFile(opts.messageFile, "utf-8");
|
|
192484
|
-
if (opts.message !== undefined)
|
|
192485
|
-
body.body = opts.message;
|
|
192486
|
-
if (opts.clearMessage)
|
|
192487
|
-
body.body = "";
|
|
192488
|
-
if (opts.expectedJsonFile)
|
|
192489
|
-
body.expected = JSON.parse(await fs7.readFile(opts.expectedJsonFile, "utf-8"));
|
|
192490
|
-
if (opts.expectedJson !== undefined)
|
|
192491
|
-
body.expected = JSON.parse(opts.expectedJson);
|
|
192492
|
-
if (opts.clearExpectedJson)
|
|
192493
|
-
body.expected = null;
|
|
192494
|
-
const payload = await client.patch(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/feedback`, body);
|
|
192495
|
-
renderGeneric(payload, opts, `Updated feedback for ${executionId}`);
|
|
192496
|
-
}
|
|
192497
|
-
function compactRunRow(run) {
|
|
192498
|
-
const feedback = run.feedback && typeof run.feedback === "object" ? run.feedback : null;
|
|
192499
|
-
return {
|
|
192500
|
-
id: run.id,
|
|
192501
|
-
status: run.status,
|
|
192502
|
-
exampleId: run.exampleId,
|
|
192503
|
-
feedback: feedback ? {
|
|
192504
|
-
rating: feedback.rating ?? null,
|
|
192505
|
-
status: feedback.status ?? null,
|
|
192506
|
-
updatedAt: feedback.updatedAt ?? null
|
|
192507
|
-
} : null,
|
|
192508
|
-
hasExpectedJson: run.expected != null,
|
|
192509
|
-
expectedFileCount: Array.isArray(run.expectedFiles) ? run.expectedFiles.length : undefined,
|
|
192510
|
-
createdAt: run.createdAt,
|
|
192511
|
-
completedAt: run.completedAt
|
|
192512
|
-
};
|
|
192513
|
-
}
|
|
192514
|
-
async function clearRunFeedback(executionId, opts) {
|
|
192515
|
-
if (!(opts.yes || await confirmTyped(executionId, "clear feedback artifacts"))) {
|
|
192516
|
-
throw new Error("Clear cancelled");
|
|
192517
|
-
}
|
|
192518
|
-
const client = buildClient(opts);
|
|
192519
|
-
const payload = await client.delete(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/feedback`);
|
|
192520
|
-
renderGeneric(payload, opts, `Cleared feedback for ${executionId}`);
|
|
192521
|
-
}
|
|
192522
|
-
async function listRunExpected(executionId, opts) {
|
|
192523
|
-
const client = buildClient(opts);
|
|
192524
|
-
const payload = await client.get(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/expected`);
|
|
192525
|
-
if (opts.json)
|
|
192526
|
-
return printJson(payload);
|
|
192527
|
-
console.log(table(payload.files ?? [], [{ key: "name", header: "NAME" }]));
|
|
192528
|
-
if (payload.expected != null)
|
|
192529
|
-
dim("expected.json present");
|
|
192530
|
-
}
|
|
192531
|
-
async function pullRunExpected(executionId, opts) {
|
|
192532
|
-
const client = buildClient(opts);
|
|
192533
|
-
const payload = await client.get(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/expected`);
|
|
192534
|
-
const out = path12.resolve(opts.out ?? path12.join(executionId, "expected"));
|
|
192535
|
-
await fs7.mkdir(out, { recursive: true });
|
|
192536
|
-
if (payload.expected != null) {
|
|
192537
|
-
await fs7.writeFile(path12.join(out, "expected.json"), JSON.stringify(payload.expected, null, 2));
|
|
192538
|
-
}
|
|
192539
|
-
await downloadExpectedFiles(client, executionId, out, payload.files);
|
|
192540
|
-
success2(`Pulled expected artifacts for ${executionId} to ${out}`);
|
|
192541
|
-
}
|
|
192542
|
-
async function downloadExpectedFiles(client, executionId, out, files) {
|
|
192543
|
-
const rows = Array.isArray(files) ? files : [];
|
|
192544
|
-
const written = await Promise.all(rows.map(async (file2) => {
|
|
192545
|
-
const name = String(file2.name ?? "");
|
|
192546
|
-
if (!name || name.includes("/") || name.includes(".."))
|
|
192547
|
-
return null;
|
|
192548
|
-
const response = await client.getStream(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/expected/${encodeURIComponent(name)}`);
|
|
192549
|
-
await fs7.mkdir(out, { recursive: true });
|
|
192550
|
-
await fs7.writeFile(path12.join(out, name), Buffer.from(await response.arrayBuffer()));
|
|
192551
|
-
return name;
|
|
192552
|
-
}));
|
|
192553
|
-
return written.filter((name) => Boolean(name));
|
|
192554
|
-
}
|
|
192555
|
-
async function uploadRunExpected(executionId, file2, opts) {
|
|
192556
|
-
const client = buildClient(opts);
|
|
192557
|
-
const form = new FormData;
|
|
192558
|
-
const data = await fs7.readFile(file2);
|
|
192559
|
-
form.append("file", new Blob([data]), path12.basename(file2));
|
|
192560
|
-
if (opts.name)
|
|
192561
|
-
form.append("name", opts.name);
|
|
192562
|
-
const payload = await client.postFormData(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/expected`, form);
|
|
192563
|
-
renderGeneric(payload, opts, `Uploaded expected file for ${executionId}`);
|
|
192564
|
-
}
|
|
192565
|
-
async function copyOutputToExpected(executionId, outputFile, opts) {
|
|
192566
|
-
const client = buildClient(opts);
|
|
192567
|
-
const payload = await client.post(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/expected`, { outputFileName: outputFile, ...opts.name ? { expectedName: opts.name } : {} });
|
|
192568
|
-
renderGeneric(payload, opts, `Copied output file to expected for ${executionId}`);
|
|
192569
|
-
}
|
|
192570
|
-
async function renameRunExpected(executionId, oldName, newName, opts) {
|
|
192571
|
-
const client = buildClient(opts);
|
|
192572
|
-
const payload = await client.patch(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/expected/${encodeURIComponent(oldName)}`, { name: newName });
|
|
192573
|
-
renderGeneric(payload, opts, `Renamed expected file for ${executionId}`);
|
|
192574
|
-
}
|
|
192575
|
-
async function deleteRunExpected(executionId, name, opts) {
|
|
192576
|
-
if (!(opts.yes || await confirmTyped(name, "delete expected file"))) {
|
|
192577
|
-
throw new Error("Delete cancelled");
|
|
192578
|
-
}
|
|
192579
|
-
const client = buildClient(opts);
|
|
192580
|
-
await client.delete(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/expected/${encodeURIComponent(name)}`);
|
|
192581
|
-
renderGeneric({ ok: true }, opts, `Deleted expected file ${name}`);
|
|
192582
|
-
}
|
|
192583
|
-
async function watchRunCommand(executionId, opts) {
|
|
192584
|
-
const client = buildClient(opts);
|
|
192585
|
-
const payload = await pollRun(client, executionId, opts.interval, opts.maxWait);
|
|
192586
|
-
renderRunPayload(payload, opts);
|
|
192587
|
-
setExitCodeForFailedTerminalRun(payload);
|
|
192588
|
-
}
|
|
192589
|
-
async function cancelRun(executionId, opts) {
|
|
192590
|
-
if (!(opts.yes || process.stdin.isTTY))
|
|
192591
|
-
throw new Error("Pass --yes to cancel in non-interactive mode");
|
|
192592
|
-
const client = buildClient(opts);
|
|
192593
|
-
const payload = await client.post(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/cancel`, {});
|
|
192594
|
-
renderRunPayload(payload, opts);
|
|
192595
|
-
}
|
|
192596
|
-
async function downloadTraceText(client, executionId) {
|
|
192597
|
-
const response = await client.getStream(`/api/v1/agents/runs/${encodeURIComponent(executionId)}/files/trace.jsonl`);
|
|
192598
|
-
return Buffer.from(await response.arrayBuffer()).toString("utf-8");
|
|
192599
|
-
}
|
|
192600
|
-
function diffJson(expected, actual, basePath = "$") {
|
|
192601
|
-
if (expected === undefined || expected === null)
|
|
192602
|
-
return [];
|
|
192603
|
-
if (Object.is(expected, actual))
|
|
192604
|
-
return [];
|
|
192605
|
-
if (expected && actual && typeof expected === "object" && typeof actual === "object" && !Array.isArray(expected) && !Array.isArray(actual)) {
|
|
192606
|
-
const keys = new Set([...Object.keys(expected), ...Object.keys(actual)]);
|
|
192607
|
-
return [...keys].flatMap((key) => {
|
|
192608
|
-
const next = `${basePath}.${key}`;
|
|
192609
|
-
if (!(key in actual))
|
|
192610
|
-
return [{ path: next, type: "missing" }];
|
|
192611
|
-
if (!(key in expected))
|
|
192612
|
-
return [{ path: next, type: "extra" }];
|
|
192613
|
-
return diffJson(expected[key], actual[key], next);
|
|
192614
|
-
});
|
|
192615
|
-
}
|
|
192616
|
-
if (Array.isArray(expected) && Array.isArray(actual)) {
|
|
192617
|
-
const diffs = [];
|
|
192618
|
-
const max = Math.max(expected.length, actual.length);
|
|
192619
|
-
for (let index = 0;index < max; index += 1) {
|
|
192620
|
-
const next = `${basePath}[${index}]`;
|
|
192621
|
-
if (index >= actual.length)
|
|
192622
|
-
diffs.push({ path: next, type: "missing" });
|
|
192623
|
-
else if (index >= expected.length)
|
|
192624
|
-
diffs.push({ path: next, type: "extra" });
|
|
192625
|
-
else
|
|
192626
|
-
diffs.push(...diffJson(expected[index], actual[index], next));
|
|
192627
|
-
}
|
|
192628
|
-
return diffs;
|
|
192629
|
-
}
|
|
192630
|
-
return [
|
|
192631
|
-
{
|
|
192632
|
-
path: basePath,
|
|
192633
|
-
type: "changed",
|
|
192634
|
-
expected: JSON.stringify(expected),
|
|
192635
|
-
actual: JSON.stringify(actual)
|
|
192636
|
-
}
|
|
192637
|
-
];
|
|
192638
|
-
}
|
|
192639
|
-
function fileNames(files) {
|
|
192640
|
-
return (Array.isArray(files) ? files : []).map((file2) => String(file2.name ?? "")).filter(Boolean).sort();
|
|
192641
|
-
}
|
|
192642
|
-
function stringOrNull(value) {
|
|
192643
|
-
return typeof value === "string" && value.length > 0 ? value : null;
|
|
192644
|
-
}
|
|
192645
|
-
function runArtifactInventory(run) {
|
|
192646
|
-
const rows = [
|
|
192647
|
-
{ kind: "metadata", name: "run.json", path: "run.json", present: "yes" }
|
|
192648
|
-
];
|
|
192649
|
-
if (run.inputJson != null) {
|
|
192650
|
-
rows.push({ kind: "input", name: "input.json", path: "input.json", present: "yes" });
|
|
192651
|
-
}
|
|
192652
|
-
if (run.metadata != null) {
|
|
192653
|
-
rows.push({ kind: "metadata", name: "metadata.json", path: "metadata.json", present: "yes" });
|
|
192654
|
-
}
|
|
192655
|
-
if (run.expected != null) {
|
|
192656
|
-
rows.push({ kind: "expected", name: "expected.json", path: "expected.json", present: "yes" });
|
|
192657
|
-
}
|
|
192658
|
-
for (const name of fileNames(run.inputFiles)) {
|
|
192659
|
-
rows.push({ kind: "input", name, path: `input/${name}`, present: "yes" });
|
|
192660
|
-
}
|
|
192661
|
-
for (const name of fileNames(run.resultFiles).filter((name2) => name2 !== "issues.md" && name2 !== "trace.jsonl")) {
|
|
192662
|
-
rows.push({ kind: "output", name, path: `output/${name}`, present: "yes" });
|
|
192663
|
-
}
|
|
192664
|
-
for (const name of fileNames(run.expectedFiles)) {
|
|
192665
|
-
rows.push({ kind: "expected", name, path: `expected/${name}`, present: "yes" });
|
|
192666
|
-
}
|
|
192667
|
-
for (const name of fileNames(run.issueFiles)) {
|
|
192668
|
-
rows.push({ kind: "issues", name, path: name, present: "yes" });
|
|
192669
|
-
}
|
|
192670
|
-
for (const name of fileNames(run.traceFiles)) {
|
|
192671
|
-
rows.push({ kind: "trace", name, path: name, present: "yes" });
|
|
192672
|
-
}
|
|
192673
|
-
for (const name of fileNames(run.lockfileFiles)) {
|
|
192674
|
-
rows.push({ kind: "lockfile", name, path: name, present: "yes" });
|
|
192675
|
-
}
|
|
192676
|
-
return rows.sort((a, b2) => a.path.localeCompare(b2.path));
|
|
192677
|
-
}
|
|
192678
|
-
function normalizeGeneratedTokens(value) {
|
|
192679
|
-
return value.replace(/\d{4}-\d{2}-\d{2}/g, "<date>").replace(/\d{8}/g, "<date>");
|
|
192680
|
-
}
|
|
192681
|
-
function compareFileInventory(expected, output, normalizeDates) {
|
|
192682
|
-
const normalize = (value) => normalizeDates ? normalizeGeneratedTokens(value) : value;
|
|
192683
|
-
const outputByNormalized = new Map(output.map((name) => [normalize(name), name]));
|
|
192684
|
-
const expectedByNormalized = new Map(expected.map((name) => [normalize(name), name]));
|
|
192685
|
-
const matched = expected.map((expectedName2) => {
|
|
192686
|
-
const outputName = outputByNormalized.get(normalize(expectedName2));
|
|
192687
|
-
return outputName ? { expected: expectedName2, output: outputName } : null;
|
|
192688
|
-
}).filter((match) => Boolean(match));
|
|
192689
|
-
return {
|
|
192690
|
-
matched,
|
|
192691
|
-
missing: expected.filter((name) => !outputByNormalized.has(normalize(name))),
|
|
192692
|
-
extra: output.filter((name) => !expectedByNormalized.has(normalize(name)))
|
|
192693
|
-
};
|
|
192694
|
-
}
|
|
192695
|
-
async function compareMatchedFileText(client, left, right, out, matched, normalizeDates) {
|
|
192696
|
-
const expectedDir = path12.join(out, "expected");
|
|
192697
|
-
const outputDir = path12.join(out, "output");
|
|
192698
|
-
await fs7.mkdir(expectedDir, { recursive: true });
|
|
192699
|
-
await fs7.mkdir(outputDir, { recursive: true });
|
|
192700
|
-
const diffs = [];
|
|
192701
|
-
for (const pair of matched) {
|
|
192702
|
-
const expectedPath = path12.join(expectedDir, pair.expected);
|
|
192703
|
-
const outputPath = path12.join(outputDir, pair.output);
|
|
192704
|
-
await downloadStreamToFile(client, runFileUrl(left, pair.expected), expectedPath);
|
|
192705
|
-
await downloadStreamToFile(client, runFileUrl(right, pair.output), outputPath);
|
|
192706
|
-
const expectedText = extractComparableText(expectedPath);
|
|
192707
|
-
const outputText = extractComparableText(outputPath);
|
|
192708
|
-
if (expectedText.text == null || outputText.text == null) {
|
|
192709
|
-
const [expectedBuffer, outputBuffer] = await Promise.all([
|
|
192710
|
-
fs7.readFile(expectedPath),
|
|
192711
|
-
fs7.readFile(outputPath)
|
|
192712
|
-
]);
|
|
192713
|
-
const expectedHash = createHash3("sha256").update(expectedBuffer).digest("hex");
|
|
192714
|
-
const outputHash = createHash3("sha256").update(outputBuffer).digest("hex");
|
|
192715
|
-
diffs.push({
|
|
192716
|
-
...pair,
|
|
192717
|
-
status: expectedBuffer.length === outputBuffer.length && expectedHash === outputHash ? "binary-match" : "binary-different",
|
|
192718
|
-
expectedBytes: expectedBuffer.length,
|
|
192719
|
-
outputBytes: outputBuffer.length,
|
|
192720
|
-
expectedSha256: expectedHash,
|
|
192721
|
-
outputSha256: outputHash,
|
|
192722
|
-
textExtraction: {
|
|
192723
|
-
expected: expectedText.reason ?? null,
|
|
192724
|
-
output: outputText.reason ?? null
|
|
192725
|
-
}
|
|
192726
|
-
});
|
|
192727
|
-
continue;
|
|
192728
|
-
}
|
|
192729
|
-
const normalize = (value) => normalizeDates ? normalizeGeneratedTokens(value).trim() : value.trim();
|
|
192730
|
-
diffs.push({
|
|
192731
|
-
...pair,
|
|
192732
|
-
status: normalize(expectedText.text) === normalize(outputText.text) ? "match" : "text-different"
|
|
192733
|
-
});
|
|
192734
|
-
}
|
|
192735
|
-
return diffs;
|
|
192736
|
-
}
|
|
192737
|
-
function runFileUrl(side, name) {
|
|
192738
|
-
const runId = encodeURIComponent(side.runId);
|
|
192739
|
-
const filename = encodeURIComponent(name);
|
|
192740
|
-
return side.kind === "expected" ? `/api/v1/agents/runs/${runId}/expected/${filename}` : `/api/v1/agents/runs/${runId}/files/output/${filename}`;
|
|
192741
|
-
}
|
|
192742
|
-
function encodeRunArtifactPath(artifactPath) {
|
|
192743
|
-
return artifactPath.split("/").map(encodeURIComponent).join("/");
|
|
192744
|
-
}
|
|
192745
|
-
async function downloadStreamToFile(client, url2, target) {
|
|
192746
|
-
await fs7.mkdir(path12.dirname(target), { recursive: true });
|
|
192747
|
-
const response = await client.getStream(url2);
|
|
192748
|
-
await fs7.writeFile(target, Buffer.from(await response.arrayBuffer()));
|
|
192749
|
-
}
|
|
192750
|
-
function extractComparableText(file2) {
|
|
192751
|
-
const ext = path12.extname(file2).toLowerCase();
|
|
192752
|
-
if ([".txt", ".md", ".json", ".csv", ".xml"].includes(ext)) {
|
|
192753
|
-
return { text: readFileSync8(file2, "utf8") };
|
|
192754
|
-
}
|
|
192755
|
-
if (ext === ".pdf") {
|
|
192756
|
-
const result = spawnSync4("pdftotext", [file2, "-"], { encoding: "utf8" });
|
|
192757
|
-
return result.status === 0 ? { text: result.stdout } : { text: null, reason: textExtractionFallbackReason("pdftotext", result) };
|
|
192758
|
-
}
|
|
192759
|
-
if (ext === ".docx") {
|
|
192760
|
-
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))";
|
|
192761
|
-
const result = spawnSync4("python3", ["-c", script, file2], { encoding: "utf8" });
|
|
192762
|
-
return result.status === 0 ? { text: result.stdout } : { text: null, reason: textExtractionFallbackReason("python3", result) };
|
|
192763
|
-
}
|
|
192764
|
-
return { text: null, reason: `No text extractor for ${ext || "extensionless"} files` };
|
|
192765
|
-
}
|
|
192766
|
-
function textExtractionFallbackReason(tool, result) {
|
|
192767
|
-
if (result.error)
|
|
192768
|
-
return `${tool} unavailable; compared bytes instead`;
|
|
192769
|
-
return `${tool} could not extract text; compared bytes instead`;
|
|
192770
|
-
}
|
|
192771
|
-
function renderComparisonReport(report) {
|
|
192772
|
-
const header = `Comparison ${report.status}`;
|
|
192773
|
-
if (report.status === "pass") {
|
|
192774
|
-
success2(header);
|
|
192775
|
-
} else {
|
|
192776
|
-
error51(header);
|
|
192777
|
-
}
|
|
192778
|
-
for (const warning of report.warnings ?? []) {
|
|
192779
|
-
dim(`warning: ${warning}`);
|
|
192780
|
-
}
|
|
192781
|
-
console.log(table([
|
|
192782
|
-
{ item: "JSON differences", count: report.jsonDifferences.length },
|
|
192783
|
-
{ item: "Matched files", count: report.matchedFiles.length },
|
|
192784
|
-
{ item: "Missing files", count: report.missingFiles.length },
|
|
192785
|
-
{ item: "Extra files", count: report.extraFiles.length },
|
|
192786
|
-
{
|
|
192787
|
-
item: "Text differences",
|
|
192788
|
-
count: report.textDifferences.filter((diff) => diff.status === "text-different").length
|
|
192789
|
-
}
|
|
192790
|
-
], [
|
|
192791
|
-
{ key: "item", header: "ITEM" },
|
|
192792
|
-
{ key: "count", header: "COUNT" }
|
|
192793
|
-
]));
|
|
192794
|
-
for (const diff of report.textDifferences) {
|
|
192795
|
-
const textExtraction = diff.textExtraction;
|
|
192796
|
-
const reasons = [textExtraction?.expected, textExtraction?.output].filter((value) => typeof value === "string" && value.length > 0).filter((value, index, values) => values.indexOf(value) === index);
|
|
192797
|
-
for (const reason of reasons) {
|
|
192798
|
-
dim(`warning: ${String(diff.expected)} vs ${String(diff.output)}: ${reason}`);
|
|
192799
|
-
}
|
|
192800
|
-
}
|
|
192801
|
-
}
|
|
192802
|
-
|
|
192803
193350
|
// src/commands/agents/sessions.ts
|
|
192804
193351
|
function registerSessionCommands(agent) {
|
|
192805
193352
|
const session = agent.command("session").description("Manage agent builder sessions.").action(() => {
|
|
@@ -192848,18 +193395,16 @@ async function stopSession(sessionId, opts) {
|
|
|
192848
193395
|
|
|
192849
193396
|
// src/commands/agents/index.ts
|
|
192850
193397
|
function registerAgentCommands(program2) {
|
|
192851
|
-
const agent = program2.command("agents").
|
|
193398
|
+
const agent = program2.command("agents").description("Manage Eigenpal agents: Git source, datasets, runs, experiments, sessions, and releases.").action(() => {
|
|
192852
193399
|
process.stderr.write("`eigenpal agents` requires a subcommand. Run `eigenpal agents --help`.\n");
|
|
192853
193400
|
process.exit(2);
|
|
192854
193401
|
});
|
|
192855
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));
|
|
192856
|
-
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));
|
|
192857
193403
|
addJsonFlag(withPagination(withBaseUrl(agent.command("list")), 50)).description("List agents.").option("--search <q>", "Search by slug, name, or description").action(action(listAgents));
|
|
192858
193404
|
registerAgentFileCommands(agent);
|
|
192859
193405
|
addJsonFlag(agent.command("validate [dir]")).description("Validate a local agent package (layout, manifest, schemas, and Git source rules).").action(action(validateAgentCommand));
|
|
192860
193406
|
registerAgentSourceCommands(agent);
|
|
192861
193407
|
registerDatasetCommands(agent);
|
|
192862
|
-
registerRunCommands(agent);
|
|
192863
193408
|
registerExperimentCommands(agent);
|
|
192864
193409
|
registerSessionCommands(agent);
|
|
192865
193410
|
registerEnvCommands(agent);
|
|
@@ -193435,7 +193980,7 @@ async function init(name, opts) {
|
|
|
193435
193980
|
info("Next steps:");
|
|
193436
193981
|
dim(` cd ${targetDisplay}`);
|
|
193437
193982
|
dim(` eigenpal workflow validate # all three local checks`);
|
|
193438
|
-
dim(` eigenpal workflow
|
|
193983
|
+
dim(` eigenpal workflow run ${name} sample`);
|
|
193439
193984
|
}
|
|
193440
193985
|
function assertSafeTargetDir(target, dir) {
|
|
193441
193986
|
if (dir !== undefined && !dir.startsWith("/") && dir.split(/[/\\]/).some((s) => s === "..")) {
|
|
@@ -194806,80 +195351,6 @@ function formatEigenpalDirIfAvailable(dir) {
|
|
|
194806
195351
|
});
|
|
194807
195352
|
}
|
|
194808
195353
|
|
|
194809
|
-
// src/lib/resolve-workflow.ts
|
|
194810
|
-
var WORKFLOW_ID_PREFIX = "wf_";
|
|
194811
|
-
function looksLikeWorkflowId(value) {
|
|
194812
|
-
return value.startsWith(WORKFLOW_ID_PREFIX);
|
|
194813
|
-
}
|
|
194814
|
-
function levenshtein(a, b2, maxDistance) {
|
|
194815
|
-
if (a === b2)
|
|
194816
|
-
return 0;
|
|
194817
|
-
if (Math.abs(a.length - b2.length) > maxDistance)
|
|
194818
|
-
return maxDistance + 1;
|
|
194819
|
-
if (a.length === 0 || b2.length === 0)
|
|
194820
|
-
return a.length || b2.length;
|
|
194821
|
-
const prev = new Array(b2.length + 1);
|
|
194822
|
-
const curr = new Array(b2.length + 1);
|
|
194823
|
-
for (let j3 = 0;j3 <= b2.length; j3++)
|
|
194824
|
-
prev[j3] = j3;
|
|
194825
|
-
for (let i2 = 1;i2 <= a.length; i2++) {
|
|
194826
|
-
curr[0] = i2;
|
|
194827
|
-
let minRow = curr[0];
|
|
194828
|
-
for (let j3 = 1;j3 <= b2.length; j3++) {
|
|
194829
|
-
const cost = a[i2 - 1] === b2[j3 - 1] ? 0 : 1;
|
|
194830
|
-
curr[j3] = Math.min(prev[j3] + 1, curr[j3 - 1] + 1, prev[j3 - 1] + cost);
|
|
194831
|
-
if (curr[j3] < minRow)
|
|
194832
|
-
minRow = curr[j3];
|
|
194833
|
-
}
|
|
194834
|
-
if (minRow > maxDistance)
|
|
194835
|
-
return maxDistance + 1;
|
|
194836
|
-
for (let j3 = 0;j3 <= b2.length; j3++)
|
|
194837
|
-
prev[j3] = curr[j3];
|
|
194838
|
-
}
|
|
194839
|
-
return prev[b2.length];
|
|
194840
|
-
}
|
|
194841
|
-
async function findSimilarSlug(client, slug) {
|
|
194842
|
-
try {
|
|
194843
|
-
const res = await client.get("/api/v1/workflows", {
|
|
194844
|
-
search: slug,
|
|
194845
|
-
limit: "20"
|
|
194846
|
-
});
|
|
194847
|
-
const candidates = (res.data ?? []).map((w3) => w3.name).filter((n) => typeof n === "string" && n.length > 0);
|
|
194848
|
-
let best = null;
|
|
194849
|
-
for (const candidate of candidates) {
|
|
194850
|
-
const distance = levenshtein(candidate, slug, 2);
|
|
194851
|
-
if (distance <= 2 && (best === null || distance < best.distance)) {
|
|
194852
|
-
best = { name: candidate, distance };
|
|
194853
|
-
}
|
|
194854
|
-
}
|
|
194855
|
-
return best?.name ?? null;
|
|
194856
|
-
} catch {
|
|
194857
|
-
return null;
|
|
194858
|
-
}
|
|
194859
|
-
}
|
|
194860
|
-
function notFound(idOrSlug, suggestion) {
|
|
194861
|
-
const base = `Workflow "${idOrSlug}" not found on the server.`;
|
|
194862
|
-
const action2 = "Push it first, or run `eigenpal workflow list` to see what's available.";
|
|
194863
|
-
return new Error(suggestion ? `${base} Did you mean "${suggestion}"? ${action2}` : `${base} ${action2}`);
|
|
194864
|
-
}
|
|
194865
|
-
async function resolveWorkflowId(client, idOrSlug) {
|
|
194866
|
-
if (looksLikeWorkflowId(idOrSlug)) {
|
|
194867
|
-
try {
|
|
194868
|
-
const wf = await client.get(`/api/v1/workflows/${idOrSlug}`);
|
|
194869
|
-
return wf.id;
|
|
194870
|
-
} catch {
|
|
194871
|
-
throw notFound(idOrSlug, null);
|
|
194872
|
-
}
|
|
194873
|
-
}
|
|
194874
|
-
const res = await client.get("/api/v1/workflows", { name: idOrSlug });
|
|
194875
|
-
const match = res.data?.[0];
|
|
194876
|
-
if (!match) {
|
|
194877
|
-
const suggestion = await findSimilarSlug(client, idOrSlug);
|
|
194878
|
-
throw notFound(idOrSlug, suggestion);
|
|
194879
|
-
}
|
|
194880
|
-
return match.id;
|
|
194881
|
-
}
|
|
194882
|
-
|
|
194883
195354
|
// src/commands/workflow/clear.ts
|
|
194884
195355
|
import { existsSync as existsSync14, readdirSync as readdirSync5, rmSync as rmSync3, statSync as statSync4 } from "fs";
|
|
194885
195356
|
import { basename, join as join7 } from "path";
|
|
@@ -195173,125 +195644,6 @@ function registerEvaluatorTypeCommands(parent) {
|
|
|
195173
195644
|
});
|
|
195174
195645
|
}
|
|
195175
195646
|
|
|
195176
|
-
// src/lib/watch.ts
|
|
195177
|
-
var TERMINAL_STATUSES = new Set(["completed", "failed", "cancelled", "rejected"]);
|
|
195178
|
-
var STATUS_BADGES = {
|
|
195179
|
-
pending: "[ ]",
|
|
195180
|
-
running: "[..]",
|
|
195181
|
-
completed: "[ok]",
|
|
195182
|
-
failed: "[x]",
|
|
195183
|
-
cancelled: "[-]",
|
|
195184
|
-
skipped: "[s]"
|
|
195185
|
-
};
|
|
195186
|
-
function statusBadge(status2) {
|
|
195187
|
-
return STATUS_BADGES[status2] ?? `[${status2.slice(0, 3)}]`;
|
|
195188
|
-
}
|
|
195189
|
-
function durationSuffix(ms) {
|
|
195190
|
-
const formatted = formatDuration(ms);
|
|
195191
|
-
return formatted ? ` ${formatted}` : "";
|
|
195192
|
-
}
|
|
195193
|
-
function renderFrame(snapshot) {
|
|
195194
|
-
const lines = [
|
|
195195
|
-
`${statusBadge(snapshot.status)} ${snapshot.id}${durationSuffix(snapshot.durationMs)}`
|
|
195196
|
-
];
|
|
195197
|
-
for (const step of snapshot.stepExecutions ?? []) {
|
|
195198
|
-
const errSuffix = step.error ? ` err: ${step.error.slice(0, 80)}` : "";
|
|
195199
|
-
lines.push(` ${statusBadge(step.status)} ${step.stepName}${durationSuffix(step.durationMs)}${errSuffix}`);
|
|
195200
|
-
if (step.outputPreview) {
|
|
195201
|
-
lines.push(` ↳ ${step.outputPreview}`);
|
|
195202
|
-
}
|
|
195203
|
-
}
|
|
195204
|
-
if (snapshot.error && (snapshot.stepExecutions ?? []).length === 0) {
|
|
195205
|
-
lines.push(` err: ${snapshot.error.slice(0, 200)}`);
|
|
195206
|
-
}
|
|
195207
|
-
return lines.join(`
|
|
195208
|
-
`);
|
|
195209
|
-
}
|
|
195210
|
-
var ANSI_CURSOR_UP = "\x1B[A";
|
|
195211
|
-
var ANSI_CLEAR_LINE = "\x1B[2K\r";
|
|
195212
|
-
function makeAnsiRenderer() {
|
|
195213
|
-
const isTty = Boolean(process.stderr.isTTY);
|
|
195214
|
-
let previousLineCount = 0;
|
|
195215
|
-
return (frame) => {
|
|
195216
|
-
let buf = "";
|
|
195217
|
-
if (isTty) {
|
|
195218
|
-
for (let i2 = 0;i2 < previousLineCount; i2++) {
|
|
195219
|
-
buf += i2 === 0 ? ANSI_CLEAR_LINE : `${ANSI_CURSOR_UP}${ANSI_CLEAR_LINE}`;
|
|
195220
|
-
}
|
|
195221
|
-
}
|
|
195222
|
-
buf += frame;
|
|
195223
|
-
if (!frame.endsWith(`
|
|
195224
|
-
`))
|
|
195225
|
-
buf += `
|
|
195226
|
-
`;
|
|
195227
|
-
if (!isTty && previousLineCount > 0)
|
|
195228
|
-
buf += `
|
|
195229
|
-
`;
|
|
195230
|
-
process.stderr.write(buf);
|
|
195231
|
-
previousLineCount = frame.split(`
|
|
195232
|
-
`).length;
|
|
195233
|
-
};
|
|
195234
|
-
}
|
|
195235
|
-
function defaultSleep(ms) {
|
|
195236
|
-
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
195237
|
-
}
|
|
195238
|
-
function snapshotsDiffer(a, b2) {
|
|
195239
|
-
if (a.status !== b2.status)
|
|
195240
|
-
return true;
|
|
195241
|
-
const aSteps = a.stepExecutions ?? [];
|
|
195242
|
-
const bSteps = b2.stepExecutions ?? [];
|
|
195243
|
-
if (aSteps.length !== bSteps.length)
|
|
195244
|
-
return true;
|
|
195245
|
-
for (let i2 = 0;i2 < aSteps.length; i2++) {
|
|
195246
|
-
if (aSteps[i2].stepName !== bSteps[i2].stepName || aSteps[i2].status !== bSteps[i2].status) {
|
|
195247
|
-
return true;
|
|
195248
|
-
}
|
|
195249
|
-
}
|
|
195250
|
-
return false;
|
|
195251
|
-
}
|
|
195252
|
-
function defaultShouldAbort(err2) {
|
|
195253
|
-
return err2 instanceof ApiError && err2.status >= 400 && err2.status < 500;
|
|
195254
|
-
}
|
|
195255
|
-
async function watchExecution(opts) {
|
|
195256
|
-
const sleep = opts.sleep ?? defaultSleep;
|
|
195257
|
-
const now = opts.now ?? (() => Date.now());
|
|
195258
|
-
const render = opts.render ?? makeAnsiRenderer();
|
|
195259
|
-
const maxMs = opts.maxMs ?? 30 * 60 * 1000;
|
|
195260
|
-
const transitionInterval = opts.transitionIntervalMs ?? 2000;
|
|
195261
|
-
const steadyInterval = opts.steadyIntervalMs ?? 5000;
|
|
195262
|
-
const shouldAbort = opts.shouldAbort ?? defaultShouldAbort;
|
|
195263
|
-
const start = now();
|
|
195264
|
-
let previous;
|
|
195265
|
-
let lastFetched;
|
|
195266
|
-
while (true) {
|
|
195267
|
-
let current;
|
|
195268
|
-
try {
|
|
195269
|
-
current = await opts.fetch();
|
|
195270
|
-
} catch (err2) {
|
|
195271
|
-
if (shouldAbort(err2))
|
|
195272
|
-
throw err2;
|
|
195273
|
-
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
195274
|
-
render(`(network) ${message.slice(0, 120)}`);
|
|
195275
|
-
if (lastFetched && now() - start >= maxMs) {
|
|
195276
|
-
return { final: lastFetched, detached: true };
|
|
195277
|
-
}
|
|
195278
|
-
await sleep(steadyInterval);
|
|
195279
|
-
continue;
|
|
195280
|
-
}
|
|
195281
|
-
lastFetched = current;
|
|
195282
|
-
render(renderFrame(current));
|
|
195283
|
-
if (TERMINAL_STATUSES.has(current.status)) {
|
|
195284
|
-
return { final: current, detached: false };
|
|
195285
|
-
}
|
|
195286
|
-
if (now() - start >= maxMs) {
|
|
195287
|
-
return { final: current, detached: true };
|
|
195288
|
-
}
|
|
195289
|
-
const transitioned = previous == null || snapshotsDiffer(previous, current);
|
|
195290
|
-
previous = current;
|
|
195291
|
-
await sleep(transitioned ? transitionInterval : steadyInterval);
|
|
195292
|
-
}
|
|
195293
|
-
}
|
|
195294
|
-
|
|
195295
195647
|
// src/commands/workflow/exec.ts
|
|
195296
195648
|
import { join as join11 } from "path";
|
|
195297
195649
|
|
|
@@ -195480,7 +195832,8 @@ var DEFAULT_CONCURRENCY = 3;
|
|
|
195480
195832
|
async function pollExecution(client, executionId) {
|
|
195481
195833
|
const start = Date.now();
|
|
195482
195834
|
while (Date.now() - start < MAX_POLL_MS) {
|
|
195483
|
-
const
|
|
195835
|
+
const payload = await client.get(`/api/v1/runs/${executionId}?include=detail`);
|
|
195836
|
+
const res = "run" in payload && payload.run ? payload.run : payload;
|
|
195484
195837
|
if (["completed", "failed", "cancelled", "rejected"].includes(res.status))
|
|
195485
195838
|
return res;
|
|
195486
195839
|
await new Promise((r5) => setTimeout(r5, POLL_INTERVAL_MS));
|
|
@@ -195558,7 +195911,8 @@ async function runExec(client, dir, workflowIdOrSlug, exampleNames, options8 = {
|
|
|
195558
195911
|
else
|
|
195559
195912
|
failed++;
|
|
195560
195913
|
try {
|
|
195561
|
-
const
|
|
195914
|
+
const artifactPayload = await client.get(`/api/v1/runs/${executionId}?include=detail`);
|
|
195915
|
+
const artifact = "run" in artifactPayload && artifactPayload.run ? artifactPayload.run : artifactPayload;
|
|
195562
195916
|
const artifactDir = await writeExecutionArtifacts(client, exampleDir, artifact);
|
|
195563
195917
|
if (!interactive)
|
|
195564
195918
|
console.log(ui.dim(` Artifacts: ${artifactDir}`));
|
|
@@ -195585,14 +195939,13 @@ ${ui.bold("Results:")} ${ui.ok(String(passed))} passed, ${failed > 0 ? ui.err(St
|
|
|
195585
195939
|
}
|
|
195586
195940
|
|
|
195587
195941
|
// src/commands/workflow/execution.ts
|
|
195588
|
-
function
|
|
195589
|
-
const
|
|
195590
|
-
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", `
|
|
195591
195944
|
Examples:
|
|
195592
|
-
$ eigenpal workflow
|
|
195593
|
-
$ eigenpal workflow
|
|
195594
|
-
$ eigenpal workflow
|
|
195595
|
-
$ 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'
|
|
195596
195949
|
|
|
195597
195950
|
Reads examples from ./dataset/examples/<example>/ and writes per-run
|
|
195598
195951
|
artifacts to ./dataset/examples/<example>/executions/<timestamp>/.
|
|
@@ -195619,272 +195972,6 @@ Exits 1 when any example fails.
|
|
|
195619
195972
|
formatEigenpalDirIfAvailable(config2.dir);
|
|
195620
195973
|
}
|
|
195621
195974
|
});
|
|
195622
|
-
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'
|
|
195623
|
-
` + ` $ eigenpal workflow execution get exec_… --json | jq '.stepExecutions[] | select(.status=="failed")'
|
|
195624
|
-
`);
|
|
195625
|
-
addJsonFlag(withBaseUrl(getCmd)).action(getExecution);
|
|
195626
|
-
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");
|
|
195627
|
-
addJsonFlag(withBaseUrl(withPagination(listCmd))).action(listExecutions);
|
|
195628
|
-
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);
|
|
195629
|
-
withBaseUrl(watchCmd).action(watchCommand);
|
|
195630
|
-
const compareCmd = execution.command("compare <executionA> <executionB>").description("Diff two executions side-by-side, per step.").option("--step <name>", "Restrict comparison to one step");
|
|
195631
|
-
withBaseUrl(compareCmd).action(compareExecutions);
|
|
195632
|
-
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", `
|
|
195633
|
-
Examples:
|
|
195634
|
-
$ eigenpal workflow execution cancel exec_123abc
|
|
195635
|
-
$ eigenpal workflow execution cancel exec_123abc --yes # required in CI
|
|
195636
|
-
$ eigenpal workflow execution cancel exec_123abc --json # raw server payload
|
|
195637
|
-
|
|
195638
|
-
Behavior:
|
|
195639
|
-
- created/pending: transitions straight to cancelled.
|
|
195640
|
-
- running/waiting: stamps cancelRequestedAt; the worker observes it
|
|
195641
|
-
between step transitions and exits cleanly.
|
|
195642
|
-
- already terminal (completed/failed/cancelled): no-op, exits 0 with an
|
|
195643
|
-
info line. Idempotent — safe to retry.
|
|
195644
|
-
`);
|
|
195645
|
-
addJsonFlag(withBaseUrl(cancelCmd)).action(async (executionId, opts) => {
|
|
195646
|
-
const config2 = resolveConfig(opts);
|
|
195647
|
-
requireApiKey(config2);
|
|
195648
|
-
const client = new ApiClient(config2);
|
|
195649
|
-
try {
|
|
195650
|
-
await cancelExecution(client, executionId, opts);
|
|
195651
|
-
} catch (err2) {
|
|
195652
|
-
error51(err2 instanceof Error ? err2.message : String(err2));
|
|
195653
|
-
process.exit(1);
|
|
195654
|
-
}
|
|
195655
|
-
});
|
|
195656
|
-
}
|
|
195657
|
-
async function cancelExecution(client, executionId, opts) {
|
|
195658
|
-
if (!opts.yes && !isTTY()) {
|
|
195659
|
-
throw new Error("cancel is destructive and requires --yes when run non-interactively");
|
|
195660
|
-
}
|
|
195661
|
-
const result = await client.post(`/api/v1/workflows/executions/${executionId}/cancel`);
|
|
195662
|
-
if (opts.json) {
|
|
195663
|
-
console.log(JSON.stringify(result, null, 2));
|
|
195664
|
-
return;
|
|
195665
|
-
}
|
|
195666
|
-
const status2 = result.status;
|
|
195667
|
-
if (status2 === "already-terminal") {
|
|
195668
|
-
info(`already terminal: ${result.wasStatus ?? "unknown"}`);
|
|
195669
|
-
return;
|
|
195670
|
-
}
|
|
195671
|
-
if (status2 === "cancelled" || status2 === "cancellation-requested") {
|
|
195672
|
-
success2("cancellation requested");
|
|
195673
|
-
return;
|
|
195674
|
-
}
|
|
195675
|
-
info(`server returned status: ${String(status2 ?? "unknown")}`);
|
|
195676
|
-
}
|
|
195677
|
-
async function getExecution(executionId, opts) {
|
|
195678
|
-
const config2 = resolveConfig(opts);
|
|
195679
|
-
requireApiKey(config2);
|
|
195680
|
-
const client = new ApiClient(config2);
|
|
195681
|
-
try {
|
|
195682
|
-
const result = await client.get(`/api/v1/workflows/executions/${executionId}?includeSteps=true`);
|
|
195683
|
-
if (opts.step) {
|
|
195684
|
-
const wanted = new Set(opts.step.split(",").map((s5) => s5.trim()));
|
|
195685
|
-
const filtered = (result.stepExecutions ?? []).filter((s5) => wanted.has(s5.stepName));
|
|
195686
|
-
console.log(JSON.stringify({ stepExecutions: filtered.map((s5) => filterIncludes(s5, opts.include)) }, null, 2));
|
|
195687
|
-
return;
|
|
195688
|
-
}
|
|
195689
|
-
if (opts.json) {
|
|
195690
|
-
console.log(JSON.stringify({
|
|
195691
|
-
...result,
|
|
195692
|
-
stepExecutions: (result.stepExecutions ?? []).map((s5) => filterIncludes(s5, opts.include))
|
|
195693
|
-
}, null, 2));
|
|
195694
|
-
return;
|
|
195695
|
-
}
|
|
195696
|
-
const snapshot = {
|
|
195697
|
-
id: result.id ?? executionId,
|
|
195698
|
-
status: result.status ?? "unknown",
|
|
195699
|
-
startedAt: result.startedAt ?? null,
|
|
195700
|
-
completedAt: result.completedAt ?? null,
|
|
195701
|
-
durationMs: result.durationMs ?? null,
|
|
195702
|
-
stepExecutions: (result.stepExecutions ?? []).map((s5) => ({
|
|
195703
|
-
stepName: s5.stepName,
|
|
195704
|
-
status: s5.status,
|
|
195705
|
-
durationMs: s5.durationMs,
|
|
195706
|
-
error: s5.error,
|
|
195707
|
-
outputPreview: previewOutput(s5.outputData)
|
|
195708
|
-
})),
|
|
195709
|
-
error: result.error ?? null
|
|
195710
|
-
};
|
|
195711
|
-
console.log(renderFrame(snapshot));
|
|
195712
|
-
process.stderr.write(`${ui.dim(`use --json for the full payload, then pipe to jq for field projection`)}
|
|
195713
|
-
`);
|
|
195714
|
-
} catch (err2) {
|
|
195715
|
-
error51(err2 instanceof Error ? err2.message : String(err2));
|
|
195716
|
-
process.exit(1);
|
|
195717
|
-
}
|
|
195718
|
-
}
|
|
195719
|
-
async function listExecutions(workflow2, opts) {
|
|
195720
|
-
const config2 = resolveConfig(opts);
|
|
195721
|
-
requireApiKey(config2);
|
|
195722
|
-
const client = new ApiClient(config2);
|
|
195723
|
-
const workflowId = await resolveWorkflowId(client, workflow2);
|
|
195724
|
-
const params = {
|
|
195725
|
-
limit: String(opts.limit),
|
|
195726
|
-
offset: String(opts.offset),
|
|
195727
|
-
workflowId
|
|
195728
|
-
};
|
|
195729
|
-
if (opts.status)
|
|
195730
|
-
params.status = opts.status;
|
|
195731
|
-
try {
|
|
195732
|
-
const raw = await client.get(`/api/v1/workflows/${workflowId}/executions`, params);
|
|
195733
|
-
renderListResult(raw, [
|
|
195734
|
-
{ key: "id", header: "id" },
|
|
195735
|
-
{ key: "status", header: "status" },
|
|
195736
|
-
{ key: "startedAt", header: "startedAt", format: formatTimestamp },
|
|
195737
|
-
{
|
|
195738
|
-
key: "durationMs",
|
|
195739
|
-
header: "duration",
|
|
195740
|
-
align: "right",
|
|
195741
|
-
format: (v11) => typeof v11 === "number" ? formatDuration(v11) : "-"
|
|
195742
|
-
}
|
|
195743
|
-
], { json: opts.json, entityLabel: "execution" });
|
|
195744
|
-
} catch (err2) {
|
|
195745
|
-
error51(err2 instanceof Error ? err2.message : String(err2));
|
|
195746
|
-
process.exit(1);
|
|
195747
|
-
}
|
|
195748
|
-
}
|
|
195749
|
-
async function watchCommand(executionId, opts) {
|
|
195750
|
-
const config2 = resolveConfig(opts);
|
|
195751
|
-
requireApiKey(config2);
|
|
195752
|
-
const client = new ApiClient(config2);
|
|
195753
|
-
try {
|
|
195754
|
-
const result = await watchExecution({
|
|
195755
|
-
fetch: () => client.get(`/api/v1/workflows/executions/${executionId}?includeSteps=true`),
|
|
195756
|
-
maxMs: opts.maxWait * 1000
|
|
195757
|
-
});
|
|
195758
|
-
if (result.detached) {
|
|
195759
|
-
process.stderr.write(`
|
|
195760
|
-
Detached after ${opts.maxWait}s without reaching terminal status. Re-run \`eigenpal workflow execution watch ${executionId}\` to resume.
|
|
195761
|
-
`);
|
|
195762
|
-
process.exit(2);
|
|
195763
|
-
}
|
|
195764
|
-
if (result.final.status === "failed" || result.final.status === "cancelled") {
|
|
195765
|
-
process.exit(1);
|
|
195766
|
-
}
|
|
195767
|
-
} catch (err2) {
|
|
195768
|
-
error51(err2 instanceof Error ? err2.message : String(err2));
|
|
195769
|
-
process.exit(1);
|
|
195770
|
-
}
|
|
195771
|
-
}
|
|
195772
|
-
async function compareExecutions(idA, idB, opts) {
|
|
195773
|
-
const config2 = resolveConfig(opts);
|
|
195774
|
-
requireApiKey(config2);
|
|
195775
|
-
const client = new ApiClient(config2);
|
|
195776
|
-
try {
|
|
195777
|
-
const [a5, b8] = await Promise.all([
|
|
195778
|
-
client.get(`/api/v1/workflows/executions/${idA}?includeSteps=true`),
|
|
195779
|
-
client.get(`/api/v1/workflows/executions/${idB}?includeSteps=true`)
|
|
195780
|
-
]);
|
|
195781
|
-
const stepNames = new Set;
|
|
195782
|
-
for (const s5 of a5.stepExecutions ?? [])
|
|
195783
|
-
stepNames.add(s5.stepName);
|
|
195784
|
-
for (const s5 of b8.stepExecutions ?? [])
|
|
195785
|
-
stepNames.add(s5.stepName);
|
|
195786
|
-
const ordered = Array.from(stepNames).sort();
|
|
195787
|
-
const targetSteps = opts.step ? ordered.filter((n5) => n5 === opts.step) : ordered;
|
|
195788
|
-
console.log(`${ui.dim("A =")} ${ui.bold(idA)} ${ui.dim("status=")}${colorStatus(a5.status)} ${ui.dim("durationMs=")}${a5.durationMs ?? "-"}`);
|
|
195789
|
-
console.log(`${ui.dim("B =")} ${ui.bold(idB)} ${ui.dim("status=")}${colorStatus(b8.status)} ${ui.dim("durationMs=")}${b8.durationMs ?? "-"}`);
|
|
195790
|
-
console.log("");
|
|
195791
|
-
console.log(ui.dim(`step A status B status Δ duration output`));
|
|
195792
|
-
console.log(ui.dim(`──────────────────────── ───────── ───────── ────────── ──────`));
|
|
195793
|
-
for (const stepName of targetSteps) {
|
|
195794
|
-
const aStep = (a5.stepExecutions ?? []).find((s5) => s5.stepName === stepName);
|
|
195795
|
-
const bStep = (b8.stepExecutions ?? []).find((s5) => s5.stepName === stepName);
|
|
195796
|
-
const aStatus = aStep?.status ?? "—";
|
|
195797
|
-
const bStatus = bStep?.status ?? "—";
|
|
195798
|
-
const aDur = aStep?.durationMs ?? null;
|
|
195799
|
-
const bDur = bStep?.durationMs ?? null;
|
|
195800
|
-
const delta = aDur != null && bDur != null ? `${bDur - aDur >= 0 ? "+" : ""}${bDur - aDur}ms` : "—";
|
|
195801
|
-
const outputState = compareOutputs(aStep?.output, bStep?.output);
|
|
195802
|
-
console.log(`${stepName.padEnd(28)} ${padColored(colorStatus(aStatus), aStatus, 11)} ${padColored(colorStatus(bStatus), bStatus, 11)} ${delta.padEnd(12)} ${colorOutputState(outputState)}`);
|
|
195803
|
-
}
|
|
195804
|
-
const finalState = compareOutputs(a5.output, b8.output);
|
|
195805
|
-
console.log("");
|
|
195806
|
-
console.log(`${ui.dim("final output:")} ${colorOutputState(finalState)}`);
|
|
195807
|
-
} catch (err2) {
|
|
195808
|
-
error51(err2 instanceof Error ? err2.message : String(err2));
|
|
195809
|
-
process.exit(1);
|
|
195810
|
-
}
|
|
195811
|
-
}
|
|
195812
|
-
function padColored(colored, plain, width) {
|
|
195813
|
-
const pad = Math.max(0, width - plain.length);
|
|
195814
|
-
return colored + " ".repeat(pad);
|
|
195815
|
-
}
|
|
195816
|
-
function colorStatus(status2) {
|
|
195817
|
-
if (!status2)
|
|
195818
|
-
return "—";
|
|
195819
|
-
if (status2 === "completed")
|
|
195820
|
-
return ui.ok(status2);
|
|
195821
|
-
if (status2 === "failed" || status2 === "cancelled")
|
|
195822
|
-
return ui.err(status2);
|
|
195823
|
-
if (status2 === "running" || status2 === "pending")
|
|
195824
|
-
return ui.info(status2);
|
|
195825
|
-
return status2;
|
|
195826
|
-
}
|
|
195827
|
-
function colorOutputState(state) {
|
|
195828
|
-
if (state === "identical")
|
|
195829
|
-
return ui.ok(state);
|
|
195830
|
-
if (state === "—")
|
|
195831
|
-
return ui.dim(state);
|
|
195832
|
-
if (state.startsWith("differs"))
|
|
195833
|
-
return ui.warn(state);
|
|
195834
|
-
return state;
|
|
195835
|
-
}
|
|
195836
|
-
function compareOutputs(a5, b8) {
|
|
195837
|
-
if (a5 === undefined && b8 === undefined)
|
|
195838
|
-
return "—";
|
|
195839
|
-
if (a5 === undefined)
|
|
195840
|
-
return "B only";
|
|
195841
|
-
if (b8 === undefined)
|
|
195842
|
-
return "A only";
|
|
195843
|
-
const aJson = JSON.stringify(a5);
|
|
195844
|
-
const bJson = JSON.stringify(b8);
|
|
195845
|
-
if (aJson === bJson)
|
|
195846
|
-
return "identical";
|
|
195847
|
-
return `differs (A=${aJson.length}b, B=${bJson.length}b)`;
|
|
195848
|
-
}
|
|
195849
|
-
var PREVIEW_MAX = 120;
|
|
195850
|
-
function previewOutput(value) {
|
|
195851
|
-
if (value == null)
|
|
195852
|
-
return null;
|
|
195853
|
-
if (typeof value === "string") {
|
|
195854
|
-
if (value.length === 0)
|
|
195855
|
-
return null;
|
|
195856
|
-
return value.length > PREVIEW_MAX ? `${value.slice(0, PREVIEW_MAX - 1)}…` : value;
|
|
195857
|
-
}
|
|
195858
|
-
let json2;
|
|
195859
|
-
try {
|
|
195860
|
-
json2 = JSON.stringify(value);
|
|
195861
|
-
} catch {
|
|
195862
|
-
return null;
|
|
195863
|
-
}
|
|
195864
|
-
if (!json2 || json2 === "{}" || json2 === "[]" || json2 === "null")
|
|
195865
|
-
return null;
|
|
195866
|
-
const flat = json2.replace(/\s+/g, " ");
|
|
195867
|
-
return flat.length > PREVIEW_MAX ? `${flat.slice(0, PREVIEW_MAX - 1)}…` : flat;
|
|
195868
|
-
}
|
|
195869
|
-
function filterIncludes(step, includes) {
|
|
195870
|
-
if (!includes)
|
|
195871
|
-
return step;
|
|
195872
|
-
const wanted = new Set(includes.split(",").map((s5) => s5.trim()));
|
|
195873
|
-
const out = { stepName: step.stepName, status: step.status };
|
|
195874
|
-
if (wanted.has("input") || wanted.has("config") || wanted.has("resolvedConfig")) {
|
|
195875
|
-
out.input = step.resolvedConfig;
|
|
195876
|
-
}
|
|
195877
|
-
if (wanted.has("inputRef") || wanted.has("inputData"))
|
|
195878
|
-
out.inputRef = step.inputData;
|
|
195879
|
-
if (wanted.has("output") || wanted.has("outputData"))
|
|
195880
|
-
out.output = step.outputData;
|
|
195881
|
-
if (wanted.has("error"))
|
|
195882
|
-
out.error = step.error;
|
|
195883
|
-
if (wanted.has("duration"))
|
|
195884
|
-
out.durationMs = step.durationMs;
|
|
195885
|
-
if ("overrideMode" in step)
|
|
195886
|
-
out.overrideMode = step.overrideMode;
|
|
195887
|
-
return out;
|
|
195888
195975
|
}
|
|
195889
195976
|
|
|
195890
195977
|
// src/commands/workflow/experiment-compare.ts
|
|
@@ -196174,13 +196261,13 @@ function exitUnsupported(type) {
|
|
|
196174
196261
|
` + ` redesign in EIG-104.
|
|
196175
196262
|
|
|
196176
196263
|
` + ` To run a single step today, push the workflow and run it via:
|
|
196177
|
-
` + ` $ eigenpal workflow
|
|
196264
|
+
` + ` $ eigenpal workflow run <workflow-id>
|
|
196178
196265
|
` + ` $ eigenpal workflow experiment run <workflow-id> --example-id <name>`);
|
|
196179
196266
|
process.exit(UNSUPPORTED_TYPE_EXIT);
|
|
196180
196267
|
}
|
|
196181
196268
|
function registerStepExecCommands(workflow2) {
|
|
196182
196269
|
const step = workflow2.command("step").description("Run a single workflow step (placeholder; see EIG-104).");
|
|
196183
|
-
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) => {
|
|
196184
196271
|
const known = listStepTypes();
|
|
196185
196272
|
if (!known.includes(rawType)) {
|
|
196186
196273
|
error51(`Unknown step type: ${rawType}. Run \`eigenpal workflow step-type list\`.`);
|
|
@@ -197086,7 +197173,7 @@ YAML's \`name:\` field). Both:
|
|
|
197086
197173
|
registerEvaluatorsCommands(workflow2);
|
|
197087
197174
|
registerDatasetCommands2(workflow2);
|
|
197088
197175
|
registerExperimentCommands2(workflow2);
|
|
197089
|
-
|
|
197176
|
+
registerWorkflowRunCommand(workflow2);
|
|
197090
197177
|
registerVersionsCommands(workflow2);
|
|
197091
197178
|
registerStepTypeCommands(workflow2);
|
|
197092
197179
|
registerEvaluatorTypeCommands(workflow2);
|
|
@@ -197910,7 +197997,7 @@ ${ui.bold(`✗ ${failedExecs.length}/${totalCount} did not pass`)}
|
|
|
197910
197997
|
const reason = (e8.error ?? "(no error message — fetch full execution)").replace(/\s+/g, " ").slice(0, 200);
|
|
197911
197998
|
process.stderr.write(` ${ui.bold(label2)} ${ui.dim(`[${e8.status}]`)} ${reason}
|
|
197912
197999
|
`);
|
|
197913
|
-
process.stderr.write(` ${ui.dim(`eigenpal
|
|
198000
|
+
process.stderr.write(` ${ui.dim(`eigenpal runs get ${e8.id}`)}
|
|
197914
198001
|
`);
|
|
197915
198002
|
}
|
|
197916
198003
|
process.stderr.write(`
|
|
@@ -198284,7 +198371,7 @@ var HELP_GROUPS = [
|
|
|
198284
198371
|
},
|
|
198285
198372
|
{
|
|
198286
198373
|
title: "Workflows",
|
|
198287
|
-
description: "Definition, evaluators, dataset, experiments,
|
|
198374
|
+
description: "Definition, evaluators, dataset, experiments, and local runs",
|
|
198288
198375
|
commands: ["workflow"]
|
|
198289
198376
|
},
|
|
198290
198377
|
{
|
|
@@ -198455,7 +198542,7 @@ program2.name("eigenpal").description("Eigenpal CLI — AI workflows, evals, and
|
|
|
198455
198542
|
setQuiet(true);
|
|
198456
198543
|
});
|
|
198457
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)));
|
|
198458
|
-
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) => {
|
|
198459
198546
|
try {
|
|
198460
198547
|
if (name) {
|
|
198461
198548
|
await init(name, opts);
|
|
@@ -198502,6 +198589,7 @@ authCmd.command("use [profile]").description("Switch the active profile (persist
|
|
|
198502
198589
|
});
|
|
198503
198590
|
registerWorkflowCommands(program2);
|
|
198504
198591
|
registerAgentCommands(program2);
|
|
198592
|
+
registerRunsCommands(program2);
|
|
198505
198593
|
registerGitCommands(program2);
|
|
198506
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) => {
|
|
198507
198595
|
try {
|