deepline 0.1.208 → 0.1.210
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.
- package/dist/bundling-sources/apps/play-runner-workers/src/coordinator-entry.ts +0 -2
- package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +102 -138
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/receipts.ts +2 -12
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +12 -4
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-receipts.ts +25 -92
- package/dist/bundling-sources/sdk/src/client.ts +10 -0
- package/dist/bundling-sources/sdk/src/play.ts +2 -2
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +16 -31
- package/dist/bundling-sources/shared_libs/play-runtime/child-execution-strategy.ts +18 -0
- package/dist/bundling-sources/shared_libs/play-runtime/child-run-id.ts +4 -5
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +316 -232
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +16 -2
- package/dist/bundling-sources/shared_libs/play-runtime/daytona-runtime-config.ts +0 -5
- package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +2 -4
- package/dist/bundling-sources/shared_libs/play-runtime/execution-capabilities.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +429 -102
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +27 -5
- package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +18 -0
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +31 -12
- package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +103 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +105 -10
- package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +21 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +32 -86
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +141 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-session-execution.ts +45 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +180 -447
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-constants.ts +12 -0
- package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +33 -1
- package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +11 -0
- package/dist/bundling-sources/shared_libs/plays/artifact-kind-guard.ts +112 -0
- package/dist/cli/index.js +33 -8
- package/dist/cli/index.mjs +33 -8
- package/dist/index.d.mts +11 -2
- package/dist/index.d.ts +11 -2
- package/dist/index.js +26 -4
- package/dist/index.mjs +26 -4
- package/package.json +1 -1
- 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
|
-
|
|
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
|
+
}
|