deepline 0.3.149 → 0.3.151

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.149",
3071
+ version: "0.3.151",
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
@@ -3827,6 +3827,11 @@ var HttpClient = class {
3827
3827
  body: options?.formData !== void 0 ? typeof options.formData === "function" ? options.formData() : options.formData : options?.body !== void 0 ? JSON.stringify(options.body) : void 0,
3828
3828
  signal: controller.signal
3829
3829
  });
3830
+ options?.onResponse?.(response);
3831
+ if (response.status === 404 && options?.allowNotFound) {
3832
+ clearTimeout(timeoutId);
3833
+ return null;
3834
+ }
3830
3835
  clearTimeout(timeoutId);
3831
3836
  const body = await response.text();
3832
3837
  const parsed = parseResponseBody(body);
@@ -3966,7 +3971,8 @@ var HttpClient = class {
3966
3971
  retryAfterMs: null,
3967
3972
  networkKind: code === "NETWORK_TIMEOUT" ? "timeout" : code === "NETWORK_ABORTED" ? "unknown" : "unavailable",
3968
3973
  networkScope: "client_to_deepline",
3969
- details: mappedNetworkError.details
3974
+ details: mappedNetworkError.details,
3975
+ publicDetails: options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : null
3970
3976
  }
3971
3977
  ),
3972
3978
  lastError
@@ -6570,6 +6576,56 @@ var MONITOR_NON_RETRYABLE_MUTATION_OPTIONS = {
6570
6576
  exactUrlOnly: true
6571
6577
  };
6572
6578
  var RAW_V2_EXECUTE_RESPONSE_CONTRACT = RAW_V2_TOOL_RESPONSE_CONTRACT;
6579
+ var DEFAULT_EXECUTION_RECOVERY_TIMEOUT_MS = 15 * 60 * 1e3;
6580
+ function validateExecutionIdempotencyKey(key) {
6581
+ if (typeof key !== "string" || key.length < 1 || key.length > 200 || !/^[A-Za-z0-9._:-]+$/.test(key)) {
6582
+ throw new DeeplineError(
6583
+ "Execution idempotency keys must be 1\u2013200 ASCII letters, digits, dots, underscores, colons, or hyphens.",
6584
+ void 0,
6585
+ "IDEMPOTENCY_KEY_INVALID"
6586
+ );
6587
+ }
6588
+ }
6589
+ function isExecutionInProgressResponse(value) {
6590
+ return typeof value === "object" && value !== null && typeof value.executionRecovery === "object" && value.executionRecovery.state === "running";
6591
+ }
6592
+ function getExecutionRecoveryState(value) {
6593
+ if (typeof value !== "object" || value === null || typeof value.executionRecovery !== "object") {
6594
+ return null;
6595
+ }
6596
+ const state = value.executionRecovery.state;
6597
+ return state === "running" || state === "completed" || state === "outcome_unknown" ? state : null;
6598
+ }
6599
+ function isRecoverableExecutionAttemptError(error) {
6600
+ if (error instanceof ToolExecutionError) {
6601
+ return error.code === "EXECUTION_IN_PROGRESS" || error.origin === "deepline" && error.category === "network" && error.networkScope === "client_to_deepline";
6602
+ }
6603
+ return error instanceof DeeplineError && (error.code === "EXECUTION_IN_PROGRESS" || error.code?.startsWith("NETWORK_") === true && error.statusCode === void 0);
6604
+ }
6605
+ function isRecoverableExecutionLookupError(error) {
6606
+ if (isRecoverableExecutionAttemptError(error)) return true;
6607
+ return error instanceof DeeplineError && (error.statusCode === 429 || error.statusCode !== void 0 && error.statusCode >= 500);
6608
+ }
6609
+ function timeoutWithinRecoveryBudget(requestTimeoutMs, defaultRequestTimeoutMs, remainingMs) {
6610
+ return Math.min(requestTimeoutMs ?? defaultRequestTimeoutMs, remainingMs);
6611
+ }
6612
+ function executionRecoveryError(input2) {
6613
+ return new ToolExecutionError(input2.message, {
6614
+ toolId: input2.toolId,
6615
+ provider: null,
6616
+ operation: input2.toolId,
6617
+ code: input2.code,
6618
+ origin: "deepline",
6619
+ category: input2.code === "EXECUTION_OUTCOME_UNKNOWN" ? "unknown" : "conflict",
6620
+ retryable: input2.code !== "EXECUTION_OUTCOME_UNKNOWN",
6621
+ statusCode: null,
6622
+ requestId: null,
6623
+ retryAfterMs: null,
6624
+ networkKind: null,
6625
+ networkScope: null,
6626
+ publicDetails: { idempotencyKey: input2.idempotencyKey }
6627
+ });
6628
+ }
6573
6629
  var COMPILE_MANIFEST_RETRY_DELAYS_MS = [250, 1e3];
