deepline 0.1.274 → 0.1.276

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.
@@ -5,6 +5,10 @@ import { pipeline } from 'node:stream/promises';
5
5
  import type { PlaySheetContract } from '@shared_libs/plays/static-pipeline';
6
6
  import type { PlayRowUpdate } from '@shared_libs/play-runtime/ctx-types';
7
7
  import type { PlayRunTimelineEntry } from '@shared_libs/play-runtime/live-events';
8
+ import {
9
+ routePlayActivityObservation,
10
+ type PlayActivityObservation,
11
+ } from '@shared_libs/play-runtime/activity-observation';
8
12
  import {
9
13
  buildPlayRunLedgerEventsFromStatusPatch,
10
14
  buildTerminalLogReplayEvents,
@@ -82,6 +86,8 @@ export type RuntimeStatusUpdate = {
82
86
  complete?: boolean;
83
87
  at: number;
84
88
  }>;
89
+ /** Sparse typed activity facts. Routing/deduplication happens server-side. */
90
+ activityObservations?: PlayActivityObservation[];
85
91
  /**
86
92
  * Explicit terminal-output replay for final runner logs when the caller must
87
93
  * keep `status` nonterminal. Direct-worker/Postgres progress finalizers do this so
@@ -1491,6 +1497,25 @@ async function updateRunStatusViaAppRuntimeUnlocked(
1491
1497
  ...(event.complete === undefined ? {} : { complete: event.complete }),
1492
1498
  });
1493
1499
  }
1500
+ const latestActivities = new Map(
1501
+ Object.entries(previousSnapshot.activitiesById),
1502
+ );
1503
+ for (const observation of update.activityObservations ?? []) {
1504
+ const previous = latestActivities.get(observation.activityId) ?? null;
1505
+ const lane = routePlayActivityObservation({
1506
+ previous,
1507
+ next: observation,
1508
+ });
1509
+ if (lane === 'drop' || lane === 'liveness') continue;
1510
+ events.push({
1511
+ type: 'activity.observed',
1512
+ runId: update.playId,
1513
+ source: 'worker',
1514
+ occurredAt: observation.observedAt,
1515
+ observation,
1516
+ });
1517
+ latestActivities.set(observation.activityId, observation);
1518
+ }
1494
1519
  const terminalLogEvents =
1495
1520
  terminalLogReplay && terminalLogReplay.lines.length > 0
1496
1521
  ? buildTerminalLogReplayEvents({
@@ -8405,6 +8405,10 @@ export class PlayContextImpl {
8405
8405
  }
8406
8406
  const url = `${this.#options.baseUrl}/api/v2/integrations/${encodeURIComponent(toolId)}/execute`;
8407
8407
  const timeoutMs = resolveToolRuntimeTimeoutMs(toolId, options?.timeoutMs);
8408
+ const provider = toolId.split(/[._]/)[0]?.trim() || 'provider';
8409
+ const activityId = `provider:${toolId}`;
8410
+ let retryActivityEmitted = false;
8411
+ let toolCallSucceeded = false;
8408
8412
 
8409
8413
  // Hold one logical tool slot and charge one logical tool call for this
8410
8414
  // execution. Per-provider rate admission is intentionally deferred until
@@ -8757,8 +8761,46 @@ export class PlayContextImpl {
8757
8761
  this.log(
8758
8762
  `Tool ${toolId} returned ${response.status}; retrying after ${failure.retryDelayMs}ms`,
8759
8763
  );
8764
+ retryActivityEmitted = true;
8765
+ this.emitExecutionEvent({
8766
+ type: 'activity.observed',
8767
+ observation: {
8768
+ schemaVersion: 1,
8769
+ activityId,
8770
+ target: {
8771
+ kind: 'provider',
8772
+ provider,
8773
+ operation: toolId,
8774
+ label: toolId,
8775
+ },
8776
+ state: {
8777
+ kind: 'retrying',
8778
+ reason: failure.isRateLimit
8779
+ ? 'rate_limit'
8780
+ : 'provider_error',
8781
+ retryAt: Date.now() + failure.retryDelayMs,
8782
+ attempt: httpFailureAttempt,
8783
+ },
8784
+ observedAt: Date.now(),
8785
+ },
8786
+ });
8760
8787
  await options?.parkProviderCall?.();
8761
8788
  await this.sleepWithCheckpointHeartbeat(failure.retryDelayMs);
8789
+ this.emitExecutionEvent({
8790
+ type: 'activity.observed',
8791
+ observation: {
8792
+ schemaVersion: 1,
8793
+ activityId,
8794
+ target: {
8795
+ kind: 'provider',
8796
+ provider,
8797
+ operation: toolId,
8798
+ label: toolId,
8799
+ },
8800
+ state: { kind: 'active' },
8801
+ observedAt: Date.now(),
8802
+ },
8803
+ });
8762
8804
  continue;
8763
8805
  }
8764
8806
  this.log(failure.error.message);
@@ -8775,11 +8817,31 @@ export class PlayContextImpl {
8775
8817
  ? 'array'
8776
8818
  : typeof parsed.result,
8777
8819
  });
8820
+ toolCallSucceeded = true;
8778
8821
  return parsed;
8779
8822
  }
8780
8823
  },
8781
8824
  );
8782
8825
  } finally {
8826
+ if (retryActivityEmitted) {
8827
+ this.emitExecutionEvent({
8828
+ type: 'activity.observed',
8829
+ observation: {
8830
+ schemaVersion: 1,
8831
+ activityId,
8832
+ target: {
8833
+ kind: 'provider',
8834
+ provider,
8835
+ operation: toolId,
8836
+ label: toolId,
8837
+ },
8838
+ state: toolCallSucceeded
8839
+ ? { kind: 'completed' }
8840
+ : { kind: 'failed', code: 'PROVIDER_CALL_FAILED' },
8841
+ observedAt: Date.now(),
8842
+ },
8843
+ });
8844
+ }
8783
8845
  toolSlot.release();
8784
8846
  }
8785
8847
  }
@@ -61,6 +61,7 @@ export interface PlayRowUpdate {
61
61
  }
62
62
 
63
63
  export type PlayExecutionEvent =
64
+ | PlayActivityEvent
64
65
  | {
65
66
  type: 'map.preparing';
66
67
  mapInvocationId: string;
@@ -264,3 +265,4 @@ export type PlayDatasetSubstep =
264
265
  results: PlayStepRowResult[];
265
266
  description?: string;
266
267
  };
268
+ import type { PlayActivityEvent } from './activity-observation';
@@ -25,6 +25,7 @@ import type { MapRowOutcome } from './durability-store';
25
25
  import type { WorkReceiptFailureKind } from './work-receipts';
26
26
  import type { RunExecutionScope } from './run-execution-scope';
27
27
  import type { PlayCallExecution } from './play-call-execution';
28
+ import type { PlayActivityEvent } from './activity-observation';
28
29
 
29
30
  export interface RowState {
30
31
  results: Map<string, unknown>;
@@ -385,6 +386,7 @@ export interface MapExecutionScope {
385
386
  }
386
387
 
387
388
  export type PlayExecutionEvent =
389
+ | PlayActivityEvent
388
390
  | {
389
391
  type: 'dataset.lifecycle';
390
392
  datasetId: string;
@@ -1,4 +1,8 @@
1
1
  import type { PlayRunLifecycleStatus } from './run-lifecycle-policy';
2
+ import type {
3
+ PlayActivityObservation,
4
+ PlayRunActivityProjection,
5
+ } from './activity-observation';
2
6
 
3
7
  export type PlayRunLiveStatus = PlayRunLifecycleStatus;
4
8
 
@@ -46,4 +50,7 @@ export type PlayRunLiveSnapshot = {
46
50
  resultTableNamespace: string | null;
47
51
  nodeStates: PlayVisualNodeStateSnapshot[];
48
52
  activeNodeId: string | null;
53
+ activities?: PlayActivityObservation[];
54
+ activeActivity?: PlayActivityObservation | null;
55
+ activity?: PlayRunActivityProjection | null;
49
56
  };
@@ -8,6 +8,10 @@ import {
8
8
  normalizePlayRunLedgerTerminalSource,
9
9
  } from './run-terminal-source';
10
10
  import { MAX_LEDGER_LOG_LINES_PER_EVENT } from './ledger-safe-payload';
11
+ import {
12
+ isPlayActivityObservation,
13
+ type PlayActivityObservation,
14
+ } from './activity-observation';
11
15
 
12
16
  export type PlayRunLedgerStatus =
13
17
  | 'queued'
@@ -111,6 +115,8 @@ export type PlayRunLedgerSnapshot = {
111
115
  stepsById: Record<string, PlayRunLedgerStepSnapshot>;
112
116
  orderedDatasetIds: string[];
113
117
  datasetsById: Record<string, PlayRunLedgerDatasetSnapshot>;
118
+ orderedActivityIds: string[];
119
+ activitiesById: Record<string, PlayActivityObservation>;
114
120
  /**
115
121
  * Bounded tail of the run's log lines (last {@link LOG_TAIL_LIMIT}).
116
122
  * Full retention lives in the Run Log Stream (Convex `playRunLogChunks`);
@@ -129,6 +135,7 @@ export type PlayRunLedgerSnapshot = {
129
135
  */
130
136
  logsTruncated: boolean;
131
137
  activeStepId?: string | null;
138
+ activeActivityId?: string | null;
132
139
  activeArtifactTableNamespace?: string | null;
133
140
  resultTableNamespace?: string | null;
134
141
  resultSummary?: unknown;
@@ -260,6 +267,10 @@ export type PlayRunLedgerEvent =
260
267
  failedRows?: number;
261
268
  complete?: boolean;
262
269
  })
