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.
@@ -3063,7 +3063,7 @@ var SDK_RELEASE = {
3063
3063
  // getters keep their established compatibility behavior.
3064
3064
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
3065
3065
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
3066
- version: "0.3.149",
3066
+ version: "0.3.151",
3067
3067
  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.",
3068
3068
  packageCapabilities: {
3069
3069
  updatePreferences: 1
@@ -3822,6 +3822,11 @@ var HttpClient = class {
3822
3822
  body: options?.formData !== void 0 ? typeof options.formData === "function" ? options.formData() : options.formData : options?.body !== void 0 ? JSON.stringify(options.body) : void 0,
3823
3823
  signal: controller.signal
3824
3824
  });
3825
+ options?.onResponse?.(response);
3826
+ if (response.status === 404 && options?.allowNotFound) {
3827
+ clearTimeout(timeoutId);
3828
+ return null;
3829
+ }
3825
3830
  clearTimeout(timeoutId);
3826
3831
  const body = await response.text();
3827
3832
  const parsed = parseResponseBody(body);
@@ -3961,7 +3966,8 @@ var HttpClient = class {
3961
3966
  retryAfterMs: null,
3962
3967
  networkKind: code === "NETWORK_TIMEOUT" ? "timeout" : code === "NETWORK_ABORTED" ? "unknown" : "unavailable",
3963
3968
  networkScope: "client_to_deepline",
3964
- details: mappedNetworkError.details
3969
+ details: mappedNetworkError.details,
3970
+ publicDetails: options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : null
3965
3971
  }
3966
3972
  ),
3967
3973
  lastError
@@ -6565,6 +6571,56 @@ var MONITOR_NON_RETRYABLE_MUTATION_OPTIONS = {
6565
6571
  exactUrlOnly: true
6566
6572
  };
6567
6573
  var RAW_V2_EXECUTE_RESPONSE_CONTRACT = RAW_V2_TOOL_RESPONSE_CONTRACT;
6574
+ var DEFAULT_EXECUTION_RECOVERY_TIMEOUT_MS = 15 * 60 * 1e3;
6575
+ function validateExecutionIdempotencyKey(key) {
6576
+ if (typeof key !== "string" || key.length < 1 || key.length > 200 || !/^[A-Za-z0-9._:-]+$/.test(key)) {
6577
+ throw new DeeplineError(
6578
+ "Execution idempotency keys must be 1\u2013200 ASCII letters, digits, dots, underscores, colons, or hyphens.",
6579
+ void 0,
6580
+ "IDEMPOTENCY_KEY_INVALID"
6581
+ );
6582
+ }
6583
+ }
6584
+ function isExecutionInProgressResponse(value) {
6585
+ return typeof value === "object" && value !== null && typeof value.executionRecovery === "object" && value.executionRecovery.state === "running";
6586
+ }
6587
+ function getExecutionRecoveryState(value) {
6588
+ if (typeof value !== "object" || value === null || typeof value.executionRecovery !== "object") {
6589
+ return null;
6590
+ }
6591
+ const state = value.executionRecovery.state;
6592
+ return state === "running" || state === "completed" || state === "outcome_unknown" ? state : null;
6593
+ }
6594
+ function isRecoverableExecutionAttemptError(error) {
6595
+ if (error instanceof ToolExecutionError) {
6596
+ return error.code === "EXECUTION_IN_PROGRESS" || error.origin === "deepline" && error.category === "network" && error.networkScope === "client_to_deepline";
6597
+ }
6598
+ return error instanceof DeeplineError && (error.code === "EXECUTION_IN_PROGRESS" || error.code?.startsWith("NETWORK_") === true && error.statusCode === void 0);
6599
+ }
6600
+ function isRecoverableExecutionLookupError(error) {
6601
+ if (isRecoverableExecutionAttemptError(error)) return true;
6602
+ return error instanceof DeeplineError && (error.statusCode === 429 || error.statusCode !== void 0 && error.statusCode >= 500);
6603
+ }
6604
+ function timeoutWithinRecoveryBudget(requestTimeoutMs, defaultRequestTimeoutMs, remainingMs) {
6605
+ return Math.min(requestTimeoutMs ?? defaultRequestTimeoutMs, remainingMs);
6606
+ }
6607
+ function executionRecoveryError(input2) {
6608
+ return new ToolExecutionError(input2.message, {
6609
+ toolId: input2.toolId,
6610
+ provider: null,
6611
+ operation: input2.toolId,
6612
+ code: input2.code,
6613
+ origin: "deepline",
6614
+ category: input2.code === "EXECUTION_OUTCOME_UNKNOWN" ? "unknown" : "conflict",
6615
+ retryable: input2.code !== "EXECUTION_OUTCOME_UNKNOWN",
6616
+ statusCode: null,
6617
+ requestId: null,
6618
+ retryAfterMs: null,
6619
+ networkKind: null,
6620
+ networkScope: null,
6621
+ publicDetails: { idempotencyKey: input2.idempotencyKey }
6622
+ });
6623
+ }
6568
6624
  var COMPILE_MANIFEST_RETRY_DELAYS_MS = [250, 1e3];
