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.
- package/dist/bundling-sources/sdk/src/client.ts +12 -0
- package/dist/bundling-sources/sdk/src/index.ts +4 -0
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/sdk/src/types.ts +26 -0
- package/dist/bundling-sources/shared_libs/play-runtime/activity-observation.ts +588 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +25 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +62 -0
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-contract.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/live-state-contract.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +70 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +49 -9
- package/dist/cli/index.js +638 -185
- package/dist/cli/index.mjs +638 -185
- package/dist/index.d.mts +120 -1
- package/dist/index.d.ts +120 -1
- package/dist/index.js +367 -56
- package/dist/index.mjs +367 -56
- package/package.json +1 -1
|
@@ -71,6 +71,7 @@ import type {
|
|
|
71
71
|
PublishSharePageRequest,
|
|
72
72
|
UpdateSharePageRequest,
|
|
73
73
|
ToolDefinition,
|
|
74
|
+
ProviderDefinition,
|
|
74
75
|
ToolSearchOptions,
|
|
75
76
|
ToolSearchResult,
|
|
76
77
|
ToolMetadata,
|
|
@@ -1640,6 +1641,17 @@ export class DeeplineClient {
|
|
|
1640
1641
|
return res.tools;
|
|
1641
1642
|
}
|
|
1642
1643
|
|
|
1644
|
+
/** List discoverable providers without requiring a local plugin catalog. */
|
|
1645
|
+
async listProviders(options?: {
|
|
1646
|
+
changed?: boolean;
|
|
1647
|
+
}): Promise<ProviderDefinition[]> {
|
|
1648
|
+
const suffix = options?.changed ? '?changed=true' : '';
|
|
1649
|
+
const res = await this.http.get<{ providers: ProviderDefinition[] }>(
|
|
1650
|
+
`/api/v2/tools/providers${suffix}`,
|
|
1651
|
+
);
|
|
1652
|
+
return res.providers;
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1643
1655
|
/**
|
|
1644
1656
|
* Search available tools using Deepline's ranked backend search.
|
|
1645
1657
|
*
|
|
@@ -168,6 +168,10 @@ export type {
|
|
|
168
168
|
PlayRunActionPackage,
|
|
169
169
|
PlayRunDatasetActions,
|
|
170
170
|
PlayRunPackage,
|
|
171
|
+
PlayActivityObservation,
|
|
172
|
+
PlayActivityState,
|
|
173
|
+
PlayActivityTarget,
|
|
174
|
+
PlayRunActivityProjection,
|
|
171
175
|
PlayRunStart,
|
|
172
176
|
ClearPlayHistoryRequest,
|
|
173
177
|
ClearPlayHistoryResult,
|
|
@@ -155,7 +155,7 @@ export const SDK_RELEASE = {
|
|
|
155
155
|
// 0.1.253 makes play-page browser opening opt-in and retires --no-open.
|
|
156
156
|
// 0.1.254 removes the internal operations tree from the published SDK CLI.
|
|
157
157
|
// Operators use the checkout-local deepline-admin binary instead.
|
|
158
|
-
version: '0.1.
|
|
158
|
+
version: '0.1.276',
|
|
159
159
|
contracts: {
|
|
160
160
|
api: {
|
|
161
161
|
name: 'sdk-http-api',
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import type { PlayCompilerManifest } from '../../shared_libs/plays/compiler-manifest';
|
|
2
2
|
import type { PlayRuntimeSelection } from '../../shared_libs/play-runtime/runtime-environment';
|
|
3
|
+
import type {
|
|
4
|
+
PlayActivityObservation,
|
|
5
|
+
PlayActivityState,
|
|
6
|
+
PlayActivityTarget,
|
|
7
|
+
PlayRunActivityProjection,
|
|
8
|
+
} from '../../shared_libs/play-runtime/activity-observation';
|
|
9
|
+
export type {
|
|
10
|
+
PlayActivityObservation,
|
|
11
|
+
PlayActivityState,
|
|
12
|
+
PlayActivityTarget,
|
|
13
|
+
PlayRunActivityProjection,
|
|
14
|
+
};
|
|
3
15
|
import type {
|
|
4
16
|
DeeplineToolCategory,
|
|
5
17
|
PlayBootstrapFinderKind,
|
|
@@ -112,6 +124,18 @@ export interface ToolPricingSummary {
|
|
|
112
124
|
details: string[];
|
|
113
125
|
}
|
|
114
126
|
|
|
127
|
+
/** A discoverable provider, returned by {@link DeeplineClient.listProviders}. */
|
|
128
|
+
export interface ProviderDefinition {
|
|
129
|
+
provider: string;
|
|
130
|
+
displayName: string;
|
|
131
|
+
toolCount: number;
|
|
132
|
+
categories: string[];
|
|
133
|
+
/** Present for providers in the recent-additions ledger. */
|
|
134
|
+
version?: string;
|
|
135
|
+
/** ISO-8601 date on which this provider was added. */
|
|
136
|
+
releasedAt?: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
115
139
|
/**
|
|
116
140
|
* Summary definition of a tool, returned by {@link DeeplineClient.listTools}.
|
|
117
141
|
*
|
|
@@ -568,6 +592,8 @@ export interface PlayRunPackage {
|
|
|
568
592
|
finishedAt?: number | null;
|
|
569
593
|
durationMs?: number | null;
|
|
570
594
|
error?: string;
|
|
595
|
+
/** Canonical explanation of what this run is doing or waiting on. */
|
|
596
|
+
activity?: PlayRunActivityProjection | null;
|
|
571
597
|
};
|
|
572
598
|
/** Bounded customer-safe warnings about output projection or availability. */
|
|
573
599
|
warnings?: string[];
|
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical, producer-facing description of what a play is doing.
|
|
3
|
+
*
|
|
4
|
+
* Producers report facts. They do not choose log copy, UI placement, polling
|
|
5
|
+
* intervals, or whether a run is "healthy". The activity router and projector
|
|
6
|
+
* own those decisions centrally.
|
|
7
|
+
*/
|
|
8
|
+
export type PlayActivityTarget =
|
|
9
|
+
| {
|
|
10
|
+
kind: 'provider';
|
|
11
|
+
provider: string;
|
|
12
|
+
operation: string;
|
|
13
|
+
label?: string;
|
|
14
|
+
}
|
|
15
|
+
| {
|
|
16
|
+
kind: 'dataset';
|
|
17
|
+
tableNamespace: string;
|
|
18
|
+
operation: 'materializing' | 'persisting' | 'indexing' | 'reading';
|
|
19
|
+
label?: string;
|
|
20
|
+
}
|
|
21
|
+
| {
|
|
22
|
+
kind: 'lease';
|
|
23
|
+
resourceKind: 'run' | 'dataset' | 'row' | 'runner';
|
|
24
|
+
resourceLabel: string;
|
|
25
|
+
}
|
|
26
|
+
| {
|
|
27
|
+
kind: 'runner';
|
|
28
|
+
backend: string;
|
|
29
|
+
label?: string;
|
|
30
|
+
}
|
|
31
|
+
| {
|
|
32
|
+
kind: 'step';
|
|
33
|
+
stepId: string;
|
|
34
|
+
label?: string;
|
|
35
|
+
}
|
|
36
|
+
| {
|
|
37
|
+
kind: 'capacity';
|
|
38
|
+
pool: 'worker' | 'provider' | 'dataset' | 'runner';
|
|
39
|
+
label?: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type PlayActivityProgress = {
|
|
43
|
+
completed?: number;
|
|
44
|
+
total?: number;
|
|
45
|
+
failed?: number;
|
|
46
|
+
message?: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type PlayActivityState =
|
|
50
|
+
| {
|
|
51
|
+
kind: 'active';
|
|
52
|
+
progress?: PlayActivityProgress;
|
|
53
|
+
}
|
|
54
|
+
| {
|
|
55
|
+
kind: 'queued';
|
|
56
|
+
reason: 'capacity';
|
|
57
|
+
}
|
|
58
|
+
| {
|
|
59
|
+
kind: 'waiting';
|
|
60
|
+
reason:
|
|
61
|
+
| {
|
|
62
|
+
kind: 'external_event';
|
|
63
|
+
provider?: string;
|
|
64
|
+
eventKey?: string;
|
|
65
|
+
remaining?: number;
|
|
66
|
+
deadlineAt?: number;
|
|
67
|
+
}
|
|
68
|
+
| {
|
|
69
|
+
kind: 'dataset';
|
|
70
|
+
tableNamespace: string;
|
|
71
|
+
requiredPhase: 'available' | 'persisted' | 'indexed';
|
|
72
|
+
}
|
|
73
|
+
| {
|
|
74
|
+
kind: 'lease';
|
|
75
|
+
resourceKind: 'run' | 'dataset' | 'row' | 'runner';
|
|
76
|
+
resourceLabel: string;
|
|
77
|
+
expiresAt?: number;
|
|
78
|
+
}
|
|
79
|
+
| {
|
|
80
|
+
/** Mixed-version fallback only; new producers must choose a typed reason. */
|
|
81
|
+
kind: 'unknown';
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
| {
|
|
85
|
+
kind: 'retrying';
|
|
86
|
+
reason: 'rate_limit' | 'provider_error' | 'transport_error';
|
|
87
|
+
retryAt: number;
|
|
88
|
+
attempt?: number;
|
|
89
|
+
}
|
|
90
|
+
| {
|
|
91
|
+
kind: 'scheduled';
|
|
92
|
+
reason: 'sleep';
|
|
93
|
+
resumeAt: number;
|
|
94
|
+
}
|
|
95
|
+
| {
|
|
96
|
+
kind: 'completed';
|
|
97
|
+
}
|
|
98
|
+
| {
|
|
99
|
+
kind: 'failed';
|
|
100
|
+
code?: string;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export type PlayActivityObservation = {
|
|
104
|
+
schemaVersion: 1;
|
|
105
|
+
/** Stable within one run, for example `tool:discover_ctos` or `dataset:leads`. */
|
|
106
|
+
activityId: string;
|
|
107
|
+
/** Optional owning play step. */
|
|
108
|
+
stepId?: string;
|
|
109
|
+
target: PlayActivityTarget;
|
|
110
|
+
state: PlayActivityState;
|
|
111
|
+
/** Producer clock. The runtime envelope supplies run identity and ordering. */
|
|
112
|
+
observedAt: number;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export type PlayActivityEvent = {
|
|
116
|
+
type: 'activity.observed';
|
|
117
|
+
observation: PlayActivityObservation;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
121
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function isPlayActivityObservation(
|
|
125
|
+
value: unknown,
|
|
126
|
+
): value is PlayActivityObservation {
|
|
127
|
+
if (
|
|
128
|
+
!isRecord(value) ||
|
|
129
|
+
value.schemaVersion !== 1 ||
|
|
130
|
+
typeof value.activityId !== 'string' ||
|
|
131
|
+
!value.activityId.trim() ||
|
|
132
|
+
typeof value.observedAt !== 'number' ||
|
|
133
|
+
!Number.isFinite(value.observedAt) ||
|
|
134
|
+
!isRecord(value.target) ||
|
|
135
|
+
!isRecord(value.state)
|
|
136
|
+
) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
const targetKinds = new Set([
|
|
140
|
+
'provider',
|
|
141
|
+
'dataset',
|
|
142
|
+
'lease',
|
|
143
|
+
'runner',
|
|
144
|
+
'step',
|
|
145
|
+
'capacity',
|
|
146
|
+
]);
|
|
147
|
+
const stateKinds = new Set([
|
|
148
|
+
'active',
|
|
149
|
+
'queued',
|
|
150
|
+
'waiting',
|
|
151
|
+
'retrying',
|
|
152
|
+
'scheduled',
|
|
153
|
+
'completed',
|
|
154
|
+
'failed',
|
|
155
|
+
]);
|
|
156
|
+
return (
|
|
157
|
+
typeof value.target.kind === 'string' &&
|
|
158
|
+
targetKinds.has(value.target.kind) &&
|
|
159
|
+
typeof value.state.kind === 'string' &&
|
|
160
|
+
stateKinds.has(value.state.kind)
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export type PlayRunActivityProjection = {
|
|
165
|
+
observation: PlayActivityObservation;
|
|
166
|
+
summary: string;
|
|
167
|
+
visibility: PlayActivityVisibility;
|
|
168
|
+
/** False only when old payloads lacked enough typed facts to classify. */
|
|
169
|
+
classified: boolean;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Persistence lanes are a routing concern, not a producer concern:
|
|
174
|
+
*
|
|
175
|
+
* - durable: append a meaningful transition to the run ledger;
|
|
176
|
+
* - snapshot: replace current progress for this activity;
|
|
177
|
+
* - liveness: update last-seen evidence without growing the event ledger;
|
|
178
|
+
* - drop: stale or duplicate observation.
|
|
179
|
+
*/
|
|
180
|
+
export type PlayActivityRoutingLane =
|
|
181
|
+
| 'durable'
|
|
182
|
+
| 'snapshot'
|
|
183
|
+
| 'liveness'
|
|
184
|
+
| 'drop';
|
|
185
|
+
|
|
186
|
+
function stableJson(value: unknown): string {
|
|
187
|
+
return JSON.stringify(value);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function targetIdentity(target: PlayActivityTarget): string {
|
|
191
|
+
return stableJson(target);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function routePlayActivityObservation(input: {
|
|
195
|
+
previous?: PlayActivityObservation | null;
|
|
196
|
+
next: PlayActivityObservation;
|
|
197
|
+
}): PlayActivityRoutingLane {
|
|
198
|
+
const { previous, next } = input;
|
|
199
|
+
if (!previous) return 'durable';
|
|
200
|
+
if (next.observedAt <= previous.observedAt) return 'drop';
|
|
201
|
+
if (targetIdentity(previous.target) !== targetIdentity(next.target)) {
|
|
202
|
+
return 'durable';
|
|
203
|
+
}
|
|
204
|
+
if (previous.state.kind !== next.state.kind) return 'durable';
|
|
205
|
+
if (next.state.kind !== 'active') {
|
|
206
|
+
return stableJson(previous.state) === stableJson(next.state)
|
|
207
|
+
? 'liveness'
|
|
208
|
+
: 'durable';
|
|
209
|
+
}
|
|
210
|
+
return stableJson(previous.state) === stableJson(next.state)
|
|
211
|
+
? 'liveness'
|
|
212
|
+
: 'snapshot';
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export type PlayActivityVisibility =
|
|
216
|
+
| 'hidden'
|
|
217
|
+
| 'timeline'
|
|
218
|
+
| 'headline'
|
|
219
|
+
| 'action';
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Central presentation policy. Short, self-healing retries do not become
|
|
223
|
+
* alarming UI/log messages; blockers and failures do.
|
|
224
|
+
*/
|
|
225
|
+
export function derivePlayActivityVisibility(input: {
|
|
226
|
+
observation: PlayActivityObservation;
|
|
227
|
+
now: number;
|
|
228
|
+
}): PlayActivityVisibility {
|
|
229
|
+
const { state } = input.observation;
|
|
230
|
+
if (state.kind === 'failed') return 'action';
|
|
231
|
+
if (state.kind === 'waiting') {
|
|
232
|
+
return state.reason.kind === 'external_event' ? 'action' : 'headline';
|
|
233
|
+
}
|
|
234
|
+
if (state.kind === 'queued' || state.kind === 'scheduled') return 'headline';
|
|
235
|
+
if (state.kind === 'retrying') {
|
|
236
|
+
return state.retryAt - input.now <= 30_000 ? 'hidden' : 'timeline';
|
|
237
|
+
}
|
|
238
|
+
if (state.kind === 'active') return 'headline';
|
|
239
|
+
return 'timeline';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function targetLabel(target: PlayActivityTarget): string {
|
|
243
|
+
switch (target.kind) {
|
|
244
|
+
case 'provider':
|
|
245
|
+
return target.label ?? `${target.provider} ${target.operation}`;
|
|
246
|
+
case 'dataset':
|
|
247
|
+
return target.label ?? `dataset ${target.tableNamespace}`;
|
|
248
|
+
case 'lease':
|
|
249
|
+
return `${target.resourceKind} ${target.resourceLabel}`;
|
|
250
|
+
case 'runner':
|
|
251
|
+
return target.label ?? `${target.backend} runner`;
|
|
252
|
+
case 'step':
|
|
253
|
+
return target.label ?? target.stepId;
|
|
254
|
+
case 'capacity':
|
|
255
|
+
return target.label ?? `${target.pool} capacity`;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function summarizePlayActivity(
|
|
260
|
+
observation: PlayActivityObservation,
|
|
261
|
+
): string {
|
|
262
|
+
const label = targetLabel(observation.target);
|
|
263
|
+
const { state } = observation;
|
|
264
|
+
switch (state.kind) {
|
|
265
|
+
case 'active': {
|
|
266
|
+
const progress = state.progress;
|
|
267
|
+
const counts =
|
|
268
|
+
typeof progress?.completed === 'number' &&
|
|
269
|
+
typeof progress.total === 'number'
|
|
270
|
+
? ` (${progress.completed}/${progress.total})`
|
|
271
|
+
: '';
|
|
272
|
+
return `Active: ${label}${counts}.`;
|
|
273
|
+
}
|
|
274
|
+
case 'queued':
|
|
275
|
+
return `Queued: waiting for ${label}.`;
|
|
276
|
+
case 'waiting':
|
|
277
|
+
if (state.reason.kind === 'external_event') {
|
|
278
|
+
const provider = state.reason.provider
|
|
279
|
+
? ` from ${state.reason.provider}`
|
|
280
|
+
: '';
|
|
281
|
+
return `Blocked: waiting for an external event${provider}.`;
|
|
282
|
+
}
|
|
283
|
+
if (state.reason.kind === 'dataset') {
|
|
284
|
+
return `Blocked: waiting for dataset ${state.reason.tableNamespace} to become ${state.reason.requiredPhase}.`;
|
|
285
|
+
}
|
|
286
|
+
if (state.reason.kind === 'lease') {
|
|
287
|
+
return `Contended: waiting for ${state.reason.resourceKind} lease ${state.reason.resourceLabel}.`;
|
|
288
|
+
}
|
|
289
|
+
return `Paused: ${label}; the legacy producer did not report a typed reason.`;
|
|
290
|
+
case 'retrying':
|
|
291
|
+
return `Delayed: ${label} will retry automatically.`;
|
|
292
|
+
case 'scheduled':
|
|
293
|
+
return `Scheduled: ${label} will resume automatically.`;
|
|
294
|
+
case 'completed':
|
|
295
|
+
return `Completed: ${label}.`;
|
|
296
|
+
case 'failed':
|
|
297
|
+
return `Failed: ${label}.`;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export type PlayActivityReporter = {
|
|
302
|
+
observe(
|
|
303
|
+
input: Omit<PlayActivityObservation, 'schemaVersion' | 'observedAt'> & {
|
|
304
|
+
observedAt?: number;
|
|
305
|
+
},
|
|
306
|
+
): void;
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
export function createPlayActivityReporter(input: {
|
|
310
|
+
emit: (event: PlayActivityEvent) => void;
|
|
311
|
+
now?: () => number;
|
|
312
|
+
}): PlayActivityReporter {
|
|
313
|
+
return {
|
|
314
|
+
observe(observation) {
|
|
315
|
+
input.emit({
|
|
316
|
+
type: 'activity.observed',
|
|
317
|
+
observation: {
|
|
318
|
+
...observation,
|
|
319
|
+
schemaVersion: 1,
|
|
320
|
+
observedAt: observation.observedAt ?? input.now?.() ?? Date.now(),
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
type ProjectionStep = {
|
|
328
|
+
nodeId: string;
|
|
329
|
+
status?: string;
|
|
330
|
+
label?: string;
|
|
331
|
+
updatedAt?: number | null;
|
|
332
|
+
progress?: PlayActivityProgress | null;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
type ProjectionDataset = {
|
|
336
|
+
datasetId?: string;
|
|
337
|
+
tableNamespace: string;
|
|
338
|
+
phase?: string;
|
|
339
|
+
persistedRows?: number;
|
|
340
|
+
succeededRows?: number;
|
|
341
|
+
failedRows?: number;
|
|
342
|
+
complete?: boolean;
|
|
343
|
+
updatedAt?: number;
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
function isTerminalActivityState(state: PlayActivityState): boolean {
|
|
347
|
+
return state.kind === 'completed' || state.kind === 'failed';
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function projection(
|
|
351
|
+
observation: PlayActivityObservation,
|
|
352
|
+
now: number,
|
|
353
|
+
classified = true,
|
|
354
|
+
): PlayRunActivityProjection {
|
|
355
|
+
return {
|
|
356
|
+
observation,
|
|
357
|
+
summary: summarizePlayActivity(observation),
|
|
358
|
+
visibility: derivePlayActivityVisibility({ observation, now }),
|
|
359
|
+
classified,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Read-side compatibility projector. Explicit typed observations win; existing
|
|
365
|
+
* suspension, dataset, and step facts are adapted without requiring every
|
|
366
|
+
* legacy producer to migrate in one deploy.
|
|
367
|
+
*/
|
|
368
|
+
export function projectPlayRunActivity(input: {
|
|
369
|
+
runId: string;
|
|
370
|
+
playName?: string | null;
|
|
371
|
+
status: string;
|
|
372
|
+
updatedAt?: number | null;
|
|
373
|
+
waitKind?: string | null;
|
|
374
|
+
waitUntil?: number | null;
|
|
375
|
+
eventKey?: string | null;
|
|
376
|
+
runtimeBackend?: string | null;
|
|
377
|
+
activeNodeId?: string | null;
|
|
378
|
+
nodeStates?: ProjectionStep[] | null;
|
|
379
|
+
datasets?: ProjectionDataset[] | null;
|
|
380
|
+
explicit?: PlayActivityObservation[] | null;
|
|
381
|
+
now?: number;
|
|
382
|
+
}): PlayRunActivityProjection | null {
|
|
383
|
+
const now = input.now ?? Date.now();
|
|
384
|
+
const observedAt = input.updatedAt ?? now;
|
|
385
|
+
const normalizedStatus = input.status.trim().toLowerCase();
|
|
386
|
+
if (
|
|
387
|
+
normalizedStatus === 'completed' ||
|
|
388
|
+
normalizedStatus === 'failed' ||
|
|
389
|
+
normalizedStatus === 'cancelled' ||
|
|
390
|
+
normalizedStatus === 'terminated' ||
|
|
391
|
+
normalizedStatus === 'timed_out'
|
|
392
|
+
) {
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const explicit = [...(input.explicit ?? [])]
|
|
397
|
+
.filter((candidate) => !isTerminalActivityState(candidate.state))
|
|
398
|
+
.sort((left, right) => right.observedAt - left.observedAt)[0];
|
|
399
|
+
if (explicit) return projection(explicit, now);
|
|
400
|
+
|
|
401
|
+
if (input.waitKind === 'detached_runner') {
|
|
402
|
+
return projection(
|
|
403
|
+
{
|
|
404
|
+
schemaVersion: 1,
|
|
405
|
+
activityId: 'runner:managed',
|
|
406
|
+
target: {
|
|
407
|
+
kind: 'runner',
|
|
408
|
+
backend: input.runtimeBackend ?? 'managed',
|
|
409
|
+
label: 'managed play runner',
|
|
410
|
+
},
|
|
411
|
+
state: { kind: 'active' },
|
|
412
|
+
observedAt,
|
|
413
|
+
},
|
|
414
|
+
now,
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
if (input.waitKind === 'sleep') {
|
|
418
|
+
return projection(
|
|
419
|
+
{
|
|
420
|
+
schemaVersion: 1,
|
|
421
|
+
activityId: 'run:sleep',
|
|
422
|
+
target: {
|
|
423
|
+
kind: 'step',
|
|
424
|
+
stepId: input.activeNodeId ?? 'run',
|
|
425
|
+
label: input.playName ?? 'play',
|
|
426
|
+
},
|
|
427
|
+
state: {
|
|
428
|
+
kind: 'scheduled',
|
|
429
|
+
reason: 'sleep',
|
|
430
|
+
resumeAt: input.waitUntil ?? observedAt,
|
|
431
|
+
},
|
|
432
|
+
observedAt,
|
|
433
|
+
},
|
|
434
|
+
now,
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
if (
|
|
438
|
+
input.waitKind === 'integration_event' ||
|
|
439
|
+
input.waitKind === 'integration_event_batch'
|
|
440
|
+
) {
|
|
441
|
+
return projection(
|
|
442
|
+
{
|
|
443
|
+
schemaVersion: 1,
|
|
444
|
+
activityId: `event:${input.activeNodeId ?? 'run'}`,
|
|
445
|
+
stepId: input.activeNodeId ?? undefined,
|
|
446
|
+
target: {
|
|
447
|
+
kind: 'step',
|
|
448
|
+
stepId: input.activeNodeId ?? 'run',
|
|
449
|
+
label: input.playName ?? 'play',
|
|
450
|
+
},
|
|
451
|
+
state: {
|
|
452
|
+
kind: 'waiting',
|
|
453
|
+
reason: {
|
|
454
|
+
kind: 'external_event',
|
|
455
|
+
eventKey: input.eventKey ?? undefined,
|
|
456
|
+
deadlineAt: input.waitUntil ?? undefined,
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
observedAt,
|
|
460
|
+
},
|
|
461
|
+
now,
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
// Mixed-version readers may see the durable WAITING status before the
|
|
465
|
+
// scheduler's typed wait projection is available. Do not let a stale
|
|
466
|
+
// running step overwrite that stronger lifecycle fact.
|
|
467
|
+
if (normalizedStatus === 'waiting') {
|
|
468
|
+
return projection(
|
|
469
|
+
{
|
|
470
|
+
schemaVersion: 1,
|
|
471
|
+
activityId: 'run:legacy-wait',
|
|
472
|
+
target: {
|
|
473
|
+
kind: 'step',
|
|
474
|
+
stepId: input.activeNodeId ?? 'run',
|
|
475
|
+
label: input.playName ?? 'play',
|
|
476
|
+
},
|
|
477
|
+
state: { kind: 'waiting', reason: { kind: 'unknown' } },
|
|
478
|
+
observedAt,
|
|
479
|
+
},
|
|
480
|
+
now,
|
|
481
|
+
false,
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const pendingDataset = [...(input.datasets ?? [])]
|
|
486
|
+
.filter(
|
|
487
|
+
(dataset) =>
|
|
488
|
+
dataset.phase === 'registered' ||
|
|
489
|
+
(dataset.complete !== true && dataset.phase !== 'failed'),
|
|
490
|
+
)
|
|
491
|
+
.sort((left, right) => (right.updatedAt ?? 0) - (left.updatedAt ?? 0))[0];
|
|
492
|
+
if (pendingDataset) {
|
|
493
|
+
return projection(
|
|
494
|
+
{
|
|
495
|
+
schemaVersion: 1,
|
|
496
|
+
activityId: `dataset:${pendingDataset.datasetId ?? pendingDataset.tableNamespace}`,
|
|
497
|
+
target: {
|
|
498
|
+
kind: 'dataset',
|
|
499
|
+
tableNamespace: pendingDataset.tableNamespace,
|
|
500
|
+
operation: 'materializing',
|
|
501
|
+
},
|
|
502
|
+
state: {
|
|
503
|
+
kind: 'active',
|
|
504
|
+
progress: {
|
|
505
|
+
completed: pendingDataset.persistedRows,
|
|
506
|
+
total:
|
|
507
|
+
typeof pendingDataset.succeededRows === 'number' ||
|
|
508
|
+
typeof pendingDataset.failedRows === 'number'
|
|
509
|
+
? (pendingDataset.succeededRows ?? 0) +
|
|
510
|
+
(pendingDataset.failedRows ?? 0)
|
|
511
|
+
: undefined,
|
|
512
|
+
failed: pendingDataset.failedRows,
|
|
513
|
+
},
|
|
514
|
+
},
|
|
515
|
+
observedAt: pendingDataset.updatedAt ?? observedAt,
|
|
516
|
+
},
|
|
517
|
+
now,
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const activeStep =
|
|
522
|
+
input.nodeStates?.find(
|
|
523
|
+
(candidate) => candidate.nodeId === input.activeNodeId,
|
|
524
|
+
) ??
|
|
525
|
+
input.nodeStates?.find((candidate) => candidate.status === 'running') ??
|
|
526
|
+
null;
|
|
527
|
+
if (activeStep) {
|
|
528
|
+
const toolId = activeStep.nodeId.startsWith('tool:')
|
|
529
|
+
? activeStep.nodeId.split(':').at(-1)?.trim() || null
|
|
530
|
+
: null;
|
|
531
|
+
const provider = toolId?.split(/[._]/)[0]?.trim() || null;
|
|
532
|
+
return projection(
|
|
533
|
+
{
|
|
534
|
+
schemaVersion: 1,
|
|
535
|
+
activityId: `step:${activeStep.nodeId}`,
|
|
536
|
+
stepId: activeStep.nodeId,
|
|
537
|
+
target:
|
|
538
|
+
toolId && provider
|
|
539
|
+
? {
|
|
540
|
+
kind: 'provider',
|
|
541
|
+
provider,
|
|
542
|
+
operation: toolId,
|
|
543
|
+
label: activeStep.label ?? toolId,
|
|
544
|
+
}
|
|
545
|
+
: {
|
|
546
|
+
kind: 'step',
|
|
547
|
+
stepId: activeStep.nodeId,
|
|
548
|
+
label: activeStep.label,
|
|
549
|
+
},
|
|
550
|
+
state: {
|
|
551
|
+
kind: 'active',
|
|
552
|
+
progress: activeStep.progress ?? undefined,
|
|
553
|
+
},
|
|
554
|
+
observedAt: activeStep.updatedAt ?? observedAt,
|
|
555
|
+
},
|
|
556
|
+
now,
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (normalizedStatus === 'queued') {
|
|
561
|
+
return projection(
|
|
562
|
+
{
|
|
563
|
+
schemaVersion: 1,
|
|
564
|
+
activityId: 'capacity:worker',
|
|
565
|
+
target: { kind: 'capacity', pool: 'worker' },
|
|
566
|
+
state: { kind: 'queued', reason: 'capacity' },
|
|
567
|
+
observedAt,
|
|
568
|
+
},
|
|
569
|
+
now,
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const fallback: PlayActivityObservation = {
|
|
574
|
+
schemaVersion: 1,
|
|
575
|
+
activityId: 'run:legacy',
|
|
576
|
+
target: {
|
|
577
|
+
kind: 'step',
|
|
578
|
+
stepId: input.activeNodeId ?? 'run',
|
|
579
|
+
label: input.playName ?? 'play',
|
|
580
|
+
},
|
|
581
|
+
state:
|
|
582
|
+
normalizedStatus === 'waiting'
|
|
583
|
+
? { kind: 'waiting', reason: { kind: 'unknown' } }
|
|
584
|
+
: { kind: 'active' },
|
|
585
|
+
observedAt,
|
|
586
|
+
};
|
|
587
|
+
return projection(fallback, now, normalizedStatus !== 'waiting');
|
|
588
|
+
}
|