deepline 0.3.200 → 0.3.202

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.
Files changed (43) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +62 -1
  2. package/dist/bundling-sources/sdk/src/http.ts +55 -6
  3. package/dist/bundling-sources/sdk/src/index.ts +1 -0
  4. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  5. package/dist/bundling-sources/sdk/src/types.ts +10 -2
  6. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +4 -2
  7. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +460 -96
  8. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +11 -1
  9. package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-invocation-fence.ts +6 -2
  10. package/dist/bundling-sources/shared_libs/play-runtime/fixture-behavior.ts +401 -2
  11. package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +458 -0
  12. package/dist/bundling-sources/shared_libs/play-runtime/provider-no-progress.ts +40 -0
  13. package/dist/bundling-sources/shared_libs/play-runtime/receipt-sql.ts +7 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +25 -3
  15. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +1 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +119 -171
  17. package/dist/bundling-sources/shared_libs/play-runtime/runtime-work-counters.ts +37 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-deadline-reconciliation.ts +160 -58
  19. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +111 -6
  20. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd.ts +51 -46
  21. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +16 -7
  22. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +304 -33
  23. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +21 -0
  24. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-value-storage.ts +64 -15
  25. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-capacity.ts +133 -5
  26. package/dist/bundling-sources/shared_libs/play-runtime/sheet-attempt-sql.ts +5 -5
  27. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +1 -0
  28. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/result-receipt-policy.ts +46 -0
  29. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +14 -5
  30. package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +5 -1
  31. package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-state-machine.ts +3 -9
  32. package/dist/cli/index.js +364 -39
  33. package/dist/cli/index.mjs +367 -41
  34. package/dist/index.d.mts +131 -3
  35. package/dist/index.d.ts +131 -3
  36. package/dist/index.js +64 -7
  37. package/dist/index.mjs +64 -7
  38. package/dist/install-integrity.json +3 -0
  39. package/dist/release.d.mts +1 -1
  40. package/dist/release.d.ts +1 -1
  41. package/dist/release.js +1 -1
  42. package/dist/release.mjs +1 -1
  43. package/package.json +1 -1
