deepline 0.1.206 → 0.1.208
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 +25 -16
- package/dist/bundling-sources/sdk/src/http.ts +10 -1
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +96 -12
- package/dist/bundling-sources/shared_libs/play-runtime/child-execution-strategy.ts +51 -0
- package/dist/bundling-sources/shared_libs/play-runtime/child-run-id.ts +16 -0
- package/dist/bundling-sources/shared_libs/play-runtime/child-run-lifecycle.ts +70 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +321 -175
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +13 -1
- package/dist/bundling-sources/shared_libs/play-runtime/governor/adaptive-admission.ts +16 -4
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +161 -81
- package/dist/bundling-sources/shared_libs/play-runtime/governor/coordinator-rate-state-backend.ts +26 -9
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +109 -1
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-completion-sink.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-execution-scope.ts +256 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +17 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +103 -18
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +8 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-pg-driver-pg.ts +5 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-compute-usage.ts +74 -0
- package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +5 -12
- package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +2 -1
- package/dist/bundling-sources/shared_libs/runtime-env.ts +55 -0
- package/dist/cli/index.js +33 -7
- package/dist/cli/index.mjs +33 -7
- package/dist/index.js +8 -3
- package/dist/index.mjs +8 -3
- package/package.json +1 -1
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
STANDARD_PLAY_RUNTIME_LIMIT_LABEL,
|
|
41
41
|
STANDARD_PLAY_RUNTIME_LIMIT_SECONDS,
|
|
42
42
|
} from '../../../shared_libs/play-runtime/runtime-constants';
|
|
43
|
+
import { childSubmitIdempotencyKey } from '../../../shared_libs/play-runtime/child-run-id';
|
|
43
44
|
import {
|
|
44
45
|
createPlayExecutionGovernor,
|
|
45
46
|
type GovernanceSnapshot,
|
|
@@ -3545,18 +3546,18 @@ function resolveSheetContractFromReq(
|
|
|
3545
3546
|
}
|
|
3546
3547
|
|
|
3547
3548
|
/**
|
|
3548
|
-
*
|
|
3549
|
-
*
|
|
3550
|
-
* out the 10-minute lease TTL. Every release is attempt/owner fenced downstream,
|
|
3551
|
-
* so it can never weaken a different run's live lease. Failures are logged loud
|
|
3552
|
-
* and swallowed here — the caller still throws the original run-fatal error.
|
|
3549
|
+
* Release attempt-scoped leases after terminal settlement. Completed and failed
|
|
3550
|
+
* rows are durable; retaining their live fences only delays the next repair run.
|
|
3553
3551
|
*/
|
|
3554
|
-
async function
|
|
3552
|
+
async function releaseRuntimeLeasesOnSettlement(input: {
|
|
3555
3553
|
req: RunRequest;
|
|
3556
3554
|
leasedSheetNamespaces: Set<string>;
|
|
3557
3555
|
emit: (event: RunnerEvent) => void;
|
|
3556
|
+
reason: 'completed' | 'run-fatal';
|
|
3558
3557
|
}): Promise<void> {
|
|
3559
|
-
const { req, leasedSheetNamespaces, emit } = input;
|
|
3558
|
+
const { req, leasedSheetNamespaces, emit, reason } = input;
|
|
3559
|
+
const reasonLabel =
|
|
3560
|
+
reason === 'completed' ? 'completed settlement' : 'run-fatal teardown';
|
|
3560
3561
|
const scope = runtimeSheetSessionScope(req);
|
|
3561
3562
|
for (const tableNamespace of leasedSheetNamespaces) {
|
|
3562
3563
|
const sheetContract = resolveSheetContractFromReq(req, tableNamespace);
|
|
@@ -3574,7 +3575,7 @@ async function releaseRuntimeLeasesOnTeardown(input: {
|
|
|
3574
3575
|
emit({
|
|
3575
3576
|
type: 'log',
|
|
3576
3577
|
level: 'info',
|
|
3577
|
-
message: `Released ${released.released} runtime sheet row lease(s) for ctx.dataset("${tableNamespace}") on
|
|
3578
|
+
message: `Released ${released.released} runtime sheet row lease(s) for ctx.dataset("${tableNamespace}") on ${reasonLabel}.`,
|
|
3578
3579
|
ts: nowMs(),
|
|
3579
3580
|
});
|
|
3580
3581
|
}
|
|
@@ -3582,7 +3583,7 @@ async function releaseRuntimeLeasesOnTeardown(input: {
|
|
|
3582
3583
|
emit({
|
|
3583
3584
|
type: 'log',
|
|
3584
3585
|
level: 'warn',
|
|
3585
|
-
message: `Runtime sheet attempt release failed for ctx.dataset("${tableNamespace}") on
|
|
3586
|
+
message: `Runtime sheet attempt release failed for ctx.dataset("${tableNamespace}") on ${reasonLabel} (rerun will fall back to lease-TTL expiry): ${
|
|
3586
3587
|
releaseError instanceof Error
|
|
3587
3588
|
? releaseError.message
|
|
3588
3589
|
: String(releaseError)
|
|
@@ -3601,7 +3602,7 @@ async function releaseRuntimeLeasesOnTeardown(input: {
|
|
|
3601
3602
|
emit({
|
|
3602
3603
|
type: 'log',
|
|
3603
3604
|
level: 'info',
|
|
3604
|
-
message: `Released ${released.released} work-receipt lease(s) on
|
|
3605
|
+
message: `Released ${released.released} work-receipt lease(s) on ${reasonLabel}.`,
|
|
3605
3606
|
ts: nowMs(),
|
|
3606
3607
|
});
|
|
3607
3608
|
}
|
|
@@ -3609,7 +3610,7 @@ async function releaseRuntimeLeasesOnTeardown(input: {
|
|
|
3609
3610
|
emit({
|
|
3610
3611
|
type: 'log',
|
|
3611
3612
|
level: 'warn',
|
|
3612
|
-
message: `Work-receipt release failed on
|
|
3613
|
+
message: `Work-receipt release failed on ${reasonLabel} (rerun will fall back to lease-TTL expiry): ${
|
|
3613
3614
|
releaseError instanceof Error
|
|
3614
3615
|
? releaseError.message
|
|
3615
3616
|
: String(releaseError)
|
|
@@ -7046,10 +7047,11 @@ function createMinimalWorkerCtx(
|
|
|
7046
7047
|
options?.timeoutMs == null && !childNeedsWorkflowScheduler,
|
|
7047
7048
|
body: {
|
|
7048
7049
|
name: resolvedName,
|
|
7049
|
-
childIdempotencyKey:
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7050
|
+
childIdempotencyKey: childSubmitIdempotencyKey({
|
|
7051
|
+
receiptKey,
|
|
7052
|
+
wasFailed,
|
|
7053
|
+
leaseId: receiptContext.leaseId,
|
|
7054
|
+
}),
|
|
7053
7055
|
input: isRecord(input) ? input : {},
|
|
7054
7056
|
orgId: req.orgId,
|
|
7055
7057
|
callbackUrl: req.callbackUrl,
|
|
@@ -8154,6 +8156,12 @@ async function executeRunRequest(
|
|
|
8154
8156
|
if (req.playCallGovernance && !options?.persistResultDatasets) {
|
|
8155
8157
|
await persistProjectedResultDatasets();
|
|
8156
8158
|
}
|
|
8159
|
+
await releaseRuntimeLeasesOnSettlement({
|
|
8160
|
+
req,
|
|
8161
|
+
leasedSheetNamespaces,
|
|
8162
|
+
emit: wrappedEmit,
|
|
8163
|
+
reason: 'completed',
|
|
8164
|
+
});
|
|
8157
8165
|
if (options?.persistResultDatasets) {
|
|
8158
8166
|
await persistProjectedResultDatasets();
|
|
8159
8167
|
// Capped runs settle compute billing BEFORE declaring run.completed: a
|
|
@@ -8274,10 +8282,11 @@ async function executeRunRequest(
|
|
|
8274
8282
|
// blocked for the full lease TTL. Best-effort but LOUD — a release failure
|
|
8275
8283
|
// is logged and never masks the original run-fatal error. TTL expiry stays
|
|
8276
8284
|
// the recovery path for true crashes (isolate death) where this never runs.
|
|
8277
|
-
await
|
|
8285
|
+
await releaseRuntimeLeasesOnSettlement({
|
|
8278
8286
|
req,
|
|
8279
8287
|
leasedSheetNamespaces,
|
|
8280
8288
|
emit: wrappedEmit,
|
|
8289
|
+
reason: 'run-fatal',
|
|
8281
8290
|
});
|
|
8282
8291
|
const errorBilling = extractErrorBilling(error);
|
|
8283
8292
|
const salvage: ReceiptSalvage | null = aborted
|
|
@@ -28,6 +28,7 @@ import type { LiveEventEnvelope } from './types.js';
|
|
|
28
28
|
import { baseUrlSlug, sdkCliStateDirPath } from './config.js';
|
|
29
29
|
import { detectAgentRuntime, isCoworkLikeSandbox } from './agent-runtime.js';
|
|
30
30
|
import {
|
|
31
|
+
ABSURD_RELEASE_OVERRIDE_HEADER,
|
|
31
32
|
COORDINATOR_INTERNAL_TOKEN_HEADER,
|
|
32
33
|
COORDINATOR_URL_OVERRIDE_HEADER,
|
|
33
34
|
RUNTIME_SCHEDULER_SCHEMA_OVERRIDE_HEADER,
|
|
@@ -245,6 +246,10 @@ export class HttpClient {
|
|
|
245
246
|
typeof process !== 'undefined'
|
|
246
247
|
? process.env?.DEEPLINE_INTERNAL_TOKEN
|
|
247
248
|
: undefined;
|
|
249
|
+
const absurdReleaseOverride =
|
|
250
|
+
typeof process !== 'undefined'
|
|
251
|
+
? process.env?.DEEPLINE_ABSURD_RELEASE
|
|
252
|
+
: undefined;
|
|
248
253
|
if (coordinatorUrl?.trim()) {
|
|
249
254
|
headers[COORDINATOR_URL_OVERRIDE_HEADER] = coordinatorUrl.trim();
|
|
250
255
|
}
|
|
@@ -282,11 +287,15 @@ export class HttpClient {
|
|
|
282
287
|
if (runtimeTestFault?.trim()) {
|
|
283
288
|
headers[PLAY_RUNTIME_TEST_FAULT_HEADER] = runtimeTestFault.trim();
|
|
284
289
|
}
|
|
290
|
+
if (absurdReleaseOverride?.trim() && coordinatorInternalToken?.trim()) {
|
|
291
|
+
headers[ABSURD_RELEASE_OVERRIDE_HEADER] = absurdReleaseOverride.trim();
|
|
292
|
+
}
|
|
285
293
|
if (
|
|
286
294
|
coordinatorInternalToken?.trim() &&
|
|
287
295
|
(coordinatorUrl?.trim() ||
|
|
288
296
|
workerCallbackUrl?.trim() ||
|
|
289
|
-
runtimeTestFault?.trim()
|
|
297
|
+
runtimeTestFault?.trim() ||
|
|
298
|
+
absurdReleaseOverride?.trim())
|
|
290
299
|
) {
|
|
291
300
|
headers[COORDINATOR_INTERNAL_TOKEN_HEADER] =
|
|
292
301
|
coordinatorInternalToken.trim();
|
|
@@ -106,10 +106,10 @@ export const SDK_RELEASE = {
|
|
|
106
106
|
// 0.1.154 removes the short-lived generated enrich StepOptions recompute
|
|
107
107
|
// fields shipped in 0.1.153.
|
|
108
108
|
// 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
|
|
109
|
-
version: '0.1.
|
|
109
|
+
version: '0.1.208',
|
|
110
110
|
apiContract: '2026-06-dataset-handle-results-hard-cutover',
|
|
111
111
|
supportPolicy: {
|
|
112
|
-
latest: '0.1.
|
|
112
|
+
latest: '0.1.208',
|
|
113
113
|
minimumSupported: '0.1.53',
|
|
114
114
|
deprecatedBelow: '0.1.53',
|
|
115
115
|
commandMinimumSupported: [
|
|
@@ -129,6 +129,15 @@ type RuntimeApiRequest =
|
|
|
129
129
|
staticPipeline?: unknown;
|
|
130
130
|
source?: 'published' | 'ad_hoc' | 'draft';
|
|
131
131
|
}
|
|
132
|
+
| {
|
|
133
|
+
action: 'settle_inline_child_run';
|
|
134
|
+
parentRunId: string;
|
|
135
|
+
childRunId: string;
|
|
136
|
+
childPlayName: string;
|
|
137
|
+
status: 'completed' | 'failed' | 'cancelled';
|
|
138
|
+
result?: unknown;
|
|
139
|
+
error?: string | null;
|
|
140
|
+
}
|
|
132
141
|
| ({
|
|
133
142
|
action: 'save_results';
|
|
134
143
|
} & RuntimeSaveResults)
|
|
@@ -169,7 +178,7 @@ type RuntimeApiRequest =
|
|
|
169
178
|
workflowId?: string;
|
|
170
179
|
runId?: string;
|
|
171
180
|
maxCreditsPerRun?: number | null;
|
|
172
|
-
finalItem
|
|
181
|
+
finalItem?: ComputeBillingItem;
|
|
173
182
|
}
|
|
174
183
|
| ({
|
|
175
184
|
action: 'rate_state_acquire';
|
|
@@ -324,6 +333,7 @@ export type WorkerRuntimeApiContext = {
|
|
|
324
333
|
vercelProtectionBypassToken?: string | null;
|
|
325
334
|
runtimeTestFaultHeader?: string | null;
|
|
326
335
|
fetch?: typeof fetch;
|
|
336
|
+
requestTimeoutMs?: number | null;
|
|
327
337
|
};
|
|
328
338
|
|
|
329
339
|
const APP_RUNTIME_API_RETRY_DELAYS_MS = [100, 250, 500, 1_000] as const;
|
|
@@ -333,6 +343,7 @@ const APP_RUNTIME_API_RECEIPT_RETRY_DELAYS_MS = [
|
|
|
333
343
|
const APP_RUNTIME_API_APPEND_RETRY_DELAYS_MS = [
|
|
334
344
|
100, 250, 500, 1_000, 2_000, 4_000, 8_000,
|
|
335
345
|
] as const;
|
|
346
|
+
const APP_RUNTIME_API_DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
|
|
336
347
|
const RUN_STATUS_LEDGER_SNAPSHOT_CACHE_LIMIT = 1_000;
|
|
337
348
|
const runStatusLedgerSnapshots = new Map<string, PlayRunLedgerSnapshot>();
|
|
338
349
|
// Positional log cursor per run: count of liveLogs lines already forwarded to
|
|
@@ -420,11 +431,13 @@ function summarizeAppRuntimeErrorBody(body: string): string {
|
|
|
420
431
|
? record.debug_error
|
|
421
432
|
: typeof record.details === 'string'
|
|
422
433
|
? record.details
|
|
423
|
-
: typeof record.
|
|
424
|
-
? record.
|
|
425
|
-
: typeof record.
|
|
426
|
-
? record.
|
|
427
|
-
:
|
|
434
|
+
: typeof record.detail === 'string'
|
|
435
|
+
? record.detail
|
|
436
|
+
: typeof record.error === 'string'
|
|
437
|
+
? record.error
|
|
438
|
+
: typeof record.message === 'string'
|
|
439
|
+
? record.message
|
|
440
|
+
: null;
|
|
428
441
|
if (message?.trim()) {
|
|
429
442
|
return message.trim();
|
|
430
443
|
}
|
|
@@ -449,6 +462,13 @@ function isRetryableAppRuntimeFetchError(input: {
|
|
|
449
462
|
if (!isRetryableAppRuntimeAction(input.action)) {
|
|
450
463
|
return false;
|
|
451
464
|
}
|
|
465
|
+
// The only signal on the request is AbortSignal.timeout(requestTimeoutMs), so
|
|
466
|
+
// any abort here is our own per-request deadline firing against a slow app
|
|
467
|
+
// runtime API (common under heavy map load) — always safe to retry across the
|
|
468
|
+
// backoff ladder rather than failing the tool call on the first slow write.
|
|
469
|
+
if (isRequestTimeoutAbort(input.error)) {
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
452
472
|
const message =
|
|
453
473
|
input.error instanceof Error ? input.error.message : String(input.error);
|
|
454
474
|
return /fetch failed|connection (terminated|timeout|timed out|closed|reset)|econnreset|etimedout|econnrefused/i.test(
|
|
@@ -456,6 +476,24 @@ function isRetryableAppRuntimeFetchError(input: {
|
|
|
456
476
|
);
|
|
457
477
|
}
|
|
458
478
|
|
|
479
|
+
function isRequestTimeoutAbort(error: unknown): boolean {
|
|
480
|
+
if (!error || typeof error !== 'object') {
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
// AbortSignal.timeout() — the only signal on the request — rejects with a
|
|
484
|
+
// DOMException named TimeoutError. Match that (and its message across
|
|
485
|
+
// runtimes) but NOT a bare AbortError / "the operation was aborted", so a
|
|
486
|
+
// genuine run cancellation still propagates instead of being retried.
|
|
487
|
+
if ((error as { name?: unknown }).name === 'TimeoutError') {
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
const message =
|
|
491
|
+
error instanceof Error
|
|
492
|
+
? error.message
|
|
493
|
+
: String((error as { message?: unknown }).message ?? '');
|
|
494
|
+
return /operation was aborted due to timeout|signal timed out/i.test(message);
|
|
495
|
+
}
|
|
496
|
+
|
|
459
497
|
function isRetryableAppRuntimeAction(
|
|
460
498
|
action: RuntimeApiRequest['action'],
|
|
461
499
|
): boolean {
|
|
@@ -486,7 +524,8 @@ function isRetryableAppRuntimeAction(
|
|
|
486
524
|
action === 'release_runtime_step_receipt' ||
|
|
487
525
|
action === 'save_results' ||
|
|
488
526
|
action === 'skip_runtime_step_receipt' ||
|
|
489
|
-
action === 'start_inline_child_run'
|
|
527
|
+
action === 'start_inline_child_run' ||
|
|
528
|
+
action === 'settle_inline_child_run'
|
|
490
529
|
);
|
|
491
530
|
}
|
|
492
531
|
|
|
@@ -562,6 +601,27 @@ function sleep(ms: number): Promise<void> {
|
|
|
562
601
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
563
602
|
}
|
|
564
603
|
|
|
604
|
+
export class AppRuntimeApiTransportError extends Error {
|
|
605
|
+
readonly action: RuntimeApiRequest['action'];
|
|
606
|
+
readonly attempts: number;
|
|
607
|
+
|
|
608
|
+
constructor(input: {
|
|
609
|
+
action: RuntimeApiRequest['action'];
|
|
610
|
+
attempts: number;
|
|
611
|
+
cause: unknown;
|
|
612
|
+
}) {
|
|
613
|
+
const causeMessage =
|
|
614
|
+
input.cause instanceof Error ? input.cause.message : String(input.cause);
|
|
615
|
+
super(
|
|
616
|
+
`App runtime API transport exhausted action=${input.action} attempts=${input.attempts}: ${causeMessage}`,
|
|
617
|
+
{ cause: input.cause },
|
|
618
|
+
);
|
|
619
|
+
this.name = 'AppRuntimeApiTransportError';
|
|
620
|
+
this.action = input.action;
|
|
621
|
+
this.attempts = input.attempts;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
565
625
|
function resolveAppRuntimeApiUrl(context: WorkerRuntimeApiContext): string {
|
|
566
626
|
const baseUrl = context.baseUrl.trim().replace(/\/$/, '');
|
|
567
627
|
return `${baseUrl}${PLAY_RUNTIME_API_COMPAT_PATH}`;
|
|
@@ -587,6 +647,12 @@ async function postAppRuntimeApi<TResponse>(
|
|
|
587
647
|
});
|
|
588
648
|
|
|
589
649
|
const maxAttempts = appRuntimeMaxAttempts(body.action);
|
|
650
|
+
const requestTimeoutMs = Math.max(
|
|
651
|
+
1,
|
|
652
|
+
Math.floor(
|
|
653
|
+
context.requestTimeoutMs ?? APP_RUNTIME_API_DEFAULT_REQUEST_TIMEOUT_MS,
|
|
654
|
+
),
|
|
655
|
+
);
|
|
590
656
|
|
|
591
657
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
592
658
|
let response: Response;
|
|
@@ -606,6 +672,7 @@ async function postAppRuntimeApi<TResponse>(
|
|
|
606
672
|
: {}),
|
|
607
673
|
},
|
|
608
674
|
body: JSON.stringify(body),
|
|
675
|
+
signal: AbortSignal.timeout(requestTimeoutMs),
|
|
609
676
|
});
|
|
610
677
|
} catch (error) {
|
|
611
678
|
if (
|
|
@@ -615,11 +682,11 @@ async function postAppRuntimeApi<TResponse>(
|
|
|
615
682
|
await sleep(appRuntimeRetryDelayMs(body.action, attempt));
|
|
616
683
|
continue;
|
|
617
684
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
);
|
|
685
|
+
throw new AppRuntimeApiTransportError({
|
|
686
|
+
action: body.action,
|
|
687
|
+
attempts: attempt,
|
|
688
|
+
cause: error,
|
|
689
|
+
});
|
|
623
690
|
}
|
|
624
691
|
if (response.ok) {
|
|
625
692
|
return (await response.json()) as TResponse;
|
|
@@ -953,6 +1020,23 @@ export async function startRunViaAppRuntime(
|
|
|
953
1020
|
});
|
|
954
1021
|
}
|
|
955
1022
|
|
|
1023
|
+
export async function settleInlineChildRunViaAppRuntime(
|
|
1024
|
+
context: WorkerRuntimeApiContext,
|
|
1025
|
+
input: {
|
|
1026
|
+
parentRunId: string;
|
|
1027
|
+
childRunId: string;
|
|
1028
|
+
childPlayName: string;
|
|
1029
|
+
status: 'completed' | 'failed' | 'cancelled';
|
|
1030
|
+
result?: unknown;
|
|
1031
|
+
error?: string | null;
|
|
1032
|
+
},
|
|
1033
|
+
): Promise<void> {
|
|
1034
|
+
await postAppRuntimeApi<{ ok: true }>(context, {
|
|
1035
|
+
action: 'settle_inline_child_run',
|
|
1036
|
+
...input,
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
|
|
956
1040
|
export async function saveResultsViaAppRuntime(
|
|
957
1041
|
context: WorkerRuntimeApiContext,
|
|
958
1042
|
input: RuntimeSaveResults,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
flattenStaticPipeline,
|
|
3
|
+
type PlayStaticPipeline,
|
|
4
|
+
} from '../plays/static-pipeline';
|
|
5
|
+
|
|
6
|
+
export type ChildExecutionStrategy = 'inline' | 'scheduled';
|
|
7
|
+
|
|
8
|
+
export type ChildExecutionDecision = {
|
|
9
|
+
strategy: ChildExecutionStrategy;
|
|
10
|
+
reason:
|
|
11
|
+
| 'scalar_child'
|
|
12
|
+
| 'missing_static_contract'
|
|
13
|
+
| 'dataset_child'
|
|
14
|
+
| 'event_wait_child'
|
|
15
|
+
| 'explicit_timeout';
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Choose execution placement from the child's authored contract. Scalar child
|
|
20
|
+
* plays are ordinary function composition. Dataset and event-wait children own
|
|
21
|
+
* independent durable state, so they remain scheduler jobs.
|
|
22
|
+
*/
|
|
23
|
+
export function resolveChildExecutionStrategy(input: {
|
|
24
|
+
pipeline: PlayStaticPipeline | null | undefined;
|
|
25
|
+
timeoutMs?: number | null;
|
|
26
|
+
}): ChildExecutionDecision {
|
|
27
|
+
if (input.timeoutMs != null) {
|
|
28
|
+
return { strategy: 'scheduled', reason: 'explicit_timeout' };
|
|
29
|
+
}
|
|
30
|
+
if (!input.pipeline) {
|
|
31
|
+
return { strategy: 'scheduled', reason: 'missing_static_contract' };
|
|
32
|
+
}
|
|
33
|
+
const substeps = flattenStaticPipeline(input.pipeline);
|
|
34
|
+
if (
|
|
35
|
+
input.pipeline.tableNamespace ||
|
|
36
|
+
input.pipeline.sheetContract ||
|
|
37
|
+
substeps.some((step) => step.type === 'dataset' || step.type === 'csv')
|
|
38
|
+
) {
|
|
39
|
+
return { strategy: 'scheduled', reason: 'dataset_child' };
|
|
40
|
+
}
|
|
41
|
+
if (
|
|
42
|
+
substeps.some(
|
|
43
|
+
(step) =>
|
|
44
|
+
step.type === 'tool' &&
|
|
45
|
+
(step.isEventWait === true || step.toolId === 'test_wait_for_event'),
|
|
46
|
+
)
|
|
47
|
+
) {
|
|
48
|
+
return { strategy: 'scheduled', reason: 'event_wait_child' };
|
|
49
|
+
}
|
|
50
|
+
return { strategy: 'inline', reason: 'scalar_child' };
|
|
51
|
+
}
|
|
@@ -56,6 +56,22 @@ export interface ChildRunIdInputs {
|
|
|
56
56
|
graphHash?: string | null;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* A failed durable runPlay receipt must launch a fresh child attempt while
|
|
61
|
+
* ordinary submit retries keep attaching to the original child. The receipt
|
|
62
|
+
* lease is the attempt fence, so it is the only extra identity required.
|
|
63
|
+
*/
|
|
64
|
+
export function childSubmitIdempotencyKey(input: {
|
|
65
|
+
receiptKey: string;
|
|
66
|
+
wasFailed: boolean;
|
|
67
|
+
leaseId?: string | null;
|
|
68
|
+
}): string {
|
|
69
|
+
const leaseId = input.leaseId?.trim();
|
|
70
|
+
return input.wasFailed && leaseId
|
|
71
|
+
? `${input.receiptKey}:claim:${leaseId}`
|
|
72
|
+
: input.receiptKey;
|
|
73
|
+
}
|
|
74
|
+
|
|
59
75
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
60
76
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
61
77
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { RunExecutionScope } from './run-execution-scope';
|
|
2
|
+
|
|
3
|
+
export type ChildRunPlacement = 'inline' | 'scheduled';
|
|
4
|
+
|
|
5
|
+
export interface ChildRunLifecycleMetadata {
|
|
6
|
+
placement: ChildRunPlacement;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ChildRunLifecycleAdapter<
|
|
10
|
+
Metadata extends ChildRunLifecycleMetadata,
|
|
11
|
+
Result,
|
|
12
|
+
> {
|
|
13
|
+
open(scope: RunExecutionScope, metadata: Metadata): Promise<void> | void;
|
|
14
|
+
started(scope: RunExecutionScope, metadata: Metadata): Promise<void> | void;
|
|
15
|
+
complete(
|
|
16
|
+
scope: RunExecutionScope,
|
|
17
|
+
metadata: Metadata,
|
|
18
|
+
result: Result,
|
|
19
|
+
): Promise<void> | void;
|
|
20
|
+
fail(
|
|
21
|
+
scope: RunExecutionScope,
|
|
22
|
+
metadata: Metadata,
|
|
23
|
+
error: unknown,
|
|
24
|
+
): Promise<void> | void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ExecuteChildRunLifecycleInput<
|
|
28
|
+
Metadata extends ChildRunLifecycleMetadata,
|
|
29
|
+
Result,
|
|
30
|
+
> {
|
|
31
|
+
scope: RunExecutionScope;
|
|
32
|
+
metadata: Metadata;
|
|
33
|
+
adapter: ChildRunLifecycleAdapter<Metadata, Result>;
|
|
34
|
+
execute(
|
|
35
|
+
scope: RunExecutionScope,
|
|
36
|
+
metadata: Metadata,
|
|
37
|
+
): Promise<Result> | Result;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Projects one logical child execution through its lifecycle. Placement is
|
|
42
|
+
* descriptive metadata; inline and scheduled children use the same protocol.
|
|
43
|
+
*/
|
|
44
|
+
export async function executeChildRunLifecycle<
|
|
45
|
+
Metadata extends ChildRunLifecycleMetadata,
|
|
46
|
+
Result,
|
|
47
|
+
>(input: ExecuteChildRunLifecycleInput<Metadata, Result>): Promise<Result> {
|
|
48
|
+
const { adapter, execute, metadata, scope } = input;
|
|
49
|
+
|
|
50
|
+
let result: Result;
|
|
51
|
+
try {
|
|
52
|
+
await adapter.open(scope, metadata);
|
|
53
|
+
await adapter.started(scope, metadata);
|
|
54
|
+
result = await execute(scope, metadata);
|
|
55
|
+
} catch (executionError) {
|
|
56
|
+
try {
|
|
57
|
+
await adapter.fail(scope, metadata, executionError);
|
|
58
|
+
} catch (failureProjectionError) {
|
|
59
|
+
throw new AggregateError(
|
|
60
|
+
[executionError, failureProjectionError],
|
|
61
|
+
'Child run execution and failure projection both failed',
|
|
62
|
+
{ cause: executionError },
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
throw executionError;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
await adapter.complete(scope, metadata, result);
|
|
69
|
+
return result;
|
|
70
|
+
}
|