deepline 0.1.238 → 0.1.240

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.
@@ -1,6 +1,8 @@
1
1
  import type { DaytonaSandbox } from './daytona-lifecycle';
2
2
 
3
3
  export type DetachedDaytonaStartupDiagnostic = {
4
+ readyMarkerObserved: boolean;
5
+ startAcknowledgementAttempted: boolean;
4
6
  commandFound: boolean;
5
7
  exitCode: number | null;
6
8
  sessionFound: boolean;
@@ -51,6 +53,8 @@ export async function inspectDetachedDaytonaStartup(input: {
51
53
  }): Promise<DetachedDaytonaStartupDiagnostic> {
52
54
  const errors: string[] = [];
53
55
  const diagnostic: DetachedDaytonaStartupDiagnostic = {
56
+ readyMarkerObserved: false,
57
+ startAcknowledgementAttempted: false,
54
58
  commandFound: false,
55
59
  exitCode: null,
56
60
  sessionFound: false,
@@ -112,11 +116,17 @@ export type DetachedDaytonaCommandStart = {
112
116
  cmdId: string;
113
117
  };
114
118
 
115
- const DAYTONA_RUNNER_READY_TIMEOUT_MS = 3_000;
119
+ // The marker now proves artifact materialization plus an accepted gateway
120
+ // heartbeat. Keep the handoff bounded, while allowing a cold Fly gateway and
121
+ // one timed-out heartbeat request to recover before the worker rejects it.
122
+ export const DAYTONA_RUNNER_READY_TIMEOUT_MS = 60_000;
116
123
  const DAYTONA_RUNNER_READY_POLL_MS = 250;
117
124
 
118
125
  export class DaytonaRunnerInitializationError extends Error {
119
- constructor(message: string) {
126
+ constructor(
127
+ message: string,
128
+ readonly startupDiagnostic: DetachedDaytonaStartupDiagnostic,
129
+ ) {
120
130
  super(message);
121
131
  this.name = 'DaytonaRunnerInitializationError';
122
132
  }
@@ -150,15 +160,17 @@ export async function startDetachedDaytonaCommand(input: {
150
160
 
151
161
  /**
152
162
  * A detached command id only proves that Daytona accepted the request. Before
153
- * the worker parks, require a marker written by the initialized runner process.
154
- * This closes the unobservable handoff where a sandbox or command disappears
155
- * after executeSessionCommand but before the first gateway heartbeat.
163
+ * the worker parks, require a marker written only after the initialized runner
164
+ * materializes its artifact and the receipt gateway accepts its first
165
+ * heartbeat. This closes the unobservable handoff where a sandbox or command
166
+ * disappears after executeSessionCommand but before durable runner liveness.
156
167
  */
157
168
  export async function confirmDetachedDaytonaRunnerReady(input: {
158
169
  sandbox: DaytonaSandbox;
159
170
  sessionId: string;
160
171
  cmdId: string;
161
172
  readyPath: string;
173
+ startAckPath: string;
162
174
  exitCodePath: string;
163
175
  timeoutMs?: number;
164
176
  pollMs?: number;
@@ -176,6 +188,8 @@ export async function confirmDetachedDaytonaRunnerReady(input: {
176
188
  const pollMs = Math.max(1, input.pollMs ?? DAYTONA_RUNNER_READY_POLL_MS);
177
189
  const deadline = now() + timeoutMs;
178
190
  let markerDiagnosis = 'ready_marker_unavailable';
191
+ let readyMarkerObserved = false;
192
+ let startAcknowledgementAttempted = false;
179
193
 
180
194
  while (now() < deadline) {
181
195
  try {
@@ -183,8 +197,21 @@ export async function confirmDetachedDaytonaRunnerReady(input: {
183
197
  const parsed = JSON.parse(marker.toString('utf-8')) as {
184
198
  ready?: unknown;
185
199
  };
186
- if (parsed.ready === true) return;
187
- markerDiagnosis = 'ready_marker_invalid';
200
+ if (parsed.ready === true) {
201
+ readyMarkerObserved = true;
202
+ startAcknowledgementAttempted = true;
203
+ try {
204
+ await input.sandbox.fs.uploadFile(
205
+ Buffer.from('{"start":true}', 'utf-8'),
206
+ input.startAckPath,
207
+ );
208
+ return;
209
+ } catch {
210
+ markerDiagnosis = 'start_ack_unavailable';
211
+ }
212
+ } else {
213
+ markerDiagnosis = 'ready_marker_invalid';
214
+ }
188
215
  } catch {
189
216
  markerDiagnosis = 'ready_marker_unavailable';
190
217
  }
@@ -197,9 +224,12 @@ export async function confirmDetachedDaytonaRunnerReady(input: {
197
224
  cmdId: input.cmdId,
198
225
  exitCodePath: input.exitCodePath,
199
226
  });
227
+ diagnostic.readyMarkerObserved = readyMarkerObserved;
228
+ diagnostic.startAcknowledgementAttempted = startAcknowledgementAttempted;
200
229
  const oomIndicated =
201
230
  diagnostic.exitCode === 137 || diagnostic.exitCodeFile === 137;
202
231
  throw new DaytonaRunnerInitializationError(
203
- `RUNTIME_SANDBOX_START_FAILED: Daytona accepted detached command ${input.cmdId} in sandbox ${input.sandbox.id}, but the play runner did not initialize within ${timeoutMs}ms. Marker diagnosis: ${markerDiagnosis}. Daytona diagnosis: ${JSON.stringify(diagnostic)}. OOM is ${oomIndicated ? 'indicated by exit code 137' : 'not confirmed by the available Daytona evidence'}. The play was stopped and was not retried.`,
232
+ `RUNTIME_SANDBOX_START_FAILED: Daytona accepted detached command ${input.cmdId} in sandbox ${input.sandbox.id}, but the play runner did not materialize and establish receipt-gateway liveness within ${timeoutMs}ms. Marker diagnosis: ${markerDiagnosis}. Daytona diagnosis: ${JSON.stringify(diagnostic)}. OOM is ${oomIndicated ? 'indicated by exit code 137' : 'not confirmed by the available Daytona evidence'}. The worker will not park this runner; the failed runner was stopped and was not retried in place.`,
233
+ diagnostic,
204
234
  );
205
235
  }
@@ -474,6 +474,27 @@ function isRetryableDaytonaInfrastructureFailure(error: unknown): boolean {
474
474
  return DAYTONA_INFRASTRUCTURE_RETRY_PATTERN.test(message);
475
475
  }
476
476
 
477
+ function isRetryableUnstartedDaytonaRunner(
478
+ error: unknown,
479
+ ): error is DaytonaRunnerInitializationError {
480
+ if (!(error instanceof DaytonaRunnerInitializationError)) return false;
481
+ const diagnostic = error.startupDiagnostic;
482
+ // The ready fence runs before any customer play code. If Daytona accepted
483
+ // the command but the worker never observed its ready marker, the runner
484
+ // cannot pass the reverse start-ack fence and customer code cannot begin.
485
+ // Terminate that sandbox and retry once on a fresh one even when its Node
486
+ // process is still alive. Do not retry after a start acknowledgement was
487
+ // attempted (its write outcome may be ambiguous), or after a runner exit.
488
+ return (
489
+ diagnostic.commandFound &&
490
+ diagnostic.sessionFound &&
491
+ diagnostic.exitCode === null &&
492
+ diagnostic.exitCodeFile === null &&
493
+ !diagnostic.readyMarkerObserved &&
494
+ !diagnostic.startAcknowledgementAttempted
495
+ );
496
+ }
497
+
477
498
  function prepareDaytonaExecution(
478
499
  input: PlayRunnerPrepareInput,
479
500
  callbacks: Parameters<PlayRunnerBackend['execute']>[1],
@@ -785,11 +806,11 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
785
806
  });
786
807
  // Push execution (B2-final, park-and-wake): start the runner command
787
808
  // DETACHED via a Daytona session and return a `detached_runner`
788
- // suspension after the runner writes its one-time ready marker. The
789
- // marker is the handoff proof: a Daytona cmdId alone does not prove
790
- // the sandbox process initialized. After that bounded startup check,
791
- // the worker parks (releasing its claim and slot) and wakes only on
792
- // the runner's pushed terminal or the ceiling timeout.
809
+ // suspension only after the runner writes its one-time ready marker.
810
+ // The marker proves artifact materialization and an accepted gateway
811
+ // heartbeat: a Daytona cmdId alone does not prove durable liveness.
812
+ // After that bounded startup check, the worker parks (releasing its
813
+ // claim and slot) and wakes on terminal push or the ceiling timeout.
793
814
  const sessionId = `deepline-play-${randomUUID()}`;
794
815
  let start: Awaited<ReturnType<typeof startDetachedDaytonaCommand>>;
795
816
  try {
@@ -819,6 +840,7 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
819
840
  sessionId: start.sessionId,
820
841
  cmdId: start.cmdId,
821
842
  readyPath: stagedPayload.readyPath,
843
+ startAckPath: stagedPayload.startAckPath,
822
844
  exitCodePath: stagedPayload.exitCodePath,
823
845
  }),
824
846
  cancellationPromise,
@@ -868,6 +890,25 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
868
890
  onCancel();
869
891
  throw new Error(DAYTONA_CANCELLED_ERROR);
870
892
  }
893
+ if (
894
+ executionAttempt < DAYTONA_RUNTIME_INITIALIZATION_MAX_ATTEMPTS &&
895
+ isRetryableUnstartedDaytonaRunner(error)
896
+ ) {
897
+ emitDaytonaStage(callbacks, config.context, 'execute:retry', {
898
+ sandboxId: sandboxForAttempt?.id ?? null,
899
+ attempt: executionAttempt + 1,
900
+ reason: 'runner_startup_not_live',
901
+ error: error.message,
902
+ elapsedMs: Date.now() - startedAt,
903
+ });
904
+ if (sandboxForAttempt) {
905
+ activeAcquiredResource!.billingEndedAt = Date.now();
906
+ await reportRetiringRuntimeResource(activeAcquiredResource!);
907
+ sandboxCleanup.stashActiveSandboxForRetry();
908
+ }
909
+ acquiredSandboxPromise = sandboxLifecycle.createFreshSandbox();
910
+ continue;
911
+ }
871
912
  if (
872
913
  executionAttempt < DAYTONA_INFRASTRUCTURE_MAX_ATTEMPTS &&
873
914
  isRetryableDaytonaInfrastructureFailure(error)
@@ -2,7 +2,11 @@ export const SECRET_REDACTION_PLACEHOLDER = '[REDACTED_SECRET]';
2
2
 
3
3
  const BEARER_TOKEN_RE = /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}\b/g;
4
4
  const BASIC_CREDENTIAL_RE = /\bBasic\s+[A-Za-z0-9+/]{4,}={0,2}\b/g;
5
- const JWT_RE = /\b[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{1,}\.[A-Za-z0-9_-]{8,}\b/g;
5
+ // Bound each segment so V8 cannot recursively backtrack across a multi-megabyte
6
+ // opaque receipt value that contains no dots. Real executor/provider JWT
7
+ // segments are far smaller; 16 KiB per segment still leaves ample headroom.
8
+ const JWT_RE =
9
+ /\b[A-Za-z0-9_-]{8,16384}\.[A-Za-z0-9_-]{1,16384}\.[A-Za-z0-9_-]{8,16384}\b/g;
6
10
  const PRIVATE_KEY_RE =
7
11
  /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g;
8
12
  const SENSITIVE_FIELD_NAME =