270
+ | (PlayRunLedgerBaseEvent & {
271
+ type: 'activity.observed';
272
+ observation: PlayActivityObservation;
273
+ })
263
274
  | (PlayRunLedgerBaseEvent & {
264
275
  /**
265
276
  * @deprecated Mixed-version deploy seam only. Owner: Play Runtime.
@@ -362,6 +373,15 @@ export function isTerminalPlayRunLedgerStatus(
362
373
  return TERMINAL_STATUSES.has(status);
363
374
  }
364
375
 
376
+ function isTerminalActivityObservation(
377
+ observation: PlayActivityObservation,
378
+ ): boolean {
379
+ return (
380
+ observation.state.kind === 'completed' ||
381
+ observation.state.kind === 'failed'
382
+ );
383
+ }
384
+
365
385
  /**
366
386
  * Aggregate, row-level outcome of a run derived purely from the settled step
367
387
  * progress. This is an ADDITIVE truthfulness signal, NOT a status: a run can be
@@ -449,10 +469,13 @@ export function createEmptyPlayRunLedgerSnapshot(input: {
449
469
  stepsById: {},
450
470
  orderedDatasetIds: [],
451
471
  datasetsById: {},
472
+ orderedActivityIds: [],
473
+ activitiesById: {},
452
474
  logTail: [],
453
475
  totalLogCount: 0,
454
476
  logsTruncated: false,
455
477
  activeStepId: null,
478
+ activeActivityId: null,
456
479
  activeArtifactTableNamespace: null,
457
480
  resultTableNamespace: null,
458
481
  };
@@ -516,6 +539,19 @@ export function normalizePlayRunLedgerSnapshot(
516
539
  updatedAt: finiteNumber(rawDataset.updatedAt) ?? 0,
517
540
  };
518
541
  }
542
+ const rawActivities = isRecord(value.activitiesById)
543
+ ? value.activitiesById
544
+ : {};
545
+ const activitiesById: Record<string, PlayActivityObservation> = {};
546
+ for (const [activityId, rawActivity] of Object.entries(rawActivities)) {
547
+ if (
548
+ activityId.trim() &&
549
+ isPlayActivityObservation(rawActivity) &&
550
+ rawActivity.activityId === activityId
551
+ ) {
552
+ activitiesById[activityId] = rawActivity;
553
+ }
554
+ }
519
555
  const createdAt = finiteNumber(value.createdAt) ?? fallback.createdAt ?? null;
520
556
  const startedAt = finiteNumber(value.startedAt) ?? fallback.startedAt ?? null;
521
557
  const finishedAt =
@@ -561,6 +597,14 @@ export function normalizePlayRunLedgerSnapshot(
561
597
  typeof datasetId === 'string' && Boolean(datasetsById[datasetId]),
562
598
  ),
563
599
  datasetsById,
600
+ orderedActivityIds: (Array.isArray(value.orderedActivityIds)
601
+ ? value.orderedActivityIds
602
+ : Object.keys(activitiesById)
603
+ ).filter(
604
+ (activityId): activityId is string =>
605
+ typeof activityId === 'string' && Boolean(activitiesById[activityId]),
606
+ ),
607
+ activitiesById,
564
608
  logTail: logTail.slice(-LOG_TAIL_LIMIT),
565
609
  // Snapshots persisted before totalLogCount existed only know the retained
566
610
  // tail, so the best lower bound for the cumulative count is the tail size.
@@ -570,6 +614,7 @@ export function normalizePlayRunLedgerSnapshot(
570
614
  ),
571
615
  logsTruncated: value.logsTruncated === true,
572
616
  activeStepId: optionalNullableString(value.activeStepId),
617
+ activeActivityId: optionalNullableString(value.activeActivityId),
573
618
  activeArtifactTableNamespace: optionalNullableString(
574
619
  value.activeArtifactTableNamespace,
575
620
  ),
@@ -1177,6 +1222,7 @@ export function reducePlayRunLedgerEvent(
1177
1222
  startedAt: base.startedAt ?? occurredAt,
1178
1223
  finishedAt: terminalFinishedAt(base, event.type, occurredAt),
1179
1224
  activeStepId: null,
1225
+ activeActivityId: null,
1180
1226
  activeArtifactTableNamespace: null,
1181
1227
  result: event.result ?? base.result,
1182
1228
  resultSummary: event.resultSummary ?? base.resultSummary,
@@ -1202,6 +1248,7 @@ export function reducePlayRunLedgerEvent(
1202
1248
  startedAt: base.startedAt ?? occurredAt,
1203
1249
  finishedAt: terminalFinishedAt(base, event.type, occurredAt),
1204
1250
  activeStepId: null,
1251
+ activeActivityId: null,
1205
1252
  activeArtifactTableNamespace: null,
1206
1253
  result: event.result ?? base.result,
1207
1254
  terminalSource: nextTerminalSource(base, event.source),
@@ -1225,6 +1272,7 @@ export function reducePlayRunLedgerEvent(
1225
1272
  startedAt: base.startedAt ?? occurredAt,
1226
1273
  finishedAt: terminalFinishedAt(base, event.type, occurredAt),
1227
1274
  activeStepId: null,
1275
+ activeActivityId: null,
1228
1276
  activeArtifactTableNamespace: null,
1229
1277
  result: event.result ?? base.result,
1230
1278
  terminalSource: nextTerminalSource(base, event.source),
@@ -1473,6 +1521,28 @@ export function reducePlayRunLedgerEvent(
1473
1521
  resultTableNamespace: base.resultTableNamespace ?? event.tableNamespace,
1474
1522
  });
1475
1523
  }
1524
+ case 'activity.observed': {
1525
+ const current = base.activitiesById[event.observation.activityId];
1526
+ if (current && current.observedAt >= event.observation.observedAt) {
1527
+ return withTiming(base);
1528
+ }
1529
+ const terminal = isTerminalActivityObservation(event.observation);
1530
+ return withTiming({
1531
+ ...base,
1532
+ orderedActivityIds: current
1533
+ ? base.orderedActivityIds
1534
+ : [...base.orderedActivityIds, event.observation.activityId],
1535
+ activitiesById: {
1536
+ ...base.activitiesById,
1537
+ [event.observation.activityId]: event.observation,
1538
+ },
1539
+ activeActivityId: terminal
1540
+ ? base.activeActivityId === event.observation.activityId
1541
+ ? null
1542
+ : base.activeActivityId
1543
+ : event.observation.activityId,
1544
+ });
1545
+ }
1476
1546
  case 'sheet.summary.updated':
1477
1547
  return withTiming({
1478
1548
  ...base,
@@ -14,6 +14,11 @@ import {
14
14
  normalizePlayRunLifecycleStatus,
15
15
  type PlayRunLifecycleStatus,
16
16
  } from './run-lifecycle-policy';
17
+ import {
18
+ projectPlayRunActivity,
19
+ type PlayActivityObservation,
20
+ type PlayRunActivityProjection,
21
+ } from './activity-observation';
17
22
 
18
23
  /**
19
24
  * Run Snapshot Stream.
@@ -79,6 +84,11 @@ export type PlayRunLiveSnapshot = {
79
84
  activeArtifactTableNamespace: string | null;
80
85
  resultTableNamespace: string | null;
81
86
  datasets?: PlayRunLedgerDatasetSnapshot[];
87
+ /** Explicit typed activity facts; legacy steps/datasets remain alongside them. */
88
+ activities?: PlayActivityObservation[];
89
+ activeActivity?: PlayActivityObservation | null;
90
+ /** Canonical current activity, including compatibility projection. */
91
+ activity?: PlayRunActivityProjection | null;
82
92
  nodeStates: PlayRunStreamNodeState[];
83
93
  activeNodeId: string | null;
84
94
  /**
@@ -121,6 +131,11 @@ export type LedgerBackedRunLike = {
121
131
  updatedAt?: number | null;
122
132
  runSnapshot?: unknown;
123
133
  result?: unknown;
134
+ wait?: { kind?: string; eventKey?: string; boundaryId?: string } | null;
135
+ waitKind?: string | null;
136
+ waitUntil?: number | null;
137
+ activeBoundaryId?: string | null;
138
+ runtimeBackend?: string | null;
124
139
  };
125
140
 
126
141
  function isRecord(value: unknown): value is Record<string, unknown> {
@@ -221,6 +236,14 @@ function buildSnapshotFromLedger(
221
236
  .filter((dataset): dataset is PlayRunLedgerDatasetSnapshot =>
222
237
  Boolean(dataset),
223
238
  ),
239
+ activities: snapshot.orderedActivityIds
240
+ .map((activityId) => snapshot.activitiesById[activityId])
241
+ .filter((activity): activity is PlayActivityObservation =>
242
+ Boolean(activity),
243
+ ),
244
+ activeActivity: snapshot.activeActivityId
245
+ ? (snapshot.activitiesById[snapshot.activeActivityId] ?? null)
246
+ : null,
224
247
  nodeStates,
225
248
  activeNodeId: snapshot.activeStepId ?? null,
226
249
  ...(rowOutcomes ? { rowOutcomes } : {}),
@@ -240,17 +263,33 @@ export function buildPlayRunStatusSnapshot(input: {
240
263
  finishedAt: input.run.finishedAt ?? null,
241
264
  });
242
265
  const terminalRunLogTail = extractTerminalRunLogTail(input.run.result);
243
- if (
266
+ const projectedSnapshot =
244
267
  terminalRunLogTail &&
245
268
  terminalRunLogTail.totalLogCount > ledgerSnapshot.totalLogCount
246
- ) {
247
- return buildSnapshotFromLedger({
248
- ...ledgerSnapshot,
249
- logTail: terminalRunLogTail.logTail,
250
- totalLogCount: terminalRunLogTail.totalLogCount,
251
- });
252
- }
253
- return buildSnapshotFromLedger(ledgerSnapshot);
269
+ ? buildSnapshotFromLedger({
270
+ ...ledgerSnapshot,
271
+ logTail: terminalRunLogTail.logTail,
272
+ totalLogCount: terminalRunLogTail.totalLogCount,
273
+ })
274
+ : buildSnapshotFromLedger(ledgerSnapshot);
275
+ const activity = projectPlayRunActivity({
276
+ runId: input.run.workflowId,
277
+ playName: input.run.name,
278
+ status: projectedSnapshot.status,
279
+ updatedAt: projectedSnapshot.updatedAt,
280
+ waitKind: input.run.waitKind ?? input.run.wait?.kind ?? null,
281
+ waitUntil: input.run.waitUntil,
282
+ eventKey: input.run.wait?.eventKey ?? null,
283
+ runtimeBackend: input.run.runtimeBackend,
284
+ activeNodeId: input.run.activeBoundaryId ?? projectedSnapshot.activeNodeId,
285
+ nodeStates: projectedSnapshot.nodeStates,
286
+ datasets: projectedSnapshot.datasets,
287
+ explicit: projectedSnapshot.activities,
288
+ });
289
+ return {
290
+ ...projectedSnapshot,
291
+ activity,
292
+ };
254
293
  }
255
294
 
256
295
  /** Generic live-event envelope shape shared with the SSE protocol. */
@@ -635,6 +674,7 @@ export function diffPlayRunStreamEvents(input: {
635
674
  runId: snapshot.runId,
636
675
  status: snapshot.status,
637
676
  updatedAt: snapshot.updatedAt,
677
+ ...(snapshot.activity ? { activity: snapshot.activity } : {}),
638
678
  },
639
679
  }),
640
680
  );