deepline 0.1.211 → 0.1.212

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +150 -11
  2. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/customer-console.ts +23 -0
  3. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +1 -1
  4. package/dist/bundling-sources/sdk/src/client.ts +118 -3
  5. package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +1 -1
  6. package/dist/bundling-sources/sdk/src/release.ts +2 -2
  7. package/dist/bundling-sources/sdk/src/types.ts +47 -0
  8. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +253 -30
  9. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +22 -20
  10. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +12 -0
  11. package/dist/bundling-sources/shared_libs/play-runtime/dataset-id.ts +10 -4
  12. package/dist/bundling-sources/shared_libs/play-runtime/db-session.ts +9 -0
  13. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +3 -5
  14. package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +14 -2
  15. package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +4 -2
  16. package/dist/bundling-sources/shared_libs/play-runtime/receipt-sql.ts +21 -1
  17. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +128 -6
  18. package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +7 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +37 -5
  20. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +19 -10
  21. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +63 -7
  22. package/dist/bundling-sources/shared_libs/play-runtime/secret-capability.ts +18 -4
  23. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +29 -9
  24. package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-state-machine.ts +22 -1
  25. package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +79 -1
  26. package/dist/cli/index.js +637 -229
  27. package/dist/cli/index.mjs +637 -229
  28. package/dist/index.d.mts +76 -6
  29. package/dist/index.d.ts +76 -6
  30. package/dist/index.js +225 -43
  31. package/dist/index.mjs +225 -43
  32. package/dist/plays/bundle-play-file.mjs +65 -4
  33. package/package.json +1 -1
@@ -68,6 +68,7 @@ import {
68
68
  } from './child-play-await';
69
69
  import { submitChildPlayThroughCoordinator } from './child-play-submit';
70
70
  import { isRetryableWorkerRuntimeApiError } from './runtime-api-retry';
71
+ import { formatCustomerConsoleValue } from './runtime/customer-console';
71
72
  import {
72
73
  attachToolResultListDataset,
73
74
  createToolExecuteResult,
@@ -116,6 +117,7 @@ import {
116
117
  isQueryResultDatasetTool,
117
118
  } from '../../../shared_libs/play-runtime/query-result-dataset';
118
119
  import { DEDUPE_DUPLICATE_KEY_SAMPLE_CAP } from '../../../shared_libs/play-runtime/map-row-identity';
120
+ import { createRuntimeDatasetId } from '../../../shared_libs/play-runtime/dataset-id';
119
121
  import {
120
122
  getTopLevelPipelineSubsteps,
121
123
  getCompiledPipelineSubsteps,
@@ -300,7 +302,7 @@ import type {
300
302
  LiveNodeProgressMap,
301
303
  LiveNodeProgressSnapshot,
302
304
  } from './runtime/live-progress';
303
- import { extractErrorBilling } from './runtime/tool-http-errors';
305
+ import { extractErrorBilling, ToolHttpError } from './runtime/tool-http-errors';
304
306
  import {
305
307
  WorkflowAbortError,
306
308
  isAbortLikeError,
@@ -324,6 +326,55 @@ import type { PlayRunInputPayload } from '../../../shared_libs/play-runtime/sche
324
326
  // @ts-ignore - virtual module substituted by esbuild plugin at bundle time.
325
327
  import playFn from 'deepline-play-entry';
326
328
 
329
+ const customerConsoleStorage = new AsyncLocalStorage<
330
+ (message: string) => void
331
+ >();
332
+
333
+ type CustomerConsoleGlobal = typeof globalThis & {
334
+ __deeplineCaptureCustomerConsole?: (
335
+ method: string,
336
+ args: unknown[],
337
+ ) => unknown;
338
+ };
339
+
340
+ (globalThis as CustomerConsoleGlobal).__deeplineCaptureCustomerConsole = (
341
+ method,
342
+ args,
343
+ ) => {
344
+ const log = customerConsoleStorage.getStore();
345
+ if (log) {
346
+ assertNoSecretTaint(args, `console.${method}`);
347
+ const message = args.map(formatCustomerConsoleValue).join(' ');
348
+ log(`[console.${method}] ${message}`.slice(0, 2_000));
349
+ }
350
+ };
351
+
352
+ function runCustomerPlay(
353
+ ctx: unknown,
354
+ input: PlayRunInputPayload,
355
+ ): Promise<unknown> {
356
+ return customerConsoleStorage.run(
357
+ (ctx as { log(message: string): void }).log,
358
+ () => (playFn as typeof runCustomerPlay)(ctx, input),
359
+ );
360
+ }
361
+
362
+ function runtimeDatasetIdentity(playName: string, tableNamespace: string) {
363
+ return {
364
+ datasetId: createRuntimeDatasetId(playName, tableNamespace),
365
+ path: `datasets.${tableNamespace}`,
366
+ tableNamespace,
367
+ };
368
+ }
369
+
370
+ function datasetAvailableLog(
371
+ runId: string,
372
+ dataset: ReturnType<typeof runtimeDatasetIdentity>,
373
+ count: number,
374
+ ): string {
375
+ return `Dataset ${dataset.path} available: ${count} persisted rows; export: deepline runs export ${runId} --dataset ${dataset.path} --out ${dataset.tableNamespace}.csv`;
376
+ }
377
+
327
378
  type RunRequest = {
328
379
  runId: string;
329
380
  callbackUrl: string;
@@ -999,6 +1050,17 @@ type WorkerCtxCallbacks = {
999
1050
  }) => void | Promise<void>;
1000
1051
  onMapStarted?: (nodeId: string, at?: number) => void;
1001
1052
  onMapCompleted?: (nodeId: string, at?: number) => void;
1053
+ onDatasetLifecycle?: (event: {
1054
+ datasetId: string;
1055
+ path: string;
1056
+ tableNamespace: string;
1057
+ phase: 'registered' | 'available' | 'failed';
1058
+ persistedRows?: number;
1059
+ succeededRows?: number;
1060
+ failedRows?: number;
1061
+ complete?: boolean;
1062
+ at: number;
1063
+ }) => void | Promise<void>;
1002
1064
  onToolCalled?: (toolId: string, at?: number) => void;
1003
1065
  onToolFailed?: (toolId: string, at?: number) => void;
1004
1066
  };
@@ -2073,6 +2135,7 @@ async function callToolDirect(
2073
2135
  while (true) {
2074
2136
  requestAttempt += 1;
2075
2137
  let res: Response;
2138
+ const providerCallStartedAt = nowMs();
2076
2139
  try {
2077
2140
  const runtimeApiTimeout = resolveToolRuntimeApiTimeout({
2078
2141
  requestInput: input,
@@ -2161,8 +2224,11 @@ async function callToolDirect(
2161
2224
  } catch (error) {
2162
2225
  transportAttempt += 1;
2163
2226
  const message = error instanceof Error ? error.message : String(error);
2164
- lastError = new Error(
2227
+ lastError = new ToolHttpError(
2165
2228
  `Tool ${toolId} transport failed calling ${path} for run ${req.runId} on attempt ${transportAttempt}/${TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS}: ${message}`,
2229
+ null,
2230
+ 0,
2231
+ 'repairable',
2166
2232
  );
2167
2233
  if (
2168
2234
  transportAttempt >= TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS ||
@@ -2212,6 +2278,7 @@ async function callToolDirect(
2212
2278
  bodyText: text,
2213
2279
  retryAfterHeader: res.headers.get('retry-after'),
2214
2280
  transientHttpRetrySafe,
2281
+ providerLatencyMs: nowMs() - providerCallStartedAt,
2215
2282
  });
2216
2283
  lastError = failure.error;
2217
2284
  if (failure.backpressureDelayMs !== null) {
@@ -4865,6 +4932,7 @@ function createMinimalWorkerCtx(
4865
4932
  };
4866
4933
 
4867
4934
  let totalRowsWritten = 0;
4935
+ let datasetLifecycleRegistered = false;
4868
4936
 
4869
4937
  const volatileWorkflowChunkRows = new Map<
4870
4938
  number,
@@ -4915,6 +4983,22 @@ function createMinimalWorkerCtx(
4915
4983
  budgetMeter: workBudgetMeter,
4916
4984
  force: !!req.force,
4917
4985
  });
4986
+ if (!datasetLifecycleRegistered) {
4987
+ datasetLifecycleRegistered = true;
4988
+ const dataset = runtimeDatasetIdentity(req.playName, name);
4989
+ await callbacks?.onDatasetLifecycle?.({
4990
+ ...dataset,
4991
+ phase: 'registered',
4992
+ complete: false,
4993
+ at: nowMs(),
4994
+ });
4995
+ emitEvent({
4996
+ type: 'log',
4997
+ level: 'info',
4998
+ message: `Dataset ${dataset.path} registered`,
4999
+ ts: nowMs(),
5000
+ });
5001
+ }
4918
5002
  const activeRowAttempt = {
4919
5003
  attemptId: prepared.attemptId ?? rowAttempt.attemptId,
4920
5004
  attemptOwnerRunId:
@@ -6261,6 +6345,23 @@ function createMinimalWorkerCtx(
6261
6345
  finalizedRowsWritten,
6262
6346
  ),
6263
6347
  });
6348
+ const dataset = runtimeDatasetIdentity(req.playName, name);
6349
+ const persistedRows = finalizedRowsWritten + totalRowsFailed;
6350
+ await callbacks?.onDatasetLifecycle?.({
6351
+ ...dataset,
6352
+ phase: 'available',
6353
+ persistedRows,
6354
+ succeededRows: finalizedRowsWritten,
6355
+ failedRows: totalRowsFailed,
6356
+ complete: true,
6357
+ at: completedAt,
6358
+ });
6359
+ emitEvent({
6360
+ type: 'log',
6361
+ level: totalRowsFailed > 0 ? 'warn' : 'info',
6362
+ message: datasetAvailableLog(req.runId, dataset, persistedRows),
6363
+ ts: completedAt,
6364
+ });
6264
6365
  callbacks?.onMapCompleted?.(mapNodeId, completedAt);
6265
6366
  emitEvent({
6266
6367
  type: 'log',
@@ -6534,7 +6635,8 @@ function createMinimalWorkerCtx(
6534
6635
  if (chunkResult.fatalError) {
6535
6636
  throw new Error(
6536
6637
  `ctx.dataset("${name}") stopped after a runtime persistence failure ` +
6537
- `outside the retryable chunk step. Provider calls already executed for ` +
6638
+ `outside the retryable chunk step. Circuit breaker prevented queued row ` +
6639
+ `dispatch. Provider calls already executed for ` +
6538
6640
  `${chunkResult.rowsExecuted} row(s), so the chunk was not retried. ` +
6539
6641
  `Fix the runtime persistence cause and re-run to resume. ` +
6540
6642
  `First error: ${chunkResult.fatalError}`,
@@ -7607,9 +7709,7 @@ async function handleRun(request: Request, env: WorkerEnv): Promise<Response> {
7607
7709
  captureHarnessBinding(env);
7608
7710
  await probeHarnessOnce(env, runPrefix);
7609
7711
  const ctx = createMinimalWorkerCtx(req, emit, env);
7610
- const result = await (
7611
- playFn as (ctx: unknown, input: PlayRunInputPayload) => Promise<unknown>
7612
- )(ctx, req.runtimeInput);
7712
+ const result = await runCustomerPlay(ctx, req.runtimeInput);
7613
7713
  emit({
7614
7714
  type: 'result',
7615
7715
  result,
@@ -8023,6 +8123,17 @@ async function executeRunRequest(
8023
8123
  },
8024
8124
  onMapStarted: (nodeId, at) => stepLifecycle?.onMapStarted(nodeId, at),
8025
8125
  onMapCompleted: (nodeId, at) => stepLifecycle?.onMapCompleted(nodeId, at),
8126
+ onDatasetLifecycle: async (event) => {
8127
+ const { at, ...dataset } = event;
8128
+ pendingLedgerEvents.push({
8129
+ ...dataset,
8130
+ type: 'dataset.lifecycle',
8131
+ runId: req.runId,
8132
+ source: 'worker',
8133
+ occurredAt: at,
8134
+ });
8135
+ await flushLedgerEvents(event.complete === true);
8136
+ },
8026
8137
  onToolCalled: (toolId, at) => stepLifecycle?.onToolCalled(toolId, at),
8027
8138
  onToolFailed: (toolId, at) => stepLifecycle?.onToolFailed(toolId, at),
8028
8139
  };
@@ -8089,9 +8200,7 @@ async function executeRunRequest(
8089
8200
  : null;
8090
8201
  try {
8091
8202
  const playStartedAt = nowMs();
8092
- const result = await (
8093
- playFn as (ctx: unknown, input: PlayRunInputPayload) => Promise<unknown>
8094
- )(ctx, req.runtimeInput);
8203
+ const result = await runCustomerPlay(ctx, req.runtimeInput);
8095
8204
  recordRunnerPerfTrace({
8096
8205
  req,
8097
8206
  phase: 'runner.play_function',
@@ -8135,7 +8244,33 @@ async function executeRunRequest(
8135
8244
  ms: nowMs() - ledgerFlushWaitStartedAt,
8136
8245
  });
8137
8246
  const resultDatasetStartedAt = nowMs();
8138
- await persistResultDatasets(req, promoted.handles, serializedResult);
8247
+ const persistedResultCounts = await persistResultDatasets(
8248
+ req,
8249
+ promoted.handles,
8250
+ serializedResult,
8251
+ );
8252
+ for (const dataset of promoted.handles) {
8253
+ if (dataset.datasetKind === 'map') continue;
8254
+ const identity = runtimeDatasetIdentity(
8255
+ req.playName,
8256
+ dataset.tableNamespace,
8257
+ );
8258
+ const count = persistedResultCounts.get(dataset.tableNamespace) ?? 0;
8259
+ pendingLedgerEvents.push({
8260
+ ...identity,
8261
+ type: 'dataset.lifecycle',
8262
+ runId: req.runId,
8263
+ source: 'worker',
8264
+ occurredAt: nowMs(),
8265
+ phase: 'available',
8266
+ persistedRows: count,
8267
+ succeededRows: count,
8268
+ failedRows: 0,
8269
+ complete: true,
8270
+ });
8271
+ appendRunLogLine(datasetAvailableLog(req.runId, identity, count));
8272
+ }
8273
+ await flushLedgerEvents(true);
8139
8274
  resultDatasetsPersisted = true;
8140
8275
  recordRunnerPerfTrace({
8141
8276
  req,
@@ -8599,8 +8734,9 @@ async function persistResultDatasets(
8599
8734
  req: RunRequest,
8600
8735
  resultDatasets: ProjectedResultDatasetHandle[],
8601
8736
  serializedResult: unknown,
8602
- ): Promise<void> {
8737
+ ): Promise<Map<string, number>> {
8603
8738
  const persistedNamespaces = new Set<string>();
8739
+ const persistedCounts = new Map<string, number>();
8604
8740
  for (const dataset of resultDatasets) {
8605
8741
  if (dataset.datasetKind === 'map') continue;
8606
8742
  const handle = dataset.handle as WorkerDatasetHandle<
@@ -8639,6 +8775,7 @@ async function persistResultDatasets(
8639
8775
  inputOffset += chunk.length;
8640
8776
  }
8641
8777
  persistedNamespaces.add(dataset.tableNamespace);
8778
+ persistedCounts.set(dataset.tableNamespace, inputOffset);
8642
8779
  }
8643
8780
 
8644
8781
  const datasets = collectDatasetEnvelopes(serializedResult);
@@ -8672,7 +8809,9 @@ async function persistResultDatasets(
8672
8809
  attemptLeaseTtlMs: runtimeSheetAttemptLeaseTtlMs(req),
8673
8810
  userEmail: req.userEmail,
8674
8811
  });
8812
+ persistedCounts.set(dataset.tableNamespace, dataset.rows.length);
8675
8813
  }
8814
+ return persistedCounts;
8676
8815
  }
8677
8816
 
8678
8817
  const RESULT_DATASET_PERSIST_CHUNK_ROWS = 5_000;
@@ -0,0 +1,23 @@
1
+ export function formatCustomerConsoleValue(value: unknown): string {
2
+ if (typeof value === 'string') return value;
3
+ try {
4
+ if (value instanceof Error) return value.stack ?? value.message;
5
+ if (value && typeof value === 'object') {
6
+ const seen = new WeakSet<object>();
7
+ const serialized = JSON.stringify(value, (_key, nested) => {
8
+ if (typeof nested === 'symbol' || typeof nested === 'bigint') {
9
+ return nested.toString();
10
+ }
11
+ if (nested && typeof nested === 'object') {
12
+ if (seen.has(nested)) return '[Circular]';
13
+ seen.add(nested);
14
+ }
15
+ return nested;
16
+ });
17
+ if (serialized !== undefined) return serialized;
18
+ }
19
+ return String(value);
20
+ } catch {
21
+ return '[Unprintable]';
22
+ }
23
+ }
@@ -229,7 +229,7 @@ type WorkerToolCompletionResult =
229
229
  const WORKER_TOOL_BATCH_GRACE_MS = 250;
230
230
  const COMPLETE_RECEIPT_RETRY_BACKOFFS_MS = [1_000, 3_000, 9_000] as const;
231
231
  const COMPLETE_RECEIPT_RETRY_MIN_HEADROOM_MS = 10_000;
232
- export const WORKER_TOOL_COMPLETE_RECEIPTS_MAX_BATCH_BYTES = 8 * 1024 * 1024;
232
+ export const WORKER_TOOL_COMPLETE_RECEIPTS_MAX_BATCH_BYTES = 32 * 1024 * 1024;
233
233
  export const WORKER_TOOL_COMPLETE_RECEIPTS_MAX_BATCH_MEMBERS = 500;
234
234
  export const WORKER_TOOL_RECEIPT_RECOVERY_MAX_CONCURRENT_READS = 25;
235
235
 
@@ -282,9 +282,23 @@ export type RunsListOptions = {
282
282
  limit?: number;
283
283
  };
284
284
 
285
+ /** Options for `client.runs.get(...)`. */
286
+ export type RunsGetOptions = {
287
+ /** Return the raw status payload instead of the compact package. */
288
+ full?: boolean;
289
+ /** Attach a bounded end-of-stream window for a failed run. */
290
+ failedLogs?: boolean;
291
+ /** Requested failed-log lines to attach (default 20, hard-capped at 20). */
292
+ failedLogLimit?: number;
293
+ };
294
+
295
+ const RUNS_FAILED_LOG_LIMIT = 20;
296
+
285
297
  /** Streaming options for `client.runs.tail(...)`. */
286
298
  export type RunsTailOptions = {
287
299
  signal?: AbortSignal;
300
+ /** Observe each canonical live event while `tail` waits for terminal state. */
301
+ onEvent?: (event: PlayLiveEvent) => void;
288
302
  /**
289
303
  * Called before each stream reconnect. Server stream windows are finite, so
290
304
  * long runs reconnect with backoff until a terminal status is observed.
@@ -308,6 +322,8 @@ export type RunsLogsOptions = {
308
322
  limit?: number;
309
323
  /** Fetch every stored log line, paginating to the full totalCount. */
310
324
  all?: boolean;
325
+ /** Select the bounded failure view; truncated runs degrade explicitly. */
326
+ failed?: boolean;
311
327
  };
312
328
 
313
329
  /** Persisted log response for one play run. */
@@ -320,6 +336,14 @@ export type RunsLogsResult = {
320
336
  truncated: boolean;
321
337
  hasMore: boolean;
322
338
  entries: string[];
339
+ /** Selected public log view. */
340
+ view?: 'tail' | 'failed' | 'all';
341
+ /** Whether entries are terminal context or the last pre-truncation lines. */
342
+ association?: 'terminal_failure_window' | 'retained_before_truncation';
343
+ /** Loud explanation when the terminal failure window was not retained. */
344
+ warning?: string;
345
+ /** Exact follow-up command when the selected view is degraded. */
346
+ next?: { logs: string };
323
347
  /**
324
348
  * True when the run crossed the Run Log Stream retention cap: `totalCount`
325
349
  * keeps counting, but stored line bodies end at a loud truncation marker.
@@ -342,6 +366,10 @@ type RunLogsPageResponse = {
342
366
  lastSeq: number | null;
343
367
  hasMore: boolean;
344
368
  nextAfterSeq: number | null;
369
+ view?: 'failed';
370
+ association?: 'terminal_failure_window' | 'retained_before_truncation';
371
+ warning?: string;
372
+ next?: { logs: string };
345
373
  };
346
374
 
347
375
  /** One persisted runtime-sheet row returned by `client.runs.exportDatasetRows(...)`. */
@@ -395,7 +423,7 @@ export type PlaySecretMetadata = {
395
423
  */
396
424
  export type RunsNamespace = {
397
425
  /** Get current run status by public run id. */
398
- get: (runId: string, options?: { full?: boolean }) => Promise<PlayStatus>;
426
+ get: (runId: string, options?: RunsGetOptions) => Promise<PlayStatus>;
399
427
  /** List runs for one play, optionally filtered by status. */
400
428
  list: (options: RunsListOptions) => Promise<PlayRunListItem[]>;
401
429
  /** Stream run events and return the latest/terminal run status. */
@@ -2469,7 +2497,7 @@ export class DeeplineClient {
2469
2497
  */
2470
2498
  async getRunStatus(
2471
2499
  runId: string,
2472
- options?: { full?: boolean },
2500
+ options?: RunsGetOptions,
2473
2501
  ): Promise<PlayStatus> {
2474
2502
  const params = new URLSearchParams();
2475
2503
  if (options?.full === true) {
@@ -2479,7 +2507,55 @@ export class DeeplineClient {
2479
2507
  const response = await this.http.get<Record<string, unknown>>(
2480
2508
  `/api/v2/runs/${encodeURIComponent(runId)}${query}`,
2481
2509
  );
2482
- return normalizePlayStatus(response);
2510
+ const status = normalizePlayStatus(response);
2511
+ if (options?.failedLogs !== true || status.status !== 'failed') {
2512
+ return status;
2513
+ }
2514
+ const requestedFailedLogLimit =
2515
+ typeof options.failedLogLimit === 'number' &&
2516
+ Number.isFinite(options.failedLogLimit)
2517
+ ? Math.max(1, Math.floor(options.failedLogLimit))
2518
+ : RUNS_FAILED_LOG_LIMIT;
2519
+ let failedLogs: RunsLogsResult;
2520
+ let failedLogsLoaded = true;
2521
+ try {
2522
+ failedLogs = await this.getRunLogs(runId, {
2523
+ failed: true,
2524
+ limit: Math.min(RUNS_FAILED_LOG_LIMIT, requestedFailedLogLimit),
2525
+ });
2526
+ } catch (error) {
2527
+ failedLogsLoaded = false;
2528
+ const retryCommand = `deepline runs get ${runId} --log-failed --json`;
2529
+ const reason =
2530
+ error instanceof Error && error.message.trim()
2531
+ ? ` (${error.message.trim().slice(0, 500)})`
2532
+ : '';
2533
+ failedLogs = {
2534
+ runId,
2535
+ totalCount: 0,
2536
+ returnedCount: 0,
2537
+ firstSequence: null,
2538
+ lastSequence: null,
2539
+ truncated: false,
2540
+ hasMore: false,
2541
+ entries: [],
2542
+ view: 'failed',
2543
+ warning: `Failed logs could not be loaded${reason}. The run status and persisted datasets are still available.`,
2544
+ next: { logs: retryCommand },
2545
+ };
2546
+ }
2547
+ return {
2548
+ ...status,
2549
+ failedLogs: {
2550
+ ...failedLogs,
2551
+ view: 'failed' as const,
2552
+ ...(failedLogsLoaded
2553
+ ? {
2554
+ association: failedLogs.association ?? 'terminal_failure_window',
2555
+ }
2556
+ : {}),
2557
+ },
2558
+ };
2483
2559
  }
2484
2560
 
2485
2561
  /**
@@ -2657,6 +2733,7 @@ export class DeeplineClient {
2657
2733
  onNotice: options?.onNotice,
2658
2734
  fallback: 'none',
2659
2735
  })) {
2736
+ options?.onEvent?.(event);
2660
2737
  const status = updatePlayLiveStatusState(state, event);
2661
2738
  if (!status || !TERMINAL_PLAY_STATUSES.has(status.status)) {
2662
2739
  continue;
@@ -2725,6 +2802,7 @@ export class DeeplineClient {
2725
2802
  mode: 'cli',
2726
2803
  signal: options?.signal,
2727
2804
  })) {
2805
+ options?.onEvent?.(event);
2728
2806
  sawEvent = true;
2729
2807
  const status = updatePlayLiveStatusState(state, event);
2730
2808
  if (!status || !TERMINAL_PLAY_STATUSES.has(status.status)) {
@@ -2786,6 +2864,42 @@ export class DeeplineClient {
2786
2864
  runId: string,
2787
2865
  options?: RunsLogsOptions,
2788
2866
  ): Promise<RunsLogsResult> {
2867
+ if (options?.all === true && options.failed === true) {
2868
+ throw new DeeplineError(
2869
+ 'runs.logs cannot combine all and failed views.',
2870
+ undefined,
2871
+ 'INVALID_RUN_LOG_VIEW',
2872
+ );
2873
+ }
2874
+ if (options?.failed === true) {
2875
+ const requestedLimit =
2876
+ typeof options.limit === 'number' &&
2877
+ Number.isFinite(options.limit) &&
2878
+ options.limit > 0
2879
+ ? Math.trunc(options.limit)
2880
+ : RUNS_FAILED_LOG_LIMIT;
2881
+ const failedLimit = Math.min(RUNS_FAILED_LOG_LIMIT, requestedLimit);
2882
+ const page = await this.http.get<RunLogsPageResponse>(
2883
+ `/api/v2/runs/${encodeURIComponent(runId)}/logs?view=failed&limit=${failedLimit}`,
2884
+ );
2885
+ return {
2886
+ runId: page.runId,
2887
+ totalCount: page.totalLogCount,
2888
+ returnedCount: page.entries.length,
2889
+ firstSequence: page.firstSeq,
2890
+ lastSequence: page.lastSeq,
2891
+ // The failed view is a complete designed window, not a pageable slice.
2892
+ // `truncated` means retention loss here; association/warning explain it.
2893
+ truncated: page.logsTruncated === true,
2894
+ hasMore: false,
2895
+ entries: page.entries.map((entry) => entry.line),
2896
+ view: page.view ?? 'failed',
2897
+ association: page.association ?? 'terminal_failure_window',
2898
+ ...(page.warning ? { warning: page.warning } : {}),
2899
+ ...(page.next ? { next: page.next } : {}),
2900
+ ...(page.logsTruncated ? { logsTruncated: true } : {}),
2901
+ };
2902
+ }
2789
2903
  const limit = options?.all
2790
2904
  ? Number.MAX_SAFE_INTEGER
2791
2905
  : typeof options?.limit === 'number' &&
@@ -2830,6 +2944,7 @@ export class DeeplineClient {
2830
2944
  truncated: entries.length < probe.totalLogCount,
2831
2945
  hasMore: lastSequence !== null && lastSequence < lastStoredSeq,
2832
2946
  entries: entries.map((entry) => entry.line),
2947
+ view: options?.all ? 'all' : 'tail',
2833
2948
  ...(probe.logsTruncated ? { logsTruncated: true } : {}),
2834
2949
  };
2835
2950
  }
@@ -39,7 +39,7 @@ export type {
39
39
 
40
40
  export { extractDefinedPlayName } from '../../../shared_libs/plays/bundling/index.js';
41
41
 
42
- const PLAY_BUNDLE_CACHE_VERSION = 30;
42
+ const PLAY_BUNDLE_CACHE_VERSION = 31;
43
43
  const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
44
44
  const SDK_PACKAGE_ROOT = resolve(MODULE_DIR, '..', '..');
45
45
  const SOURCE_REPO_ROOT = resolve(SDK_PACKAGE_ROOT, '..');
@@ -106,10 +106,10 @@ export const SDK_RELEASE = {
106
106
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
107
107
  // fields shipped in 0.1.153.
108
108
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
109
- version: '0.1.211',
109
+ version: '0.1.212',
110
110
  apiContract: '2026-06-dataset-handle-results-hard-cutover',
111
111
  supportPolicy: {
112
- latest: '0.1.211',
112
+ latest: '0.1.212',
113
113
  minimumSupported: '0.1.53',
114
114
  deprecatedBelow: '0.1.53',
115
115
  commandMinimumSupported: [
@@ -463,6 +463,14 @@ export type PlayRunActionPackage =
463
463
  runId: string;
464
464
  datasetPath: string;
465
465
  format: 'csv';
466
+ }
467
+ | {
468
+ kind: 'deepline_run_logs';
469
+ runId: string;
470
+ view: 'failed' | 'tail';
471
+ limit: number;
472
+ command: string;
473
+ api: { method: 'GET'; path: string };
466
474
  };
467
475
 
468
476
  /**
@@ -493,11 +501,30 @@ export interface PlayRunPackage {
493
501
  steps: Array<Record<string, unknown>>;
494
502
  /** Named output summaries, including dataset handles and scalar outputs. */
495
503
  outputs: Record<string, Record<string, unknown>>;
504
+ /** Every durable Dataset Handle explicitly registered by this run. */
505
+ datasets?: Array<{
506
+ kind: 'dataset';
507
+ datasetId?: string;
508
+ path: string;
509
+ tableNamespace?: string;
510
+ rowCount?: number;
511
+ recovered?: true;
512
+ preview?: Record<string, unknown>;
513
+ actions?: Record<string, PlayRunActionPackage>;
514
+ }>;
515
+ /** Small retained tail of customer and runtime logs; fetch the full stream through `runs.logs`. */
516
+ logs?: {
517
+ tail: string[];
518
+ totalCount: number;
519
+ returnedCount: number;
520
+ truncated?: boolean;
521
+ };
496
522
  /** Follow-up actions a caller can perform against the run. */
497
523
  next?: {
498
524
  inspect?: PlayRunActionPackage;
499
525
  export?: PlayRunActionPackage;
500
526
  query?: PlayRunActionPackage;
527
+ logs?: PlayRunActionPackage;
501
528
  };
502
529
  }
503
530
 
@@ -524,6 +551,8 @@ export interface PlayStatus {
524
551
  apiVersion?: number;
525
552
  /** Saved play name for this run, when available. */
526
553
  name?: string;
554
+ /** Exact saved revision launched for this run, when applicable. */
555
+ revisionId?: string;
527
556
  /** Alias for `name` used by run/result APIs. */
528
557
  playName?: string;
529
558
  /** Dashboard URL for inspecting the play and its run output in the app. */
@@ -564,6 +593,24 @@ export interface PlayStatus {
564
593
  } | null;
565
594
  /** Structured follow-up actions for inspect/query/export. */
566
595
  next?: PlayRunPackage['next'] | Record<string, unknown>;
596
+ /** Bounded terminal-failure log window requested by `runs.get`. */
597
+ failedLogs?: {
598
+ runId: string;
599
+ totalCount: number;
600
+ returnedCount: number;
601
+ firstSequence: number | null;
602
+ lastSequence: number | null;
603
+ truncated: boolean;
604
+ hasMore: boolean;
605
+ entries: string[];
606
+ view?: 'failed';
607
+ association?: 'terminal_failure_window' | 'retained_before_truncation';
608
+ warning?: string;
609
+ next?: { logs: string };
610
+ logsTruncated?: boolean;
611
+ };
612
+ /** Exact ordinary `plays run` command that can rerun a failed execution. */
613
+ rerunCommand?: string;
567
614
  }
568
615
 
569
616
  export type LiveEventScope = 'play' | 'agent';