6574
6630
  var REGISTER_PLAY_ARTIFACTS_COMPILE_CONCURRENCY = 3;
6575
6631
  var REGISTER_PLAY_ARTIFACTS_MAX_BATCH_COUNT = 3;
@@ -7483,29 +7539,183 @@ var DeeplineClient = class _DeeplineClient {
7483
7539
  * Deepline execution envelope.
7484
7540
  */
7485
7541
  async executeTool(toolId, input2, options) {
7542
+ const inputSnapshot = JSON.parse(JSON.stringify(input2));
7543
+ const metadataSnapshot = options?.metadata ? JSON.parse(JSON.stringify(options.metadata)) : void 0;
7544
+ const idempotencyKey = options?.idempotencyKey ?? (options?.recover ? crypto.randomUUID() : void 0);
7545
+ if (idempotencyKey !== void 0) {
7546
+ validateExecutionIdempotencyKey(idempotencyKey);
7547
+ if (options?.recoveryTimeoutMs !== void 0 && (!Number.isFinite(options.recoveryTimeoutMs) || options.recoveryTimeoutMs < 0)) {
7548
+ throw new DeeplineError(
7549
+ "recoveryTimeoutMs must be a finite, non-negative number.",
7550
+ void 0,
7551
+ "IDEMPOTENCY_KEY_INVALID"
7552
+ );
7553
+ }
7554
+ await options?.onExecution?.({ idempotencyKey });
7555
+ const timeoutMs2 = options?.timeout ?? resolveToolExecuteTimeoutMs(toolId, inputSnapshot);
7556
+ await this.lookupExecutionByKey(idempotencyKey, timeoutMs2);
7557
+ }
7558
+ const timeoutMs = options?.timeout ?? resolveToolExecuteTimeoutMs(toolId, inputSnapshot);
7486
7559
  const headers = {
7487
7560
  [EXECUTE_RESPONSE_CONTRACT_HEADER]: RAW_V2_EXECUTE_RESPONSE_CONTRACT,
7488
7561
  [TOOL_EXECUTION_ERROR_SCHEMA_HEADER]: String(
7489
7562
  TOOL_EXECUTION_ERROR_SCHEMA_VERSION
7490
7563
  ),
7491
7564
  ...options?.includeToolMetadata ? { [INCLUDE_TOOL_METADATA_HEADER]: "true" } : {},
7492
- [EXECUTE_RESPONSE_INTENT_HEADER]: options?.responseIntent ?? "raw"
7565
+ [EXECUTE_RESPONSE_INTENT_HEADER]: options?.responseIntent ?? "raw",
7566
+ ...idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}
7493
7567
  };
7494
- const response = await this.http.post(
7568
+ const request = (requestTimeoutMs) => this.http.post(
7495
7569
  `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
7496
7570
  {
7497
- payload: input2,
7498
- ...options?.metadata ? { metadata: options.metadata } : {}
7571
+ payload: inputSnapshot,
7572
+ ...metadataSnapshot ? { metadata: metadataSnapshot } : {}
7499
7573
  },
7500
7574
  headers,
7501
7575
  {
7502
- timeout: options?.timeout ?? resolveToolExecuteTimeoutMs(toolId, input2),
7503
- maxRetries: options?.maxRetries ?? 0,
7576
+ timeout: requestTimeoutMs,
7577
+ maxRetries: idempotencyKey ? 0 : options?.maxRetries ?? 0,
7578
+ exactUrlOnly: true,
7579
+ toolId,
7580
+ idempotencyKey
7581
+ }
7582
+ );
7583
+ let response;
7584
+ let recoveryDeadline = null;
7585
+ const recoveryTimeoutMs = options?.recoveryTimeoutMs ?? DEFAULT_EXECUTION_RECOVERY_TIMEOUT_MS;
7586
+ let retryDelayMs = 1e3;
7587
+ const recoveryTimeoutError = () => executionRecoveryError({
7588
+ toolId,
7589
+ idempotencyKey,
7590
+ code: "EXECUTION_RECOVERY_TIMEOUT",
7591
+ message: `Execution recovery for ${idempotencyKey} did not finish before the recovery timeout. Inspect client.executions.getByKey() and resume with the same idempotency key.`
7592
+ });
7593
+ const waitForRecoveryRetry = async (deadline) => {
7594
+ const remainingMs = deadline - Date.now();
7595
+ if (remainingMs <= 0) throw recoveryTimeoutError();
7596
+ await new Promise(
7597
+ (resolve22) => setTimeout(resolve22, Math.min(retryDelayMs, remainingMs))
7598
+ );
7599
+ retryDelayMs = Math.min(retryDelayMs * 2, 5e3);
7600
+ };
7601
+ const lookupDuringRecovery = async () => {
7602
+ while (true) {
7603
+ const remainingMs = recoveryDeadline - Date.now();
7604
+ if (remainingMs <= 0) throw recoveryTimeoutError();
7605
+ try {
7606
+ return await this.lookupExecutionByKey(
7607
+ idempotencyKey,
7608
+ timeoutWithinRecoveryBudget(
7609
+ timeoutMs,
7610
+ this.config.timeout,
7611
+ remainingMs
7612
+ )
7613
+ );
7614
+ } catch (error) {
7615
+ if (!isRecoverableExecutionLookupError(error)) throw error;
7616
+ await waitForRecoveryRetry(recoveryDeadline);
7617
+ }
7618
+ }
7619
+ };
7620
+ while (true) {
7621
+ const remainingMs = recoveryDeadline === null ? null : recoveryDeadline - Date.now();
7622
+ if (remainingMs !== null && remainingMs <= 0) {
7623
+ throw recoveryTimeoutError();
7624
+ }
7625
+ try {
7626
+ response = await request(
7627
+ remainingMs === null ? timeoutMs : timeoutWithinRecoveryBudget(
7628
+ timeoutMs,
7629
+ this.config.timeout,
7630
+ remainingMs
7631
+ )
7632
+ );
7633
+ if (getExecutionRecoveryState(response) === "outcome_unknown") {
7634
+ if (!idempotencyKey) break;
7635
+ throw executionRecoveryError({
7636
+ toolId,
7637
+ idempotencyKey,
7638
+ code: "EXECUTION_OUTCOME_UNKNOWN",
7639
+ message: `The outcome of execution ${idempotencyKey} is unknown. Do not retry with a new key; inspect client.executions.getByKey().`
7640
+ });
7641
+ }
7642
+ if (!idempotencyKey || !isExecutionInProgressResponse(response)) {
7643
+ break;
7644
+ }
7645
+ if (recoveryDeadline === null) {
7646
+ recoveryDeadline = Date.now() + recoveryTimeoutMs;
7647
+ }
7648
+ } catch (error) {
7649
+ if (idempotencyKey && error instanceof DeeplineError && error.code === "EXECUTION_OUTCOME_UNKNOWN") {
7650
+ throw executionRecoveryError({
7651
+ toolId,
7652
+ idempotencyKey,
7653
+ code: "EXECUTION_OUTCOME_UNKNOWN",
7654
+ message: error.message
7655
+ });
7656
+ }
7657
+ if (!idempotencyKey || !isRecoverableExecutionAttemptError(error)) {
7658
+ throw error;
7659
+ }
7660
+ if (recoveryDeadline === null) {
7661
+ recoveryDeadline = Date.now() + recoveryTimeoutMs;
7662
+ }
7663
+ }
7664
+ await waitForRecoveryRetry(recoveryDeadline);
7665
+ const recovered = await lookupDuringRecovery();
7666
+ if (getExecutionRecoveryState(recovered) === "outcome_unknown") {
7667
+ throw executionRecoveryError({
7668
+ toolId,
7669
+ idempotencyKey,
7670
+ code: "EXECUTION_OUTCOME_UNKNOWN",
7671
+ message: `The outcome of execution ${idempotencyKey} is unknown. Do not retry with a new key; inspect client.executions.getByKey().`
7672
+ });
7673
+ }
7674
+ }
7675
+ const materialized = materializeToolExecutionResponse(response);
7676
+ return idempotencyKey ? { ...materialized, idempotencyKey } : materialized;
7677
+ }
7678
+ /** Read the durable state of a keyed tool execution. */
7679
+ async getExecutionByKey(idempotencyKey) {
7680
+ validateExecutionIdempotencyKey(idempotencyKey);
7681
+ const result = await this.lookupExecutionByKey(idempotencyKey);
7682
+ if (!result) {
7683
+ throw new DeeplineError(
7684
+ `No execution exists for idempotency key ${idempotencyKey}.`,
7685
+ 404,
7686
+ "EXECUTION_NOT_FOUND"
7687
+ );
7688
+ }
7689
+ return result;
7690
+ }
7691
+ async lookupExecutionByKey(idempotencyKey, timeoutMs) {
7692
+ validateExecutionIdempotencyKey(idempotencyKey);
7693
+ let supported = false;
7694
+ const result = await this.http.get(
7695
+ `/api/v2/executions/by-key/${encodeURIComponent(idempotencyKey)}`,
7696
+ {
7504
7697
  exactUrlOnly: true,
7505
- toolId
7698
+ maxRetries: 0,
7699
+ timeout: timeoutMs,
7700
+ idempotencyKey,
7701
+ allowNotFound: true,
7702
+ onResponse: (response) => {
7703
+ supported = response.headers.get("X-Deepline-Idempotency-Supported") === "true";
7704
+ }
7506
7705
  }
7507
7706
  );
7508
- return materializeToolExecutionResponse(response);
7707
+ if (!supported) {
7708
+ throw new DeeplineError(
7709
+ "This Deepline server does not support recoverable tool executions; no tool was dispatched.",
7710
+ 422,
7711
+ "IDEMPOTENCY_NOT_SUPPORTED"
7712
+ );
7713
+ }
7714
+ return result;
7715
+ }
7716
+ /** Public recovery namespace. */
7717
+ get executions() {
7718
+ return { getByKey: (key) => this.getExecutionByKey(key) };
7509
7719
  }
7510
7720
  /**
7511
7721
  * Back-compatible alias for {@link executeTool}.
@@ -11132,6 +11342,15 @@ function errorToJsonPayload(error) {
11132
11342
  if (typeof maybeRecord?.status === "number") {
11133
11343
  details.status = maybeRecord.status;
11134
11344
  }
11345
+ if (typeof maybeRecord?.requestId === "string" || maybeRecord?.requestId === null) {
11346
+ details.requestId = maybeRecord.requestId;
11347
+ }
11348
+ if (typeof maybeRecord?.retryable === "boolean") {
11349
+ details.retryable = maybeRecord.retryable;
11350
+ }
11351
+ if (maybeRecord?.publicDetails && typeof maybeRecord.publicDetails === "object" && !Array.isArray(maybeRecord.publicDetails)) {
11352
+ details.publicDetails = maybeRecord.publicDetails;
11353
+ }
11135
11354
  if (maybeRecord?.details && typeof maybeRecord.details === "object" && !Array.isArray(maybeRecord.details)) {
11136
11355
  details.details = maybeRecord.details;
11137
11356
  }
@@ -36047,6 +36266,17 @@ function registerEnrichCommand(program) {
36047
36266
  });
36048
36267
  }
36049
36268
 
36269
+ // src/cli/commands/executions.ts
36270
+ function registerExecutionsCommands(program) {
36271
+ const executions = program.command("executions").description("Inspect and recover keyed tool executions.");
36272
+ executions.command("get").description("Retrieve a tool execution by its idempotency key.").requiredOption("--idempotency-key <key>", "Stable execution recovery key").option("--json", "Emit the stable JSON response").action(async (options) => {
36273
+ const result = await new DeeplineClient().executions.getByKey(
36274
+ options.idempotencyKey
36275
+ );
36276
+ printCommandEnvelope(result, { json: true });
36277
+ });
36278
+ }
36279
+
36050
36280
  // src/cli/commands/feedback.ts
36051
36281
  var import_commander3 = require("commander");
36052
36282
  var import_node_fs13 = require("fs");
@@ -46928,6 +47158,25 @@ Examples:
46928
47158
 
46929
47159
  // src/cli/commands/tools.ts
46930
47160
  var import_commander4 = require("commander");
47161
+ var import_node_crypto12 = require("crypto");
47162
+
47163
+ // ../plays/row-identity.ts
47164
+ function stableValue(value) {
47165
+ if (Array.isArray(value)) {
47166
+ return value.map((entry) => stableValue(entry));
47167
+ }
47168
+ if (value && typeof value === "object") {
47169
+ return Object.fromEntries(
47170
+ Object.entries(value).filter(([, entry]) => entry !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, stableValue(entry)])
47171
+ );
47172
+ }
47173
+ return value;
47174
+ }
47175
+ function stableStringify(value) {
47176
+ return JSON.stringify(stableValue(value));
47177
+ }
47178
+
47179
+ // src/cli/commands/tools.ts
46931
47180
  var import_node_fs26 = require("fs");
46932
47181
  var import_node_os18 = require("os");
46933
47182
  var import_node_path30 = require("path");
@@ -48058,6 +48307,15 @@ Examples:
48058
48307
  ).option("-o, --out <path>", "Write row-shaped tool output to this CSV path").option(
48059
48308
  "--no-preview",
48060
48309
  "Only print the extracted output path when applicable"
48310
+ ).option(
48311
+ "--idempotency-key <key>",
48312
+ "Stable key for retrying or recovering this tool execution"
48313
+ ).option(
48314
+ "--recover",
48315
+ "Generate or reuse a saved idempotency key for safe recovery"
48316
+ ).option(
48317
+ "--recovery-timeout-ms <milliseconds>",
48318
+ "Maximum wait for a keyed execution to finish (default: 900000)"
48061
48319
  ).action(async (toolId, options) => {
48062
48320
  const args = [
48063
48321
  toolId,
@@ -48069,7 +48327,10 @@ Examples:
48069
48327
  ...options.timeout ? ["--timeout", options.timeout] : [],
48070
48328
  ...options.outputFormat ? ["--output-format", options.outputFormat] : [],
48071
48329
  ...options.out ? ["--out", options.out] : [],
48072
- ...options.preview === false ? ["--no-preview"] : []
48330
+ ...options.preview === false ? ["--no-preview"] : [],
48331
+ ...options.idempotencyKey ? ["--idempotency-key", options.idempotencyKey] : [],
48332
+ ...options.recover ? ["--recover"] : [],
48333
+ ...options.recoveryTimeoutMs ? ["--recovery-timeout-ms", options.recoveryTimeoutMs] : []
48073
48334
  ];
48074
48335
  process.exitCode = await executeTool(args);
48075
48336
  });
@@ -49306,7 +49567,7 @@ function parseExecuteOptions(args) {
49306
49567
  const toolId = args[0];
49307
49568
  if (!toolId) {
49308
49569
  throw new Error(
49309
- `Usage: deepline tools execute <toolId> [--param key=value ...] [--input '{"k":"v"}'] [--timeout <duration>] [--out rows.csv] [--output-format auto|csv|csv_file|json|json_file] [--no-preview]`
49570
+ `Usage: deepline tools execute <toolId> [--param key=value ...] [--input '{"k":"v"}'] [--timeout <duration>] [--recover | --idempotency-key <key>] [--recovery-timeout-ms <ms>] [--out rows.csv] [--output-format auto|csv|csv_file|json|json_file] [--no-preview]`
49310
49571
  );
49311
49572
  }
49312
49573
  const params = {};
@@ -49314,6 +49575,9 @@ function parseExecuteOptions(args) {
49314
49575
  let noPreview = false;
49315
49576
  let outPath = null;
49316
49577
  let timeoutMs;
49578
+ let idempotencyKey;
49579
+ let recover = false;
49580
+ let recoveryTimeoutMs;
49317
49581
  for (let index = 1; index < args.length; index += 1) {
49318
49582
  const arg = args[index];
49319
49583
  if ((arg === "--param" || arg === "-p") && args[index + 1]) {
@@ -49349,13 +49613,39 @@ function parseExecuteOptions(args) {
49349
49613
  noPreview = true;
49350
49614
  continue;
49351
49615
  }
49616
+ if (arg === "--idempotency-key" && args[index + 1]) {
49617
+ idempotencyKey = args[++index];
49618
+ continue;
49619
+ }
49620
+ if (arg === "--recover") {
49621
+ recover = true;
49622
+ continue;
49623
+ }
49624
+ if (arg === "--recovery-timeout-ms" && args[index + 1]) {
49625
+ const parsed = Number(args[++index]);
49626
+ if (!Number.isFinite(parsed) || parsed < 0) {
49627
+ throw new Error("--recovery-timeout-ms must be a non-negative number.");
49628
+ }
49629
+ recoveryTimeoutMs = parsed;
49630
+ continue;
49631
+ }
49352
49632
  if ((arg === "--out" || arg === "-o") && args[index + 1]) {
49353
49633
  outPath = (0, import_node_path30.resolve)(args[++index]);
49354
49634
  continue;
49355
49635
  }
49356
49636
  throw new Error(`Unknown option: ${arg}`);
49357
49637
  }
49358
- return { toolId, params, outputFormat, noPreview, outPath, timeoutMs };
49638
+ return {
49639
+ toolId,
49640
+ params,
49641
+ outputFormat,
49642
+ noPreview,
49643
+ outPath,
49644
+ timeoutMs,
49645
+ idempotencyKey,
49646
+ recover,
49647
+ recoveryTimeoutMs
49648
+ };
49359
49649
  }
49360
49650
  function parseToolExecuteTimeout(raw) {
49361
49651
  const match = /^(\d+)(ms|s|m|h)?$/i.exec(raw.trim());
@@ -49375,6 +49665,65 @@ function parseToolExecuteTimeout(raw) {
49375
49665
  }
49376
49666
  return timeoutMs;
49377
49667
  }
49668
+ function pendingToolExecutionPath(input2) {
49669
+ const config = resolveConfig();
49670
+ const fingerprint = (0, import_node_crypto12.createHash)("sha256").update(config.baseUrl).update("\0").update((0, import_node_crypto12.createHash)("sha256").update(config.apiKey).digest("hex")).update("\0").update(input2.toolId).update("\0").update(stableStringify(input2.params)).update("\0").update(input2.responseIntent).digest("hex");
49671
+ return (0, import_node_path30.join)(
49672
+ sdkCliStateDirPath(resolveConfig().baseUrl),
49673
+ `pending-tool-execution-${fingerprint}.json`
49674
+ );
49675
+ }
49676
+ function loadOrCreatePendingToolExecution(path) {
49677
+ (0, import_node_fs26.mkdirSync)((0, import_node_path30.dirname)(path), { recursive: true });
49678
+ if ((0, import_node_fs26.existsSync)(path)) {
49679
+ return readPendingToolExecution(path);
49680
+ }
49681
+ const idempotencyKey = (0, import_node_crypto12.randomUUID)();
49682
+ const temporaryPath = `${path}.${process.pid}.${(0, import_node_crypto12.randomUUID)()}.tmp`;
49683
+ try {
49684
+ (0, import_node_fs26.writeFileSync)(temporaryPath, `${JSON.stringify({ idempotencyKey })}
49685
+ `, {
49686
+ encoding: "utf8",
49687
+ flag: "wx",
49688
+ mode: 384
49689
+ });
49690
+ (0, import_node_fs26.linkSync)(temporaryPath, path);
49691
+ return idempotencyKey;
49692
+ } catch (error) {
49693
+ if (error.code !== "EEXIST") throw error;
49694
+ return readPendingToolExecution(path);
49695
+ } finally {
49696
+ if ((0, import_node_fs26.existsSync)(temporaryPath)) (0, import_node_fs26.unlinkSync)(temporaryPath);
49697
+ }
49698
+ }
49699
+ function readPendingToolExecution(path) {
49700
+ const saved = JSON.parse((0, import_node_fs26.readFileSync)(path, "utf8"));
49701
+ if (typeof saved.idempotencyKey !== "string") {
49702
+ throw new Error(
49703
+ `Cannot resume the saved tool execution at ${path}: invalid recovery key.`
49704
+ );
49705
+ }
49706
+ return saved.idempotencyKey;
49707
+ }
49708
+ async function completePendingToolExecution(path) {
49709
+ if (!path || !(0, import_node_fs26.existsSync)(path)) return;
49710
+ await new Promise((resolve22, reject) => {
49711
+ const onError = (error) => {
49712
+ process.stdout.off("error", onError);
49713
+ reject(error);
49714
+ };
49715
+ process.stdout.once("error", onError);
49716
+ process.stdout.write("", (error) => {
49717
+ process.stdout.off("error", onError);
49718
+ if (error) {
49719
+ reject(error);
49720
+ } else {
49721
+ resolve22();
49722
+ }
49723
+ });
49724
+ });
49725
+ if ((0, import_node_fs26.existsSync)(path)) (0, import_node_fs26.unlinkSync)(path);
49726
+ }
49378
49727
  function safeFileStem(value) {
49379
49728
  return value.trim().replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "tool";
49380
49729
  }
@@ -49492,6 +49841,8 @@ function buildToolExecuteBaseEnvelope(input2) {
49492
49841
  ] : [];
49493
49842
  return {
49494
49843
  ...envelope,
49844
+ ...input2.idempotencyKey ? { idempotency_key: input2.idempotencyKey } : {},
49845
+ ...isRecord12(input2.rawResponse) && isRecord12(input2.rawResponse.executionRecovery) ? { execution_recovery: input2.rawResponse.executionRecovery } : {},
49495
49846
  ...envelopeHasCanonicalOutput || envelopeHasDeclaredOutput ? { output_preview: outputPreview } : { output: outputPreview },
49496
49847
  ...summaryEntries.length > 0 ? { summary: input2.summary } : {},
49497
49848
  ...warningMessages.length > 0 ? { warnings: warningMessages } : {},
@@ -49675,10 +50026,25 @@ async function executeTool(args) {
49675
50026
  }
49676
50027
  return 2;
49677
50028
  }
50029
+ let idempotencyKey = parsed.idempotencyKey;
50030
+ let pendingRecoveryPath = null;
50031
+ const responseIntent = parsed.outPath || parsed.outputFormat === "csv" || parsed.outputFormat === "csv_file" ? "row_artifact" : "raw";
50032
+ if (parsed.recover && !idempotencyKey) {
50033
+ pendingRecoveryPath = pendingToolExecutionPath({
50034
+ toolId: parsed.toolId,
50035
+ params: parsed.params,
50036
+ responseIntent
50037
+ });
50038
+ idempotencyKey = loadOrCreatePendingToolExecution(pendingRecoveryPath);
50039
+ }
50040
+ if (idempotencyKey) {
50041
+ console.error(`Tool execution recovery key: ${idempotencyKey}`);
50042
+ }
49678
50043
  const rawResponse = await client2.executeTool(parsed.toolId, parsed.params, {
49679
50044
  ...parsed.timeoutMs !== void 0 ? { timeout: parsed.timeoutMs } : {},
49680
- responseIntent: parsed.outPath || parsed.outputFormat === "csv" || parsed.outputFormat === "csv_file" ? "row_artifact" : "raw",
49681
- ...parsed.timeoutMs !== void 0 ? { timeout: parsed.timeoutMs } : {}
50045
+ responseIntent,
50046
+ ...idempotencyKey ? { idempotencyKey } : {},
50047
+ ...parsed.recoveryTimeoutMs !== void 0 ? { recoveryTimeoutMs: parsed.recoveryTimeoutMs } : {}
49682
50048
  });
49683
50049
  const listConversion = tryConvertToList(rawResponse, {
49684
50050
  listExtractorPaths: listExtractorPathsFromUsageGuidance(metadata)
@@ -49687,12 +50053,14 @@ async function executeTool(args) {
49687
50053
  const baseEnvelope = buildToolExecuteBaseEnvelope({
49688
50054
  toolId: parsed.toolId,
49689
50055
  params: parsed.params,
50056
+ ...idempotencyKey ? { idempotencyKey } : {},
49690
50057
  rawResponse,
49691
50058
  listConversion,
49692
50059
  summary
49693
50060
  });
49694
50061
  if (!parsed.outPath && (parsed.outputFormat === "json" || parsed.outputFormat === "auto" && shouldEmitJson())) {
49695
50062
  printCommandEnvelope(baseEnvelope, { json: true });
50063
+ await completePendingToolExecution(pendingRecoveryPath);
49696
50064
  return 0;
49697
50065
  }
49698
50066
  if (parsed.outputFormat === "json_file") {
@@ -49710,6 +50078,7 @@ async function executeTool(args) {
49710
50078
  },
49711
50079
  { json: true }
49712
50080
  );
50081
+ await completePendingToolExecution(pendingRecoveryPath);
49713
50082
  return 0;
49714
50083
  }
49715
50084
  if (!listConversion) {
@@ -49738,9 +50107,11 @@ async function executeTool(args) {
49738
50107
  },
49739
50108
  { json: parsed.outputFormat === "csv_file" || shouldEmitJson() }
49740
50109
  );
50110
+ await completePendingToolExecution(pendingRecoveryPath);
49741
50111
  return 0;
49742
50112
  }
49743
50113
  printCommandEnvelope(baseEnvelope, { json: false });
50114
+ await completePendingToolExecution(pendingRecoveryPath);
49744
50115
  return 0;
49745
50116
  }
49746
50117
  const rowOutput = projectRowOutput(listConversion);
@@ -49763,6 +50134,7 @@ async function executeTool(args) {
49763
50134
  });
49764
50135
  if (parsed.outputFormat === "csv_file") {
49765
50136
  printCommandEnvelope(materializedEnvelope, { json: true });
50137
+ await completePendingToolExecution(pendingRecoveryPath);
49766
50138
  return 0;
49767
50139
  }
49768
50140
  if (parsed.outPath) {
@@ -49771,6 +50143,7 @@ async function executeTool(args) {
49771
50143
  text: `Wrote ${csv.rowCount} row(s) to ${csv.path}
49772
50144
  `
49773
50145
  });
50146
+ await completePendingToolExecution(pendingRecoveryPath);
49774
50147
  return 0;
49775
50148
  }
49776
50149
  if (parsed.noPreview) {
@@ -49785,9 +50158,11 @@ async function executeTool(args) {
49785
50158
  { json: shouldEmitJson(), text: `${csv.path}
49786
50159
  ` }
49787
50160
  );
50161
+ await completePendingToolExecution(pendingRecoveryPath);
49788
50162
  return 0;
49789
50163
  }
49790
50164
  printCommandEnvelope(materializedEnvelope, { json: false });
50165
+ await completePendingToolExecution(pendingRecoveryPath);
49791
50166
  return 0;
49792
50167
  }
49793
50168
 
@@ -49873,7 +50248,7 @@ var import_promises13 = require("fs/promises");
49873
50248
  var import_node_path31 = require("path");
49874
50249
 
49875
50250
  // src/cli/workflow-to-play.ts
49876
- var import_node_crypto12 = require("crypto");
50251
+ var import_node_crypto13 = require("crypto");
49877
50252
  var HITL_WAIT_FOR_SIGNAL_TOOL = "deepline_workflow_wait_for_signal";
49878
50253
  var HITL_SLACK_TOOL = "slack_message_with_hitl";
49879
50254
  var SUB_WORKFLOW_TOOL_PREFIX = "deepline_workflow_";
@@ -49979,7 +50354,7 @@ function sanitizePlayNameSegment(value) {
49979
50354
  }
49980
50355
  function deriveWorkflowPlayName(workflowName) {
49981
50356
  const base = sanitizePlayNameSegment(workflowName) || "workflow";
49982
- const suffix = (0, import_node_crypto12.createHash)("sha256").update(workflowName).digest("hex").slice(0, 8);
50357
+ const suffix = (0, import_node_crypto13.createHash)("sha256").update(workflowName).digest("hex").slice(0, 8);
49983
50358
  const reserved = suffix.length + 1;
49984
50359
  const allowedBase = Math.max(1, MAX_PLAY_NAME_LENGTH - reserved);
49985
50360
  let name = `${base.slice(0, allowedBase)}_${suffix}`;
@@ -50384,6 +50759,7 @@ function registerDeeplineCommandGroups(program) {
50384
50759
  registerAuthCommands(program);
50385
50760
  registerProviderCommands(program);
50386
50761
  registerToolsCommands(program);
50762
+ registerExecutionsCommands(program);
50387
50763
  registerPlayCommands(program);
50388
50764
  registerSessionsCommands(program);
50389
50765
  registerWorkflowCommands(program);