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,433 @@
1
+ import type { Daytona } from '@daytonaio/sdk';
2
+ import type {
3
+ PlayRunnerDeferredRuntimeTask,
4
+ PlayRunnerExecutionConfig,
5
+ PlayRunnerResult,
6
+ } from '@shared_libs/play-runtime/protocol';
7
+ import { shouldCleanupSandboxInBackground } from '@shared_libs/play-runtime/daytona-runtime-config';
8
+
9
+ const DAYTONA_CREATE_TIMEOUT_SECONDS = 10;
10
+ const DAYTONA_CREATE_HEDGE_DELAYS_MS = [0, 200, 400, 700] as const;
11
+ const DAYTONA_CREATE_ACQUIRE_TIMEOUT_MS = 3_500;
12
+ const DAYTONA_AUTO_STOP_INTERVAL_MINUTES = 5;
13
+ const DAYTONA_AUTO_ARCHIVE_INTERVAL_MINUTES = 15;
14
+ const DAYTONA_SANDBOX_LABEL_SOURCE = 'deepline-play-runner';
15
+
16
+ export const DAYTONA_CANCELLED_ERROR = 'Daytona play runner cancelled';
17
+
18
+ export type DaytonaClient = Pick<Daytona, 'create'>;
19
+ export type DaytonaSandbox = Awaited<ReturnType<DaytonaClient['create']>>;
20
+ export type DaytonaExecutionContext = PlayRunnerExecutionConfig['context'];
21
+ export type DaytonaStageEmitter = (
22
+ stage: string,
23
+ extra?: Record<string, unknown>,
24
+ ) => void;
25
+
26
+ export type AcquiredDaytonaSandbox = {
27
+ sandbox: DaytonaSandbox;
28
+ billingStartedAt: number;
29
+ };
30
+
31
+ export type OneShotDaytonaSandboxLifecycle = {
32
+ startedAt: number;
33
+ acquiredSandboxPromise: Promise<AcquiredDaytonaSandbox>;
34
+ createFreshSandbox: () => Promise<AcquiredDaytonaSandbox>;
35
+ dispose: () => Promise<void>;
36
+ };
37
+
38
+ type HedgedDaytonaCreateResult = {
39
+ sandbox: DaytonaSandbox;
40
+ attempt: number;
41
+ attemptElapsedMs: number;
42
+ };
43
+
44
+ function normalizeLabelValue(value: string | null | undefined): string | null {
45
+ const trimmed = value?.trim();
46
+ return trimmed ? trimmed.slice(0, 63) : null;
47
+ }
48
+
49
+ function daytonaOrgIdFromContext(
50
+ context: DaytonaExecutionContext,
51
+ ): string | null {
52
+ return context.orgId?.trim() || null;
53
+ }
54
+
55
+ export function validateDaytonaExecutionContext(
56
+ context: DaytonaExecutionContext,
57
+ ): string {
58
+ const orgId = daytonaOrgIdFromContext(context);
59
+ if (!orgId) {
60
+ throw new Error(
61
+ 'Missing required org context for Daytona execution. Refusing to start a shared sandbox without orgId.',
62
+ );
63
+ }
64
+ return orgId;
65
+ }
66
+
67
+ async function createOneShotDaytonaSandbox(input: {
68
+ daytona: DaytonaClient;
69
+ orgId: string;
70
+ context: DaytonaExecutionContext;
71
+ }): Promise<DaytonaSandbox> {
72
+ const orgId = normalizeLabelValue(input.orgId);
73
+ const workflowId = normalizeLabelValue(input.context.workflowId);
74
+ const playId = normalizeLabelValue(input.context.playId);
75
+ const runId = normalizeLabelValue(input.context.runId);
76
+ const labels: {
77
+ source: string;
78
+ orgId?: string;
79
+ workflowId?: string;
80
+ playId?: string;
81
+ runId?: string;
82
+ } = {
83
+ source: DAYTONA_SANDBOX_LABEL_SOURCE,
84
+ };
85
+ if (orgId) labels.orgId = orgId;
86
+ if (workflowId) labels.workflowId = workflowId;
87
+ if (playId) labels.playId = playId;
88
+ if (runId) labels.runId = runId;
89
+
90
+ // Intentionally omit image/snapshot so Daytona uses its default fast sandbox
91
+ // image; custom images would add build/pull tax to one-shot cold starts.
92
+ return input.daytona.create(
93
+ {
94
+ labels,
95
+ ephemeral: true,
96
+ autoStopInterval: DAYTONA_AUTO_STOP_INTERVAL_MINUTES,
97
+ autoArchiveInterval: DAYTONA_AUTO_ARCHIVE_INTERVAL_MINUTES,
98
+ },
99
+ { timeout: DAYTONA_CREATE_TIMEOUT_SECONDS },
100
+ );
101
+ }
102
+
103
+ function cleanupLateDaytonaCreate(input: {
104
+ sandbox: DaytonaSandbox;
105
+ attempt: number;
106
+ emitStage: DaytonaStageEmitter;
107
+ }): void {
108
+ void input.sandbox.delete(30).then(
109
+ () =>
110
+ input.emitStage('create:late_cleanup_done', {
111
+ sandboxId: input.sandbox.id,
112
+ attempt: input.attempt,
113
+ }),
114
+ (error: unknown) => {
115
+ console.warn('[play-runner.daytona.create_late_cleanup_failed]', {
116
+ sandboxId: input.sandbox.id,
117
+ attempt: input.attempt,
118
+ error: error instanceof Error ? error.message : String(error),
119
+ });
120
+ },
121
+ );
122
+ }
123
+
124
+ async function createHedgedOneShotDaytonaSandbox(input: {
125
+ daytona: DaytonaClient;
126
+ orgId: string;
127
+ context: DaytonaExecutionContext;
128
+ emitStage: DaytonaStageEmitter;
129
+ startedAt: number;
130
+ }): Promise<HedgedDaytonaCreateResult> {
131
+ return await new Promise<HedgedDaytonaCreateResult>((resolve, reject) => {
132
+ let settled = false;
133
+ let startedAttempts = 0;
134
+ let rejectedAttempts = 0;
135
+ const errors: string[] = [];
136
+ const timers: ReturnType<typeof setTimeout>[] = [];
137
+
138
+ const clearTimers = () => {
139
+ while (timers.length > 0) {
140
+ const timer = timers.pop();
141
+ if (timer) clearTimeout(timer);
142
+ }
143
+ };
144
+
145
+ const rejectIfExhausted = () => {
146
+ if (
147
+ settled ||
148
+ rejectedAttempts !== DAYTONA_CREATE_HEDGE_DELAYS_MS.length
149
+ ) {
150
+ return;
151
+ }
152
+ settled = true;
153
+ clearTimers();
154
+ reject(
155
+ new Error(
156
+ `Daytona sandbox create failed across ${rejectedAttempts} attempts: ${errors.join('; ')}`,
157
+ ),
158
+ );
159
+ };
160
+
161
+ const startAttempt = (attempt: number) => {
162
+ if (settled) return;
163
+ startedAttempts = Math.max(startedAttempts, attempt);
164
+ if (attempt > 1) {
165
+ input.emitStage('create:retry', {
166
+ attempt: attempt - 1,
167
+ elapsedMs: Date.now() - input.startedAt,
168
+ reason: 'hedge_delay',
169
+ });
170
+ }
171
+ const attemptStartedAt = Date.now();
172
+ void createOneShotDaytonaSandbox({
173
+ daytona: input.daytona,
174
+ orgId: input.orgId,
175
+ context: input.context,
176
+ }).then(
177
+ (sandbox) => {
178
+ if (settled) {
179
+ cleanupLateDaytonaCreate({
180
+ sandbox,
181
+ attempt,
182
+ emitStage: input.emitStage,
183
+ });
184
+ return;
185
+ }
186
+ settled = true;
187
+ clearTimers();
188
+ resolve({
189
+ sandbox,
190
+ attempt,
191
+ attemptElapsedMs: Date.now() - attemptStartedAt,
192
+ });
193
+ },
194
+ (error: unknown) => {
195
+ rejectedAttempts += 1;
196
+ errors.push(error instanceof Error ? error.message : String(error));
197
+ console.warn('[play-runner.daytona.create_attempt_failed]', {
198
+ attempt,
199
+ error: error instanceof Error ? error.message : String(error),
200
+ });
201
+ rejectIfExhausted();
202
+ },
203
+ );
204
+ };
205
+
206
+ for (const [index, delayMs] of DAYTONA_CREATE_HEDGE_DELAYS_MS.entries()) {
207
+ const attempt = index + 1;
208
+ if (delayMs === 0) {
209
+ startAttempt(attempt);
210
+ } else {
211
+ timers.push(setTimeout(() => startAttempt(attempt), delayMs));
212
+ }
213
+ }
214
+
215
+ timers.push(
216
+ setTimeout(() => {
217
+ if (settled) return;
218
+ settled = true;
219
+ clearTimers();
220
+ reject(
221
+ new Error(
222
+ `Daytona sandbox create did not complete within ${DAYTONA_CREATE_ACQUIRE_TIMEOUT_MS}ms after starting ${startedAttempts} attempts.`,
223
+ ),
224
+ );
225
+ }, DAYTONA_CREATE_ACQUIRE_TIMEOUT_MS),
226
+ );
227
+ });
228
+ }
229
+
230
+ async function acquireOneShotDaytonaSandbox(input: {
231
+ daytona: DaytonaClient;
232
+ orgId: string;
233
+ context: DaytonaExecutionContext;
234
+ emitStage: DaytonaStageEmitter;
235
+ startedAt: number;
236
+ }): Promise<AcquiredDaytonaSandbox> {
237
+ input.emitStage('create:start');
238
+ const result = await createHedgedOneShotDaytonaSandbox(input);
239
+ const billingStartedAt = Date.now();
240
+ input.emitStage('create:done', {
241
+ sandboxId: result.sandbox.id,
242
+ attempt: result.attempt,
243
+ elapsedMs: billingStartedAt - input.startedAt,
244
+ attemptElapsedMs: result.attemptElapsedMs,
245
+ });
246
+ return {
247
+ sandbox: result.sandbox,
248
+ billingStartedAt,
249
+ };
250
+ }
251
+
252
+ export function createOneShotDaytonaSandboxLifecycle(input: {
253
+ daytona: DaytonaClient;
254
+ context: DaytonaExecutionContext;
255
+ emitStage: DaytonaStageEmitter;
256
+ startedAt?: number;
257
+ }): OneShotDaytonaSandboxLifecycle {
258
+ const orgId = validateDaytonaExecutionContext(input.context);
259
+ const startedAt = input.startedAt ?? Date.now();
260
+ let disposed = false;
261
+ let latestAcquiredSandboxPromise: Promise<AcquiredDaytonaSandbox>;
262
+ const createFreshSandbox = () => {
263
+ latestAcquiredSandboxPromise = acquireOneShotDaytonaSandbox({
264
+ daytona: input.daytona,
265
+ orgId,
266
+ context: input.context,
267
+ emitStage: input.emitStage,
268
+ startedAt,
269
+ });
270
+ return latestAcquiredSandboxPromise;
271
+ };
272
+ const acquiredSandboxPromise = createFreshSandbox();
273
+
274
+ return {
275
+ startedAt,
276
+ acquiredSandboxPromise,
277
+ createFreshSandbox,
278
+ dispose: async () => {
279
+ if (disposed) return;
280
+ disposed = true;
281
+ try {
282
+ const acquired = await latestAcquiredSandboxPromise;
283
+ await acquired.sandbox.delete(30);
284
+ } catch (error) {
285
+ console.warn('[play-runner.daytona.dispose_failed_before_acquire]', {
286
+ error: error instanceof Error ? error.message : String(error),
287
+ });
288
+ }
289
+ },
290
+ };
291
+ }
292
+
293
+ function attachDeferredDaytonaCleanup(
294
+ result: PlayRunnerResult,
295
+ task: PlayRunnerDeferredRuntimeTask | null,
296
+ ): PlayRunnerResult {
297
+ if (!task) {
298
+ return result;
299
+ }
300
+ return {
301
+ ...result,
302
+ deferredRuntimeTasks: [...(result.deferredRuntimeTasks ?? []), task],
303
+ };
304
+ }
305
+
306
+ export function createDaytonaSandboxCleanupManager(): {
307
+ activate(acquired: AcquiredDaytonaSandbox): void;
308
+ currentSandbox(): DaytonaSandbox | null;
309
+ cleanupActiveSandboxForCancellation(): boolean;
310
+ stashActiveSandboxForRetry(): void;
311
+ withCleanup(
312
+ result: PlayRunnerResult,
313
+ input: { cancellationCleanupStarted: boolean },
314
+ ): PlayRunnerResult;
315
+ } {
316
+ let sandbox: DaytonaSandbox | null = null;
317
+ let sandboxBillingStartedAt: number | null = null;
318
+ const deferredCleanupTasksFromPreviousAttempts: PlayRunnerDeferredRuntimeTask[] =
319
+ [];
320
+ const backgroundCleanupSandboxIds = new Set<string>();
321
+
322
+ const deferredCleanupTask = (): PlayRunnerDeferredRuntimeTask | null => {
323
+ if (!sandbox || sandboxBillingStartedAt === null) {
324
+ return null;
325
+ }
326
+ return {
327
+ kind: 'daytona_sandbox_cleanup',
328
+ sandboxId: sandbox.id,
329
+ billingStartedAt: sandboxBillingStartedAt,
330
+ cpu: typeof sandbox.cpu === 'number' ? sandbox.cpu : 1,
331
+ memoryGiB: typeof sandbox.memory === 'number' ? sandbox.memory : 1,
332
+ diskGiB: typeof sandbox.disk === 'number' ? sandbox.disk : 0,
333
+ };
334
+ };
335
+
336
+ const deleteSandboxOnce = (input: {
337
+ reason: string;
338
+ task: PlayRunnerDeferredRuntimeTask | null;
339
+ sandboxToDelete: DaytonaSandbox | null;
340
+ }) => {
341
+ if (
342
+ !input.task ||
343
+ !input.sandboxToDelete ||
344
+ backgroundCleanupSandboxIds.has(input.task.sandboxId)
345
+ ) {
346
+ return false;
347
+ }
348
+ backgroundCleanupSandboxIds.add(input.task.sandboxId);
349
+ void input.sandboxToDelete.delete(30).then(
350
+ () => {
351
+ console.info('[play-runner.daytona.cleanup_done]', {
352
+ reason: input.reason,
353
+ sandboxId: input.task!.sandboxId,
354
+ elapsedMs: Date.now() - input.task!.billingStartedAt,
355
+ });
356
+ },
357
+ (error: unknown) => {
358
+ console.warn('[play-runner.daytona.cleanup_failed]', {
359
+ reason: input.reason,
360
+ sandboxId: input.task!.sandboxId,
361
+ error: error instanceof Error ? error.message : String(error),
362
+ });
363
+ },
364
+ );
365
+ return true;
366
+ };
367
+
368
+ const scheduleBackgroundCleanup = (
369
+ task: PlayRunnerDeferredRuntimeTask | null,
370
+ sandboxToDelete: DaytonaSandbox | null,
371
+ ) => {
372
+ if (
373
+ !shouldCleanupSandboxInBackground() ||
374
+ !task ||
375
+ !sandboxToDelete ||
376
+ backgroundCleanupSandboxIds.has(task.sandboxId)
377
+ ) {
378
+ return false;
379
+ }
380
+ return deleteSandboxOnce({
381
+ reason: 'background',
382
+ task,
383
+ sandboxToDelete,
384
+ });
385
+ };
386
+
387
+ return {
388
+ activate(acquired) {
389
+ sandbox = acquired.sandbox;
390
+ sandboxBillingStartedAt = acquired.billingStartedAt;
391
+ },
392
+ currentSandbox() {
393
+ return sandbox;
394
+ },
395
+ cleanupActiveSandboxForCancellation() {
396
+ return deleteSandboxOnce({
397
+ reason: 'cancelled',
398
+ task: deferredCleanupTask(),
399
+ sandboxToDelete: sandbox,
400
+ });
401
+ },
402
+ stashActiveSandboxForRetry() {
403
+ const task = deferredCleanupTask();
404
+ const sandboxToDelete = sandbox;
405
+ if (!scheduleBackgroundCleanup(task, sandboxToDelete) && task) {
406
+ deferredCleanupTasksFromPreviousAttempts.push(task);
407
+ }
408
+ sandbox = null;
409
+ sandboxBillingStartedAt = null;
410
+ },
411
+ withCleanup(result, input) {
412
+ const resultWithPreviousCleanup =
413
+ deferredCleanupTasksFromPreviousAttempts.length === 0
414
+ ? result
415
+ : {
416
+ ...result,
417
+ deferredRuntimeTasks: [
418
+ ...deferredCleanupTasksFromPreviousAttempts,
419
+ ...(result.deferredRuntimeTasks ?? []),
420
+ ],
421
+ };
422
+ const task = deferredCleanupTask();
423
+ const sandboxToDelete = sandbox;
424
+ if (input.cancellationCleanupStarted) {
425
+ return resultWithPreviousCleanup;
426
+ }
427
+ if (scheduleBackgroundCleanup(task, sandboxToDelete)) {
428
+ return resultWithPreviousCleanup;
429
+ }
430
+ return attachDeferredDaytonaCleanup(resultWithPreviousCleanup, task);
431
+ },
432
+ };
433
+ }
@@ -0,0 +1,227 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { gzipSync } from 'node:zlib';
4
+ import type { PlayRunnerExecutionConfig } from '@shared_libs/play-runtime/protocol';
5
+ import { compactPlayArtifactForRuntimeTransport } from '@shared_libs/plays/artifact-transport';
6
+ import type {
7
+ DaytonaExecutionContext,
8
+ DaytonaSandbox,
9
+ DaytonaStageEmitter,
10
+ } from './daytona-lifecycle';
11
+
12
+ type DaytonaRemoteRuntimeContext = PlayRunnerExecutionConfig['context'];
13
+
14
+ type DaytonaPayloadEnvelope = {
15
+ schemaVersion: 1;
16
+ runnerCode: string;
17
+ artifactBundledCode: string;
18
+ config: PlayRunnerExecutionConfig;
19
+ };
20
+
21
+ export type StagedDaytonaPayload = {
22
+ workDir: string;
23
+ command: string;
24
+ outputPath: string;
25
+ exitCodePath: string;
26
+ progressEventPath: string;
27
+ };
28
+
29
+ function shellQuote(value: string): string {
30
+ return `'${value.replace(/'/g, `'\\''`)}'`;
31
+ }
32
+
33
+ function gzipUtf8(value: string): Buffer {
34
+ return gzipSync(Buffer.from(value, 'utf-8'));
35
+ }
36
+
37
+ function gzipJson(value: DaytonaPayloadEnvelope): Buffer {
38
+ return gzipUtf8(JSON.stringify(value));
39
+ }
40
+
41
+ function nodeMaterializePayloadCommand(input: {
42
+ envelopePath: string;
43
+ runnerPath: string;
44
+ configPath: string;
45
+ artifactCodePath: string;
46
+ }): string {
47
+ const script =
48
+ "const fs=require('node:fs');const zlib=require('node:zlib');const p=JSON.parse(zlib.gunzipSync(fs.readFileSync(process.argv[1])));if(p.schemaVersion!==1)throw new Error('Unsupported Daytona payload envelope');fs.writeFileSync(process.argv[2],p.runnerCode);fs.writeFileSync(process.argv[3],JSON.stringify(p.config));fs.writeFileSync(process.argv[4],p.artifactBundledCode);";
49
+ return [
50
+ 'node',
51
+ '-e',
52
+ shellQuote(script),
53
+ shellQuote(input.envelopePath),
54
+ shellQuote(input.runnerPath),
55
+ shellQuote(input.configPath),
56
+ shellQuote(input.artifactCodePath),
57
+ ].join(' ');
58
+ }
59
+
60
+ function remoteRuntimeContextForDaytona(
61
+ context: DaytonaExecutionContext,
62
+ ): DaytonaRemoteRuntimeContext {
63
+ if ((context.preloadedDbSessions?.length ?? 0) > 0) {
64
+ return {
65
+ ...context,
66
+ dbSessionStrategy: 'preloaded',
67
+ preloadedDbSessions: context.preloadedDbSessions,
68
+ };
69
+ }
70
+ return {
71
+ ...context,
72
+ dbSessionStrategy: 'sandbox_public_key',
73
+ preloadedDbSessions: undefined,
74
+ };
75
+ }
76
+
77
+ async function timedDaytonaUpload(input: {
78
+ sandbox: DaytonaSandbox;
79
+ emitStage: DaytonaStageEmitter;
80
+ label: string;
81
+ path: string;
82
+ content: Buffer;
83
+ }) {
84
+ const startedAt = Date.now();
85
+ await input.sandbox.fs.uploadFile(input.content, input.path);
86
+ input.emitStage(`upload:${input.label}:done`, {
87
+ bytes: input.content.byteLength,
88
+ elapsedMs: Date.now() - startedAt,
89
+ });
90
+ }
91
+
92
+ export async function stageDaytonaRunnerPayload(input: {
93
+ sandbox: DaytonaSandbox;
94
+ bundlePromise: Promise<string>;
95
+ config: PlayRunnerExecutionConfig;
96
+ workDir: string;
97
+ startedAt: number;
98
+ emitStage: DaytonaStageEmitter;
99
+ }): Promise<StagedDaytonaPayload> {
100
+ const workDir = input.workDir.replace(/\/$/, '');
101
+ const runnerPath = `${workDir}/deepline-play-runner-${randomUUID()}.cjs`;
102
+ const envelopePath = `${workDir}/deepline-play-payload-${randomUUID()}.json.gz`;
103
+ const configPath = `${workDir}/deepline-play-config-${randomUUID()}.json`;
104
+ const artifactCodePath = `${workDir}/deepline-play-artifact-${randomUUID()}.cjs`;
105
+
106
+ const remoteMaterializedFiles: Record<string, string> = {};
107
+ const hasInlineCsv = Boolean(input.config.csvSourceContentBase64);
108
+ input.emitStage('upload:start', {
109
+ sandboxId: input.sandbox.id,
110
+ materializedFileCount: Object.keys(input.config.materializedFiles).length,
111
+ hasCsv: Boolean(
112
+ input.config.csvSourceContentBase64 ||
113
+ input.config.csvSourcePath ||
114
+ input.config.csvSourceUrl,
115
+ ),
116
+ });
117
+
118
+ const compactedArtifact = compactPlayArtifactForRuntimeTransport(
119
+ input.config.artifact,
120
+ );
121
+ const uniqueLocalPaths = new Map<string, string>();
122
+ for (const localPath of Object.values(input.config.materializedFiles)) {
123
+ if (hasInlineCsv && localPath === input.config.csvSourcePath) {
124
+ continue;
125
+ }
126
+ if (!uniqueLocalPaths.has(localPath)) {
127
+ uniqueLocalPaths.set(localPath, `${workDir}/files/${randomUUID()}`);
128
+ }
129
+ }
130
+ if (
131
+ input.config.csvSourcePath &&
132
+ !hasInlineCsv &&
133
+ !uniqueLocalPaths.has(input.config.csvSourcePath)
134
+ ) {
135
+ uniqueLocalPaths.set(
136
+ input.config.csvSourcePath,
137
+ `${workDir}/files/${randomUUID()}`,
138
+ );
139
+ }
140
+
141
+ await Promise.all(
142
+ [...uniqueLocalPaths.entries()].map(async ([localPath, remotePath]) => {
143
+ await timedDaytonaUpload({
144
+ sandbox: input.sandbox,
145
+ emitStage: input.emitStage,
146
+ label:
147
+ localPath === input.config.csvSourcePath
148
+ ? 'csv'
149
+ : 'materialized_file',
150
+ path: remotePath,
151
+ content: await readFile(localPath),
152
+ });
153
+ }),
154
+ );
155
+
156
+ for (const [logicalPath, localPath] of Object.entries(
157
+ input.config.materializedFiles,
158
+ )) {
159
+ const remotePath = uniqueLocalPaths.get(localPath);
160
+ if (remotePath) {
161
+ remoteMaterializedFiles[logicalPath] = remotePath;
162
+ }
163
+ }
164
+
165
+ const remoteConfig: PlayRunnerExecutionConfig = {
166
+ ...input.config,
167
+ artifact: {
168
+ ...compactedArtifact,
169
+ bundledCode: '',
170
+ },
171
+ artifactTransport: {
172
+ bundledCodePath: artifactCodePath,
173
+ bundledCodeEncoding: 'utf8',
174
+ },
175
+ workspaceRoot: input.workDir,
176
+ csvSourcePath: hasInlineCsv
177
+ ? null
178
+ : input.config.csvSourcePath
179
+ ? (uniqueLocalPaths.get(input.config.csvSourcePath) ??
180
+ input.config.csvSourcePath)
181
+ : null,
182
+ csvSourceUrl: input.config.csvSourceUrl ?? null,
183
+ csvSourceContentBase64: input.config.csvSourceContentBase64 ?? null,
184
+ materializedFiles: remoteMaterializedFiles,
185
+ context: remoteRuntimeContextForDaytona(input.config.context),
186
+ };
187
+
188
+ const envelopeUpload = input.bundlePromise.then((bundle) =>
189
+ timedDaytonaUpload({
190
+ sandbox: input.sandbox,
191
+ emitStage: input.emitStage,
192
+ label: 'payload',
193
+ path: envelopePath,
194
+ content: gzipJson({
195
+ schemaVersion: 1,
196
+ runnerCode: bundle,
197
+ artifactBundledCode: compactedArtifact.bundledCode,
198
+ config: remoteConfig,
199
+ }),
200
+ }),
201
+ );
202
+ await envelopeUpload;
203
+ input.emitStage('upload:done', {
204
+ sandboxId: input.sandbox.id,
205
+ uploadedFileCount: uniqueLocalPaths.size + 1,
206
+ elapsedMs: Date.now() - input.startedAt,
207
+ });
208
+
209
+ const outputPath = `${workDir}/deepline-play-output-${randomUUID()}.log`;
210
+ const exitCodePath = `${workDir}/deepline-play-exit-${randomUUID()}.txt`;
211
+ const progressEventPath = `${workDir}/deepline-play-progress-${randomUUID()}.jsonl`;
212
+ const runnerCommand = `${nodeMaterializePayloadCommand({
213
+ envelopePath,
214
+ runnerPath,
215
+ configPath,
216
+ artifactCodePath,
217
+ })} && DEEPLINE_PLAY_RUNNER_PROGRESS_EVENT_PATH=${shellQuote(progressEventPath)} DEEPLINE_PLAY_RUNNER_STDOUT_EVENT_MODE=compact node ${shellQuote(runnerPath)} ${shellQuote(configPath)}`;
218
+ const command = `rm -f ${shellQuote(outputPath)} ${shellQuote(exitCodePath)} ${shellQuote(progressEventPath)}; ( ${runnerCommand} ) > ${shellQuote(outputPath)} 2>&1; code=$?; printf '%s' "$code" > ${shellQuote(exitCodePath)}; printf 'deepline runner output captured: %s\\n' ${shellQuote(outputPath)}; exit "$code"`;
219
+
220
+ return {
221
+ workDir: input.workDir,
222
+ command,
223
+ outputPath,
224
+ exitCodePath,
225
+ progressEventPath,
226
+ };
227
+ }