deepline 0.3.122 → 0.3.124

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/index.js CHANGED
@@ -3068,7 +3068,7 @@ var SDK_RELEASE = {
3068
3068
  // getters keep their established compatibility behavior.
3069
3069
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
3070
3070
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
3071
- version: "0.3.122",
3071
+ version: "0.3.124",
3072
3072
  updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
3073
3073
  packageCapabilities: {
3074
3074
  updatePreferences: 1
@@ -6242,6 +6242,11 @@ async function* observeRunEvents(options) {
6242
6242
  }
6243
6243
  }
6244
6244
 
6245
+ // src/types.ts
6246
+ function isStopAllPlayRunsDryRunResult(result) {
6247
+ return "dryRun" in result && result.dryRun === true;
6248
+ }
6249
+
6245
6250
  // ../integration-contracts/theirstack/execution-policy.ts
6246
6251
  var THEIRSTACK_SLOW_JOB_SEARCH_PROVIDER_TIMEOUT_MS = 135e3;
6247
6252
  var THEIRSTACK_SLOW_JOB_SEARCH_CLIENT_TIMEOUT_MS = 6e4 + THEIRSTACK_SLOW_JOB_SEARCH_PROVIDER_TIMEOUT_MS + 15e3;
@@ -6914,7 +6919,7 @@ function playRunStatusFromState(state) {
6914
6919
  ..."result" in state ? { result: state.result } : {}
6915
6920
  };
6916
6921
  }
