deepline 0.3.236 → 0.3.238
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 +164 -295
- package/dist/bundling-sources/sdk/src/http.ts +1 -1
- package/dist/bundling-sources/sdk/src/index.ts +1 -0
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/sdk/src/runs/observe-transport.ts +5 -468
- package/dist/bundling-sources/sdk/src/runs/watch-polling.ts +220 -0
- package/dist/bundling-sources/sdk/src/stream-reconnect.ts +9 -12
- package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +4 -4
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +264 -120
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +219 -46
- package/dist/cli/index.js +687 -2108
- package/dist/cli/index.mjs +687 -2108
- package/dist/index.d.mts +77 -57
- package/dist/index.d.ts +77 -57
- package/dist/index.js +297 -1732
- package/dist/index.mjs +297 -1732
- package/dist/install-integrity.json +1 -0
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +1 -1
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
|
@@ -64,15 +64,7 @@ import {
|
|
|
64
64
|
type EffectiveProductNotificationGroup,
|
|
65
65
|
type ProductNotificationGroupDraft,
|
|
66
66
|
} from '../../shared_libs/product-notifications/configuration.js';
|
|
67
|
-
import {
|
|
68
|
-
STREAM_HEALTHY_CONNECTION_MS,
|
|
69
|
-
isTransientPlayStreamError,
|
|
70
|
-
streamReconnectDelayMs,
|
|
71
|
-
} from './stream-reconnect.js';
|
|
72
|
-
import {
|
|
73
|
-
observeRunEvents,
|
|
74
|
-
RunObserveTransportUnavailableError,
|
|
75
|
-
} from './runs/observe-transport.js';
|
|
67
|
+
import { watchRunEvents as pollRunSnapshotEvents } from './runs/watch-polling.js';
|
|
76
68
|
import type {
|
|
77
69
|
DeeplineClientOptions,
|
|
78
70
|
ResolvedConfig,
|
|
@@ -627,28 +619,35 @@ export type RunGetStatus =
|
|
|
627
619
|
|
|
628
620
|
const RUNS_FAILED_LOG_LIMIT = 20;
|
|
629
621
|
|
|
630
|
-
/**
|
|
631
|
-
export type
|
|
622
|
+
/** Options for `client.runs.watch(...)`. */
|
|
623
|
+
export type RunsWatchOptions = {
|
|
632
624
|
signal?: AbortSignal;
|
|
633
|
-
/** Observe each canonical
|
|
625
|
+
/** Observe each canonical event while `watch` waits for terminal state. */
|
|
634
626
|
onEvent?: (event: PlayLiveEvent) => void;
|
|
635
627
|
/**
|
|
636
|
-
* Called
|
|
637
|
-
*
|
|
628
|
+
* Called once when a transient REST polling outage begins. Polling retries
|
|
629
|
+
* on its normal fixed interval.
|
|
638
630
|
*/
|
|
631
|
+
onRetry?: (info: {
|
|
632
|
+
attempt: number;
|
|
633
|
+
delayMs: number;
|
|
634
|
+
reason: string;
|
|
635
|
+
}) => void;
|
|
636
|
+
/** @deprecated Use `onRetry`. */
|
|
639
637
|
onReconnect?: (info: {
|
|
640
638
|
attempt: number;
|
|
641
639
|
delayMs: number;
|
|
642
640
|
reason: string;
|
|
643
641
|
}) => void;
|
|
644
642
|
/**
|
|
645
|
-
* Display-only
|
|
646
|
-
* staleness warnings, and the one-time fallback notice when the server
|
|
647
|
-
* cannot serve the Convex subscription transport (ADR-0008).
|
|
643
|
+
* Display-only notices for a transient snapshot polling outage or recovery.
|
|
648
644
|
*/
|
|
649
645
|
onNotice?: (message: string) => void;
|
|
650
646
|
};
|
|
651
647
|
|
|
648
|
+
/** @deprecated Use `RunsWatchOptions`. */
|
|
649
|
+
export type RunsTailOptions = RunsWatchOptions;
|
|
650
|
+
|
|
652
651
|
/** Log fetch options for `client.runs.logs(...)`. */
|
|
653
652
|
export type RunsLogsOptions = {
|
|
654
653
|
/** Return the LAST `limit` stored log lines (default 200). */
|
|
@@ -756,7 +755,7 @@ export type PlaySecretCheck = {
|
|
|
756
755
|
};
|
|
757
756
|
|
|
758
757
|
/**
|
|
759
|
-
* Use `client.runs` (`/api/v2/runs`) to
|
|
758
|
+
* Use `client.runs` (`/api/v2/runs`) to watch, stop, read logs, and export durable dataset rows.
|
|
760
759
|
*
|
|
761
760
|
* @sdkReference client 020 client.runs
|
|
762
761
|
*/
|
|
@@ -782,7 +781,9 @@ export type RunsNamespace = {
|
|
|
782
781
|
list: (options: RunsListOptions) => Promise<PlayRunListItem[]>;
|
|
783
782
|
/** Read one run page with total, offset, limit and completeness metadata. */
|
|
784
783
|
listPage: (options: RunsListOptions) => Promise<RunsListPage>;
|
|
785
|
-
/**
|
|
784
|
+
/** Poll run snapshots every two seconds until terminal status. */
|
|
785
|
+
watch: (runId: string, options?: RunsWatchOptions) => Promise<PlayStatus>;
|
|
786
|
+
/** @deprecated Use `watch`; retained as a source-compatible alias. */
|
|
786
787
|
tail: (runId: string, options?: RunsTailOptions) => Promise<PlayStatus>;
|
|
787
788
|
/** Fetch persisted log lines for a run. */
|
|
788
789
|
logs: (runId: string, options?: RunsLogsOptions) => Promise<RunsLogsResult>;
|
|
@@ -2047,6 +2048,8 @@ export type BillingRecentUsageEntry = {
|
|
|
2047
2048
|
| 'pending'
|
|
2048
2049
|
| 'free'
|
|
2049
2050
|
| 'failed';
|
|
2051
|
+
/** Final billing truth may be unknown even after execution completes. */
|
|
2052
|
+
charge_finality?: 'final' | 'pending' | 'unknown';
|
|
2050
2053
|
rough_usd_cost: number | null;
|
|
2051
2054
|
created_at: string;
|
|
2052
2055
|
};
|
|
@@ -2076,6 +2079,10 @@ export type BillingUsageResult = {
|
|
|
2076
2079
|
run_id: string | null;
|
|
2077
2080
|
request_id?: string;
|
|
2078
2081
|
total: number;
|
|
2082
|
+
/** Only true when total covers the complete matching history. */
|
|
2083
|
+
total_is_exact?: boolean;
|
|
2084
|
+
/** Proven matching entries, without assuming unscanned rows match. */
|
|
2085
|
+
total_lower_bound?: number;
|
|
2079
2086
|
offset: number;
|
|
2080
2087
|
limit: number;
|
|
2081
2088
|
next_offset: number;
|
|
@@ -2659,6 +2666,7 @@ export class DeeplineClient {
|
|
|
2659
2666
|
status: (runId) => this.getRunLifecycleStatus(runId),
|
|
2660
2667
|
list: (options) => this.listRuns(options),
|
|
2661
2668
|
listPage: (options) => this.listRunsPage(options),
|
|
2669
|
+
watch: (runId, options) => this.watchRunToTerminal(runId, options),
|
|
2662
2670
|
tail: (runId, options) => this.tailRun(runId, options),
|
|
2663
2671
|
logs: (runId, options) => this.getRunLogs(runId, options),
|
|
2664
2672
|
input: (runId) => this.getRunInput(runId),
|
|
@@ -3385,15 +3393,17 @@ export class DeeplineClient {
|
|
|
3385
3393
|
}
|
|
3386
3394
|
|
|
3387
3395
|
/**
|
|
3388
|
-
*
|
|
3396
|
+
* Submit a play run through the legacy event response.
|
|
3389
3397
|
*
|
|
3390
|
-
*
|
|
3391
|
-
*
|
|
3398
|
+
* This reports admission from the start request; it does not watch an
|
|
3399
|
+
* existing run. Use `startPlayRun()` and `watchRunEvents(runId)` separately.
|
|
3400
|
+
*
|
|
3401
|
+
* @deprecated Use the separate start and watch methods.
|
|
3392
3402
|
*
|
|
3393
3403
|
* @param request - Play run configuration.
|
|
3394
|
-
* @param options - Optional
|
|
3395
|
-
* @param options.signal -
|
|
3396
|
-
* @returns Async
|
|
3404
|
+
* @param options - Optional compatibility response options.
|
|
3405
|
+
* @param options.signal - Abort the start response.
|
|
3406
|
+
* @returns Async generator of play-scoped live events.
|
|
3397
3407
|
*/
|
|
3398
3408
|
async *startPlayRunStream(
|
|
3399
3409
|
request: StartPlayRunRequest,
|
|
@@ -4122,11 +4132,31 @@ export class DeeplineClient {
|
|
|
4122
4132
|
return normalizePlayStatus(response);
|
|
4123
4133
|
}
|
|
4124
4134
|
|
|
4135
|
+
/** Poll authenticated run snapshots every two seconds and emit changed events. */
|
|
4136
|
+
async *watchRunEvents(
|
|
4137
|
+
workflowId: string,
|
|
4138
|
+
options?: {
|
|
4139
|
+
signal?: AbortSignal;
|
|
4140
|
+
onNotice?: (message: string) => void;
|
|
4141
|
+
onRetry?: RunsWatchOptions['onRetry'];
|
|
4142
|
+
},
|
|
4143
|
+
): AsyncGenerator<PlayLiveEvent> {
|
|
4144
|
+
for await (const event of pollRunSnapshotEvents({
|
|
4145
|
+
http: this.http,
|
|
4146
|
+
runId: workflowId,
|
|
4147
|
+
signal: options?.signal,
|
|
4148
|
+
onNotice: options?.onNotice,
|
|
4149
|
+
onRetry: options?.onRetry,
|
|
4150
|
+
})) {
|
|
4151
|
+
yield event as PlayLiveEvent;
|
|
4152
|
+
}
|
|
4153
|
+
}
|
|
4154
|
+
|
|
4125
4155
|
/**
|
|
4126
|
-
*
|
|
4156
|
+
* Read raw legacy SSE events for callers that explicitly require that
|
|
4157
|
+
* compatibility endpoint. Ordinary run watching uses REST snapshot polling.
|
|
4127
4158
|
*
|
|
4128
|
-
*
|
|
4129
|
-
* connection, then incremental live events until terminal state or reconnect.
|
|
4159
|
+
* @deprecated Use `watchRunEvents` for ordinary run watching.
|
|
4130
4160
|
*/
|
|
4131
4161
|
async *streamPlayRunEvents(
|
|
4132
4162
|
workflowId: string,
|
|
@@ -4152,6 +4182,82 @@ export class DeeplineClient {
|
|
|
4152
4182
|
}
|
|
4153
4183
|
}
|
|
4154
4184
|
|
|
4185
|
+
/**
|
|
4186
|
+
* Compatibility alias for polling canonical run snapshots and emitting
|
|
4187
|
+
* their changed events.
|
|
4188
|
+
*
|
|
4189
|
+
* @deprecated Use `watchRunEvents`; kept as a source-compatible alias.
|
|
4190
|
+
* The `fallback` option is ignored because watching uses REST polling.
|
|
4191
|
+
*/
|
|
4192
|
+
async *observeRunEvents(
|
|
4193
|
+
runId: string,
|
|
4194
|
+
options?: {
|
|
4195
|
+
signal?: AbortSignal;
|
|
4196
|
+
onNotice?: (message: string) => void;
|
|
4197
|
+
/** @deprecated Ignored; watching always uses REST snapshot polling. */
|
|
4198
|
+
fallback?: 'sse' | 'none';
|
|
4199
|
+
},
|
|
4200
|
+
): AsyncGenerator<PlayLiveEvent> {
|
|
4201
|
+
yield* this.watchRunEvents(runId, {
|
|
4202
|
+
signal: options?.signal,
|
|
4203
|
+
onNotice: options?.onNotice,
|
|
4204
|
+
});
|
|
4205
|
+
}
|
|
4206
|
+
|
|
4207
|
+
/** Poll snapshots to terminal status and return the durable package. */
|
|
4208
|
+
private async watchRunToTerminal(
|
|
4209
|
+
runId: string,
|
|
4210
|
+
options?: RunsWatchOptions,
|
|
4211
|
+
): Promise<PlayStatus> {
|
|
4212
|
+
const state: PlayLiveStatusState = {
|
|
4213
|
+
runId,
|
|
4214
|
+
status: 'running',
|
|
4215
|
+
logs: [],
|
|
4216
|
+
lastLogSeq: 0,
|
|
4217
|
+
latest: null,
|
|
4218
|
+
};
|
|
4219
|
+
let terminalObserved = false;
|
|
4220
|
+
for await (const event of this.watchRunEvents(runId, {
|
|
4221
|
+
signal: options?.signal,
|
|
4222
|
+
onNotice: options?.onNotice,
|
|
4223
|
+
onRetry: options?.onRetry ?? options?.onReconnect,
|
|
4224
|
+
})) {
|
|
4225
|
+
options?.onEvent?.(event);
|
|
4226
|
+
const status = updatePlayLiveStatusState(state, event);
|
|
4227
|
+
if (status && TERMINAL_PLAY_STATUSES.has(status.status)) {
|
|
4228
|
+
terminalObserved = true;
|
|
4229
|
+
}
|
|
4230
|
+
}
|
|
4231
|
+
if (options?.signal?.aborted) {
|
|
4232
|
+
throw new DeeplineError('Run watch aborted.', undefined, 'ABORTED');
|
|
4233
|
+
}
|
|
4234
|
+
if (terminalObserved) {
|
|
4235
|
+
return await this.getRunStatus(runId);
|
|
4236
|
+
}
|
|
4237
|
+
const refreshed = await this.getRunStatus(runId);
|
|
4238
|
+
if (TERMINAL_PLAY_STATUSES.has(refreshed.status)) return refreshed;
|
|
4239
|
+
throw new DeeplineError(
|
|
4240
|
+
`Run watch for ${runId} ended before a terminal status.`,
|
|
4241
|
+
undefined,
|
|
4242
|
+
'PLAY_WATCH_ENDED',
|
|
4243
|
+
);
|
|
4244
|
+
}
|
|
4245
|
+
|
|
4246
|
+
/**
|
|
4247
|
+
* Wait for a run and return its terminal status.
|
|
4248
|
+
*
|
|
4249
|
+
* This source-compatible alias uses the same authenticated two-second REST
|
|
4250
|
+
* snapshot polling as `client.runs.watch`.
|
|
4251
|
+
*
|
|
4252
|
+
* @deprecated Use `client.runs.watch`; retained for SDK compatibility.
|
|
4253
|
+
* @param runId - Public Deepline play-run id.
|
|
4254
|
+
* @param options - Optional callbacks and cancellation signal.
|
|
4255
|
+
* @returns The final durable run status.
|
|
4256
|
+
*/
|
|
4257
|
+
async tailRun(runId: string, options?: RunsTailOptions): Promise<PlayStatus> {
|
|
4258
|
+
return this.watchRunToTerminal(runId, options);
|
|
4259
|
+
}
|
|
4260
|
+
|
|
4155
4261
|
/**
|
|
4156
4262
|
* Cancel a running play execution.
|
|
4157
4263
|
*
|
|
@@ -4486,263 +4592,6 @@ export class DeeplineClient {
|
|
|
4486
4592
|
return { ...response, runs: response.runs ?? [] };
|
|
4487
4593
|
}
|
|
4488
4594
|
|
|
4489
|
-
/**
|
|
4490
|
-
* Observe one run's live events. Uses the Convex Run Snapshot subscription
|
|
4491
|
-
* transport first (ADR-0008), then falls back to the canonical SSE stream
|
|
4492
|
-
* when the subscription transport or its optional client modules are not
|
|
4493
|
-
* available. Pass `fallback: 'none'` to receive
|
|
4494
|
-
* {@link RunObserveTransportUnavailableError} instead.
|
|
4495
|
-
*/
|
|
4496
|
-
async *observeRunEvents(
|
|
4497
|
-
runId: string,
|
|
4498
|
-
options?: {
|
|
4499
|
-
signal?: AbortSignal;
|
|
4500
|
-
onNotice?: (message: string) => void;
|
|
4501
|
-
fallback?: 'sse' | 'none';
|
|
4502
|
-
},
|
|
4503
|
-
): AsyncGenerator<PlayLiveEvent> {
|
|
4504
|
-
let yieldedObserveEvent = false;
|
|
4505
|
-
try {
|
|
4506
|
-
for await (const event of observeRunEvents({
|
|
4507
|
-
http: this.http,
|
|
4508
|
-
runId,
|
|
4509
|
-
signal: options?.signal,
|
|
4510
|
-
onNotice: options?.onNotice,
|
|
4511
|
-
}) as AsyncGenerator<PlayLiveEvent>) {
|
|
4512
|
-
yieldedObserveEvent = true;
|
|
4513
|
-
yield event;
|
|
4514
|
-
}
|
|
4515
|
-
} catch (error) {
|
|
4516
|
-
if (
|
|
4517
|
-
!(error instanceof RunObserveTransportUnavailableError) ||
|
|
4518
|
-
yieldedObserveEvent ||
|
|
4519
|
-
options?.fallback === 'none'
|
|
4520
|
-
) {
|
|
4521
|
-
throw error;
|
|
4522
|
-
}
|
|
4523
|
-
options?.onNotice?.(
|
|
4524
|
-
`[observe] live subscription unavailable (${error.reason}); falling back to SSE tail (support window, ADR-0008)`,
|
|
4525
|
-
);
|
|
4526
|
-
yield* this.streamPlayRunEventsUntilTerminal(runId, options);
|
|
4527
|
-
}
|
|
4528
|
-
}
|
|
4529
|
-
|
|
4530
|
-
private async *streamPlayRunEventsUntilTerminal(
|
|
4531
|
-
runId: string,
|
|
4532
|
-
options?: { signal?: AbortSignal; onNotice?: (message: string) => void },
|
|
4533
|
-
): AsyncGenerator<PlayLiveEvent> {
|
|
4534
|
-
const state: PlayLiveStatusState = {
|
|
4535
|
-
runId,
|
|
4536
|
-
status: 'running',
|
|
4537
|
-
logs: [],
|
|
4538
|
-
lastLogSeq: 0,
|
|
4539
|
-
latest: null,
|
|
4540
|
-
};
|
|
4541
|
-
let lastEventId: string | undefined;
|
|
4542
|
-
let reconnectAttempt = 0;
|
|
4543
|
-
|
|
4544
|
-
for (;;) {
|
|
4545
|
-
if (options?.signal?.aborted) {
|
|
4546
|
-
return;
|
|
4547
|
-
}
|
|
4548
|
-
const connectedAt = Date.now();
|
|
4549
|
-
let sawEvent = false;
|
|
4550
|
-
let endedReason = 'stream window ended before a terminal event';
|
|
4551
|
-
try {
|
|
4552
|
-
for await (const event of this.streamPlayRunEvents(runId, {
|
|
4553
|
-
mode: 'cli',
|
|
4554
|
-
signal: options?.signal,
|
|
4555
|
-
...(lastEventId ? { lastEventId } : {}),
|
|
4556
|
-
})) {
|
|
4557
|
-
sawEvent = true;
|
|
4558
|
-
if (event.cursor?.trim()) {
|
|
4559
|
-
lastEventId = event.cursor;
|
|
4560
|
-
}
|
|
4561
|
-
yield event;
|
|
4562
|
-
const status = updatePlayLiveStatusState(state, event);
|
|
4563
|
-
if (status && TERMINAL_PLAY_STATUSES.has(status.status)) {
|
|
4564
|
-
return;
|
|
4565
|
-
}
|
|
4566
|
-
}
|
|
4567
|
-
} catch (error) {
|
|
4568
|
-
if (options?.signal?.aborted) {
|
|
4569
|
-
return;
|
|
4570
|
-
}
|
|
4571
|
-
if (!isTransientPlayStreamError(error)) {
|
|
4572
|
-
throw error;
|
|
4573
|
-
}
|
|
4574
|
-
endedReason = error instanceof Error ? error.message : String(error);
|
|
4575
|
-
}
|
|
4576
|
-
|
|
4577
|
-
let refreshed: PlayStatus | null = null;
|
|
4578
|
-
try {
|
|
4579
|
-
refreshed = await this.getRunStatus(runId);
|
|
4580
|
-
} catch (error) {
|
|
4581
|
-
if (!isTransientPlayStreamError(error)) {
|
|
4582
|
-
throw error;
|
|
4583
|
-
}
|
|
4584
|
-
}
|
|
4585
|
-
if (refreshed && TERMINAL_PLAY_STATUSES.has(refreshed.status)) {
|
|
4586
|
-
yield {
|
|
4587
|
-
cursor: String(Date.now()),
|
|
4588
|
-
streamId: `sse-fallback:${runId}`,
|
|
4589
|
-
scope: 'play',
|
|
4590
|
-
type: 'play.run.status',
|
|
4591
|
-
at: new Date().toISOString(),
|
|
4592
|
-
payload: refreshed as unknown as Record<string, unknown>,
|
|
4593
|
-
};
|
|
4594
|
-
return;
|
|
4595
|
-
}
|
|
4596
|
-
|
|
4597
|
-
if (
|
|
4598
|
-
sawEvent ||
|
|
4599
|
-
Date.now() - connectedAt >= STREAM_HEALTHY_CONNECTION_MS
|
|
4600
|
-
) {
|
|
4601
|
-
reconnectAttempt = 0;
|
|
4602
|
-
}
|
|
4603
|
-
const delayMs = streamReconnectDelayMs(reconnectAttempt);
|
|
4604
|
-
reconnectAttempt += 1;
|
|
4605
|
-
options?.onNotice?.(
|
|
4606
|
-
`[observe] SSE tail window ended before terminal status (${endedReason}); reconnecting to run ${runId}`,
|
|
4607
|
-
);
|
|
4608
|
-
await sleep(delayMs);
|
|
4609
|
-
}
|
|
4610
|
-
}
|
|
4611
|
-
|
|
4612
|
-
/**
|
|
4613
|
-
* Tail one run through the subscription transport until terminal, then
|
|
4614
|
-
* return one durable REST status read (the final Run Response Package).
|
|
4615
|
-
*/
|
|
4616
|
-
private async tailRunViaObserveTransport(
|
|
4617
|
-
runId: string,
|
|
4618
|
-
options?: RunsTailOptions,
|
|
4619
|
-
): Promise<PlayStatus> {
|
|
4620
|
-
const state: PlayLiveStatusState = {
|
|
4621
|
-
runId,
|
|
4622
|
-
status: 'running',
|
|
4623
|
-
logs: [],
|
|
4624
|
-
lastLogSeq: 0,
|
|
4625
|
-
latest: null,
|
|
4626
|
-
};
|
|
4627
|
-
for await (const event of this.observeRunEvents(runId, {
|
|
4628
|
-
signal: options?.signal,
|
|
4629
|
-
onNotice: options?.onNotice,
|
|
4630
|
-
fallback: 'none',
|
|
4631
|
-
})) {
|
|
4632
|
-
options?.onEvent?.(event);
|
|
4633
|
-
const status = updatePlayLiveStatusState(state, event);
|
|
4634
|
-
if (!status || !TERMINAL_PLAY_STATUSES.has(status.status)) {
|
|
4635
|
-
continue;
|
|
4636
|
-
}
|
|
4637
|
-
return await this.getRunStatus(status.runId || runId);
|
|
4638
|
-
}
|
|
4639
|
-
if (options?.signal?.aborted) {
|
|
4640
|
-
throw new DeeplineError('Run observation aborted.', undefined, 'ABORTED');
|
|
4641
|
-
}
|
|
4642
|
-
// The transport ends only after a terminal snapshot; the differ always
|
|
4643
|
-
// emits a terminal `play.run.status` first, so reaching here means the
|
|
4644
|
-
// terminal package read raced — re-check durable status once, loudly.
|
|
4645
|
-
const refreshed = await this.getRunStatus(runId);
|
|
4646
|
-
if (TERMINAL_PLAY_STATUSES.has(refreshed.status)) {
|
|
4647
|
-
return refreshed;
|
|
4648
|
-
}
|
|
4649
|
-
throw new DeeplineError(
|
|
4650
|
-
`Run observation for ${runId} ended before a terminal status.`,
|
|
4651
|
-
undefined,
|
|
4652
|
-
'PLAY_LIVE_STREAM_ENDED',
|
|
4653
|
-
);
|
|
4654
|
-
}
|
|
4655
|
-
|
|
4656
|
-
/**
|
|
4657
|
-
* Read the canonical run stream until a terminal run status is observed.
|
|
4658
|
-
*
|
|
4659
|
-
* Tries the Convex Run Snapshot subscription transport first (ADR-0008);
|
|
4660
|
-
* when the server cannot serve it (grant endpoint missing/unconfigured or
|
|
4661
|
-
* Convex unreachable) it falls back — with one `onNotice` message — to the
|
|
4662
|
-
* support-window SSE stream below.
|
|
4663
|
-
*
|
|
4664
|
-
* Server stream windows are finite: they end cleanly at the function
|
|
4665
|
-
* ceiling even while the run keeps executing. A window that ends (cleanly
|
|
4666
|
-
* or via transient network error) without a terminal event triggers one
|
|
4667
|
-
* durable-status re-check followed by a backed-off reconnect, so long runs
|
|
4668
|
-
* tail to completion. Abort via `options.signal` to stop waiting.
|
|
4669
|
-
*/
|
|
4670
|
-
async tailRun(runId: string, options?: RunsTailOptions): Promise<PlayStatus> {
|
|
4671
|
-
try {
|
|
4672
|
-
return await this.tailRunViaObserveTransport(runId, options);
|
|
4673
|
-
} catch (error) {
|
|
4674
|
-
if (!(error instanceof RunObserveTransportUnavailableError)) {
|
|
4675
|
-
throw error;
|
|
4676
|
-
}
|
|
4677
|
-
options?.onNotice?.(
|
|
4678
|
-
`[observe] live subscription unavailable (${error.reason}); falling back to SSE tail (support window, ADR-0008)`,
|
|
4679
|
-
);
|
|
4680
|
-
}
|
|
4681
|
-
const state: PlayLiveStatusState = {
|
|
4682
|
-
runId,
|
|
4683
|
-
status: 'running',
|
|
4684
|
-
logs: [],
|
|
4685
|
-
lastLogSeq: 0,
|
|
4686
|
-
latest: null,
|
|
4687
|
-
};
|
|
4688
|
-
let reconnectAttempt = 0;
|
|
4689
|
-
|
|
4690
|
-
for (;;) {
|
|
4691
|
-
const connectedAt = Date.now();
|
|
4692
|
-
let sawEvent = false;
|
|
4693
|
-
let terminalRunId: string | null = null;
|
|
4694
|
-
let endedReason = 'stream window ended before a terminal event';
|
|
4695
|
-
try {
|
|
4696
|
-
for await (const event of this.streamPlayRunEvents(runId, {
|
|
4697
|
-
mode: 'cli',
|
|
4698
|
-
signal: options?.signal,
|
|
4699
|
-
})) {
|
|
4700
|
-
options?.onEvent?.(event);
|
|
4701
|
-
sawEvent = true;
|
|
4702
|
-
const status = updatePlayLiveStatusState(state, event);
|
|
4703
|
-
if (!status || !TERMINAL_PLAY_STATUSES.has(status.status)) {
|
|
4704
|
-
continue;
|
|
4705
|
-
}
|
|
4706
|
-
terminalRunId = status.runId || runId;
|
|
4707
|
-
break;
|
|
4708
|
-
}
|
|
4709
|
-
} catch (error) {
|
|
4710
|
-
if (options?.signal?.aborted || !isTransientPlayStreamError(error)) {
|
|
4711
|
-
throw error;
|
|
4712
|
-
}
|
|
4713
|
-
endedReason = error instanceof Error ? error.message : String(error);
|
|
4714
|
-
}
|
|
4715
|
-
|
|
4716
|
-
if (terminalRunId) {
|
|
4717
|
-
return await this.getRunStatus(terminalRunId);
|
|
4718
|
-
}
|
|
4719
|
-
|
|
4720
|
-
// Window ended without a terminal event. The run may have finished
|
|
4721
|
-
// during the gap — re-check durable status once before reconnecting.
|
|
4722
|
-
// `runs.get` owns the durable snapshot; its optional status evidence is
|
|
4723
|
-
// additive and must not prevent old servers from ending this stream.
|
|
4724
|
-
const refreshed = await this.getRunStatus(runId);
|
|
4725
|
-
if (TERMINAL_PLAY_STATUSES.has(refreshed.status)) {
|
|
4726
|
-
return refreshed;
|
|
4727
|
-
}
|
|
4728
|
-
|
|
4729
|
-
if (
|
|
4730
|
-
sawEvent ||
|
|
4731
|
-
Date.now() - connectedAt >= STREAM_HEALTHY_CONNECTION_MS
|
|
4732
|
-
) {
|
|
4733
|
-
reconnectAttempt = 0;
|
|
4734
|
-
}
|
|
4735
|
-
const delayMs = streamReconnectDelayMs(reconnectAttempt);
|
|
4736
|
-
reconnectAttempt += 1;
|
|
4737
|
-
options?.onReconnect?.({
|
|
4738
|
-
attempt: reconnectAttempt,
|
|
4739
|
-
delayMs,
|
|
4740
|
-
reason: endedReason,
|
|
4741
|
-
});
|
|
4742
|
-
await sleep(delayMs);
|
|
4743
|
-
}
|
|
4744
|
-
}
|
|
4745
|
-
|
|
4746
4595
|
/** Get the exact original input retained for a run. This is intentionally separate from status. */
|
|
4747
4596
|
async getRunInput(runId: string): Promise<{
|
|
4748
4597
|
runId: string;
|
|
@@ -5816,10 +5665,10 @@ export class DeeplineClient {
|
|
|
5816
5665
|
// ——————————————————————————————————————————————————————————
|
|
5817
5666
|
|
|
5818
5667
|
/**
|
|
5819
|
-
* Run a play end-to-end: submit,
|
|
5668
|
+
* Run a play end-to-end: submit, poll progress until terminal, return result.
|
|
5820
5669
|
*
|
|
5821
5670
|
* This is the highest-level play execution method. It submits the play,
|
|
5822
|
-
* reads the canonical run
|
|
5671
|
+
* reads the canonical run snapshot for status updates, and returns a structured
|
|
5823
5672
|
* result with logs and timing. Supports cancellation via `AbortSignal`.
|
|
5824
5673
|
*
|
|
5825
5674
|
* @param code - Source string fallback; pass the bundled artifact in `options.artifact`
|
|
@@ -5896,6 +5745,7 @@ export class DeeplineClient {
|
|
|
5896
5745
|
lastLogSeq: 0,
|
|
5897
5746
|
latest: null,
|
|
5898
5747
|
};
|
|
5748
|
+
let terminalObserved: PlayStatus | null = null;
|
|
5899
5749
|
|
|
5900
5750
|
if (options?.signal?.aborted) {
|
|
5901
5751
|
await this.cancelPlay(workflowId);
|
|
@@ -5908,8 +5758,7 @@ export class DeeplineClient {
|
|
|
5908
5758
|
};
|
|
5909
5759
|
}
|
|
5910
5760
|
|
|
5911
|
-
for await (const event of this.
|
|
5912
|
-
mode: 'cli',
|
|
5761
|
+
for await (const event of this.watchRunEvents(workflowId, {
|
|
5913
5762
|
signal: options?.signal,
|
|
5914
5763
|
})) {
|
|
5915
5764
|
if (options?.signal?.aborted) {
|
|
@@ -5923,22 +5772,42 @@ export class DeeplineClient {
|
|
|
5923
5772
|
};
|
|
5924
5773
|
}
|
|
5925
5774
|
|
|
5775
|
+
const previousStatus = state.latest?.status;
|
|
5926
5776
|
const status = updatePlayLiveStatusState(state, event);
|
|
5927
5777
|
if (!status) {
|
|
5928
5778
|
continue;
|
|
5929
5779
|
}
|
|
5930
|
-
|
|
5780
|
+
if (
|
|
5781
|
+
event.type !== 'play.run.status' ||
|
|
5782
|
+
status.status !== previousStatus
|
|
5783
|
+
) {
|
|
5784
|
+
options?.onProgress?.(status);
|
|
5785
|
+
}
|
|
5931
5786
|
|
|
5932
5787
|
if (TERMINAL_PLAY_STATUSES.has(status.status)) {
|
|
5933
|
-
|
|
5934
|
-
status.runId || workflowId,
|
|
5935
|
-
).catch(() => status);
|
|
5936
|
-
return playRunResultFromStatus(finalStatus, start, workflowId);
|
|
5788
|
+
terminalObserved = status;
|
|
5937
5789
|
}
|
|
5938
5790
|
}
|
|
5939
5791
|
|
|
5792
|
+
if (options?.signal?.aborted) {
|
|
5793
|
+
await this.cancelPlay(workflowId);
|
|
5794
|
+
return {
|
|
5795
|
+
success: false,
|
|
5796
|
+
runId: workflowId,
|
|
5797
|
+
logs: state.logs,
|
|
5798
|
+
durationMs: Date.now() - start,
|
|
5799
|
+
error: 'Cancelled by user',
|
|
5800
|
+
};
|
|
5801
|
+
}
|
|
5802
|
+
if (terminalObserved) {
|
|
5803
|
+
const finalStatus = await this.getPlayStatus(
|
|
5804
|
+
terminalObserved.runId || workflowId,
|
|
5805
|
+
).catch(() => terminalObserved!);
|
|
5806
|
+
return playRunResultFromStatus(finalStatus, start, workflowId);
|
|
5807
|
+
}
|
|
5808
|
+
|
|
5940
5809
|
throw new DeeplineError(
|
|
5941
|
-
`Run
|
|
5810
|
+
`Run watch for ${workflowId} ended before the run reached a terminal state.`,
|
|
5942
5811
|
undefined,
|
|
5943
5812
|
'PLAY_RUN_STREAM_ENDED',
|
|
5944
5813
|
{ runId: workflowId, workflowId },
|
|
@@ -992,7 +992,7 @@ function isRetryableApiErrorResponse(status: number): boolean {
|
|
|
992
992
|
* to CLI users — it lands verbatim in `DeeplineError.message`/`details`. When
|
|
993
993
|
* detected, we synthesize a structured message (status + extracted <title>) and
|
|
994
994
|
* preserve the literal token `Worker threw exception` when present, since SDK
|
|
995
|
-
* retry classifiers (`
|
|
995
|
+
* retry classifiers (`isTransientPlayRunReadError`) regex-match on it.
|
|
996
996
|
*/
|
|
997
997
|
function detectHtmlErrorBody(
|
|
998
998
|
body: string,
|
|
@@ -200,7 +200,7 @@ export const SDK_RELEASE = {
|
|
|
200
200
|
// getters keep their established compatibility behavior.
|
|
201
201
|
// 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
|
|
202
202
|
// 0.3.90 is the first deliberately versioned SDK release for API v3.
|
|
203
|
-
version: '0.3.
|
|
203
|
+
version: '0.3.238',
|
|
204
204
|
updateSummary:
|
|
205
205
|
'Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.',
|
|
206
206
|
packageCapabilities: {
|