deepline 0.3.30 → 0.3.32

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.
@@ -192,7 +192,7 @@ export const SDK_RELEASE = {
192
192
  // 0.3.0 introduces raw-v2: complete scrubbed provider responses are
193
193
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
194
194
  // getters keep their established compatibility behavior.
195
- version: '0.3.30',
195
+ version: '0.3.32',
196
196
  updateSummary:
197
197
  'New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.',
198
198
  contracts: {
@@ -49,7 +49,10 @@ import {
49
49
  PLAY_RUNTIME_TEST_FAULT_HEADER,
50
50
  recognizedRuntimeTestFaultCount,
51
51
  } from '@shared_libs/play-runtime/test-runtime-seams';
52
- import { vercelProtectionBypassHeaders } from '@shared_libs/play-runtime/vercel-protection';
52
+ import {
53
+ isVercelSecurityCheckpointResponse,
54
+ vercelProtectionBypassHeaders,
55
+ } from '@shared_libs/play-runtime/vercel-protection';
53
56
  import type { RuntimeReceiptAction } from '@shared_libs/play-runtime/runtime-actions';
54
57
  import { RUNTIME_CAPACITY_POLICY } from '@shared_libs/play-runtime/runtime-capacity-policy';
55
58
  import { RUNTIME_RELIABILITY_POLICY } from '@shared_libs/play-runtime/runtime-reliability-policy';
@@ -478,10 +481,20 @@ function isRetryableAppRuntimeResponse(input: {
478
481
  action: RuntimeApiRequest['action'];
479
482
  status: number;
480
483
  body: string;
484
+ contentType: string | null;
481
485
  }): boolean {
482
486
  if (!isRetryableAppRuntimeAction(input.action)) {
483
487
  return false;
484
488
  }
489
+ // A WAF checkpoint is not a statement from the app about the idempotent
490
+ // projection. Do not make every 403 retryable: only this exact Vercel HTML
491
+ // response on a replay-safe action is an intermediary failure.
492
+ if (
493
+ input.action === 'append_run_events' &&
494
+ isVercelSecurityCheckpointResponse(input)
495
+ ) {
496
+ return true;
497
+ }
485
498
  // The app runtime may explicitly classify an otherwise-client-error status
486
499
  // as transient. Keep the structured delivery contract authoritative rather
487
500
  // than reducing every 4xx to permanent at the worker boundary.
@@ -1273,6 +1286,7 @@ async function postAppRuntimeApi<TResponse>(
1273
1286
  action: body.action,
1274
1287
  status: response.status,
1275
1288
  body: responseText,
1289
+ contentType: response.headers.get('content-type'),
1276
1290
  })
1277
1291
  ) {
1278
1292
  const retryDelayMs = Math.max(
@@ -1299,6 +1313,7 @@ async function postAppRuntimeApi<TResponse>(
1299
1313
  action: body.action,
1300
1314
  status: response.status,
1301
1315
  body: responseText,
1316
+ contentType: response.headers.get('content-type'),
1302
1317
  })
1303
1318
  ? 'retryable_http'
1304
1319
  : 'http_error',
@@ -1328,6 +1343,7 @@ async function postAppRuntimeApi<TResponse>(
1328
1343
  action: body.action,
1329
1344
  status: response.status,
1330
1345
  body: responseText,
1346
+ contentType: response.headers.get('content-type'),
1331
1347
  }),
1332
1348
  detail: summarizeAppRuntimeErrorBody(responseText),
1333
1349
  boundaryLabel,
@@ -255,6 +255,7 @@ async function createOneShotDaytonaSandbox(input: {
255
255
  orgId: string;
256
256
  context: DaytonaExecutionContext;
257
257
  sandboxName: string;
258
+ providerOperationToken?: string;
258
259
  }): Promise<DaytonaSandbox> {
259
260
  const limits = validatePlaySandboxRuntimeLimits(
260
261
  input.context.sandboxRuntimeLimits ?? {
@@ -271,6 +272,7 @@ async function createOneShotDaytonaSandbox(input: {
271
272
  workflowId?: string;
272
273
  playId?: string;
273
274
  runId?: string;
275
+ operationKey?: string;
274
276
  } = {
275
277
  source: DAYTONA_PLAY_RUNNER_LABEL_SOURCE,
276
278
  };
@@ -278,6 +280,8 @@ async function createOneShotDaytonaSandbox(input: {
278
280
  if (workflowId) labels.workflowId = workflowId;
279
281
  if (playId) labels.playId = playId;
280
282
  if (runId) labels.runId = runId;
283
+ if (input.providerOperationToken)
284
+ labels.operationKey = input.providerOperationToken;
281
285
  const { networkAllowList } = resolveDaytonaSandboxNetworkPolicy({
282
286
  runtimeSchedulerSchema: input.context.runtimeSchedulerSchema ?? null,
283
287
  });
@@ -357,7 +361,9 @@ async function createRetriedOneShotDaytonaSandbox(input: {
357
361
  context: DaytonaExecutionContext;
358
362
  emitStage: DaytonaStageEmitter;
359
363
  observeCreateCall?: DaytonaCreateCallObserver;
360
- reserveSandboxCapacity?: () => Promise<{ leaseId: string }>;
364
+ reserveSandboxCapacity?: (
365
+ providerAttempt: number,
366
+ ) => Promise<{ leaseId: string; providerOperationToken: string | null }>;
361
367
  releaseSandboxCapacity?: (leaseId: string) => Promise<void>;
362
368
  nextProviderAttempt: () => number;
363
369
  startedAt: number;
@@ -381,7 +387,7 @@ async function createRetriedOneShotDaytonaSandbox(input: {
381
387
  occurredAtMs: attemptStartedAt,
382
388
  providerAttempt: attempt,
383
389
  });
384
- const reservation = await input.reserveSandboxCapacity?.();
390
+ const reservation = await input.reserveSandboxCapacity?.(attempt);
385
391
  let sandbox: DaytonaSandbox;
386
392
  try {
387
393
  sandbox = await createOneShotDaytonaSandbox({
@@ -389,6 +395,8 @@ async function createRetriedOneShotDaytonaSandbox(input: {
389
395
  orgId: input.orgId,
390
396
  context: input.context,
391
397
  sandboxName,
398
+ providerOperationToken:
399
+ reservation?.providerOperationToken ?? undefined,
392
400
  });
393
401
  } catch (error) {
394
402
  // A normal rejected create has no durable provider resource. A timeout
@@ -500,7 +508,9 @@ async function acquireOneShotDaytonaSandbox(input: {
500
508
  context: DaytonaExecutionContext;
501
509
  emitStage: DaytonaStageEmitter;
502
510
  observeCreateCall?: DaytonaCreateCallObserver;
503
- reserveSandboxCapacity?: () => Promise<{ leaseId: string }>;
511
+ reserveSandboxCapacity?: (
512
+ providerAttempt: number,
513
+ ) => Promise<{ leaseId: string; providerOperationToken: string | null }>;
504
514
  releaseSandboxCapacity?: (leaseId: string) => Promise<void>;
505
515
  nextProviderAttempt: () => number;
506
516
  startedAt: number;
@@ -607,7 +617,10 @@ export function createOneShotDaytonaSandboxLifecycle(input: {
607
617
  context: DaytonaExecutionContext;
608
618
  emitStage: DaytonaStageEmitter;
609
619
  observeCreateCall?: DaytonaCreateCallObserver;
610
- reserveSandboxCapacity?: () => Promise<{ leaseId: string }>;
620
+ reserveSandboxCapacity?: (providerAttempt: number) => Promise<{
621
+ leaseId: string;
622
+ providerOperationToken: string | null;
623
+ }>;
611
624
  releaseSandboxCapacity?: (leaseId: string) => Promise<void>;
612
625
  startedAt?: number;
613
626
  }): OneShotDaytonaSandboxLifecycle {
@@ -953,7 +953,8 @@ function prepareDaytonaExecution(
953
953
  emitDaytonaStage(callbacks, input.context, stage, extra),
954
954
  observeCreateCall: callbacks?.onRuntimeLifecycleEvent,
955
955
  reserveSandboxCapacity: callbacks?.reserveSandboxCapacity
956
- ? () => callbacks.reserveSandboxCapacity!('daytona')
956
+ ? (providerAttempt) =>
957
+ callbacks.reserveSandboxCapacity!('daytona', providerAttempt)
957
958
  : undefined,
958
959
  releaseSandboxCapacity: callbacks?.releaseSandboxCapacity,
959
960
  });
@@ -131,7 +131,8 @@ export interface PlayRunnerCallbacks {
131
131
  /** Reserve one physical-sandbox slot immediately before a provider create. */
132
132
  reserveSandboxCapacity?: (
133
133
  provider: 'daytona' | 'modal',
134
- ) => Promise<{ leaseId: string }>;
134
+ providerAttempt?: number,
135
+ ) => Promise<{ leaseId: string; providerOperationToken: string | null }>;
135
136
  /** Release an unbound reservation when provider create never produced a resource. */
136
137
  releaseSandboxCapacity?: (leaseId: string) => Promise<void>;
137
138
  /**
@@ -1,6 +1,7 @@
1
1
  type FetchLike = typeof fetch;
2
2
 
3
3
  const vercelProtectionCookieCache = new Map<string, Promise<string | null>>();
4
+ const VERCEL_PROTECTION_COOKIE_PREFLIGHT_TIMEOUT_MS = 5_000;
4
5
 
5
6
  async function awaitWithSignal<T>(
6
7
  promise: Promise<T>,
@@ -45,6 +46,25 @@ export function vercelProtectionBypassHeader(
45
46
  return normalized ? { 'x-vercel-protection-bypass': normalized } : {};
46
47
  }
47
48
 
49
+ /**
50
+ * A Vercel Firewall checkpoint is a transport/intermediary response, not an
51
+ * application authorization decision. Keep this deliberately narrow: callers
52
+ * still decide which replay-safe internal action may retry it.
53
+ */
54
+ export function isVercelSecurityCheckpointResponse(input: {
55
+ status: number;
56
+ contentType: string | null | undefined;
57
+ body: string;
58
+ }): boolean {
59
+ if (input.status !== 403) return false;
60
+ if (!/^text\/html(?:\s*;|$)/i.test(input.contentType?.trim() ?? '')) {
61
+ return false;
62
+ }
63
+ return /<title\b[^>]*>\s*Vercel Security Checkpoint\s*<\/title>/i.test(
64
+ input.body,
65
+ );
66
+ }
67
+
48
68
  function setCookieHeaders(headers: Headers): string[] {
49
69
  const getter = (headers as Headers & { getSetCookie?: () => string[] })
50
70
  .getSetCookie;
@@ -75,14 +95,27 @@ export async function resolveVercelProtectionBypassCookie(input: {
75
95
 
76
96
  const cacheKey = `${baseUrl}\n${token}`;
77
97
  const cached = vercelProtectionCookieCache.get(cacheKey);
78
- if (cached) return await awaitWithSignal(cached, input.signal);
98
+ if (cached) {
99
+ try {
100
+ return await awaitWithSignal(cached, input.signal);
101
+ } catch (error) {
102
+ if (input.signal?.aborted) throw input.signal.reason ?? error;
103
+ // A concurrent caller must retain the same header-only fallback as the
104
+ // caller that created the shared preflight promise.
105
+ return null;
106
+ }
107
+ }
79
108
 
80
109
  const promise = (async () => {
81
110
  const url = new URL(`${baseUrl}/api/v2/health`);
82
111
  addVercelProtectionBypassSearchParams(url, token);
112
+ // Keep the cache independent from an individual outbox lease. A caller
113
+ // may stop waiting, but the shared bootstrap is always bounded and either
114
+ // supplies a reusable cookie or evicts itself for a later retry.
83
115
  const response = await (input.fetchImpl ?? fetch)(url.toString(), {
84
116
  headers: { 'x-vercel-protection-bypass': token },
85
- }).catch(() => null);
117
+ signal: AbortSignal.timeout(VERCEL_PROTECTION_COOKIE_PREFLIGHT_TIMEOUT_MS),
118
+ });
86
119
  return response ? cookieHeaderFromSetCookie(response.headers) : null;
87
120
  })();
88
121
  vercelProtectionCookieCache.set(cacheKey, promise);
@@ -91,7 +124,15 @@ export async function resolveVercelProtectionBypassCookie(input: {
91
124
  vercelProtectionCookieCache.delete(cacheKey);
92
125
  }
93
126
  });
94
- return await awaitWithSignal(promise, input.signal);
127
+ try {
128
+ return await awaitWithSignal(promise, input.signal);
129
+ } catch (error) {
130
+ if (input.signal?.aborted) throw input.signal.reason ?? error;
131
+ // The header-only bypass remains useful when cookie bootstrap is briefly
132
+ // unavailable; do not turn a bounded preflight failure into a permanent
133
+ // delivery failure. The rejection handler above evicts this cache entry.
134
+ return null;
135
+ }
95
136
  }
96
137
 
97
138
  export async function vercelProtectionBypassHeaders(input: {
package/dist/cli/index.js CHANGED
@@ -1042,7 +1042,7 @@ var SDK_RELEASE = {
1042
1042
  // 0.3.0 introduces raw-v2: complete scrubbed provider responses are
1043
1043
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
1044
1044
  // getters keep their established compatibility behavior.
1045
- version: "0.3.30",
1045
+ version: "0.3.32",
1046
1046
  updateSummary: "New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.",
1047
1047
  contracts: {
1048
1048
  api: {
@@ -34243,9 +34243,10 @@ Notes:
34243
34243
  for a patch-style change.
34244
34244
  Repeating the exact saved definition resumes an incomplete deploy cleanup:
34245
34245
  Deepline keeps the replacement, removes only the stored previous binding after
34246
- provider confirmation, and never creates or charges another monitor. Deepline
34247
- also retries eligible incomplete deploy cleanups automatically in bounded
34248
- background passes; no separate repair command is required.
34246
+ provider confirmation, and never creates or charges another monitor. There is
34247
+ no separate repair command or retry queue: rerun the same deploy/update.
34248
+ Scheduled reconciliation can also finish that exact saved cleanup; it never
34249
+ creates another monitor.
34249
34250
  For a bounded urgent Deepline Native preview, set
34250
34251
  controls.execution_type="priority". Deepline injects the provider custom
34251
34252
  field and enforces a ten-slot per-org cap; do not use it for regular or bulk
@@ -1028,7 +1028,7 @@ var SDK_RELEASE = {
1028
1028
  // 0.3.0 introduces raw-v2: complete scrubbed provider responses are
1029
1029
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
1030
1030
  // getters keep their established compatibility behavior.
1031
- version: "0.3.30",
1031
+ version: "0.3.32",
1032
1032
  updateSummary: "New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.",
1033
1033
  contracts: {
1034
1034
  api: {
@@ -34313,9 +34313,10 @@ Notes:
34313
34313
  for a patch-style change.
34314
34314
  Repeating the exact saved definition resumes an incomplete deploy cleanup:
34315
34315
  Deepline keeps the replacement, removes only the stored previous binding after
34316
- provider confirmation, and never creates or charges another monitor. Deepline
34317
- also retries eligible incomplete deploy cleanups automatically in bounded
34318
- background passes; no separate repair command is required.
34316
+ provider confirmation, and never creates or charges another monitor. There is
34317
+ no separate repair command or retry queue: rerun the same deploy/update.
34318
+ Scheduled reconciliation can also finish that exact saved cleanup; it never
34319
+ creates another monitor.
34319
34320
  For a bounded urgent Deepline Native preview, set
34320
34321
  controls.execution_type="priority". Deepline injects the provider custom
34321
34322
  field and enforces a ten-slot per-org cap; do not use it for regular or bulk
package/dist/index.js CHANGED
@@ -778,7 +778,7 @@ var SDK_RELEASE = {
778
778
  // 0.3.0 introduces raw-v2: complete scrubbed provider responses are
779
779
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
780
780
  // getters keep their established compatibility behavior.
781
- version: "0.3.30",
781
+ version: "0.3.32",
782
782
  updateSummary: "New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.",
783
783
  contracts: {
784
784
  api: {
package/dist/index.mjs CHANGED
@@ -701,7 +701,7 @@ var SDK_RELEASE = {
701
701
  // 0.3.0 introduces raw-v2: complete scrubbed provider responses are
702
702
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
703
703
  // getters keep their established compatibility behavior.
704
- version: "0.3.30",
704
+ version: "0.3.32",
705
705
  updateSummary: "New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.",
706
706
  contracts: {
707
707
  api: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.3.30",
3
+ "version": "0.3.32",
4
4
  "description": "GTM data CLI and TypeScript SDK for coding agents",
5
5
  "license": "MIT",
6
6
  "homepage": "https://code.deepline.com",