deepline 0.1.208 → 0.1.209

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 (32) hide show
  1. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/receipts.ts +2 -12
  2. package/dist/bundling-sources/sdk/src/client.ts +10 -0
  3. package/dist/bundling-sources/sdk/src/release.ts +2 -2
  4. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +16 -31
  5. package/dist/bundling-sources/shared_libs/play-runtime/child-execution-strategy.ts +18 -0
  6. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +312 -207
  7. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +15 -0
  8. package/dist/bundling-sources/shared_libs/play-runtime/daytona-runtime-config.ts +0 -5
  9. package/dist/bundling-sources/shared_libs/play-runtime/execution-capabilities.ts +1 -0
  10. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +429 -102
  11. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +27 -5
  12. package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +18 -0
  13. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +31 -12
  14. package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +103 -0
  15. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +105 -10
  16. package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +21 -1
  17. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +32 -86
  18. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +141 -2
  19. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-session-execution.ts +45 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +180 -447
  21. package/dist/bundling-sources/shared_libs/play-runtime/runtime-constants.ts +12 -0
  22. package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +33 -1
  23. package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +11 -0
  24. package/dist/bundling-sources/shared_libs/plays/artifact-kind-guard.ts +112 -0
  25. package/dist/cli/index.js +32 -4
  26. package/dist/cli/index.mjs +32 -4
  27. package/dist/index.d.mts +10 -0
  28. package/dist/index.d.ts +10 -0
  29. package/dist/index.js +26 -4
  30. package/dist/index.mjs +26 -4
  31. package/package.json +1 -1
  32. package/dist/bundling-sources/shared_libs/play-runtime/child-run-lifecycle.ts +0 -70
@@ -2,6 +2,10 @@ import { randomUUID } from 'node:crypto';
2
2
  import { readFile } from 'node:fs/promises';
3
3
  import { gzipSync } from 'node:zlib';
4
4
  import type { PlayRunnerExecutionConfig } from '@shared_libs/play-runtime/protocol';
5
+ import {
6
+ PLAY_RUNTIME_CONTRACT,
7
+ PLAY_RUNTIME_CONTRACT_HEADER,
8
+ } from '@shared_libs/play-runtime/runtime-contract';
5
9
  import { compactPlayArtifactForRuntimeTransport } from '@shared_libs/plays/artifact-transport';
