deepline 0.3.71 → 0.3.73
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/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +712 -632
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +40 -10
- package/dist/bundling-sources/shared_libs/play-runtime/log-provenance.ts +116 -4
- package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +4 -1
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +2 -14
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/native-batch.ts +9 -92
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +159 -621
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +19 -13
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +10 -5
- package/dist/bundling-sources/shared_libs/plays/contracts.ts +1 -4
- package/dist/bundling-sources/shared_libs/plays/tool-response-contract.ts +2 -4
- package/dist/cli/index.js +324 -123
- package/dist/cli/index.mjs +324 -123
- package/dist/{compiler-manifest-IkyvsUO-.d.mts → compiler-manifest-CMNgA_JQ.d.mts} +7 -1
- package/dist/{compiler-manifest-IkyvsUO-.d.ts → compiler-manifest-CMNgA_JQ.d.ts} +7 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +32 -3
- package/dist/index.mjs +32 -3
- package/dist/install-integrity.json +2 -2
- 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 +1 -1
- package/package.json +1 -1
|
@@ -69,6 +69,8 @@ export interface ToolCallRequest {
|
|
|
69
69
|
providerIdempotencyKeyBase?: string | null;
|
|
70
70
|
cacheable?: boolean;
|
|
71
71
|
receiptKey?: string | null;
|
|
72
|
+
/** Pre-cutover result-cache keys to read before executing a canonical miss. */
|
|
73
|
+
legacyReceiptKeys?: string[];
|
|
72
74
|
executionAuthScopeDigest?: string | null;
|
|
73
75
|
receiptLeaseId?: string | null;
|
|
74
76
|
receiptLeaseExpiresAt?: string | null;
|
|
@@ -45,10 +45,39 @@ export function buildDurableToolCallCacheKey(input: {
|
|
|
45
45
|
/** Run-stable clock used to choose the stale bucket. */
|
|
46
46
|
cacheEpochMs?: number;
|
|
47
47
|
playLocalScope?: string | null;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
}): string {
|
|
49
|
+
return buildDurableToolCallCacheKeyWithPresentation(input);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Reads receipt keys written before response presentation stopped partitioning
|
|
54
|
+
* the exact-request cache. New writes must use `buildDurableToolCallCacheKey`.
|
|
55
|
+
*/
|
|
56
|
+
export function buildLegacyRevisionedDurableToolCallCacheKey(input: {
|
|
57
|
+
orgId?: string | null;
|
|
58
|
+
toolId: string;
|
|
59
|
+
requestInput: Record<string, unknown>;
|
|
60
|
+
authScopeDigest?: string | null;
|
|
61
|
+
providerActionVersion?: string | null;
|
|
62
|
+
cachePolicyVersion?: string | null;
|
|
63
|
+
staleAfterSeconds?: number | null;
|
|
64
|
+
cacheEpochMs?: number;
|
|
65
|
+
playLocalScope?: string | null;
|
|
66
|
+
toolResponseReceiptRevision: string;
|
|
67
|
+
}): string {
|
|
68
|
+
return buildDurableToolCallCacheKeyWithPresentation(input);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function buildDurableToolCallCacheKeyWithPresentation(input: {
|
|
72
|
+
orgId?: string | null;
|
|
73
|
+
toolId: string;
|
|
74
|
+
requestInput: Record<string, unknown>;
|
|
75
|
+
authScopeDigest?: string | null;
|
|
76
|
+
providerActionVersion?: string | null;
|
|
77
|
+
cachePolicyVersion?: string | null;
|
|
78
|
+
staleAfterSeconds?: number | null;
|
|
79
|
+
cacheEpochMs?: number;
|
|
80
|
+
playLocalScope?: string | null;
|
|
52
81
|
toolResponseReceiptRevision?: string | null;
|
|
53
82
|
}): string {
|
|
54
83
|
const orgId = input.orgId?.trim() || 'org';
|
|
@@ -63,11 +92,6 @@ export function buildDurableToolCallCacheKey(input: {
|
|
|
63
92
|
);
|
|
64
93
|
}
|
|
65
94
|
const playLocalScope = input.playLocalScope?.trim() || 'play';
|
|
66
|
-
// Keep historical digests byte-for-byte stable when no explicit revision
|
|
67
|
-
// exists on the stored artifact.
|
|
68
|
-
const toolResponseReceiptRevision = normalizeToolResponseReceiptRevision(
|
|
69
|
-
input.toolResponseReceiptRevision,
|
|
70
|
-
);
|
|
71
95
|
const digest = sha256Hex(
|
|
72
96
|
stableStringify({
|
|
73
97
|
kind: 'tool' satisfies DurableCallKind,
|
|
@@ -78,9 +102,15 @@ export function buildDurableToolCallCacheKey(input: {
|
|
|
78
102
|
requestInput: input.requestInput,
|
|
79
103
|
authScopeDigest: input.authScopeDigest ?? null,
|
|
80
104
|
providerActionVersion,
|
|
105
|
+
...(input.toolResponseReceiptRevision
|
|
106
|
+
? {
|
|
107
|
+
toolResponseReceiptRevision: normalizeToolResponseReceiptRevision(
|
|
108
|
+
input.toolResponseReceiptRevision,
|
|
109
|
+
),
|
|
110
|
+
}
|
|
111
|
+
: {}),
|
|
81
112
|
cachePolicyVersion:
|
|
82
113
|
input.cachePolicyVersion ?? DURABLE_CALL_CACHE_POLICY_VERSION,
|
|
83
|
-
...(toolResponseReceiptRevision ? { toolResponseReceiptRevision } : {}),
|
|
84
114
|
staleBucket: durableCacheStaleBucket({
|
|
85
115
|
staleAfterSeconds: input.staleAfterSeconds,
|
|
86
116
|
nowMs: input.cacheEpochMs,
|
|
@@ -52,6 +52,27 @@ export type LogProvenance =
|
|
|
52
52
|
* Never customer-facing. */
|
|
53
53
|
| 'receipt';
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* How urgent a log line is. This is deliberately independent from provenance:
|
|
57
|
+
* a customer can write an error, while a runtime can emit a harmless debug
|
|
58
|
+
* detail. CLI log-level filters use this ordered taxonomy.
|
|
59
|
+
*/
|
|
60
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
61
|
+
|
|
62
|
+
export const LOG_LEVELS: readonly LogLevel[] = [
|
|
63
|
+
'debug',
|
|
64
|
+
'info',
|
|
65
|
+
'warn',
|
|
66
|
+
'error',
|
|
67
|
+
] as const;
|
|
68
|
+
|
|
69
|
+
const LOG_LEVEL_RANK: Record<LogLevel, number> = {
|
|
70
|
+
debug: 0,
|
|
71
|
+
info: 1,
|
|
72
|
+
warn: 2,
|
|
73
|
+
error: 3,
|
|
74
|
+
};
|
|
75
|
+
|
|
55
76
|
/** Every provenance class, for exhaustiveness checks and tests. */
|
|
56
77
|
export const LOG_PROVENANCE_CLASSES: readonly LogProvenance[] = [
|
|
57
78
|
'user',
|
|
@@ -125,6 +146,51 @@ export function tagLogProvenance(
|
|
|
125
146
|
return `${PROVENANCE_PREFIX}${provenance}${PROVENANCE_SENTINEL}${line}`;
|
|
126
147
|
}
|
|
127
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Persist a severity in text that released readers can still render sensibly.
|
|
151
|
+
* The provenance tag is already a compatible structural carrier; level text
|
|
152
|
+
* must remain readable after an older client strips only that provenance tag.
|
|
153
|
+
*/
|
|
154
|
+
export function tagLogLevel(line: string, level: LogLevel): string {
|
|
155
|
+
const timestamp = line.match(/^(\[[^\]]+\]\s*)/u)?.[1] ?? '';
|
|
156
|
+
const message = timestamp ? line.slice(timestamp.length) : line;
|
|
157
|
+
const explicitLevel = message.match(/^\[(debug|info|warn|error)\]\s*/i);
|
|
158
|
+
// The caller-selected severity is authoritative, but customer text remains
|
|
159
|
+
// immutable. Add a framework marker before a conflicting customer prefix
|
|
160
|
+
// instead of replacing the author's original `[warn]`/`[error]` wording.
|
|
161
|
+
if (explicitLevel?.[1]?.toLowerCase() === level) return line;
|
|
162
|
+
const needsInfoDisambiguation =
|
|
163
|
+
level === 'info' &&
|
|
164
|
+
(explicitLevel !== null ||
|
|
165
|
+
/^\[console\.(?:debug|warn|error)\]/i.test(message));
|
|
166
|
+
if (level === 'info' && !needsInfoDisambiguation) {
|
|
167
|
+
return line;
|
|
168
|
+
}
|
|
169
|
+
return `${timestamp}[${level}] ${message}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Tag a new log line with both independent dimensions. */
|
|
173
|
+
export function tagLogEntry(
|
|
174
|
+
provenance: LogProvenance,
|
|
175
|
+
level: LogLevel,
|
|
176
|
+
line: string,
|
|
177
|
+
): string {
|
|
178
|
+
return tagLogProvenance(provenance, tagLogLevel(line, level));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** True when a line at `level` reaches a minimum-severity filter. */
|
|
182
|
+
export function logLevelReaches(level: LogLevel, minimum: LogLevel): boolean {
|
|
183
|
+
return LOG_LEVEL_RANK[level] >= LOG_LEVEL_RANK[minimum];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Parse a user-supplied log level without accepting a silently ignored value. */
|
|
187
|
+
export function parseLogLevel(value: string): LogLevel | null {
|
|
188
|
+
const normalized = value.trim().toLowerCase();
|
|
189
|
+
return LOG_LEVELS.includes(normalized as LogLevel)
|
|
190
|
+
? (normalized as LogLevel)
|
|
191
|
+
: null;
|
|
192
|
+
}
|
|
193
|
+
|
|
128
194
|
/**
|
|
129
195
|
* A customer-safe record of a `ctx.fetch` response that reached the server but
|
|
130
196
|
* was not successful. Its URL is destination-origin-only; it deliberately
|
|
@@ -179,9 +245,10 @@ export function parseCtxFetchHttpFailureDiagnostic(
|
|
|
179
245
|
): CtxFetchHttpFailureDiagnostic | null {
|
|
180
246
|
const tagged = readProvenanceTag(rawLine);
|
|
181
247
|
if (tagged.provenance !== 'diagnostic') return null;
|
|
182
|
-
const
|
|
248
|
+
const line = tagged.line;
|
|
249
|
+
const index = line.indexOf(CTX_FETCH_HTTP_FAILURE_LOG_PREFIX);
|
|
183
250
|
if (index === -1) return null;
|
|
184
|
-
const json =
|
|
251
|
+
const json = line
|
|
185
252
|
.slice(index + CTX_FETCH_HTTP_FAILURE_LOG_PREFIX.length)
|
|
186
253
|
.trim();
|
|
187
254
|
try {
|
|
@@ -296,13 +363,58 @@ export function classifyLegacyLogLine(line: string): LogProvenance {
|
|
|
296
363
|
*/
|
|
297
364
|
export function classifyLogLine(line: string): {
|
|
298
365
|
provenance: LogProvenance;
|
|
366
|
+
level: LogLevel;
|
|
299
367
|
line: string;
|
|
300
368
|
} {
|
|
301
369
|
const tagged = readProvenanceTag(line);
|
|
370
|
+
const provenance = tagged.provenance ?? classifyLegacyLogLine(tagged.line);
|
|
302
371
|
if (tagged.provenance !== null) {
|
|
303
|
-
return {
|
|
372
|
+
return {
|
|
373
|
+
provenance,
|
|
374
|
+
level: inferLogLevel(provenance, tagged.line),
|
|
375
|
+
line: tagged.line,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
provenance,
|
|
380
|
+
level: inferLogLevel(provenance, tagged.line),
|
|
381
|
+
line: tagged.line,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Compatibility classification for lines that predate explicit level tags.
|
|
387
|
+
* New runtime-authored lines should use `tagLogEntry`; these rules keep old
|
|
388
|
+
* run history useful without pretending arbitrary customer text is an error.
|
|
389
|
+
*/
|
|
390
|
+
function inferLogLevel(provenance: LogProvenance, line: string): LogLevel {
|
|
391
|
+
const message = stripLeadingTimestamp(line);
|
|
392
|
+
if (/^\[info\]/i.test(message)) {
|
|
393
|
+
return 'info';
|
|
394
|
+
}
|
|
395
|
+
if (/^\[debug\]/i.test(message)) {
|
|
396
|
+
return 'debug';
|
|
397
|
+
}
|
|
398
|
+
if (/^\[console\.error\]/i.test(message) || /^\[error\]/i.test(message)) {
|
|
399
|
+
return 'error';
|
|
400
|
+
}
|
|
401
|
+
if (/^\[console\.warn\]/i.test(message) || /^\[warn\]/i.test(message)) {
|
|
402
|
+
return 'warn';
|
|
403
|
+
}
|
|
404
|
+
if (/^\[console\.debug\]/i.test(message)) {
|
|
405
|
+
return 'debug';
|
|
406
|
+
}
|
|
407
|
+
if (
|
|
408
|
+
provenance === 'replay' ||
|
|
409
|
+
provenance === 'infra' ||
|
|
410
|
+
provenance === 'receipt'
|
|
411
|
+
) {
|
|
412
|
+
return 'debug';
|
|
413
|
+
}
|
|
414
|
+
if (provenance === 'diagnostic') {
|
|
415
|
+
return 'warn';
|
|
304
416
|
}
|
|
305
|
-
return
|
|
417
|
+
return 'info';
|
|
306
418
|
}
|
|
307
419
|
|
|
308
420
|
/** True when a raw (possibly tagged) log line should render on the surface. */
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
type PlayActivityObservation,
|
|
22
22
|
type PlayRunActivityProjection,
|
|
23
23
|
} from './activity-observation';
|
|
24
|
+
import { stripProvenanceTag } from './log-provenance';
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Run Snapshot Stream.
|
|
@@ -238,7 +239,9 @@ function buildSnapshotFromLedger(
|
|
|
238
239
|
durationMs: snapshot.durationMs ?? null,
|
|
239
240
|
updatedAt:
|
|
240
241
|
snapshot.updatedAt ?? snapshot.finishedAt ?? snapshot.startedAt ?? null,
|
|
241
|
-
|
|
242
|
+
// This snapshot is public SDK/API transport. Keep the historical plain
|
|
243
|
+
// timestamp-prefixed text while provenance remains runtime metadata.
|
|
244
|
+
logs: snapshot.logTail.map(stripProvenanceTag),
|
|
242
245
|
totalLogCount: snapshot.totalLogCount,
|
|
243
246
|
...(snapshot.logsTruncated ? { logsTruncated: true } : {}),
|
|
244
247
|
activeArtifactTableNamespace: snapshot.activeArtifactTableNamespace ?? null,
|
|
@@ -15,7 +15,6 @@ export {
|
|
|
15
15
|
} from './runtime-step-receipts-adapter';
|
|
16
16
|
export {
|
|
17
17
|
createToolCallReceiptCohortJob,
|
|
18
|
-
ToolCallReceiptCohortWaitTimeoutError,
|
|
19
18
|
type ToolCallReceiptCohort,
|
|
20
19
|
type ToolCallReceiptCohortJob,
|
|
21
20
|
type ToolCallReceiptCohortMember,
|
|
@@ -54,12 +53,8 @@ export { ToolCallReceiptLeaseLostError } from './contract';
|
|
|
54
53
|
* Adapts a receipt lifecycle that is already owned by a higher-level runtime
|
|
55
54
|
* job. This is a composition seam, not a second receipt implementation.
|
|
56
55
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* auth-scope re-keying, and external-call-slot retention. Once it has
|
|
60
|
-
* claimed that protocol's receipt, ToolCall can still own the call boundary
|
|
61
|
-
* (validation and dispatch) without trying to claim or terminally write the
|
|
62
|
-
* same receipt a second time. The outer job remains the sole durable owner.
|
|
56
|
+
* The outer runtime owns the physical batch operation. ToolCall keeps the
|
|
57
|
+
* result boundary uniform without performing a second cache write.
|
|
63
58
|
*
|
|
64
59
|
* Do not use this for a new caller. New runtimes should provide a real
|
|
65
60
|
* ToolResultReceipts Adapter. This exists only while PlayContext's historical
|
|
@@ -117,21 +112,14 @@ export function createExternallyOwnedToolResultReceipts(input: {
|
|
|
117
112
|
},
|
|
118
113
|
async heartbeat(candidate) {
|
|
119
114
|
assertKeyAndLease(candidate);
|
|
120
|
-
// The outer runtime owns durable heartbeating / execution locks. Returning
|
|
121
|
-
// active here deliberately avoids a duplicate heartbeat write.
|
|
122
115
|
return 'active';
|
|
123
116
|
},
|
|
124
117
|
async complete(candidate) {
|
|
125
118
|
assertKeyAndLease(candidate);
|
|
126
|
-
// The outer runtime atomically publishes the result, including its
|
|
127
|
-
// cache-only convergence behaviour. A second completion here would alter
|
|
128
|
-
// that race and can overwrite its execution metadata.
|
|
129
119
|
return { kind: 'stored', result: candidate.result };
|
|
130
120
|
},
|
|
131
121
|
async fail(candidate) {
|
|
132
122
|
assertKeyAndLease(candidate);
|
|
133
|
-
// Failure publication belongs to the outer state machine because it
|
|
134
|
-
// classifies auth-scope changes, suspensions, and lost ownership.
|
|
135
123
|
},
|
|
136
124
|
};
|
|
137
125
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { ToolExecuteResult } from '../tool-result';
|
|
2
2
|
import {
|
|
3
|
-
ToolCallReceiptLeaseLostError,
|
|
4
3
|
type ToolCallInput,
|
|
5
4
|
type ToolCallJob,
|
|
6
5
|
type ToolCallReceiptLease,
|
|
@@ -24,8 +23,6 @@ export type NativeBatchToolCallMember = {
|
|
|
24
23
|
|
|
25
24
|
export type NativeBatchOwnedToolCallMember = NativeBatchToolCallMember & {
|
|
26
25
|
receipt: ToolCallReceiptLease;
|
|
27
|
-
/** Actual member lease expiry supplied by the receipt-cohort Adapter. */
|
|
28
|
-
leaseExpiresAt?: string | null;
|
|
29
26
|
};
|
|
30
27
|
|
|
31
28
|
export type NativeBatchToolCallInput = {
|
|
@@ -50,14 +47,6 @@ export type NativeBatchToolCallInput = {
|
|
|
50
47
|
}):
|
|
51
48
|
| Array<{ memberId: string; result: ToolExecuteResult }>
|
|
52
49
|
| Promise<Array<{ memberId: string; result: ToolExecuteResult }>>;
|
|
53
|
-
/** Keep all owned member leases alive during a long provider batch. */
|
|
54
|
-
heartbeat?: {
|
|
55
|
-
/** May derive a real interval from the claimed member leases. */
|
|
56
|
-
intervalMs:
|
|
57
|
-
| number
|
|
58
|
-
| ((input: { members: NativeBatchOwnedToolCallMember[] }) => number);
|
|
59
|
-
onTransientFailure?(error: unknown): void;
|
|
60
|
-
};
|
|
61
50
|
};
|
|
62
51
|
|
|
63
52
|
/** A native batch returns each local member result, including cache followers. */
|
|
@@ -70,12 +59,8 @@ export type NativeBatchToolCallJob = {
|
|
|
70
59
|
/**
|
|
71
60
|
* Owns one provider-native map batch.
|
|
72
61
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* fanout; `ToolCall` owns the physical provider-operation invocation. This
|
|
76
|
-
* Module supplies the missing semantic rule between them: exactly one
|
|
77
|
-
* aggregate operation may be dispatched, and it must be split completely
|
|
78
|
-
* before every owned member receipt is completed.
|
|
62
|
+
* It reads each member's completed-result cache before assembling one physical
|
|
63
|
+
* provider batch for the misses, then atomically stores each successful split.
|
|
79
64
|
*/
|
|
80
65
|
export function createNativeBatchToolCallJob(dependencies: {
|
|
81
66
|
receiptCohort: ToolCallReceiptCohortJob;
|
|
@@ -98,68 +83,26 @@ export function createNativeBatchToolCallJob(dependencies: {
|
|
|
98
83
|
forceFailedRefresh: member.forceFailedRefresh,
|
|
99
84
|
waitMaxAttempts: member.waitMaxAttempts,
|
|
100
85
|
})),
|
|
101
|
-
// Do not make locally owned rows wait behind a foreign receipt's
|
|
102
|
-
// bounded recovery. The first aggregate can run now; any receipt
|
|
103
|
-
// reclaimed after the wait forms its own later aggregate operation.
|
|
104
|
-
eager: true,
|
|
105
86
|
});
|
|
106
87
|
const executeOwned = async (): Promise<void> => {
|
|
107
88
|
const owned = ownedMembers(cohort, input.members);
|
|
108
89
|
if (owned.length === 0) return;
|
|
109
90
|
const physical = input.buildPhysicalCall({ members: owned });
|
|
110
91
|
validatePhysicalCall(physical, input.owner);
|
|
111
|
-
let leaseLost: ToolCallReceiptLeaseLostError | null = null;
|
|
112
|
-
const heartbeatIntervalMs = input.heartbeat
|
|
113
|
-
? typeof input.heartbeat.intervalMs === 'function'
|
|
114
|
-
? input.heartbeat.intervalMs({ members: owned })
|
|
115
|
-
: input.heartbeat.intervalMs
|
|
116
|
-
: null;
|
|
117
|
-
const heartbeat =
|
|
118
|
-
input.heartbeat && heartbeatIntervalMs !== null
|
|
119
|
-
? cohort.startHeartbeat({
|
|
120
|
-
intervalMs: positiveHeartbeatInterval(heartbeatIntervalMs),
|
|
121
|
-
onLeaseLost(error) {
|
|
122
|
-
leaseLost ??= error;
|
|
123
|
-
},
|
|
124
|
-
onTransientFailure: input.heartbeat.onTransientFailure,
|
|
125
|
-
})
|
|
126
|
-
: null;
|
|
127
92
|
try {
|
|
128
|
-
await assertCohortOwned(cohort, heartbeat, leaseLost);
|
|
129
93
|
const aggregate = await dependencies.physicalToolCall.call(physical);
|
|
130
|
-
await
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
members: owned,
|
|
136
|
-
});
|
|
137
|
-
validateSplit(owned, split);
|
|
138
|
-
} catch (error) {
|
|
139
|
-
throw error;
|
|
140
|
-
}
|
|
94
|
+
const split = await input.splitResult({
|
|
95
|
+
result: aggregate,
|
|
96
|
+
members: owned,
|
|
97
|
+
});
|
|
98
|
+
validateSplit(owned, split);
|
|
141
99
|
await cohort.complete(split);
|
|
142
100
|
} catch (error) {
|
|
143
|
-
|
|
144
|
-
// terminal fact for every owned member. Lease loss is the exception:
|
|
145
|
-
// a stale owner must not overwrite the new holder's receipt state.
|
|
146
|
-
if (!(error instanceof ToolCallReceiptLeaseLostError)) {
|
|
147
|
-
await failOwned(cohort, owned, error);
|
|
148
|
-
}
|
|
101
|
+
await failOwned(cohort, owned, error);
|
|
149
102
|
throw error;
|
|
150
|
-
} finally {
|
|
151
|
-
heartbeat?.stop();
|
|
152
103
|
}
|
|
153
104
|
};
|
|
154
105
|
|
|
155
|
-
// Start the known-owned aggregate before foreign receipt hydration. The
|
|
156
|
-
// settlement is still awaited before collection, and any newly reclaimed
|
|
157
|
-
// member gets a distinct physical operation rather than being silently
|
|
158
|
-
// omitted from this batch.
|
|
159
|
-
const initialExecution = executeOwned();
|
|
160
|
-
const settlement = cohort.settle();
|
|
161
|
-
await initialExecution;
|
|
162
|
-
await settlement;
|
|
163
106
|
await executeOwned();
|
|
164
107
|
|
|
165
108
|
return await collectMemberResults(cohort, input.members);
|
|
@@ -178,21 +121,11 @@ function ownedMembers(
|
|
|
178
121
|
owned.push({
|
|
179
122
|
...member,
|
|
180
123
|
receipt: state.lease,
|
|
181
|
-
...(state.leaseExpiresAt ? { leaseExpiresAt: state.leaseExpiresAt } : {}),
|
|
182
124
|
});
|
|
183
125
|
}
|
|
184
126
|
return owned;
|
|
185
127
|
}
|
|
186
128
|
|
|
187
|
-
function positiveHeartbeatInterval(value: number): number {
|
|
188
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
189
|
-
throw new Error(
|
|
190
|
-
'Native Batch Tool Call heartbeat interval must be positive.',
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
return Math.max(1, Math.floor(value));
|
|
194
|
-
}
|
|
195
|
-
|
|
196
129
|
async function collectMemberResults(
|
|
197
130
|
cohort: ToolCallReceiptCohort,
|
|
198
131
|
members: NativeBatchToolCallMember[],
|
|
@@ -204,11 +137,10 @@ async function collectMemberResults(
|
|
|
204
137
|
results.set(member.memberId, state.result);
|
|
205
138
|
continue;
|
|
206
139
|
}
|
|
207
|
-
if (state.kind === 'following'
|
|
140
|
+
if (state.kind === 'following') {
|
|
208
141
|
results.set(member.memberId, await state.result);
|
|
209
142
|
continue;
|
|
210
143
|
}
|
|
211
|
-
if (state.kind === 'failed') throw state.error;
|
|
212
144
|
throw new Error(
|
|
213
145
|
`Native Batch Tool Call left owned member ${member.memberId} without a terminal result.`,
|
|
214
146
|
);
|
|
@@ -216,21 +148,6 @@ async function collectMemberResults(
|
|
|
216
148
|
return results;
|
|
217
149
|
}
|
|
218
150
|
|
|
219
|
-
async function assertCohortOwned(
|
|
220
|
-
cohort: ToolCallReceiptCohort,
|
|
221
|
-
heartbeat: { assertOwned(): void } | null,
|
|
222
|
-
leaseLost: ToolCallReceiptLeaseLostError | null,
|
|
223
|
-
): Promise<void> {
|
|
224
|
-
heartbeat?.assertOwned();
|
|
225
|
-
if (leaseLost) throw leaseLost;
|
|
226
|
-
const status = await cohort.heartbeat();
|
|
227
|
-
if (status === 'terminal') {
|
|
228
|
-
throw new ToolCallReceiptLeaseLostError('native-batch-cohort');
|
|
229
|
-
}
|
|
230
|
-
heartbeat?.assertOwned();
|
|
231
|
-
if (leaseLost) throw leaseLost;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
151
|
async function failOwned(
|
|
235
152
|
cohort: ToolCallReceiptCohort,
|
|
236
153
|
owned: NativeBatchOwnedToolCallMember[],
|