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.
@@ -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
- /** Streaming options for `client.runs.tail(...)`. */
631
- export type RunsTailOptions = {
622
+ /** Options for `client.runs.watch(...)`. */
623
+ export type RunsWatchOptions = {
632
624
  signal?: AbortSignal;
633
- /** Observe each canonical live event while `tail` waits for terminal state. */
625
+ /** Observe each canonical event while `watch` waits for terminal state. */
634
626
  onEvent?: (event: PlayLiveEvent) => void;
635
627
  /**
636
- * Called before each stream reconnect. Server stream windows are finite, so
637
- * long runs reconnect with backoff until a terminal status is observed.
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 transport notices: subscription-transport reconnects,
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 poll, stream, stop, read logs, and export durable dataset rows.
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
- /** Stream run events and return the latest/terminal run status. */
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
- * Start a play run and stream live runtime events from the same request.
3396
+ * Submit a play run through the legacy event response.
3389
3397
  *
3390
- * Use this when a caller wants low-level event handling instead of submitting
3391
- * first and then connecting to `streamPlayRunEvents(runId)`.
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 streaming options.
3395
- * @param options.signal - Optional abort signal for the streaming request.
3396
- * @returns Async stream of play-scoped live events.
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
- * Stream semantic play-run events using the same SSE feed as the dashboard.
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
- * The server emits a canonical `play.run.snapshot` event first for every
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, stream until terminal, return result.
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 stream for status updates, and returns a structured
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.streamPlayRunEvents(workflowId, {
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
- options?.onProgress?.(status);
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
- const finalStatus = await this.getPlayStatus(
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 stream for ${workflowId} ended before the run reached a terminal state.`,
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 (`isTransientPlayStreamError`) regex-match on it.
995
+ * retry classifiers (`isTransientPlayRunReadError`) regex-match on it.
996
996
  */
997
997
  function detectHtmlErrorBody(
998
998
  body: string,
@@ -137,6 +137,7 @@ export type {
137
137
  RunsLogsOptions,
138
138
  RunsLogsResult,
139
139
  RunsNamespace,
140
+ RunsWatchOptions,
140
141
  RunsTailOptions,
141
142
  StopAllRunsOptions,
142
143
  ToolExecution,
@@ -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.236',
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: {