@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/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.8",
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 agents runs artifacts fetch.").option("--dir <dir>", "Repository directory").action(action(pullSource));
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/agents/dataset.ts
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/commands/agents/shared.ts
191277
- import { existsSync as existsSync6, promises as fs } from "node:fs";
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 fs.readdir(current, { withFileTypes: true })) {
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 = path6.join(current, entry.name);
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 = path6.relative(dir, absolute).split(path6.sep).join("/");
191590
+ const relative = path7.relative(dir, absolute).split(path7.sep).join("/");
191310
191591
  out.push({
191311
191592
  path: relative,
191312
- contentBase64: (await fs.readFile(absolute)).toString("base64")
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 fs.mkdir(path6.dirname(file2), { recursive: true });
191322
- await fs.writeFile(file2, Buffer.from(contentBase64, "base64"));
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/agents/runs/${encodeURIComponent(executionId)}`);
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 fs2 } from "node:fs";
191425
- import path7 from "node:path";
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(path7.join(root, oldName))) {
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(path7.join(root, "agent.yaml"))) {
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(path7.join(root, "agent"))) {
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 = path7.join(root, PACKAGE_MANIFEST);
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 fs2.readFile(manifestPath, "utf8")));
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(path7.join(root, "AGENT.md")))
192724
+ if (!existsSync7(path9.join(root, "AGENT.md")))
191451
192725
  warnings.push("Missing AGENT.md");
191452
- if (!existsSync7(path7.join(root, DATASET_DIR)))
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 = path7.join(root, filename);
192729
+ const schemaPath = path9.join(root, filename);
191456
192730
  if (!existsSync7(schemaPath))
191457
192731
  continue;
191458
- const validation2 = validateWorkspaceSchema(await fs2.readFile(schemaPath, "utf8"), filename);
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 (path7.basename(dir) === oldName) {
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 fs2.readdir(dir, { withFileTypes: true });
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 = path7.join(dir, example);
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(path7.join(exampleRoot, AGENT_EXAMPLE_INPUT_JSON), `${example}/${AGENT_EXAMPLE_INPUT_JSON}`, errors3);
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(path7.join(exampleRoot, AGENT_EXAMPLE_EXPECTED_JSON), `${example}/${AGENT_EXAMPLE_EXPECTED_JSON}`, errors3);
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 = path7.join(agentDir, filename);
192773
+ const schemaPath = path9.join(agentDir, filename);
191500
192774
  if (!existsSync7(schemaPath))
191501
192775
  return null;
191502
- const raw = await fs2.readFile(schemaPath, "utf8");
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 = path7.join(exampleRoot, "input");
191538
- const inputFiles = existsSync7(inputDir) ? (await fs2.readdir(inputDir, { withFileTypes: true })).filter((entry) => entry.isFile()).map((entry) => entry.name) : [];
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 = path7.join(exampleRoot, AGENT_EXAMPLE_INPUT_JSON);
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 = path7.join(exampleRoot, AGENT_EXAMPLE_EXPECTED_JSON);
191592
- const expectedDir = path7.join(exampleRoot, "expected");
191593
- const goldenFiles = existsSync7(expectedDir) ? (await fs2.readdir(expectedDir, { withFileTypes: true })).filter((entry) => entry.isFile() && !entry.name.startsWith(".")).map((entry) => entry.name) : [];
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 fs2.readFile(filePath, "utf8"));
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(path8.resolve(opts.file));
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 = path8.resolve(opts.out ?? DATASET_DIR);
192991
+ const out = path10.resolve(opts.out ?? DATASET_DIR);
191718
192992
  for (const file2 of payload.files) {
191719
- await writeBase64File(path8.join(out, file2.path), file2.contentBase64);
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(path8.resolve(dir), {
191725
- agentDir: path8.resolve(opts.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 fs3, readFileSync as readFileSync7 } from "node:fs";
191740
- import path9 from "node:path";
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(path9.resolve(opts.dir ?? "."));
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(path9.join(root, "eigenpal_modules"))];
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 = path9.join(packageRoot, SOURCE_SECRETS_FILENAME);
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 fs3.readFile(secretsPath, "utf8")));
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 fs3.readdir(modulesRoot)) {
191875
- const typeRoot = path9.join(modulesRoot, type);
191876
- const typeStat = await fs3.stat(typeRoot).catch(() => null);
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 fs3.readdir(typeRoot)) {
191880
- const packageRoot = path9.join(typeRoot, slug);
191881
- const packageStat = await fs3.stat(packageRoot).catch(() => null);
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 = path9.relative(root, packageRoot).replace(/\\/g, "/");
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 = path9.relative(path9.join(root, "eigenpal_modules"), packageRoot).replace(/\\/g, "/");
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 = path9.join(root, ".eigenpal", "eigenpal.lock");
193150
+ const lockfilePath = path11.join(root, ".eigenpal", "eigenpal.lock");
191897
193151
  if (packageRoot === root && existsSync8(lockfilePath)) {
191898
- const lockfile = JSON.parse(readFileSync7(lockfilePath, "utf8"));
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 fs4 } from "node:fs";
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 fs4.writeFile(opts.out, content);
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 createHash2 } from "node:crypto";
191989
- import { promises as fs5 } from "node:fs";
191990
- import path10 from "node:path";
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 = path10.resolve(opts.out ?? payload.path);
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, path10.resolve(localPath), opts);
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 fs5.readFile(localPath);
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 = createHash2("sha256").update(local).digest("hex");
192042
- const remoteHash = remote ? createHash2("sha256").update(remote).digest("hex") : null;
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").alias("agent").description("Manage Eigenpal agents: Git source, datasets, runs, experiments, sessions, and releases.").action(() => {
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 execution run ${name} sample`);
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 res = await client.get(`/api/v1/workflows/executions/${executionId}`);
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 artifact = await client.get(`/api/v1/workflows/executions/${executionId}?includeSteps=true`);
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 registerWorkflowExecutionCommands(parent) {
195589
- const execution = parent.command("execution").description("Run, inspect, and compare server-side executions.");
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 execution run wf_abc123 # all examples
195593
- $ eigenpal workflow execution run wf_abc123 sample-1 sample-2
195594
- $ eigenpal workflow execution run wf_abc123 --concurrency 5
195595
- $ eigenpal workflow execution run wf_abc123 sample --json | jq '.passed'
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 execution run <workflow-id>
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 execution 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) => {
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
- registerWorkflowExecutionCommands(workflow2);
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 workflow execution get ${e8.id}`)}
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, executions — all in one tree",
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 execution 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) => {
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 {