deepline 0.3.44 → 0.3.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +2 -3
  2. package/dist/bundling-sources/sdk/src/errors.ts +2 -2
  3. package/dist/bundling-sources/sdk/src/http.ts +1 -1
  4. package/dist/bundling-sources/sdk/src/play.ts +7 -10
  5. package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +3 -3
  6. package/dist/bundling-sources/sdk/src/release.ts +3 -3
  7. package/dist/bundling-sources/sdk/src/version.ts +2 -2
  8. package/dist/bundling-sources/shared_libs/play-data-plane/contracts.ts +25 -0
  9. package/dist/bundling-sources/shared_libs/play-data-plane/index.ts +1 -0
  10. package/dist/bundling-sources/shared_libs/play-data-plane/sheet-contract.ts +1 -1
  11. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +19 -19
  12. package/dist/bundling-sources/shared_libs/play-runtime/backend.ts +5 -6
  13. package/dist/bundling-sources/shared_libs/play-runtime/cell-staleness.ts +1 -66
  14. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +1551 -276
  15. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +14 -3
  16. package/dist/bundling-sources/shared_libs/play-runtime/dataset-id.ts +4 -3
  17. package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +3 -4
  18. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +2 -2
  19. package/dist/bundling-sources/shared_libs/play-runtime/email-status.ts +1 -275
  20. package/dist/bundling-sources/shared_libs/play-runtime/execution-ledger-store.ts +1 -1
  21. package/dist/bundling-sources/shared_libs/play-runtime/extractor-targets.ts +1 -106
  22. package/dist/bundling-sources/shared_libs/play-runtime/gateway-postgres-admission.ts +1 -2
  23. package/dist/bundling-sources/shared_libs/play-runtime/governor/in-memory-rate-state-backend.ts +13 -4
  24. package/dist/bundling-sources/shared_libs/play-runtime/live-events.ts +1 -5
  25. package/dist/bundling-sources/shared_libs/play-runtime/map-row-identity.ts +2 -3
  26. package/dist/bundling-sources/shared_libs/play-runtime/map-row-outcome.ts +2 -1
  27. package/dist/bundling-sources/shared_libs/play-runtime/modal-runtime-config.ts +1 -1
  28. package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +1 -1
  29. package/dist/bundling-sources/shared_libs/play-runtime/projection.ts +5 -5
  30. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +3 -3
  31. package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +1 -1
  32. package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +1 -1
  33. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +4 -4
  34. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-modal-fallback.ts +3 -3
  35. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +8 -8
  36. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-session-execution.ts +11 -1
  37. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +26 -8
  38. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +7 -7
  39. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal.ts +23 -11
  40. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/bundle.ts +13 -2
  41. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/index.ts +2 -2
  42. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runner-events.ts +1 -1
  43. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +1 -1
  44. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +58 -2
  45. package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +1 -1
  46. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +6 -3
  47. package/dist/bundling-sources/shared_libs/play-runtime/runtime-constants.ts +1 -1
  48. package/dist/bundling-sources/shared_libs/play-runtime/runtime-constraints.ts +1 -2
  49. package/dist/bundling-sources/shared_libs/play-runtime/runtime-env.ts +55 -0
  50. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +418 -0
  51. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/index.ts +452 -0
  52. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +304 -0
  53. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/testkit.ts +83 -0
  54. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runtime-limits.ts +1 -109
  55. package/dist/bundling-sources/shared_libs/play-runtime/step-program-dataset-builder.ts +1 -1
  56. package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +1 -1
  57. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/always-fresh-adapter.ts +50 -0
  58. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/contract.ts +135 -0
  59. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +291 -0
  60. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/native-batch.ts +335 -0
  61. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +904 -0
  62. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +522 -0
  63. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/testkit.ts +165 -0
  64. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +1 -1
  65. package/dist/bundling-sources/shared_libs/play-runtime/tool-execution-outcome.ts +6 -5
  66. package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +1 -1
  67. package/dist/bundling-sources/shared_libs/play-runtime/tool-result-types.ts +1 -206
  68. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +10 -7
  69. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +1 -1
  70. package/dist/bundling-sources/shared_libs/plays/artifact-types.ts +8 -3
  71. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +4 -4
  72. package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +3 -6
  73. package/dist/bundling-sources/shared_libs/plays/cell-staleness.ts +66 -0
  74. package/dist/bundling-sources/shared_libs/plays/compiler-manifest.ts +1 -1
  75. package/dist/bundling-sources/shared_libs/plays/contracts.ts +2 -2
  76. package/dist/bundling-sources/shared_libs/plays/core.ts +466 -0
  77. package/dist/bundling-sources/shared_libs/plays/email-status.ts +287 -0
  78. package/dist/bundling-sources/shared_libs/plays/extractor-targets.ts +106 -0
  79. package/dist/bundling-sources/shared_libs/plays/row-identity.ts +1 -3
  80. package/dist/bundling-sources/shared_libs/plays/runtime-constraints.ts +2 -0
  81. package/dist/bundling-sources/shared_libs/plays/runtime-validation.ts +4 -2
  82. package/dist/bundling-sources/shared_libs/plays/sandbox-runtime-limits.ts +109 -0
  83. package/dist/bundling-sources/shared_libs/plays/tool-execution-error.ts +624 -0
  84. package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +206 -0
  85. package/dist/bundling-sources/shared_libs/security/safe-outbound-fetch.ts +1 -1
  86. package/dist/cli/index.js +174 -168
  87. package/dist/cli/index.mjs +182 -169
  88. package/dist/{compiler-manifest-DwYe2C2S.d.mts → compiler-manifest-BuoqasqI.d.mts} +585 -540
  89. package/dist/{compiler-manifest-DwYe2C2S.d.ts → compiler-manifest-BuoqasqI.d.ts} +585 -540
  90. package/dist/index.d.mts +9 -2
  91. package/dist/index.d.ts +9 -2
  92. package/dist/index.js +47 -40
  93. package/dist/index.mjs +47 -40
  94. package/dist/install-integrity.json +24 -3
  95. package/dist/plays/bundle-play-file.d.mts +4 -61
  96. package/dist/plays/bundle-play-file.d.ts +4 -61
  97. package/dist/plays/bundle-play-file.mjs +157 -155
  98. package/package.json +9 -6
  99. /package/dist/bundling-sources/shared_libs/{play-runtime → plays}/tool-response-contract.ts +0 -0
