deepline 0.3.228 → 0.3.230

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  2. package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +18 -0
  3. package/dist/bundling-sources/shared_libs/play-data-plane/contracts.ts +12 -0
  4. package/dist/bundling-sources/shared_libs/play-data-plane/index.ts +1 -0
  5. package/dist/bundling-sources/shared_libs/play-data-plane/sheet-column-storage.ts +252 -0
  6. package/dist/bundling-sources/shared_libs/play-data-plane/sheet-contract.ts +29 -8
  7. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +19 -0
  8. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +297 -69
  9. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +5 -0
  10. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +38 -2
  11. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/gateway-adapter.ts +8 -3
  12. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +9 -0
  13. package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +39 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +293 -4
  15. package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +29 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +56 -11
  17. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/progress-sidecar.ts +1 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +4 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +4 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +380 -183
  21. package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +187 -14
  22. package/dist/bundling-sources/shared_libs/play-runtime/runtime-work-counters.ts +52 -2
  23. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +5 -0
  24. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +30 -2
  25. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +118 -6
  26. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +135 -21
  27. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +1 -0
  28. package/dist/bundling-sources/shared_libs/plays/dataset.ts +16 -2
  29. package/dist/bundling-sources/shared_libs/plays/execution-pipeline.ts +72 -0
  30. package/dist/bundling-sources/shared_libs/plays/launch/prepare.ts +1 -3
  31. package/dist/bundling-sources/shared_libs/plays/sheet-filter.ts +9 -13
  32. package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +28 -31
  33. package/dist/cli/index.js +102 -2
  34. package/dist/cli/index.mjs +102 -2
  35. package/dist/{compiler-manifest-Dpd2hEet.d.mts → compiler-manifest-n8ZtPv2h.d.mts} +31 -21
  36. package/dist/{compiler-manifest-Dpd2hEet.d.ts → compiler-manifest-n8ZtPv2h.d.ts} +31 -21
  37. package/dist/index.d.mts +2 -2
  38. package/dist/index.d.ts +2 -2
  39. package/dist/index.js +98 -2
  40. package/dist/index.mjs +98 -2
  41. package/dist/install-integrity.json +4 -2
  42. package/dist/plays/bundle-play-file.d.mts +2 -2
  43. package/dist/plays/bundle-play-file.d.ts +2 -2
  44. package/dist/release.d.mts +1 -1
  45. package/dist/release.d.ts +1 -1
  46. package/dist/release.js +1 -1
  47. package/dist/release.mjs +1 -1
  48. package/package.json +1 -1
@@ -12,6 +12,10 @@ import type { RunLedgerProjectionPorts } from '../../play-runs/projection/ports'
12
12
 
