deepline 0.1.209 → 0.1.211
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 +155 -139
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-chunk-plan.ts +15 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-latency-profile.ts +90 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +173 -103
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-receipts.ts +25 -92
- package/dist/bundling-sources/sdk/src/play.ts +4 -2
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/shared_libs/play-runtime/child-run-id.ts +4 -5
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +348 -109
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +1 -2
- package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +2 -4
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +42 -3
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +5 -1
- package/dist/bundling-sources/shared_libs/play-runtime/single-flight.ts +31 -0
- package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +12 -1
- package/dist/bundling-sources/shared_libs/plays/dataset.ts +41 -5
- package/dist/cli/index.js +94 -16
- package/dist/cli/index.mjs +94 -16
- package/dist/index.d.mts +18 -3
- package/dist/index.d.ts +18 -3
- package/dist/index.js +22 -5
- package/dist/index.mjs +22 -5
- package/package.json +1 -1
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
markWorkerToolReceiptResultCached,
|
|
55
55
|
markWorkerToolReceiptResultExecution,
|
|
56
56
|
planWorkerToolReceiptGroups,
|
|
57
|
+
resolveRunningReceiptWaitMaxAttempts,
|
|
57
58
|
resolveWorkerToolReceiptGroupWaitMaxAttempts,
|
|
58
59
|
resolveWorkerToolRuntimeTimeoutMs,
|
|
59
60
|
} from './tool-receipts';
|
|
@@ -1232,10 +1233,17 @@ export class WorkerToolBatchScheduler {
|
|
|
1232
1233
|
try {
|
|
1233
1234
|
const result = await this.waitForDurableToolReceipt({
|
|
1234
1235
|
receiptKey,
|
|
1235
|
-
maxAttempts:
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1236
|
+
maxAttempts: resolveRunningReceiptWaitMaxAttempts({
|
|
1237
|
+
ownerRunId: claim.receipt.runId,
|
|
1238
|
+
currentRunId: this.options.req.runId,
|
|
1239
|
+
updatedAt: claim.receipt.updatedAt,
|
|
1240
|
+
requestedMaxAttempts:
|
|
1241
|
+
resolveWorkerToolReceiptGroupWaitMaxAttempts(
|
|
1242
|
+
group,
|
|
1243
|
+
(request) => request.receiptWaitMaxAttempts,
|
|
1244
|
+
),
|
|
1245
|
+
nowMs: this.options.nowMs(),
|
|
1246
|
+
}),
|
|
1239
1247
|
});
|
|
1240
1248
|
if (groupState.forceDurableRefresh) {
|
|
1241
1249
|
return await this.claimForcedDurableToolReceiptGroupAfterWait(
|
|
@@ -1992,8 +2000,14 @@ export class WorkerToolBatchScheduler {
|
|
|
1992
2000
|
toolId: string,
|
|
1993
2001
|
requests: WorkerToolBatchRequest[],
|
|
1994
2002
|
): Promise<void> {
|
|
2003
|
+
const receiptPrepareStartedAt = this.options.nowMs();
|
|
1995
2004
|
const { claimedRequests, deferredClaimedRequests } =
|
|
1996
2005
|
await this.prepareDurableToolRequests(requests);
|
|
2006
|
+
this.options.recordPerfTrace?.({
|
|
2007
|
+
phase: 'runner.tool.receipt_prepare',
|
|
2008
|
+
ms: this.options.nowMs() - receiptPrepareStartedAt,
|
|
2009
|
+
extra: { toolId, requests: requests.length },
|
|
2010
|
+
});
|
|
1997
2011
|
await this.executeClaimedToolRequests(
|
|
1998
2012
|
toolId,
|
|
1999
2013
|
requests.length,
|
|
@@ -2063,6 +2077,7 @@ export class WorkerToolBatchScheduler {
|
|
|
2063
2077
|
// charges tool budget, holds a global tool-concurrency slot, and
|
|
2064
2078
|
// applies per-(org,provider) pacing before the call runs.
|
|
2065
2079
|
let slot: { release: () => void };
|
|
2080
|
+
const admissionStartedAt = this.options.nowMs();
|
|
2066
2081
|
try {
|
|
2067
2082
|
slot = await this.options.governor.acquireToolSlot(toolId, {
|
|
2068
2083
|
signal: this.options.abortSignal,
|
|
@@ -2074,6 +2089,11 @@ export class WorkerToolBatchScheduler {
|
|
|
2074
2089
|
);
|
|
2075
2090
|
return;
|
|
2076
2091
|
}
|
|
2092
|
+
this.options.recordPerfTrace?.({
|
|
2093
|
+
phase: 'runner.tool.admission',
|
|
2094
|
+
ms: this.options.nowMs() - admissionStartedAt,
|
|
2095
|
+
extra: { toolId, requests: 1 },
|
|
2096
|
+
});
|
|
2077
2097
|
try {
|
|
2078
2098
|
let result: unknown;
|
|
2079
2099
|
try {
|
|
@@ -2092,59 +2112,68 @@ export class WorkerToolBatchScheduler {
|
|
|
2092
2112
|
claimed,
|
|
2093
2113
|
})
|
|
2094
2114
|
: null;
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
this.options.
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
this.
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2115
|
+
const providerStartedAt = this.options.nowMs();
|
|
2116
|
+
try {
|
|
2117
|
+
result = await executeWithWorkerReceiptHeartbeats({
|
|
2118
|
+
playName: this.options.req.playName,
|
|
2119
|
+
runId: this.options.req.runId,
|
|
2120
|
+
runAttempt: this.options.req.runAttempt ?? 0,
|
|
2121
|
+
...(this.options.receiptStore
|
|
2122
|
+
? { receiptStore: this.options.receiptStore }
|
|
2123
|
+
: {}),
|
|
2124
|
+
budgetMeter: this.options.budgetMeter,
|
|
2125
|
+
heartbeatIntervalMs:
|
|
2126
|
+
this.options.runtimeReceiptHeartbeatIntervalMs,
|
|
2127
|
+
targets: claimed.receiptKey
|
|
2128
|
+
? [
|
|
2129
|
+
{
|
|
2130
|
+
key: claimed.receiptKey,
|
|
2131
|
+
leaseId: claimed.receiptLeaseId,
|
|
2132
|
+
},
|
|
2133
|
+
]
|
|
2134
|
+
: [],
|
|
2135
|
+
execute: () =>
|
|
2136
|
+
this.options.executeTool({
|
|
2137
|
+
id: request.id,
|
|
2138
|
+
toolId,
|
|
2139
|
+
input: request.input,
|
|
2140
|
+
workflowStep: request.workflowStep,
|
|
2141
|
+
callbacks: this.options.callbacks,
|
|
2142
|
+
onProviderBackpressure: (retryAfterMs) =>
|
|
2143
|
+
this.reportBackpressure(toolId, retryAfterMs),
|
|
2144
|
+
onRetryAttempt: () =>
|
|
2145
|
+
this.options.governor.chargeBudget('retry'),
|
|
2146
|
+
transientHttpRetrySafe:
|
|
2147
|
+
toolContract?.retrySafeTransientHttp === true,
|
|
2148
|
+
...(durableReceiptKey
|
|
2149
|
+
? {
|
|
2150
|
+
durableCallReceiptKey: durableReceiptKey,
|
|
2151
|
+
executionAuthScopeDigest:
|
|
2152
|
+
request.executionAuthScopeDigest,
|
|
2153
|
+
providerIdempotencyKey:
|
|
2154
|
+
providerIdempotencyKeyForClaimed({
|
|
2155
|
+
req: this.options.req,
|
|
2156
|
+
claimed,
|
|
2157
|
+
}),
|
|
2158
|
+
}
|
|
2159
|
+
: {}),
|
|
2160
|
+
runtimeTimeoutMs: resolveWorkerToolRuntimeTimeoutMs(
|
|
2161
|
+
[claimed],
|
|
2162
|
+
{
|
|
2163
|
+
resolveOwnerTimeoutMs: (request) =>
|
|
2164
|
+
request.runtimeTimeoutMs,
|
|
2165
|
+
},
|
|
2166
|
+
),
|
|
2167
|
+
directOptions: request.directOptions,
|
|
2168
|
+
}),
|
|
2169
|
+
});
|
|
2170
|
+
} finally {
|
|
2171
|
+
this.options.recordPerfTrace?.({
|
|
2172
|
+
phase: 'runner.tool.provider',
|
|
2173
|
+
ms: this.options.nowMs() - providerStartedAt,
|
|
2174
|
+
extra: { toolId, requests: 1 },
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2148
2177
|
} catch (error) {
|
|
2149
2178
|
if (error instanceof RuntimeLeaseLostError) {
|
|
2150
2179
|
this.rejectRequests(claimed, error);
|
|
@@ -2165,10 +2194,16 @@ export class WorkerToolBatchScheduler {
|
|
|
2165
2194
|
return;
|
|
2166
2195
|
}
|
|
2167
2196
|
try {
|
|
2197
|
+
const receiptCompleteStartedAt = this.options.nowMs();
|
|
2168
2198
|
this.settleRequests(
|
|
2169
2199
|
claimed,
|
|
2170
2200
|
await this.completeDurableToolRequest(claimed, result),
|
|
2171
2201
|
);
|
|
2202
|
+
this.options.recordPerfTrace?.({
|
|
2203
|
+
phase: 'runner.tool.receipt_complete',
|
|
2204
|
+
ms: this.options.nowMs() - receiptCompleteStartedAt,
|
|
2205
|
+
extra: { toolId, requests: 1 },
|
|
2206
|
+
});
|
|
2172
2207
|
} catch (receiptError) {
|
|
2173
2208
|
this.rejectRequests(claimed, receiptError);
|
|
2174
2209
|
}
|
|
@@ -2253,12 +2288,22 @@ export class WorkerToolBatchScheduler {
|
|
|
2253
2288
|
}
|
|
2254
2289
|
// One provider call per batch -> one tool slot (budget + global
|
|
2255
2290
|
// concurrency + per-(org,provider) pacing) around the whole batch.
|
|
2291
|
+
const admissionStartedAt = this.options.nowMs();
|
|
2256
2292
|
const slot = await this.options.governor.acquireToolSlot(
|
|
2257
2293
|
batch.batchOperation,
|
|
2258
2294
|
{
|
|
2259
2295
|
signal: this.options.abortSignal,
|
|
2260
2296
|
},
|
|
2261
2297
|
);
|
|
2298
|
+
this.options.recordPerfTrace?.({
|
|
2299
|
+
phase: 'runner.tool.admission',
|
|
2300
|
+
ms: this.options.nowMs() - admissionStartedAt,
|
|
2301
|
+
extra: {
|
|
2302
|
+
toolId: batch.batchOperation,
|
|
2303
|
+
requests: batch.memberRequests.length,
|
|
2304
|
+
batched: true,
|
|
2305
|
+
},
|
|
2306
|
+
});
|
|
2262
2307
|
try {
|
|
2263
2308
|
await this.markAdmittedToolRequestsRunning(batch.memberRequests);
|
|
2264
2309
|
this.options.budgetMeter?.count('provider');
|
|
@@ -2273,54 +2318,69 @@ export class WorkerToolBatchScheduler {
|
|
|
2273
2318
|
batchOperation: batch.batchOperation,
|
|
2274
2319
|
receiptKeys,
|
|
2275
2320
|
});
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2321
|
+
const providerStartedAt = this.options.nowMs();
|
|
2322
|
+
try {
|
|
2323
|
+
return await executeWithWorkerReceiptHeartbeats({
|
|
2324
|
+
playName: this.options.req.playName,
|
|
2325
|
+
runId: this.options.req.runId,
|
|
2326
|
+
runAttempt: this.options.req.runAttempt ?? 0,
|
|
2327
|
+
...(this.options.receiptStore
|
|
2328
|
+
? { receiptStore: this.options.receiptStore }
|
|
2329
|
+
: {}),
|
|
2330
|
+
budgetMeter: this.options.budgetMeter,
|
|
2331
|
+
heartbeatIntervalMs:
|
|
2332
|
+
this.options.runtimeReceiptHeartbeatIntervalMs,
|
|
2333
|
+
targets: batch.memberRequests.map((member) => ({
|
|
2334
|
+
key: member.receiptKey,
|
|
2335
|
+
leaseId: member.receiptLeaseId,
|
|
2336
|
+
})),
|
|
2337
|
+
execute: () =>
|
|
2338
|
+
this.options.executeTool({
|
|
2339
|
+
id: `batch:${batch.memberRequests.map((request) => request.request.id).join('|')}`,
|
|
2340
|
+
toolId: batch.batchOperation,
|
|
2341
|
+
input: batch.batchPayload,
|
|
2342
|
+
callbacks: this.options.callbacks,
|
|
2343
|
+
onProviderBackpressure: (retryAfterMs) =>
|
|
2344
|
+
this.reportBackpressure(input.sourceToolId, retryAfterMs),
|
|
2345
|
+
onRetryAttempt: () =>
|
|
2346
|
+
this.options.governor.chargeBudget('retry'),
|
|
2347
|
+
transientHttpRetrySafe:
|
|
2348
|
+
toolContract?.retrySafeTransientHttp === true,
|
|
2349
|
+
durableCallReceiptKey: aggregateReceiptKey,
|
|
2350
|
+
executionAuthScopeDigest:
|
|
2351
|
+
batch.memberRequests[0]?.request.executionAuthScopeDigest ??
|
|
2352
|
+
null,
|
|
2353
|
+
providerIdempotencyKey: buildBatchProviderIdempotencyKey({
|
|
2354
|
+
aggregateReceiptKey,
|
|
2355
|
+
receiptKeys,
|
|
2356
|
+
providerIdempotencyKeys: batch.memberRequests.map(
|
|
2357
|
+
(member) =>
|
|
2358
|
+
providerIdempotencyKeyForClaimed({
|
|
2359
|
+
req: this.options.req,
|
|
2360
|
+
claimed: member,
|
|
2361
|
+
}),
|
|
2362
|
+
),
|
|
2363
|
+
}),
|
|
2364
|
+
runtimeTimeoutMs: resolveWorkerToolRuntimeTimeoutMs(
|
|
2365
|
+
batch.memberRequests,
|
|
2366
|
+
{
|
|
2367
|
+
resolveOwnerTimeoutMs: (request) =>
|
|
2368
|
+
request.runtimeTimeoutMs,
|
|
2369
|
+
},
|
|
2313
2370
|
),
|
|
2314
2371
|
}),
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2372
|
+
});
|
|
2373
|
+
} finally {
|
|
2374
|
+
this.options.recordPerfTrace?.({
|
|
2375
|
+
phase: 'runner.tool.provider',
|
|
2376
|
+
ms: this.options.nowMs() - providerStartedAt,
|
|
2377
|
+
extra: {
|
|
2378
|
+
toolId: batch.batchOperation,
|
|
2379
|
+
requests: batch.memberRequests.length,
|
|
2380
|
+
batched: true,
|
|
2381
|
+
},
|
|
2382
|
+
});
|
|
2383
|
+
}
|
|
2324
2384
|
} finally {
|
|
2325
2385
|
slot.release();
|
|
2326
2386
|
}
|
|
@@ -2381,6 +2441,7 @@ export class WorkerToolBatchScheduler {
|
|
|
2381
2441
|
: entry.request.memberRequests.map(() => null);
|
|
2382
2442
|
let completedResults: WorkerToolCompletionResult[];
|
|
2383
2443
|
try {
|
|
2444
|
+
const receiptCompleteStartedAt = this.options.nowMs();
|
|
2384
2445
|
completedResults = await this.completeDurableToolRequests(
|
|
2385
2446
|
entry.request.memberRequests.map((claimed, index) => ({
|
|
2386
2447
|
claimed,
|
|
@@ -2391,6 +2452,15 @@ export class WorkerToolBatchScheduler {
|
|
|
2391
2452
|
),
|
|
2392
2453
|
})),
|
|
2393
2454
|
);
|
|
2455
|
+
this.options.recordPerfTrace?.({
|
|
2456
|
+
phase: 'runner.tool.receipt_complete',
|
|
2457
|
+
ms: this.options.nowMs() - receiptCompleteStartedAt,
|
|
2458
|
+
extra: {
|
|
2459
|
+
toolId: entry.request.batchOperation,
|
|
2460
|
+
requests: entry.request.memberRequests.length,
|
|
2461
|
+
batched: true,
|
|
2462
|
+
},
|
|
2463
|
+
});
|
|
2394
2464
|
} catch (receiptError) {
|
|
2395
2465
|
for (const claimed of entry.request.memberRequests) {
|
|
2396
2466
|
this.rejectRequests(claimed, receiptError);
|
|
@@ -39,106 +39,39 @@ export function canReclaimTimedOutWorkerToolReceipt(input: {
|
|
|
39
39
|
* conservative constant, not a heartbeat multiple.
|
|
40
40
|
*/
|
|
41
41
|
export const RUNNING_RECEIPT_DEAD_OWNER_STALENESS_MS = 60_000;
|
|
42
|
+
export const RUNNING_RECEIPT_POLL_INTERVAL_MS = 250;
|
|
42
43
|
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
* the completion poll.
|
|
46
|
-
*
|
|
47
|
-
* The poll (`waitForCompletedRuntimeReceipt`) issues one service-binding
|
|
48
|
-
* subrequest per 250ms tick, up to the receipt's wait budget (~1320 ticks for a
|
|
49
|
-
* 5.5-minute default). On resume, a map can carry many receipts left `running`
|
|
50
|
-
* by a DEAD prior run; polling each one burns its full tick budget waiting on a
|
|
51
|
-
* receipt no live worker will complete, which blows Cloudflare's ~1000
|
|
52
|
-
* subrequests-per-invocation budget and kills the resume with
|
|
53
|
-
* "Too many subrequests by single Worker invocation" (prod resume deaths
|
|
54
|
-
* 20260703t230209 / 20260703t232104).
|
|
55
|
-
*
|
|
56
|
-
* A receipt is a dead-owner orphan when it is owned by a DIFFERENT run (a
|
|
57
|
-
* same-run `running` is this run replaying — its owner may still be live) and
|
|
58
|
-
* its last write is stale (or missing entirely — a live owner would carry a
|
|
59
|
-
* recent claim timestamp). Those reclaim immediately, matching the dispatch
|
|
60
|
-
* code's own contract ("on resume it reclaims and re-executes"). A live or
|
|
61
|
-
* freshly-claimed owner keeps the poll.
|
|
62
|
-
*/
|
|
63
|
-
export function shouldReclaimRunningReceiptWithoutPolling(input: {
|
|
44
|
+
/** Bound a follower poll by when a different-run owner becomes reclaimable. */
|
|
45
|
+
export function resolveRunningReceiptWaitMaxAttempts(input: {
|
|
64
46
|
ownerRunId?: string | null;
|
|
65
47
|
currentRunId: string;
|
|
66
48
|
updatedAt?: string | null;
|
|
49
|
+
requestedMaxAttempts: number;
|
|
67
50
|
nowMs?: number;
|
|
68
51
|
stalenessFloorMs?: number;
|
|
69
|
-
|
|
70
|
-
|
|
52
|
+
pollIntervalMs?: number;
|
|
53
|
+
}): number {
|
|
54
|
+
const requested = Math.max(1, Math.floor(input.requestedMaxAttempts));
|
|
71
55
|
const ownerRunId = input.ownerRunId?.trim() ?? '';
|
|
72
|
-
|
|
73
|
-
if (!
|
|
74
|
-
|
|
75
|
-
input.stalenessFloorMs ?? RUNNING_RECEIPT_DEAD_OWNER_STALENESS_MS;
|
|
76
|
-
// No freshness signal at all: treat as stale. A live owner would carry a
|
|
77
|
-
// recent claim timestamp; an orphan from a dead run may carry none.
|
|
78
|
-
if (!input.updatedAt) return true;
|
|
79
|
-
const updatedAtMs = Date.parse(input.updatedAt);
|
|
80
|
-
if (!Number.isFinite(updatedAtMs)) return true;
|
|
81
|
-
const now = input.nowMs ?? Date.now();
|
|
82
|
-
return now - updatedAtMs >= floorMs;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** Metadata a `running` receipt carries about its owner and last write. */
|
|
86
|
-
export interface RunningReceiptOwnershipInput {
|
|
87
|
-
ownerRunId?: string | null;
|
|
88
|
-
currentRunId: string;
|
|
89
|
-
updatedAt?: string | null;
|
|
90
|
-
nowMs?: number;
|
|
91
|
-
stalenessFloorMs?: number;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Outcome of polling a `running` receipt for completion. */
|
|
95
|
-
export type RunningReceiptPollOutcome =
|
|
96
|
-
/** The poll saw the receipt complete and already resolved the group. */
|
|
97
|
-
| { kind: 'completed' }
|
|
98
|
-
/** The poll timed out; the receipt is still `running`. */
|
|
99
|
-
| { kind: 'timed_out'; error: unknown }
|
|
100
|
-
/** The poll failed for a non-timeout reason (e.g. the receipt failed). */
|
|
101
|
-
| { kind: 'errored'; error: unknown };
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Orchestrate the resume-time handling of a `running` receipt: reclaim it
|
|
105
|
-
* immediately when it is a dead-owner orphan, otherwise poll for completion and
|
|
106
|
-
* only reclaim after the poll times out. Kept as a pure, dependency-injected
|
|
107
|
-
* function (the poll and reclaim side effects are passed in) so the
|
|
108
|
-
* "skip the poll for a dead owner" decision is unit-testable with a poll counter
|
|
109
|
-
* — the Worker scheduler that owns the real effects imports `cloudflare:workers`
|
|
110
|
-
* and cannot be loaded under vitest.
|
|
111
|
-
*
|
|
112
|
-
* Behavior (must match the scheduler's prior inline logic exactly):
|
|
113
|
-
* - dead-owner + reclaim-eligible → `reclaim` now, never polling;
|
|
114
|
-
* - otherwise `poll`; on `completed` the group is already resolved (return []);
|
|
115
|
-
* on a non-timeout error `reject` and stop; on timeout, `reclaim` if still
|
|
116
|
-
* eligible else `reject` with the timeout error.
|
|
117
|
-
*/
|
|
118
|
-
export async function reclaimRunningReceiptGroupAfterWait<TClaimed>(input: {
|
|
119
|
-
ownership: RunningReceiptOwnershipInput;
|
|
120
|
-
timeoutError: unknown;
|
|
121
|
-
poll: () => Promise<RunningReceiptPollOutcome>;
|
|
122
|
-
reclaim: (rejectionOnFallthrough: unknown) => Promise<TClaimed[]>;
|
|
123
|
-
reject: (error: unknown) => void;
|
|
124
|
-
}): Promise<TClaimed[]> {
|
|
125
|
-
if (
|
|
126
|
-
shouldReclaimRunningReceiptWithoutPolling(input.ownership) &&
|
|
127
|
-
canReclaimTimedOutWorkerToolReceipt(input.ownership)
|
|
128
|
-
) {
|
|
129
|
-
return await input.reclaim(input.timeoutError);
|
|
130
|
-
}
|
|
131
|
-
const outcome = await input.poll();
|
|
132
|
-
if (outcome.kind === 'completed') return [];
|
|
133
|
-
if (outcome.kind === 'errored') {
|
|
134
|
-
input.reject(outcome.error);
|
|
135
|
-
return [];
|
|
136
|
-
}
|
|
137
|
-
if (!canReclaimTimedOutWorkerToolReceipt(input.ownership)) {
|
|
138
|
-
input.reject(outcome.error);
|
|
139
|
-
return [];
|
|
56
|
+
const currentRunId = input.currentRunId.trim();
|
|
57
|
+
if (!ownerRunId || !currentRunId || ownerRunId === currentRunId) {
|
|
58
|
+
return requested;
|
|
140
59
|
}
|
|
141
|
-
|
|
60
|
+
const updatedAtMs = input.updatedAt ? Date.parse(input.updatedAt) : NaN;
|
|
61
|
+
if (!Number.isFinite(updatedAtMs)) return 1;
|
|
62
|
+
const remainingMs = Math.max(
|
|
63
|
+
0,
|
|
64
|
+
updatedAtMs +
|
|
65
|
+
(input.stalenessFloorMs ?? RUNNING_RECEIPT_DEAD_OWNER_STALENESS_MS) -
|
|
66
|
+
(input.nowMs ?? Date.now()),
|
|
67
|
+
);
|
|
68
|
+
const attemptsUntilReclaim = Math.max(
|
|
69
|
+
1,
|
|
70
|
+
Math.floor(
|
|
71
|
+
remainingMs / (input.pollIntervalMs ?? RUNNING_RECEIPT_POLL_INTERVAL_MS),
|
|
72
|
+
) + 1,
|
|
73
|
+
);
|
|
74
|
+
return Math.min(requested, attemptsUntilReclaim);
|
|
142
75
|
}
|
|
143
76
|
|
|
144
77
|
export function canReclaimFailedWorkerToolReceipt(input: {
|
|
@@ -173,6 +173,8 @@ export type PlayBindings = {
|
|
|
173
173
|
* older clients can continue to register revisions during the migration.
|
|
174
174
|
*/
|
|
175
175
|
description?: string;
|
|
176
|
+
/** Allow compilers to bundle this named handler directly without a child run. */
|
|
177
|
+
inline?: boolean;
|
|
176
178
|
/** Optional per-run billing controls enforced by the runtime. */
|
|
177
179
|
billing?: {
|
|
178
180
|
/** Stop the run before a billed action would push total run credits above this cap. */
|
|
@@ -400,7 +402,7 @@ export type StepOptions<Row, Value = unknown> = {
|
|
|
400
402
|
* Legacy dataset-column recompute flag accepted for older authored plays.
|
|
401
403
|
*
|
|
402
404
|
* Prefer putting freshness on the actual reusable call
|
|
403
|
-
* (`ctx.tools.execute`, `ctx.step`,
|
|
405
|
+
* (`ctx.tools.execute`, `ctx.step`, or `ctx.fetch`).
|
|
404
406
|
*/
|
|
405
407
|
readonly recompute?: boolean;
|
|
406
408
|
/** Legacy error-recompute flag accepted for older authored plays. */
|
|
@@ -889,7 +891,7 @@ export interface DeeplinePlayRuntimeContext {
|
|
|
889
891
|
key: string,
|
|
890
892
|
playRef: string | PlayReferenceLike,
|
|
891
893
|
input: Record<string, unknown>,
|
|
892
|
-
options: { description: string
|
|
894
|
+
options: { description: string },
|
|
893
895
|
): Promise<TOutput>;
|
|
894
896
|
|
|
895
897
|
/**
|
|
@@ -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.211',
|
|
110
110
|
apiContract: '2026-06-dataset-handle-results-hard-cutover',
|
|
111
111
|
supportPolicy: {
|
|
112
|
-
latest: '0.1.
|
|
112
|
+
latest: '0.1.211',
|
|
113
113
|
minimumSupported: '0.1.53',
|
|
114
114
|
deprecatedBelow: '0.1.53',
|
|
115
115
|
commandMinimumSupported: [
|
|
@@ -44,8 +44,7 @@ export interface ChildRunIdInputs {
|
|
|
44
44
|
key?: string | null;
|
|
45
45
|
/**
|
|
46
46
|
* Canonical durable runPlay semantic key when the launching substrate has one.
|
|
47
|
-
*
|
|
48
|
-
* receipt identity and child run identity cannot drift apart.
|
|
47
|
+
* This scopes each fresh child invocation to its parent call site and row.
|
|
49
48
|
*/
|
|
50
49
|
runPlaySemanticKey?: string | null;
|
|
51
50
|
/** Child input payload (semantic-fallback input). */
|
|
@@ -57,9 +56,9 @@ export interface ChildRunIdInputs {
|
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
/**
|
|
60
|
-
* A failed
|
|
61
|
-
*
|
|
62
|
-
* lease is the attempt fence
|
|
59
|
+
* A failed child launch must use a fresh attempt identity while ordinary
|
|
60
|
+
* transport retries keep attaching to the original child. The caller-provided
|
|
61
|
+
* lease is the attempt fence.
|
|
63
62
|
*/
|
|
64
63
|
export function childSubmitIdempotencyKey(input: {
|
|
65
64
|
receiptKey: string;
|