deepline 0.1.199 → 0.1.200

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 (72) hide show
  1. package/dist/bundling-sources/apps/play-runner-workers/src/coordinator-entry.ts +5 -1
  2. package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +51 -17
  3. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/README.md +1 -1
  4. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/harness-receipt-store.ts +16 -0
  5. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/receipts.ts +40 -9
  6. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +108 -39
  7. package/dist/bundling-sources/sdk/src/http.ts +5 -2
  8. package/dist/bundling-sources/sdk/src/play.ts +1 -1
  9. package/dist/bundling-sources/sdk/src/plays/harness-stub.ts +7 -0
  10. package/dist/bundling-sources/sdk/src/release.ts +2 -2
  11. package/dist/bundling-sources/sdk/src/worker-play-entry.ts +11 -55
  12. package/dist/bundling-sources/shared_libs/play-runtime/adaptive-tool-dispatcher.ts +355 -0
  13. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +172 -73
  14. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +1794 -377
  15. package/dist/bundling-sources/shared_libs/play-runtime/ctx-contract.ts +3 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +67 -4
  17. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +161 -39
  18. package/dist/bundling-sources/shared_libs/play-runtime/governor/adaptive-admission.ts +218 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +1 -1
  20. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +115 -25
  21. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +17 -17
  22. package/dist/bundling-sources/shared_libs/play-runtime/lease-policy.ts +22 -0
  23. package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +2 -1
  24. package/dist/bundling-sources/shared_libs/play-runtime/live-events.ts +0 -1
  25. package/dist/bundling-sources/shared_libs/play-runtime/map-memory-limits.ts +265 -0
  26. package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +135 -0
  27. package/dist/bundling-sources/shared_libs/play-runtime/play-input.ts +17 -0
  28. package/dist/bundling-sources/shared_libs/play-runtime/play-latency-trace.ts +0 -3
  29. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +10 -1
  30. package/dist/bundling-sources/shared_libs/play-runtime/providers.ts +9 -15
  31. package/dist/bundling-sources/shared_libs/play-runtime/receipt-completion-sink.ts +262 -0
  32. package/dist/bundling-sources/shared_libs/play-runtime/receipt-heartbeat-supervisor.ts +64 -0
  33. package/dist/bundling-sources/shared_libs/play-runtime/receipt-sql.ts +76 -8
  34. package/dist/bundling-sources/shared_libs/play-runtime/receipt-state-sink.ts +59 -0
  35. package/dist/bundling-sources/shared_libs/play-runtime/receipt-status.ts +3 -2
  36. package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +269 -0
  37. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +2 -3
  38. package/dist/bundling-sources/shared_libs/play-runtime/run-terminal-source.ts +0 -1
  39. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +433 -0
  40. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +227 -0
  41. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +882 -0
  42. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +573 -0
  43. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/bundle.ts +264 -0
  44. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/index.ts +33 -0
  45. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runner-events.ts +35 -0
  46. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +53 -0
  47. package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +0 -1
  48. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +459 -103
  49. package/dist/bundling-sources/shared_libs/play-runtime/runtime-constants.ts +12 -0
  50. package/dist/bundling-sources/shared_libs/play-runtime/runtime-infra-mode.ts +23 -0
  51. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-attempt-state-machine.ts +23 -2
  52. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +16 -24
  53. package/dist/bundling-sources/shared_libs/play-runtime/serial-task-queue.ts +26 -0
  54. package/dist/bundling-sources/shared_libs/play-runtime/submit-limits.ts +1 -1
  55. package/dist/bundling-sources/shared_libs/play-runtime/vercel-protection.ts +81 -0
  56. package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-state-machine.ts +54 -9
  57. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +17 -3
  58. package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +0 -20
  59. package/dist/bundling-sources/shared_libs/plays/dataset.ts +105 -4
  60. package/dist/bundling-sources/shared_libs/plays/row-identity.ts +4 -11
  61. package/dist/cli/index.js +167 -54
  62. package/dist/cli/index.mjs +167 -54
  63. package/dist/index.d.mts +1 -1
  64. package/dist/index.d.ts +1 -1
  65. package/dist/index.js +8 -8
  66. package/dist/index.mjs +8 -8
  67. package/dist/plays/bundle-play-file.mjs +15 -20
  68. package/package.json +1 -1
  69. package/dist/bundling-sources/shared_libs/play-runtime/hatchet-cold-execution-diagnosis.ts +0 -321
  70. package/dist/bundling-sources/shared_libs/play-runtime/hatchet-cold-execution-target.ts +0 -158
  71. package/dist/bundling-sources/shared_libs/temporal/constants.ts +0 -39
  72. package/dist/bundling-sources/shared_libs/temporal/preview-config.ts +0 -150