6917
- var DeeplineClient = class {
6922
+ var DeeplineClient = class _DeeplineClient {
6918
6923
  http;
6919
6924
  config;
6920
6925
  /** Canonical run lifecycle namespace backed by `/api/v2/runs`. */
@@ -6946,10 +6951,10 @@ var DeeplineClient = class {
6946
6951
  tail: (runId, options2) => this.tailRun(runId, options2),
6947
6952
  logs: (runId, options2) => this.getRunLogs(runId, options2),
6948
6953
  input: (runId) => this.getRunInput(runId),
6949
- rerun: (runId) => this.rerun(runId),
6954
+ rerun: (runId, options2) => this.rerun(runId, options2),
6950
6955
  exportDatasetRows: (input2) => this.getPlaySheetRows(input2),
6951
6956
  stop: (runId, options2) => this.stopRun(runId, options2),
6952
- stopAll: (options2) => this.stopAllRuns(options2)
6957
+ stopAll: ((options2) => this.stopAllRuns(options2))
6953
6958
  };
6954
6959
  this.db = {
6955
6960
  query: (input2) => this.queryDb(input2)
@@ -8465,11 +8470,28 @@ var DeeplineClient = class {
8465
8470
  async getRunInput(runId) {
8466
8471
  return this.http.get(`/api/v2/runs/${encodeURIComponent(runId)}/input`);
8467
8472
  }
8468
- /** Start a fresh run from a prior run's retained input and pinned revision. */
8469
- async rerun(runId) {
8473
+ /**
8474
+ * Start a fresh run from a prior run's retained input and pinned revision.
8475
+ *
8476
+ * Every invocation sends an `Idempotency-Key`. When the caller does not
8477
+ * supply one, a fresh key is generated once per call so the transport's own
8478
+ * network retries converge on a single rerun. Pass the same explicit key
8479
+ * across separate invocations to repeat an ambiguous result safely; a
8480
+ * different key intentionally starts another rerun.
8481
+ */
8482
+ async rerun(runId, options) {
8483
+ const idempotencyKey = options?.idempotencyKey?.trim();
8484
+ if (options?.idempotencyKey !== void 0 && !idempotencyKey) {
8485
+ throw new DeeplineError(
8486
+ "runs.rerun idempotencyKey must not be empty.",
8487
+ void 0,
8488
+ "INVALID_IDEMPOTENCY_KEY"
8489
+ );
8490
+ }
8470
8491
  return this.http.post(
8471
8492
  `/api/v2/runs/${encodeURIComponent(runId)}/rerun`,
8472
- {}
8493
+ {},
8494
+ { "Idempotency-Key": idempotencyKey || globalThis.crypto.randomUUID() }
8473
8495
  );
8474
8496
  }
8475
8497
  /**
@@ -8614,11 +8636,13 @@ var DeeplineClient = class {
8614
8636
  );
8615
8637
  }
8616
8638
  /**
8617
- * Stop every active run visible to the current workspace.
8639
+ * Stop every active run visible to the current workspace, or with
8640
+ * `dryRun: true` enumerate the exact candidates without cancelling anything.
8618
8641
  *
8619
8642
  * This is the SDK equivalent of:
8620
8643
  *
8621
8644
  * ```bash
8645
+ * deepline runs stop-all --dry-run --json
8622
8646
  * deepline runs stop-all --reason "stale lock" --json
8623
8647
  * ```
8624
8648
  *
@@ -8626,10 +8650,22 @@ var DeeplineClient = class {
8626
8650
  * need to clear the workspace run-slot state without knowing each run id.
8627
8651
  */
8628
8652
  async stopAllRuns(options) {
8629
- return this.http.post(
8653
+ const dryRun = options?.dryRun === true;
8654
+ const result = await this.http.post(
8630
8655
  "/api/v2/runs/stop-all",
8631
- options?.reason ? { reason: options.reason } : {}
8656
+ {
8657
+ ...options?.reason ? { reason: options.reason } : {},
8658
+ ...dryRun ? { dryRun: true } : {}
8659
+ }
8632
8660
  );
8661
+ if (dryRun && !isStopAllPlayRunsDryRunResult(result)) {
8662
+ throw new DeeplineError(
8663
+ "This Deepline server does not support runs stop-all --dry-run and performed a real stop-all. Update the server before using --dry-run.",
8664
+ void 0,
8665
+ "STOP_ALL_DRY_RUN_UNSUPPORTED"
8666
+ );
8667
+ }
8668
+ return result;
8633
8669
  }
8634
8670
  /**
8635
8671
  * List one page of callable plays; grep retains exhaustive matching results.
@@ -9808,9 +9844,16 @@ var DeeplineClient = class {
9808
9844
  // ——————————————————————————————————————————————————————————
9809
9845
  // Monitor Jobs, Batches, and Fleets
9810
9846
  // ——————————————————————————————————————————————————————————
9847
+ static MONITOR_JOB_IDEMPOTENCY_KEY_MAX_LENGTH = 96;
9811
9848
  monitorJobIdempotencyKey(operation) {
9812
- const uuid = globalThis.crypto?.randomUUID?.();
9813
- return `monitor-job-${operation}-${uuid ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`;
9849
+ const uuid = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
9850
+ const prefix = "monitor-job-";
9851
+ const safeOperation = operation.replace(/[^A-Za-z0-9._-]/g, "-");
9852
+ const maxOperationLength = Math.max(
9853
+ 0,
9854
+ _DeeplineClient.MONITOR_JOB_IDEMPOTENCY_KEY_MAX_LENGTH - prefix.length - uuid.length - 1
9855
+ );
9856
+ return `${prefix}${safeOperation.slice(0, maxOperationLength)}-${uuid}`;
9814
9857
  }
9815
9858
  monitorJobHeaders(operation, idempotencyKey) {
9816
9859
  return {
@@ -24441,6 +24484,77 @@ function authoredPackageStackFrames(packaged) {
24441
24484
  (line) => !line.includes("/virtual/deepline-plays/") && !line.includes("/apps/play-runner/") && !line.includes("/shared_libs/play-runtime/") && !line.includes("node:internal")
24442
24485
  ).slice(0, 5);
24443
24486
  }
24487
+ function activityTargetLabel(target) {
24488
+ const kind = typeof target.kind === "string" ? target.kind : "target";
24489
+ const label = typeof target.label === "string" ? target.label.trim() : "";
24490
+ if (label) return `${kind} ${label}`;
24491
+ switch (kind) {
24492
+ case "provider":
24493
+ return `provider ${String(target.provider ?? "?")} ${String(target.operation ?? "?")}`;
24494
+ case "dataset":
24495
+ return `dataset ${String(target.tableNamespace ?? "?")} (${String(target.operation ?? "?")})`;
24496
+ case "lease":
24497
+ return `${String(target.resourceKind ?? "resource")} lease ${String(target.resourceLabel ?? "?")}`;
24498
+ case "runner":
24499
+ return `runner ${String(target.backend ?? "?")}`;
24500
+ case "step":
24501
+ return `step ${String(target.stepId ?? "?")}`;
24502
+ case "capacity":
24503
+ return `${String(target.pool ?? "?")} capacity`;
24504
+ default:
24505
+ return kind;
24506
+ }
24507
+ }
24508
+ function activityIsoTimestamp(value) {
24509
+ return typeof value === "number" || typeof value === "string" ? isoTimestamp(value) : null;
24510
+ }
24511
+ function buildRunActivityTextLines(run) {
24512
+ const activity = readRecord(run.activity);
24513
+ const observation = readRecord(activity?.observation);
24514
+ const state = readRecord(observation?.state);
24515
+ const target = readRecord(observation?.target);
24516
+ if (!activity || !observation || !state) return [];
24517
+ const kind = typeof state.kind === "string" ? state.kind : "unknown";
24518
+ if (kind === "completed") return [];
24519
+ const summary = typeof activity.summary === "string" && activity.summary.trim() ? activity.summary.trim() : null;
24520
+ const unclassified = activity.classified === false;
24521
+ const lines = [];
24522
+ lines.push(
24523
+ ` activity: ${kind}${summary ? ` \u2014 ${summary}` : ""}${unclassified ? " (unclassified evidence; may be stale)" : ""}`
24524
+ );
24525
+ if (target) lines.push(` target: ${activityTargetLabel(target)}`);
24526
+ if (typeof observation.stepId === "string" && observation.stepId.trim()) {
24527
+ lines.push(` step: ${observation.stepId.trim()}`);
24528
+ }
24529
+ const reason = readRecord(state.reason);
24530
+ if (kind === "retrying") {
24531
+ const attempt = typeof state.attempt === "number" ? ` (attempt ${state.attempt})` : "";
24532
+ const why = typeof state.reason === "string" ? ` after ${state.reason}` : "";
24533
+ lines.push(
24534
+ ` next retry: ${activityIsoTimestamp(state.retryAt) ?? "unknown"}${why}${attempt}`
24535
+ );
24536
+ } else if (kind === "scheduled") {
24537
+ lines.push(
24538
+ ` resumes: ${activityIsoTimestamp(state.resumeAt) ?? "unknown"}`
24539
+ );
24540
+ } else if (kind === "waiting" && reason) {
24541
+ if (reason.kind === "external_event") {
24542
+ const deadline = activityIsoTimestamp(reason.deadlineAt);
24543
+ if (deadline) lines.push(` deadline: ${deadline}`);
24544
+ if (typeof reason.eventKey === "string" && reason.eventKey.trim()) {
24545
+ lines.push(` event key: ${reason.eventKey.trim()}`);
24546
+ }
24547
+ } else if (reason.kind === "lease") {
24548
+ const expires = activityIsoTimestamp(reason.expiresAt);
24549
+ if (expires) lines.push(` lease expires: ${expires}`);
24550
+ } else if (reason.kind === "unknown") {
24551
+ lines.push(" wait reason: unknown (legacy producer)");
24552
+ }
24553
+ }
24554
+ const observedAt = activityIsoTimestamp(observation.observedAt);
24555
+ if (observedAt) lines.push(` observed: ${observedAt}`);
24556
+ return lines;
24557
+ }
24444
24558
  function buildRunPackageTextLines(packaged) {
24445
24559
  const run = readRunPackageRun(packaged);
24446
24560
  const runId = typeof run.id === "string" ? run.id : "unknown";
@@ -24491,6 +24605,7 @@ function buildRunPackageTextLines(packaged) {
24491
24605
  if (playName) {
24492
24606
  lines.push(` play: ${playName}`);
24493
24607
  }
24608
+ lines.push(...buildRunActivityTextLines(run));
24494
24609
  lines.push(...formatPackageValueOutputLines(packaged));
24495
24610
  lines.push(...formatPackageDatasetActionLines(packaged));
24496
24611
  const next = packaged.next && typeof packaged.next === "object" && !Array.isArray(packaged.next) ? packaged.next : {};
@@ -24634,6 +24749,11 @@ function writePlayResult(status, jsonOutput, options) {
24634
24749
  const displayError = formatPlayErrorForDisplay(status, progressError) ?? progressError;
24635
24750
  lines.push(` error: ${truncateErrorForDisplay(displayError, runId)}`);
24636
24751
  }
24752
+ lines.push(
24753
+ ...buildRunActivityTextLines(
24754
+ readRecord(status.run) ?? {}
24755
+ )
24756
+ );
24637
24757
  if (status.status === "failed") {
24638
24758
  const failedLogEntries = status.failedLogs?.entries ?? [];
24639
24759
  if (failedLogEntries.length > 0) {
@@ -26804,7 +26924,7 @@ function parseRunIdPositional(args, usage) {
26804
26924
  continue;
26805
26925
  }
26806
26926
  if (arg.startsWith("--log-level=")) continue;
26807
- if ((arg === "--out" || arg === "--reason" || arg === "--dataset") && args[index + 1]) {
26927
+ if ((arg === "--out" || arg === "--reason" || arg === "--dataset" || arg === "--idempotency-key") && args[index + 1]) {
26808
26928
  index += 1;
26809
26929
  continue;
26810
26930
  }
@@ -26839,7 +26959,7 @@ async function handleRunGet(args) {
26839
26959
  return 0;
26840
26960
  }
26841
26961
  async function handleRunsRerun(args) {
26842
- const usage = "Usage: deepline runs rerun <run-id> [--json]";
26962
+ const usage = "Usage: deepline runs rerun <run-id> [--idempotency-key <key>] [--json]";
26843
26963
  let runId;
26844
26964
  try {
26845
26965
  runId = parseRunIdPositional(args, usage);
@@ -26847,16 +26967,33 @@ async function handleRunsRerun(args) {
26847
26967
  console.error(error instanceof Error ? error.message : usage);
26848
26968
  return 1;
26849
26969
  }
26850
- const result = await new DeeplineClient().runs.rerun(runId);
26970
+ let idempotencyKey;
26971
+ for (let index = 0; index < args.length; index += 1) {
26972
+ if (args[index] === "--idempotency-key") {
26973
+ const value = args[index + 1];
26974
+ if (!value || value.startsWith("--") || !value.trim()) {
26975
+ console.error("--idempotency-key requires a non-empty value.");
26976
+ return 1;
26977
+ }
26978
+ idempotencyKey = value.trim();
26979
+ index += 1;
26980
+ }
26981
+ }
26982
+ const result = await new DeeplineClient().runs.rerun(runId, {
26983
+ ...idempotencyKey ? { idempotencyKey } : {}
26984
+ });
26851
26985
  if (argsWantJson(args)) {
26852
26986
  printCommandEnvelope(result, { json: true });
26853
26987
  } else {
26854
- console.log(`
26855
- Rerun started from ${runId}.`);
26988
+ console.log(
26989
+ result.deduplicated === true ? `
26990
+ Rerun already exists for ${runId} (matched by idempotency key): ${result.runId}` : `
26991
+ Rerun started from ${runId}.`
26992
+ );
26856
26993
  console.log(` inspect: deepline runs get ${result.runId} --json`);
26857
26994
  console.log(` original input: deepline runs get ${result.runId} --input`);
26858
26995
  console.log(
26859
- " This is a fresh run using the original input and pinned revision. Normal tool receipt reuse still applies."
26996
+ result.deduplicated === true ? " No new run was started. Use a different --idempotency-key to start another rerun." : " This is a fresh run using the original input and pinned revision. Normal tool receipt reuse still applies."
26860
26997
  );
26861
26998
  }
26862
26999
  return 0;
@@ -27334,8 +27471,46 @@ async function handleRunStop(args) {
27334
27471
  );
27335
27472
  return 0;
27336
27473
  }
27474
+ function padColumn(value, width) {
27475
+ return value.length >= width ? value : value + " ".repeat(width - value.length);
27476
+ }
27477
+ function buildStopAllDryRunTextLines(result) {
27478
+ const count = result.candidateCount;
27479
+ const lines = [
27480
+ `Dry run: ${count} active run${count === 1 ? "" : "s"} would be stopped`,
27481
+ `Scope: ${result.scope}`,
27482
+ `Includes child runs: ${result.includesChildren ? "yes" : "no"}`
27483
+ ];
27484
+ if (result.runs.length > 0) {
27485
+ const rows = result.runs.map((run) => [
27486
+ run.runId,
27487
+ run.playName ?? "\u2014",
27488
+ run.status ?? "\u2014"
27489
+ ]);
27490
+ const header = ["RUN_ID", "PLAY", "STATUS"];
27491
+ const widths = header.map(
27492
+ (label, column) => Math.max(label.length, ...rows.map((row) => row[column].length))
27493
+ );
27494
+ lines.push("");
27495
+ for (const row of [header, ...rows]) {
27496
+ lines.push(
27497
+ row.map(
27498
+ (cell, column) => column === row.length - 1 ? cell : padColumn(cell, widths[column])
27499
+ ).join(" ")
27500
+ );
27501
+ }
27502
+ }
27503
+ if (result.truncated) {
27504
+ lines.push("");
27505
+ lines.push(
27506
+ `Showing the first ${result.runs.length} matching runs. Results are truncated; no runs were stopped.`
27507
+ );
27508
+ }
27509
+ return lines;
27510
+ }
27337
27511
  async function handleRunStopAll(args) {
27338
27512
  let reason;
27513
+ const dryRun = args.includes("--dry-run");
27339
27514
  for (let index = 0; index < args.length; index += 1) {
27340
27515
  const arg = args[index];
27341
27516
  if (arg === "--reason" && args[index + 1]) {
@@ -27343,6 +27518,21 @@ async function handleRunStopAll(args) {
27343
27518
  }
27344
27519
  }
27345
27520
  const client2 = new DeeplineClient();
27521
+ if (dryRun) {
27522
+ const result2 = await client2.runs.stopAll({ dryRun: true, reason });
27523
+ const lines2 = buildStopAllDryRunTextLines(result2);
27524
+ printCommandEnvelope(
27525
+ {
27526
+ ...result2,
27527
+ render: {
27528
+ sections: [{ title: "workspace stop-all (dry run)", lines: lines2 }]
27529
+ }
27530
+ },
27531
+ { json: argsWantJson(args), text: `${lines2.join("\n")}
27532
+ ` }
27533
+ );
27534
+ return 0;
27535
+ }
27346
27536
  const result = await client2.runs.stopAll({ reason });
27347
27537
  const lines = [
27348
27538
  `Stopped ${result.stopped} active run${result.stopped === 1 ? "" : "s"}`,
@@ -29111,13 +29301,23 @@ Notes:
29111
29301
  The rerun uses the original input and exact saved revision. Normal completed
29112
29302
  tool receipts may be reused; inspect the new run before forcing any external
29113
29303
  side effect.
29304
+ Every call sends an Idempotency-Key. Pass --idempotency-key to reuse the
29305
+ same key across separate invocations (for example after an ambiguous or
29306
+ lost response): the same source run and key always resolve to the same
29307
+ rerun, and the JSON result reports deduplicated: true when nothing new was
29308
+ started. A different key intentionally starts another rerun.
29114
29309
 
29115
29310
  Examples:
29116
29311
  deepline runs rerun play/my-play/webhook/abc --json
29312
+ deepline runs rerun play/my-play/webhook/abc --idempotency-key incident-42 --json
29117
29313
  `
29314
+ ).option(
29315
+ "--idempotency-key <key>",
29316
+ "Reuse this key so repeated calls resolve to one rerun"
29118
29317
  ).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (runId, options) => {
29119
29318
  process.exitCode = await handleRunsRerun([
29120
29319
  runId,
29320
+ ...options.idempotencyKey ? ["--idempotency-key", options.idempotencyKey] : [],
29121
29321
  ...options.json ? ["--json"] : []
29122
29322
  ]);
29123
29323
  });
@@ -29148,14 +29348,24 @@ Examples:
29148
29348
  `
29149
29349
  Notes:
29150
29350
  Stops all active runs visible to this workspace, including child runs created by ctx.runPlay.
29351
+ --dry-run lists the exact candidates (same scope, statuses, and order) and
29352
+ never cancels or mutates anything. A real stop-all exits nonzero when any
29353
+ cancellation failed or the candidate list was truncated; failed runs stay
29354
+ active and are reported truthfully.
29151
29355
 
29152
29356
  Examples:
29357
+ deepline runs stop-all --dry-run
29358
+ deepline runs stop-all --dry-run --json
29153
29359
  deepline runs stop-all --reason "unjam workspace"
29154
29360
  deepline runs stop-all --json
29155
29361
  `
29156
- ).option("--reason <text>", "Reason to include with each stop request").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
29362
+ ).option("--reason <text>", "Reason to include with each stop request").option(
29363
+ "--dry-run",
29364
+ "List the runs that would be stopped without cancelling anything"
29365
+ ).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
29157
29366
  process.exitCode = await handleRunStopAll([
29158
29367
  ...options.reason ? ["--reason", options.reason] : [],
29368
+ ...options.dryRun ? ["--dry-run"] : [],
29159
29369
  ...options.json ? ["--json"] : []
29160
29370
  ]);
29161
29371
  });