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.
@@ -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
- adaptV2ExecuteResponseToToolResult,
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 { result } = adaptV2ExecuteResponseToToolResult(body);
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
- parseExecuteToolMetadata(toolId, body),
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
- candidateFields.add(field);
2841
+ if (isDatasetPayloadField(field)) {
2842
+ candidateFields.add(field);
2843
+ }
2967
2844
  }
2968
2845
  }
2969
2846
  for (const field of outputFields) {
2970
- candidateFields.add(field);
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
- const sqlName = sqlSafePlayColumnName(field);
2977
- if (existingSqlNames.has(sqlName)) {
2978
- continue;
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
- columns.push({
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
- return { ...input.contract, columns };
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 = (progress: LiveNodeProgressSnapshot) => {
3546
- callbacks?.onNodeProgress?.({
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: formatMapProgressMessage(0, rowCountHint ?? undefined),
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
- updateMapProgress({
3685
- completed: prepared.completedRows.length,
3686
- total: chunkRows.length,
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
- prepared.pendingRows.length > 0
3690
- ? `${prepared.pendingRows.length.toLocaleString()} rows queued`
3691
- : formatMapProgressMessage(
3692
- prepared.completedRows.length,
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 settledToolRequests = 0;
3744
- let lastToolProgressAt = 0;
3745
- const reportSettledToolRequests = (count: number) => {
3746
- if (count <= 0) return;
3747
- settledToolRequests += count;
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
- const estimatedCompleted = Math.min(
3750
- chunkRows.length,
3751
- prepared.completedRows.length + settledToolRequests,
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
- const isTerminalEstimate = estimatedCompleted >= chunkRows.length;
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 - lastToolProgressAt < RUN_LEDGER_FLUSH_INTERVAL_MS
3761
+ now - lastChunkProgressAt < RUN_LEDGER_FLUSH_INTERVAL_MS
3757
3762
  ) {
3758
3763
  return;
3759
3764
  }
3760
- lastToolProgressAt = now;
3761
- updateMapProgress({
3762
- completed: estimatedCompleted,
3763
- total: chunkRows.length,
3764
- startedAt: mapStartedAt,
3765
- message: formatMapProgressMessage(
3766
- estimatedCompleted,
3767
- chunkRows.length,
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
- const workerResults = await Promise.allSettled(workers);
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
- flushLedgerEvents(Boolean(input.forceFlush));
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),