@@ -0,0 +1,882 @@
1
+ import { Daytona } from '@daytonaio/sdk';
2
+ import type {
3
+ PlayRunnerBackend,
4
+ PlayRunnerPreparedExecution,
5
+ PlayRunnerPrepareInput,
6
+ } from '../types';
7
+ import { buildPlayRunnerBundle } from '../bundle';
8
+ import {
9
+ findPlayRunnerResult,
10
+ parsePlayRunnerEventLine,
11
+ parsePlayRunnerEvents,
12
+ } from '../runner-events';
13
+ import type {
14
+ PlayRunnerEvent,
15
+ PlayRunnerExecutionConfig,
16
+ PlayRunnerRuntimeTiming,
17
+ PlayRunnerResult,
18
+ } from '@shared_libs/play-runtime/protocol';
19
+ import {
20
+ STANDARD_PLAY_RUNTIME_LIMIT_LABEL,
21
+ STANDARD_PLAY_RUNTIME_LIMIT_SECONDS,
22
+ } from '@shared_libs/play-runtime/runtime-constants';
23
+ import {
24
+ loadDaytonaRequiredConfig,
25
+ loadDaytonaRunnerPathsConfig,
26
+ } from '@shared_libs/play-runtime/daytona-runtime-config';
27
+ import {
28
+ DAYTONA_CANCELLED_ERROR,
29
+ createDaytonaSandboxCleanupManager,
30
+ createOneShotDaytonaSandboxLifecycle,
31
+ type DaytonaClient,
32
+ type DaytonaExecutionContext,
33
+ type DaytonaSandbox,
34
+ type OneShotDaytonaSandboxLifecycle,
35
+ validateDaytonaExecutionContext,
36
+ } from './daytona-lifecycle';
37
+ import { stageDaytonaRunnerPayload } from './daytona-payload-transport';
38
+
39
+ const DAYTONA_EXECUTE_TIMEOUT_SECONDS = STANDARD_PLAY_RUNTIME_LIMIT_SECONDS;
40
+ const STANDARD_WORKFLOW_RUNTIME_LIMIT_ERROR = `Based on this plan, max runtime is ${STANDARD_PLAY_RUNTIME_LIMIT_LABEL}. Use smaller batches; ask for runtime.`;
41
+ const DAYTONA_OUTPUT_TAIL_CHARS = 2_000;
42
+ const DAYTONA_COMMAND_RECOVERY_TIMEOUT_MS = 60_000;
43
+ const DAYTONA_COMMAND_RECOVERY_POLL_MS = 2_000;
44
+ const DAYTONA_PROGRESS_SIDECAR_POLL_MS = 1_000;
45
+ const DAYTONA_RUNTIME_DB_CONNECT_MAX_ATTEMPTS = 2;
46
+ const DAYTONA_INFRASTRUCTURE_MAX_ATTEMPTS = 2;
47
+ const DAYTONA_UPLOAD_MAX_ATTEMPTS = 2;
48
+ const DAYTONA_UPLOAD_ATTEMPT_DEADLINE_MS = 90_000;
49
+ const RUNTIME_POSTGRES_CONNECT_RETRY_PATTERN =
50
+ /\bRuntime Postgres\b.*\b(connection timed out|connect timeout|ETIMEDOUT|ECONNRESET|ECONNREFUSED|Connection terminated|Connection ended unexpectedly)\b/i;
51
+ const DAYTONA_INFRASTRUCTURE_RETRY_PATTERN =
52
+ /\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;
53
+
54
+ type DaytonaExecution = { exitCode: number | null; result: string };
55
+ type DaytonaUploadAttemptTiming = {
56
+ attempt: number;
57
+ sandboxId: string;
58
+ acquireElapsedMs: number;
59
+ uploadElapsedMs: number;
60
+ attemptElapsedMs: number;
61
+ totalElapsedMs: number;
62
+ };
63
+ type DaytonaPreparedExecution = PlayRunnerPreparedExecution & {
64
+ kind: 'daytona';
65
+ bundlePromise: Promise<string>;
66
+ sandboxLifecycle: OneShotDaytonaSandboxLifecycle;
67
+ };
68
+
69
+ class DaytonaUploadDeadlineBreachError extends Error {
70
+ readonly attemptTimings: DaytonaUploadAttemptTiming[];
71
+ readonly deadlineMs: number;
72
+
73
+ constructor(input: {
74
+ deadlineMs: number;
75
+ attemptTimings: DaytonaUploadAttemptTiming[];
76
+ }) {
77
+ super(
78
+ `Daytona upload deadline breached after ${input.attemptTimings.length} attempts (deadlineMs=${input.deadlineMs}). ${formatDaytonaUploadAttemptTimings(input.attemptTimings)}`,
79
+ );
80
+ this.name = 'DaytonaUploadDeadlineBreachError';
81
+ this.deadlineMs = input.deadlineMs;
82
+ this.attemptTimings = input.attemptTimings;
83
+ }
84
+ }
85
+
86
+ class DaytonaCommandTransportError extends Error {
87
+ readonly sandboxId: string;
88
+ readonly commandError: unknown;
89
+ readonly recoveryError: unknown;
90
+
91
+ constructor(input: {
92
+ sandboxId: string;
93
+ commandError: unknown;
94
+ recoveryError: unknown;
95
+ }) {
96
+ super(
97
+ `Daytona command transport failed for sandbox ${input.sandboxId}: ${formatDaytonaError(input.commandError)}. Recovery could not read the command result: ${formatDaytonaError(input.recoveryError)}.`,
98
+ { cause: input.commandError },
99
+ );
100
+ this.name = 'DaytonaCommandTransportError';
101
+ this.sandboxId = input.sandboxId;
102
+ this.commandError = input.commandError;
103
+ this.recoveryError = input.recoveryError;
104
+ }
105
+ }
106
+
107
+ export const daytonaSdkClientFactory = {
108
+ create(input: ConstructorParameters<typeof Daytona>[0]): DaytonaClient {
109
+ return new Daytona(input);
110
+ },
111
+ };
112
+
113
+ function formatDaytonaExecutionError(error: unknown): string {
114
+ const message = formatDaytonaError(error);
115
+ return isConfiguredDaytonaRuntimeLimit(error)
116
+ ? STANDARD_WORKFLOW_RUNTIME_LIMIT_ERROR
117
+ : message;
118
+ }
119
+
120
+ function formatDaytonaError(error: unknown): string {
121
+ if (!(error instanceof Error)) {
122
+ return String(error);
123
+ }
124
+ const errorCode = (error as Error & { code?: unknown }).code;
125
+ const code = typeof errorCode === 'string' ? ` code=${errorCode}` : '';
126
+ const cause =
127
+ error.cause && error.cause !== error
128
+ ? ` cause=${formatDaytonaError(error.cause)}`
129
+ : '';
130
+ const details =
131
+ error.name && error.name !== 'Error'
132
+ ? [error.name, error.message].filter(Boolean).join(': ')
133
+ : error.message || error.name || 'Error';
134
+ return `${details || 'Error'}${code}${cause}`;
135
+ }
136
+
137
+ function isConfiguredDaytonaRuntimeLimit(error: unknown): boolean {
138
+ const message = error instanceof Error ? error.message : String(error);
139
+ return new RegExp(
140
+ `(?:execution|command).{0,80}(?:timed out|timeout).{0,80}${DAYTONA_EXECUTE_TIMEOUT_SECONDS}|${DAYTONA_EXECUTE_TIMEOUT_SECONDS}.{0,80}(?:timed out|timeout)`,
141
+ 'i',
142
+ ).test(message);
143
+ }
144
+
145
+ function getDaytonaOutputTail(output: unknown): string | null {
146
+ const text = String(output ?? '').trim();
147
+ if (!text) {
148
+ return null;
149
+ }
150
+ return text.slice(-DAYTONA_OUTPUT_TAIL_CHARS);
151
+ }
152
+
153
+ function sleep(ms: number): Promise<void> {
154
+ return new Promise((resolve) => setTimeout(resolve, ms));
155
+ }
156
+
157
+ function formatDaytonaUploadAttemptTimings(
158
+ timings: readonly DaytonaUploadAttemptTiming[],
159
+ ): string {
160
+ return `attempts=[${timings
161
+ .map(
162
+ (timing) =>
163
+ `{attempt=${timing.attempt}, sandboxId=${timing.sandboxId}, acquireElapsedMs=${timing.acquireElapsedMs}, uploadElapsedMs=${timing.uploadElapsedMs}, attemptElapsedMs=${timing.attemptElapsedMs}, totalElapsedMs=${timing.totalElapsedMs}}`,
164
+ )
165
+ .join(', ')}]`;
166
+ }
167
+
168
+ function emitDaytonaStage(
169
+ _callbacks: Parameters<PlayRunnerBackend['execute']>[1],
170
+ context: DaytonaExecutionContext,
171
+ stage: string,
172
+ extra: Record<string, unknown> = {},
173
+ ): void {
174
+ const payload = {
175
+ workflowId: context.workflowId ?? null,
176
+ runId: context.runId ?? null,
177
+ playName: context.playName ?? null,
178
+ stage,
179
+ ...extra,
180
+ };
181
+ console.info('[play-runner.daytona.stage]', JSON.stringify(payload));
182
+ }
183
+
184
+ function dispatchDaytonaRunnerEvent(input: {
185
+ event: PlayRunnerEvent;
186
+ callbacks: Parameters<PlayRunnerBackend['execute']>[1];
187
+ context: DaytonaExecutionContext;
188
+ sandboxId: string;
189
+ startedAt: number;
190
+ }): void {
191
+ const { event, callbacks, context, sandboxId, startedAt } = input;
192
+ if (event.type === 'log') {
193
+ if (
194
+ event.line.startsWith('[perf]') ||
195
+ event.line.includes('Executing tool batch')
196
+ ) {
197
+ emitDaytonaStage(callbacks, context, 'runner:log', {
198
+ sandboxId,
199
+ line: event.line,
200
+ elapsedMs: Date.now() - startedAt,
201
+ });
202
+ }
203
+ callbacks?.onLog?.(event);
204
+ return;
205
+ }
206
+ if (event.type === 'checkpoint') {
207
+ callbacks?.onCheckpoint?.(event.checkpoint);
208
+ return;
209
+ }
210
+ if (event.type === 'row_update') {
211
+ callbacks?.onRowUpdate?.(event.update);
212
+ return;
213
+ }
214
+ if (event.type === 'execution_event') {
215
+ callbacks?.onExecutionEvent?.(event.event);
216
+ }
217
+ }
218
+
219
+ function createDaytonaProgressSidecarPoller(input: {
220
+ sandbox: DaytonaSandbox;
221
+ progressEventPath: string;
222
+ callbacks: Parameters<PlayRunnerBackend['execute']>[1];
223
+ context: DaytonaExecutionContext;
224
+ startedAt: number;
225
+ processedEventKeys: Set<string>;
226
+ }): { start(): void; stop(): Promise<void> } {
227
+ let timer: ReturnType<typeof setTimeout> | null = null;
228
+ let stopped = false;
229
+ let inFlight: Promise<void> = Promise.resolve();
230
+ let offsetBytes = 0;
231
+ let pendingLine = '';
232
+ let warned = false;
233
+
234
+ const processText = (text: string, flushPartial = false) => {
235
+ const combined = pendingLine + text;
236
+ const lines = combined.split(/\r?\n/);
237
+ const completeLines = flushPartial ? lines : lines.slice(0, -1);
238
+ pendingLine = flushPartial ? '' : (lines.at(-1) ?? '');
239
+ for (const rawLine of completeLines) {
240
+ const line = rawLine.trim();
241
+ if (!line) {
242
+ continue;
243
+ }
244
+ const event = parsePlayRunnerEventLine(line);
245
+ if (!event) {
246
+ continue;
247
+ }
248
+ const eventKey = JSON.stringify(event);
249
+ input.processedEventKeys.add(eventKey);
250
+ dispatchDaytonaRunnerEvent({
251
+ event,
252
+ callbacks: input.callbacks,
253
+ context: input.context,
254
+ sandboxId: input.sandbox.id,
255
+ startedAt: input.startedAt,
256
+ });
257
+ }
258
+ };
259
+
260
+ const poll = async (flushPartial = false): Promise<void> => {
261
+ try {
262
+ const file = await input.sandbox.fs.downloadFile(
263
+ input.progressEventPath,
264
+ 5,
265
+ );
266
+ const buffer = Buffer.isBuffer(file) ? file : Buffer.from(file);
267
+ if (buffer.length < offsetBytes) {
268
+ offsetBytes = 0;
269
+ pendingLine = '';
270
+ }
271
+ if (buffer.length > offsetBytes) {
272
+ const chunk = buffer.subarray(offsetBytes).toString('utf-8');
273
+ offsetBytes = buffer.length;
274
+ processText(chunk, flushPartial);
275
+ } else if (flushPartial && pendingLine.trim()) {
276
+ processText('\n', true);
277
+ }
278
+ } catch (error) {
279
+ if (!warned && !/not found|no such file|ENOENT/i.test(String(error))) {
280
+ warned = true;
281
+ console.warn('[play-runner.daytona.progress_sidecar_poll_failed]', {
282
+ sandboxId: input.sandbox.id,
283
+ progressEventPath: input.progressEventPath,
284
+ error: error instanceof Error ? error.message : String(error),
285
+ });
286
+ }
287
+ }
288
+ };
289
+
290
+ const schedule = () => {
291
+ if (stopped) {
292
+ return;
293
+ }
294
+ timer = setTimeout(() => {
295
+ inFlight = inFlight
296
+ .catch(() => {})
297
+ .then(() => poll())
298
+ .finally(schedule);
299
+ }, DAYTONA_PROGRESS_SIDECAR_POLL_MS);
300
+ timer.unref?.();
301
+ };
302
+
303
+ return {
304
+ start() {
305
+ inFlight = inFlight.then(() => poll()).finally(schedule);
306
+ },
307
+ async stop() {
308
+ stopped = true;
309
+ if (timer) {
310
+ clearTimeout(timer);
311
+ timer = null;
312
+ }
313
+ await inFlight.catch(() => {});
314
+ await poll(true);
315
+ },
316
+ };
317
+ }
318
+
319
+ async function downloadDaytonaTextFile(
320
+ sandbox: DaytonaSandbox,
321
+ remotePath: string,
322
+ ): Promise<string> {
323
+ const file = await sandbox.fs.downloadFile(remotePath, 15);
324
+ return file.toString('utf-8');
325
+ }
326
+
327
+ async function recoverDaytonaCommandExecution(input: {
328
+ sandbox: DaytonaSandbox;
329
+ outputPath: string;
330
+ exitCodePath: string;
331
+ timeoutMs?: number;
332
+ }): Promise<{ execution: DaytonaExecution | null; lastError: unknown }> {
333
+ const deadline =
334
+ Date.now() + (input.timeoutMs ?? DAYTONA_COMMAND_RECOVERY_TIMEOUT_MS);
335
+ let lastError: unknown = null;
336
+
337
+ while (Date.now() < deadline) {
338
+ try {
339
+ const exitCodeText = await downloadDaytonaTextFile(
340
+ input.sandbox,
341
+ input.exitCodePath,
342
+ );
343
+ const output = await downloadDaytonaTextFile(
344
+ input.sandbox,
345
+ input.outputPath,
346
+ );
347
+ const exitCode = Number.parseInt(exitCodeText.trim(), 10);
348
+ return {
349
+ execution: {
350
+ exitCode: Number.isFinite(exitCode) ? exitCode : null,
351
+ result: output,
352
+ },
353
+ lastError: null,
354
+ };
355
+ } catch (error) {
356
+ lastError = error;
357
+ await sleep(DAYTONA_COMMAND_RECOVERY_POLL_MS);
358
+ }
359
+ }
360
+
361
+ return { execution: null, lastError };
362
+ }
363
+
364
+ async function resolveDaytonaCommandOutput(input: {
365
+ sandbox: DaytonaSandbox;
366
+ outputPath: string;
367
+ exitCodePath: string;
368
+ executionResult: unknown;
369
+ }): Promise<string> {
370
+ const recovered = await recoverDaytonaCommandExecution({
371
+ sandbox: input.sandbox,
372
+ outputPath: input.outputPath,
373
+ exitCodePath: input.exitCodePath,
374
+ });
375
+ if (recovered.execution) {
376
+ return recovered.execution.result;
377
+ }
378
+ return String(input.executionResult ?? '');
379
+ }
380
+
381
+ function createDaytonaCancelledResult(
382
+ config: PlayRunnerExecutionConfig,
383
+ ): PlayRunnerResult {
384
+ return {
385
+ status: 'failed',
386
+ error: DAYTONA_CANCELLED_ERROR,
387
+ logs: [],
388
+ stats: {},
389
+ steps: [],
390
+ checkpoint: config.checkpoint ?? null,
391
+ tableNamespace: null,
392
+ };
393
+ }
394
+
395
+ function createDaytonaFailedResult(input: {
396
+ config: PlayRunnerExecutionConfig;
397
+ error: string;
398
+ runtimeTiming?: PlayRunnerRuntimeTiming;
399
+ }): PlayRunnerResult {
400
+ return {
401
+ status: 'failed',
402
+ error: input.error,
403
+ logs: [],
404
+ stats: {},
405
+ steps: [],
406
+ checkpoint: input.config.checkpoint ?? null,
407
+ tableNamespace: null,
408
+ ...(input.runtimeTiming ? { runtimeTiming: input.runtimeTiming } : {}),
409
+ };
410
+ }
411
+
412
+ function isRetryableRuntimePostgresConnectFailure(
413
+ result: PlayRunnerResult,
414
+ ): result is Extract<PlayRunnerResult, { status: 'failed' }> {
415
+ return (
416
+ result.status === 'failed' &&
417
+ RUNTIME_POSTGRES_CONNECT_RETRY_PATTERN.test(result.error)
418
+ );
419
+ }
420
+
421
+ function isRetryableDaytonaInfrastructureFailure(error: unknown): boolean {
422
+ if (error instanceof DaytonaCommandTransportError) {
423
+ return false;
424
+ }
425
+ const message = error instanceof Error ? error.message : String(error);
426
+ if (!message || /cancelled|aborted/i.test(message)) {
427
+ return false;
428
+ }
429
+ return DAYTONA_INFRASTRUCTURE_RETRY_PATTERN.test(message);
430
+ }
431
+
432
+ function prepareDaytonaExecution(
433
+ input: PlayRunnerPrepareInput,
434
+ callbacks: Parameters<PlayRunnerBackend['execute']>[1],
435
+ ): DaytonaPreparedExecution {
436
+ const { clientOptions: daytonaOptions } = loadDaytonaRequiredConfig();
437
+ const daytona = daytonaSdkClientFactory.create(daytonaOptions);
438
+ const bundlePromise = buildPlayRunnerBundle();
439
+ const sandboxLifecycle = createOneShotDaytonaSandboxLifecycle({
440
+ daytona,
441
+ context: input.context,
442
+ emitStage: (stage, extra) =>
443
+ emitDaytonaStage(callbacks, input.context, stage, extra),
444
+ });
445
+ return {
446
+ kind: 'daytona',
447
+ bundlePromise,
448
+ sandboxLifecycle,
449
+ dispose: sandboxLifecycle.dispose,
450
+ };
451
+ }
452
+
453
+ function isDaytonaPreparedExecution(
454
+ prepared: PlayRunnerPreparedExecution | undefined,
455
+ ): prepared is DaytonaPreparedExecution {
456
+ return prepared?.kind === 'daytona';
457
+ }
458
+
459
+ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
460
+ async prepare(input, callbacks) {
461
+ return prepareDaytonaExecution(input, callbacks);
462
+ },
463
+
464
+ async execute(config, callbacks, prepared) {
465
+ try {
466
+ validateDaytonaExecutionContext(config.context);
467
+ } catch (error) {
468
+ return {
469
+ status: 'failed',
470
+ error: error instanceof Error ? error.message : String(error),
471
+ logs: [],
472
+ stats: {},
473
+ steps: [],
474
+ checkpoint: config.checkpoint ?? null,
475
+ tableNamespace: null,
476
+ };
477
+ }
478
+ let preparedExecution: DaytonaPreparedExecution;
479
+ try {
480
+ preparedExecution = isDaytonaPreparedExecution(prepared)
481
+ ? prepared
482
+ : prepareDaytonaExecution({ context: config.context }, callbacks);
483
+ } catch (error) {
484
+ return {
485
+ status: 'failed',
486
+ error: error instanceof Error ? error.message : String(error),
487
+ logs: [],
488
+ stats: {},
489
+ steps: [],
490
+ checkpoint: config.checkpoint ?? null,
491
+ tableNamespace: null,
492
+ };
493
+ }
494
+
495
+ if (callbacks?.cancellationSignal?.aborted) {
496
+ await preparedExecution.dispose?.();
497
+ return createDaytonaCancelledResult(config);
498
+ }
499
+
500
+ const bundlePromise = preparedExecution.bundlePromise;
501
+ const sandboxLifecycle = preparedExecution.sandboxLifecycle;
502
+ const startedAt = sandboxLifecycle.startedAt;
503
+ const runtimeTiming: PlayRunnerRuntimeTiming = { backend: 'daytona' };
504
+ const sandboxCleanup = createDaytonaSandboxCleanupManager();
505
+ const withCleanup = (result: PlayRunnerResult): PlayRunnerResult => {
506
+ return sandboxCleanup.withCleanup(
507
+ {
508
+ ...result,
509
+ runtimeTiming: result.runtimeTiming ?? runtimeTiming,
510
+ },
511
+ {
512
+ cancellationCleanupStarted,
513
+ },
514
+ );
515
+ };
516
+ let cancellationRequested = false;
517
+ let cancellationCleanupStarted = false;
518
+ const uploadDeadlineBreaches: DaytonaUploadAttemptTiming[] = [];
519
+ let cancelExecution!: (error: Error) => void;
520
+ const cancellationPromise = new Promise<never>((_resolve, reject) => {
521
+ cancelExecution = reject;
522
+ });
523
+ const onCancel = () => {
524
+ cancellationRequested = true;
525
+ if (cancellationCleanupStarted) {
526
+ cancelExecution(new Error(DAYTONA_CANCELLED_ERROR));
527
+ return;
528
+ }
529
+ if (sandboxCleanup.cleanupActiveSandboxForCancellation()) {
530
+ cancellationCleanupStarted = true;
531
+ } else {
532
+ cancellationCleanupStarted = true;
533
+ void preparedExecution.dispose?.();
534
+ }
535
+ cancelExecution(new Error(DAYTONA_CANCELLED_ERROR));
536
+ };
537
+ callbacks?.cancellationSignal?.addEventListener('abort', onCancel, {
538
+ once: true,
539
+ });
540
+ const throwIfCancellationRequested = () => {
541
+ if (cancellationRequested || callbacks?.cancellationSignal?.aborted) {
542
+ onCancel();
543
+ throw new Error(DAYTONA_CANCELLED_ERROR);
544
+ }
545
+ };
546
+ let acquiredSandboxPromise = sandboxLifecycle.acquiredSandboxPromise;
547
+ try {
548
+ for (
549
+ let executionAttempt = 1;
550
+ executionAttempt <=
551
+ Math.max(
552
+ DAYTONA_RUNTIME_DB_CONNECT_MAX_ATTEMPTS,
553
+ DAYTONA_INFRASTRUCTURE_MAX_ATTEMPTS,
554
+ );
555
+ executionAttempt += 1
556
+ ) {
557
+ let sandboxForAttempt: DaytonaSandbox | null = null;
558
+ const attemptStartedAt = Date.now();
559
+ try {
560
+ const acquiredSandbox = await Promise.race([
561
+ acquiredSandboxPromise,
562
+ cancellationPromise,
563
+ ]);
564
+ sandboxCleanup.activate(acquiredSandbox);
565
+ runtimeTiming.daytonaCreateMs =
566
+ acquiredSandbox.billingStartedAt - startedAt;
567
+ const sandbox = acquiredSandbox.sandbox;
568
+ sandboxForAttempt = sandbox;
569
+ await callbacks?.onRuntimeResourceAcquired?.({
570
+ kind: 'daytona_sandbox',
571
+ sandboxId: sandbox.id,
572
+ billingStartedAt: acquiredSandbox.billingStartedAt,
573
+ cpu: typeof sandbox.cpu === 'number' ? sandbox.cpu : null,
574
+ memoryGiB:
575
+ typeof sandbox.memory === 'number' ? sandbox.memory : null,
576
+ diskGiB: typeof sandbox.disk === 'number' ? sandbox.disk : null,
577
+ });
578
+ throwIfCancellationRequested();
579
+
580
+ const { workdir: workDir } = loadDaytonaRunnerPathsConfig();
581
+ emitDaytonaStage(callbacks, config.context, 'workdir:resolved', {
582
+ sandboxId: sandbox.id,
583
+ workDir,
584
+ });
585
+
586
+ const uploadStartedAt = Date.now();
587
+ let uploadDeadlineTimer: ReturnType<typeof setTimeout> | null = null;
588
+ const uploadDeadlinePromise = new Promise<never>(
589
+ (_resolve, reject) => {
590
+ uploadDeadlineTimer = setTimeout(() => {
591
+ const now = Date.now();
592
+ const timing: DaytonaUploadAttemptTiming = {
593
+ attempt: executionAttempt,
594
+ sandboxId: sandbox.id,
595
+ acquireElapsedMs: uploadStartedAt - attemptStartedAt,
596
+ uploadElapsedMs: now - uploadStartedAt,
597
+ attemptElapsedMs: now - attemptStartedAt,
598
+ totalElapsedMs: now - startedAt,
599
+ };
600
+ uploadDeadlineBreaches.push(timing);
601
+ reject(
602
+ new DaytonaUploadDeadlineBreachError({
603
+ deadlineMs: DAYTONA_UPLOAD_ATTEMPT_DEADLINE_MS,
604
+ attemptTimings: uploadDeadlineBreaches,
605
+ }),
606
+ );
607
+ }, DAYTONA_UPLOAD_ATTEMPT_DEADLINE_MS);
608
+ },
609
+ );
610
+ let stagedPayload: Awaited<
611
+ ReturnType<typeof stageDaytonaRunnerPayload>
612
+ >;
613
+ try {
614
+ stagedPayload = await Promise.race([
615
+ stageDaytonaRunnerPayload({
616
+ sandbox,
617
+ bundlePromise,
618
+ config,
619
+ workDir,
620
+ startedAt,
621
+ emitStage: (stage, extra) =>
622
+ emitDaytonaStage(callbacks, config.context, stage, extra),
623
+ }),
624
+ uploadDeadlinePromise,
625
+ cancellationPromise,
626
+ ]);
627
+ } catch (error) {
628
+ if (error instanceof DaytonaUploadDeadlineBreachError) {
629
+ const latestTiming =
630
+ error.attemptTimings[error.attemptTimings.length - 1];
631
+ emitDaytonaStage(callbacks, config.context, 'upload:timeout', {
632
+ sandboxId: sandbox.id,
633
+ attempt: executionAttempt,
634
+ deadlineMs: DAYTONA_UPLOAD_ATTEMPT_DEADLINE_MS,
635
+ uploadElapsedMs: latestTiming?.uploadElapsedMs ?? null,
636
+ attemptElapsedMs: latestTiming?.attemptElapsedMs ?? null,
637
+ elapsedMs: Date.now() - startedAt,
638
+ });
639
+ if (executionAttempt < DAYTONA_UPLOAD_MAX_ATTEMPTS) {
640
+ sandboxCleanup.stashActiveSandboxForRetry();
641
+ emitDaytonaStage(callbacks, config.context, 'recreate:start', {
642
+ previousSandboxId: sandbox.id,
643
+ attempt: executionAttempt + 1,
644
+ reason: 'upload_deadline',
645
+ elapsedMs: Date.now() - startedAt,
646
+ });
647
+ acquiredSandboxPromise = sandboxLifecycle.createFreshSandbox();
648
+ continue;
649
+ }
650
+ }
651
+ throw error;
652
+ } finally {
653
+ if (uploadDeadlineTimer) {
654
+ clearTimeout(uploadDeadlineTimer);
655
+ }
656
+ }
657
+ runtimeTiming.daytonaUploadMs = Date.now() - uploadStartedAt;
658
+ throwIfCancellationRequested();
659
+
660
+ emitDaytonaStage(callbacks, config.context, 'execute:start', {
661
+ sandboxId: sandbox.id,
662
+ timeoutSeconds: DAYTONA_EXECUTE_TIMEOUT_SECONDS,
663
+ mode: 'command',
664
+ });
665
+ const executionStartedAt = Date.now();
666
+ const streamedEventKeys = new Set<string>();
667
+ const progressSidecar = createDaytonaProgressSidecarPoller({
668
+ sandbox,
669
+ progressEventPath: stagedPayload.progressEventPath,
670
+ callbacks,
671
+ context: config.context,
672
+ startedAt,
673
+ processedEventKeys: streamedEventKeys,
674
+ });
675
+ let execution: DaytonaExecution;
676
+ try {
677
+ progressSidecar.start();
678
+ execution = await Promise.race([
679
+ sandbox.process.executeCommand(
680
+ stagedPayload.command,
681
+ stagedPayload.workDir,
682
+ undefined,
683
+ DAYTONA_EXECUTE_TIMEOUT_SECONDS,
684
+ ),
685
+ cancellationPromise,
686
+ ]);
687
+ } catch (error) {
688
+ await progressSidecar.stop();
689
+ if (
690
+ cancellationRequested ||
691
+ callbacks?.cancellationSignal?.aborted
692
+ ) {
693
+ onCancel();
694
+ throw new Error(DAYTONA_CANCELLED_ERROR);
695
+ }
696
+ const recovered = await recoverDaytonaCommandExecution({
697
+ sandbox,
698
+ outputPath: stagedPayload.outputPath,
699
+ exitCodePath: stagedPayload.exitCodePath,
700
+ });
701
+ emitDaytonaStage(
702
+ callbacks,
703
+ config.context,
704
+ 'execute:transport_error',
705
+ {
706
+ sandboxId: sandbox.id,
707
+ operation: 'sandbox.process.executeCommand',
708
+ error: formatDaytonaError(error),
709
+ recovery: recovered.execution ? 'recovered' : 'unavailable',
710
+ recoveryError: recovered.execution
711
+ ? null
712
+ : formatDaytonaError(recovered.lastError),
713
+ elapsedMs: Date.now() - startedAt,
714
+ },
715
+ );
716
+ if (!recovered.execution) {
717
+ throw new DaytonaCommandTransportError({
718
+ sandboxId: sandbox.id,
719
+ commandError: error,
720
+ recoveryError: recovered.lastError,
721
+ });
722
+ }
723
+ emitDaytonaStage(callbacks, config.context, 'execute:recovered', {
724
+ sandboxId: sandbox.id,
725
+ error: formatDaytonaError(error),
726
+ exitCode: recovered.execution.exitCode,
727
+ outputBytes: recovered.execution.result.length,
728
+ elapsedMs: Date.now() - startedAt,
729
+ });
730
+ execution = recovered.execution;
731
+ } finally {
732
+ await progressSidecar.stop();
733
+ }
734
+ const runnerOutput = await resolveDaytonaCommandOutput({
735
+ sandbox,
736
+ outputPath: stagedPayload.outputPath,
737
+ exitCodePath: stagedPayload.exitCodePath,
738
+ executionResult: execution.result,
739
+ });
740
+ emitDaytonaStage(callbacks, config.context, 'execute:done', {
741
+ sandboxId: sandbox.id,
742
+ exitCode: execution.exitCode,
743
+ outputBytes: runnerOutput.length,
744
+ executeElapsedMs: Date.now() - executionStartedAt,
745
+ elapsedMs: Date.now() - startedAt,
746
+ });
747
+ runtimeTiming.daytonaExecuteMs = Date.now() - executionStartedAt;
748
+ const events = parsePlayRunnerEvents(runnerOutput);
749
+ for (const event of events) {
750
+ const eventKey = JSON.stringify(event);
751
+ if (streamedEventKeys.has(eventKey)) {
752
+ continue;
753
+ }
754
+ dispatchDaytonaRunnerEvent({
755
+ event,
756
+ callbacks,
757
+ context: config.context,
758
+ sandboxId: sandbox.id,
759
+ startedAt,
760
+ });
761
+ }
762
+
763
+ const result = findPlayRunnerResult(events);
764
+ if (result) {
765
+ if (
766
+ executionAttempt < DAYTONA_RUNTIME_DB_CONNECT_MAX_ATTEMPTS &&
767
+ isRetryableRuntimePostgresConnectFailure(result)
768
+ ) {
769
+ emitDaytonaStage(callbacks, config.context, 'execute:retry', {
770
+ sandboxId: sandbox.id,
771
+ attempt: executionAttempt + 1,
772
+ reason: 'runtime_postgres_connect',
773
+ error: result.error,
774
+ elapsedMs: Date.now() - startedAt,
775
+ });
776
+ sandboxCleanup.stashActiveSandboxForRetry();
777
+ acquiredSandboxPromise = sandboxLifecycle.createFreshSandbox();
778
+ continue;
779
+ }
780
+ if (result.status === 'failed') {
781
+ emitDaytonaStage(
782
+ callbacks,
783
+ config.context,
784
+ 'runner:failed_result',
785
+ {
786
+ sandboxId: sandbox.id,
787
+ exitCode: execution.exitCode,
788
+ outputBytes: runnerOutput.length,
789
+ error: result.error,
790
+ outputTail: getDaytonaOutputTail(runnerOutput),
791
+ elapsedMs: Date.now() - startedAt,
792
+ },
793
+ );
794
+ }
795
+ return withCleanup(result);
796
+ }
797
+
798
+ // executeCommand only returns the transport wrapper's captured-file
799
+ // path. The downloaded runner output contains the actual exception.
800
+ const outputTail = getDaytonaOutputTail(runnerOutput);
801
+ if (outputTail) {
802
+ emitDaytonaStage(
803
+ callbacks,
804
+ config.context,
805
+ 'runner:missing_result',
806
+ {
807
+ sandboxId: sandbox.id,
808
+ exitCode: execution.exitCode,
809
+ outputTail,
810
+ elapsedMs: Date.now() - startedAt,
811
+ },
812
+ );
813
+ }
814
+
815
+ return withCleanup(
816
+ createDaytonaFailedResult({
817
+ config,
818
+ error:
819
+ typeof execution.exitCode === 'number' &&
820
+ execution.exitCode !== 0
821
+ ? `Daytona play runner exited with code ${execution.exitCode}.${
822
+ outputTail ? ` Output tail: ${outputTail}` : ''
823
+ }`
824
+ : 'Daytona play runner produced no result.',
825
+ runtimeTiming,
826
+ }),
827
+ );
828
+ } catch (error) {
829
+ if (cancellationRequested || callbacks?.cancellationSignal?.aborted) {
830
+ onCancel();
831
+ throw new Error(DAYTONA_CANCELLED_ERROR);
832
+ }
833
+ if (
834
+ executionAttempt < DAYTONA_INFRASTRUCTURE_MAX_ATTEMPTS &&
835
+ isRetryableDaytonaInfrastructureFailure(error)
836
+ ) {
837
+ emitDaytonaStage(callbacks, config.context, 'execute:retry', {
838
+ sandboxId: sandboxForAttempt?.id ?? null,
839
+ attempt: executionAttempt + 1,
840
+ reason: 'daytona_infrastructure',
841
+ error: error instanceof Error ? error.message : String(error),
842
+ elapsedMs: Date.now() - startedAt,
843
+ });
844
+ if (sandboxForAttempt) {
845
+ sandboxCleanup.stashActiveSandboxForRetry();
846
+ }
847
+ acquiredSandboxPromise = sandboxLifecycle.createFreshSandbox();
848
+ continue;
849
+ }
850
+ throw error;
851
+ }
852
+ }
853
+
854
+ return withCleanup(
855
+ createDaytonaFailedResult({
856
+ config,
857
+ error: 'Daytona play runner exhausted retry attempts.',
858
+ runtimeTiming,
859
+ }),
860
+ );
861
+ } catch (error) {
862
+ emitDaytonaStage(callbacks, config.context, 'execute:error', {
863
+ sandboxId: sandboxCleanup.currentSandbox()?.id ?? null,
864
+ error: formatDaytonaError(error),
865
+ operation:
866
+ error instanceof DaytonaCommandTransportError
867
+ ? 'sandbox.process.executeCommand'
868
+ : 'runner_backend',
869
+ elapsedMs: Date.now() - startedAt,
870
+ });
871
+ return withCleanup(
872
+ createDaytonaFailedResult({
873
+ config,
874
+ error: formatDaytonaExecutionError(error),
875
+ runtimeTiming,
876
+ }),
877
+ );
878
+ } finally {
879
+ callbacks?.cancellationSignal?.removeEventListener('abort', onCancel);
880
+ }
881
+ },
882
+ };