@@ -0,0 +1,904 @@
1
+ import type { ToolExecuteResult } from '../tool-result';
2
+ import { createRuntimeReceiptHeartbeatSupervisor } from '../receipt-heartbeat-supervisor';
3
+ import {
4
+ ToolCallReceiptLeaseLostError,
5
+ type ToolCallReceiptLease,
6
+ type ToolCallReceiptRecoverySource,
7
+ type ToolResultReceipts,
8
+ } from './contract';
9
+
10
+ /**
11
+ * The durable state of one map-member Tool Result Receipt. This is deliberately
12
+ * smaller than RuntimeStepReceipt: row persistence and provider transport are
13
+ * not receipt-cohort concerns.
14
+ */
15
+ export type ToolCallReceiptRecord = {
16
+ resultReceiptKey: string;
17
+ status: 'queued' | 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
18
+ result?: ToolExecuteResult;
19
+ error?: unknown;
20
+ lease?: ToolCallReceiptLease;
21
+ leaseExpiresAt?: string | null;
22
+ claimState?: 'claimed' | 'existing';
23
+ };
24
+
25
+ export type ToolCallReceiptCohortMember = {
26
+ /** A caller-local identity; it is never persisted. */
27
+ memberId: string;
28
+ resultReceiptKey: string;
29
+ force?: boolean;
30
+ forceFailedRefresh?: boolean;
31
+ /** Number of poll reads before a foreign/incomplete receipt is reclaimed. */
32
+ waitMaxAttempts?: number;
33
+ };
34
+
35
+ export type ToolCallReceiptCohortStore = {
36
+ read(input: {
37
+ resultReceiptKeys: string[];
38
+ }): Promise<ToolCallReceiptRecord[]>;
39
+ /**
40
+ * The Adapter may partition this vector by refresh policy to reach a legacy
41
+ * gateway that accepts one policy per RPC. Results are key-aligned by value,
42
+ * not array position, so duplicates are forbidden at this seam.
43
+ */
44
+ claim(input: {
45
+ ownerRunId: string;
46
+ ownerAttempt: number;
47
+ receipts: Array<{
48
+ resultReceiptKey: string;
49
+ force: boolean;
50
+ forceFailedRefresh: boolean;
51
+ reclaimRunning: boolean;
52
+ }>;
53
+ }): Promise<ToolCallReceiptRecord[]>;
54
+ heartbeat(input: {
55
+ ownerRunId: string;
56
+ ownerAttempt: number;
57
+ receipts: Array<{ resultReceiptKey: string; lease: ToolCallReceiptLease }>;
58
+ }): Promise<ToolCallReceiptRecord[]>;
59
+ complete(input: {
60
+ ownerRunId: string;
61
+ ownerAttempt: number;
62
+ receipts: Array<{
63
+ resultReceiptKey: string;
64
+ lease: ToolCallReceiptLease;
65
+ result: ToolExecuteResult;
66
+ }>;
67
+ }): Promise<ToolCallReceiptRecord[]>;
68
+ fail(input: {
69
+ ownerRunId: string;
70
+ ownerAttempt: number;
71
+ receipts: Array<{
72
+ resultReceiptKey: string;
73
+ lease: ToolCallReceiptLease;
74
+ error: unknown;
75
+ }>;
76
+ }): Promise<ToolCallReceiptRecord[]>;
77
+ };
78
+
79
+ export type ToolCallReceiptCohortMemberState =
80
+ | {
81
+ kind: 'completed';
82
+ result: ToolExecuteResult;
83
+ source: 'cache' | 'in_flight' | 'owner';
84
+ }
85
+ | { kind: 'failed'; error: unknown }
86
+ | {
87
+ kind: 'owned';
88
+ resultReceiptKey: string;
89
+ lease: ToolCallReceiptLease;
90
+ leaseExpiresAt?: string | null;
91
+ receipts: ToolResultReceipts;
92
+ }
93
+ | {
94
+ kind: 'following';
95
+ resultReceiptKey: string;
96
+ ownerMemberId: string;
97
+ result: Promise<ToolExecuteResult>;
98
+ }
99
+ | {
100
+ /** A foreign receipt is being hydrated/reclaimed by the cohort. */
101
+ kind: 'recovering';
102
+ resultReceiptKey: string;
103
+ result: Promise<ToolExecuteResult>;
104
+ };
105
+
106
+ export type ToolCallReceiptCohort = {
107
+ member(memberId: string): ToolCallReceiptCohortMemberState;
108
+ /** A native batch uses this after splitting one physical provider response. */
109
+ complete(
110
+ input: Array<{ memberId: string; result: ToolExecuteResult }>,
111
+ ): Promise<Map<string, ToolExecuteResult>>;
112
+ fail(input: Array<{ memberId: string; error: unknown }>): Promise<void>;
113
+ /** One RPC for every distinct owned receipt, including queued map tail work. */
114
+ heartbeat(): Promise<'active' | 'terminal'>;
115
+ /**
116
+ * Starts the cohort's ownership supervisor. Per-member receipt views only
117
+ * assert this shared fence; they never fragment it into one heartbeat RPC per
118
+ * provider call.
119
+ */
120
+ startHeartbeat(input: {
121
+ intervalMs: number;
122
+ onLeaseLost(error: ToolCallReceiptLeaseLostError): void;
123
+ onTransientFailure?(error: unknown): void;
124
+ }): { stop(): void; assertOwned(): void };
125
+ /** Wait for foreign-receipt hydration/reclaim work started by eager acquire. */
126
+ settle(): Promise<void>;
127
+ };
128
+
129
+ export type ToolCallReceiptCohortJob = {
130
+ acquire(input: {
131
+ owner: { runId: string; attempt: number };
132
+ members: ToolCallReceiptCohortMember[];
133
+ /**
134
+ * Return after the atomic initial claim so independently owned map rows
135
+ * can start provider work while foreign rows hydrate. The default keeps
136
+ * the standalone Module's all-settled acquisition contract.
137
+ */
138
+ eager?: boolean;
139
+ }): Promise<ToolCallReceiptCohort>;
140
+ };
141
+
142
+ export class ToolCallReceiptCohortWaitTimeoutError extends Error {
143
+ constructor(resultReceiptKey: string) {
144
+ super(`Timed out waiting for Tool Result Receipt ${resultReceiptKey}.`);
145
+ this.name = 'ToolCallReceiptCohortWaitTimeoutError';
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Owns the receipt protocol for one mapped Tool Call cohort.
151
+ *
152
+ * Its leverage is deliberately map-specific: scalar ToolCall needs one receipt;
153
+ * a map needs deduplicated bulk ownership, bounded foreign-owner recovery,
154
+ * one heartbeat for undispatched work, and a terminal fanout after a native
155
+ * batch splits. Keeping that protocol out of PlayContext makes ToolCall's
156
+ * scalar Interface remain small without discarding the real map behaviour.
157
+ */
158
+ export function createToolCallReceiptCohortJob(input: {
159
+ store: ToolCallReceiptCohortStore;
160
+ sleep?: (milliseconds: number) => Promise<void>;
161
+ waitDelayMs?: number;
162
+ }): ToolCallReceiptCohortJob {
163
+ const sleep = input.sleep ?? defaultSleep;
164
+ const waitDelayMs = positive(input.waitDelayMs ?? 250, 'wait delay');
165
+
166
+ return {
167
+ async acquire({ owner, members, eager = false }) {
168
+ const ownerRunId = required(owner.runId, 'owner run id');
169
+ if (!Number.isInteger(owner.attempt) || owner.attempt < 0) {
170
+ throw new Error(
171
+ 'Tool Call Receipt Cohort owner attempt must be a non-negative integer.',
172
+ );
173
+ }
174
+ if (members.length === 0) {
175
+ throw new Error(
176
+ 'Tool Call Receipt Cohort requires at least one member.',
177
+ );
178
+ }
179
+
180
+ const groups = groupMembers(members);
181
+ const states = new Map<string, ToolCallReceiptCohortMemberState>();
182
+ const resultDeferred = new Map<string, Deferred<ToolExecuteResult>>();
183
+ const owned = new Map<string, OwnedGroup>();
184
+ let leaseLost: ToolCallReceiptLeaseLostError | null = null;
185
+ const queuedCompletions: Array<{
186
+ memberId: string;
187
+ result: ToolExecuteResult;
188
+ resolve(result: ToolExecuteResult): void;
189
+ reject(error: unknown): void;
190
+ }> = [];
191
+ const queuedFailures: Array<{
192
+ memberId: string;
193
+ error: unknown;
194
+ resolve(): void;
195
+ reject(error: unknown): void;
196
+ }> = [];
197
+ let completionFlushScheduled = false;
198
+ let failureFlushScheduled = false;
199
+
200
+ const resolveGroup = (
201
+ group: Group,
202
+ result: ToolExecuteResult,
203
+ source: 'cache' | 'in_flight' | 'owner',
204
+ ) => {
205
+ const deferred = deferredFor(group, resultDeferred);
206
+ deferred.resolve(result);
207
+ for (const [index, member] of group.members.entries()) {
208
+ states.set(member.memberId, {
209
+ kind: 'completed',
210
+ result,
211
+ // The physical owner receives its live dispatch result. Duplicate
212
+ // map cells attached to that receipt are followers even though
213
+ // the completion fanout is local to this process.
214
+ source: source === 'owner' && index > 0 ? 'in_flight' : source,
215
+ });
216
+ }
217
+ };
218
+ const rejectGroup = (group: Group, error: unknown) => {
219
+ const deferred = deferredFor(group, resultDeferred);
220
+ deferred.reject(error);
221
+ for (const member of group.members) {
222
+ states.set(member.memberId, { kind: 'failed', error });
223
+ }
224
+ };
225
+
226
+ const ownGroup = (group: Group, record: ToolCallReceiptRecord) => {
227
+ const lease = record.lease;
228
+ if (!lease || lease.ownerRunId !== ownerRunId) {
229
+ throw new ToolCallReceiptLeaseLostError(group.resultReceiptKey);
230
+ }
231
+ const ownerMember = group.members[0]!;
232
+ const groupOwned: OwnedGroup = { group, lease };
233
+ owned.set(group.resultReceiptKey, groupOwned);
234
+ states.set(ownerMember.memberId, {
235
+ kind: 'owned',
236
+ resultReceiptKey: group.resultReceiptKey,
237
+ lease,
238
+ ...(record.leaseExpiresAt
239
+ ? { leaseExpiresAt: record.leaseExpiresAt }
240
+ : {}),
241
+ receipts: receiptView(ownerMember.memberId, groupOwned),
242
+ });
243
+ const deferred = deferredFor(group, resultDeferred);
244
+ for (const follower of group.members.slice(1)) {
245
+ states.set(follower.memberId, {
246
+ kind: 'following',
247
+ resultReceiptKey: group.resultReceiptKey,
248
+ ownerMemberId: ownerMember.memberId,
249
+ result: deferred.promise,
250
+ });
251
+ }
252
+ };
253
+
254
+ const applyClaim = (
255
+ group: Group,
256
+ record: ToolCallReceiptRecord | undefined,
257
+ source: 'cache' | 'in_flight',
258
+ ) => {
259
+ if (!record) return false;
260
+ if (isCompleted(record)) {
261
+ resolveGroup(group, record.result, source);
262
+ return true;
263
+ }
264
+ if (record.status === 'failed') {
265
+ rejectGroup(
266
+ group,
267
+ record.error ??
268
+ new Error(
269
+ `Tool Result Receipt ${group.resultReceiptKey} failed.`,
270
+ ),
271
+ );
272
+ return true;
273
+ }
274
+ if (isOwned(record, ownerRunId)) {
275
+ ownGroup(group, record);
276
+ return true;
277
+ }
278
+ return false;
279
+ };
280
+
281
+ const claimGroups = async (targets: Group[], reclaimRunning: boolean) => {
282
+ if (targets.length === 0)
283
+ return new Map<string, ToolCallReceiptRecord>();
284
+ const response = await input.store.claim({
285
+ ownerRunId,
286
+ ownerAttempt: owner.attempt,
287
+ receipts: targets.map((group) => ({
288
+ resultReceiptKey: group.resultReceiptKey,
289
+ force: group.force,
290
+ forceFailedRefresh: group.forceFailedRefresh,
291
+ reclaimRunning,
292
+ })),
293
+ });
294
+ return byKey(response);
295
+ };
296
+
297
+ // A forced refresh must not overlap an active foreign lease. Other
298
+ // requests go straight to the atomic bulk claim, matching the existing
299
+ // gateway protocol.
300
+ const forceGroups = [...groups.values()].filter((group) => group.force);
301
+ const forceExisting = forceGroups.length
302
+ ? byKey(
303
+ await input.store.read({
304
+ resultReceiptKeys: forceGroups.map(
305
+ (group) => group.resultReceiptKey,
306
+ ),
307
+ }),
308
+ )
309
+ : new Map<string, ToolCallReceiptRecord>();
310
+ const waitGroups: Group[] = [];
311
+ const initialClaimGroups: Group[] = [];
312
+ for (const group of groups.values()) {
313
+ const existing = forceExisting.get(group.resultReceiptKey);
314
+ if (group.force && existing && isLiveLeased(existing)) {
315
+ waitGroups.push(group);
316
+ } else {
317
+ initialClaimGroups.push(group);
318
+ }
319
+ }
320
+ const claims = await claimGroups(initialClaimGroups, false);
321
+ for (const group of initialClaimGroups) {
322
+ if (!applyClaim(group, claims.get(group.resultReceiptKey), 'cache')) {
323
+ waitGroups.push(group);
324
+ }
325
+ }
326
+
327
+ const waitAttempts = new Map(
328
+ waitGroups.map((group) => [
329
+ group.resultReceiptKey,
330
+ Math.max(
331
+ 1,
332
+ ...group.members.map((member) => member.waitMaxAttempts ?? 240),
333
+ ),
334
+ ]),
335
+ );
336
+ for (const group of waitGroups) {
337
+ states.set(group.members[0]!.memberId, {
338
+ kind: 'recovering',
339
+ resultReceiptKey: group.resultReceiptKey,
340
+ result: deferredFor(group, resultDeferred).promise,
341
+ });
342
+ for (const member of group.members.slice(1)) {
343
+ states.set(member.memberId, {
344
+ kind: 'recovering',
345
+ resultReceiptKey: group.resultReceiptKey,
346
+ result: deferredFor(group, resultDeferred).promise,
347
+ });
348
+ }
349
+ }
350
+
351
+ const settleWaitGroups = async (): Promise<void> => {
352
+ const pending = new Map(
353
+ waitGroups.map((group) => [group.resultReceiptKey, group]),
354
+ );
355
+ try {
356
+ const maxAttempts = Math.max(0, ...waitAttempts.values());
357
+ for (
358
+ let attempt = 0;
359
+ attempt < maxAttempts && pending.size;
360
+ attempt += 1
361
+ ) {
362
+ if (attempt > 0) await sleep(waitDelayMs);
363
+ const targets = [...pending.values()].filter(
364
+ (group) =>
365
+ attempt < (waitAttempts.get(group.resultReceiptKey) ?? 0),
366
+ );
367
+ if (targets.length === 0) continue;
368
+ // The context Adapter chunks this one vector sequentially. This is
369
+ // the bounded hydration lane: never turn an existing map into N
370
+ // per-row reads just because each member has its own recovery.
371
+ const latest = byKey(
372
+ await input.store.read({
373
+ resultReceiptKeys: targets.map(
374
+ (group) => group.resultReceiptKey,
375
+ ),
376
+ }),
377
+ );
378
+ for (const group of targets) {
379
+ const record = latest.get(group.resultReceiptKey);
380
+ // A forced caller cannot reuse the foreign terminal result, but
381
+ // it also must not spend its whole recovery window polling a
382
+ // receipt that is no longer live. Claim a fresh forced lease as
383
+ // soon as the observed owner has finished (or the row vanished).
384
+ // Waiting until the final attempt strands the map resolver for
385
+ // the entire bounded-recovery budget after a perfectly healthy
386
+ // first call completes.
387
+ if (group.force) {
388
+ if (!record || !isLiveLeased(record)) {
389
+ const claimed = await claimGroups([group], true);
390
+ if (
391
+ applyClaim(
392
+ group,
393
+ claimed.get(group.resultReceiptKey),
394
+ 'cache',
395
+ )
396
+ ) {
397
+ pending.delete(group.resultReceiptKey);
398
+ }
399
+ }
400
+ continue;
401
+ }
402
+ if (!record) continue;
403
+ if (isCompleted(record)) {
404
+ resolveGroup(group, record.result, 'in_flight');
405
+ pending.delete(group.resultReceiptKey);
406
+ } else if (record.status === 'failed') {
407
+ rejectGroup(
408
+ group,
409
+ record.error ??
410
+ new Error(
411
+ `Tool Result Receipt ${group.resultReceiptKey} failed.`,
412
+ ),
413
+ );
414
+ pending.delete(group.resultReceiptKey);
415
+ }
416
+ }
417
+ }
418
+ if (pending.size === 0) return;
419
+ const unresolved = [...pending.values()];
420
+ const reclaimed = await claimGroups(unresolved, true);
421
+ for (const group of unresolved) {
422
+ if (
423
+ applyClaim(group, reclaimed.get(group.resultReceiptKey), 'cache')
424
+ ) {
425
+ pending.delete(group.resultReceiptKey);
426
+ continue;
427
+ }
428
+ const error = new ToolCallReceiptCohortWaitTimeoutError(
429
+ group.resultReceiptKey,
430
+ );
431
+ rejectGroup(group, error);
432
+ throw error;
433
+ }
434
+ } catch (error) {
435
+ for (const group of pending.values()) rejectGroup(group, error);
436
+ throw error;
437
+ }
438
+ };
439
+ const settlement = settleWaitGroups();
440
+ // An eager caller deliberately awaits this after independently owned
441
+ // work settles. Keep a rejection observed meanwhile so a prompt adapter
442
+ // read failure cannot surface as an unhandled rejection.
443
+ void settlement.catch(() => undefined);
444
+ if (!eager) await settlement;
445
+
446
+ const assertOwned = () => {
447
+ if (leaseLost) throw leaseLost;
448
+ for (const group of owned.values()) {
449
+ const state = states.get(group.group.members[0]!.memberId);
450
+ if (
451
+ !state ||
452
+ state.kind !== 'owned' ||
453
+ state.lease.leaseId !== group.lease.leaseId
454
+ ) {
455
+ throw new ToolCallReceiptLeaseLostError(
456
+ group.group.resultReceiptKey,
457
+ );
458
+ }
459
+ }
460
+ };
461
+
462
+ const heartbeat = async (): Promise<'active' | 'terminal'> => {
463
+ if (owned.size === 0) return 'terminal';
464
+ const current = [...owned.values()];
465
+ const response = byKey(
466
+ await input.store.heartbeat({
467
+ ownerRunId,
468
+ ownerAttempt: owner.attempt,
469
+ receipts: current.map(({ group, lease }) => ({
470
+ resultReceiptKey: group.resultReceiptKey,
471
+ lease,
472
+ })),
473
+ }),
474
+ );
475
+ let active = false;
476
+ for (const groupOwned of current) {
477
+ const record = response.get(groupOwned.group.resultReceiptKey);
478
+ if (record && isCompleted(record)) {
479
+ owned.delete(groupOwned.group.resultReceiptKey);
480
+ resolveGroup(groupOwned.group, record.result, 'in_flight');
481
+ continue;
482
+ }
483
+ if (record?.status === 'failed') {
484
+ owned.delete(groupOwned.group.resultReceiptKey);
485
+ rejectGroup(
486
+ groupOwned.group,
487
+ record.error ??
488
+ new Error(
489
+ `Tool Result Receipt ${groupOwned.group.resultReceiptKey} failed.`,
490
+ ),
491
+ );
492
+ continue;
493
+ }
494
+ if (!record || !isSameLease(record, groupOwned.lease)) {
495
+ leaseLost ??= new ToolCallReceiptLeaseLostError(
496
+ groupOwned.group.resultReceiptKey,
497
+ );
498
+ throw leaseLost;
499
+ }
500
+ active = true;
501
+ }
502
+ return active ? 'active' : 'terminal';
503
+ };
504
+
505
+ const complete = async (
506
+ entries: Array<{ memberId: string; result: ToolExecuteResult }>,
507
+ ) => {
508
+ const requested = ownedEntries(entries, states, owned);
509
+ if (requested.length === 0) return new Map<string, ToolExecuteResult>();
510
+ const response = byKey(
511
+ await input.store.complete({
512
+ ownerRunId,
513
+ ownerAttempt: owner.attempt,
514
+ receipts: requested.map(({ group, lease, result }) => ({
515
+ resultReceiptKey: group.resultReceiptKey,
516
+ lease,
517
+ result,
518
+ })),
519
+ }),
520
+ );
521
+ const results = new Map<string, ToolExecuteResult>();
522
+ for (const entry of requested) {
523
+ const persisted = response.get(entry.group.resultReceiptKey);
524
+ // Completion gateways are allowed to return a compact terminal
525
+ // acknowledgement after durably storing the result. That response
526
+ // is represented as a `no_result` wrapper by the adapter; it is not
527
+ // the provider result and must not replace this invocation's live
528
+ // value. A real no-result provider response is identical to the
529
+ // submitted entry, so retaining the entry is correct there too.
530
+ const result =
531
+ isCompleted(persisted) && persisted.result.status !== 'no_result'
532
+ ? persisted.result
533
+ : entry.result;
534
+ owned.delete(entry.group.resultReceiptKey);
535
+ resolveGroup(entry.group, result, 'owner');
536
+ for (const member of entry.group.members)
537
+ results.set(member.memberId, result);
538
+ }
539
+ return results;
540
+ };
541
+
542
+ const fail = async (
543
+ entries: Array<{ memberId: string; error: unknown }>,
544
+ ) => {
545
+ const requested = ownedEntries(entries, states, owned);
546
+ if (requested.length === 0) return;
547
+ await input.store.fail({
548
+ ownerRunId,
549
+ ownerAttempt: owner.attempt,
550
+ receipts: requested.map(({ group, lease, error }) => ({
551
+ resultReceiptKey: group.resultReceiptKey,
552
+ lease,
553
+ error,
554
+ })),
555
+ });
556
+ for (const entry of requested) {
557
+ owned.delete(entry.group.resultReceiptKey);
558
+ rejectGroup(entry.group, entry.error);
559
+ }
560
+ };
561
+
562
+ const flushCompletions = async (): Promise<void> => {
563
+ completionFlushScheduled = false;
564
+ const entries = queuedCompletions.splice(0);
565
+ if (entries.length === 0) return;
566
+ try {
567
+ const results = await complete(
568
+ entries.map(({ memberId, result }) => ({ memberId, result })),
569
+ );
570
+ for (const entry of entries) {
571
+ entry.resolve(results.get(entry.memberId) ?? entry.result);
572
+ }
573
+ } catch (error) {
574
+ for (const entry of entries) entry.reject(error);
575
+ }
576
+ };
577
+ const flushFailures = async (): Promise<void> => {
578
+ failureFlushScheduled = false;
579
+ const entries = queuedFailures.splice(0);
580
+ if (entries.length === 0) return;
581
+ try {
582
+ await fail(
583
+ entries.map(({ memberId, error }) => ({ memberId, error })),
584
+ );
585
+ for (const entry of entries) entry.resolve();
586
+ } catch (error) {
587
+ for (const entry of entries) entry.reject(error);
588
+ }
589
+ };
590
+
591
+ function receiptView(
592
+ memberId: string,
593
+ groupOwned: OwnedGroup,
594
+ ): ToolResultReceipts {
595
+ const { group, lease } = groupOwned;
596
+ return {
597
+ async claim(candidate) {
598
+ if (
599
+ candidate.resultReceiptKey !== group.resultReceiptKey ||
600
+ candidate.ownerRunId !== ownerRunId
601
+ ) {
602
+ throw new ToolCallReceiptLeaseLostError(
603
+ candidate.resultReceiptKey,
604
+ );
605
+ }
606
+ return { kind: 'owned', lease };
607
+ },
608
+ async recover(candidate) {
609
+ if (
610
+ candidate.resultReceiptKey !== group.resultReceiptKey ||
611
+ candidate.ownerRunId !== ownerRunId
612
+ ) {
613
+ throw new ToolCallReceiptLeaseLostError(
614
+ candidate.resultReceiptKey,
615
+ );
616
+ }
617
+ return await deferredFor(group, resultDeferred).promise;
618
+ },
619
+ async heartbeat(candidate) {
620
+ if (
621
+ candidate.resultReceiptKey !== group.resultReceiptKey ||
622
+ !sameLease(candidate.lease, lease)
623
+ )
624
+ return 'lost';
625
+ try {
626
+ assertOwned();
627
+ // Preserve the per-provider-admission fence from the old map
628
+ // path. The cohort-wide heartbeat keeps queued work alive;
629
+ // this one-member check closes the exact dispatch window.
630
+ const record = byKey(
631
+ await input.store.heartbeat({
632
+ ownerRunId,
633
+ ownerAttempt: owner.attempt,
634
+ receipts: [
635
+ { resultReceiptKey: group.resultReceiptKey, lease },
636
+ ],
637
+ }),
638
+ ).get(group.resultReceiptKey);
639
+ if (record && isCompleted(record)) {
640
+ owned.delete(group.resultReceiptKey);
641
+ resolveGroup(group, record.result, 'in_flight');
642
+ return 'completed';
643
+ }
644
+ if (!record || !isSameLease(record, lease)) return 'lost';
645
+ return 'active';
646
+ } catch (error) {
647
+ if (error instanceof ToolCallReceiptLeaseLostError) return 'lost';
648
+ throw error;
649
+ }
650
+ },
651
+ async complete(candidate) {
652
+ if (
653
+ candidate.resultReceiptKey !== group.resultReceiptKey ||
654
+ !sameLease(candidate.lease, lease)
655
+ ) {
656
+ throw new ToolCallReceiptLeaseLostError(
657
+ candidate.resultReceiptKey,
658
+ );
659
+ }
660
+ const result = await new Promise<ToolExecuteResult>(
661
+ (resolve, reject) => {
662
+ queuedCompletions.push({
663
+ memberId,
664
+ result: candidate.result,
665
+ resolve,
666
+ reject,
667
+ });
668
+ if (!completionFlushScheduled) {
669
+ completionFlushScheduled = true;
670
+ setTimeout(() => void flushCompletions(), 0);
671
+ }
672
+ },
673
+ );
674
+ return { kind: 'stored', result };
675
+ },
676
+ async fail(candidate) {
677
+ if (
678
+ candidate.resultReceiptKey !== group.resultReceiptKey ||
679
+ !sameLease(candidate.lease, lease)
680
+ ) {
681
+ throw new ToolCallReceiptLeaseLostError(
682
+ candidate.resultReceiptKey,
683
+ );
684
+ }
685
+ await new Promise<void>((resolve, reject) => {
686
+ queuedFailures.push({
687
+ memberId,
688
+ error: candidate.error,
689
+ resolve,
690
+ reject,
691
+ });
692
+ if (!failureFlushScheduled) {
693
+ failureFlushScheduled = true;
694
+ setTimeout(() => void flushFailures(), 0);
695
+ }
696
+ });
697
+ },
698
+ };
699
+ }
700
+
701
+ return {
702
+ member(memberId) {
703
+ const state = states.get(memberId);
704
+ if (!state)
705
+ throw new Error(
706
+ `Unknown Tool Call Receipt Cohort member ${memberId}.`,
707
+ );
708
+ return state;
709
+ },
710
+ complete,
711
+ fail,
712
+ heartbeat,
713
+ startHeartbeat({ intervalMs, onLeaseLost, onTransientFailure }) {
714
+ const supervisor = createRuntimeReceiptHeartbeatSupervisor({
715
+ intervalMs: positive(intervalMs, 'heartbeat interval'),
716
+ heartbeat,
717
+ isLeaseLost: (error) =>
718
+ error instanceof ToolCallReceiptLeaseLostError,
719
+ onLeaseLost: (error) => {
720
+ leaseLost ??= error as ToolCallReceiptLeaseLostError;
721
+ onLeaseLost(leaseLost);
722
+ },
723
+ onTransientFailure,
724
+ });
725
+ supervisor.start();
726
+ return { stop: () => supervisor.stop(), assertOwned };
727
+ },
728
+ async settle() {
729
+ await settlement;
730
+ },
731
+ };
732
+ },
733
+ };
734
+ }
735
+
736
+ type Group = {
737
+ resultReceiptKey: string;
738
+ members: ToolCallReceiptCohortMember[];
739
+ force: boolean;
740
+ forceFailedRefresh: boolean;
741
+ };
742
+
743
+ type OwnedGroup = { group: Group; lease: ToolCallReceiptLease };
744
+
745
+ type Deferred<T> = {
746
+ promise: Promise<T>;
747
+ resolve(value: T): void;
748
+ reject(error: unknown): void;
749
+ };
750
+
751
+ function groupMembers(
752
+ members: ToolCallReceiptCohortMember[],
753
+ ): Map<string, Group> {
754
+ const groups = new Map<string, Group>();
755
+ const memberIds = new Set<string>();
756
+ for (const member of members) {
757
+ const memberId = required(member.memberId, 'member id');
758
+ if (memberIds.has(memberId))
759
+ throw new Error(`Duplicate Tool Call Receipt Cohort member ${memberId}.`);
760
+ memberIds.add(memberId);
761
+ const resultReceiptKey = required(
762
+ member.resultReceiptKey,
763
+ 'result receipt key',
764
+ );
765
+ const group = groups.get(resultReceiptKey) ?? {
766
+ resultReceiptKey,
767
+ members: [],
768
+ force: false,
769
+ forceFailedRefresh: false,
770
+ };
771
+ group.members.push({ ...member, memberId, resultReceiptKey });
772
+ group.force ||= member.force === true;
773
+ group.forceFailedRefresh ||= member.forceFailedRefresh === true;
774
+ groups.set(resultReceiptKey, group);
775
+ }
776
+ for (const group of groups.values()) {
777
+ // Full force dominates failed-only refresh for every duplicate member of
778
+ // the same content-addressed receipt, just as the current map grouping.
779
+ if (group.force) group.forceFailedRefresh = false;
780
+ }
781
+ return groups;
782
+ }
783
+
784
+ function deferredFor(
785
+ group: Group,
786
+ deferred: Map<string, Deferred<ToolExecuteResult>>,
787
+ ): Deferred<ToolExecuteResult> {
788
+ let value = deferred.get(group.resultReceiptKey);
789
+ if (value) return value;
790
+ let resolve!: (result: ToolExecuteResult) => void;
791
+ let reject!: (error: unknown) => void;
792
+ const promise = new Promise<ToolExecuteResult>(
793
+ (resolvePromise, rejectPromise) => {
794
+ resolve = resolvePromise;
795
+ reject = rejectPromise;
796
+ },
797
+ );
798
+ // A receipt can fail before a same-run follower attaches. Do not create an
799
+ // unhandled rejection while preserving the rejection for real followers.
800
+ void promise.catch(() => undefined);
801
+ value = { promise, resolve, reject };
802
+ deferred.set(group.resultReceiptKey, value);
803
+ return value;
804
+ }
805
+
806
+ function ownedEntries<T extends { memberId: string }>(
807
+ entries: T[],
808
+ states: Map<string, ToolCallReceiptCohortMemberState>,
809
+ owned: Map<string, OwnedGroup>,
810
+ ): Array<T & OwnedGroup> {
811
+ const byKey = new Map<string, T & OwnedGroup>();
812
+ for (const entry of entries) {
813
+ const state = states.get(entry.memberId);
814
+ if (!state || state.kind !== 'owned') {
815
+ throw new Error(
816
+ `Tool Call Receipt Cohort member ${entry.memberId} does not own a receipt.`,
817
+ );
818
+ }
819
+ const groupOwned = owned.get(state.resultReceiptKey);
820
+ if (!groupOwned || !sameLease(groupOwned.lease, state.lease)) {
821
+ throw new ToolCallReceiptLeaseLostError(state.resultReceiptKey);
822
+ }
823
+ const existing = byKey.get(groupOwned.group.resultReceiptKey);
824
+ if (existing && existing.memberId !== entry.memberId) {
825
+ throw new Error(
826
+ `Tool Call Receipt Cohort received two terminal outcomes for ${groupOwned.group.resultReceiptKey}.`,
827
+ );
828
+ }
829
+ byKey.set(groupOwned.group.resultReceiptKey, { ...entry, ...groupOwned });
830
+ }
831
+ return [...byKey.values()];
832
+ }
833
+
834
+ function byKey(
835
+ records: ToolCallReceiptRecord[],
836
+ ): Map<string, ToolCallReceiptRecord> {
837
+ const result = new Map<string, ToolCallReceiptRecord>();
838
+ for (const record of records) result.set(record.resultReceiptKey, record);
839
+ return result;
840
+ }
841
+
842
+ function isCompleted(
843
+ record: ToolCallReceiptRecord | undefined,
844
+ ): record is ToolCallReceiptRecord & { result: ToolExecuteResult } {
845
+ if (!record) return false;
846
+ return (
847
+ (record.status === 'completed' || record.status === 'skipped') &&
848
+ record.result !== undefined
849
+ );
850
+ }
851
+
852
+ function isOwned(record: ToolCallReceiptRecord, ownerRunId: string): boolean {
853
+ return (
854
+ record.claimState !== 'existing' &&
855
+ Boolean(record.lease) &&
856
+ record.lease?.ownerRunId === ownerRunId &&
857
+ (record.status === 'queued' ||
858
+ record.status === 'pending' ||
859
+ record.status === 'running')
860
+ );
861
+ }
862
+
863
+ function isLiveLeased(record: ToolCallReceiptRecord): boolean {
864
+ if (
865
+ record.status !== 'queued' &&
866
+ record.status !== 'pending' &&
867
+ record.status !== 'running'
868
+ )
869
+ return false;
870
+ if (!record.lease) return false;
871
+ const expiresAt = Date.parse(record.leaseExpiresAt ?? '');
872
+ return !Number.isFinite(expiresAt) || expiresAt > Date.now();
873
+ }
874
+
875
+ function isSameLease(
876
+ record: ToolCallReceiptRecord,
877
+ lease: ToolCallReceiptLease,
878
+ ): boolean {
879
+ return Boolean(record.lease) && sameLease(record.lease!, lease);
880
+ }
881
+
882
+ function sameLease(
883
+ left: ToolCallReceiptLease,
884
+ right: ToolCallReceiptLease,
885
+ ): boolean {
886
+ return left.leaseId === right.leaseId && left.ownerRunId === right.ownerRunId;
887
+ }
888
+
889
+ function required(value: string, name: string): string {
890
+ const normalized = value?.trim();
891
+ if (!normalized)
892
+ throw new Error(`Tool Call Receipt Cohort requires a ${name}.`);
893
+ return normalized;
894
+ }
895
+
896
+ function positive(value: number, name: string): number {
897
+ if (!Number.isFinite(value) || value <= 0)
898
+ throw new Error(`Tool Call Receipt Cohort ${name} must be positive.`);
899
+ return Math.floor(value);
900
+ }
901
+
902
+ async function defaultSleep(milliseconds: number): Promise<void> {
903
+ await new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
904
+ }