13
13
  export type GatewayRunLedgerProjectionAdapterInput = {
14
14
  runId: string;
15
+ /**
16
+ * Launch-pinned runtime context. Gateway authority refreshes its credentials
17
+ * against the active outbox lease while retaining supported transport hooks.
18
+ */
15
19
  runtimeApi: WorkerRuntimeApiContext;
16
20
  withClient: <T>(
17
21
  operation: (client: PostgresSchedulerQueryClient) => Promise<T>,
@@ -20,9 +24,10 @@ export type GatewayRunLedgerProjectionAdapterInput = {
20
24
  };
21
25
 
22
26
  /**
23
- * Gateway Adapter for the durable Run Ledger outbox. It retains the worker's
24
- * no-signing-key rule and turns both normal and targeted delivery into the
25
- * one `RunLedgerProjection.deliver` port.
27
+ * Gateway adapter for the durable Run Ledger outbox. It retains the worker's
28
+ * no-signing-key rule, refreshes credentials for each claimed batch, and turns
29
+ * normal and targeted delivery into the one `RunLedgerProjection.deliver`
30
+ * port.
26
31
  */
27
32
  export function createGatewayRunLedgerProjectionAdapter(
28
33
  input: GatewayRunLedgerProjectionAdapterInput,
@@ -317,6 +317,15 @@ export type PlayRunnerLogEvent = {
317
317
 
318
318
  export type PlayRunnerEvent =
319
319
  | PlayRunnerLogEvent
320
+ | {
321
+ type: 'work_counters';
322
+ attempt: number;
323
+ invocationSeq: number;
324
+ segmentId: string;
325
+ logicalCalls: number;
326
+ providerRequests: number;
327
+ provider429s: number;
328
+ }
320
329
  | {
321
330
  type: 'checkpoint';
322
331
  checkpoint: PlayCheckpoint;
@@ -734,6 +734,12 @@ export interface RuntimeResourceGovernor {
734
734
  inFlightAtDispatch: number;
735
735
  release(): void;
736
736
  };
737
+ /** Count one actual tool HTTP dispatch before transport can fail ambiguously. */
738
+ recordProviderDispatch(): void;
739
+ /** Count a provider HTTP 429 as soon as response headers arrive. */
740
+ recordProvider429(): void;
741
+ /** Count one admitted ctx.tools.execute invocation, including a later failure. */
742
+ recordLogicalToolCall(): void;
737
743
  /**
738
744
  * Probe app transport capacity at the physical provider-dispatch edge. A
739
745
  * caller that misses it must cancel and re-admit its provider ticket.
@@ -757,6 +763,12 @@ export interface RuntimeResourceGovernor {
757
763
  }): Promise<void>;
758
764
  observe(input: RuntimeResourceObservation): void;
759
765
  snapshot(): RuntimeResourceSnapshot;
766
+ /** O(1) counters for sampled live status; no provider-map scan. */
767
+ workCounters(): {
768
+ logicalCalls: number;
769
+ providerRequests: number;
770
+ provider429s: number;
771
+ };
760
772
  /** Stop the event-loop lag sampler. The timer is unref'd either way. */
761
773
  stopSampling(): void;
762
774
  /** Smoothed event-loop lag from the sampler, or null before its first tick. */
@@ -1047,6 +1059,13 @@ export function createRuntimeResourceGovernor(input: {
1047
1059
  const toolCounters = {
1048
1060
  acquired: 0,
1049
1061
  };
1062
+ // Dispatch is counted at the fetch edge. A failed transport is still a
1063
+ // physical request attempt; response-residence observations omit it.
1064
+ const workCounters = {
1065
+ logicalCalls: 0,
1066
+ providerRequests: 0,
1067
+ provider429s: 0,
1068
+ };
1050
1069
  const sheetFlushCounters = {
1051
1070
  acquired: 0,
1052
1071
  bytes: 0,
@@ -1642,6 +1661,18 @@ export function createRuntimeResourceGovernor(input: {
1642
1661
  };
1643
1662
  },
1644
1663
 
1664
+ recordProviderDispatch() {
1665
+ workCounters.providerRequests += 1;
1666
+ },
1667
+
1668
+ recordProvider429() {
1669
+ workCounters.provider429s += 1;
1670
+ },
1671
+
1672
+ recordLogicalToolCall() {
1673
+ workCounters.logicalCalls += 1;
1674
+ },
1675
+
1645
1676
  tryAcquireIntegrationRequestSlot() {
1646
1677
  return input.executionGovernor.tryAcquireIntegrationRequestSlot();
1647
1678
  },
@@ -1672,6 +1703,14 @@ export function createRuntimeResourceGovernor(input: {
1672
1703
 
1673
1704
  observe,
1674
1705
 
1706
+ workCounters() {
1707
+ return {
1708
+ logicalCalls: workCounters.logicalCalls,
1709
+ providerRequests: workCounters.providerRequests,
1710
+ provider429s: workCounters.provider429s,
1711
+ };
1712
+ },
1713
+
1675
1714
  stopSampling: stopEventLoopLagSampler,
1676
1715
  eventLoopLagEwmaMs: () => phases.eventLoopLag.snapshot().ewmaMs,
1677
1716
 
@@ -122,6 +122,28 @@ export type PlayRunLedgerDatasetSnapshot = {
122
122
  updatedAt: number;
123
123
  };
124
124
 
125
+ /** Fixed-size, attempt-fenced call evidence. Counts are per scheduler attempt. */
126
+ export type PlayRunLedgerWorkCounters = {
127
+ attempt: number;
128
+ invocationSeq: number;
129
+ segmentId: string;
130
+ logicalCalls: number;
131
+ providerRequests: number;
132
+ provider429s: number;
133
+ final: boolean;
134
+ completedLogicalCalls: number;
135
+ completedProviderRequests: number;
136
+ completedProvider429s: number;
137
+ completeThroughAttempt: boolean;
138
+ consistent: boolean;
139
+ };
140
+
141
+ /** Runner invocation which actually produced the accepted terminal result. */
142
+ export type PlayRunLedgerTerminalWorkCounterSegment = Pick<
143
+ PlayRunLedgerWorkCounters,
144
+ 'attempt' | 'invocationSeq' | 'segmentId'
145
+ >;
146
+
125
147
  export type PlayRunLedgerSnapshot = {
126
148
  runId: string;
127
149
  playName?: string | null;
@@ -141,6 +163,10 @@ export type PlayRunLedgerSnapshot = {
141
163
  datasetsById: Record<string, PlayRunLedgerDatasetSnapshot>;
142
164
  orderedActivityIds: string[];
143
165
  activitiesById: Record<string, PlayActivityObservation>;
166
+ /** Absent for runs started before call accounting was enabled. */
167
+ workCounters?: PlayRunLedgerWorkCounters;
168
+ /** Absent on terminals produced before runner invocation evidence existed. */
169
+ terminalWorkCounterSegment?: PlayRunLedgerTerminalWorkCounterSegment;
144
170
  /**
145
171
  * Bounded tail of the run's log lines (last {@link LOG_TAIL_LIMIT}).
146
172
  * Full retention lives in the Run Log Stream (Convex `playRunLogChunks`);
@@ -214,12 +240,14 @@ export type PlayRunLedgerEvent =
214
240
  type: 'run.completed';
215
241
  result?: unknown;
216
242
  resultSummary?: unknown;
243
+ terminalWorkCounterSegment?: PlayRunLedgerTerminalWorkCounterSegment;
217
244
  })
218
245
  | (PlayRunLedgerBaseEvent & {
219
246
  type: 'run.failed';
220
247
  error?: string | null;
221
248
  result?: unknown;
222
249
  resultSummary?: unknown;
250
+ terminalWorkCounterSegment?: PlayRunLedgerTerminalWorkCounterSegment;
223
251
  })
224
252
  | (PlayRunLedgerBaseEvent & {
225
253
  type: 'run.cancelled';
@@ -305,6 +333,16 @@ export type PlayRunLedgerEvent =
305
333
  | (PlayRunLedgerBaseEvent & {
306
334
  type: 'activity.observed';
307
335
  observation: PlayActivityObservation;
336
+ })
337
+ | (PlayRunLedgerBaseEvent & {
338
+ type: 'work.counters';
339
+ attempt: number;
340
+ invocationSeq: number;
341
+ segmentId: string;
342
+ logicalCalls: number;
343
+ providerRequests: number;
344
+ provider429s: number;
345
+ final: boolean;
308
346
  });
309
347
 
310
348
  /**
@@ -391,6 +429,219 @@ function finiteNumber(value: unknown): number | null {
391
429
  return typeof value === 'number' && Number.isFinite(value) ? value : null;
392
430
  }
393
431
 
432
+ function nonnegativeSafeInteger(value: unknown): value is number {
433
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
434
+ }
435
+
436
+ function isWorkCounterSegmentId(value: unknown): value is string {
437
+ return (
438
+ typeof value === 'string' &&
439
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
440
+ value,
441
+ )
442
+ );
443
+ }
444
+
445
+ export function normalizePlayRunLedgerTerminalWorkCounterSegment(
446
+ value: unknown,
447
+ ): PlayRunLedgerTerminalWorkCounterSegment | undefined {
448
+ if (!isRecord(value)) return undefined;
449
+ if (
450
+ !nonnegativeSafeInteger(value.attempt) ||
451
+ !nonnegativeSafeInteger(value.invocationSeq) ||
452
+ value.invocationSeq === 0 ||
453
+ !isWorkCounterSegmentId(value.segmentId)
454
+ ) {
455
+ return undefined;
456
+ }
457
+ return {
458
+ attempt: value.attempt,
459
+ invocationSeq: value.invocationSeq,
460
+ segmentId: value.segmentId,
461
+ };
462
+ }
463
+
464
+ /** Final counts are exact only if the accepted terminal names the final report. */
465
+ export function hasConfirmedPlayRunLedgerWorkCounters(
466
+ snapshot: PlayRunLedgerSnapshot,
467
+ ): boolean {
468
+ const counters = snapshot.workCounters;
469
+ const terminal = snapshot.terminalWorkCounterSegment;
470
+ return Boolean(
471
+ isTerminalPlayRunLedgerStatus(snapshot.status) &&
472
+ (snapshot.terminalSource === 'worker' ||
473
+ snapshot.terminalSource === 'system') &&
474
+ counters?.final &&
475
+ counters.completeThroughAttempt &&
476
+ counters.consistent &&
477
+ terminal &&
478
+ counters.attempt === terminal.attempt &&
479
+ counters.invocationSeq === terminal.invocationSeq &&
480
+ counters.segmentId === terminal.segmentId &&
481
+ Number.isSafeInteger(
482
+ counters.completedLogicalCalls + counters.logicalCalls,
483
+ ) &&
484
+ Number.isSafeInteger(
485
+ counters.completedProviderRequests + counters.providerRequests,
486
+ ) &&
487
+ Number.isSafeInteger(
488
+ counters.completedProvider429s + counters.provider429s,
489
+ ),
490
+ );
491
+ }
492
+
493
+ function normalizeWorkCounters(
494
+ value: unknown,
495
+ ): PlayRunLedgerWorkCounters | undefined {
496
+ if (!isRecord(value)) return undefined;
497
+ for (const field of [
498
+ 'attempt',
499
+ 'invocationSeq',
500
+ 'logicalCalls',
501
+ 'providerRequests',
502
+ 'provider429s',
503
+ 'completedLogicalCalls',
504
+ 'completedProviderRequests',
505
+ 'completedProvider429s',
506
+ ] as const) {
507
+ if (!nonnegativeSafeInteger(value[field])) return undefined;
508
+ }
509
+ if (
510
+ value.invocationSeq === 0 ||
511
+ !isWorkCounterSegmentId(value.segmentId) ||
512
+ typeof value.final !== 'boolean' ||
513
+ typeof value.completeThroughAttempt !== 'boolean' ||
514
+ typeof value.consistent !== 'boolean'
515
+ ) {
516
+ return undefined;
517
+ }
518
+ return {
519
+ ...value,
520
+ attempt: value.attempt as number,
521
+ invocationSeq: value.invocationSeq as number,
522
+ segmentId: value.segmentId,
523
+ logicalCalls: value.logicalCalls as number,
524
+ providerRequests: value.providerRequests as number,
525
+ provider429s: value.provider429s as number,
526
+ final: value.final,
527
+ completedLogicalCalls: value.completedLogicalCalls as number,
528
+ completedProviderRequests: value.completedProviderRequests as number,
529
+ completedProvider429s: value.completedProvider429s as number,
530
+ completeThroughAttempt: value.completeThroughAttempt,
531
+ consistent: value.consistent,
532
+ };
533
+ }
534
+
535
+ function advanceNextWorkCounterSegment(
536
+ current: PlayRunLedgerWorkCounters,
537
+ event: Extract<PlayRunLedgerEvent, { type: 'work.counters' }>,
538
+ ): PlayRunLedgerWorkCounters {
539
+ const completedLogicalCalls =
540
+ current.completedLogicalCalls + current.logicalCalls;
541
+ const completedProviderRequests =
542
+ current.completedProviderRequests + current.providerRequests;
543
+ const completedProvider429s =
544
+ current.completedProvider429s + current.provider429s;
545
+ const safe = [
546
+ completedLogicalCalls,
547
+ completedProviderRequests,
548
+ completedProvider429s,
549
+ ].every(Number.isSafeInteger);
550
+ const contiguous =
551
+ event.attempt === current.attempt
552
+ ? event.invocationSeq === current.invocationSeq + 1
553
+ : event.attempt === current.attempt + 1 && event.invocationSeq === 1;
554
+ return {
555
+ attempt: event.attempt,
556
+ invocationSeq: event.invocationSeq,
557
+ segmentId: event.segmentId,
558
+ logicalCalls: event.logicalCalls,
559
+ providerRequests: event.providerRequests,
560
+ provider429s: event.provider429s,
561
+ final: event.final === true,
562
+ completedLogicalCalls: Math.min(
563
+ completedLogicalCalls,
564
+ Number.MAX_SAFE_INTEGER,
565
+ ),
566
+ completedProviderRequests: Math.min(
567
+ completedProviderRequests,
568
+ Number.MAX_SAFE_INTEGER,
569
+ ),
570
+ completedProvider429s: Math.min(
571
+ completedProvider429s,
572
+ Number.MAX_SAFE_INTEGER,
573
+ ),
574
+ completeThroughAttempt:
575
+ current.completeThroughAttempt && current.final && contiguous,
576
+ consistent:
577
+ current.consistent && safe && event.segmentId !== current.segmentId,
578
+ };
579
+ }
580
+
581
+ function advanceWorkCounters(
582
+ current: PlayRunLedgerWorkCounters | undefined,
583
+ event: Extract<PlayRunLedgerEvent, { type: 'work.counters' }>,
584
+ ): PlayRunLedgerWorkCounters | undefined {
585
+ if (
586
+ !nonnegativeSafeInteger(event.attempt) ||
587
+ !nonnegativeSafeInteger(event.invocationSeq) ||
588
+ event.invocationSeq === 0 ||
589
+ !isWorkCounterSegmentId(event.segmentId) ||
590
+ !nonnegativeSafeInteger(event.logicalCalls) ||
591
+ !nonnegativeSafeInteger(event.providerRequests) ||
592
+ !nonnegativeSafeInteger(event.provider429s)
593
+ ) {
594
+ return current;
595
+ }
596
+ if (!current) {
597
+ return {
598
+ attempt: event.attempt,
599
+ invocationSeq: event.invocationSeq,
600
+ segmentId: event.segmentId,
601
+ logicalCalls: event.logicalCalls,
602
+ providerRequests: event.providerRequests,
603
+ provider429s: event.provider429s,
604
+ final: event.final === true,
605
+ completedLogicalCalls: 0,
606
+ completedProviderRequests: 0,
607
+ completedProvider429s: 0,
608
+ completeThroughAttempt: event.attempt <= 1 && event.invocationSeq === 1,
609
+ consistent: true,
610
+ };
611
+ }
612
+ if (event.attempt < current.attempt) return current;
613
+ if (event.attempt === current.attempt) {
614
+ if (event.invocationSeq < current.invocationSeq) return current;
615
+ if (event.invocationSeq > current.invocationSeq)
616
+ return advanceNextWorkCounterSegment(current, event);
617
+ const counters = [
618
+ ['logicalCalls', event.logicalCalls],
619
+ ['providerRequests', event.providerRequests],
620
+ ['provider429s', event.provider429s],
621
+ ] as const;
622
+ const finalDisagrees = counters.some(
623
+ ([field, value]) =>
624
+ (event.final && value < current[field]) ||
625
+ (current.final && value > current[field]),
626
+ );
627
+ return {
628
+ ...current,
629
+ logicalCalls: Math.max(current.logicalCalls, event.logicalCalls),
630
+ providerRequests: Math.max(
631
+ current.providerRequests,
632
+ event.providerRequests,
633
+ ),
634
+ provider429s: Math.max(current.provider429s, event.provider429s),
635
+ final: current.final || event.final === true,
636
+ consistent:
637
+ current.consistent &&
638
+ current.segmentId === event.segmentId &&
639
+ !finalDisagrees,
640
+ };
641
+ }
642
+ return advanceNextWorkCounterSegment(current, event);
643
+ }
644
+
394
645
  function optionalFiniteNumber(value: unknown): number | undefined {
395
646
  const normalized = finiteNumber(value);
396
647
  return normalized === null ? undefined : normalized;
@@ -679,6 +930,11 @@ export function normalizePlayRunLedgerSnapshot(
679
930
  const logTail = Array.isArray(rawTail)
680
931
  ? rawTail.filter((line): line is string => typeof line === 'string')
681
932
  : [];
933
+ const workCounters = normalizeWorkCounters(value.workCounters);
934
+ const terminalWorkCounterSegment =
935
+ normalizePlayRunLedgerTerminalWorkCounterSegment(
936
+ value.terminalWorkCounterSegment,
937
+ );
682
938
  return {
683
939
  runId: optionalString(value.runId) ?? fallback.runId,
684
940
  playName:
@@ -713,6 +969,8 @@ export function normalizePlayRunLedgerSnapshot(
713
969
  typeof activityId === 'string' && Boolean(activitiesById[activityId]),
714
970
  ),
715
971
  activitiesById,
972
+ ...(workCounters ? { workCounters } : {}),
973
+ ...(terminalWorkCounterSegment ? { terminalWorkCounterSegment } : {}),
716
974
  logTail: logTail.slice(-LOG_TAIL_LIMIT),
717
975
  // Snapshots persisted before totalLogCount existed only know the retained
718
976
  // tail, so the best lower bound for the cumulative count is the tail size.
@@ -1283,6 +1541,11 @@ export function reducePlayRunLedgerEvent(
1283
1541
  shouldIgnoreNonTerminalAfterStickyTerminal(base);
1284
1542
 
1285
1543
  switch (event.type) {
1544
+ case 'work.counters': {
1545
+ const workCounters = advanceWorkCounters(base.workCounters, event);
1546
+ if (workCounters === base.workCounters) return snapshot;
1547
+ return withTiming({ ...base, workCounters });
1548
+ }
1286
1549
  case 'run.created': {
1287
1550
  if (ignoreNonTerminalAfterStickyTerminal) {
1288
1551
  return withTiming(base);
@@ -1333,14 +1596,24 @@ export function reducePlayRunLedgerEvent(
1333
1596
  waitUntil: null,
1334
1597
  activeActivityId: null,
1335
1598
  });
1336
- case 'run.completed':
1599
+ case 'run.completed': {
1600
+ const terminalWorkCounterSegment =
1601
+ (event.source === 'worker' || event.source === 'system') &&
1602
+ (!isTerminalPlayRunLedgerStatus(base.status) ||
1603
+ base.terminalSource === 'coordinator')
1604
+ ? normalizePlayRunLedgerTerminalWorkCounterSegment(
1605
+ event.terminalWorkCounterSegment,
1606
+ )
1607
+ : undefined;
1337
1608
  return (
1338
1609
  conflictingTerminalSnapshot(base, event.type, occurredAt, null, {
1339
1610
  allowStaleSameStatusPayloadMerge:
1340
1611
  (base.result === undefined && event.result !== undefined) ||
1341
1612
  canPromoteTerminalResultReference(base.result, event.result) ||
1342
1613
  (base.resultSummary === undefined &&
1343
- event.resultSummary !== undefined),
1614
+ event.resultSummary !== undefined) ||
1615
+ (base.terminalWorkCounterSegment === undefined &&
1616
+ terminalWorkCounterSegment !== undefined),
1344
1617
  }) ??
1345
1618
  withTiming({
1346
1619
  ...settleRunningStepsOnTerminal(base, 'completed', occurredAt),
@@ -1358,10 +1631,21 @@ export function reducePlayRunLedgerEvent(
1358
1631
  next: event.result,
1359
1632
  }),
1360
1633
  resultSummary: event.resultSummary ?? base.resultSummary,
1634
+ terminalWorkCounterSegment:
1635
+ base.terminalWorkCounterSegment ?? terminalWorkCounterSegment,
1361
1636
  terminalSource: nextTerminalSource(base, event.source),
1362
1637
  })
1363
1638
  );
1364
- case 'run.failed':
1639
+ }
1640
+ case 'run.failed': {
1641
+ const terminalWorkCounterSegment =
1642
+ (event.source === 'worker' || event.source === 'system') &&
1643
+ (!isTerminalPlayRunLedgerStatus(base.status) ||
1644
+ base.terminalSource === 'coordinator')
1645
+ ? normalizePlayRunLedgerTerminalWorkCounterSegment(
1646
+ event.terminalWorkCounterSegment,
1647
+ )
1648
+ : undefined;
1365
1649
  return (
1366
1650
  retryablePlatformDeployFailureSnapshot(base, event.error) ??
1367
1651
  conflictingTerminalSnapshot(base, event.type, occurredAt, event.error, {
@@ -1370,7 +1654,9 @@ export function reducePlayRunLedgerEvent(
1370
1654
  (base.result === undefined && event.result !== undefined) ||
1371
1655
  (base.resultSummary === undefined &&
1372
1656
  event.resultSummary !== undefined) ||
1373
- canPromoteTerminalResultReference(base.result, event.result),
1657
+ canPromoteTerminalResultReference(base.result, event.result) ||
1658
+ (base.terminalWorkCounterSegment === undefined &&
1659
+ terminalWorkCounterSegment !== undefined),
1374
1660
  }) ??
1375
1661
  withTiming({
1376
1662
  ...settleRunningStepsOnTerminal(base, 'failed', occurredAt),
@@ -1391,9 +1677,12 @@ export function reducePlayRunLedgerEvent(
1391
1677
  next: event.result,
1392
1678
  }),
1393
1679
  resultSummary: event.resultSummary ?? base.resultSummary,
1680
+ terminalWorkCounterSegment:
1681
+ base.terminalWorkCounterSegment ?? terminalWorkCounterSegment,
1394
1682
  terminalSource: nextTerminalSource(base, event.source),
1395
1683
  })
1396
1684
  );
1685
+ }
1397
1686
  case 'run.cancelled':
1398
1687
  return (
1399
1688
  conflictingTerminalSnapshot(base, event.type, occurredAt, event.error, {
@@ -1,6 +1,7 @@
1
1
  import { resolveTimingWindow } from './live-events';
2
2
  import {
3
3
  LOG_TAIL_LIMIT,
4
+ hasConfirmedPlayRunLedgerWorkCounters,
4
5
  normalizePlayRunLedgerSnapshot,
5
6
  summarizeRunRowOutcomes,
6
7
  type PlayRunLedgerSnapshot,
@@ -110,6 +111,13 @@ export type PlayRunLiveSnapshot = {
110
111
  * without row outcomes.
111
112
  */
112
113
  rowOutcomes?: PlayRunRowOutcomeSummary;
114
+ /** Observed run-wide call attempts. Absent for runs without counter evidence. */
115
+ calls?: {
116
+ logicalCalls: number;
117
+ providerRequests: number;
118
+ provider429s: number;
119
+ confirmed: boolean;
120
+ };
113
121
  };
114
122
 
115
123
  export function normalizePlayRunLiveStatus(value: unknown): PlayRunLiveStatus {
@@ -236,6 +244,21 @@ function buildSnapshotFromLedger(
236
244
  (terminalRowOutcomes.supersededRows ?? 0) > 0)
237
245
  ? terminalRowOutcomes
238
246
  : null;
247
+ const workCounters = snapshot.workCounters;
248
+ const calls = workCounters
249
+ ? {
250
+ logicalCalls:
251
+ workCounters.completedLogicalCalls + workCounters.logicalCalls,
252
+ providerRequests:
253
+ workCounters.completedProviderRequests +
254
+ workCounters.providerRequests,
255
+ provider429s:
256
+ workCounters.completedProvider429s + workCounters.provider429s,
257
+ confirmed:
258
+ isTerminalPlayRunLiveStatus(liveStatus) &&
259
+ hasConfirmedPlayRunLedgerWorkCounters(snapshot),
260
+ }
261
+ : null;
239
262
  return {
240
263
  runId: snapshot.runId,
241
264
  status: liveStatus,
@@ -268,6 +291,12 @@ function buildSnapshotFromLedger(
268
291
  nodeStates,
269
292
  activeNodeId: snapshot.activeStepId ?? null,
270
293
  ...(rowOutcomes ? { rowOutcomes } : {}),
294
+ ...(calls &&
295
+ Object.values(calls).every(
296
+ (value) => typeof value === 'boolean' || Number.isSafeInteger(value),
297
+ )
298
+ ? { calls }
299
+ : {}),
271
300
  };
272
301
  }
273
302