deepline 0.1.203 → 0.1.205

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.
@@ -106,10 +106,10 @@ export const SDK_RELEASE = {
106
106
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
107
107
  // fields shipped in 0.1.153.
108
108
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
109
- version: '0.1.203',
109
+ version: '0.1.205',
110
110
  apiContract: '2026-06-dataset-handle-results-hard-cutover',
111
111
  supportPolicy: {
112
- latest: '0.1.203',
112
+ latest: '0.1.205',
113
113
  minimumSupported: '0.1.53',
114
114
  deprecatedBelow: '0.1.53',
115
115
  commandMinimumSupported: [
@@ -325,6 +325,9 @@ export type WorkerRuntimeApiContext = {
325
325
  };
326
326
 
327
327
  const APP_RUNTIME_API_RETRY_DELAYS_MS = [100, 250, 500, 1_000] as const;
328
+ const APP_RUNTIME_API_RECEIPT_RETRY_DELAYS_MS = [
329
+ 250, 500, 1_000, 2_000, 4_000, 8_000, 12_000,
330
+ ] as const;
328
331
  const APP_RUNTIME_API_APPEND_RETRY_DELAYS_MS = [
329
332
  100, 250, 500, 1_000, 2_000, 4_000, 8_000,
330
333
  ] as const;
@@ -485,9 +488,33 @@ function isRetryableAppRuntimeAction(
485
488
  );
486
489
  }
487
490
 
491
+ function isRuntimeStepReceiptAction(
492
+ action: RuntimeApiRequest['action'],
493
+ ): boolean {
494
+ return (
495
+ action === 'get_runtime_step_receipt' ||
496
+ action === 'get_runtime_step_receipts' ||
497
+ action === 'heartbeat_runtime_step_receipts' ||
498
+ action === 'claim_runtime_step_receipt' ||
499
+ action === 'claim_runtime_step_receipts' ||
500
+ action === 'mark_runtime_step_receipt_running' ||
501
+ action === 'mark_runtime_step_receipts_running' ||
502
+ action === 'mark_runtime_step_receipts_queued' ||
503
+ action === 'complete_runtime_step_receipt' ||
504
+ action === 'complete_runtime_step_receipts' ||
505
+ action === 'fail_runtime_step_receipt' ||
506
+ action === 'fail_runtime_step_receipts' ||
507
+ action === 'release_runtime_step_receipt' ||
508
+ action === 'skip_runtime_step_receipt'
509
+ );
510
+ }
511
+
488
512
  function appRuntimeRetryDelaysForAction(
489
513
  action: RuntimeApiRequest['action'],
490
514
  ): readonly number[] {
515
+ if (isRuntimeStepReceiptAction(action)) {
516
+ return APP_RUNTIME_API_RECEIPT_RETRY_DELAYS_MS;
517
+ }
491
518
  return action === 'append_run_events'
492
519
  ? APP_RUNTIME_API_APPEND_RETRY_DELAYS_MS
493
520
  : APP_RUNTIME_API_RETRY_DELAYS_MS;
@@ -586,7 +613,11 @@ async function postAppRuntimeApi<TResponse>(
586
613
  await sleep(appRuntimeRetryDelayMs(body.action, attempt));
587
614
  continue;
588
615
  }
589
- throw error;
616
+ const message = error instanceof Error ? error.message : String(error);
617
+ throw new Error(
618
+ `App runtime API ${body.action} failed before receiving a response from ${resolveAppRuntimeApiUrl(context)} after ${attempt}/${maxAttempts} attempts: ${message}`,
619
+ { cause: error },
620
+ );
590
621
  }
591
622
  if (response.ok) {
592
623
  return (await response.json()) as TResponse;
@@ -82,7 +82,8 @@ export function validateDaytonaExecutionContext(
82
82
  * CUSTOMER code in PRODUCTION never runs with unrestricted egress — the
83
83
  * production worker must provision `DEEPLINE_DAYTONA_NETWORK_ALLOW_LIST`
84
84
  * (comma-separated CIDRs, per the Daytona `networkAllowList` contract) and
85
- * every prod sandbox is created `networkBlockAll: true` + that allow-list.
85
+ * every prod sandbox is created with that allow-list, which Daytona treats as
86
+ * "block all egress except these CIDRs".
86
87
  *
87
88
  * "Production" here is the RUN'S topology, not the build flag: a run whose
88
89
  * scheduler schema is isolated (per-PR preview CI, per-worktree dev — see
@@ -97,12 +98,43 @@ export function validateDaytonaExecutionContext(
97
98
  * An explicitly provisioned allow-list is always honored (preview included),
98
99
  * so a future preview fleet with enumerable egress can opt in to full parity.
99
100
  */
101
+ /**
102
+ * Keep only IPv4 CIDR entries from a comma-separated allow-list. Daytona's
103
+ * `networkAllowList` is IPv4-only; an IPv6 entry fails every sandbox create.
104
+ * Returns null when nothing valid remains so the production guard below still
105
+ * fires loudly rather than sending an empty list.
106
+ */
107
+ export function filterIpv4CidrAllowList(raw: string | null): string | null {
108
+ if (!raw) return raw;
109
+ const entries = raw
110
+ .split(',')
111
+ .map((entry) => entry.trim())
112
+ .filter(Boolean);
113
+ const ipv4 = entries.filter((entry) => !entry.includes(':'));
114
+ const dropped = entries.filter((entry) => entry.includes(':'));
115
+ if (dropped.length > 0) {
116
+ console.warn(
117
+ `[daytona] dropping ${dropped.length} non-IPv4 CIDR(s) from ` +
118
+ `${DAYTONA_NETWORK_ALLOW_LIST_ENV} (Daytona networkAllowList is ` +
119
+ `IPv4-only): ${dropped.join(', ')}`,
120
+ );
121
+ }
122
+ return ipv4.length > 0 ? ipv4.join(',') : null;
123
+ }
124
+
100
125
  export function resolveDaytonaSandboxNetworkPolicy(input: {
101
126
  runtimeSchedulerSchema: string | null | undefined;
102
127
  env?: NodeJS.ProcessEnv;
103
128
  }): { networkAllowList: string | null } {
104
129
  const env = input.env ?? process.env;
105
- const networkAllowList = env[DAYTONA_NETWORK_ALLOW_LIST_ENV]?.trim() || null;
130
+ // Daytona's `networkAllowList` accepts IPv4 CIDRs only — it rejects sandbox
131
+ // create outright if any entry is IPv6 ("Invalid IP address ... Must be a
132
+ // valid IPv4 address"). An operator-provisioned list can pick up AAAA-resolved
133
+ // hosts, so drop IPv6 (colon-bearing) entries and keep the IPv4 CIDRs that
134
+ // actually constrain egress. Warn loudly so a dropped entry is visible.
135
+ const networkAllowList = filterIpv4CidrAllowList(
136
+ env[DAYTONA_NETWORK_ALLOW_LIST_ENV]?.trim() || null,
137
+ );
106
138
  if (
107
139
  !networkAllowList &&
108
140
  env.NODE_ENV === 'production' &&
@@ -153,12 +185,13 @@ async function createOneShotDaytonaSandbox(input: {
153
185
  ephemeral: true,
154
186
  autoStopInterval: DAYTONA_AUTO_STOP_INTERVAL_MINUTES,
155
187
  autoArchiveInterval: DAYTONA_AUTO_ARCHIVE_INTERVAL_MINUTES,
156
- ...(networkAllowList
157
- ? {
158
- networkBlockAll: true,
159
- networkAllowList,
160
- }
161
- : {}),
188
+ // A non-empty `networkAllowList` IS the "block all egress except these
189
+ // CIDRs" control; Daytona rejects create when `networkBlockAll: true` is
190
+ // combined with a non-empty allow-list ("networkBlockAll: true cannot be
191
+ // combined with a non-empty networkAllowList or domainAllowList"). Pass
192
+ // the allow-list alone so the egress restriction holds without the
193
+ // contradictory flag.
194
+ ...(networkAllowList ? { networkAllowList } : {}),
162
195
  },
163
196
  { timeout: DAYTONA_CREATE_TIMEOUT_SECONDS },
164
197
  );
@@ -42,7 +42,7 @@ const DAYTONA_OUTPUT_TAIL_CHARS = 2_000;
42
42
  const DAYTONA_COMMAND_RECOVERY_TIMEOUT_MS = 60_000;
43
43
  const DAYTONA_COMMAND_RECOVERY_POLL_MS = 2_000;
44
44
  const DAYTONA_PROGRESS_SIDECAR_POLL_MS = 1_000;
45
- const DAYTONA_RUNTIME_DB_CONNECT_MAX_ATTEMPTS = 2;
45
+ const DAYTONA_RUNTIME_INITIALIZATION_MAX_ATTEMPTS = 2;
46
46
  const DAYTONA_INFRASTRUCTURE_MAX_ATTEMPTS = 2;
47
47
  const DAYTONA_UPLOAD_MAX_ATTEMPTS = 2;
48
48
  const DAYTONA_UPLOAD_ATTEMPT_DEADLINE_MS = 90_000;
@@ -113,6 +113,8 @@ export async function withDaytonaOperationDeadline<T>(input: {
113
113
  }
114
114
  const RUNTIME_POSTGRES_CONNECT_RETRY_PATTERN =
115
115
  /\bRuntime Postgres\b.*\b(connection timed out|connect timeout|ETIMEDOUT|ECONNRESET|ECONNREFUSED|Connection terminated|Connection ended unexpectedly)\b/i;
116
+ const RUNTIME_API_TRANSPORT_RETRY_PATTERN =
117
+ /\bRuntime API request to .+ failed before receiving a response:\s*(?:fetch failed|connection (?:terminated|timed out|closed|reset)|ECONNRESET|ETIMEDOUT|ECONNREFUSED)\b/i;
116
118
  const DAYTONA_INFRASTRUCTURE_RETRY_PATTERN =
117
119
  /\b(?:Request failed with status code (?:408|429|500|502|503|504)|ECONNRESET|ECONNREFUSED|ETIMEDOUT|UND_ERR_CONNECT_TIMEOUT|fetch failed|socket hang up|Daytona sandbox create failed across|Daytona sandbox create did not complete)\b/i;
118
120
 
@@ -494,13 +496,22 @@ function createDaytonaFailedResult(input: {
494
496
  };
495
497
  }
496
498
 
497
- function isRetryableRuntimePostgresConnectFailure(
499
+ function retryableRuntimeInitializationFailureReason(
498
500
  result: PlayRunnerResult,
499
- ): result is Extract<PlayRunnerResult, { status: 'failed' }> {
500
- return (
501
- result.status === 'failed' &&
502
- RUNTIME_POSTGRES_CONNECT_RETRY_PATTERN.test(result.error)
503
- );
501
+ ): 'runtime_postgres_connect' | 'runtime_api_transport' | null {
502
+ if (result.status !== 'failed') return null;
503
+ if (RUNTIME_POSTGRES_CONNECT_RETRY_PATTERN.test(result.error)) {
504
+ return 'runtime_postgres_connect';
505
+ }
506
+ const rowsProcessed = Number(result.stats?.rowsProcessed ?? 0);
507
+ if (
508
+ rowsProcessed === 0 &&
509
+ result.steps.length === 0 &&
510
+ RUNTIME_API_TRANSPORT_RETRY_PATTERN.test(result.error)
511
+ ) {
512
+ return 'runtime_api_transport';
513
+ }
514
+ return null;
504
515
  }
505
516
 
506
517
  function isRetryableDaytonaInfrastructureFailure(error: unknown): boolean {
@@ -640,7 +651,7 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
640
651
  let executionAttempt = 1;
641
652
  executionAttempt <=
642
653
  Math.max(
643
- DAYTONA_RUNTIME_DB_CONNECT_MAX_ATTEMPTS,
654
+ DAYTONA_RUNTIME_INITIALIZATION_MAX_ATTEMPTS,
644
655
  DAYTONA_INFRASTRUCTURE_MAX_ATTEMPTS,
645
656
  );
646
657
  executionAttempt += 1
@@ -868,14 +879,17 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
868
879
 
869
880
  const result = findPlayRunnerResult(events);
870
881
  if (result) {
882
+ const initializationRetryReason =
883
+ retryableRuntimeInitializationFailureReason(result);
871
884
  if (
872
- executionAttempt < DAYTONA_RUNTIME_DB_CONNECT_MAX_ATTEMPTS &&
873
- isRetryableRuntimePostgresConnectFailure(result)
885
+ result.status === 'failed' &&
886
+ executionAttempt < DAYTONA_RUNTIME_INITIALIZATION_MAX_ATTEMPTS &&
887
+ initializationRetryReason
874
888
  ) {
875
889
  emitDaytonaStage(callbacks, config.context, 'execute:retry', {
876
890
  sandboxId: sandbox.id,
877
891
  attempt: executionAttempt + 1,
878
- reason: 'runtime_postgres_connect',
892
+ reason: initializationRetryReason,
879
893
  error: result.error,
880
894
  elapsedMs: Date.now() - startedAt,
881
895
  });
@@ -2884,10 +2884,10 @@ async function prepareRuntimeSheetDatasetRows(
2884
2884
  AND (
2885
2885
  ${targetAttemptFenceSql}
2886
2886
  OR ${targetOlderForeignAttemptOwnerSql}
2887
- OR (
2888
- $11::boolean
2889
- AND target._status = 'failed'
2890
- )
2887
+ -- Failed rows are repairable projection state, never terminal
2888
+ -- ownership. This matches decideRuntimeSheetPrepare(): any
2889
+ -- attempt may reclaim them, even after a newer failed attempt.
2890
+ OR target._status = 'failed'
2891
2891
  )
2892
2892
  AND (
2893
2893
  target._run_id IS DISTINCT FROM $4::text
@@ -2919,14 +2919,13 @@ async function prepareRuntimeSheetDatasetRows(
2919
2919
  AND NOT (${existingNewerTerminalRowSql})
2920
2920
  AND (
2921
2921
  ${existingAttemptFenceSql}
2922
+ -- See claimed_existing_rows: failed rows remain repairable
2923
+ -- across attempt ordering, while enriched rows stay fenced.
2924
+ OR existing._status = 'failed'
2922
2925
  OR (
2923
2926
  ${existingForeignAttemptOwnerSql}
2924
2927
  AND COALESCE(existing._attempt_seq, 0) < $10::integer
2925
2928
  )
2926
- OR (
2927
- $11::boolean
2928
- AND existing._status = 'failed'
2929
- )
2930
2929
  )
2931
2930
  )
2932
2931
  OR (
package/dist/cli/index.js CHANGED
@@ -623,10 +623,10 @@ var SDK_RELEASE = {
623
623
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
624
624
  // fields shipped in 0.1.153.
625
625
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
626
- version: "0.1.203",
626
+ version: "0.1.205",
627
627
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
628
628
  supportPolicy: {
629
- latest: "0.1.203",
629
+ latest: "0.1.205",
630
630
  minimumSupported: "0.1.53",
631
631
  deprecatedBelow: "0.1.53",
632
632
  commandMinimumSupported: [
@@ -14113,7 +14113,7 @@ function writeStartedPlayRun(input2) {
14113
14113
  );
14114
14114
  }
14115
14115
  function parsePlayRunOptions(args) {
14116
- const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--profile <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--profile <id>] [--live|--latest|--revision-id <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--no-open] [--json] [--full] [--<input> value]\n --profile defaults to workers_edge, unless your local dev server exports a dev default such as absurd.\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotent call caching and ctx.dataset guidance.";
14116
+ const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--profile <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--profile <id>] [--live|--latest|--revision-id <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--no-open] [--json] [--full] [--<input> value]\n Production defaults to workers_edge. Use --profile absurd only for an explicit Absurd run.\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotent call caching and ctx.dataset guidance.";
14117
14117
  let filePath = null;
14118
14118
  let playName = null;
14119
14119
  let input2 = null;
@@ -16116,6 +16116,7 @@ Examples:
16116
16116
  deepline plays run prebuilt/person-linkedin-to-email --input '{"linkedin_url":"..."}'
16117
16117
  deepline plays run long-background-play --no-wait
16118
16118
  deepline plays run my.play.ts --input '{"domain":"stripe.com"}'
16119
+ deepline plays run my.play.ts --profile absurd
16119
16120
  deepline plays run my.play.ts --input @input.json --json
16120
16121
  deepline plays run cto-search.play.ts --limit 5
16121
16122
  deepline runs export <run-id> --out output.csv
@@ -608,10 +608,10 @@ var SDK_RELEASE = {
608
608
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
609
609
  // fields shipped in 0.1.153.
610
610
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
611
- version: "0.1.203",
611
+ version: "0.1.205",
612
612
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
613
613
  supportPolicy: {
614
- latest: "0.1.203",
614
+ latest: "0.1.205",
615
615
  minimumSupported: "0.1.53",
616
616
  deprecatedBelow: "0.1.53",
617
617
  commandMinimumSupported: [
@@ -14142,7 +14142,7 @@ function writeStartedPlayRun(input2) {
14142
14142
  );
14143
14143
  }
14144
14144
  function parsePlayRunOptions(args) {
14145
- const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--profile <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--profile <id>] [--live|--latest|--revision-id <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--no-open] [--json] [--full] [--<input> value]\n --profile defaults to workers_edge, unless your local dev server exports a dev default such as absurd.\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotent call caching and ctx.dataset guidance.";
14145
+ const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--profile <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--profile <id>] [--live|--latest|--revision-id <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--no-open] [--json] [--full] [--<input> value]\n Production defaults to workers_edge. Use --profile absurd only for an explicit Absurd run.\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotent call caching and ctx.dataset guidance.";
14146
14146
  let filePath = null;
14147
14147
  let playName = null;
14148
14148
  let input2 = null;
@@ -16145,6 +16145,7 @@ Examples:
16145
16145
  deepline plays run prebuilt/person-linkedin-to-email --input '{"linkedin_url":"..."}'
16146
16146
  deepline plays run long-background-play --no-wait
16147
16147
  deepline plays run my.play.ts --input '{"domain":"stripe.com"}'
16148
+ deepline plays run my.play.ts --profile absurd
16148
16149
  deepline plays run my.play.ts --input @input.json --json
16149
16150
  deepline plays run cto-search.play.ts --limit 5
16150
16151
  deepline runs export <run-id> --out output.csv
package/dist/index.js CHANGED
@@ -422,10 +422,10 @@ var SDK_RELEASE = {
422
422
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
423
423
  // fields shipped in 0.1.153.
424
424
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
425
- version: "0.1.203",
425
+ version: "0.1.205",
426
426
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
427
427
  supportPolicy: {
428
- latest: "0.1.203",
428
+ latest: "0.1.205",
429
429
  minimumSupported: "0.1.53",
430
430
  deprecatedBelow: "0.1.53",
431
431
  commandMinimumSupported: [
package/dist/index.mjs CHANGED
@@ -352,10 +352,10 @@ var SDK_RELEASE = {
352
352
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
353
353
  // fields shipped in 0.1.153.
354
354
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
355
- version: "0.1.203",
355
+ version: "0.1.205",
356
356
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
357
357
  supportPolicy: {
358
- latest: "0.1.203",
358
+ latest: "0.1.205",
359
359
  minimumSupported: "0.1.53",
360
360
  deprecatedBelow: "0.1.53",
361
361
  commandMinimumSupported: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.203",
3
+ "version": "0.1.205",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {