deepline 0.1.144 → 0.1.145

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.
@@ -640,6 +640,8 @@ type DynamicWorkflowMetadata = {
640
640
  const SUBMIT_INITIAL_STATE_MAX_WAIT_MS = 0;
641
641
  const SUBMIT_INITIAL_STATE_POLL_MS = 50;
642
642
  const WORKFLOW_RETRY_STATE_TTL_MS = 60 * 60 * 1000;
643
+ const COORDINATOR_WORKER_MEMORY_LIMIT_MESSAGE =
644
+ 'Worker memory limit hit before the run could finish. For CSV enrich, use --output or --in-place for automatic batches; otherwise rerun smaller --rows ranges.';
643
645
 
644
646
  function buildDynamicWorkflowMetadata(
645
647
  params: PlayWorkflowParams,
@@ -1052,6 +1054,27 @@ function readWorkflowPayload(event: unknown): Record<string, unknown> | null {
1052
1054
  return isRecord(payload.params) ? payload.params : payload;
1053
1055
  }
1054
1056
 
1057
+ function isCoordinatorWorkerMemoryLimitError(error: unknown): boolean {
1058
+ const message = error instanceof Error ? error.message : String(error);
1059
+ return message.toLowerCase().includes('worker exceeded memory limit');
1060
+ }
1061
+
1062
+ function normalizeCoordinatorWorkflowRuntimeFailure(
1063
+ error: unknown,
1064
+ ): ReturnType<typeof normalizePlayRunFailure> {
1065
+ const failure = normalizePlayRunFailure(error);
1066
+ if (!isCoordinatorWorkerMemoryLimitError(error)) {
1067
+ return failure;
1068
+ }
1069
+ return {
1070
+ ...failure,
1071
+ code: 'WORKER_MEMORY_LIMIT_EXCEEDED',
1072
+ message: COORDINATOR_WORKER_MEMORY_LIMIT_MESSAGE,
1073
+ retryable: false,
1074
+ cause: failure.message,
1075
+ };
1076
+ }
1077
+
1055
1078
  async function markWorkflowRuntimeFailure(input: {
1056
1079
  env: CoordinatorEnv;
1057
1080
  event: unknown;
@@ -1074,6 +1097,16 @@ async function markWorkflowRuntimeFailure(input: {
1074
1097
  input.error instanceof Error && typeof input.error.stack === 'string'
1075
1098
  ? input.error.stack.split('\n').slice(0, 12).join('\n')
1076
1099
  : null;
1100
+ const failure = normalizeCoordinatorWorkflowRuntimeFailure(input.error);
1101
+ const shouldPreserveRawError =
1102
+ failure.code === 'RUN_FAILED' ||
1103
+ (failure.cause !== undefined && failure.message === failure.cause);
1104
+ const normalizedError =
1105
+ shouldPreserveRawError
1106
+ ? `DynamicWorkflow runner failed: ${errorName}: ${errorMessage}${
1107
+ errorStack ? `\n${errorStack}` : ''
1108
+ }`
1109
+ : `DynamicWorkflow runner failed: ${failure.message}`;
1077
1110
  const headers = new Headers({
1078
1111
  authorization: `Bearer ${executorToken}`,
1079
1112
  'content-type': 'application/json',
@@ -1089,9 +1122,7 @@ async function markWorkflowRuntimeFailure(input: {
1089
1122
  runId,
1090
1123
  source: 'coordinator',
1091
1124
  occurredAt: Date.now(),
1092
- error: `DynamicWorkflow runner failed: ${errorName}: ${errorMessage}${
1093
- errorStack ? `\n${errorStack}` : ''
1094
- }`,
1125
+ error: normalizedError,
1095
1126
  } satisfies PlayRunLedgerEvent,
1096
1127
  ],
1097
1128
  });
@@ -3062,7 +3093,8 @@ export class DynamicWorkflow extends WorkflowEntrypoint<
3062
3093
  });
3063
3094
  return result;
3064
3095
  } catch (innerError) {
3065
- const failure = normalizePlayRunFailure(innerError);
3096
+ const failure =
3097
+ normalizeCoordinatorWorkflowRuntimeFailure(innerError);
3066
3098
  console.error(
3067
3099
  '[coordinator] DynamicWorkflow runner.run threw',
3068
3100
  {
@@ -43,7 +43,10 @@ import {
43
43
  type ChunkExecutionResult,
44
44
  } from '../../../shared_libs/play-runtime/batch-runtime';
45
45
  import { getPlayRuntimeBatchStrategy } from '../../../shared_libs/play-runtime/play-runtime-batching-registry';
46
- import { STANDARD_PLAY_RUNTIME_LIMIT_SECONDS } from '../../../shared_libs/temporal/constants';
46
+ import {
47
+ STANDARD_PLAY_RUNTIME_LIMIT_LABEL,
48
+ STANDARD_PLAY_RUNTIME_LIMIT_SECONDS,
49
+ } from '../../../shared_libs/temporal/constants';
47
50
  import {
48
51
  createPlayExecutionGovernor,
49
52
  type GovernanceSnapshot,
@@ -6341,7 +6344,7 @@ async function executeRunRequest(
6341
6344
  runtimeLimitExceeded = true;
6342
6345
  if (!abortSignal.aborted) {
6343
6346
  abortController.abort(
6344
- `Play runtime limit exceeded after ${STANDARD_PLAY_RUNTIME_LIMIT_SECONDS}s.`,
6347
+ `Based on this plan, max runtime is ${STANDARD_PLAY_RUNTIME_LIMIT_LABEL}. Use smaller batches; ask for runtime.`,
6345
6348
  );
6346
6349
  }
6347
6350
  }, STANDARD_PLAY_RUNTIME_LIMIT_SECONDS * 1000);
@@ -320,6 +320,7 @@ export type RunsNamespace = {
320
320
  runId?: string;
321
321
  limit?: number;
322
322
  offset?: number;
323
+ rowMode?: 'output' | 'all';
323
324
  }) => Promise<PlaySheetRowsResult>;
324
325
  /** Stop a running/waiting run. */
325
326
  stop: (
@@ -2434,6 +2435,7 @@ export class DeeplineClient {
2434
2435
  runId?: string;
2435
2436
  limit?: number;
2436
2437
  offset?: number;
2438
+ rowMode?: 'output' | 'all';
2437
2439
  }): Promise<PlaySheetRowsResult> {
2438
2440
  const params = new URLSearchParams({
2439
2441
  tableNamespace: input.tableNamespace,
@@ -2443,6 +2445,9 @@ export class DeeplineClient {
2443
2445
  if (input.runId?.trim()) {
2444
2446
  params.set('runId', input.runId.trim());
2445
2447
  }
2448
+ if (input.rowMode === 'all') {
2449
+ params.set('rowMode', 'all');
2450
+ }
2446
2451
  return await this.http.get<PlaySheetRowsResult>(
2447
2452
  `/api/v2/plays/${encodeURIComponent(input.playName)}/sheet?${params.toString()}`,
2448
2453
  );
@@ -101,10 +101,10 @@ export const SDK_RELEASE = {
101
101
  // 0.1.108 ships explicit dataset column/tool recompute policy and removes
102
102
  // the SDK enrich generator's one-second stale policy.
103
103
  // 0.1.110 ships authored V2 prebuilts and required top-level play descriptions.
104
- version: '0.1.144',
104
+ version: '0.1.145',
105
105
  apiContract: '2026-06-dataset-column-cell-stale-hard-cutover',
106
106
  supportPolicy: {
107
- latest: '0.1.144',
107
+ latest: '0.1.145',
108
108
  minimumSupported: '0.1.53',
109
109
  deprecatedBelow: '0.1.53',
110
110
  commandMinimumSupported: [
@@ -18,13 +18,14 @@ export const LOCAL_TEMPORAL_ADDRESS = `127.0.0.1:${LOCAL_TEMPORAL_FRONTEND_PORT}
18
18
  export const LOCAL_TEMPORAL_UI_URL = `http://127.0.0.1:${LOCAL_TEMPORAL_UI_PORT}`;
19
19
 
20
20
  /** Maximum active user-code runtime for a standard play, in seconds. */
21
- export const STANDARD_PLAY_RUNTIME_LIMIT_SECONDS = 10 * 60; // 10 minutes
21
+ export const STANDARD_PLAY_RUNTIME_LIMIT_SECONDS = 20 * 60; // 20 minutes
22
+ export const STANDARD_PLAY_RUNTIME_LIMIT_LABEL = '20 minutes';
22
23
 
23
24
  /**
24
- * Activity timeout includes cleanup/billing headroom after the 10 minute
25
+ * Activity timeout includes setup, cleanup, and billing headroom after the 20 minute
25
26
  * user-code runtime cap. Keep this higher than STANDARD_PLAY_RUNTIME_LIMIT_SECONDS.
26
27
  */
27
- export const PLAY_ACTIVITY_TIMEOUT_SECONDS = 12 * 60; // 12 minutes
28
+ export const PLAY_ACTIVITY_TIMEOUT_SECONDS = 30 * 60; // 30 minutes
28
29
 
29
30
  /** Heartbeat cadence for the long-running play execution activity. */
30
31
  export const PLAY_EXECUTE_ACTIVITY_HEARTBEAT_INTERVAL_SECONDS = 15;
package/dist/cli/index.js CHANGED
@@ -619,10 +619,10 @@ var SDK_RELEASE = {
619
619
  // 0.1.108 ships explicit dataset column/tool recompute policy and removes
620
620
  // the SDK enrich generator's one-second stale policy.
621
621
  // 0.1.110 ships authored V2 prebuilts and required top-level play descriptions.
622
- version: "0.1.144",
622
+ version: "0.1.145",
623
623
  apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
624
624
  supportPolicy: {
625
- latest: "0.1.144",
625
+ latest: "0.1.145",
626
626
  minimumSupported: "0.1.53",
627
627
  deprecatedBelow: "0.1.53",
628
628
  commandMinimumSupported: [
@@ -3499,6 +3499,9 @@ var DeeplineClient = class {
3499
3499
  if (input2.runId?.trim()) {
3500
3500
  params.set("runId", input2.runId.trim());
3501
3501
  }
3502
+ if (input2.rowMode === "all") {
3503
+ params.set("rowMode", "all");
3504
+ }
3502
3505
  return await this.http.get(
3503
3506
  `/api/v2/plays/${encodeURIComponent(input2.playName)}/sheet?${params.toString()}`
3504
3507
  );
@@ -11946,7 +11949,8 @@ async function fetchBackingDatasetRows(input2) {
11946
11949
  tableNamespace,
11947
11950
  runId: input2.status.runId,
11948
11951
  limit: RUN_EXPORT_PAGE_SIZE,
11949
- offset
11952
+ offset,
11953
+ rowMode: "all"
11950
11954
  });
11951
11955
  sheetRows.push(...page.rows);
11952
11956
  const summaryTotal = page.summary?.stats?.total;
@@ -16286,8 +16290,10 @@ function helperSource() {
16286
16290
 
16287
16291
  // src/cli/commands/enrich.ts
16288
16292
  var ENRICH_EXPORT_PAGE_SIZE = 5e3;
16293
+ var ENRICH_AUTO_BATCH_ROWS = 250;
16289
16294
  var EXIT_SERVER2 = 5;
16290
16295
  var ENRICH_DEBUG_T0 = Date.now();
16296
+ var GENERATED_ENRICH_ROWS_TABLE_NAMESPACE = "deepline_enrich_rows";
16291
16297
  var PLAN_SHAPING_OPTION_NAMES = [
16292
16298
  "with",
16293
16299
  "withWaterfall",
@@ -16951,7 +16957,12 @@ async function runGeneratedEnrichPlay(runArgs, options = {}) {
16951
16957
  async function writeOutputCsv(outputPath, status, options) {
16952
16958
  let rowsInfo = extractCanonicalRowsInfo(status);
16953
16959
  if (!rowsInfo) {
16954
- throw new Error("The generated play did not return row-shaped output.");
16960
+ rowsInfo = fallbackRowsInfoForGeneratedEnrichExport(status, options);
16961
+ }
16962
+ if (!rowsInfo) {
16963
+ throw new Error(
16964
+ "The generated play did not return row-shaped output, and no durable enrich rows were available to export."
16965
+ );
16955
16966
  }
16956
16967
  if (options?.client) {
16957
16968
  rowsInfo = await fetchBackingRowsForCsvExport({
@@ -17014,6 +17025,98 @@ function extractPlayName2(status) {
17014
17025
  }
17015
17026
  return null;
17016
17027
  }
17028
+ function selectedSourceCsvRange(sourceCsvPath, rows) {
17029
+ const sourceRows = readCsvRows(sourceCsvPath).length;
17030
+ if (sourceRows === 0) {
17031
+ return { start: 0, end: -1, count: 0, sourceRows };
17032
+ }
17033
+ const start = Math.max(0, rows?.rowStart ?? 0);
17034
+ if (start >= sourceRows) {
17035
+ return { start, end: start - 1, count: 0, sourceRows };
17036
+ }
17037
+ const maxEnd = sourceRows - 1;
17038
+ const end = rows?.rowEnd === null || rows?.rowEnd === void 0 ? maxEnd : Math.min(maxEnd, rows.rowEnd);
17039
+ return {
17040
+ start,
17041
+ end,
17042
+ count: Math.max(0, end - start + 1),
17043
+ sourceRows
17044
+ };
17045
+ }
17046
+ function selectedSourceCsvRowCount(options) {
17047
+ if (!options?.sourceCsvPath) {
17048
+ return null;
17049
+ }
17050
+ return selectedSourceCsvRange(options.sourceCsvPath, options.rows).count;
17051
+ }
17052
+ function collectStringFields(value, key, output2, depth = 0) {
17053
+ if (depth > 12 || !value || typeof value !== "object") {
17054
+ return;
17055
+ }
17056
+ if (Array.isArray(value)) {
17057
+ for (const item of value.slice(0, 50)) {
17058
+ collectStringFields(item, key, output2, depth + 1);
17059
+ }
17060
+ return;
17061
+ }
17062
+ const record = value;
17063
+ const direct = record[key];
17064
+ if (typeof direct === "string" && direct.trim()) {
17065
+ output2.add(direct.trim());
17066
+ }
17067
+ for (const child of Object.values(record)) {
17068
+ collectStringFields(child, key, output2, depth + 1);
17069
+ }
17070
+ }
17071
+ function fallbackRowsInfoForGeneratedEnrichExport(status, options) {
17072
+ if (!extractRunId(status) || !extractPlayName2(status)) {
17073
+ return null;
17074
+ }
17075
+ const totalRows = selectedSourceCsvRowCount(options);
17076
+ if (totalRows === null) {
17077
+ return null;
17078
+ }
17079
+ const tableNamespaces = /* @__PURE__ */ new Set();
17080
+ collectStringFields(status, "tableNamespace", tableNamespaces);
17081
+ collectStringFields(status, "artifactTableNamespace", tableNamespaces);
17082
+ const tableNamespace = tableNamespaces.values().next().value ?? GENERATED_ENRICH_ROWS_TABLE_NAMESPACE;
17083
+ return {
17084
+ rows: [],
17085
+ totalRows,
17086
+ columns: [],
17087
+ columnsExplicit: false,
17088
+ complete: false,
17089
+ source: tableNamespace,
17090
+ tableNamespace
17091
+ };
17092
+ }
17093
+ function firstCollectedStringField(value, key) {
17094
+ const fields = /* @__PURE__ */ new Set();
17095
+ collectStringFields(value, key, fields);
17096
+ return fields.values().next().value ?? null;
17097
+ }
17098
+ function emitPlainBatchRunFailure(input2) {
17099
+ process.stderr.write(
17100
+ `Batch ${input2.chunkIndex + 1}/${input2.chunkCount} failed for rows ${input2.rows.rowStart}:${input2.rows.rowEnd}.
17101
+ `
17102
+ );
17103
+ const runId = extractRunId(input2.status);
17104
+ const error = firstCollectedStringField(input2.status, "error") ?? firstCollectedStringField(input2.status, "message");
17105
+ if (runId) {
17106
+ process.stderr.write(`Run id: ${runId}
17107
+ `);
17108
+ }
17109
+ if (error) {
17110
+ process.stderr.write(`Error: ${error}
17111
+ `);
17112
+ }
17113
+ if (runId) {
17114
+ process.stderr.write(
17115
+ `Inspect run output: deepline runs get ${runId} --full --json
17116
+ `
17117
+ );
17118
+ }
17119
+ }
17017
17120
  function exportableSheetRow2(row) {
17018
17121
  if (!row || typeof row !== "object" || Array.isArray(row)) {
17019
17122
  return null;
@@ -17381,7 +17484,8 @@ async function fetchBackingRowsForCsvExport(input2) {
17381
17484
  tableNamespace,
17382
17485
  runId,
17383
17486
  limit: ENRICH_EXPORT_PAGE_SIZE,
17384
- offset
17487
+ offset,
17488
+ rowMode: "all"
17385
17489
  });
17386
17490
  sheetRows.push(...page.rows);
17387
17491
  const summaryTotal = page.summary?.stats?.total;
@@ -17749,41 +17853,177 @@ function registerEnrichCommand(program) {
17749
17853
  const tempPlay = (0, import_node_path11.join)(tempDir, "deepline-enrich.play.ts");
17750
17854
  try {
17751
17855
  await (0, import_promises3.writeFile)(tempPlay, playSource, "utf8");
17752
- const runtimeInput = {
17753
- file: (0, import_node_path11.resolve)(sourceCsvPath),
17754
- ...rows.rowStart !== null ? { rowStart: rows.rowStart } : {},
17755
- ...rows.rowEnd !== null ? { rowEnd: rows.rowEnd } : {}
17856
+ const runOne = async (input2) => {
17857
+ const runtimeInput = {
17858
+ file: (0, import_node_path11.resolve)(input2.sourceCsvPath),
17859
+ ...input2.rows.rowStart !== null ? { rowStart: input2.rows.rowStart } : {},
17860
+ ...input2.rows.rowEnd !== null ? { rowEnd: input2.rows.rowEnd } : {}
17861
+ };
17862
+ const runArgs = [
17863
+ "--file",
17864
+ tempPlay,
17865
+ "--input",
17866
+ JSON.stringify(runtimeInput),
17867
+ "--watch"
17868
+ ];
17869
+ if (options.profile) {
17870
+ runArgs.push("--profile", options.profile);
17871
+ }
17872
+ if (options.noOpen || input2.suppressOpen) {
17873
+ runArgs.push("--no-open");
17874
+ }
17875
+ if (input2.json) {
17876
+ runArgs.push("--json");
17877
+ } else {
17878
+ runArgs.push("--logs");
17879
+ }
17880
+ const timeoutSeconds = options.timeout ? Number.parseInt(options.timeout, 10) : null;
17881
+ if (timeoutSeconds !== null && Number.isFinite(timeoutSeconds) && timeoutSeconds > 0) {
17882
+ runArgs.push("--tail-timeout-ms", String(timeoutSeconds * 1e3));
17883
+ }
17884
+ const captured2 = await runGeneratedEnrichPlay(runArgs, {
17885
+ passthroughStdout: input2.passthroughStdout
17886
+ });
17887
+ const status2 = input2.json ? parseJsonOutput(captured2.stdout) : await resolveWatchedGeneratedPlayStatus({
17888
+ client: client2,
17889
+ stdout: captured2.stdout,
17890
+ exitCode: captured2.result
17891
+ });
17892
+ const exportResult2 = captured2.result === 0 && outputPath ? await writeOutputCsv(outputPath, status2, {
17893
+ client: client2,
17894
+ config,
17895
+ sourceCsvPath: input2.sourceCsvPath,
17896
+ rows: input2.rows,
17897
+ inPlace: Boolean(options.inPlace)
17898
+ }) : null;
17899
+ return { captured: captured2, status: status2, exportResult: exportResult2 };
17756
17900
  };
17757
- const runArgs = [
17758
- "--file",
17759
- tempPlay,
17760
- "--input",
17761
- JSON.stringify(runtimeInput),
17762
- "--watch"
17763
- ];
17764
- if (options.profile) {
17765
- runArgs.push("--profile", options.profile);
17766
- }
17767
- if (options.noOpen) {
17768
- runArgs.push("--no-open");
17769
- }
17770
- if (options.json) {
17771
- runArgs.push("--json");
17772
- } else {
17773
- runArgs.push("--logs");
17774
- }
17775
- const timeoutSeconds = options.timeout ? Number.parseInt(options.timeout, 10) : null;
17776
- if (timeoutSeconds !== null && Number.isFinite(timeoutSeconds) && timeoutSeconds > 0) {
17777
- runArgs.push("--tail-timeout-ms", String(timeoutSeconds * 1e3));
17901
+ const selectedRange = selectedSourceCsvRange(sourceCsvPath, rows);
17902
+ if (outputPath && selectedRange.count > ENRICH_AUTO_BATCH_ROWS) {
17903
+ const chunkCount = Math.ceil(
17904
+ selectedRange.count / ENRICH_AUTO_BATCH_ROWS
17905
+ );
17906
+ if (!options.json) {
17907
+ process.stderr.write(
17908
+ `Large enrich input selected ${selectedRange.count.toLocaleString()} rows. Running ${chunkCount.toLocaleString()} sequential batch runs of up to ${ENRICH_AUTO_BATCH_ROWS.toLocaleString()} rows and merging ${(0, import_node_path11.resolve)(outputPath)}.
17909
+ `
17910
+ );
17911
+ }
17912
+ let workingSourceCsvPath = sourceCsvPath;
17913
+ let lastStatus = null;
17914
+ let finalExportResult = null;
17915
+ let totalEnrichedRows = 0;
17916
+ const batchFailureRows = [];
17917
+ for (let chunkStart = selectedRange.start, chunkIndex = 0; chunkStart <= selectedRange.end; chunkStart += ENRICH_AUTO_BATCH_ROWS, chunkIndex += 1) {
17918
+ const chunkRows = {
17919
+ rowStart: chunkStart,
17920
+ rowEnd: Math.min(
17921
+ selectedRange.end,
17922
+ chunkStart + ENRICH_AUTO_BATCH_ROWS - 1
17923
+ )
17924
+ };
17925
+ if (!options.json) {
17926
+ process.stderr.write(
17927
+ `Batch ${chunkIndex + 1}/${chunkCount}: rows ${chunkRows.rowStart}:${chunkRows.rowEnd}
17928
+ `
17929
+ );
17930
+ }
17931
+ const chunk = await runOne({
17932
+ sourceCsvPath: workingSourceCsvPath,
17933
+ rows: chunkRows,
17934
+ json: true,
17935
+ passthroughStdout: false,
17936
+ suppressOpen: true
17937
+ });
17938
+ lastStatus = chunk.status;
17939
+ if (chunk.captured.result !== 0) {
17940
+ if (options.json) {
17941
+ printJson({
17942
+ ok: false,
17943
+ batch: {
17944
+ chunk: chunkIndex + 1,
17945
+ chunks: chunkCount,
17946
+ rows: chunkRows
17947
+ },
17948
+ result: chunk.status
17949
+ });
17950
+ } else {
17951
+ emitPlainBatchRunFailure({
17952
+ chunkIndex,
17953
+ chunkCount,
17954
+ rows: chunkRows,
17955
+ status: chunk.status
17956
+ });
17957
+ }
17958
+ process.exitCode = chunk.captured.result;
17959
+ return;
17960
+ }
17961
+ if (chunk.exportResult) {
17962
+ finalExportResult = chunk.exportResult;
17963
+ totalEnrichedRows += chunk.exportResult.enrichedRows;
17964
+ batchFailureRows.push(...chunk.exportResult.enrichedDataRows);
17965
+ workingSourceCsvPath = outputPath;
17966
+ }
17967
+ }
17968
+ const outputRows = readCsvRows(outputPath);
17969
+ const failureReport2 = await maybeEmitEnrichFailureReport({
17970
+ config,
17971
+ rows: batchFailureRows,
17972
+ rowRange: {
17973
+ rowStart: selectedRange.start,
17974
+ rowEnd: selectedRange.end
17975
+ },
17976
+ client: client2,
17977
+ outputPath
17978
+ });
17979
+ if (options.json) {
17980
+ const run = rewriteEnrichJsonStatus({
17981
+ status: lastStatus,
17982
+ config,
17983
+ forceAliases,
17984
+ output: finalExportResult ? {
17985
+ ...finalExportResult,
17986
+ sourceCsvRows: selectedRange.sourceRows,
17987
+ selectedRows: selectedRange.count,
17988
+ enrichedRows: totalEnrichedRows,
17989
+ rows: outputRows.length,
17990
+ enrichedDataRows: outputRows
17991
+ } : null,
17992
+ failureReport: failureReport2
17993
+ });
17994
+ printJson({
17995
+ ok: !failureReport2,
17996
+ run,
17997
+ batch: {
17998
+ chunks: chunkCount,
17999
+ chunkRows: ENRICH_AUTO_BATCH_ROWS,
18000
+ selectedRows: selectedRange.count
18001
+ },
18002
+ output: finalExportResult ? {
18003
+ sourceCsvRows: selectedRange.sourceRows,
18004
+ selectedRows: selectedRange.count,
18005
+ enrichedRows: totalEnrichedRows,
18006
+ path: finalExportResult.path
18007
+ } : null,
18008
+ ...failureReport2 ? {
18009
+ failure_report: {
18010
+ path: failureReport2.path,
18011
+ jobs: failureReport2.jobs.length
18012
+ }
18013
+ } : {}
18014
+ });
18015
+ }
18016
+ if (failureReport2) {
18017
+ process.exitCode = EXIT_SERVER2;
18018
+ }
18019
+ return;
17778
18020
  }
17779
- const captured = await runGeneratedEnrichPlay(runArgs, {
18021
+ const { captured, status, exportResult } = await runOne({
18022
+ sourceCsvPath,
18023
+ rows,
18024
+ json: Boolean(options.json),
17780
18025
  passthroughStdout: !options.json
17781
18026
  });
17782
- const status = options.json ? parseJsonOutput(captured.stdout) : await resolveWatchedGeneratedPlayStatus({
17783
- client: client2,
17784
- stdout: captured.stdout,
17785
- exitCode: captured.result
17786
- });
17787
18027
  if (captured.result !== 0) {
17788
18028
  if (options.json) {
17789
18029
  printJson({
@@ -17793,13 +18033,6 @@ function registerEnrichCommand(program) {
17793
18033
  process.exitCode = captured.result;
17794
18034
  return;
17795
18035
  }
17796
- const exportResult = outputPath ? await writeOutputCsv(outputPath, status, {
17797
- client: client2,
17798
- config,
17799
- sourceCsvPath,
17800
- rows,
17801
- inPlace: Boolean(options.inPlace)
17802
- }) : null;
17803
18036
  const rowsForFailureReport = exportResult?.enrichedDataRows ?? extractCanonicalRowsInfo(status)?.rows ?? [];
17804
18037
  if (options.json) {
17805
18038
  const failureReport2 = await maybeEmitEnrichFailureReport({
@@ -604,10 +604,10 @@ var SDK_RELEASE = {
604
604
  // 0.1.108 ships explicit dataset column/tool recompute policy and removes
605
605
  // the SDK enrich generator's one-second stale policy.
606
606
  // 0.1.110 ships authored V2 prebuilts and required top-level play descriptions.
607
- version: "0.1.144",
607
+ version: "0.1.145",
608
608
  apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
609
609
  supportPolicy: {
610
- latest: "0.1.144",
610
+ latest: "0.1.145",
611
611
  minimumSupported: "0.1.53",
612
612
  deprecatedBelow: "0.1.53",
613
613
  commandMinimumSupported: [
@@ -3484,6 +3484,9 @@ var DeeplineClient = class {
3484
3484
  if (input2.runId?.trim()) {
3485
3485
  params.set("runId", input2.runId.trim());
3486
3486
  }
3487
+ if (input2.rowMode === "all") {
3488
+ params.set("rowMode", "all");
3489
+ }
3487
3490
  return await this.http.get(
3488
3491
  `/api/v2/plays/${encodeURIComponent(input2.playName)}/sheet?${params.toString()}`
3489
3492
  );
@@ -11963,7 +11966,8 @@ async function fetchBackingDatasetRows(input2) {
11963
11966
  tableNamespace,
11964
11967
  runId: input2.status.runId,
11965
11968
  limit: RUN_EXPORT_PAGE_SIZE,
11966
- offset
11969
+ offset,
11970
+ rowMode: "all"
11967
11971
  });
11968
11972
  sheetRows.push(...page.rows);
11969
11973
  const summaryTotal = page.summary?.stats?.total;
@@ -16303,8 +16307,10 @@ function helperSource() {
16303
16307
 
16304
16308
  // src/cli/commands/enrich.ts
16305
16309
  var ENRICH_EXPORT_PAGE_SIZE = 5e3;
16310
+ var ENRICH_AUTO_BATCH_ROWS = 250;
16306
16311
  var EXIT_SERVER2 = 5;
16307
16312
  var ENRICH_DEBUG_T0 = Date.now();
16313
+ var GENERATED_ENRICH_ROWS_TABLE_NAMESPACE = "deepline_enrich_rows";
16308
16314
  var PLAN_SHAPING_OPTION_NAMES = [
16309
16315
  "with",
16310
16316
  "withWaterfall",
@@ -16968,7 +16974,12 @@ async function runGeneratedEnrichPlay(runArgs, options = {}) {
16968
16974
  async function writeOutputCsv(outputPath, status, options) {
16969
16975
  let rowsInfo = extractCanonicalRowsInfo(status);
16970
16976
  if (!rowsInfo) {
16971
- throw new Error("The generated play did not return row-shaped output.");
16977
+ rowsInfo = fallbackRowsInfoForGeneratedEnrichExport(status, options);
16978
+ }
16979
+ if (!rowsInfo) {
16980
+ throw new Error(
16981
+ "The generated play did not return row-shaped output, and no durable enrich rows were available to export."
16982
+ );
16972
16983
  }
16973
16984
  if (options?.client) {
16974
16985
  rowsInfo = await fetchBackingRowsForCsvExport({
@@ -17031,6 +17042,98 @@ function extractPlayName2(status) {
17031
17042
  }
17032
17043
  return null;
17033
17044
  }
17045
+ function selectedSourceCsvRange(sourceCsvPath, rows) {
17046
+ const sourceRows = readCsvRows(sourceCsvPath).length;
17047
+ if (sourceRows === 0) {
17048
+ return { start: 0, end: -1, count: 0, sourceRows };
17049
+ }
17050
+ const start = Math.max(0, rows?.rowStart ?? 0);
17051
+ if (start >= sourceRows) {
17052
+ return { start, end: start - 1, count: 0, sourceRows };
17053
+ }
17054
+ const maxEnd = sourceRows - 1;
17055
+ const end = rows?.rowEnd === null || rows?.rowEnd === void 0 ? maxEnd : Math.min(maxEnd, rows.rowEnd);
17056
+ return {
17057
+ start,
17058
+ end,
17059
+ count: Math.max(0, end - start + 1),
17060
+ sourceRows
17061
+ };
17062
+ }
17063
+ function selectedSourceCsvRowCount(options) {
17064
+ if (!options?.sourceCsvPath) {
17065
+ return null;
17066
+ }
17067
+ return selectedSourceCsvRange(options.sourceCsvPath, options.rows).count;
17068
+ }
17069
+ function collectStringFields(value, key, output2, depth = 0) {
17070
+ if (depth > 12 || !value || typeof value !== "object") {
17071
+ return;
17072
+ }
17073
+ if (Array.isArray(value)) {
17074
+ for (const item of value.slice(0, 50)) {
17075
+ collectStringFields(item, key, output2, depth + 1);
17076
+ }
17077
+ return;
17078
+ }
17079
+ const record = value;
17080
+ const direct = record[key];
17081
+ if (typeof direct === "string" && direct.trim()) {
17082
+ output2.add(direct.trim());
17083
+ }
17084
+ for (const child of Object.values(record)) {
17085
+ collectStringFields(child, key, output2, depth + 1);
17086
+ }
17087
+ }
17088
+ function fallbackRowsInfoForGeneratedEnrichExport(status, options) {
17089
+ if (!extractRunId(status) || !extractPlayName2(status)) {
17090
+ return null;
17091
+ }
17092
+ const totalRows = selectedSourceCsvRowCount(options);
17093
+ if (totalRows === null) {
17094
+ return null;
17095
+ }
17096
+ const tableNamespaces = /* @__PURE__ */ new Set();
17097
+ collectStringFields(status, "tableNamespace", tableNamespaces);
17098
+ collectStringFields(status, "artifactTableNamespace", tableNamespaces);
17099
+ const tableNamespace = tableNamespaces.values().next().value ?? GENERATED_ENRICH_ROWS_TABLE_NAMESPACE;
17100
+ return {
17101
+ rows: [],
17102
+ totalRows,
17103
+ columns: [],
17104
+ columnsExplicit: false,
17105
+ complete: false,
17106
+ source: tableNamespace,
17107
+ tableNamespace
17108
+ };
17109
+ }
17110
+ function firstCollectedStringField(value, key) {
17111
+ const fields = /* @__PURE__ */ new Set();
17112
+ collectStringFields(value, key, fields);
17113
+ return fields.values().next().value ?? null;
17114
+ }
17115
+ function emitPlainBatchRunFailure(input2) {
17116
+ process.stderr.write(
17117
+ `Batch ${input2.chunkIndex + 1}/${input2.chunkCount} failed for rows ${input2.rows.rowStart}:${input2.rows.rowEnd}.
17118
+ `
17119
+ );
17120
+ const runId = extractRunId(input2.status);
17121
+ const error = firstCollectedStringField(input2.status, "error") ?? firstCollectedStringField(input2.status, "message");
17122
+ if (runId) {
17123
+ process.stderr.write(`Run id: ${runId}
17124
+ `);
17125
+ }
17126
+ if (error) {
17127
+ process.stderr.write(`Error: ${error}
17128
+ `);
17129
+ }
17130
+ if (runId) {
17131
+ process.stderr.write(
17132
+ `Inspect run output: deepline runs get ${runId} --full --json
17133
+ `
17134
+ );
17135
+ }
17136
+ }
17034
17137
  function exportableSheetRow2(row) {
17035
17138
  if (!row || typeof row !== "object" || Array.isArray(row)) {
17036
17139
  return null;
@@ -17398,7 +17501,8 @@ async function fetchBackingRowsForCsvExport(input2) {
17398
17501
  tableNamespace,
17399
17502
  runId,
17400
17503
  limit: ENRICH_EXPORT_PAGE_SIZE,
17401
- offset
17504
+ offset,
17505
+ rowMode: "all"
17402
17506
  });
17403
17507
  sheetRows.push(...page.rows);
17404
17508
  const summaryTotal = page.summary?.stats?.total;
@@ -17766,41 +17870,177 @@ function registerEnrichCommand(program) {
17766
17870
  const tempPlay = join6(tempDir, "deepline-enrich.play.ts");
17767
17871
  try {
17768
17872
  await writeFile3(tempPlay, playSource, "utf8");
17769
- const runtimeInput = {
17770
- file: resolve8(sourceCsvPath),
17771
- ...rows.rowStart !== null ? { rowStart: rows.rowStart } : {},
17772
- ...rows.rowEnd !== null ? { rowEnd: rows.rowEnd } : {}
17873
+ const runOne = async (input2) => {
17874
+ const runtimeInput = {
17875
+ file: resolve8(input2.sourceCsvPath),
17876
+ ...input2.rows.rowStart !== null ? { rowStart: input2.rows.rowStart } : {},
17877
+ ...input2.rows.rowEnd !== null ? { rowEnd: input2.rows.rowEnd } : {}
17878
+ };
17879
+ const runArgs = [
17880
+ "--file",
17881
+ tempPlay,
17882
+ "--input",
17883
+ JSON.stringify(runtimeInput),
17884
+ "--watch"
17885
+ ];
17886
+ if (options.profile) {
17887
+ runArgs.push("--profile", options.profile);
17888
+ }
17889
+ if (options.noOpen || input2.suppressOpen) {
17890
+ runArgs.push("--no-open");
17891
+ }
17892
+ if (input2.json) {
17893
+ runArgs.push("--json");
17894
+ } else {
17895
+ runArgs.push("--logs");
17896
+ }
17897
+ const timeoutSeconds = options.timeout ? Number.parseInt(options.timeout, 10) : null;
17898
+ if (timeoutSeconds !== null && Number.isFinite(timeoutSeconds) && timeoutSeconds > 0) {
17899
+ runArgs.push("--tail-timeout-ms", String(timeoutSeconds * 1e3));
17900
+ }
17901
+ const captured2 = await runGeneratedEnrichPlay(runArgs, {
17902
+ passthroughStdout: input2.passthroughStdout
17903
+ });
17904
+ const status2 = input2.json ? parseJsonOutput(captured2.stdout) : await resolveWatchedGeneratedPlayStatus({
17905
+ client: client2,
17906
+ stdout: captured2.stdout,
17907
+ exitCode: captured2.result
17908
+ });
17909
+ const exportResult2 = captured2.result === 0 && outputPath ? await writeOutputCsv(outputPath, status2, {
17910
+ client: client2,
17911
+ config,
17912
+ sourceCsvPath: input2.sourceCsvPath,
17913
+ rows: input2.rows,
17914
+ inPlace: Boolean(options.inPlace)
17915
+ }) : null;
17916
+ return { captured: captured2, status: status2, exportResult: exportResult2 };
17773
17917
  };
17774
- const runArgs = [
17775
- "--file",
17776
- tempPlay,
17777
- "--input",
17778
- JSON.stringify(runtimeInput),
17779
- "--watch"
17780
- ];
17781
- if (options.profile) {
17782
- runArgs.push("--profile", options.profile);
17783
- }
17784
- if (options.noOpen) {
17785
- runArgs.push("--no-open");
17786
- }
17787
- if (options.json) {
17788
- runArgs.push("--json");
17789
- } else {
17790
- runArgs.push("--logs");
17791
- }
17792
- const timeoutSeconds = options.timeout ? Number.parseInt(options.timeout, 10) : null;
17793
- if (timeoutSeconds !== null && Number.isFinite(timeoutSeconds) && timeoutSeconds > 0) {
17794
- runArgs.push("--tail-timeout-ms", String(timeoutSeconds * 1e3));
17918
+ const selectedRange = selectedSourceCsvRange(sourceCsvPath, rows);
17919
+ if (outputPath && selectedRange.count > ENRICH_AUTO_BATCH_ROWS) {
17920
+ const chunkCount = Math.ceil(
17921
+ selectedRange.count / ENRICH_AUTO_BATCH_ROWS
17922
+ );
17923
+ if (!options.json) {
17924
+ process.stderr.write(
17925
+ `Large enrich input selected ${selectedRange.count.toLocaleString()} rows. Running ${chunkCount.toLocaleString()} sequential batch runs of up to ${ENRICH_AUTO_BATCH_ROWS.toLocaleString()} rows and merging ${resolve8(outputPath)}.
17926
+ `
17927
+ );
17928
+ }
17929
+ let workingSourceCsvPath = sourceCsvPath;
17930
+ let lastStatus = null;
17931
+ let finalExportResult = null;
17932
+ let totalEnrichedRows = 0;
17933
+ const batchFailureRows = [];
17934
+ for (let chunkStart = selectedRange.start, chunkIndex = 0; chunkStart <= selectedRange.end; chunkStart += ENRICH_AUTO_BATCH_ROWS, chunkIndex += 1) {
17935
+ const chunkRows = {
17936
+ rowStart: chunkStart,
17937
+ rowEnd: Math.min(
17938
+ selectedRange.end,
17939
+ chunkStart + ENRICH_AUTO_BATCH_ROWS - 1
17940
+ )
17941
+ };
17942
+ if (!options.json) {
17943
+ process.stderr.write(
17944
+ `Batch ${chunkIndex + 1}/${chunkCount}: rows ${chunkRows.rowStart}:${chunkRows.rowEnd}
17945
+ `
17946
+ );
17947
+ }
17948
+ const chunk = await runOne({
17949
+ sourceCsvPath: workingSourceCsvPath,
17950
+ rows: chunkRows,
17951
+ json: true,
17952
+ passthroughStdout: false,
17953
+ suppressOpen: true
17954
+ });
17955
+ lastStatus = chunk.status;
17956
+ if (chunk.captured.result !== 0) {
17957
+ if (options.json) {
17958
+ printJson({
17959
+ ok: false,
17960
+ batch: {
17961
+ chunk: chunkIndex + 1,
17962
+ chunks: chunkCount,
17963
+ rows: chunkRows
17964
+ },
17965
+ result: chunk.status
17966
+ });
17967
+ } else {
17968
+ emitPlainBatchRunFailure({
17969
+ chunkIndex,
17970
+ chunkCount,
17971
+ rows: chunkRows,
17972
+ status: chunk.status
17973
+ });
17974
+ }
17975
+ process.exitCode = chunk.captured.result;
17976
+ return;
17977
+ }
17978
+ if (chunk.exportResult) {
17979
+ finalExportResult = chunk.exportResult;
17980
+ totalEnrichedRows += chunk.exportResult.enrichedRows;
17981
+ batchFailureRows.push(...chunk.exportResult.enrichedDataRows);
17982
+ workingSourceCsvPath = outputPath;
17983
+ }
17984
+ }
17985
+ const outputRows = readCsvRows(outputPath);
17986
+ const failureReport2 = await maybeEmitEnrichFailureReport({
17987
+ config,
17988
+ rows: batchFailureRows,
17989
+ rowRange: {
17990
+ rowStart: selectedRange.start,
17991
+ rowEnd: selectedRange.end
17992
+ },
17993
+ client: client2,
17994
+ outputPath
17995
+ });
17996
+ if (options.json) {
17997
+ const run = rewriteEnrichJsonStatus({
17998
+ status: lastStatus,
17999
+ config,
18000
+ forceAliases,
18001
+ output: finalExportResult ? {
18002
+ ...finalExportResult,
18003
+ sourceCsvRows: selectedRange.sourceRows,
18004
+ selectedRows: selectedRange.count,
18005
+ enrichedRows: totalEnrichedRows,
18006
+ rows: outputRows.length,
18007
+ enrichedDataRows: outputRows
18008
+ } : null,
18009
+ failureReport: failureReport2
18010
+ });
18011
+ printJson({
18012
+ ok: !failureReport2,
18013
+ run,
18014
+ batch: {
18015
+ chunks: chunkCount,
18016
+ chunkRows: ENRICH_AUTO_BATCH_ROWS,
18017
+ selectedRows: selectedRange.count
18018
+ },
18019
+ output: finalExportResult ? {
18020
+ sourceCsvRows: selectedRange.sourceRows,
18021
+ selectedRows: selectedRange.count,
18022
+ enrichedRows: totalEnrichedRows,
18023
+ path: finalExportResult.path
18024
+ } : null,
18025
+ ...failureReport2 ? {
18026
+ failure_report: {
18027
+ path: failureReport2.path,
18028
+ jobs: failureReport2.jobs.length
18029
+ }
18030
+ } : {}
18031
+ });
18032
+ }
18033
+ if (failureReport2) {
18034
+ process.exitCode = EXIT_SERVER2;
18035
+ }
18036
+ return;
17795
18037
  }
17796
- const captured = await runGeneratedEnrichPlay(runArgs, {
18038
+ const { captured, status, exportResult } = await runOne({
18039
+ sourceCsvPath,
18040
+ rows,
18041
+ json: Boolean(options.json),
17797
18042
  passthroughStdout: !options.json
17798
18043
  });
17799
- const status = options.json ? parseJsonOutput(captured.stdout) : await resolveWatchedGeneratedPlayStatus({
17800
- client: client2,
17801
- stdout: captured.stdout,
17802
- exitCode: captured.result
17803
- });
17804
18044
  if (captured.result !== 0) {
17805
18045
  if (options.json) {
17806
18046
  printJson({
@@ -17810,13 +18050,6 @@ function registerEnrichCommand(program) {
17810
18050
  process.exitCode = captured.result;
17811
18051
  return;
17812
18052
  }
17813
- const exportResult = outputPath ? await writeOutputCsv(outputPath, status, {
17814
- client: client2,
17815
- config,
17816
- sourceCsvPath,
17817
- rows,
17818
- inPlace: Boolean(options.inPlace)
17819
- }) : null;
17820
18053
  const rowsForFailureReport = exportResult?.enrichedDataRows ?? extractCanonicalRowsInfo(status)?.rows ?? [];
17821
18054
  if (options.json) {
17822
18055
  const failureReport2 = await maybeEmitEnrichFailureReport({
package/dist/index.d.mts CHANGED
@@ -1205,6 +1205,7 @@ type RunsNamespace = {
1205
1205
  runId?: string;
1206
1206
  limit?: number;
1207
1207
  offset?: number;
1208
+ rowMode?: 'output' | 'all';
1208
1209
  }) => Promise<PlaySheetRowsResult>;
1209
1210
  /** Stop a running/waiting run. */
1210
1211
  stop: (runId: string, options?: {
@@ -1956,6 +1957,7 @@ declare class DeeplineClient {
1956
1957
  runId?: string;
1957
1958
  limit?: number;
1958
1959
  offset?: number;
1960
+ rowMode?: 'output' | 'all';
1959
1961
  }): Promise<PlaySheetRowsResult>;
1960
1962
  /**
1961
1963
  * Stop a run by id using the public runs resource model.
package/dist/index.d.ts CHANGED
@@ -1205,6 +1205,7 @@ type RunsNamespace = {
1205
1205
  runId?: string;
1206
1206
  limit?: number;
1207
1207
  offset?: number;
1208
+ rowMode?: 'output' | 'all';
1208
1209
  }) => Promise<PlaySheetRowsResult>;
1209
1210
  /** Stop a running/waiting run. */
1210
1211
  stop: (runId: string, options?: {
@@ -1956,6 +1957,7 @@ declare class DeeplineClient {
1956
1957
  runId?: string;
1957
1958
  limit?: number;
1958
1959
  offset?: number;
1960
+ rowMode?: 'output' | 'all';
1959
1961
  }): Promise<PlaySheetRowsResult>;
1960
1962
  /**
1961
1963
  * Stop a run by id using the public runs resource model.
package/dist/index.js CHANGED
@@ -418,10 +418,10 @@ var SDK_RELEASE = {
418
418
  // 0.1.108 ships explicit dataset column/tool recompute policy and removes
419
419
  // the SDK enrich generator's one-second stale policy.
420
420
  // 0.1.110 ships authored V2 prebuilts and required top-level play descriptions.
421
- version: "0.1.144",
421
+ version: "0.1.145",
422
422
  apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
423
423
  supportPolicy: {
424
- latest: "0.1.144",
424
+ latest: "0.1.145",
425
425
  minimumSupported: "0.1.53",
426
426
  deprecatedBelow: "0.1.53",
427
427
  commandMinimumSupported: [
@@ -3298,6 +3298,9 @@ var DeeplineClient = class {
3298
3298
  if (input.runId?.trim()) {
3299
3299
  params.set("runId", input.runId.trim());
3300
3300
  }
3301
+ if (input.rowMode === "all") {
3302
+ params.set("rowMode", "all");
3303
+ }
3301
3304
  return await this.http.get(
3302
3305
  `/api/v2/plays/${encodeURIComponent(input.playName)}/sheet?${params.toString()}`
3303
3306
  );
package/dist/index.mjs CHANGED
@@ -348,10 +348,10 @@ var SDK_RELEASE = {
348
348
  // 0.1.108 ships explicit dataset column/tool recompute policy and removes
349
349
  // the SDK enrich generator's one-second stale policy.
350
350
  // 0.1.110 ships authored V2 prebuilts and required top-level play descriptions.
351
- version: "0.1.144",
351
+ version: "0.1.145",
352
352
  apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
353
353
  supportPolicy: {
354
- latest: "0.1.144",
354
+ latest: "0.1.145",
355
355
  minimumSupported: "0.1.53",
356
356
  deprecatedBelow: "0.1.53",
357
357
  commandMinimumSupported: [
@@ -3228,6 +3228,9 @@ var DeeplineClient = class {
3228
3228
  if (input.runId?.trim()) {
3229
3229
  params.set("runId", input.runId.trim());
3230
3230
  }
3231
+ if (input.rowMode === "all") {
3232
+ params.set("rowMode", "all");
3233
+ }
3231
3234
  return await this.http.get(
3232
3235
  `/api/v2/plays/${encodeURIComponent(input.playName)}/sheet?${params.toString()}`
3233
3236
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.144",
3
+ "version": "0.1.145",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {