deepline 0.1.210 → 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/entry.ts +53 -1
- 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 +161 -99
- package/dist/bundling-sources/sdk/src/play.ts +2 -0
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +343 -83
- 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 +93 -12
- package/dist/cli/index.mjs +93 -12
- package/dist/index.d.mts +17 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +22 -5
- package/dist/index.mjs +22 -5
- package/package.json +1 -1
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
isPlayDataset,
|
|
19
19
|
iteratePlayDatasetInputPages,
|
|
20
20
|
materializePlayDatasetInput,
|
|
21
|
+
resolveMaterializeLimitCap,
|
|
21
22
|
} from '@shared_libs/plays/dataset';
|
|
22
23
|
import type { PlayDataset, PlayDatasetInput } from '@shared_libs/plays/dataset';
|
|
23
24
|
import {
|
|
@@ -261,6 +262,7 @@ const rowContext = new AsyncLocalStorage<{
|
|
|
261
262
|
rowKey?: string;
|
|
262
263
|
mapScope?: MapExecutionScope;
|
|
263
264
|
}>();
|
|
265
|
+
const toolExecutionOverrides = new AsyncLocalStorage<{ force: boolean }>();
|
|
264
266
|
const PROGRESS_HEARTBEAT_INTERVAL_MS = 1_000;
|
|
265
267
|
const PURE_JS_HEARTBEAT_ROW_INTERVAL = 250;
|
|
266
268
|
/**
|
|
@@ -819,6 +821,111 @@ function stableDigest(value: string): string {
|
|
|
819
821
|
return sha256Hex(value);
|
|
820
822
|
}
|
|
821
823
|
|
|
824
|
+
// Receipt keys contain canonicalized tool inputs. Runtime diagnostics must never
|
|
825
|
+
// emit them directly; a short digest is enough to correlate gateway, runner,
|
|
826
|
+
// and waiter observations for one failing run.
|
|
827
|
+
function runtimeReceiptKeyDigest(key: string): string {
|
|
828
|
+
return stableDigest(key).slice(0, 12);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function runtimeReceiptReadSummary(input: {
|
|
832
|
+
requested: readonly string[];
|
|
833
|
+
receipts: readonly (RuntimeStepReceipt | null | undefined)[];
|
|
834
|
+
resolved: ReadonlyMap<string, RuntimeStepReceipt>;
|
|
835
|
+
}): string {
|
|
836
|
+
const statuses = input.receipts.reduce<Record<string, number>>(
|
|
837
|
+
(counts, receipt) => {
|
|
838
|
+
const status = receipt?.status ?? 'missing';
|
|
839
|
+
counts[status] = (counts[status] ?? 0) + 1;
|
|
840
|
+
return counts;
|
|
841
|
+
},
|
|
842
|
+
{},
|
|
843
|
+
);
|
|
844
|
+
const positionalMismatches = input.receipts.reduce(
|
|
845
|
+
(count, receipt, index) =>
|
|
846
|
+
receipt && receipt.key.trim() !== (input.requested[index] ?? '').trim()
|
|
847
|
+
? count + 1
|
|
848
|
+
: count,
|
|
849
|
+
0,
|
|
850
|
+
);
|
|
851
|
+
const missingRequested = input.requested.filter(
|
|
852
|
+
(key) => !input.resolved.has(key),
|
|
853
|
+
);
|
|
854
|
+
return (
|
|
855
|
+
`requested=${input.requested.length} returned=${input.receipts.length} ` +
|
|
856
|
+
`resolved=${input.resolved.size} positional_mismatches=${positionalMismatches} ` +
|
|
857
|
+
`request_digests=${input.requested.map(runtimeReceiptKeyDigest).join(',')} ` +
|
|
858
|
+
`returned_digests=${input.receipts
|
|
859
|
+
.map((receipt) =>
|
|
860
|
+
receipt ? runtimeReceiptKeyDigest(receipt.key) : 'null',
|
|
861
|
+
)
|
|
862
|
+
.join(',')} ` +
|
|
863
|
+
`missing_digests=${missingRequested
|
|
864
|
+
.map(runtimeReceiptKeyDigest)
|
|
865
|
+
.join(',')} ` +
|
|
866
|
+
`statuses=${Object.entries(statuses)
|
|
867
|
+
.map(([status, count]) => `${status}:${count}`)
|
|
868
|
+
.join(',')}`
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
const runtimeReceiptReadTraceEnabled =
|
|
873
|
+
process.env.DEEPLINE_RUNTIME_RECEIPT_TRACE === '1';
|
|
874
|
+
|
|
875
|
+
function runtimeReceiptReadTrace(input: {
|
|
876
|
+
keys: readonly string[];
|
|
877
|
+
receipts: Array<RuntimeStepReceipt | null>;
|
|
878
|
+
byKey: ReadonlyMap<string, RuntimeStepReceipt>;
|
|
879
|
+
}): string | null {
|
|
880
|
+
if (!runtimeReceiptReadTraceEnabled) return null;
|
|
881
|
+
const digest = (value: string) => stableDigest(value).slice(0, 16);
|
|
882
|
+
const statusCounts = input.receipts.reduce<Record<string, number>>(
|
|
883
|
+
(counts, receipt) => {
|
|
884
|
+
if (receipt) {
|
|
885
|
+
counts[receipt.status] = (counts[receipt.status] ?? 0) + 1;
|
|
886
|
+
}
|
|
887
|
+
return counts;
|
|
888
|
+
},
|
|
889
|
+
{},
|
|
890
|
+
);
|
|
891
|
+
const expiredInFlightCount = input.receipts.filter((receipt) => {
|
|
892
|
+
if (
|
|
893
|
+
receipt?.status !== 'queued' &&
|
|
894
|
+
receipt?.status !== 'pending' &&
|
|
895
|
+
receipt?.status !== 'running'
|
|
896
|
+
) {
|
|
897
|
+
return false;
|
|
898
|
+
}
|
|
899
|
+
const expiresAt = receipt.leaseExpiresAt
|
|
900
|
+
? Date.parse(receipt.leaseExpiresAt)
|
|
901
|
+
: Number.NaN;
|
|
902
|
+
return Number.isFinite(expiresAt) && expiresAt <= Date.now();
|
|
903
|
+
}).length;
|
|
904
|
+
return (
|
|
905
|
+
'[runtime-receipt-context.read-trace] ' +
|
|
906
|
+
JSON.stringify({
|
|
907
|
+
requestedCount: input.keys.length,
|
|
908
|
+
returnedCount: input.receipts.filter(Boolean).length,
|
|
909
|
+
mapSize: input.byKey.size,
|
|
910
|
+
statusCounts,
|
|
911
|
+
expiredInFlightCount,
|
|
912
|
+
requestedOrderDigest: digest(input.keys.join('\u0000')),
|
|
913
|
+
returnedOrderDigest: digest(
|
|
914
|
+
input.receipts
|
|
915
|
+
.map((receipt) =>
|
|
916
|
+
receipt ? `${digest(receipt.key)}:${receipt.status}` : '-',
|
|
917
|
+
)
|
|
918
|
+
.join('\u0000'),
|
|
919
|
+
),
|
|
920
|
+
mapOrderDigest: digest(
|
|
921
|
+
[...input.byKey.entries()]
|
|
922
|
+
.map(([key, receipt]) => `${digest(key)}:${receipt.status}`)
|
|
923
|
+
.join('\u0000'),
|
|
924
|
+
),
|
|
925
|
+
})
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
|
|
822
929
|
type DurableCtxOperation = 'step' | 'tool' | 'fetch';
|
|
823
930
|
|
|
824
931
|
function durableCtxKey(input: {
|
|
@@ -1176,12 +1283,15 @@ export class PlayContextImpl {
|
|
|
1176
1283
|
);
|
|
1177
1284
|
}
|
|
1178
1285
|
assertNoSecretTaint(request.input, 'ctx.tools.execute input');
|
|
1286
|
+
const force =
|
|
1287
|
+
request.force === true ||
|
|
1288
|
+
toolExecutionOverrides.getStore()?.force === true;
|
|
1179
1289
|
return this.executeTool(
|
|
1180
1290
|
request.id.trim(),
|
|
1181
1291
|
request.tool,
|
|
1182
1292
|
request.input,
|
|
1183
1293
|
request.description ||
|
|
1184
|
-
|
|
1294
|
+
force ||
|
|
1185
1295
|
request.staleAfterSeconds !== undefined ||
|
|
1186
1296
|
request.timeoutMs !== undefined ||
|
|
1187
1297
|
request.receiptWaitMs !== undefined
|
|
@@ -1189,7 +1299,7 @@ export class PlayContextImpl {
|
|
|
1189
1299
|
...(request.description
|
|
1190
1300
|
? { description: request.description }
|
|
1191
1301
|
: {}),
|
|
1192
|
-
...(
|
|
1302
|
+
...(force ? { force: true } : {}),
|
|
1193
1303
|
...(request.staleAfterSeconds !== undefined
|
|
1194
1304
|
? { staleAfterSeconds: request.staleAfterSeconds }
|
|
1195
1305
|
: {}),
|
|
@@ -1204,6 +1314,10 @@ export class PlayContextImpl {
|
|
|
1204
1314
|
) as Promise<TOutput>;
|
|
1205
1315
|
},
|
|
1206
1316
|
};
|
|
1317
|
+
|
|
1318
|
+
async __deeplineRunWithForcedTools<T>(run: () => Promise<T>): Promise<T> {
|
|
1319
|
+
return await toolExecutionOverrides.run({ force: true }, run);
|
|
1320
|
+
}
|
|
1207
1321
|
readonly customerDb = {
|
|
1208
1322
|
query: async <TRow extends object = Record<string, unknown>>(
|
|
1209
1323
|
statement:
|
|
@@ -2161,6 +2275,21 @@ export class PlayContextImpl {
|
|
|
2161
2275
|
);
|
|
2162
2276
|
if (normalized) byKey.set(normalized.key, normalized);
|
|
2163
2277
|
}
|
|
2278
|
+
if (runtimeReceiptReadTraceEnabled) {
|
|
2279
|
+
this.log(
|
|
2280
|
+
`[runtime-receipt-normalize] ${runtimeReceiptReadSummary({
|
|
2281
|
+
requested: uniqueKeys,
|
|
2282
|
+
receipts,
|
|
2283
|
+
resolved: byKey,
|
|
2284
|
+
})}`,
|
|
2285
|
+
);
|
|
2286
|
+
const trace = runtimeReceiptReadTrace({
|
|
2287
|
+
keys: uniqueKeys,
|
|
2288
|
+
receipts,
|
|
2289
|
+
byKey,
|
|
2290
|
+
});
|
|
2291
|
+
if (trace) this.log(trace);
|
|
2292
|
+
}
|
|
2164
2293
|
return byKey;
|
|
2165
2294
|
}
|
|
2166
2295
|
|
|
@@ -2207,6 +2336,16 @@ export class PlayContextImpl {
|
|
|
2207
2336
|
);
|
|
2208
2337
|
if (normalized) byKey.set(normalized.key, normalized);
|
|
2209
2338
|
}
|
|
2339
|
+
if (runtimeReceiptReadTraceEnabled) {
|
|
2340
|
+
this.log(
|
|
2341
|
+
`[runtime-receipt-claim-normalize] reclaim_running=${reclaimRunning} force_refresh=${forceRefresh} force_failed_refresh=${forceFailedRefresh} ` +
|
|
2342
|
+
runtimeReceiptReadSummary({
|
|
2343
|
+
requested: uniqueKeys,
|
|
2344
|
+
receipts,
|
|
2345
|
+
resolved: byKey,
|
|
2346
|
+
}),
|
|
2347
|
+
);
|
|
2348
|
+
}
|
|
2210
2349
|
return byKey;
|
|
2211
2350
|
}
|
|
2212
2351
|
|
|
@@ -2667,6 +2806,7 @@ export class PlayContextImpl {
|
|
|
2667
2806
|
receiptKeys: keys,
|
|
2668
2807
|
store: this.durableReceiptExecutionStore(),
|
|
2669
2808
|
maxAttempts,
|
|
2809
|
+
log: (message) => this.log(message),
|
|
2670
2810
|
});
|
|
2671
2811
|
}
|
|
2672
2812
|
|
|
@@ -3755,6 +3895,13 @@ export class PlayContextImpl {
|
|
|
3755
3895
|
let duplicateReuseCount = 0;
|
|
3756
3896
|
let executedCount = 0;
|
|
3757
3897
|
const previewRows: Record<string, unknown>[] = [];
|
|
3898
|
+
// A bounded dataset may be materialized by authored code immediately
|
|
3899
|
+
// after run(). Keep the rows produced by this exact awaited execution so
|
|
3900
|
+
// that read-after-write does not depend on a separate sheet read becoming
|
|
3901
|
+
// visible. Datasets above the public materialize cap remain fully
|
|
3902
|
+
// streaming and sheet-backed.
|
|
3903
|
+
const materializedResultRowLimit = resolveMaterializeLimitCap();
|
|
3904
|
+
const immediateMaterializedRows: Record<string, unknown>[] = [];
|
|
3758
3905
|
|
|
3759
3906
|
const persistMapRows = async (rows: PersistableMapRow[]) => {
|
|
3760
3907
|
if (!this.#options.onMapRowsCompleted || rows.length === 0) {
|
|
@@ -4038,8 +4185,11 @@ export class PlayContextImpl {
|
|
|
4038
4185
|
this.activeMapCellMeta = null;
|
|
4039
4186
|
|
|
4040
4187
|
for (const row of mapResult.completedRows) {
|
|
4041
|
-
|
|
4042
|
-
previewRows.push(
|
|
4188
|
+
const materializedRow = this.toMaterializedOutputRow(row.data);
|
|
4189
|
+
if (previewRows.length < 5) previewRows.push(materializedRow);
|
|
4190
|
+
if (immediateMaterializedRows.length < materializedResultRowLimit) {
|
|
4191
|
+
immediateMaterializedRows.push(materializedRow);
|
|
4192
|
+
}
|
|
4043
4193
|
}
|
|
4044
4194
|
executedCount += rowsToExecute.length;
|
|
4045
4195
|
successfulCount += mapResult.completedRows.length;
|
|
@@ -4100,6 +4250,42 @@ export class PlayContextImpl {
|
|
|
4100
4250
|
return result.rows.map((row) => this.toMaterializedOutputRow(row));
|
|
4101
4251
|
};
|
|
4102
4252
|
const materializeRuntimeBackedMapRows = async (limit?: number) => {
|
|
4253
|
+
if (limit !== undefined && limit <= 0) return [];
|
|
4254
|
+
const availableResultRows = Math.min(
|
|
4255
|
+
successfulCount,
|
|
4256
|
+
materializedResultRowLimit,
|
|
4257
|
+
);
|
|
4258
|
+
if (
|
|
4259
|
+
immediateMaterializedRows.length === availableResultRows &&
|
|
4260
|
+
(limit !== undefined || successfulCount <= materializedResultRowLimit)
|
|
4261
|
+
) {
|
|
4262
|
+
return limit === undefined
|
|
4263
|
+
? immediateMaterializedRows.slice()
|
|
4264
|
+
: immediateMaterializedRows.slice(0, limit);
|
|
4265
|
+
}
|
|
4266
|
+
const pageSize = 1000;
|
|
4267
|
+
const materialized: Record<string, unknown>[] = [];
|
|
4268
|
+
let offset = 0;
|
|
4269
|
+
while (true) {
|
|
4270
|
+
const remaining =
|
|
4271
|
+
limit === undefined
|
|
4272
|
+
? pageSize
|
|
4273
|
+
: Math.max(0, limit - materialized.length);
|
|
4274
|
+
if (remaining === 0) break;
|
|
4275
|
+
const rows = await readRuntimeBackedMapRows({
|
|
4276
|
+
limit: Math.min(pageSize, remaining),
|
|
4277
|
+
offset,
|
|
4278
|
+
});
|
|
4279
|
+
if (rows.length === 0) break;
|
|
4280
|
+
materialized.push(...rows);
|
|
4281
|
+
if (limit !== undefined && materialized.length >= limit) {
|
|
4282
|
+
return materialized.slice(0, limit);
|
|
4283
|
+
}
|
|
4284
|
+
offset += rows.length;
|
|
4285
|
+
}
|
|
4286
|
+
return materialized;
|
|
4287
|
+
};
|
|
4288
|
+
const materializeFullPersistedMapRows = async (limit?: number) => {
|
|
4103
4289
|
if (limit !== undefined && limit <= 0) return [];
|
|
4104
4290
|
const pageSize = 1000;
|
|
4105
4291
|
const materialized: Record<string, unknown>[] = [];
|
|
@@ -4156,14 +4342,22 @@ export class PlayContextImpl {
|
|
|
4156
4342
|
peek: async (limit) =>
|
|
4157
4343
|
limit <= 0
|
|
4158
4344
|
? []
|
|
4159
|
-
:
|
|
4160
|
-
limit,
|
|
4161
|
-
|
|
4162
|
-
|
|
4345
|
+
: immediateMaterializedRows.length >=
|
|
4346
|
+
Math.min(limit, successfulCount)
|
|
4347
|
+
? immediateMaterializedRows.slice(0, limit)
|
|
4348
|
+
: await readRuntimeBackedMapRows({
|
|
4349
|
+
limit,
|
|
4350
|
+
offset: 0,
|
|
4351
|
+
}),
|
|
4163
4352
|
materialize: materializeRuntimeBackedMapRows,
|
|
4353
|
+
materializeFullPersistedDataset: materializeFullPersistedMapRows,
|
|
4164
4354
|
iterate: () =>
|
|
4165
4355
|
({
|
|
4166
4356
|
async *[Symbol.asyncIterator]() {
|
|
4357
|
+
if (immediateMaterializedRows.length === successfulCount) {
|
|
4358
|
+
for (const row of immediateMaterializedRows) yield row;
|
|
4359
|
+
return;
|
|
4360
|
+
}
|
|
4167
4361
|
const pageSize = 1000;
|
|
4168
4362
|
let offset = 0;
|
|
4169
4363
|
while (true) {
|
|
@@ -4598,39 +4792,33 @@ export class PlayContextImpl {
|
|
|
4598
4792
|
});
|
|
4599
4793
|
}
|
|
4600
4794
|
|
|
4601
|
-
|
|
4795
|
+
// This map's terminal rows have already crossed the persistence barrier
|
|
4796
|
+
// above. Keep that verified snapshot as the authored return value instead
|
|
4797
|
+
// of immediately re-reading the Runtime Sheet projection. A second read
|
|
4798
|
+
// can observe a lagging/dynamically incomplete physical projection even
|
|
4799
|
+
// though the terminal write succeeded (notably for inline-child object
|
|
4800
|
+
// outputs on Absurd), making `materialize()` disagree with durable truth.
|
|
4801
|
+
// Neon remains the backing for later API/export reads of this handle.
|
|
4802
|
+
const terminalRows = results.map((row) =>
|
|
4803
|
+
this.toMaterializedOutputRow(row),
|
|
4804
|
+
);
|
|
4805
|
+
const materializeTerminalRows = async (limit?: number) =>
|
|
4806
|
+
limit === undefined
|
|
4807
|
+
? terminalRows.slice()
|
|
4808
|
+
: terminalRows.slice(0, Math.max(0, limit));
|
|
4809
|
+
const runtimeSheetBacked =
|
|
4602
4810
|
this.#options.runtimeSheetBackedMapDatasets === true &&
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
userEmail: this.#options.userEmail,
|
|
4615
|
-
runId: this.#options.runId!,
|
|
4616
|
-
},
|
|
4617
|
-
{
|
|
4618
|
-
tableNamespace: resolvedTableNamespace,
|
|
4619
|
-
runId: this.#options.runId!,
|
|
4620
|
-
limit: input.limit,
|
|
4621
|
-
offset: input.offset,
|
|
4622
|
-
},
|
|
4623
|
-
);
|
|
4624
|
-
return result.rows.map((row) => this.toMaterializedOutputRow(row));
|
|
4625
|
-
}
|
|
4626
|
-
: null;
|
|
4627
|
-
const materializeRuntimeBackedMapRows = async (limit?: number) => {
|
|
4628
|
-
if (!readRuntimeBackedMapRows) {
|
|
4629
|
-
return limit === undefined
|
|
4630
|
-
? results.map((row) => this.toMaterializedOutputRow(row))
|
|
4631
|
-
: results
|
|
4632
|
-
.slice(0, Math.max(0, limit))
|
|
4633
|
-
.map((row) => this.toMaterializedOutputRow(row));
|
|
4811
|
+
Boolean(
|
|
4812
|
+
this.#options.baseUrl &&
|
|
4813
|
+
this.#options.executorToken &&
|
|
4814
|
+
this.#options.playName &&
|
|
4815
|
+
this.#options.runId,
|
|
4816
|
+
);
|
|
4817
|
+
const materializeFullPersistedMapRows = async (limit?: number) => {
|
|
4818
|
+
if (!runtimeSheetBacked) {
|
|
4819
|
+
throw new Error(
|
|
4820
|
+
'The full persisted dataset is unavailable because this run result has no Runtime Sheet backing.',
|
|
4821
|
+
);
|
|
4634
4822
|
}
|
|
4635
4823
|
if (limit !== undefined && limit <= 0) return [];
|
|
4636
4824
|
const pageSize = 1000;
|
|
@@ -4642,10 +4830,25 @@ export class PlayContextImpl {
|
|
|
4642
4830
|
? pageSize
|
|
4643
4831
|
: Math.max(0, limit - materialized.length);
|
|
4644
4832
|
if (remaining === 0) break;
|
|
4645
|
-
const
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4833
|
+
const result = await readRuntimeSheetDatasetRows(
|
|
4834
|
+
{
|
|
4835
|
+
baseUrl: this.#options.baseUrl!,
|
|
4836
|
+
executorToken: this.#options.executorToken!,
|
|
4837
|
+
dbSessionStrategy: this.#options.dbSessionStrategy,
|
|
4838
|
+
playName: this.#options.playName!,
|
|
4839
|
+
userEmail: this.#options.userEmail,
|
|
4840
|
+
runId: this.#options.runId!,
|
|
4841
|
+
},
|
|
4842
|
+
{
|
|
4843
|
+
tableNamespace: resolvedTableNamespace,
|
|
4844
|
+
runId: this.#options.runId!,
|
|
4845
|
+
limit: Math.min(pageSize, remaining),
|
|
4846
|
+
offset,
|
|
4847
|
+
},
|
|
4848
|
+
);
|
|
4849
|
+
const rows = result.rows.map((row) =>
|
|
4850
|
+
this.toMaterializedOutputRow(row),
|
|
4851
|
+
);
|
|
4649
4852
|
if (rows.length === 0) break;
|
|
4650
4853
|
materialized.push(...rows);
|
|
4651
4854
|
if (limit !== undefined && materialized.length >= limit) {
|
|
@@ -4663,7 +4866,7 @@ export class PlayContextImpl {
|
|
|
4663
4866
|
resolvedTableNamespace,
|
|
4664
4867
|
),
|
|
4665
4868
|
count: results.length,
|
|
4666
|
-
backing:
|
|
4869
|
+
backing: runtimeSheetBacked
|
|
4667
4870
|
? {
|
|
4668
4871
|
storage: 'neon_sheet',
|
|
4669
4872
|
sheet: {
|
|
@@ -4689,37 +4892,17 @@ export class PlayContextImpl {
|
|
|
4689
4892
|
},
|
|
4690
4893
|
resolvers: {
|
|
4691
4894
|
count: async () => results.length,
|
|
4692
|
-
peek: async (limit) =>
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
:
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
})
|
|
4700
|
-
: results
|
|
4701
|
-
.slice(0, Math.max(0, limit))
|
|
4702
|
-
.map((row) => this.toMaterializedOutputRow(row)),
|
|
4703
|
-
materialize: materializeRuntimeBackedMapRows,
|
|
4895
|
+
peek: async (limit) => terminalRows.slice(0, Math.max(0, limit)),
|
|
4896
|
+
materialize: materializeTerminalRows,
|
|
4897
|
+
...(runtimeSheetBacked
|
|
4898
|
+
? {
|
|
4899
|
+
materializeFullPersistedDataset: materializeFullPersistedMapRows,
|
|
4900
|
+
}
|
|
4901
|
+
: {}),
|
|
4704
4902
|
iterate: () =>
|
|
4705
4903
|
({
|
|
4706
4904
|
async *[Symbol.asyncIterator]() {
|
|
4707
|
-
|
|
4708
|
-
const pageSize = 1000;
|
|
4709
|
-
let offset = 0;
|
|
4710
|
-
while (true) {
|
|
4711
|
-
const rows = await readRuntimeBackedMapRows({
|
|
4712
|
-
limit: pageSize,
|
|
4713
|
-
offset,
|
|
4714
|
-
});
|
|
4715
|
-
if (rows.length === 0) return;
|
|
4716
|
-
for (const row of rows) {
|
|
4717
|
-
yield row;
|
|
4718
|
-
}
|
|
4719
|
-
offset += rows.length;
|
|
4720
|
-
}
|
|
4721
|
-
}
|
|
4722
|
-
for (const row of results) {
|
|
4905
|
+
for (const row of terminalRows) {
|
|
4723
4906
|
yield row;
|
|
4724
4907
|
}
|
|
4725
4908
|
},
|
|
@@ -7431,18 +7614,32 @@ export class PlayContextImpl {
|
|
|
7431
7614
|
const forcedExisting = forcedReceiptKeys.length
|
|
7432
7615
|
? await this.getRuntimeStepReceipts(forcedReceiptKeys)
|
|
7433
7616
|
: new Map<string, RuntimeStepReceipt>();
|
|
7434
|
-
|
|
7617
|
+
// A forced refresh must never duplicate a provider call owned by a
|
|
7618
|
+
// live lease. It can, however, immediately claim pending/unleased
|
|
7619
|
+
// work and any expired lease. Waiting first for those receipts turns
|
|
7620
|
+
// an already-recoverable interruption into a five-minute stall.
|
|
7621
|
+
const forcedLiveReceiptKeys = new Set(
|
|
7435
7622
|
[...forcedExisting]
|
|
7436
|
-
.filter(
|
|
7437
|
-
(
|
|
7438
|
-
receipt.status
|
|
7439
|
-
receipt.status
|
|
7440
|
-
receipt.status
|
|
7441
|
-
|
|
7623
|
+
.filter(([, receipt]) => {
|
|
7624
|
+
if (
|
|
7625
|
+
receipt.status !== 'queued' &&
|
|
7626
|
+
receipt.status !== 'pending' &&
|
|
7627
|
+
receipt.status !== 'running'
|
|
7628
|
+
) {
|
|
7629
|
+
return false;
|
|
7630
|
+
}
|
|
7631
|
+
if (!receipt.leaseId?.trim()) return false;
|
|
7632
|
+
const expiresAt = receipt.leaseExpiresAt
|
|
7633
|
+
? Date.parse(receipt.leaseExpiresAt)
|
|
7634
|
+
: Number.NaN;
|
|
7635
|
+
// Keep an unparseable leased receipt conservative. The
|
|
7636
|
+
// fenced claim remains the recovery path after the wait.
|
|
7637
|
+
return !Number.isFinite(expiresAt) || expiresAt > Date.now();
|
|
7638
|
+
})
|
|
7442
7639
|
.map(([receiptKey]) => receiptKey),
|
|
7443
7640
|
);
|
|
7444
7641
|
const claimableForcedReceiptKeys = forcedReceiptKeys.filter(
|
|
7445
|
-
(receiptKey) => !
|
|
7642
|
+
(receiptKey) => !forcedLiveReceiptKeys.has(receiptKey),
|
|
7446
7643
|
);
|
|
7447
7644
|
const claims =
|
|
7448
7645
|
normalReceiptKeys.length > 0
|
|
@@ -7706,16 +7903,39 @@ export class PlayContextImpl {
|
|
|
7706
7903
|
const processExistingRunningReceiptWaits =
|
|
7707
7904
|
async (): Promise<void> => {
|
|
7708
7905
|
if (durableExistingRunningWaits.length === 0) return;
|
|
7906
|
+
const waitingReceiptKeys = durableExistingRunningWaits.map(
|
|
7907
|
+
(wait) => wait.receiptKey,
|
|
7908
|
+
);
|
|
7709
7909
|
const waitMaxAttempts = Math.max(
|
|
7710
7910
|
...durableExistingRunningWaits.map(
|
|
7711
7911
|
(wait) => wait.waitMaxAttempts ?? 0,
|
|
7712
7912
|
),
|
|
7713
7913
|
);
|
|
7914
|
+
if (runtimeReceiptReadTraceEnabled) {
|
|
7915
|
+
this.log(
|
|
7916
|
+
`[runtime-receipt-wait] phase=start requested=${waitingReceiptKeys.length} ` +
|
|
7917
|
+
`request_digests=${waitingReceiptKeys
|
|
7918
|
+
.map(runtimeReceiptKeyDigest)
|
|
7919
|
+
.join(',')} max_attempts=${waitMaxAttempts || 'default'}`,
|
|
7920
|
+
);
|
|
7921
|
+
}
|
|
7714
7922
|
const waited = await this.waitForCompletedRuntimeToolReceipts(
|
|
7715
|
-
|
|
7923
|
+
waitingReceiptKeys,
|
|
7716
7924
|
waitMaxAttempts > 0 ? waitMaxAttempts : undefined,
|
|
7717
7925
|
);
|
|
7718
|
-
|
|
7926
|
+
if (runtimeReceiptReadTraceEnabled) {
|
|
7927
|
+
this.log(
|
|
7928
|
+
`[runtime-receipt-wait] phase=settled completed=${waited.completed.size} ` +
|
|
7929
|
+
`failed=${waited.failed.size} timed_out=${waited.timedOut.size} ` +
|
|
7930
|
+
`completed_digests=${[...waited.completed.keys()]
|
|
7931
|
+
.map(runtimeReceiptKeyDigest)
|
|
7932
|
+
.join(',')} ` +
|
|
7933
|
+
`timed_out_digests=${[...waited.timedOut]
|
|
7934
|
+
.map(runtimeReceiptKeyDigest)
|
|
7935
|
+
.join(',')}`,
|
|
7936
|
+
);
|
|
7937
|
+
}
|
|
7938
|
+
const deliverySettlements = await Promise.allSettled(
|
|
7719
7939
|
durableExistingRunningWaits.map(async (wait) => {
|
|
7720
7940
|
const failed = waited.failed.get(wait.receiptKey);
|
|
7721
7941
|
if (failed) {
|
|
@@ -7737,6 +7957,28 @@ export class PlayContextImpl {
|
|
|
7737
7957
|
);
|
|
7738
7958
|
}),
|
|
7739
7959
|
);
|
|
7960
|
+
if (runtimeReceiptReadTraceEnabled) {
|
|
7961
|
+
const rejected = deliverySettlements.flatMap(
|
|
7962
|
+
(settlement, index) => {
|
|
7963
|
+
if (settlement.status !== 'rejected') return [];
|
|
7964
|
+
const receiptKey =
|
|
7965
|
+
durableExistingRunningWaits[index]?.receiptKey;
|
|
7966
|
+
return receiptKey
|
|
7967
|
+
? [
|
|
7968
|
+
`${runtimeReceiptKeyDigest(receiptKey)}:${
|
|
7969
|
+
settlement.reason instanceof Error
|
|
7970
|
+
? settlement.reason.name
|
|
7971
|
+
: 'non_error'
|
|
7972
|
+
}`,
|
|
7973
|
+
]
|
|
7974
|
+
: ['unknown'];
|
|
7975
|
+
},
|
|
7976
|
+
);
|
|
7977
|
+
this.log(
|
|
7978
|
+
`[runtime-receipt-wait] phase=delivery settled=${deliverySettlements.length} ` +
|
|
7979
|
+
`rejected=${rejected.length} rejected_digests=${rejected.join(',')}`,
|
|
7980
|
+
);
|
|
7981
|
+
}
|
|
7740
7982
|
};
|
|
7741
7983
|
|
|
7742
7984
|
for (const [receiptKey, group] of requestsByReceiptKey) {
|
|
@@ -8222,11 +8464,17 @@ export class PlayContextImpl {
|
|
|
8222
8464
|
// The Governor's tool slot is the single seam for tool-call budget + the
|
|
8223
8465
|
// global tool-concurrency backstop + per-(org, provider) pacing. It blocks
|
|
8224
8466
|
// until all three are satisfied and is held (across retries) until release.
|
|
8467
|
+
const admissionStartedAt = Date.now();
|
|
8225
8468
|
const toolSlot = await this.resourceGovernor.acquireTool({
|
|
8226
8469
|
orgId: this.#options.orgId ?? null,
|
|
8227
8470
|
providerResourceKey: `tool:${toolId}`,
|
|
8228
8471
|
toolId,
|
|
8229
8472
|
});
|
|
8473
|
+
if (runtimeReceiptReadTraceEnabled) {
|
|
8474
|
+
this.log(
|
|
8475
|
+
`[perf] tool call id=${toolId} phase=governor_admission elapsed_ms=${Date.now() - admissionStartedAt}`,
|
|
8476
|
+
);
|
|
8477
|
+
}
|
|
8230
8478
|
try {
|
|
8231
8479
|
return await withActiveSpan(
|
|
8232
8480
|
'plays.tool.execute',
|
|
@@ -8286,7 +8534,13 @@ export class PlayContextImpl {
|
|
|
8286
8534
|
: null;
|
|
8287
8535
|
try {
|
|
8288
8536
|
const providerCallStartedAt = Date.now();
|
|
8537
|
+
const ownershipStartedAt = Date.now();
|
|
8289
8538
|
await options?.beforeProviderCall?.();
|
|
8539
|
+
if (runtimeReceiptReadTraceEnabled) {
|
|
8540
|
+
this.log(
|
|
8541
|
+
`[perf] tool call id=${toolId} phase=before_provider_ownership elapsed_ms=${Date.now() - ownershipStartedAt}`,
|
|
8542
|
+
);
|
|
8543
|
+
}
|
|
8290
8544
|
const heartbeatIntervalMs = hasReceiptHeartbeat
|
|
8291
8545
|
? runtimeLeaseHeartbeatIntervalFromExpiry({
|
|
8292
8546
|
leaseExpiresAt: options?.receiptLeaseExpiresAt,
|
|
@@ -8316,6 +8570,7 @@ export class PlayContextImpl {
|
|
|
8316
8570
|
: null;
|
|
8317
8571
|
receiptHeartbeatSupervisor?.start();
|
|
8318
8572
|
try {
|
|
8573
|
+
const integrationFetchStartedAt = Date.now();
|
|
8319
8574
|
response = await fetch(url, {
|
|
8320
8575
|
method: 'POST',
|
|
8321
8576
|
signal: abortController?.signal,
|
|
@@ -8380,6 +8635,11 @@ export class PlayContextImpl {
|
|
|
8380
8635
|
: {}),
|
|
8381
8636
|
}),
|
|
8382
8637
|
});
|
|
8638
|
+
if (runtimeReceiptReadTraceEnabled) {
|
|
8639
|
+
this.log(
|
|
8640
|
+
`[perf] tool call id=${toolId} phase=integration_fetch_headers elapsed_ms=${Date.now() - integrationFetchStartedAt} status=${response.status}`,
|
|
8641
|
+
);
|
|
8642
|
+
}
|
|
8383
8643
|
this.resourceGovernor.observe({
|
|
8384
8644
|
toolId,
|
|
8385
8645
|
providerResourceKey: `tool:${toolId}`,
|