6
10
  import type {
7
11
  DaytonaExecutionContext,
@@ -16,6 +20,8 @@ type DaytonaPayloadEnvelope = {
16
20
  runnerCode: string;
17
21
  artifactBundledCode: string;
18
22
  config: PlayRunnerExecutionConfig;
23
+ /** Crash-containment epilogue script (see buildDaytonaCrashTerminalPusherSource). */
24
+ crashPusherCode: string;
19
25
  };
20
26
 
21
27
  export type StagedDaytonaPayload = {
@@ -43,9 +49,10 @@ function nodeMaterializePayloadCommand(input: {
43
49
  runnerPath: string;
44
50
  configPath: string;
45
51
  artifactCodePath: string;
52
+ crashPusherPath: string;
46
53
  }): string {
47
54
  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);";
55
+ "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);fs.writeFileSync(process.argv[5],p.crashPusherCode);";
49
56
  return [
50
57
  'node',
51
58
  '-e',
@@ -54,9 +61,133 @@ function nodeMaterializePayloadCommand(input: {
54
61
  shellQuote(input.runnerPath),
55
62
  shellQuote(input.configPath),
56
63
  shellQuote(input.artifactCodePath),
64
+ shellQuote(input.crashPusherPath),
57
65
  ].join(' ');
58
66
  }
59
67
 
68
+ /**
69
+ * Crash-containment epilogue (push execution). The parked worker no longer
70
+ * holds anything that returns when the runner process dies, so a runner that
71
+ * exits WITHOUT pushing its terminal (play code calling `process.exit`, OOM
72
+ * SIGKILL of the node process, a crash before the in-process push) would only
73
+ * be detected by the ceiling timeout. The staged shell command therefore runs
74
+ * this tiny script AFTER the runner exits, unconditionally:
75
+ *
76
+ * 1. parse the captured output for the structured `result` event (the runner
77
+ * may have printed it even when its own gateway push failed), and
78
+ * 2. push it — or a synthesized crash failure naming the exit code — to the
79
+ * gateway's `runner_terminal` action.
80
+ *
81
+ * The gateway records terminals FIRST-WRITE-WINS and absurd wake events are
82
+ * first-emit-wins, so a duplicate push after a successful in-runner push is a
83
+ * no-op; this needs no coordination with the runner.
84
+ *
85
+ * Exported (with the builder) for the unit test that executes the script
86
+ * against a real local HTTP server.
87
+ */
88
+ export function buildDaytonaCrashTerminalPusherSource(): string {
89
+ return `
90
+ const fs = require('node:fs');
91
+ const configPath = process.argv[2];
92
+ const exitCodeRaw = Number.parseInt(process.argv[3] ?? '', 10);
93
+ const outputPath = process.argv[4];
94
+ const exitCode = Number.isFinite(exitCodeRaw) ? exitCodeRaw : null;
95
+ const MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
96
+
97
+ function readResultFromOutput() {
98
+ try {
99
+ const stat = fs.statSync(outputPath);
100
+ const start = Math.max(0, stat.size - MAX_OUTPUT_BYTES);
101
+ const fd = fs.openSync(outputPath, 'r');
102
+ const buffer = Buffer.alloc(stat.size - start);
103
+ fs.readSync(fd, buffer, 0, buffer.length, start);
104
+ fs.closeSync(fd);
105
+ const lines = buffer.toString('utf-8').split(/\\r?\\n/);
106
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
107
+ const line = lines[index].trim();
108
+ if (!line.startsWith('{')) continue;
109
+ try {
110
+ const event = JSON.parse(line);
111
+ if (
112
+ event &&
113
+ event.type === 'result' &&
114
+ event.result &&
115
+ ['completed', 'failed', 'suspended'].includes(event.result.status)
116
+ ) {
117
+ return event.result;
118
+ }
119
+ } catch {}
120
+ }
121
+ } catch {}
122
+ return null;
123
+ }
124
+
125
+ async function main() {
126
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
127
+ const context = (config && config.context) || {};
128
+ const push = context.runnerPushExecution;
129
+ const gateway = String(context.receiptGatewayBaseUrl || '').replace(/\\/$/, '');
130
+ const token = context.executorToken;
131
+ if (!push || !push.runId || !gateway || !token) {
132
+ console.log('[crash-terminal] no push config; skipping');
133
+ return;
134
+ }
135
+ const result = readResultFromOutput() || {
136
+ status: 'failed',
137
+ error:
138
+ 'Daytona play runner exited with code ' +
139
+ (exitCode === null ? 'unknown' : exitCode) +
140
+ (exitCode === 137 ? ' (sandbox OOM or forced SIGKILL)' : '') +
141
+ ' without pushing a terminal (crash containment epilogue).',
142
+ logs: [],
143
+ stats: {},
144
+ steps: [],
145
+ checkpoint: null,
146
+ tableNamespace: null,
147
+ };
148
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
149
+ try {
150
+ const controller = new AbortController();
151
+ const timer = setTimeout(() => controller.abort(), 10000);
152
+ const response = await fetch(gateway + '/api/v2/plays/runtime', {
153
+ method: 'POST',
154
+ headers: {
155
+ 'content-type': 'application/json',
156
+ authorization: 'Bearer ' + token,
157
+ '${PLAY_RUNTIME_CONTRACT_HEADER}': '${String(PLAY_RUNTIME_CONTRACT)}',
158
+ },
159
+ body: JSON.stringify({
160
+ action: 'runner_terminal',
161
+ runId: push.runId,
162
+ result,
163
+ }),
164
+ signal: controller.signal,
165
+ redirect: 'manual',
166
+ });
167
+ clearTimeout(timer);
168
+ if (response.ok) {
169
+ console.log('[crash-terminal] pushed status=' + result.status);
170
+ return;
171
+ }
172
+ console.log('[crash-terminal] rejected http=' + response.status);
173
+ // Deterministic scope/fence rejections cannot succeed on retry.
174
+ if (response.status >= 400 && response.status < 500) return;
175
+ } catch (error) {
176
+ console.log(
177
+ '[crash-terminal] transport ' + (error && error.message ? error.message : String(error)),
178
+ );
179
+ }
180
+ await new Promise((resolveDelay) => setTimeout(resolveDelay, attempt * 1000));
181
+ }
182
+ console.log('[crash-terminal] exhausted retries');
183
+ }
184
+
185
+ main().catch((error) => {
186
+ console.log('[crash-terminal] fatal ' + (error && error.message ? error.message : String(error)));
187
+ });
188
+ `;
189
+ }
190
+
60
191
  function remoteRuntimeContextForDaytona(
61
192
  context: DaytonaExecutionContext,
62
193
  ): DaytonaRemoteRuntimeContext {
@@ -104,6 +235,7 @@ export async function stageDaytonaRunnerPayload(input: {
104
235
  const envelopePath = `${workDir}/deepline-play-payload-${randomUUID()}.json.gz`;
105
236
  const configPath = `${workDir}/deepline-play-config-${randomUUID()}.json`;
106
237
  const artifactCodePath = `${workDir}/deepline-play-artifact-${randomUUID()}.cjs`;
238
+ const crashPusherPath = `${workDir}/deepline-play-crash-terminal-${randomUUID()}.cjs`;
107
239
 
108
240
  const remoteMaterializedFiles: Record<string, string> = {};
109
241
  const hasInlineCsv = Boolean(input.config.csvSourceContentBase64);
@@ -198,6 +330,7 @@ export async function stageDaytonaRunnerPayload(input: {
198
330
  runnerCode: bundle,
199
331
  artifactBundledCode: compactedArtifact.bundledCode,
200
332
  config: remoteConfig,
333
+ crashPusherCode: buildDaytonaCrashTerminalPusherSource(),
201
334
  }),
202
335
  }),
203
336
  );
@@ -216,8 +349,14 @@ export async function stageDaytonaRunnerPayload(input: {
216
349
  runnerPath,
217
350
  configPath,
218
351
  artifactCodePath,
352
+ crashPusherPath,
219
353
  })} && DEEPLINE_PLAY_RUNNER_PROGRESS_EVENT_PATH=${shellQuote(progressEventPath)} DEEPLINE_PLAY_RUNNER_STDOUT_EVENT_MODE=compact node ${shellQuote(runnerPath)} ${shellQuote(configPath)}`;
220
- const command = `rm -f ${shellQuote(outputPath)} ${shellQuote(exitCodePath)} ${shellQuote(progressEventPath)} ${shellQuote(`${progressEventPath}.*`)}; ( ${runnerCommand} ) > ${shellQuote(outputPath)} 2>&1; code=$?; printf '%s' "$code" > ${shellQuote(exitCodePath)}; printf 'deepline runner output captured: %s\\n' ${shellQuote(outputPath)}; exit "$code"`;
354
+ // Crash-containment epilogue: runs UNCONDITIONALLY after the runner exits and
355
+ // pushes the parsed (or synthesized) terminal to the gateway so the parked
356
+ // worker wakes within seconds of ANY runner death — process.exit abuse, OOM
357
+ // SIGKILL, or a crash before the in-process push. Idempotent against a
358
+ // successful in-runner push (terminals are first-write-wins).
359
+ const command = `rm -f ${shellQuote(outputPath)} ${shellQuote(exitCodePath)} ${shellQuote(progressEventPath)} ${shellQuote(`${progressEventPath}.*`)}; ( ${runnerCommand} ) > ${shellQuote(outputPath)} 2>&1; code=$?; printf '%s' "$code" > ${shellQuote(exitCodePath)}; node ${shellQuote(crashPusherPath)} ${shellQuote(configPath)} "$code" ${shellQuote(outputPath)} >> ${shellQuote(outputPath)} 2>&1 || true; printf 'deepline runner output captured: %s\\n' ${shellQuote(outputPath)}; exit "$code"`;
221
360
 
222
361
  return {
223
362
  workDir: input.workDir,
@@ -0,0 +1,45 @@
1
+ import type { DaytonaSandbox } from './daytona-lifecycle';
2
+
3
+ /**
4
+ * Detached Daytona command start (push-execution B2-final).
5
+ *
6
+ * The runner command is started DETACHED via a Daytona session
7
+ * (`createSession` + `executeSessionCommand({ runAsync: true })`, which returns
8
+ * a cmdId immediately) and the worker PARKS on a `detached_runner` suspension —
9
+ * nothing holds a socket or polls for completion. The parked attempt wakes on
10
+ * the runner's pushed terminal (receipt-gateway wake event) or on the ceiling
11
+ * timeout, where `inspectDetachedDaytonaRunner` (daytona.ts) reads the session
12
+ * command's exit code and salvages the captured output file as the fallback
13
+ * verification.
14
+ */
15
+
16
+ export type DetachedDaytonaCommandStart = {
17
+ sessionId: string;
18
+ cmdId: string;
19
+ };
20
+
21
+ /**
22
+ * Start `command` detached in a fresh session. Returns the session + command id
23
+ * immediately (no await on completion).
24
+ */
25
+ export async function startDetachedDaytonaCommand(input: {
26
+ sandbox: DaytonaSandbox;
27
+ sessionId: string;
28
+ command: string;
29
+ }): Promise<DetachedDaytonaCommandStart> {
30
+ await input.sandbox.process.createSession(input.sessionId);
31
+ const response = await input.sandbox.process.executeSessionCommand(
32
+ input.sessionId,
33
+ {
34
+ command: input.command,
35
+ runAsync: true,
36
+ },
37
+ );
38
+ const cmdId = response?.cmdId;
39
+ if (!cmdId) {
40
+ throw new Error(
41
+ `Daytona executeSessionCommand did not return a cmdId for session ${input.sessionId}.`,
42
+ );
43
+ }
44
+ return { sessionId: input.sessionId, cmdId };
45
+ }