deepline 0.1.211 → 0.1.213
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/entry.ts +317 -340
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/customer-console.ts +23 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +1 -1
- package/dist/bundling-sources/sdk/src/client.ts +118 -3
- package/dist/bundling-sources/sdk/src/index.ts +2 -0
- package/dist/bundling-sources/sdk/src/play.ts +8 -1
- package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +1 -1
- package/dist/bundling-sources/sdk/src/plays/harness-stub.ts +11 -1
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/sdk/src/types.ts +47 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +302 -30
- package/dist/bundling-sources/shared_libs/play-runtime/child-execution-placement.ts +14 -0
- package/dist/bundling-sources/shared_libs/play-runtime/child-execution-strategy.ts +57 -23
- package/dist/bundling-sources/shared_libs/play-runtime/completed-receipt-cache.ts +166 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +164 -121
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-contract.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +42 -1
- package/dist/bundling-sources/shared_libs/play-runtime/dataset-id.ts +10 -4
- package/dist/bundling-sources/shared_libs/play-runtime/db-session.ts +9 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +163 -7
- package/dist/bundling-sources/shared_libs/play-runtime/gateway-auth-session.ts +93 -0
- package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +14 -2
- package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +4 -2
- package/dist/bundling-sources/shared_libs/play-runtime/play-call-execution.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-completion-sink.ts +16 -1
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-sql.ts +21 -1
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-state-sink.ts +10 -0
- package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +80 -3
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +128 -6
- package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-session-execution.ts +94 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +91 -6
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +19 -10
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +540 -92
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-postgres-admission.ts +162 -0
- package/dist/bundling-sources/shared_libs/play-runtime/secret-capability.ts +18 -4
- package/dist/bundling-sources/shared_libs/play-runtime/sheet-attempt-sql.ts +16 -17
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +29 -9
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-state-machine.ts +22 -1
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +1 -0
- package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +79 -1
- package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +2 -4
- package/dist/cli/index.js +637 -229
- package/dist/cli/index.mjs +637 -229
- package/dist/{compiler-manifest-j6z8qzpd.d.mts → compiler-manifest-BhgZ23A4.d.mts} +1 -0
- package/dist/{compiler-manifest-j6z8qzpd.d.ts → compiler-manifest-BhgZ23A4.d.ts} +1 -0
- package/dist/index.d.mts +84 -11
- package/dist/index.d.ts +84 -11
- package/dist/index.js +225 -43
- package/dist/index.mjs +225 -43
- package/dist/plays/bundle-play-file.d.mts +2 -2
- package/dist/plays/bundle-play-file.d.ts +2 -2
- package/dist/plays/bundle-play-file.mjs +65 -4
- package/package.json +1 -1
|
@@ -30,7 +30,10 @@ export type RuntimeResourceSnapshot = {
|
|
|
30
30
|
};
|
|
31
31
|
sheetFlush: {
|
|
32
32
|
acquired: number;
|
|
33
|
+
active: number;
|
|
34
|
+
waiting: number;
|
|
33
35
|
bytes: number;
|
|
36
|
+
admissionWaitMsEwma: number | null;
|
|
34
37
|
latencyMsEwma: number | null;
|
|
35
38
|
};
|
|
36
39
|
providers: Record<
|
|
@@ -90,8 +93,74 @@ function createEwma(alpha = 0.2): Ewma {
|
|
|
90
93
|
};
|
|
91
94
|
}
|
|
92
95
|
|
|
93
|
-
|
|
94
|
-
|
|
96
|
+
// Sheet persistence has its own local admission lane. Receipt lifecycle calls
|
|
97
|
+
// are admitted by the receipt sink/gateway and must not queue behind a large
|
|
98
|
+
// Runtime Sheet flush in the runner. Two flushes keep the database busy while
|
|
99
|
+
// bounding resident payloads and gateway/pool pressure (ADR 0012).
|
|
100
|
+
const MAX_CONCURRENT_SHEET_FLUSHES = 2;
|
|
101
|
+
|
|
102
|
+
type SheetFlushWaiter = {
|
|
103
|
+
resolve: (lease: RuntimeResourceLease) => void;
|
|
104
|
+
reject: (error: unknown) => void;
|
|
105
|
+
signal?: AbortSignal;
|
|
106
|
+
onAbort?: () => void;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
class SheetFlushAdmission {
|
|
110
|
+
#active = 0;
|
|
111
|
+
#waiters: SheetFlushWaiter[] = [];
|
|
112
|
+
|
|
113
|
+
get active(): number {
|
|
114
|
+
return this.#active;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
get waiting(): number {
|
|
118
|
+
return this.#waiters.length;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
acquire(signal?: AbortSignal): Promise<RuntimeResourceLease> {
|
|
122
|
+
if (signal?.aborted) {
|
|
123
|
+
return Promise.reject(signal.reason ?? new Error('Sheet flush aborted'));
|
|
124
|
+
}
|
|
125
|
+
if (this.#active < MAX_CONCURRENT_SHEET_FLUSHES) {
|
|
126
|
+
this.#active += 1;
|
|
127
|
+
return Promise.resolve(this.#lease());
|
|
128
|
+
}
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
const waiter: SheetFlushWaiter = {
|
|
131
|
+
resolve,
|
|
132
|
+
reject,
|
|
133
|
+
...(signal ? { signal } : {}),
|
|
134
|
+
};
|
|
135
|
+
if (signal) {
|
|
136
|
+
waiter.onAbort = () => {
|
|
137
|
+
const index = this.#waiters.indexOf(waiter);
|
|
138
|
+
if (index >= 0) this.#waiters.splice(index, 1);
|
|
139
|
+
reject(signal.reason ?? new Error('Sheet flush aborted'));
|
|
140
|
+
};
|
|
141
|
+
signal.addEventListener('abort', waiter.onAbort, { once: true });
|
|
142
|
+
}
|
|
143
|
+
this.#waiters.push(waiter);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#lease(): RuntimeResourceLease {
|
|
148
|
+
let released = false;
|
|
149
|
+
return {
|
|
150
|
+
release: () => {
|
|
151
|
+
if (released) return;
|
|
152
|
+
released = true;
|
|
153
|
+
while (this.#waiters.length > 0) {
|
|
154
|
+
const waiter = this.#waiters.shift()!;
|
|
155
|
+
waiter.signal?.removeEventListener('abort', waiter.onAbort!);
|
|
156
|
+
if (waiter.signal?.aborted) continue;
|
|
157
|
+
waiter.resolve(this.#lease());
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
this.#active = Math.max(0, this.#active - 1);
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
95
164
|
}
|
|
96
165
|
|
|
97
166
|
function elapsedSince(startedAt: number): number {
|
|
@@ -109,6 +178,8 @@ export function createRuntimeResourceGovernor(input: {
|
|
|
109
178
|
const rowAdmissionWait = createEwma();
|
|
110
179
|
const toolAdmissionWait = createEwma();
|
|
111
180
|
const sheetFlushLatency = createEwma();
|
|
181
|
+
const sheetFlushAdmissionWait = createEwma();
|
|
182
|
+
const sheetFlushAdmission = new SheetFlushAdmission();
|
|
112
183
|
const providers = new Map<
|
|
113
184
|
string,
|
|
114
185
|
{
|
|
@@ -219,11 +290,14 @@ export function createRuntimeResourceGovernor(input: {
|
|
|
219
290
|
},
|
|
220
291
|
|
|
221
292
|
async acquireSheetFlush(sheetInput) {
|
|
293
|
+
const startedAt = Date.now();
|
|
294
|
+
const lease = await sheetFlushAdmission.acquire(sheetInput?.signal);
|
|
222
295
|
sheetFlushCounters.acquired += 1;
|
|
296
|
+
sheetFlushAdmissionWait.observe(elapsedSince(startedAt));
|
|
223
297
|
observe({
|
|
224
298
|
sheetFlushBytes: sheetInput?.estimatedBytes ?? null,
|
|
225
299
|
});
|
|
226
|
-
return
|
|
300
|
+
return lease;
|
|
227
301
|
},
|
|
228
302
|
|
|
229
303
|
suggestedToolParallelism(toolId, fallback) {
|
|
@@ -258,7 +332,10 @@ export function createRuntimeResourceGovernor(input: {
|
|
|
258
332
|
},
|
|
259
333
|
sheetFlush: {
|
|
260
334
|
acquired: sheetFlushCounters.acquired,
|
|
335
|
+
active: sheetFlushAdmission.active,
|
|
336
|
+
waiting: sheetFlushAdmission.waiting,
|
|
261
337
|
bytes: sheetFlushCounters.bytes,
|
|
338
|
+
admissionWaitMsEwma: sheetFlushAdmissionWait.value,
|
|
262
339
|
latencyMsEwma: sheetFlushLatency.value,
|
|
263
340
|
},
|
|
264
341
|
providers: Object.fromEntries(
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
nextPlayRunLedgerTerminalSource,
|
|
8
8
|
normalizePlayRunLedgerTerminalSource,
|
|
9
9
|
} from './run-terminal-source';
|
|
10
|
+
import { MAX_LEDGER_LOG_LINES_PER_EVENT } from './ledger-safe-payload';
|
|
10
11
|
|
|
11
12
|
export type PlayRunLedgerStatus =
|
|
12
13
|
| 'queued'
|
|
@@ -84,6 +85,18 @@ export type PlayRunLedgerStepSnapshot = {
|
|
|
84
85
|
progress?: PlayRunLedgerStepProgress | null;
|
|
85
86
|
};
|
|
86
87
|
|
|
88
|
+
export type PlayRunLedgerDatasetSnapshot = {
|
|
89
|
+
datasetId: string;
|
|
90
|
+
path: string;
|
|
91
|
+
tableNamespace: string;
|
|
92
|
+
phase: 'registered' | 'available' | 'failed';
|
|
93
|
+
persistedRows: number;
|
|
94
|
+
succeededRows: number;
|
|
95
|
+
failedRows: number;
|
|
96
|
+
complete: boolean;
|
|
97
|
+
updatedAt: number;
|
|
98
|
+
};
|
|
99
|
+
|
|
87
100
|
export type PlayRunLedgerSnapshot = {
|
|
88
101
|
runId: string;
|
|
89
102
|
playName?: string | null;
|
|
@@ -96,6 +109,8 @@ export type PlayRunLedgerSnapshot = {
|
|
|
96
109
|
durationMs?: number | null;
|
|
97
110
|
orderedStepIds: string[];
|
|
98
111
|
stepsById: Record<string, PlayRunLedgerStepSnapshot>;
|
|
112
|
+
orderedDatasetIds: string[];
|
|
113
|
+
datasetsById: Record<string, PlayRunLedgerDatasetSnapshot>;
|
|
99
114
|
/**
|
|
100
115
|
* Bounded tail of the run's log lines (last {@link LOG_TAIL_LIMIT}).
|
|
101
116
|
* Full retention lives in the Run Log Stream (Convex `playRunLogChunks`);
|
|
@@ -217,10 +232,34 @@ export type PlayRunLedgerEvent =
|
|
|
217
232
|
* repeated identical lines while absorbing redundant re-sends.
|
|
218
233
|
*/
|
|
219
234
|
channelOffset?: number;
|
|
235
|
+
/** Scheduler attempt that owns this positional log channel. */
|
|
236
|
+
producerAttempt?: number;
|
|
237
|
+
/**
|
|
238
|
+
* Terminal transport replay normally matches ctx.log lines after
|
|
239
|
+
* removing timestamps. Detached-attempt recovery uses exact matching so
|
|
240
|
+
* the same message from a later attempt remains a distinct occurrence.
|
|
241
|
+
*/
|
|
242
|
+
transportDedupe?: 'semantic' | 'exact';
|
|
220
243
|
/** Set by ingestion when this append crossed the retention cap. */
|
|
221
244
|
logsTruncated?: boolean;
|
|
222
245
|
})
|
|
223
246
|
| (PlayRunLedgerBaseEvent & {
|
|
247
|
+
type: 'dataset.lifecycle';
|
|
248
|
+
datasetId: string;
|
|
249
|
+
path: string;
|
|
250
|
+
tableNamespace: string;
|
|
251
|
+
phase: 'registered' | 'available' | 'failed';
|
|
252
|
+
persistedRows?: number;
|
|
253
|
+
succeededRows?: number;
|
|
254
|
+
failedRows?: number;
|
|
255
|
+
complete?: boolean;
|
|
256
|
+
})
|
|
257
|
+
| (PlayRunLedgerBaseEvent & {
|
|
258
|
+
/**
|
|
259
|
+
* @deprecated Mixed-version deploy seam only. Owner: Play Runtime.
|
|
260
|
+
* Remove after 2026-07-19, once pre-dataset-lifecycle workers have
|
|
261
|
+
* exceeded the maximum run duration plus the seven-day drain window.
|
|
262
|
+
*/
|
|
224
263
|
type: 'sheet.summary.updated';
|
|
225
264
|
tableNamespace: string;
|
|
226
265
|
deltaCursor?: number;
|
|
@@ -241,6 +280,7 @@ export type PlayRunLedgerStatusPatch = {
|
|
|
241
280
|
* (see `slicePositionalLogLines`), forwarded onto the `log.appended` event.
|
|
242
281
|
*/
|
|
243
282
|
liveLogsChannelOffset?: number | null;
|
|
283
|
+
liveLogsProducerAttempt?: number | null;
|
|
244
284
|
liveNodeProgress?: unknown;
|
|
245
285
|
result?: unknown;
|
|
246
286
|
};
|
|
@@ -401,6 +441,8 @@ export function createEmptyPlayRunLedgerSnapshot(input: {
|
|
|
401
441
|
: null,
|
|
402
442
|
orderedStepIds: [],
|
|
403
443
|
stepsById: {},
|
|
444
|
+
orderedDatasetIds: [],
|
|
445
|
+
datasetsById: {},
|
|
404
446
|
logTail: [],
|
|
405
447
|
totalLogCount: 0,
|
|
406
448
|
logsTruncated: false,
|
|
@@ -447,6 +489,26 @@ export function normalizePlayRunLedgerSnapshot(
|
|
|
447
489
|
progress: rawProgress ? normalizeStepProgress(rawProgress) : null,
|
|
448
490
|
};
|
|
449
491
|
}
|
|
492
|
+
const rawDatasets = isRecord(value.datasetsById) ? value.datasetsById : {};
|
|
493
|
+
const datasetsById: Record<string, PlayRunLedgerDatasetSnapshot> = {};
|
|
494
|
+
for (const [datasetId, rawDataset] of Object.entries(rawDatasets)) {
|
|
495
|
+
if (!datasetId.trim() || !isRecord(rawDataset)) continue;
|
|
496
|
+
const phase =
|
|
497
|
+
rawDataset.phase === 'available' || rawDataset.phase === 'failed'
|
|
498
|
+
? rawDataset.phase
|
|
499
|
+
: 'registered';
|
|
500
|
+
datasetsById[datasetId] = {
|
|
501
|
+
datasetId,
|
|
502
|
+
path: optionalString(rawDataset.path) ?? `datasets.${datasetId}`,
|
|
503
|
+
tableNamespace: optionalString(rawDataset.tableNamespace) ?? datasetId,
|
|
504
|
+
phase,
|
|
505
|
+
persistedRows: Math.max(0, finiteNumber(rawDataset.persistedRows) ?? 0),
|
|
506
|
+
succeededRows: Math.max(0, finiteNumber(rawDataset.succeededRows) ?? 0),
|
|
507
|
+
failedRows: Math.max(0, finiteNumber(rawDataset.failedRows) ?? 0),
|
|
508
|
+
complete: rawDataset.complete === true,
|
|
509
|
+
updatedAt: finiteNumber(rawDataset.updatedAt) ?? 0,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
450
512
|
const createdAt = finiteNumber(value.createdAt) ?? fallback.createdAt ?? null;
|
|
451
513
|
const startedAt = finiteNumber(value.startedAt) ?? fallback.startedAt ?? null;
|
|
452
514
|
const finishedAt =
|
|
@@ -484,6 +546,14 @@ export function normalizePlayRunLedgerSnapshot(
|
|
|
484
546
|
: finiteNumber(value.durationMs),
|
|
485
547
|
orderedStepIds: orderedStepIds.filter((stepId) => stepsById[stepId]),
|
|
486
548
|
stepsById,
|
|
549
|
+
orderedDatasetIds: (Array.isArray(value.orderedDatasetIds)
|
|
550
|
+
? value.orderedDatasetIds
|
|
551
|
+
: Object.keys(datasetsById)
|
|
552
|
+
).filter(
|
|
553
|
+
(datasetId): datasetId is string =>
|
|
554
|
+
typeof datasetId === 'string' && Boolean(datasetsById[datasetId]),
|
|
555
|
+
),
|
|
556
|
+
datasetsById,
|
|
487
557
|
logTail: logTail.slice(-LOG_TAIL_LIMIT),
|
|
488
558
|
// Snapshots persisted before totalLogCount existed only know the retained
|
|
489
559
|
// tail, so the best lower bound for the cumulative count is the tail size.
|
|
@@ -1269,6 +1339,34 @@ export function reducePlayRunLedgerEvent(
|
|
|
1269
1339
|
: null,
|
|
1270
1340
|
});
|
|
1271
1341
|
}
|
|
1342
|
+
case 'dataset.lifecycle': {
|
|
1343
|
+
const current = base.datasetsById[event.datasetId];
|
|
1344
|
+
const next: PlayRunLedgerDatasetSnapshot = {
|
|
1345
|
+
datasetId: event.datasetId,
|
|
1346
|
+
path: event.path,
|
|
1347
|
+
tableNamespace: event.tableNamespace,
|
|
1348
|
+
phase: event.phase,
|
|
1349
|
+
persistedRows: Math.max(
|
|
1350
|
+
current?.persistedRows ?? 0,
|
|
1351
|
+
event.persistedRows ?? 0,
|
|
1352
|
+
),
|
|
1353
|
+
succeededRows: Math.max(
|
|
1354
|
+
current?.succeededRows ?? 0,
|
|
1355
|
+
event.succeededRows ?? 0,
|
|
1356
|
+
),
|
|
1357
|
+
failedRows: Math.max(current?.failedRows ?? 0, event.failedRows ?? 0),
|
|
1358
|
+
complete: current?.complete === true || event.complete === true,
|
|
1359
|
+
updatedAt: occurredAt,
|
|
1360
|
+
};
|
|
1361
|
+
return withTiming({
|
|
1362
|
+
...base,
|
|
1363
|
+
orderedDatasetIds: current
|
|
1364
|
+
? base.orderedDatasetIds
|
|
1365
|
+
: [...base.orderedDatasetIds, event.datasetId],
|
|
1366
|
+
datasetsById: { ...base.datasetsById, [event.datasetId]: next },
|
|
1367
|
+
resultTableNamespace: base.resultTableNamespace ?? event.tableNamespace,
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1272
1370
|
case 'sheet.summary.updated':
|
|
1273
1371
|
return withTiming({
|
|
1274
1372
|
...base,
|
|
@@ -1376,6 +1474,8 @@ export function buildPlayRunLedgerEventsFromLogLines(input: {
|
|
|
1376
1474
|
source?: PlayRunLedgerEventSource;
|
|
1377
1475
|
occurredAt?: number;
|
|
1378
1476
|
channelOffset?: number | null;
|
|
1477
|
+
producerAttempt?: number | null;
|
|
1478
|
+
transportDedupe?: 'semantic' | 'exact';
|
|
1379
1479
|
includeLogAppend?: boolean;
|
|
1380
1480
|
}): PlayRunLedgerEvent[] {
|
|
1381
1481
|
const source = input.source ?? 'worker';
|
|
@@ -1392,16 +1492,31 @@ export function buildPlayRunLedgerEventsFromLogLines(input: {
|
|
|
1392
1492
|
if (newLines.length === 0 || input.includeLogAppend === false) {
|
|
1393
1493
|
return [];
|
|
1394
1494
|
}
|
|
1395
|
-
|
|
1396
|
-
|
|
1495
|
+
const events: PlayRunLedgerEvent[] = [];
|
|
1496
|
+
for (
|
|
1497
|
+
let index = 0;
|
|
1498
|
+
index < newLines.length;
|
|
1499
|
+
index += MAX_LEDGER_LOG_LINES_PER_EVENT
|
|
1500
|
+
) {
|
|
1501
|
+
events.push({
|
|
1397
1502
|
type: 'log.appended',
|
|
1398
1503
|
runId: input.runId,
|
|
1399
1504
|
source,
|
|
1400
1505
|
occurredAt,
|
|
1401
|
-
lines: newLines,
|
|
1402
|
-
...(positional ? { channelOffset: input.channelOffset! } : {}),
|
|
1403
|
-
|
|
1404
|
-
|
|
1506
|
+
lines: newLines.slice(index, index + MAX_LEDGER_LOG_LINES_PER_EVENT),
|
|
1507
|
+
...(positional ? { channelOffset: input.channelOffset! + index } : {}),
|
|
1508
|
+
...(positional &&
|
|
1509
|
+
typeof input.producerAttempt === 'number' &&
|
|
1510
|
+
Number.isFinite(input.producerAttempt) &&
|
|
1511
|
+
input.producerAttempt >= 0
|
|
1512
|
+
? { producerAttempt: Math.trunc(input.producerAttempt) }
|
|
1513
|
+
: {}),
|
|
1514
|
+
...(input.transportDedupe
|
|
1515
|
+
? { transportDedupe: input.transportDedupe }
|
|
1516
|
+
: {}),
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
return events;
|
|
1405
1520
|
}
|
|
1406
1521
|
|
|
1407
1522
|
/**
|
|
@@ -1419,6 +1534,8 @@ export function buildTerminalLogReplayEvents(input: {
|
|
|
1419
1534
|
source?: PlayRunLedgerEventSource;
|
|
1420
1535
|
occurredAt?: number;
|
|
1421
1536
|
liveLogTotalCount?: number;
|
|
1537
|
+
dedupeMode?: 'semantic' | 'exact';
|
|
1538
|
+
producerAttempt?: number;
|
|
1422
1539
|
}): PlayRunLedgerEvent[] {
|
|
1423
1540
|
const channelOffset = terminalLogReplayChannelOffset({
|
|
1424
1541
|
liveLogTotalCount: input.liveLogTotalCount,
|
|
@@ -1430,6 +1547,10 @@ export function buildTerminalLogReplayEvents(input: {
|
|
|
1430
1547
|
source: 'coordinator',
|
|
1431
1548
|
occurredAt: input.occurredAt,
|
|
1432
1549
|
channelOffset,
|
|
1550
|
+
producerAttempt: input.producerAttempt,
|
|
1551
|
+
transportDedupe:
|
|
1552
|
+
input.dedupeMode ??
|
|
1553
|
+
(typeof input.producerAttempt === 'number' ? 'exact' : undefined),
|
|
1433
1554
|
});
|
|
1434
1555
|
}
|
|
1435
1556
|
|
|
@@ -1466,6 +1587,7 @@ export function buildPlayRunLedgerEventsFromStatusPatch(input: {
|
|
|
1466
1587
|
source,
|
|
1467
1588
|
occurredAt: checkpointAt,
|
|
1468
1589
|
channelOffset: patch.liveLogsChannelOffset ?? null,
|
|
1590
|
+
producerAttempt: patch.liveLogsProducerAttempt ?? null,
|
|
1469
1591
|
})
|
|
1470
1592
|
: [];
|
|
1471
1593
|
const earliestStartedAt =
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
normalizePlayRunLedgerSnapshot,
|
|
5
5
|
summarizeRunRowOutcomes,
|
|
6
6
|
type PlayRunLedgerSnapshot,
|
|
7
|
+
type PlayRunLedgerDatasetSnapshot,
|
|
7
8
|
type PlayRunLedgerStepSnapshot,
|
|
8
9
|
type PlayRunRowOutcomeSummary,
|
|
9
10
|
} from './run-ledger';
|
|
@@ -76,6 +77,7 @@ export type PlayRunLiveSnapshot = {
|
|
|
76
77
|
logsTruncated?: boolean;
|
|
77
78
|
activeArtifactTableNamespace: string | null;
|
|
78
79
|
resultTableNamespace: string | null;
|
|
80
|
+
datasets?: PlayRunLedgerDatasetSnapshot[];
|
|
79
81
|
nodeStates: PlayRunStreamNodeState[];
|
|
80
82
|
activeNodeId: string | null;
|
|
81
83
|
/**
|
|
@@ -212,6 +214,11 @@ function buildSnapshotFromLedger(
|
|
|
212
214
|
...(snapshot.logsTruncated ? { logsTruncated: true } : {}),
|
|
213
215
|
activeArtifactTableNamespace: snapshot.activeArtifactTableNamespace ?? null,
|
|
214
216
|
resultTableNamespace: snapshot.resultTableNamespace ?? null,
|
|
217
|
+
datasets: snapshot.orderedDatasetIds
|
|
218
|
+
.map((datasetId) => snapshot.datasetsById[datasetId])
|
|
219
|
+
.filter((dataset): dataset is PlayRunLedgerDatasetSnapshot =>
|
|
220
|
+
Boolean(dataset),
|
|
221
|
+
),
|
|
215
222
|
nodeStates,
|
|
216
223
|
activeNodeId: snapshot.activeStepId ?? null,
|
|
217
224
|
...(rowOutcomes ? { rowOutcomes } : {}),
|
|
@@ -1,5 +1,99 @@
|
|
|
1
1
|
import type { DaytonaSandbox } from './daytona-lifecycle';
|
|
2
2
|
|
|
3
|
+
export type DetachedDaytonaStartupDiagnostic = {
|
|
4
|
+
commandFound: boolean;
|
|
5
|
+
exitCode: number | null;
|
|
6
|
+
sessionFound: boolean;
|
|
7
|
+
sessionCommandCount: number | null;
|
|
8
|
+
exitCodeFile: number | null;
|
|
9
|
+
processSummary: {
|
|
10
|
+
total: number;
|
|
11
|
+
node: number;
|
|
12
|
+
states: Record<string, number>;
|
|
13
|
+
} | null;
|
|
14
|
+
errors: string[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function summarizeProcesses(value: unknown): {
|
|
18
|
+
total: number;
|
|
19
|
+
node: number;
|
|
20
|
+
states: Record<string, number>;
|
|
21
|
+
} | null {
|
|
22
|
+
if (typeof value !== 'string' || !value.trim()) return null;
|
|
23
|
+
const rows = value
|
|
24
|
+
.split('\n')
|
|
25
|
+
.map((line) => line.trim().split(/\s+/, 2))
|
|
26
|
+
.filter(([state, command]) => Boolean(state && command));
|
|
27
|
+
const states: Record<string, number> = {};
|
|
28
|
+
let node = 0;
|
|
29
|
+
for (const [state, command] of rows) {
|
|
30
|
+
const normalizedState = state![0]?.toUpperCase() || '?';
|
|
31
|
+
states[normalizedState] = (states[normalizedState] ?? 0) + 1;
|
|
32
|
+
if (/^node(?:-|$)/i.test(command!)) node += 1;
|
|
33
|
+
}
|
|
34
|
+
return { total: rows.length, node, states };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Inspect an accepted detached command without exposing its command line or
|
|
39
|
+
* environment. Daytona's command model has no queued/running state, so the
|
|
40
|
+
* combination of session membership, a numeric exit marker, and a
|
|
41
|
+
* aggregate process counts are the narrowest useful startup diagnostic.
|
|
42
|
+
* Process names are reduced to a Node count before logging; runner logs,
|
|
43
|
+
* output, and arbitrary process names are deliberately excluded because no
|
|
44
|
+
* heuristic redactor can guarantee removal of arbitrary customer secrets.
|
|
45
|
+
*/
|
|
46
|
+
export async function inspectDetachedDaytonaStartup(input: {
|
|
47
|
+
sandbox: DaytonaSandbox;
|
|
48
|
+
sessionId: string;
|
|
49
|
+
cmdId: string;
|
|
50
|
+
exitCodePath: string;
|
|
51
|
+
}): Promise<DetachedDaytonaStartupDiagnostic> {
|
|
52
|
+
const errors: string[] = [];
|
|
53
|
+
const diagnostic: DetachedDaytonaStartupDiagnostic = {
|
|
54
|
+
commandFound: false,
|
|
55
|
+
exitCode: null,
|
|
56
|
+
sessionFound: false,
|
|
57
|
+
sessionCommandCount: null,
|
|
58
|
+
exitCodeFile: null,
|
|
59
|
+
processSummary: null,
|
|
60
|
+
errors,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
await Promise.all([
|
|
64
|
+
input.sandbox.process
|
|
65
|
+
.getSessionCommand(input.sessionId, input.cmdId)
|
|
66
|
+
.then((command) => {
|
|
67
|
+
diagnostic.commandFound = true;
|
|
68
|
+
diagnostic.exitCode =
|
|
69
|
+
typeof command.exitCode === 'number' ? command.exitCode : null;
|
|
70
|
+
})
|
|
71
|
+
.catch(() => errors.push('get_session_command_failed')),
|
|
72
|
+
input.sandbox.process
|
|
73
|
+
.getSession(input.sessionId)
|
|
74
|
+
.then((session) => {
|
|
75
|
+
diagnostic.sessionFound = true;
|
|
76
|
+
diagnostic.sessionCommandCount = session.commands.length;
|
|
77
|
+
})
|
|
78
|
+
.catch(() => errors.push('get_session_failed')),
|
|
79
|
+
input.sandbox.fs
|
|
80
|
+
.downloadFile(input.exitCodePath, 5)
|
|
81
|
+
.then((file) => {
|
|
82
|
+
const value = Number.parseInt(file.toString('utf-8').trim(), 10);
|
|
83
|
+
diagnostic.exitCodeFile = Number.isFinite(value) ? value : null;
|
|
84
|
+
})
|
|
85
|
+
.catch(() => errors.push('exit_code_file_unavailable')),
|
|
86
|
+
input.sandbox.process
|
|
87
|
+
.executeCommand('ps -eo stat=,comm=', undefined, {}, 5)
|
|
88
|
+
.then((result) => {
|
|
89
|
+
diagnostic.processSummary = summarizeProcesses(result.result);
|
|
90
|
+
})
|
|
91
|
+
.catch(() => errors.push('process_snapshot_failed')),
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
return diagnostic;
|
|
95
|
+
}
|
|
96
|
+
|
|
3
97
|
/**
|
|
4
98
|
* Detached Daytona command start (push-execution B2-final).
|
|
5
99
|
*
|
|
@@ -32,7 +32,10 @@ import {
|
|
|
32
32
|
validateDaytonaExecutionContext,
|
|
33
33
|
} from './daytona-lifecycle';
|
|
34
34
|
import { stageDaytonaRunnerPayload } from './daytona-payload-transport';
|
|
35
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
inspectDetachedDaytonaStartup,
|
|
37
|
+
startDetachedDaytonaCommand,
|
|
38
|
+
} from './daytona-session-execution';
|
|
36
39
|
|
|
37
40
|
const DAYTONA_EXECUTE_TIMEOUT_SECONDS = STANDARD_PLAY_RUNTIME_LIMIT_SECONDS;
|
|
38
41
|
const STANDARD_WORKFLOW_RUNTIME_LIMIT_ERROR = `Based on this plan, max runtime is ${STANDARD_PLAY_RUNTIME_LIMIT_LABEL}. Use smaller batches; ask for runtime.`;
|
|
@@ -42,6 +45,8 @@ const DAYTONA_RUNTIME_INITIALIZATION_MAX_ATTEMPTS = 2;
|
|
|
42
45
|
const DAYTONA_INFRASTRUCTURE_MAX_ATTEMPTS = 2;
|
|
43
46
|
const DAYTONA_UPLOAD_MAX_ATTEMPTS = 2;
|
|
44
47
|
const DAYTONA_UPLOAD_ATTEMPT_DEADLINE_MS = 90_000;
|
|
48
|
+
const DAYTONA_STARTUP_DIAGNOSTIC_DELAY_MS = 15_000;
|
|
49
|
+
const DAYTONA_STARTUP_TRACE_ENV = 'DEEPLINE_DAYTONA_STARTUP_TRACE';
|
|
45
50
|
|
|
46
51
|
const RUNTIME_POSTGRES_CONNECT_RETRY_PATTERN =
|
|
47
52
|
/\bRuntime Postgres\b.*\b(connection timed out|connect timeout|ETIMEDOUT|ECONNRESET|ECONNREFUSED|Connection terminated|Connection ended unexpectedly)\b/i;
|
|
@@ -122,19 +127,34 @@ export async function inspectDetachedDaytonaRunner(input: {
|
|
|
122
127
|
cmdId: string;
|
|
123
128
|
outputPath: string;
|
|
124
129
|
exitCodePath: string;
|
|
125
|
-
}): Promise<{
|
|
130
|
+
}): Promise<{
|
|
131
|
+
exitCode: number | null;
|
|
132
|
+
result: PlayRunnerResult | null;
|
|
133
|
+
diagnosis: {
|
|
134
|
+
stage:
|
|
135
|
+
| 'salvaged'
|
|
136
|
+
| 'result_missing'
|
|
137
|
+
| 'command_inspection_failed'
|
|
138
|
+
| 'sandbox_lookup_failed';
|
|
139
|
+
detail: string | null;
|
|
140
|
+
};
|
|
141
|
+
}> {
|
|
126
142
|
try {
|
|
127
143
|
const { clientOptions } = loadDaytonaRequiredConfig();
|
|
128
144
|
const daytona = daytonaSdkClientFactory.createFull(clientOptions);
|
|
129
145
|
const sandbox = (await daytona.get(input.sandboxId)) as DaytonaSandbox;
|
|
130
146
|
let exitCode: number | null = null;
|
|
147
|
+
let commandInspectionError: string | null = null;
|
|
131
148
|
try {
|
|
132
149
|
const command = await sandbox.process.getSessionCommand(
|
|
133
150
|
input.sessionId,
|
|
134
151
|
input.cmdId,
|
|
135
152
|
);
|
|
136
|
-
exitCode =
|
|
153
|
+
exitCode =
|
|
154
|
+
typeof command?.exitCode === 'number' ? command.exitCode : null;
|
|
137
155
|
} catch (error) {
|
|
156
|
+
commandInspectionError =
|
|
157
|
+
error instanceof Error ? error.message : String(error);
|
|
138
158
|
console.warn('[play-runner.daytona.detached_inspect_command_failed]', {
|
|
139
159
|
sandboxId: input.sandboxId,
|
|
140
160
|
cmdId: input.cmdId,
|
|
@@ -148,7 +168,16 @@ export async function inspectDetachedDaytonaRunner(input: {
|
|
|
148
168
|
timeoutMs: 10_000,
|
|
149
169
|
});
|
|
150
170
|
if (!recovered.execution) {
|
|
151
|
-
return {
|
|
171
|
+
return {
|
|
172
|
+
exitCode,
|
|
173
|
+
result: null,
|
|
174
|
+
diagnosis: commandInspectionError
|
|
175
|
+
? {
|
|
176
|
+
stage: 'command_inspection_failed',
|
|
177
|
+
detail: commandInspectionError,
|
|
178
|
+
}
|
|
179
|
+
: { stage: 'result_missing', detail: null },
|
|
180
|
+
};
|
|
152
181
|
}
|
|
153
182
|
const result =
|
|
154
183
|
findPlayRunnerResult(parsePlayRunnerEvents(recovered.execution.result)) ??
|
|
@@ -156,14 +185,22 @@ export async function inspectDetachedDaytonaRunner(input: {
|
|
|
156
185
|
return {
|
|
157
186
|
exitCode: exitCode ?? recovered.execution.exitCode,
|
|
158
187
|
result,
|
|
188
|
+
diagnosis: result
|
|
189
|
+
? { stage: 'salvaged', detail: null }
|
|
190
|
+
: { stage: 'result_missing', detail: null },
|
|
159
191
|
};
|
|
160
192
|
} catch (error) {
|
|
193
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
161
194
|
console.warn('[play-runner.daytona.detached_inspect_failed]', {
|
|
162
195
|
sandboxId: input.sandboxId,
|
|
163
196
|
cmdId: input.cmdId,
|
|
164
|
-
error:
|
|
197
|
+
error: detail,
|
|
165
198
|
});
|
|
166
|
-
return {
|
|
199
|
+
return {
|
|
200
|
+
exitCode: null,
|
|
201
|
+
result: null,
|
|
202
|
+
diagnosis: { stage: 'sandbox_lookup_failed', detail },
|
|
203
|
+
};
|
|
167
204
|
}
|
|
168
205
|
}
|
|
169
206
|
|
|
@@ -717,8 +754,56 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
|
|
|
717
754
|
cmdId: start.cmdId,
|
|
718
755
|
runnerAttempt,
|
|
719
756
|
ceilingSeconds: DAYTONA_EXECUTE_TIMEOUT_SECONDS,
|
|
757
|
+
outputPath: stagedPayload.outputPath,
|
|
758
|
+
exitCodePath: stagedPayload.exitCodePath,
|
|
720
759
|
elapsedMs: Date.now() - startedAt,
|
|
721
760
|
});
|
|
761
|
+
// Daytona acknowledges an async command with only a cmdId. It does
|
|
762
|
+
// not expose a queued/running state, and the backend parks before the
|
|
763
|
+
// runner contacts the runtime sheet. During startup investigations,
|
|
764
|
+
// retain a bounded snapshot while the sandbox is still alive; the
|
|
765
|
+
// normal cancellation path otherwise deletes the only evidence.
|
|
766
|
+
// This is opt-in because it adds Daytona control-plane calls to every
|
|
767
|
+
// run. The diagnostic excludes command lines and environment values.
|
|
768
|
+
if (process.env[DAYTONA_STARTUP_TRACE_ENV] === '1') {
|
|
769
|
+
const diagnosticTimer = setTimeout(() => {
|
|
770
|
+
void inspectDetachedDaytonaStartup({
|
|
771
|
+
sandbox,
|
|
772
|
+
sessionId: start.sessionId,
|
|
773
|
+
cmdId: start.cmdId,
|
|
774
|
+
exitCodePath: stagedPayload.exitCodePath,
|
|
775
|
+
}).then(
|
|
776
|
+
(diagnostic) =>
|
|
777
|
+
emitDaytonaStage(
|
|
778
|
+
callbacks,
|
|
779
|
+
config.context,
|
|
780
|
+
'execute:startup_diagnostic',
|
|
781
|
+
{
|
|
782
|
+
sandboxId: sandbox.id,
|
|
783
|
+
sessionId: start.sessionId,
|
|
784
|
+
cmdId: start.cmdId,
|
|
785
|
+
diagnostic,
|
|
786
|
+
elapsedMs: Date.now() - startedAt,
|
|
787
|
+
},
|
|
788
|
+
),
|
|
789
|
+
(error: unknown) =>
|
|
790
|
+
emitDaytonaStage(
|
|
791
|
+
callbacks,
|
|
792
|
+
config.context,
|
|
793
|
+
'execute:startup_diagnostic_failed',
|
|
794
|
+
{
|
|
795
|
+
sandboxId: sandbox.id,
|
|
796
|
+
sessionId: start.sessionId,
|
|
797
|
+
cmdId: start.cmdId,
|
|
798
|
+
errorType:
|
|
799
|
+
error instanceof Error ? error.name : 'unknown',
|
|
800
|
+
elapsedMs: Date.now() - startedAt,
|
|
801
|
+
},
|
|
802
|
+
),
|
|
803
|
+
);
|
|
804
|
+
}, DAYTONA_STARTUP_DIAGNOSTIC_DELAY_MS);
|
|
805
|
+
diagnosticTimer.unref?.();
|
|
806
|
+
}
|
|
722
807
|
return {
|
|
723
808
|
status: 'suspended',
|
|
724
809
|
suspension: {
|
|
@@ -9,16 +9,25 @@ import type {
|
|
|
9
9
|
PlayRunnerResult,
|
|
10
10
|
} from '@shared_libs/play-runtime/protocol';
|
|
11
11
|
|
|
12
|
-
export type PlayRunnerRuntimeResource =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
export type PlayRunnerRuntimeResource = {
|
|
13
|
+
kind: 'daytona_sandbox';
|
|
14
|
+
sandboxId: string;
|
|
15
|
+
billingStartedAt: number;
|
|
16
|
+
billingEndedAt?: number | null;
|
|
17
|
+
terminalReason?: RuntimeResourceTerminalReason | null;
|
|
18
|
+
terminalAt?: number | null;
|
|
19
|
+
cpu?: number | null;
|
|
20
|
+
memoryGiB?: number | null;
|
|
21
|
+
diskGiB?: number | null;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type RuntimeResourceTerminalReason =
|
|
25
|
+
| 'completed'
|
|
26
|
+
| 'runner_failed'
|
|
27
|
+
| 'sandbox_missing'
|
|
28
|
+
| 'sandbox_oom'
|
|
29
|
+
| 'cancelled'
|
|
30
|
+
| 'lease_expired';
|
|
22
31
|
|
|
23
32
|
export class RuntimeResourceFenceLostError extends Error {
|
|
24
33
|
constructor(message: string) {
|