6569
6625
  var REGISTER_PLAY_ARTIFACTS_COMPILE_CONCURRENCY = 3;
6570
6626
  var REGISTER_PLAY_ARTIFACTS_MAX_BATCH_COUNT = 3;
@@ -7478,29 +7534,183 @@ var DeeplineClient = class _DeeplineClient {
7478
7534
  * Deepline execution envelope.
7479
7535
  */
7480
7536
  async executeTool(toolId, input2, options) {
7537
+ const inputSnapshot = JSON.parse(JSON.stringify(input2));
7538
+ const metadataSnapshot = options?.metadata ? JSON.parse(JSON.stringify(options.metadata)) : void 0;
7539
+ const idempotencyKey = options?.idempotencyKey ?? (options?.recover ? crypto.randomUUID() : void 0);
7540
+ if (idempotencyKey !== void 0) {
7541
+ validateExecutionIdempotencyKey(idempotencyKey);
7542
+ if (options?.recoveryTimeoutMs !== void 0 && (!Number.isFinite(options.recoveryTimeoutMs) || options.recoveryTimeoutMs < 0)) {
7543
+ throw new DeeplineError(
7544
+ "recoveryTimeoutMs must be a finite, non-negative number.",
7545
+ void 0,
7546
+ "IDEMPOTENCY_KEY_INVALID"
7547
+ );
7548
+ }
7549
+ await options?.onExecution?.({ idempotencyKey });
7550
+ const timeoutMs2 = options?.timeout ?? resolveToolExecuteTimeoutMs(toolId, inputSnapshot);
7551
+ await this.lookupExecutionByKey(idempotencyKey, timeoutMs2);
7552
+ }
7553
+ const timeoutMs = options?.timeout ?? resolveToolExecuteTimeoutMs(toolId, inputSnapshot);
7481
7554
  const headers = {
7482
7555
  [EXECUTE_RESPONSE_CONTRACT_HEADER]: RAW_V2_EXECUTE_RESPONSE_CONTRACT,
7483
7556
  [TOOL_EXECUTION_ERROR_SCHEMA_HEADER]: String(
7484
7557
  TOOL_EXECUTION_ERROR_SCHEMA_VERSION
7485
7558
  ),
7486
7559
  ...options?.includeToolMetadata ? { [INCLUDE_TOOL_METADATA_HEADER]: "true" } : {},
7487
- [EXECUTE_RESPONSE_INTENT_HEADER]: options?.responseIntent ?? "raw"
7560
+ [EXECUTE_RESPONSE_INTENT_HEADER]: options?.responseIntent ?? "raw",
7561
+ ...idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}
7488
7562
  };
7489
- const response = await this.http.post(
7563
+ const request = (requestTimeoutMs) => this.http.post(
7490
7564
  `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
7491
7565
  {
7492
- payload: input2,
7493
- ...options?.metadata ? { metadata: options.metadata } : {}
7566
+ payload: inputSnapshot,
7567
+ ...metadataSnapshot ? { metadata: metadataSnapshot } : {}
7494
7568
  },
7495
7569
  headers,
7496
7570
  {
7497
- timeout: options?.timeout ?? resolveToolExecuteTimeoutMs(toolId, input2),
7498
- maxRetries: options?.maxRetries ?? 0,
7571
+ timeout: requestTimeoutMs,
7572
+ maxRetries: idempotencyKey ? 0 : options?.maxRetries ?? 0,
7499
7573
  exactUrlOnly: true,
7500
- toolId
7574
+ toolId,
7575
+ idempotencyKey
7576
+ }
7577
+ );
7578
+ let response;
7579
+ let recoveryDeadline = null;
7580
+ const recoveryTimeoutMs = options?.recoveryTimeoutMs ?? DEFAULT_EXECUTION_RECOVERY_TIMEOUT_MS;
7581
+ let retryDelayMs = 1e3;
7582
+ const recoveryTimeoutError = () => executionRecoveryError({
7583
+ toolId,
7584
+ idempotencyKey,
7585
+ code: "EXECUTION_RECOVERY_TIMEOUT",
7586
+ message: `Execution recovery for ${idempotencyKey} did not finish before the recovery timeout. Inspect client.executions.getByKey() and resume with the same idempotency key.`
7587
+ });
7588
+ const waitForRecoveryRetry = async (deadline) => {
7589
+ const remainingMs = deadline - Date.now();
7590
+ if (remainingMs <= 0) throw recoveryTimeoutError();
7591
+ await new Promise(
7592
+ (resolve22) => setTimeout(resolve22, Math.min(retryDelayMs, remainingMs))
7593
+ );
7594
+ retryDelayMs = Math.min(retryDelayMs * 2, 5e3);
7595
+ };
7596
+ const lookupDuringRecovery = async () => {
7597
+ while (true) {
7598
+ const remainingMs = recoveryDeadline - Date.now();
7599
+ if (remainingMs <= 0) throw recoveryTimeoutError();
7600
+ try {
7601
+ return await this.lookupExecutionByKey(
7602
+ idempotencyKey,
7603
+ timeoutWithinRecoveryBudget(
7604
+ timeoutMs,
7605
+ this.config.timeout,
7606
+ remainingMs
7607
+ )
7608
+ );
7609
+ } catch (error) {
7610
+ if (!isRecoverableExecutionLookupError(error)) throw error;
7611
+ await waitForRecoveryRetry(recoveryDeadline);
7612
+ }
7613
+ }
7614
+ };
7615
+ while (true) {
7616
+ const remainingMs = recoveryDeadline === null ? null : recoveryDeadline - Date.now();
7617
+ if (remainingMs !== null && remainingMs <= 0) {
7618
+ throw recoveryTimeoutError();
7619
+ }
7620
+ try {
7621
+ response = await request(
7622
+ remainingMs === null ? timeoutMs : timeoutWithinRecoveryBudget(
7623
+ timeoutMs,
7624
+ this.config.timeout,
7625
+ remainingMs
7626
+ )
7627
+ );
7628
+ if (getExecutionRecoveryState(response) === "outcome_unknown") {
7629
+ if (!idempotencyKey) break;
7630
+ throw executionRecoveryError({
7631
+ toolId,
7632
+ idempotencyKey,
7633
+ code: "EXECUTION_OUTCOME_UNKNOWN",
7634
+ message: `The outcome of execution ${idempotencyKey} is unknown. Do not retry with a new key; inspect client.executions.getByKey().`
7635
+ });
7636
+ }
7637
+ if (!idempotencyKey || !isExecutionInProgressResponse(response)) {
7638
+ break;
7639
+ }
7640
+ if (recoveryDeadline === null) {
7641
+ recoveryDeadline = Date.now() + recoveryTimeoutMs;
7642
+ }
7643
+ } catch (error) {
7644
+ if (idempotencyKey && error instanceof DeeplineError && error.code === "EXECUTION_OUTCOME_UNKNOWN") {
7645
+ throw executionRecoveryError({
7646
+ toolId,
7647
+ idempotencyKey,
7648
+ code: "EXECUTION_OUTCOME_UNKNOWN",
7649
+ message: error.message
7650
+ });
7651
+ }
7652
+ if (!idempotencyKey || !isRecoverableExecutionAttemptError(error)) {
7653
+ throw error;
7654
+ }
7655
+ if (recoveryDeadline === null) {
7656
+ recoveryDeadline = Date.now() + recoveryTimeoutMs;
7657
+ }
7658
+ }
7659
+ await waitForRecoveryRetry(recoveryDeadline);
7660
+ const recovered = await lookupDuringRecovery();
7661
+ if (getExecutionRecoveryState(recovered) === "outcome_unknown") {
7662
+ throw executionRecoveryError({
7663
+ toolId,
7664
+ idempotencyKey,
7665
+ code: "EXECUTION_OUTCOME_UNKNOWN",
7666
+ message: `The outcome of execution ${idempotencyKey} is unknown. Do not retry with a new key; inspect client.executions.getByKey().`
7667
+ });
7668
+ }
7669
+ }
7670
+ const materialized = materializeToolExecutionResponse(response);
7671
+ return idempotencyKey ? { ...materialized, idempotencyKey } : materialized;
7672
+ }
7673
+ /** Read the durable state of a keyed tool execution. */
7674
+ async getExecutionByKey(idempotencyKey) {
7675
+ validateExecutionIdempotencyKey(idempotencyKey);
7676
+ const result = await this.lookupExecutionByKey(idempotencyKey);
7677
+ if (!result) {
7678
+ throw new DeeplineError(
7679
+ `No execution exists for idempotency key ${idempotencyKey}.`,
7680
+ 404,
7681
+ "EXECUTION_NOT_FOUND"
7682
+ );
7683
+ }
7684
+ return result;
7685
+ }
7686
+ async lookupExecutionByKey(idempotencyKey, timeoutMs) {
7687
+ validateExecutionIdempotencyKey(idempotencyKey);
7688
+ let supported = false;
7689
+ const result = await this.http.get(
7690
+ `/api/v2/executions/by-key/${encodeURIComponent(idempotencyKey)}`,
7691
+ {
7692
+ exactUrlOnly: true,
7693
+ maxRetries: 0,
7694
+ timeout: timeoutMs,
7695
+ idempotencyKey,
7696
+ allowNotFound: true,
7697
+ onResponse: (response) => {
7698
+ supported = response.headers.get("X-Deepline-Idempotency-Supported") === "true";
7699
+ }
7501
7700
  }
7502
7701
  );
7503
- return materializeToolExecutionResponse(response);
7702
+ if (!supported) {
7703
+ throw new DeeplineError(
7704
+ "This Deepline server does not support recoverable tool executions; no tool was dispatched.",
7705
+ 422,
7706
+ "IDEMPOTENCY_NOT_SUPPORTED"
7707
+ );
7708
+ }
7709
+ return result;
7710
+ }
7711
+ /** Public recovery namespace. */
7712
+ get executions() {
7713
+ return { getByKey: (key) => this.getExecutionByKey(key) };
7504
7714
  }
7505
7715
  /**
7506
7716
  * Back-compatible alias for {@link executeTool}.
@@ -11139,6 +11349,15 @@ function errorToJsonPayload(error) {
11139
11349
  if (typeof maybeRecord?.status === "number") {
11140
11350
  details.status = maybeRecord.status;
11141
11351
  }
11352
+ if (typeof maybeRecord?.requestId === "string" || maybeRecord?.requestId === null) {
11353
+ details.requestId = maybeRecord.requestId;
11354
+ }
11355
+ if (typeof maybeRecord?.retryable === "boolean") {
11356
+ details.retryable = maybeRecord.retryable;
11357
+ }
11358
+ if (maybeRecord?.publicDetails && typeof maybeRecord.publicDetails === "object" && !Array.isArray(maybeRecord.publicDetails)) {
11359
+ details.publicDetails = maybeRecord.publicDetails;
11360
+ }
11142
11361
  if (maybeRecord?.details && typeof maybeRecord.details === "object" && !Array.isArray(maybeRecord.details)) {
11143
11362
  details.details = maybeRecord.details;
11144
11363
  }
@@ -36119,6 +36338,17 @@ function registerEnrichCommand(program) {
36119
36338
  });
36120
36339
  }
36121
36340
 
36341
+ // src/cli/commands/executions.ts
36342
+ function registerExecutionsCommands(program) {
36343
+ const executions = program.command("executions").description("Inspect and recover keyed tool executions.");
36344
+ 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) => {
36345
+ const result = await new DeeplineClient().executions.getByKey(
36346
+ options.idempotencyKey
36347
+ );
36348
+ printCommandEnvelope(result, { json: true });
36349
+ });
36350
+ }
36351
+
36122
36352
  // src/cli/commands/feedback.ts
36123
36353
  import { Option as Option3 } from "commander";
36124
36354
  import { readFileSync as readFileSync10 } from "fs";
@@ -47058,15 +47288,37 @@ Examples:
47058
47288
 
47059
47289
  // src/cli/commands/tools.ts
47060
47290
  import { Option as Option4 } from "commander";
47291
+ import { createHash as createHash6, randomUUID as randomUUID9 } from "crypto";
47292
+
47293
+ // ../plays/row-identity.ts
47294
+ function stableValue(value) {
47295
+ if (Array.isArray(value)) {
47296
+ return value.map((entry) => stableValue(entry));
47297
+ }
47298
+ if (value && typeof value === "object") {
47299
+ return Object.fromEntries(
47300
+ Object.entries(value).filter(([, entry]) => entry !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, stableValue(entry)])
47301
+ );
47302
+ }
47303
+ return value;
47304
+ }
47305
+ function stableStringify(value) {
47306
+ return JSON.stringify(stableValue(value));
47307
+ }
47308
+
47309
+ // src/cli/commands/tools.ts
47061
47310
  import {
47062
47311
  chmodSync,
47063
47312
  existsSync as existsSync19,
47313
+ linkSync,
47314
+ mkdirSync as mkdirSync16,
47064
47315
  mkdtempSync,
47065
47316
  readFileSync as readFileSync22,
47317
+ unlinkSync as unlinkSync4,
47066
47318
  writeFileSync as writeFileSync20
47067
47319
  } from "fs";
47068
47320
  import { tmpdir as tmpdir6 } from "os";
47069
- import { join as join25, resolve as resolve20 } from "path";
47321
+ import { dirname as dirname20, join as join25, resolve as resolve20 } from "path";
47070
47322
 
47071
47323
  // src/tool-output.ts
47072
47324
  import {
@@ -48200,6 +48452,15 @@ Examples:
48200
48452
  ).option("-o, --out <path>", "Write row-shaped tool output to this CSV path").option(
48201
48453
  "--no-preview",
48202
48454
  "Only print the extracted output path when applicable"
48455
+ ).option(
48456
+ "--idempotency-key <key>",
48457
+ "Stable key for retrying or recovering this tool execution"
48458
+ ).option(
48459
+ "--recover",
48460
+ "Generate or reuse a saved idempotency key for safe recovery"
48461
+ ).option(
48462
+ "--recovery-timeout-ms <milliseconds>",
48463
+ "Maximum wait for a keyed execution to finish (default: 900000)"
48203
48464
  ).action(async (toolId, options) => {
48204
48465
  const args = [
48205
48466
  toolId,
@@ -48211,7 +48472,10 @@ Examples:
48211
48472
  ...options.timeout ? ["--timeout", options.timeout] : [],
48212
48473
  ...options.outputFormat ? ["--output-format", options.outputFormat] : [],
48213
48474
  ...options.out ? ["--out", options.out] : [],
48214
- ...options.preview === false ? ["--no-preview"] : []
48475
+ ...options.preview === false ? ["--no-preview"] : [],
48476
+ ...options.idempotencyKey ? ["--idempotency-key", options.idempotencyKey] : [],
48477
+ ...options.recover ? ["--recover"] : [],
48478
+ ...options.recoveryTimeoutMs ? ["--recovery-timeout-ms", options.recoveryTimeoutMs] : []
48215
48479
  ];
48216
48480
  process.exitCode = await executeTool(args);
48217
48481
  });
@@ -49448,7 +49712,7 @@ function parseExecuteOptions(args) {
49448
49712
  const toolId = args[0];
49449
49713
  if (!toolId) {
49450
49714
  throw new Error(
49451
- `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]`
49715
+ `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]`
49452
49716
  );
49453
49717
  }
49454
49718
  const params = {};
@@ -49456,6 +49720,9 @@ function parseExecuteOptions(args) {
49456
49720
  let noPreview = false;
49457
49721
  let outPath = null;
49458
49722
  let timeoutMs;
49723
+ let idempotencyKey;
49724
+ let recover = false;
49725
+ let recoveryTimeoutMs;
49459
49726
  for (let index = 1; index < args.length; index += 1) {
49460
49727
  const arg = args[index];
49461
49728
  if ((arg === "--param" || arg === "-p") && args[index + 1]) {
@@ -49491,13 +49758,39 @@ function parseExecuteOptions(args) {
49491
49758
  noPreview = true;
49492
49759
  continue;
49493
49760
  }
49761
+ if (arg === "--idempotency-key" && args[index + 1]) {
49762
+ idempotencyKey = args[++index];
49763
+ continue;
49764
+ }
49765
+ if (arg === "--recover") {
49766
+ recover = true;
49767
+ continue;
49768
+ }
49769
+ if (arg === "--recovery-timeout-ms" && args[index + 1]) {
49770
+ const parsed = Number(args[++index]);
49771
+ if (!Number.isFinite(parsed) || parsed < 0) {
49772
+ throw new Error("--recovery-timeout-ms must be a non-negative number.");
49773
+ }
49774
+ recoveryTimeoutMs = parsed;
49775
+ continue;
49776
+ }
49494
49777
  if ((arg === "--out" || arg === "-o") && args[index + 1]) {
49495
49778
  outPath = resolve20(args[++index]);
49496
49779
  continue;
49497
49780
  }
49498
49781
  throw new Error(`Unknown option: ${arg}`);
49499
49782
  }
49500
- return { toolId, params, outputFormat, noPreview, outPath, timeoutMs };
49783
+ return {
49784
+ toolId,
49785
+ params,
49786
+ outputFormat,
49787
+ noPreview,
49788
+ outPath,
49789
+ timeoutMs,
49790
+ idempotencyKey,
49791
+ recover,
49792
+ recoveryTimeoutMs
49793
+ };
49501
49794
  }
49502
49795
  function parseToolExecuteTimeout(raw) {
49503
49796
  const match = /^(\d+)(ms|s|m|h)?$/i.exec(raw.trim());
@@ -49517,6 +49810,65 @@ function parseToolExecuteTimeout(raw) {
49517
49810
  }
49518
49811
  return timeoutMs;
49519
49812
  }
49813
+ function pendingToolExecutionPath(input2) {
49814
+ const config = resolveConfig();
49815
+ const fingerprint = createHash6("sha256").update(config.baseUrl).update("\0").update(createHash6("sha256").update(config.apiKey).digest("hex")).update("\0").update(input2.toolId).update("\0").update(stableStringify(input2.params)).update("\0").update(input2.responseIntent).digest("hex");
49816
+ return join25(
49817
+ sdkCliStateDirPath(resolveConfig().baseUrl),
49818
+ `pending-tool-execution-${fingerprint}.json`
49819
+ );
49820
+ }
49821
+ function loadOrCreatePendingToolExecution(path) {
49822
+ mkdirSync16(dirname20(path), { recursive: true });
49823
+ if (existsSync19(path)) {
49824
+ return readPendingToolExecution(path);
49825
+ }
49826
+ const idempotencyKey = randomUUID9();
49827
+ const temporaryPath = `${path}.${process.pid}.${randomUUID9()}.tmp`;
49828
+ try {
49829
+ writeFileSync20(temporaryPath, `${JSON.stringify({ idempotencyKey })}
49830
+ `, {
49831
+ encoding: "utf8",
49832
+ flag: "wx",
49833
+ mode: 384
49834
+ });
49835
+ linkSync(temporaryPath, path);
49836
+ return idempotencyKey;
49837
+ } catch (error) {
49838
+ if (error.code !== "EEXIST") throw error;
49839
+ return readPendingToolExecution(path);
49840
+ } finally {
49841
+ if (existsSync19(temporaryPath)) unlinkSync4(temporaryPath);
49842
+ }
49843
+ }
49844
+ function readPendingToolExecution(path) {
49845
+ const saved = JSON.parse(readFileSync22(path, "utf8"));
49846
+ if (typeof saved.idempotencyKey !== "string") {
49847
+ throw new Error(
49848
+ `Cannot resume the saved tool execution at ${path}: invalid recovery key.`
49849
+ );
49850
+ }
49851
+ return saved.idempotencyKey;
49852
+ }
49853
+ async function completePendingToolExecution(path) {
49854
+ if (!path || !existsSync19(path)) return;
49855
+ await new Promise((resolve22, reject) => {
49856
+ const onError = (error) => {
49857
+ process.stdout.off("error", onError);
49858
+ reject(error);
49859
+ };
49860
+ process.stdout.once("error", onError);
49861
+ process.stdout.write("", (error) => {
49862
+ process.stdout.off("error", onError);
49863
+ if (error) {
49864
+ reject(error);
49865
+ } else {
49866
+ resolve22();
49867
+ }
49868
+ });
49869
+ });
49870
+ if (existsSync19(path)) unlinkSync4(path);
49871
+ }
49520
49872
  function safeFileStem(value) {
49521
49873
  return value.trim().replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "tool";
49522
49874
  }
@@ -49634,6 +49986,8 @@ function buildToolExecuteBaseEnvelope(input2) {
49634
49986
  ] : [];
49635
49987
  return {
49636
49988
  ...envelope,
49989
+ ...input2.idempotencyKey ? { idempotency_key: input2.idempotencyKey } : {},
49990
+ ...isRecord12(input2.rawResponse) && isRecord12(input2.rawResponse.executionRecovery) ? { execution_recovery: input2.rawResponse.executionRecovery } : {},
49637
49991
  ...envelopeHasCanonicalOutput || envelopeHasDeclaredOutput ? { output_preview: outputPreview } : { output: outputPreview },
49638
49992
  ...summaryEntries.length > 0 ? { summary: input2.summary } : {},
49639
49993
  ...warningMessages.length > 0 ? { warnings: warningMessages } : {},
@@ -49817,10 +50171,25 @@ async function executeTool(args) {
49817
50171
  }
49818
50172
  return 2;
49819
50173
  }
50174
+ let idempotencyKey = parsed.idempotencyKey;
50175
+ let pendingRecoveryPath = null;
50176
+ const responseIntent = parsed.outPath || parsed.outputFormat === "csv" || parsed.outputFormat === "csv_file" ? "row_artifact" : "raw";
50177
+ if (parsed.recover && !idempotencyKey) {
50178
+ pendingRecoveryPath = pendingToolExecutionPath({
50179
+ toolId: parsed.toolId,
50180
+ params: parsed.params,
50181
+ responseIntent
50182
+ });
50183
+ idempotencyKey = loadOrCreatePendingToolExecution(pendingRecoveryPath);
50184
+ }
50185
+ if (idempotencyKey) {
50186
+ console.error(`Tool execution recovery key: ${idempotencyKey}`);
50187
+ }
49820
50188
  const rawResponse = await client2.executeTool(parsed.toolId, parsed.params, {
49821
50189
  ...parsed.timeoutMs !== void 0 ? { timeout: parsed.timeoutMs } : {},
49822
- responseIntent: parsed.outPath || parsed.outputFormat === "csv" || parsed.outputFormat === "csv_file" ? "row_artifact" : "raw",
49823
- ...parsed.timeoutMs !== void 0 ? { timeout: parsed.timeoutMs } : {}
50190
+ responseIntent,
50191
+ ...idempotencyKey ? { idempotencyKey } : {},
50192
+ ...parsed.recoveryTimeoutMs !== void 0 ? { recoveryTimeoutMs: parsed.recoveryTimeoutMs } : {}
49824
50193
  });
49825
50194
  const listConversion = tryConvertToList(rawResponse, {
49826
50195
  listExtractorPaths: listExtractorPathsFromUsageGuidance(metadata)
@@ -49829,12 +50198,14 @@ async function executeTool(args) {
49829
50198
  const baseEnvelope = buildToolExecuteBaseEnvelope({
49830
50199
  toolId: parsed.toolId,
49831
50200
  params: parsed.params,
50201
+ ...idempotencyKey ? { idempotencyKey } : {},
49832
50202
  rawResponse,
49833
50203
  listConversion,
49834
50204
  summary
49835
50205
  });
49836
50206
  if (!parsed.outPath && (parsed.outputFormat === "json" || parsed.outputFormat === "auto" && shouldEmitJson())) {
49837
50207
  printCommandEnvelope(baseEnvelope, { json: true });
50208
+ await completePendingToolExecution(pendingRecoveryPath);
49838
50209
  return 0;
49839
50210
  }
49840
50211
  if (parsed.outputFormat === "json_file") {
@@ -49852,6 +50223,7 @@ async function executeTool(args) {
49852
50223
  },
49853
50224
  { json: true }
49854
50225
  );
50226
+ await completePendingToolExecution(pendingRecoveryPath);
49855
50227
  return 0;
49856
50228
  }
49857
50229
  if (!listConversion) {
@@ -49880,9 +50252,11 @@ async function executeTool(args) {
49880
50252
  },
49881
50253
  { json: parsed.outputFormat === "csv_file" || shouldEmitJson() }
49882
50254
  );
50255
+ await completePendingToolExecution(pendingRecoveryPath);
49883
50256
  return 0;
49884
50257
  }
49885
50258
  printCommandEnvelope(baseEnvelope, { json: false });
50259
+ await completePendingToolExecution(pendingRecoveryPath);
49886
50260
  return 0;
49887
50261
  }
49888
50262
  const rowOutput = projectRowOutput(listConversion);
@@ -49905,6 +50279,7 @@ async function executeTool(args) {
49905
50279
  });
49906
50280
  if (parsed.outputFormat === "csv_file") {
49907
50281
  printCommandEnvelope(materializedEnvelope, { json: true });
50282
+ await completePendingToolExecution(pendingRecoveryPath);
49908
50283
  return 0;
49909
50284
  }
49910
50285
  if (parsed.outPath) {
@@ -49913,6 +50288,7 @@ async function executeTool(args) {
49913
50288
  text: `Wrote ${csv.rowCount} row(s) to ${csv.path}
49914
50289
  `
49915
50290
  });
50291
+ await completePendingToolExecution(pendingRecoveryPath);
49916
50292
  return 0;
49917
50293
  }
49918
50294
  if (parsed.noPreview) {
@@ -49927,9 +50303,11 @@ async function executeTool(args) {
49927
50303
  { json: shouldEmitJson(), text: `${csv.path}
49928
50304
  ` }
49929
50305
  );
50306
+ await completePendingToolExecution(pendingRecoveryPath);
49930
50307
  return 0;
49931
50308
  }
49932
50309
  printCommandEnvelope(materializedEnvelope, { json: false });
50310
+ await completePendingToolExecution(pendingRecoveryPath);
49933
50311
  return 0;
49934
50312
  }
49935
50313
 
@@ -50012,10 +50390,10 @@ Examples:
50012
50390
 
50013
50391
  // src/cli/commands/workflow.ts
50014
50392
  import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
50015
- import { dirname as dirname20, join as join26, resolve as resolve21 } from "path";
50393
+ import { dirname as dirname21, join as join26, resolve as resolve21 } from "path";
50016
50394
 
50017
50395
  // src/cli/workflow-to-play.ts
50018
- import { createHash as createHash6 } from "crypto";
50396
+ import { createHash as createHash7 } from "crypto";
50019
50397
  var HITL_WAIT_FOR_SIGNAL_TOOL = "deepline_workflow_wait_for_signal";
50020
50398
  var HITL_SLACK_TOOL = "slack_message_with_hitl";
50021
50399
  var SUB_WORKFLOW_TOOL_PREFIX = "deepline_workflow_";
@@ -50121,7 +50499,7 @@ function sanitizePlayNameSegment(value) {
50121
50499
  }
50122
50500
  function deriveWorkflowPlayName(workflowName) {
50123
50501
  const base = sanitizePlayNameSegment(workflowName) || "workflow";
50124
- const suffix = createHash6("sha256").update(workflowName).digest("hex").slice(0, 8);
50502
+ const suffix = createHash7("sha256").update(workflowName).digest("hex").slice(0, 8);
50125
50503
  const reserved = suffix.length + 1;
50126
50504
  const allowedBase = Math.max(1, MAX_PLAY_NAME_LENGTH - reserved);
50127
50505
  let name = `${base.slice(0, allowedBase)}_${suffix}`;
@@ -50264,7 +50642,7 @@ async function transformOne(api, workflowId, outDir, publish) {
50264
50642
  { workflowName: workflow.name, version: revision.version }
50265
50643
  );
50266
50644
  const file = join26(resolve21(outDir), `${compiled.playName}.play.ts`);
50267
- await mkdir5(dirname20(file), { recursive: true });
50645
+ await mkdir5(dirname21(file), { recursive: true });
50268
50646
  await writeFile5(file, compiled.sourceCode, "utf8");
50269
50647
  let published = false;
50270
50648
  if (publish) {
@@ -50526,6 +50904,7 @@ function registerDeeplineCommandGroups(program) {
50526
50904
  registerAuthCommands(program);
50527
50905
  registerProviderCommands(program);
50528
50906
  registerToolsCommands(program);
50907
+ registerExecutionsCommands(program);
50529
50908
  registerPlayCommands(program);
50530
50909
  registerSessionsCommands(program);
50531
50910
  registerWorkflowCommands(program);