@@ -3046,7 +3046,7 @@ var SDK_RELEASE = {
3046
3046
  // getters keep their established compatibility behavior.
3047
3047
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
3048
3048
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
3049
- version: "0.3.200",
3049
+ version: "0.3.202",
3050
3050
  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.",
3051
3051
  packageCapabilities: {
3052
3052
  updatePreferences: 1
@@ -3637,6 +3637,11 @@ var MAX_DIAGNOSTIC_HEADER_LENGTH = 120;
3637
3637
  var COWORK_NETWORK_HINT = "Claude Cowork appears to be running Deepline in a network-restricted sandbox. In Claude Desktop, open Settings > Capabilities, turn on Allow network egress, and set Domain allowlist to All domains for the Cowork session.";
3638
3638
  var REQUEST_TIMEOUT_MARKER = Symbol("deeplineRequestTimeout");
3639
3639
  var REQUEST_ABORT_MARKER = Symbol("deeplineRequestAbort");
3640
+ function requestCancelledError(signal) {
3641
+ const error = withErrorCause(new Error("Request aborted"), signal.reason);
3642
+ error[REQUEST_ABORT_MARKER] = true;
3643
+ return error;
3644
+ }
3640
3645
  function normalizeRequestAbortError(error, input2) {
3641
3646
  const normalized = error instanceof Error ? error : new Error(String(error));
3642
3647
  const tagged = normalized;
@@ -3815,14 +3820,27 @@ var HttpClient = class {
3815
3820
  const candidateUrls = options?.exactUrlOnly ? [url] : buildCandidateUrls(url);
3816
3821
  let retryAfterDelayMs = null;
3817
3822
  const maxRetries = options?.maxRetries ?? this.config.maxRetries;
3818
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
3823
+ requestAttempts: for (let attempt = 0; attempt <= maxRetries; attempt++) {
3824
+ if (options?.signal?.aborted) {
3825
+ lastError = requestCancelledError(options.signal);
3826
+ break;
3827
+ }
3819
3828
  if (attempt > 0) {
3820
3829
  const backoffMs = Math.min(1e3 * Math.pow(2, attempt - 1), 3e4);
3821
3830
  const delayMs = retryAfterDelayMs === null ? backoffMs : Math.max(backoffMs, retryAfterDelayMs);
3822
3831
  retryAfterDelayMs = null;
3823
- await sleep(delayMs);
3832
+ try {
3833
+ await sleep(delayMs, options?.signal);
3834
+ } catch (error) {
3835
+ lastError = error instanceof Error ? error : new Error(String(error));
3836
+ break;
3837
+ }
3824
3838
  }
3825
3839
  for (const candidateUrl of candidateUrls) {
3840
+ if (options?.signal?.aborted) {
3841
+ lastError = requestCancelledError(options.signal);
3842
+ break requestAttempts;
3843
+ }
3826
3844
  const controller = new AbortController();
3827
3845
  const timeoutMs = options?.timeout ?? this.config.timeout;
3828
3846
  let requestTimedOut = false;
@@ -3830,6 +3848,10 @@ var HttpClient = class {
3830
3848
  requestTimedOut = true;
3831
3849
  controller.abort();
3832
3850
  }, timeoutMs);
3851
+ const abortRequest = () => controller.abort();
3852
+ options?.signal?.addEventListener("abort", abortRequest, {
3853
+ once: true
3854
+ });
3833
3855
  try {
3834
3856
  const response = await fetch(candidateUrl, {
3835
3857
  method,
@@ -3842,8 +3864,8 @@ var HttpClient = class {
3842
3864
  clearTimeout(timeoutId);
3843
3865
  return null;
3844
3866
  }
3845
- clearTimeout(timeoutId);
3846
3867
  const body = await response.text();
3868
+ clearTimeout(timeoutId);
3847
3869
  const parsed = parseResponseBody(body);
3848
3870
  const structuredToolError = options?.toolId && !response.ok ? deserializeToolExecutionFailure(
3849
3871
  apiErrorMessage(parsed, response.status),
@@ -3938,6 +3960,10 @@ var HttpClient = class {
3938
3960
  return parsed;
3939
3961
  } catch (error) {
3940
3962
  clearTimeout(timeoutId);
3963
+ if (options?.signal?.aborted) {
3964
+ lastError = requestCancelledError(options.signal);
3965
+ break requestAttempts;
3966
+ }
3941
3967
  if (error instanceof AuthError || error instanceof DeeplineError) {
3942
3968
  throw error;
3943
3969
  }
@@ -3949,6 +3975,9 @@ var HttpClient = class {
3949
3975
  timeoutMs,
3950
3976
  timedOut: requestTimedOut
3951
3977
  });
3978
+ } finally {
3979
+ clearTimeout(timeoutId);
3980
+ options?.signal?.removeEventListener("abort", abortRequest);
3952
3981
  }
3953
3982
  }
3954
3983
  if (attempt < maxRetries) continue;
@@ -4277,8 +4306,23 @@ function decodeSseFrame(frame) {
4277
4306
  }
4278
4307
  return parsed;
4279
4308
  }
4280
- function sleep(ms) {
4281
- return new Promise((resolve22) => setTimeout(resolve22, ms));
4309
+ function sleep(ms, signal) {
4310
+ return new Promise((resolve22, reject) => {
4311
+ if (signal?.aborted) {
4312
+ reject(requestCancelledError(signal));
4313
+ return;
4314
+ }
4315
+ const timeoutId = setTimeout(() => {
4316
+ signal?.removeEventListener("abort", abort);
4317
+ resolve22();
4318
+ }, ms);
4319
+ const abort = () => {
4320
+ clearTimeout(timeoutId);
4321
+ signal?.removeEventListener("abort", abort);
4322
+ reject(requestCancelledError(signal));
4323
+ };
4324
+ signal?.addEventListener("abort", abort, { once: true });
4325
+ });
4282
4326
  }
4283
4327
  function withCoworkNetworkHint(message) {
4284
4328
  if (!isCoworkLikeSandbox2() || message.includes(COWORK_NETWORK_HINT)) {
@@ -5465,9 +5509,13 @@ function normalizePlayRunLedgerSnapshot(value, fallback) {
5465
5509
  tableNamespace: optionalString(rawDataset.tableNamespace) ?? datasetId,
5466
5510
  phase,
5467
5511
  persistedRows: Math.max(0, finiteNumber(rawDataset.persistedRows) ?? 0),
5512
+ persistedRowsObserved: rawDataset.persistedRowsObserved === true,
5468
5513
  succeededRows: Math.max(0, finiteNumber(rawDataset.succeededRows) ?? 0),
5514
+ succeededRowsObserved: rawDataset.succeededRowsObserved === true,
5469
5515
  failedRows: Math.max(0, finiteNumber(rawDataset.failedRows) ?? 0),
5516
+ failedRowsObserved: rawDataset.failedRowsObserved === true,
5470
5517
  complete: rawDataset.complete === true,
5518
+ completeObserved: rawDataset.completeObserved === true,
5471
5519
  // ADR 0019 birth survives the snapshot round trip. A partial record is
5472
5520
  // dropped rather than half-admitted, so a dataset either states where its
5473
5521
  // rows came from or says nothing.
@@ -7207,6 +7255,7 @@ var DeeplineClient = class _DeeplineClient {
7207
7255
  this.http = new HttpClient(this.config);
7208
7256
  this.runs = {
7209
7257
  get: (runId, options2) => this.getRunStatus(runId, options2),
7258
+ status: (runId) => this.getRunLifecycleStatus(runId),
7210
7259
  list: (options2) => this.listRuns(options2),
7211
7260
  listPage: (options2) => this.listRunsPage(options2),
7212
7261
  tail: (runId, options2) => this.tailRun(runId, options2),
@@ -8619,6 +8668,13 @@ var DeeplineClient = class _DeeplineClient {
8619
8668
  }
8620
8669
  };
8621
8670
  }
8671
+ /** Read only the bounded scalar projection; does not hydrate the run package. */
8672
+ async getRunLifecycleStatus(runId) {
8673
+ const response = await this.http.get(
8674
+ `/api/v2/runs/${encodeURIComponent(runId)}/status`
8675
+ );
8676
+ return response;
8677
+ }
8622
8678
  /**
8623
8679
  * List play runs using the public runs resource model.
8624
8680
  *
@@ -9012,7 +9068,8 @@ var DeeplineClient = class _DeeplineClient {
9012
9068
  params.set("rowMode", "all");
9013
9069
  }
9014
9070
  const result = await this.http.get(
9015
- `/api/v2/plays/${encodeURIComponent(input2.playName)}/sheet?${params.toString()}`
9071
+ `/api/v2/plays/${encodeURIComponent(input2.playName)}/sheet?${params.toString()}`,
9072
+ { signal: input2.signal }
9016
9073
  );
9017
9074
  const requestedRunId = input2.runId?.trim() || "";
9018
9075
  if (requestedRunId) {
@@ -15808,7 +15865,7 @@ import {
15808
15865
  mkdtemp as mkdtemp3,
15809
15866
  readFile as readFile4,
15810
15867
  realpath as realpath4,
15811
- rename,
15868
+ rename as rename2,
15812
15869
  rm as rm3,
15813
15870
  stat as stat4,
15814
15871
  writeFile as writeFile4
@@ -15833,6 +15890,7 @@ import {
15833
15890
  link,
15834
15891
  open,
15835
15892
  readFile as readFileAsync,
15893
+ rename,
15836
15894
  unlink
15837
15895
  } from "fs/promises";
15838
15896
  import {
@@ -20734,6 +20792,12 @@ var FIXTURE_BEHAVIOR_REPLAY_VERSION = 3;
20734
20792
  var MAX_FIXTURE_RESPONSE_DELAY_SAMPLES = 256;
20735
20793
  var MAX_FIXTURE_RESPONSE_DELAY_MS = 8 * 6e4;
20736
20794
  var MAX_FIXTURE_RESPONSE_ERROR_MESSAGE_LENGTH = 500;
20795
+ var MAX_FIXTURE_RETRY_AFTER_MS = 60 * 6e4;
20796
+ var FIXTURE_RATE_WINDOWS = [
20797
+ { field: "requestsPerSecond", name: "rps", windowMs: 1e3 },
20798
+ { field: "requestsPerMinute", name: "rpm", windowMs: 6e4 },
20799
+ { field: "requestsPerHour", name: "rph", windowMs: 36e5 }
20800
+ ];
20737
20801
  function validateFixtureBehavior(value) {
20738
20802
  if (value === void 0 || value === null) {
20739
20803
  return { ok: true, behavior: null };
@@ -20745,6 +20809,8 @@ function validateFixtureBehavior(value) {
20745
20809
  const supportedKeys = record3.version === FIXTURE_BEHAVIOR_RESPONSE_VERSION || record3.version === FIXTURE_BEHAVIOR_REPLAY_VERSION ? /* @__PURE__ */ new Set([
20746
20810
  "version",
20747
20811
  "responseSamples",
20812
+ "transient429",
20813
+ "sharedRateLimit",
20748
20814
  ...record3.version === FIXTURE_BEHAVIOR_REPLAY_VERSION ? ["replayBundle"] : []
20749
20815
  ]) : /* @__PURE__ */ new Set(["version", "responseDelaySamplesMs"]);
20750
20816
  const unknownKeys = Object.keys(record3).filter(
@@ -20841,7 +20907,7 @@ function validateFixtureBehavior(value) {
20841
20907
  }
20842
20908
  const error = sample.httpError;
20843
20909
  const errorUnknownKeys = Object.keys(error).filter(
20844
- (key) => key !== "status" && key !== "message"
20910
+ (key) => key !== "status" && key !== "message" && key !== "retryAfterMs" && key !== "retryAfterFormat" && key !== "failuresBeforeSuccess"
20845
20911
  );
20846
20912
  if (errorUnknownKeys.length > 0) {
20847
20913
  return {
@@ -20861,7 +20927,33 @@ function validateFixtureBehavior(value) {
20861
20927
  error: `fixtureBehavior.responseSamples[${index}].httpError.message must contain 1-${MAX_FIXTURE_RESPONSE_ERROR_MESSAGE_LENGTH} characters.`
20862
20928
  };
20863
20929
  }
20864
- httpError = { status: error.status, message: error.message.trim() };
20930
+ if (error.retryAfterMs !== void 0 && (error.status !== 429 || typeof error.retryAfterMs !== "number" || !Number.isSafeInteger(error.retryAfterMs) || error.retryAfterMs < 1 || error.retryAfterMs > MAX_FIXTURE_RETRY_AFTER_MS)) {
20931
+ return {
20932
+ ok: false,
20933
+ error: `fixtureBehavior.responseSamples[${index}].httpError.retryAfterMs requires status 429 and an integer from 1 through ${MAX_FIXTURE_RETRY_AFTER_MS}.`
20934
+ };
20935
+ }
20936
+ if (error.retryAfterFormat !== void 0 && (error.status !== 429 || error.retryAfterFormat !== "delta_seconds" && error.retryAfterFormat !== "http_date")) {
20937
+ return {
20938
+ ok: false,
20939
+ error: `fixtureBehavior.responseSamples[${index}].httpError.retryAfterFormat requires status 429 and delta_seconds or http_date.`
20940
+ };
20941
+ }
20942
+ if (error.failuresBeforeSuccess !== void 0 && (error.status !== 429 || typeof error.failuresBeforeSuccess !== "number" || !Number.isSafeInteger(error.failuresBeforeSuccess) || error.failuresBeforeSuccess < 1 || error.failuresBeforeSuccess > 20)) {
20943
+ return {
20944
+ ok: false,
20945
+ error: `fixtureBehavior.responseSamples[${index}].httpError.failuresBeforeSuccess requires status 429 and an integer from 1 through 20.`
20946
+ };
20947
+ }
20948
+ httpError = {
20949
+ status: error.status,
20950
+ message: error.message.trim(),
20951
+ ...error.retryAfterMs !== void 0 ? { retryAfterMs: error.retryAfterMs } : {},
20952
+ ...error.retryAfterFormat !== void 0 ? {
20953
+ retryAfterFormat: error.retryAfterFormat
20954
+ } : {},
20955
+ ...error.failuresBeforeSuccess !== void 0 ? { failuresBeforeSuccess: error.failuresBeforeSuccess } : {}
20956
+ };
20865
20957
  }
20866
20958
  samples2.push({
20867
20959
  delayMs: sample.delayMs,
@@ -20869,6 +20961,106 @@ function validateFixtureBehavior(value) {
20869
20961
  ...httpError ? { httpError } : {}
20870
20962
  });
20871
20963
  }
20964
+ let sharedRateLimit;
20965
+ if (record3.sharedRateLimit !== void 0) {
20966
+ const plan = record3.sharedRateLimit;
20967
+ if (!plan || typeof plan !== "object" || Array.isArray(plan)) {
20968
+ return {
20969
+ ok: false,
20970
+ error: "fixtureBehavior.sharedRateLimit must be an object."
20971
+ };
20972
+ }
20973
+ const planRecord = plan;
20974
+ const entries = Object.entries(planRecord);
20975
+ const boundaryAfterMs = planRecord.requestsPerHourBoundaryAfterMs;
20976
+ const quotaEntries = entries.filter(
20977
+ ([name]) => name !== "requestsPerHourBoundaryAfterMs"
20978
+ );
20979
+ if (quotaEntries.length === 0 || quotaEntries.some(
20980
+ ([name, limit]) => !FIXTURE_RATE_WINDOWS.some((window) => window.field === name) || typeof limit !== "number" || !Number.isSafeInteger(limit) || limit < 1
20981
+ )) {
20982
+ return {
20983
+ ok: false,
20984
+ error: "fixtureBehavior.sharedRateLimit requires positive integer requestsPerSecond, requestsPerMinute, or requestsPerHour quotas."
20985
+ };
20986
+ }
20987
+ if (boundaryAfterMs !== void 0 && (planRecord.requestsPerHour === void 0 || typeof boundaryAfterMs !== "number" || !Number.isSafeInteger(boundaryAfterMs) || boundaryAfterMs < 1e3 || boundaryAfterMs > 3e5)) {
20988
+ return {
20989
+ ok: false,
20990
+ error: "fixtureBehavior.sharedRateLimit.requestsPerHourBoundaryAfterMs requires requestsPerHour and an integer from 1000 through 300000."
20991
+ };
20992
+ }
20993
+ sharedRateLimit = Object.fromEntries(entries);
20994
+ }
20995
+ let transient429;
20996
+ if (record3.transient429 !== void 0) {
20997
+ const selected = record3.transient429;
20998
+ if (!selected || typeof selected !== "object" || Array.isArray(selected)) {
20999
+ return {
21000
+ ok: false,
21001
+ error: "fixtureBehavior.transient429 must be an object."
21002
+ };
21003
+ }
21004
+ const fault = selected;
21005
+ const unknownFaultKeys = Object.keys(fault).filter(
21006
+ (key) => key !== "everyNthCall" && key !== "retryAfterMs" && key !== "failuresBeforeSuccess" && key !== "when"
21007
+ );
21008
+ if (unknownFaultKeys.length > 0) {
21009
+ return {
21010
+ ok: false,
21011
+ error: `Unsupported fixtureBehavior.transient429 field "${unknownFaultKeys[0]}".`
21012
+ };
21013
+ }
21014
+ for (const [name, value2, max] of [
21015
+ ["everyNthCall", fault.everyNthCall, 1e4],
21016
+ ["retryAfterMs", fault.retryAfterMs, MAX_FIXTURE_RETRY_AFTER_MS],
21017
+ ["failuresBeforeSuccess", fault.failuresBeforeSuccess, 20]
21018
+ ]) {
21019
+ if (typeof value2 !== "number" || !Number.isSafeInteger(value2) || value2 < 1 || value2 > max) {
21020
+ return {
21021
+ ok: false,
21022
+ error: `fixtureBehavior.transient429.${name} must be an integer from 1 through ${max}.`
21023
+ };
21024
+ }
21025
+ }
21026
+ let when;
21027
+ if (fault.when !== void 0) {
21028
+ if (!fault.when || typeof fault.when !== "object" || Array.isArray(fault.when)) {
21029
+ return {
21030
+ ok: false,
21031
+ error: "fixtureBehavior.transient429.when must be an object."
21032
+ };
21033
+ }
21034
+ const condition = fault.when;
21035
+ const unknownConditionKeys = Object.keys(condition).filter(
21036
+ (key) => key !== "provider" && key !== "operation"
21037
+ );
21038
+ if (unknownConditionKeys.length > 0) {
21039
+ return {
21040
+ ok: false,
21041
+ error: `Unsupported fixtureBehavior.transient429.when field "${unknownConditionKeys[0]}".`
21042
+ };
21043
+ }
21044
+ const provider = normalizeFixtureScopeValue(condition.provider, 100);
21045
+ const operation = normalizeFixtureScopeValue(condition.operation, 200);
21046
+ if (provider.error || operation.error || !provider.value && !operation.value) {
21047
+ return {
21048
+ ok: false,
21049
+ error: "fixtureBehavior.transient429.when must select a valid provider or operation."
21050
+ };
21051
+ }
21052
+ when = {
21053
+ ...provider.value ? { provider: provider.value } : {},
21054
+ ...operation.value ? { operation: operation.value } : {}
21055
+ };
21056
+ }
21057
+ transient429 = {
21058
+ everyNthCall: fault.everyNthCall,
21059
+ retryAfterMs: fault.retryAfterMs,
21060
+ failuresBeforeSuccess: fault.failuresBeforeSuccess,
21061
+ ...when ? { when } : {}
21062
+ };
21063
+ }
20872
21064
  let replayBundle;
20873
21065
  if (record3.version === FIXTURE_BEHAVIOR_REPLAY_VERSION) {
20874
21066
  if (!record3.replayBundle || typeof record3.replayBundle !== "object" || Array.isArray(record3.replayBundle)) {
@@ -20931,6 +21123,8 @@ function validateFixtureBehavior(value) {
20931
21123
  behavior: {
20932
21124
  version: FIXTURE_BEHAVIOR_REPLAY_VERSION,
20933
21125
  responseSamples: samples2,
21126
+ ...transient429 ? { transient429 } : {},
21127
+ ...sharedRateLimit ? { sharedRateLimit } : {},
20934
21128
  replayBundle
20935
21129
  }
20936
21130
  };
@@ -20939,7 +21133,9 @@ function validateFixtureBehavior(value) {
20939
21133
  ok: true,
20940
21134
  behavior: {
20941
21135
  version: FIXTURE_BEHAVIOR_RESPONSE_VERSION,
20942
- responseSamples: samples2
21136
+ responseSamples: samples2,
21137
+ ...transient429 ? { transient429 } : {},
21138
+ ...sharedRateLimit ? { sharedRateLimit } : {}
20943
21139
  }
20944
21140
  };
20945
21141
  }
@@ -22823,43 +23019,52 @@ async function readRunIdFile(destination) {
22823
23019
  }
22824
23020
  return parsed;
22825
23021
  }
22826
- async function writePlayRunIdFile(destination, runId) {
23022
+ async function writePlayRunIdFile(destination, runId, options = {}) {
22827
23023
  const existing = await readRunIdFile(destination);
22828
23024
  if (existing) {
22829
- if (existing.runId === runId) return;
22830
- throw new Error(
22831
- `--run-id-file destination already records run ${existing.runId}; refusing to replace it with ${runId}: ${destination}`
22832
- );
23025
+ if (existing.runId !== runId) {
23026
+ throw new Error(
23027
+ `--run-id-file destination already records run ${existing.runId}; refusing to replace it with ${runId}: ${destination}`
23028
+ );
23029
+ }
23030
+ if (!options.launchConfirmed || existing.launchConfirmed) return;
22833
23031
  }
22834
23032
  const tempPath = runIdFileTempPath(destination);
22835
23033
  let handle = null;
22836
23034
  try {
22837
23035
  handle = await open(tempPath, "wx", 384);
22838
23036
  await handle.writeFile(
22839
- `${JSON.stringify({ version: 1, runId })}
23037
+ `${JSON.stringify({ version: 1, runId, ...options })}
22840
23038
  `,
22841
23039
  "utf8"
22842
23040
  );
22843
23041
  await handle.sync();
22844
23042
  await handle.close();
22845
23043
  handle = null;
22846
- try {
22847
- await link(tempPath, destination);
22848
- if (process.platform !== "win32") {
22849
- const directory = await open(dirname10(destination), "r");
22850
- try {
22851
- await directory.sync();
22852
- } finally {
22853
- await directory.close();
23044
+ if (existing) {
23045
+ await rename(tempPath, destination);
23046
+ } else {
23047
+ try {
23048
+ await link(tempPath, destination);
23049
+ } catch (error) {
23050
+ if (error.code !== "EEXIST") throw error;
23051
+ const raced = await readRunIdFile(destination);
23052
+ if (raced?.runId !== runId) {
23053
+ throw new Error(
23054
+ `--run-id-file destination already records run ${raced?.runId ?? "<invalid>"}; refusing to replace it with ${runId}: ${destination}`
23055
+ );
22854
23056
  }
23057
+ if (!options.launchConfirmed || raced.launchConfirmed) return;
23058
+ await rename(tempPath, destination);
23059
+ }
23060
+ }
23061
+ if (process.platform !== "win32") {
23062
+ const directory = await open(dirname10(destination), "r");
23063
+ try {
23064
+ await directory.sync();
23065
+ } finally {
23066
+ await directory.close();
22855
23067
  }
22856
- } catch (error) {
22857
- if (error.code !== "EEXIST") throw error;
22858
- const raced = await readRunIdFile(destination);
22859
- if (raced?.runId === runId) return;
22860
- throw new Error(
22861
- `--run-id-file destination already records run ${raced?.runId ?? "<invalid>"}; refusing to replace it with ${runId}: ${destination}`
22862
- );
22863
23068
  }
22864
23069
  } finally {
22865
23070
  await handle?.close();
@@ -23916,6 +24121,16 @@ function getRunIdFromLiveEvent(event) {
23916
24121
  function eventConfirmsPlayRunLaunch(event) {
23917
24122
  return event.type === "play.run.status" && getEventPayload(event).launchConfirmed === true;
23918
24123
  }
24124
+ function statusProvesPlayRunExecution(status) {
24125
+ return status === "waiting" || status === "completed";
24126
+ }
24127
+ function eventProvesPlayRunExecution(event) {
24128
+ if (statusProvesPlayRunExecution(getStatusFromLiveEvent(event))) return true;
24129
+ const payload = getEventPayload(event);
24130
+ return Boolean(getStringField(payload, "stepId")) && (event.type === "play.step.progress" || event.type === "play.step.status" && ["running", "completed", "failed", "skipped"].includes(
24131
+ getStringField(payload, "status") ?? ""
24132
+ ));
24133
+ }
23919
24134
  function getStatusFromLiveEvent(event) {
23920
24135
  if (event.type !== "play.run.status" && event.type !== "play.run.snapshot" && event.type !== "play.run.final_status") {
23921
24136
  return null;
@@ -24392,6 +24607,12 @@ async function waitForPlayCompletionByStream(input2) {
24392
24607
  const durableStatusProbeIntervalMs = input2.durableStatusProbeIntervalMs ?? PLAY_DURABLE_STATUS_PROBE_INTERVAL_MS;
24393
24608
  let nextDurableProbeAt = Date.now() + durableStatusProbeIntervalMs;
24394
24609
  let durableStatusProbe = null;
24610
+ let admissionConfirmed = false;
24611
+ const confirmAdmission = async () => {
24612
+ if (admissionConfirmed) return;
24613
+ admissionConfirmed = true;
24614
+ await input2.onRunAdmitted?.(input2.workflowId);
24615
+ };
24395
24616
  const withDashboardUrl = (status) => input2.dashboardUrl ? { ...status, dashboardUrl: input2.dashboardUrl } : status;
24396
24617
  const remainingWaitMs = () => input2.waitTimeoutMs === null ? null : input2.waitTimeoutMs - (Date.now() - input2.startedAt);
24397
24618
  const fetchTerminalStatus = async () => {
@@ -24408,6 +24629,9 @@ async function waitForPlayCompletionByStream(input2) {
24408
24629
  }
24409
24630
  throw error;
24410
24631
  }
24632
+ if (refreshed.runId === input2.workflowId && statusProvesPlayRunExecution(refreshed.status)) {
24633
+ await confirmAdmission();
24634
+ }
24411
24635
  return TERMINAL_PLAY_STATUSES2.has(refreshed.status) ? withDashboardUrl(refreshed) : null;
24412
24636
  })();
24413
24637
  durableStatusProbe = probe;
@@ -24419,6 +24643,9 @@ async function waitForPlayCompletionByStream(input2) {
24419
24643
  };
24420
24644
  const handleLiveEvent = async (event, probeDurableStatus) => {
24421
24645
  assertPlayWaitNotTimedOut({ ...input2, lastPhase });
24646
+ if (getRunIdFromLiveEvent(event) === input2.workflowId && (eventConfirmsPlayRunLaunch(event) || eventProvesPlayRunExecution(event))) {
24647
+ await confirmAdmission();
24648
+ }
24422
24649
  const now = Date.now();
24423
24650
  if (now >= nextDurableProbeAt) {
24424
24651
  nextDurableProbeAt = now + durableStatusProbeIntervalMs;
@@ -24668,6 +24895,11 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
24668
24895
  let eventCount = 0;
24669
24896
  let firstRunIdMs = null;
24670
24897
  let launchConfirmed = false;
24898
+ const confirmAdmission = async (runId) => {
24899
+ if (launchConfirmed || !runId || runId === "pending") return;
24900
+ launchConfirmed = true;
24901
+ await input2.onRunAdmitted?.(runId);
24902
+ };
24671
24903
  let lastPhase = null;
24672
24904
  const timeout = input2.waitTimeoutMs === null ? null : setTimeout(
24673
24905
  () => {
@@ -24691,6 +24923,9 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
24691
24923
  }
24692
24924
  throw error;
24693
24925
  }
24926
+ if (refreshed.runId === lastKnownWorkflowId && statusProvesPlayRunExecution(refreshed.status)) {
24927
+ await confirmAdmission(lastKnownWorkflowId);
24928
+ }
24694
24929
  return TERMINAL_PLAY_STATUSES2.has(refreshed.status) ? {
24695
24930
  ...refreshed,
24696
24931
  dashboardUrl,
@@ -24718,8 +24953,8 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
24718
24953
  await input2.onRunStarted?.(eventRunId);
24719
24954
  }
24720
24955
  }
24721
- if (eventConfirmsPlayRunLaunch(event) && eventRunId === lastKnownWorkflowId) {
24722
- launchConfirmed = true;
24956
+ if (!launchConfirmed && eventConfirmsPlayRunLaunch(event) && eventRunId === lastKnownWorkflowId) {
24957
+ await confirmAdmission(lastKnownWorkflowId);
24723
24958
  }
24724
24959
  const workflowId = lastKnownWorkflowId || "pending";
24725
24960
  if (workflowId !== "pending" && !emittedDashboardUrl) {
@@ -24834,6 +25069,9 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
24834
25069
  firstRunIdMs,
24835
25070
  lastPhase
24836
25071
  });
25072
+ if (statusProvesPlayRunExecution(canonicalTerminal.status)) {
25073
+ await confirmAdmission(lastKnownWorkflowId);
25074
+ }
24837
25075
  return canonicalTerminal;
24838
25076
  }
24839
25077
  if (lastKnownWorkflowId && launchConfirmed) {
@@ -24861,7 +25099,8 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
24861
25099
  waitTimeoutMs: input2.waitTimeoutMs,
24862
25100
  startedAt,
24863
25101
  state,
24864
- progress: input2.progress
25102
+ progress: input2.progress,
25103
+ onRunAdmitted: confirmAdmission
24865
25104
  });
24866
25105
  }
24867
25106
  }
@@ -24921,7 +25160,8 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
24921
25160
  waitTimeoutMs: input2.waitTimeoutMs,
24922
25161
  startedAt,
24923
25162
  state,
24924
- progress: input2.progress
25163
+ progress: input2.progress,
25164
+ onRunAdmitted: confirmAdmission
24925
25165
  });
24926
25166
  }
24927
25167
  if (!lastKnownWorkflowId && isTransientPlayStreamError(error)) {
@@ -24974,7 +25214,8 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
24974
25214
  waitTimeoutMs: input2.waitTimeoutMs,
24975
25215
  startedAt,
24976
25216
  state,
24977
- progress: input2.progress
25217
+ progress: input2.progress,
25218
+ onRunAdmitted: confirmAdmission
24978
25219
  });
24979
25220
  }
24980
25221
  const phaseSuffix = lastPhase && lastPhase.trim() ? ` (last observed phase: ${lastPhase.trim()})` : "";
@@ -25917,8 +26158,15 @@ function formatPackageDatasetActionLines(packaged, options) {
25917
26158
  const isReturned = datasetId && returned.datasetIds.has(datasetId) || returned.paths.has(path);
25918
26159
  const category = isReturned ? "returned" : dataset.recovered === true ? "recovered" : "persisted";
25919
26160
  const rowCount = typeof dataset.rowCount === "number" && Number.isFinite(dataset.rowCount) ? Math.max(0, Math.trunc(dataset.rowCount)) : null;
26161
+ const lifecycle = readRecord(dataset.lifecycle);
26162
+ const countUnconfirmed = lifecycle?.complete === false;
26163
+ const persistedLowerBound = typeof lifecycle?.persistedRowsAtLeast === "number" && Number.isFinite(lifecycle.persistedRowsAtLeast) ? Math.max(0, Math.trunc(lifecycle.persistedRowsAtLeast)) : 0;
25920
26164
  const actions = readRecord(dataset.actions) ?? {};
25921
- if (includeSummary) {
26165
+ if (countUnconfirmed) {
26166
+ lines.push(
26167
+ ` dataset ${path}: row count unconfirmed (at least ${formatInteger(persistedLowerBound)} persisted); export this run to inspect saved rows`
26168
+ );
26169
+ } else if (includeSummary) {
25922
26170
  if (category === "recovered" && Object.keys(actions).length === 0) {
25923
26171
  lines.push(
25924
26172
  ` dataset ${path}: available, ${rowCount === null ? "persisted" : formatInteger(rowCount)} rows persisted; re-running reuses completed work`
@@ -28266,7 +28514,8 @@ async function handleFileBackedRun(options, hooks) {
28266
28514
  waitTimeoutMs: options.waitTimeoutMs,
28267
28515
  open: options.open,
28268
28516
  progress,
28269
- onRunStarted: hooks?.onRunStarted
28517
+ onRunStarted: hooks?.onRunStarted,
28518
+ onRunAdmitted: hooks?.onRunAdmitted
28270
28519
  }).catch(async (error) => {
28271
28520
  throw await normalizePlayStartError(client2, error, playName);
28272
28521
  })
@@ -28307,6 +28556,7 @@ async function handleFileBackedRun(options, hooks) {
28307
28556
  })
28308
28557
  );
28309
28558
  await hooks?.onRunStarted?.(started.workflowId);
28559
+ await hooks?.onRunAdmitted?.(started.workflowId);
28310
28560
  const resolvedDashboardUrl = buildPlayDashboardUrl(client2.baseUrl, playName);
28311
28561
  openPlayDashboard(resolvedDashboardUrl, options.open);
28312
28562
  progress.phase("started run");
@@ -28449,7 +28699,8 @@ async function handleNamedRun(options, hooks) {
28449
28699
  waitTimeoutMs: options.waitTimeoutMs,
28450
28700
  open: options.open,
28451
28701
  progress,
28452
- onRunStarted: hooks?.onRunStarted
28702
+ onRunStarted: hooks?.onRunStarted,
28703
+ onRunAdmitted: hooks?.onRunAdmitted
28453
28704
  }).catch(async (error) => {
28454
28705
  throw await normalizePlayStartError(client2, error, playName, {
28455
28706
  suggestMissingPlay: true
@@ -28495,6 +28746,7 @@ async function handleNamedRun(options, hooks) {
28495
28746
  })
28496
28747
  );
28497
28748
  await hooks?.onRunStarted?.(started.workflowId);
28749
+ await hooks?.onRunAdmitted?.(started.workflowId);
28498
28750
  const resolvedDashboardUrl = buildPlayDashboardUrl(client2.baseUrl, playName);
28499
28751
  openPlayDashboard(resolvedDashboardUrl, options.open);
28500
28752
  progress.phase("started run");
@@ -28532,6 +28784,19 @@ async function handlePlayRun(args, hooks) {
28532
28784
  );
28533
28785
  }
28534
28786
  await hooks?.onRunStarted?.(runId);
28787
+ },
28788
+ onRunAdmitted: async (runId) => {
28789
+ try {
28790
+ await writePlayRunIdFile(options.runIdFile, runId, {
28791
+ launchConfirmed: true
28792
+ });
28793
+ } catch (error) {
28794
+ throw new Error(
28795
+ `Run ${runId} was admitted, but its confirmation could not be persisted to ${options.runIdFile}: ${error instanceof Error ? error.message : String(error)}. Inspect it with 'deepline runs get ${runId} --json'; do not submit a replacement run.`,
28796
+ { cause: error }
28797
+ );
28798
+ }
28799
+ await hooks?.onRunAdmitted?.(runId);
28535
28800
  }
28536
28801
  } : hooks;
28537
28802
  if (options.target.kind === "file") {
@@ -28604,6 +28869,36 @@ async function handleRunGet(args) {
28604
28869
  });
28605
28870
  return 0;
28606
28871
  }
28872
+ async function handleRunLifecycleStatus(args) {
28873
+ const usage = "Usage: deepline runs status <run-id> [--json]";
28874
+ let runId;
28875
+ try {
28876
+ runId = parseRunIdPositional(args, usage);
28877
+ } catch (error) {
28878
+ console.error(error instanceof Error ? error.message : usage);
28879
+ return 1;
28880
+ }
28881
+ const status = await new DeeplineClient().runs.status(runId);
28882
+ if (argsWantJson(args)) {
28883
+ printCommandEnvelope(status, { json: true });
28884
+ return 0;
28885
+ }
28886
+ if (!status.available) {
28887
+ console.log(`Scalar lifecycle status is not available for run ${runId}.`);
28888
+ return 0;
28889
+ }
28890
+ const formatCount = (count) => count.value === null ? "unknown" : `${count.value}${count.confirmed ? "" : " (unconfirmed)"}`;
28891
+ console.log(`${status.lifecycle.status} \xB7 ${runId}`);
28892
+ console.log(
28893
+ `Map-step work (summed, not unique rows): completed ${formatCount(status.mapStepWork.completed)} / ${formatCount(status.mapStepWork.total)}, failed ${formatCount(status.mapStepWork.failed)}`
28894
+ );
28895
+ if (status.lifecycle.status === "waiting") {
28896
+ console.log(
28897
+ status.wait.confirmed && status.wait.kind ? `Waiting: ${status.wait.kind}` : "Waiting reason: unknown"
28898
+ );
28899
+ }
28900
+ return 0;
28901
+ }
28607
28902
  async function handleRunsRerun(args) {
28608
28903
  const usage = "Usage: deepline runs rerun <run-id> [--idempotency-key <key>] [--json]";
28609
28904
  let runId;
@@ -29076,6 +29371,30 @@ async function handleRunStop(args) {
29076
29371
  }
29077
29372
  const client2 = new DeeplineClient();
29078
29373
  const result = await client2.runs.stop(runId, { reason });
29374
+ if (result.cancelling === true) {
29375
+ printCommandEnvelope(
29376
+ {
29377
+ ...result,
29378
+ render: {
29379
+ sections: [
29380
+ {
29381
+ title: "run stop",
29382
+ lines: [
29383
+ `Cancellation queued for ${result.runId || runId}; sandbox cleanup is pending`,
29384
+ ...result.hitlCancelledCount > 0 ? [`cancelled HITL waits: ${result.hitlCancelledCount}`] : [],
29385
+ ...result.hitlCancellationPending === true ? [
29386
+ "warning: HITL cancellation or same-card Slack refresh is still pending"
29387
+ ] : [],
29388
+ `inspect: deepline runs get ${runId} --full --json`
29389
+ ]
29390
+ }
29391
+ ]
29392
+ }
29393
+ },
29394
+ { json: argsWantJson(args) }
29395
+ );
29396
+ return 0;
29397
+ }
29079
29398
  const stopNotConfirmed = result.stopped === false || result.staleSchedulerState === true;
29080
29399
  if (stopNotConfirmed) {
29081
29400
  const detail = typeof result.error === "string" && result.error.trim() ? result.error.trim() : result.staleSchedulerState === true ? "scheduler state for the run is stale" : "the server did not confirm the stop";
@@ -29190,6 +29509,7 @@ async function handleRunStopAll(args) {
29190
29509
  const result = await client2.runs.stopAll({ reason });
29191
29510
  const lines = [
29192
29511
  `Stopped ${result.stopped} active run${result.stopped === 1 ? "" : "s"}`,
29512
+ ...result.cancelling ? [`cancellation queued: ${result.cancelling}`] : [],
29193
29513
  ...result.partial ? ["partial: active runs may remain; rerun stop-all"] : [],
29194
29514
  ...result.failed > 0 ? [`failed stops: ${result.failed}`] : [],
29195
29515
  ...result.skipped > 0 ? [`skipped: ${result.skipped}`] : []
@@ -30979,6 +31299,12 @@ Examples:
30979
31299
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv
30980
31300
  `
30981
31301
  );
31302
+ runs.command("status <runId>").description("Read bounded lifecycle and confirmed work counts for a run.").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (runId, options) => {
31303
+ process.exitCode = await handleRunLifecycleStatus([
31304
+ runId,
31305
+ ...options.json ? ["--json"] : []
31306
+ ]);
31307
+ });
30982
31308
  runs.command("get <runId>").description(
30983
31309
  "Get status, progress, result, errors, and recovery metadata for a play run."
30984
31310
  ).addHelpText(
@@ -36596,7 +36922,7 @@ function registerEnrichCommand(program) {
36596
36922
  return exportResult;
36597
36923
  }
36598
36924
  const committedTempDir = inPlaceTempDir;
36599
- await rename(inPlaceTempOutputPath, inPlaceCommitOutputPath);
36925
+ await rename2(inPlaceTempOutputPath, inPlaceCommitOutputPath);
36600
36926
  inPlaceTempDir = null;
36601
36927
  inPlaceTempOutputPath = null;
36602
36928
  outputPath = inPlaceFinalOutputPath;