deepline 0.1.85 → 0.1.89
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/cli/index.js +419 -70
- package/dist/cli/index.mjs +438 -83
- package/dist/index.d.mts +442 -60
- package/dist/index.d.ts +442 -60
- package/dist/index.js +161 -4
- package/dist/index.mjs +161 -4
- package/dist/repo/apps/play-runner-workers/src/dedup-do.ts +1 -0
- package/dist/repo/apps/play-runner-workers/src/entry.ts +276 -192
- package/dist/repo/sdk/src/client.ts +155 -1
- package/dist/repo/sdk/src/http.ts +11 -0
- package/dist/repo/sdk/src/index.ts +24 -1
- package/dist/repo/sdk/src/play.ts +198 -15
- package/dist/repo/sdk/src/release.ts +2 -2
- package/dist/repo/sdk/src/types.ts +61 -0
- package/dist/repo/sdk/src/worker-play-entry.ts +6 -3
- package/dist/repo/shared_libs/play-runtime/cell-staleness.ts +23 -0
- package/dist/repo/shared_libs/play-runtime/coordinator-headers.ts +10 -0
- package/dist/repo/shared_libs/play-runtime/scheduler-backend.ts +10 -1
- package/dist/repo/shared_libs/play-runtime/tool-result.ts +202 -12
- package/dist/repo/shared_libs/plays/bundling/index.ts +23 -16
- package/dist/repo/shared_libs/plays/dataset.ts +2 -0
- package/dist/repo/shared_libs/plays/row-identity.ts +14 -8
- package/package.json +1 -1
|
@@ -62,7 +62,7 @@ import {
|
|
|
62
62
|
} from './child-play-await';
|
|
63
63
|
import type { AnyBatchOperationStrategy } from '../../../shared_libs/play-runtime/batching-types';
|
|
64
64
|
import {
|
|
65
|
-
|
|
65
|
+
parseToolExecuteResponse,
|
|
66
66
|
createToolExecuteResult,
|
|
67
67
|
deserializeToolExecuteResult,
|
|
68
68
|
isSerializedToolExecuteResult,
|
|
@@ -600,7 +600,7 @@ type WorkerCtxCallbacks = {
|
|
|
600
600
|
nodeId: string;
|
|
601
601
|
progress: LiveNodeProgressSnapshot;
|
|
602
602
|
forceFlush?: boolean;
|
|
603
|
-
}) => void
|
|
603
|
+
}) => void | Promise<void>;
|
|
604
604
|
onMapStarted?: (nodeId: string, at?: number) => void;
|
|
605
605
|
onMapCompleted?: (nodeId: string, at?: number) => void;
|
|
606
606
|
onToolCalled?: (toolId: string, at?: number) => void;
|
|
@@ -1289,18 +1289,12 @@ async function callToolDirect(
|
|
|
1289
1289
|
});
|
|
1290
1290
|
if (res.ok) {
|
|
1291
1291
|
const body = (await res.json()) as Record<string, unknown>;
|
|
1292
|
-
const
|
|
1293
|
-
const status =
|
|
1294
|
-
typeof body.status === 'string'
|
|
1295
|
-
? body.status
|
|
1296
|
-
: result == null
|
|
1297
|
-
? 'no_result'
|
|
1298
|
-
: 'completed';
|
|
1292
|
+
const parsed = parseToolExecuteResponse(toolId, body);
|
|
1299
1293
|
return wrapWorkerToolResult(
|
|
1300
1294
|
toolId,
|
|
1301
|
-
result,
|
|
1302
|
-
|
|
1303
|
-
status,
|
|
1295
|
+
parsed.result,
|
|
1296
|
+
parsed.metadata,
|
|
1297
|
+
parsed.status,
|
|
1304
1298
|
);
|
|
1305
1299
|
}
|
|
1306
1300
|
|
|
@@ -1338,121 +1332,6 @@ async function callToolDirect(
|
|
|
1338
1332
|
throw lastError ?? new Error(`tool ${toolId} failed before execution.`);
|
|
1339
1333
|
}
|
|
1340
1334
|
|
|
1341
|
-
function parseExecuteToolMetadata(
|
|
1342
|
-
toolId: string,
|
|
1343
|
-
data: Record<string, unknown>,
|
|
1344
|
-
): ToolResultMetadataInput | null {
|
|
1345
|
-
const metadata = data._metadata;
|
|
1346
|
-
if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) {
|
|
1347
|
-
return null;
|
|
1348
|
-
}
|
|
1349
|
-
const tool = (metadata as Record<string, unknown>).tool;
|
|
1350
|
-
if (!tool || typeof tool !== 'object' || Array.isArray(tool)) return null;
|
|
1351
|
-
const record = tool as Record<string, unknown>;
|
|
1352
|
-
const metadataToolId =
|
|
1353
|
-
typeof record.toolId === 'string' && record.toolId.trim()
|
|
1354
|
-
? record.toolId.trim()
|
|
1355
|
-
: toolId;
|
|
1356
|
-
return {
|
|
1357
|
-
toolId: metadataToolId,
|
|
1358
|
-
extractors: parseExtractorMetadata(record.extractors),
|
|
1359
|
-
targetGetters: parseGetterMetadata(record.targetGetters),
|
|
1360
|
-
listExtractorPaths: parseStringArray(record.listExtractorPaths),
|
|
1361
|
-
listIdentityGetters: parseGetterMetadata(record.listIdentityGetters),
|
|
1362
|
-
};
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
function parseExtractorMetadata(
|
|
1366
|
-
value: unknown,
|
|
1367
|
-
): ToolResultMetadataInput['extractors'] {
|
|
1368
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
1369
|
-
return undefined;
|
|
1370
|
-
}
|
|
1371
|
-
const entries = Object.entries(value as Record<string, unknown>).flatMap(
|
|
1372
|
-
([key, descriptor]) => {
|
|
1373
|
-
if (
|
|
1374
|
-
!descriptor ||
|
|
1375
|
-
typeof descriptor !== 'object' ||
|
|
1376
|
-
Array.isArray(descriptor)
|
|
1377
|
-
) {
|
|
1378
|
-
return [];
|
|
1379
|
-
}
|
|
1380
|
-
const record = descriptor as Record<string, unknown>;
|
|
1381
|
-
const paths = parseStringArray(record.paths);
|
|
1382
|
-
if (paths.length === 0) return [];
|
|
1383
|
-
const transforms = parseStringArray(record.transforms);
|
|
1384
|
-
const enumValues = parseStringArray(record.enum);
|
|
1385
|
-
const overrides = parseExtractorOverrides(record.overrides);
|
|
1386
|
-
return [
|
|
1387
|
-
[
|
|
1388
|
-
key,
|
|
1389
|
-
{
|
|
1390
|
-
paths,
|
|
1391
|
-
...(transforms.length > 0 ? { transforms } : {}),
|
|
1392
|
-
...(enumValues.length > 0 ? { enum: enumValues } : {}),
|
|
1393
|
-
...(overrides.length > 0 ? { overrides } : {}),
|
|
1394
|
-
},
|
|
1395
|
-
],
|
|
1396
|
-
] as const;
|
|
1397
|
-
},
|
|
1398
|
-
);
|
|
1399
|
-
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
function parseExtractorOverrides(
|
|
1403
|
-
value: unknown,
|
|
1404
|
-
): NonNullable<
|
|
1405
|
-
NonNullable<ToolResultMetadataInput['extractors']>[string]['overrides']
|
|
1406
|
-
> {
|
|
1407
|
-
if (!Array.isArray(value)) return [];
|
|
1408
|
-
return value.flatMap((entry) => {
|
|
1409
|
-
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
1410
|
-
return [];
|
|
1411
|
-
}
|
|
1412
|
-
const record = entry as Record<string, unknown>;
|
|
1413
|
-
const paths = parseStringArray(record.paths);
|
|
1414
|
-
if (paths.length === 0) return [];
|
|
1415
|
-
const equals =
|
|
1416
|
-
record.equals === null ||
|
|
1417
|
-
typeof record.equals === 'string' ||
|
|
1418
|
-
typeof record.equals === 'number' ||
|
|
1419
|
-
typeof record.equals === 'boolean'
|
|
1420
|
-
? record.equals
|
|
1421
|
-
: true;
|
|
1422
|
-
const overrideValue = record.value;
|
|
1423
|
-
if (
|
|
1424
|
-
overrideValue !== null &&
|
|
1425
|
-
typeof overrideValue !== 'string' &&
|
|
1426
|
-
typeof overrideValue !== 'number' &&
|
|
1427
|
-
typeof overrideValue !== 'boolean'
|
|
1428
|
-
) {
|
|
1429
|
-
return [];
|
|
1430
|
-
}
|
|
1431
|
-
return [{ paths, equals, value: overrideValue }];
|
|
1432
|
-
});
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
|
-
function parseGetterMetadata(
|
|
1436
|
-
value: unknown,
|
|
1437
|
-
): Record<string, readonly string[]> | undefined {
|
|
1438
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
1439
|
-
return undefined;
|
|
1440
|
-
}
|
|
1441
|
-
const entries = Object.entries(value as Record<string, unknown>)
|
|
1442
|
-
.map(([key, paths]) => [key, parseStringArray(paths)] as const)
|
|
1443
|
-
.filter(
|
|
1444
|
-
(entry): entry is readonly [string, string[]] => entry[1].length > 0,
|
|
1445
|
-
);
|
|
1446
|
-
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
function parseStringArray(value: unknown): string[] {
|
|
1450
|
-
if (!Array.isArray(value)) return [];
|
|
1451
|
-
return value
|
|
1452
|
-
.map((entry) => (typeof entry === 'string' ? entry.trim() : ''))
|
|
1453
|
-
.filter(Boolean);
|
|
1454
|
-
}
|
|
1455
|
-
|
|
1456
1335
|
function toolMetadataFallback(toolId: string): ToolResultMetadataInput {
|
|
1457
1336
|
if (toolId === 'test_rate_limit') {
|
|
1458
1337
|
return {
|
|
@@ -1592,6 +1471,7 @@ type WorkerToolBatchRequest = {
|
|
|
1592
1471
|
};
|
|
1593
1472
|
|
|
1594
1473
|
const WORKER_TOOL_BATCH_GRACE_MS = 250;
|
|
1474
|
+
const MAP_EXECUTION_HEARTBEAT_INTERVAL_MS = 5_000;
|
|
1595
1475
|
// Fallback batch-chunk parallelism when a tool declares no provider rate hints.
|
|
1596
1476
|
// Matches the prior hardcoded `Math.min(4, ...)` so undeclared providers keep
|
|
1597
1477
|
// their previous batching behavior; declared providers tighten via the
|
|
@@ -1599,6 +1479,10 @@ const WORKER_TOOL_BATCH_GRACE_MS = 250;
|
|
|
1599
1479
|
const WORKER_TOOL_BATCH_DEFAULT_PARALLELISM = 4;
|
|
1600
1480
|
const WORKER_RETRY_SAFE_5XX_TOOLS = new Set(['test_transient_500']);
|
|
1601
1481
|
|
|
1482
|
+
function sleepWorkerMs(ms: number): Promise<void> {
|
|
1483
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1602
1486
|
function stepProgramColumnName(parentField: string, stepId: string): string {
|
|
1603
1487
|
return sqlSafePlayColumnName(`${parentField}.${stepId}`);
|
|
1604
1488
|
}
|
|
@@ -2951,42 +2835,71 @@ function augmentSheetContractWithDatasetFields(input: {
|
|
|
2951
2835
|
outputFields?: readonly string[];
|
|
2952
2836
|
}): PlaySheetContract {
|
|
2953
2837
|
const outputFields = new Set(input.outputFields ?? []);
|
|
2954
|
-
const existingFields = new Set(
|
|
2955
|
-
input.contract.columns.flatMap((column) =>
|
|
2956
|
-
typeof column.field === 'string' ? [column.field] : [],
|
|
2957
|
-
),
|
|
2958
|
-
);
|
|
2959
|
-
const existingSqlNames = new Set(
|
|
2960
|
-
input.contract.columns.map((column) => column.sqlName),
|
|
2961
|
-
);
|
|
2962
|
-
const columns = [...input.contract.columns];
|
|
2963
2838
|
const candidateFields = new Set<string>();
|
|
2964
2839
|
for (const row of input.rows) {
|
|
2965
2840
|
for (const field of Object.keys(row)) {
|
|
2966
|
-
|
|
2841
|
+
if (isDatasetPayloadField(field)) {
|
|
2842
|
+
candidateFields.add(field);
|
|
2843
|
+
}
|
|
2967
2844
|
}
|
|
2968
2845
|
}
|
|
2969
2846
|
for (const field of outputFields) {
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
for (const field of candidateFields) {
|
|
2973
|
-
if (!isDatasetPayloadField(field) || existingFields.has(field)) {
|
|
2974
|
-
continue;
|
|
2847
|
+
if (isDatasetPayloadField(field)) {
|
|
2848
|
+
candidateFields.add(field);
|
|
2975
2849
|
}
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
const existingFields = new Set<string>();
|
|
2853
|
+
const existingSqlNames = new Set<string>();
|
|
2854
|
+
const inputColumns: PlaySheetContract['columns'] = [];
|
|
2855
|
+
const outputColumns: PlaySheetContract['columns'] = [];
|
|
2856
|
+
const appendColumn = (
|
|
2857
|
+
target: PlaySheetContract['columns'],
|
|
2858
|
+
column: PlaySheetContract['columns'][number],
|
|
2859
|
+
) => {
|
|
2860
|
+
const field = typeof column.field === 'string' ? column.field : column.id;
|
|
2861
|
+
const sqlName = column.sqlName.trim();
|
|
2862
|
+
if (
|
|
2863
|
+
!field ||
|
|
2864
|
+
!sqlName ||
|
|
2865
|
+
existingFields.has(field) ||
|
|
2866
|
+
existingSqlNames.has(sqlName)
|
|
2867
|
+
) {
|
|
2868
|
+
return;
|
|
2979
2869
|
}
|
|
2980
2870
|
existingFields.add(field);
|
|
2981
2871
|
existingSqlNames.add(sqlName);
|
|
2982
|
-
|
|
2872
|
+
target.push(column);
|
|
2873
|
+
};
|
|
2874
|
+
|
|
2875
|
+
for (const column of input.contract.columns) {
|
|
2876
|
+
const field = typeof column.field === 'string' ? column.field : column.id;
|
|
2877
|
+
if (
|
|
2878
|
+
column.source === 'input' &&
|
|
2879
|
+
field === input.contract.tableNamespace &&
|
|
2880
|
+
!candidateFields.has(field)
|
|
2881
|
+
) {
|
|
2882
|
+
continue;
|
|
2883
|
+
}
|
|
2884
|
+
appendColumn(
|
|
2885
|
+
column.source === 'input' ? inputColumns : outputColumns,
|
|
2886
|
+
column,
|
|
2887
|
+
);
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
for (const field of candidateFields) {
|
|
2891
|
+
if (existingFields.has(field)) continue;
|
|
2892
|
+
const sqlName = sqlSafePlayColumnName(field);
|
|
2893
|
+
if (existingSqlNames.has(sqlName)) continue;
|
|
2894
|
+
appendColumn(outputFields.has(field) ? outputColumns : inputColumns, {
|
|
2983
2895
|
id: `runtime:${input.contract.tableNamespace}:${field}`,
|
|
2984
2896
|
sqlName,
|
|
2985
2897
|
source: outputFields.has(field) ? 'datasetColumn' : 'input',
|
|
2986
2898
|
field,
|
|
2987
2899
|
});
|
|
2988
2900
|
}
|
|
2989
|
-
|
|
2901
|
+
|
|
2902
|
+
return { ...input.contract, columns: [...inputColumns, ...outputColumns] };
|
|
2990
2903
|
}
|
|
2991
2904
|
|
|
2992
2905
|
async function persistCompletedMapRows(input: {
|
|
@@ -3542,8 +3455,11 @@ function createMinimalWorkerCtx(
|
|
|
3542
3455
|
softWorkflowStepBudget: plan?.chunkPlan.softWorkflowStepBudget,
|
|
3543
3456
|
});
|
|
3544
3457
|
const outputFields = fieldEntries.map(([field]) => field);
|
|
3545
|
-
const updateMapProgress = (
|
|
3546
|
-
|
|
3458
|
+
const updateMapProgress = (
|
|
3459
|
+
progress: LiveNodeProgressSnapshot,
|
|
3460
|
+
options?: { forceFlush?: boolean },
|
|
3461
|
+
): void | Promise<void> => {
|
|
3462
|
+
return callbacks?.onNodeProgress?.({
|
|
3547
3463
|
nodeId: mapNodeId,
|
|
3548
3464
|
progress: {
|
|
3549
3465
|
artifactTableNamespace: name,
|
|
@@ -3551,19 +3467,62 @@ function createMinimalWorkerCtx(
|
|
|
3551
3467
|
...progress,
|
|
3552
3468
|
updatedAt: progress.updatedAt ?? nowMs(),
|
|
3553
3469
|
},
|
|
3554
|
-
forceFlush: true,
|
|
3470
|
+
forceFlush: options?.forceFlush ?? true,
|
|
3555
3471
|
});
|
|
3556
3472
|
};
|
|
3557
3473
|
const formatMapProgressMessage = (completed: number, total?: number) =>
|
|
3558
3474
|
typeof total === 'number' && Number.isFinite(total) && total > 0
|
|
3559
3475
|
? `${completed.toLocaleString()} / ${total.toLocaleString()} rows processed`
|
|
3560
3476
|
: `${completed.toLocaleString()} rows processed`;
|
|
3477
|
+
const formatMapPreparingMessage = (total?: number) =>
|
|
3478
|
+
typeof total === 'number' && Number.isFinite(total) && total > 0
|
|
3479
|
+
? `Preparing ${total.toLocaleString()} rows`
|
|
3480
|
+
: 'Preparing rows';
|
|
3481
|
+
const formatMapQueuedMessage = (input: {
|
|
3482
|
+
completed: number;
|
|
3483
|
+
queued: number;
|
|
3484
|
+
total?: number;
|
|
3485
|
+
}) => {
|
|
3486
|
+
const completed = Math.max(0, input.completed);
|
|
3487
|
+
const queued = Math.max(0, input.queued);
|
|
3488
|
+
if (completed > 0 && queued > 0) {
|
|
3489
|
+
return `${completed.toLocaleString()} already satisfied, ${queued.toLocaleString()} queued`;
|
|
3490
|
+
}
|
|
3491
|
+
if (queued > 0) {
|
|
3492
|
+
return `${queued.toLocaleString()} rows queued`;
|
|
3493
|
+
}
|
|
3494
|
+
return formatMapProgressMessage(completed, input.total);
|
|
3495
|
+
};
|
|
3496
|
+
const formatMapProcessingMessage = (rowsToExecute: number) =>
|
|
3497
|
+
rowsToExecute > 0
|
|
3498
|
+
? `Processing ${rowsToExecute.toLocaleString()} rows`
|
|
3499
|
+
: null;
|
|
3500
|
+
const formatMapExecutionHeartbeatMessage = (input: {
|
|
3501
|
+
rowsToExecute: number;
|
|
3502
|
+
startedRows: number;
|
|
3503
|
+
activeRows: number;
|
|
3504
|
+
completedRows: number;
|
|
3505
|
+
}) => {
|
|
3506
|
+
const rowsToExecute = Math.max(0, input.rowsToExecute);
|
|
3507
|
+
const startedRows = Math.max(0, input.startedRows);
|
|
3508
|
+
const activeRows = Math.max(0, input.activeRows);
|
|
3509
|
+
const completedRows = Math.max(0, input.completedRows);
|
|
3510
|
+
const waitingRows = Math.max(0, rowsToExecute - startedRows);
|
|
3511
|
+
const parts = [
|
|
3512
|
+
activeRows > 0 ? `${activeRows.toLocaleString()} active` : null,
|
|
3513
|
+
waitingRows > 0 ? `${waitingRows.toLocaleString()} waiting` : null,
|
|
3514
|
+
completedRows > 0 ? `${completedRows.toLocaleString()} done` : null,
|
|
3515
|
+
].filter((part): part is string => Boolean(part));
|
|
3516
|
+
const base =
|
|
3517
|
+
formatMapProcessingMessage(rowsToExecute) ?? 'Processing rows';
|
|
3518
|
+
return parts.length > 0 ? `${base} (${parts.join(', ')})` : base;
|
|
3519
|
+
};
|
|
3561
3520
|
callbacks?.onMapStarted?.(mapNodeId, mapStartedAt);
|
|
3562
|
-
updateMapProgress({
|
|
3521
|
+
await updateMapProgress({
|
|
3563
3522
|
completed: 0,
|
|
3564
3523
|
total: rowCountHint ?? undefined,
|
|
3565
3524
|
startedAt: mapStartedAt,
|
|
3566
|
-
message:
|
|
3525
|
+
message: formatMapPreparingMessage(rowCountHint ?? undefined),
|
|
3567
3526
|
});
|
|
3568
3527
|
const explicitRowKeysSeen =
|
|
3569
3528
|
opts?.key === undefined ? null : new Map<string, number>();
|
|
@@ -3637,6 +3596,8 @@ function createMinimalWorkerCtx(
|
|
|
3637
3596
|
}
|
|
3638
3597
|
};
|
|
3639
3598
|
|
|
3599
|
+
let totalRowsWritten = 0;
|
|
3600
|
+
|
|
3640
3601
|
const processChunk = async (
|
|
3641
3602
|
chunkRows: T[],
|
|
3642
3603
|
chunkStart: number,
|
|
@@ -3681,17 +3642,20 @@ function createMinimalWorkerCtx(
|
|
|
3681
3642
|
completedRows: prepared.completedRows.length,
|
|
3682
3643
|
},
|
|
3683
3644
|
});
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3645
|
+
const progressTotalRows = rowCountHint ?? chunkRows.length;
|
|
3646
|
+
const preparedCompletedRows = Math.min(
|
|
3647
|
+
progressTotalRows,
|
|
3648
|
+
totalRowsWritten + prepared.completedRows.length,
|
|
3649
|
+
);
|
|
3650
|
+
await updateMapProgress({
|
|
3651
|
+
completed: preparedCompletedRows,
|
|
3652
|
+
total: progressTotalRows,
|
|
3687
3653
|
startedAt: mapStartedAt,
|
|
3688
|
-
message:
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
chunkRows.length,
|
|
3694
|
-
),
|
|
3654
|
+
message: formatMapQueuedMessage({
|
|
3655
|
+
completed: preparedCompletedRows,
|
|
3656
|
+
queued: prepared.pendingRows.length,
|
|
3657
|
+
total: progressTotalRows,
|
|
3658
|
+
}),
|
|
3695
3659
|
});
|
|
3696
3660
|
const pendingKeys = new Set<string>();
|
|
3697
3661
|
const pendingRowsByKey = new Map<string, Record<string, unknown>>();
|
|
@@ -3735,38 +3699,83 @@ function createMinimalWorkerCtx(
|
|
|
3735
3699
|
new Set(chunkEntries.map((entry) => entry.rowKey)).size,
|
|
3736
3700
|
);
|
|
3737
3701
|
const rowsToExecute = uniqueRowsToExecuteEntries.map(({ row }) => row);
|
|
3702
|
+
const processingMessage = formatMapProcessingMessage(
|
|
3703
|
+
rowsToExecute.length,
|
|
3704
|
+
);
|
|
3705
|
+
if (processingMessage) {
|
|
3706
|
+
await updateMapProgress({
|
|
3707
|
+
completed: preparedCompletedRows,
|
|
3708
|
+
total: progressTotalRows,
|
|
3709
|
+
startedAt: mapStartedAt,
|
|
3710
|
+
message: processingMessage,
|
|
3711
|
+
});
|
|
3712
|
+
}
|
|
3738
3713
|
const rowsInserted = prepared.inserted + missingPreparedRows.length;
|
|
3739
3714
|
const rowsSkipped = Math.max(
|
|
3740
3715
|
0,
|
|
3741
3716
|
prepared.skipped - missingPreparedRows.length,
|
|
3742
3717
|
);
|
|
3743
|
-
let
|
|
3744
|
-
let
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3718
|
+
let completedExecutedRows = 0;
|
|
3719
|
+
let startedExecutedRows = 0;
|
|
3720
|
+
let activeExecutedRows = 0;
|
|
3721
|
+
let lastChunkProgressAt = 0;
|
|
3722
|
+
let lastExecutionHeartbeatAt = nowMs();
|
|
3723
|
+
const completedRowsForProgress = () =>
|
|
3724
|
+
Math.min(
|
|
3725
|
+
progressTotalRows,
|
|
3726
|
+
totalRowsWritten +
|
|
3727
|
+
prepared.completedRows.length +
|
|
3728
|
+
completedExecutedRows,
|
|
3729
|
+
);
|
|
3730
|
+
const reportExecutionHeartbeat = (force = false) => {
|
|
3748
3731
|
const now = nowMs();
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3732
|
+
if (
|
|
3733
|
+
!force &&
|
|
3734
|
+
now - lastExecutionHeartbeatAt < MAP_EXECUTION_HEARTBEAT_INTERVAL_MS
|
|
3735
|
+
) {
|
|
3736
|
+
return;
|
|
3737
|
+
}
|
|
3738
|
+
lastExecutionHeartbeatAt = now;
|
|
3739
|
+
void updateMapProgress(
|
|
3740
|
+
{
|
|
3741
|
+
completed: completedRowsForProgress(),
|
|
3742
|
+
total: progressTotalRows,
|
|
3743
|
+
startedAt: mapStartedAt,
|
|
3744
|
+
message: formatMapExecutionHeartbeatMessage({
|
|
3745
|
+
rowsToExecute: rowsToExecute.length,
|
|
3746
|
+
startedRows: startedExecutedRows,
|
|
3747
|
+
activeRows: activeExecutedRows,
|
|
3748
|
+
completedRows: completedExecutedRows,
|
|
3749
|
+
}),
|
|
3750
|
+
},
|
|
3751
|
+
{ forceFlush: force },
|
|
3752
3752
|
);
|
|
3753
|
-
|
|
3753
|
+
};
|
|
3754
|
+
const reportChunkProgress = (force = false) => {
|
|
3755
|
+
const now = nowMs();
|
|
3756
|
+
const completed = completedRowsForProgress();
|
|
3757
|
+
const isTerminalEstimate = completed >= progressTotalRows;
|
|
3754
3758
|
if (
|
|
3759
|
+
!force &&
|
|
3755
3760
|
!isTerminalEstimate &&
|
|
3756
|
-
now -
|
|
3761
|
+
now - lastChunkProgressAt < RUN_LEDGER_FLUSH_INTERVAL_MS
|
|
3757
3762
|
) {
|
|
3758
3763
|
return;
|
|
3759
3764
|
}
|
|
3760
|
-
|
|
3761
|
-
updateMapProgress(
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3765
|
+
lastChunkProgressAt = now;
|
|
3766
|
+
void updateMapProgress(
|
|
3767
|
+
{
|
|
3768
|
+
completed,
|
|
3769
|
+
total: progressTotalRows,
|
|
3770
|
+
startedAt: mapStartedAt,
|
|
3771
|
+
message: formatMapProgressMessage(completed, progressTotalRows),
|
|
3772
|
+
},
|
|
3773
|
+
{ forceFlush: force },
|
|
3774
|
+
);
|
|
3775
|
+
};
|
|
3776
|
+
const reportSettledToolRequests = (count: number) => {
|
|
3777
|
+
if (count <= 0) return;
|
|
3778
|
+
reportChunkProgress(false);
|
|
3770
3779
|
};
|
|
3771
3780
|
// Row concurrency comes from the Governor: an explicit map concurrency is
|
|
3772
3781
|
// clamped to the policy row-max, otherwise the policy default. Each row
|
|
@@ -3812,7 +3821,12 @@ function createMinimalWorkerCtx(
|
|
|
3812
3821
|
const rowSlot = await governor.acquireRowSlot({
|
|
3813
3822
|
signal: abortSignal,
|
|
3814
3823
|
});
|
|
3824
|
+
let rowMarkedActive = false;
|
|
3815
3825
|
try {
|
|
3826
|
+
startedExecutedRows += 1;
|
|
3827
|
+
activeExecutedRows += 1;
|
|
3828
|
+
rowMarkedActive = true;
|
|
3829
|
+
reportExecutionHeartbeat(false);
|
|
3816
3830
|
const entry = uniqueRowsToExecuteEntries[myIndex]!;
|
|
3817
3831
|
const pendingRow = pendingRowsByKey.get(entry.rowKey);
|
|
3818
3832
|
const row = pendingRow
|
|
@@ -3985,7 +3999,13 @@ function createMinimalWorkerCtx(
|
|
|
3985
3999
|
? cellMetaPatch
|
|
3986
4000
|
: undefined;
|
|
3987
4001
|
executedRows[myIndex] = enriched as T & Record<string, unknown>;
|
|
4002
|
+
completedExecutedRows += 1;
|
|
4003
|
+
reportChunkProgress(false);
|
|
3988
4004
|
} finally {
|
|
4005
|
+
if (rowMarkedActive) {
|
|
4006
|
+
activeExecutedRows = Math.max(0, activeExecutedRows - 1);
|
|
4007
|
+
reportExecutionHeartbeat(false);
|
|
4008
|
+
}
|
|
3989
4009
|
rowSlot.release();
|
|
3990
4010
|
}
|
|
3991
4011
|
}
|
|
@@ -4031,7 +4051,27 @@ function createMinimalWorkerCtx(
|
|
|
4031
4051
|
});
|
|
4032
4052
|
};
|
|
4033
4053
|
const workersStartedAt = nowMs();
|
|
4034
|
-
|
|
4054
|
+
// Track completion with a boolean flag rather than narrowing a
|
|
4055
|
+
// closure-assigned `| null` variable: TypeScript's control-flow analysis
|
|
4056
|
+
// does not see the assignment inside `.then(...)`, so a
|
|
4057
|
+
// `while (results === null)` loop would narrow it to `never` afterwards.
|
|
4058
|
+
let workerSettled = false;
|
|
4059
|
+
const workerResultsPromise = Promise.allSettled(workers).then(
|
|
4060
|
+
(results) => {
|
|
4061
|
+
workerSettled = true;
|
|
4062
|
+
return results;
|
|
4063
|
+
},
|
|
4064
|
+
);
|
|
4065
|
+
while (!workerSettled) {
|
|
4066
|
+
await Promise.race([
|
|
4067
|
+
workerResultsPromise,
|
|
4068
|
+
sleepWorkerMs(MAP_EXECUTION_HEARTBEAT_INTERVAL_MS),
|
|
4069
|
+
]);
|
|
4070
|
+
if (!workerSettled) {
|
|
4071
|
+
reportExecutionHeartbeat(false);
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
const workerResults = await workerResultsPromise;
|
|
4035
4075
|
recordRunnerPerfTrace({
|
|
4036
4076
|
req,
|
|
4037
4077
|
phase: 'runner.map_chunk.execute_workers',
|
|
@@ -4198,7 +4238,7 @@ function createMinimalWorkerCtx(
|
|
|
4198
4238
|
`inserted=${totalRowsInserted} skipped=${totalRowsSkipped}`;
|
|
4199
4239
|
const completedAt = nowMs();
|
|
4200
4240
|
callbacks?.onMapCompleted?.(mapNodeId, completedAt);
|
|
4201
|
-
updateMapProgress({
|
|
4241
|
+
void updateMapProgress({
|
|
4202
4242
|
completed: totalRowsWritten,
|
|
4203
4243
|
total: totalRowsWritten,
|
|
4204
4244
|
completedAt,
|
|
@@ -4249,7 +4289,6 @@ function createMinimalWorkerCtx(
|
|
|
4249
4289
|
});
|
|
4250
4290
|
};
|
|
4251
4291
|
|
|
4252
|
-
let totalRowsWritten = 0;
|
|
4253
4292
|
let chunkIndex = 0;
|
|
4254
4293
|
let chunkStart = 0;
|
|
4255
4294
|
for await (const chunkRows of iterDatasetChunks(inputRows, rowsPerChunk)) {
|
|
@@ -4263,7 +4302,7 @@ function createMinimalWorkerCtx(
|
|
|
4263
4302
|
totalRowsDuplicateReused += chunkResult.rowsDuplicateReused;
|
|
4264
4303
|
totalRowsInserted += chunkResult.rowsInserted;
|
|
4265
4304
|
totalRowsSkipped += chunkResult.rowsSkipped;
|
|
4266
|
-
updateMapProgress({
|
|
4305
|
+
await updateMapProgress({
|
|
4267
4306
|
completed: totalRowsWritten,
|
|
4268
4307
|
total: rowCountHint ?? undefined,
|
|
4269
4308
|
message: formatMapProgressMessage(
|
|
@@ -5374,6 +5413,10 @@ async function executeRunRequest(
|
|
|
5374
5413
|
];
|
|
5375
5414
|
let lastLedgerFlushAt = startedAt;
|
|
5376
5415
|
let ledgerFlushInFlight: Promise<void> = Promise.resolve();
|
|
5416
|
+
let ledgerFlushQueueDepth = 0;
|
|
5417
|
+
let lastCoordinatorProgressPublishAt = 0;
|
|
5418
|
+
let coordinatorProgressPublishInFlight: Promise<void> = Promise.resolve();
|
|
5419
|
+
let coordinatorProgressPublishQueueDepth = 0;
|
|
5377
5420
|
|
|
5378
5421
|
const appendRunLogLine = (line: string) => {
|
|
5379
5422
|
const trimmed = redactSecretsFromLogString(line.trim());
|
|
@@ -5496,6 +5539,36 @@ async function executeRunRequest(
|
|
|
5496
5539
|
});
|
|
5497
5540
|
};
|
|
5498
5541
|
|
|
5542
|
+
const flushCoordinatorProgressEvent = (force: boolean): Promise<void> => {
|
|
5543
|
+
const now = nowMs();
|
|
5544
|
+
if (
|
|
5545
|
+
!force &&
|
|
5546
|
+
now - lastCoordinatorProgressPublishAt <
|
|
5547
|
+
MAP_EXECUTION_HEARTBEAT_INTERVAL_MS
|
|
5548
|
+
) {
|
|
5549
|
+
return Promise.resolve();
|
|
5550
|
+
}
|
|
5551
|
+
if (!force && coordinatorProgressPublishQueueDepth > 0) {
|
|
5552
|
+
return Promise.resolve();
|
|
5553
|
+
}
|
|
5554
|
+
lastCoordinatorProgressPublishAt = now;
|
|
5555
|
+
coordinatorProgressPublishQueueDepth += 1;
|
|
5556
|
+
coordinatorProgressPublishInFlight = coordinatorProgressPublishInFlight
|
|
5557
|
+
.catch(() => undefined)
|
|
5558
|
+
.then(async () => {
|
|
5559
|
+
try {
|
|
5560
|
+
await publishCoordinatorProgressEvent(now);
|
|
5561
|
+
} finally {
|
|
5562
|
+
coordinatorProgressPublishQueueDepth = Math.max(
|
|
5563
|
+
0,
|
|
5564
|
+
coordinatorProgressPublishQueueDepth - 1,
|
|
5565
|
+
);
|
|
5566
|
+
}
|
|
5567
|
+
})
|
|
5568
|
+
.catch(() => undefined);
|
|
5569
|
+
return force ? coordinatorProgressPublishInFlight : Promise.resolve();
|
|
5570
|
+
};
|
|
5571
|
+
|
|
5499
5572
|
const appendStepLifecycleEvent = (event: PlayStepLifecycleEvent) => {
|
|
5500
5573
|
updateStepProgress({
|
|
5501
5574
|
nodeId: event.nodeId,
|
|
@@ -5595,15 +5668,19 @@ async function executeRunRequest(
|
|
|
5595
5668
|
return events;
|
|
5596
5669
|
};
|
|
5597
5670
|
|
|
5598
|
-
const flushLedgerEvents = (force: boolean): void => {
|
|
5599
|
-
if (!options?.persistResultDatasets) return;
|
|
5671
|
+
const flushLedgerEvents = (force: boolean): Promise<void> => {
|
|
5672
|
+
if (!options?.persistResultDatasets) return Promise.resolve();
|
|
5600
5673
|
const now = nowMs();
|
|
5601
5674
|
if (!force && now - lastLedgerFlushAt < RUN_LEDGER_FLUSH_INTERVAL_MS) {
|
|
5602
|
-
return;
|
|
5675
|
+
return Promise.resolve();
|
|
5676
|
+
}
|
|
5677
|
+
if (!force && ledgerFlushQueueDepth > 0) {
|
|
5678
|
+
return Promise.resolve();
|
|
5603
5679
|
}
|
|
5604
5680
|
const events = drainPendingLedgerEvents(now);
|
|
5605
|
-
if (events.length === 0) return;
|
|
5681
|
+
if (events.length === 0) return Promise.resolve();
|
|
5606
5682
|
lastLedgerFlushAt = now;
|
|
5683
|
+
ledgerFlushQueueDepth += 1;
|
|
5607
5684
|
ledgerFlushInFlight = ledgerFlushInFlight
|
|
5608
5685
|
.catch(() => undefined)
|
|
5609
5686
|
.then(async () => {
|
|
@@ -5616,10 +5693,12 @@ async function executeRunRequest(
|
|
|
5616
5693
|
} catch {
|
|
5617
5694
|
pendingLedgerEvents = [...events, ...pendingLedgerEvents];
|
|
5618
5695
|
throw new Error('runtime run-ledger append failed');
|
|
5696
|
+
} finally {
|
|
5697
|
+
ledgerFlushQueueDepth = Math.max(0, ledgerFlushQueueDepth - 1);
|
|
5619
5698
|
}
|
|
5620
|
-
await publishCoordinatorProgressEvent(now).catch(() => undefined);
|
|
5621
5699
|
})
|
|
5622
5700
|
.catch(() => undefined);
|
|
5701
|
+
return force ? ledgerFlushInFlight : Promise.resolve();
|
|
5623
5702
|
};
|
|
5624
5703
|
|
|
5625
5704
|
const flushTerminalLedgerEvents = async (
|
|
@@ -5661,7 +5740,12 @@ async function executeRunRequest(
|
|
|
5661
5740
|
const workerCallbacks: WorkerCtxCallbacks = {
|
|
5662
5741
|
onNodeProgress: (input) => {
|
|
5663
5742
|
updateStepProgress(input);
|
|
5664
|
-
|
|
5743
|
+
const force = Boolean(input.forceFlush);
|
|
5744
|
+
const ledgerFlush = flushLedgerEvents(force);
|
|
5745
|
+
const progressFlush = flushCoordinatorProgressEvent(force);
|
|
5746
|
+
return force
|
|
5747
|
+
? Promise.all([ledgerFlush, progressFlush]).then(() => undefined)
|
|
5748
|
+
: Promise.resolve();
|
|
5665
5749
|
},
|
|
5666
5750
|
onMapStarted: (nodeId, at) => stepLifecycle?.onMapStarted(nodeId, at),
|
|
5667
5751
|
onMapCompleted: (nodeId, at) => stepLifecycle?.onMapCompleted(nodeId, at),
|