deepline 0.1.101 → 0.1.102

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -233,10 +233,14 @@ var SDK_RELEASE = {
233
233
  // 0.1.98 ships the duplicate-browser-tab fix (default-browser detection).
234
234
  // 0.1.99 ships prebuilt job-change source-column preservation and validation fixes.
235
235
  // 0.1.101 ships retryable play artifact publish failures and CI retry hardening.
236
- version: "0.1.101",
236
+ // 0.1.102 ships the job-change ledger fixes: recovered-dataset export on
237
+ // failed runs, persisted/succeeded/failed row counts, strict local CSV
238
+ // preflight (existence, data rows, quotes, duplicate headers), HTML error
239
+ // scrubbing, and word-boundary watch truncation.
240
+ version: "0.1.102",
237
241
  apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
238
242
  supportPolicy: {
239
- latest: "0.1.101",
243
+ latest: "0.1.102",
240
244
  minimumSupported: "0.1.53",
241
245
  deprecatedBelow: "0.1.53"
242
246
  }
@@ -387,6 +391,22 @@ var HttpClient = class {
387
391
  parsed = body;
388
392
  }
389
393
  if (!response.ok) {
394
+ const htmlError = detectHtmlErrorBody(
395
+ body,
396
+ response.headers.get("content-type")
397
+ );
398
+ if (htmlError) {
399
+ throw new DeeplineError(
400
+ htmlError.message(response.status),
401
+ response.status,
402
+ "API_ERROR",
403
+ {
404
+ htmlErrorPage: true,
405
+ ...htmlError.title ? { title: htmlError.title } : {},
406
+ ...htmlError.workerThrewException ? { workerThrewException: true } : {}
407
+ }
408
+ );
409
+ }
390
410
  const errorValue = typeof parsed === "object" && parsed && "error" in parsed ? parsed.error : void 0;
391
411
  const msg = typeof errorValue === "string" ? errorValue : errorValue && typeof errorValue === "object" && "message" in errorValue && typeof errorValue.message === "string" ? errorValue.message : typeof parsed === "object" && parsed && "message" in parsed && typeof parsed.message === "string" ? parsed.message : `HTTP ${response.status}`;
392
412
  throw new DeeplineError(msg, response.status, "API_ERROR", {
@@ -447,6 +467,22 @@ var HttpClient = class {
447
467
  }
448
468
  if (!response.ok) {
449
469
  const body = await response.text();
470
+ const htmlError = detectHtmlErrorBody(
471
+ body,
472
+ response.headers.get("content-type")
473
+ );
474
+ if (htmlError) {
475
+ throw new DeeplineError(
476
+ htmlError.message(response.status),
477
+ response.status,
478
+ "API_ERROR",
479
+ {
480
+ htmlErrorPage: true,
481
+ ...htmlError.title ? { title: htmlError.title } : {},
482
+ ...htmlError.workerThrewException ? { workerThrewException: true } : {}
483
+ }
484
+ );
485
+ }
450
486
  const parsed = parseResponseBody(body);
451
487
  throw new DeeplineError(
452
488
  apiErrorMessage(parsed, response.status),
@@ -512,6 +548,31 @@ function parseResponseBody(body) {
512
548
  return body;
513
549
  }
514
550
  }
551
+ function detectHtmlErrorBody(body, contentType) {
552
+ const trimmed = body.trim();
553
+ const lower = trimmed.toLowerCase();
554
+ const isHtml = (contentType ?? "").toLowerCase().includes("text/html") || lower.startsWith("<!doctype") || lower.startsWith("<html");
555
+ if (!isHtml) {
556
+ return null;
557
+ }
558
+ const titleMatch = trimmed.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
559
+ const title = titleMatch?.[1]?.replace(/\s+/g, " ").trim() || void 0;
560
+ const workerThrewException = /worker threw exception/i.test(trimmed);
561
+ return {
562
+ title,
563
+ workerThrewException,
564
+ message: (status) => {
565
+ const segments = [`HTTP ${status}`];
566
+ if (workerThrewException) {
567
+ segments.push("Worker threw exception");
568
+ }
569
+ if (title) {
570
+ segments.push(title);
571
+ }
572
+ return `${segments.join(": ")} (Cloudflare HTML error page suppressed)`;
573
+ }
574
+ };
575
+ }
515
576
  function apiErrorMessage(parsed, status) {
516
577
  const errorValue = typeof parsed === "object" && parsed && "error" in parsed ? parsed.error : void 0;
517
578
  if (typeof errorValue === "string") {
@@ -607,7 +668,7 @@ function isTransientPlayStreamError(error) {
607
668
  return error.statusCode >= 500 && error.statusCode < 600;
608
669
  }
609
670
  const text = error instanceof Error ? error.message : String(error);
610
- return /auth validation backend timed out|fetch failed|eaddrnotavail|econnreset|etimedout|eai_again|socket hang up/i.test(
671
+ return /auth validation backend timed out|coordinator \/submit(?:\?[^ ]*)? 5\d\d|Worker threw exception|Internal Server Error|Service Unavailable|fetch failed|eaddrnotavail|econnreset|etimedout|eai_again|socket hang up/i.test(
611
672
  text
612
673
  );
613
674
  }
@@ -839,6 +900,10 @@ function buildSnapshotFromLedger(snapshot) {
839
900
  return {
840
901
  runId: snapshot.runId,
841
902
  status: normalizePlayRunLiveStatus(snapshot.status),
903
+ createdAt: snapshot.createdAt ?? null,
904
+ startedAt: snapshot.startedAt ?? null,
905
+ finishedAt: snapshot.finishedAt ?? null,
906
+ durationMs: snapshot.durationMs ?? null,
842
907
  updatedAt: snapshot.updatedAt ?? snapshot.finishedAt ?? snapshot.startedAt ?? null,
843
908
  logs: snapshot.logTail,
844
909
  totalLogCount: snapshot.totalLogCount,
@@ -4549,14 +4614,25 @@ var import_node_fs5 = require("fs");
4549
4614
  var import_node_path6 = require("path");
4550
4615
 
4551
4616
  // ../shared_libs/plays/dataset-summary.ts
4617
+ function formatDatasetRowCountsLine(counts) {
4618
+ const { persisted, succeeded, failed } = counts;
4619
+ if (succeeded === persisted && failed === 0) {
4620
+ return `${persisted} persisted`;
4621
+ }
4622
+ return `${persisted} persisted (${succeeded} succeeded, ${failed} failed)`;
4623
+ }
4552
4624
  function datasetSummaryPercentText(numerator, denominator) {
4553
4625
  return denominator > 0 ? `${numerator}/${denominator} (${Math.round(100 * numerator / denominator)}%)` : "0/0 (0%)";
4554
4626
  }
4555
4627
  function readCount(value) {
4556
4628
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : 0;
4557
4629
  }
4558
- function formatDatasetExecutionStats(raw, denominator) {
4559
- return {
4630
+ function executionAttemptTotal(raw) {
4631
+ return readCount(raw.queued) + readCount(raw.running) + readCount(raw.completed) + readCount(raw.cached) + readCount(raw.skipped) + readCount(raw.missed) + readCount(raw.failed);
4632
+ }
4633
+ function formatDatasetExecutionStats(raw, _persistedRowTotal) {
4634
+ const denominator = executionAttemptTotal(raw);
4635
+ const stats = {
4560
4636
  queued: datasetSummaryPercentText(readCount(raw.queued), denominator),
4561
4637
  running: datasetSummaryPercentText(readCount(raw.running), denominator),
4562
4638
  "completed:executed": datasetSummaryPercentText(
@@ -4577,6 +4653,17 @@ function formatDatasetExecutionStats(raw, denominator) {
4577
4653
  ),
4578
4654
  failed: datasetSummaryPercentText(readCount(raw.failed), denominator)
4579
4655
  };
4656
+ if (Object.values(stats).some((text) => {
4657
+ const match = /\((\d+)%\)/.exec(text);
4658
+ return match ? Number(match[1]) > 100 : false;
4659
+ })) {
4660
+ throw new Error(
4661
+ `formatDatasetExecutionStats produced a >100% execution stat; column counts are corrupt: ${JSON.stringify(
4662
+ raw
4663
+ )}`
4664
+ );
4665
+ }
4666
+ return stats;
4580
4667
  }
4581
4668
 
4582
4669
  // src/cli/dataset-stats.ts
@@ -4726,7 +4813,8 @@ function canonicalRowsInfoFromCandidate(input2) {
4726
4813
  complete: rows2.length === totalRows2,
4727
4814
  source: candidate.source,
4728
4815
  datasetId: typeof candidate.value.datasetId === "string" ? candidate.value.datasetId : null,
4729
- tableNamespace: typeof candidate.value.tableNamespace === "string" ? candidate.value.tableNamespace : null
4816
+ tableNamespace: typeof candidate.value.tableNamespace === "string" ? candidate.value.tableNamespace : null,
4817
+ ...candidate.value.recovered === true ? { recovered: true } : {}
4730
4818
  };
4731
4819
  }
4732
4820
  if (candidate.serializedOnly) {
@@ -4876,18 +4964,46 @@ function collectCanonicalRowsInfos(statusOrResult) {
4876
4964
  }
4877
4965
  return infos;
4878
4966
  }
4879
- function collectSerializedDatasetRowsInfos(statusOrResult) {
4967
+ function collectPackagedStepDatasetCandidates(statusOrResult) {
4880
4968
  const root = isRecord3(statusOrResult) ? statusOrResult : null;
4881
- const result = isRecord3(root?.result) ? root.result : root;
4882
- if (!result) {
4969
+ if (!root) {
4883
4970
  return [];
4884
4971
  }
4972
+ const pkg = isRecord3(root.package) ? root.package : root;
4973
+ const steps = Array.isArray(pkg.steps) ? pkg.steps : [];
4885
4974
  const candidates = [];
4886
- collectDatasetCandidates({
4887
- value: result,
4888
- path: "result",
4889
- output: candidates
4890
- });
4975
+ for (const step of steps) {
4976
+ if (!isRecord3(step) || !isRecord3(step.output)) {
4977
+ continue;
4978
+ }
4979
+ const output2 = step.output;
4980
+ if (!isPackagedDatasetOutput(output2)) {
4981
+ continue;
4982
+ }
4983
+ const source = typeof output2.path === "string" && output2.path.trim() ? output2.path.trim() : typeof step.id === "string" ? step.id : null;
4984
+ if (!source) {
4985
+ continue;
4986
+ }
4987
+ candidates.push({
4988
+ source,
4989
+ value: output2,
4990
+ total: output2.rowCount ?? (isRecord3(output2.preview) ? output2.preview.totalRows : void 0) ?? (isRecord3(step.progress) ? step.progress.total : void 0)
4991
+ });
4992
+ }
4993
+ return candidates;
4994
+ }
4995
+ function collectSerializedDatasetRowsInfos(statusOrResult) {
4996
+ const root = isRecord3(statusOrResult) ? statusOrResult : null;
4997
+ const result = isRecord3(root?.result) ? root.result : root;
4998
+ const candidates = [];
4999
+ if (result) {
5000
+ collectDatasetCandidates({
5001
+ value: result,
5002
+ path: "result",
5003
+ output: candidates
5004
+ });
5005
+ }
5006
+ candidates.push(...collectPackagedStepDatasetCandidates(statusOrResult));
4891
5007
  const seen = /* @__PURE__ */ new Set();
4892
5008
  const infos = [];
4893
5009
  for (const candidate of candidates) {
@@ -5576,6 +5692,7 @@ var import_node_path13 = require("path");
5576
5692
  var import_node_crypto3 = require("crypto");
5577
5693
  var import_node_fs10 = require("fs");
5578
5694
  var import_node_path12 = require("path");
5695
+ var import_sync5 = require("csv-parse/sync");
5579
5696
 
5580
5697
  // src/plays/bundle-play-file.ts
5581
5698
  var import_node_os6 = require("os");
@@ -9374,6 +9491,177 @@ function inputContainsLocalFilePath(value) {
9374
9491
  }
9375
9492
  return false;
9376
9493
  }
9494
+ function isUrlValue(value) {
9495
+ return /^[a-z][a-z0-9+.-]*:\/\//i.test(value.trim());
9496
+ }
9497
+ function looksLikeStagedFileRef(value) {
9498
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
9499
+ return false;
9500
+ }
9501
+ const record = value;
9502
+ return typeof record.contentHash === "string" || typeof record.contentBase64 === "string" || typeof record.logicalPath === "string" && typeof record.bytes === "number";
9503
+ }
9504
+ var CSV_DATA_INPUT_KEY = "csv";
9505
+ function collectLocalFileInputRefs(value, inputPath, key, out) {
9506
+ if (typeof value === "string") {
9507
+ const trimmed = value.trim();
9508
+ if (!trimmed || isUrlValue(trimmed)) {
9509
+ return;
9510
+ }
9511
+ const keyIsCsvData = key === CSV_DATA_INPUT_KEY;
9512
+ const endsWithCsv = /\.csv$/i.test(trimmed);
9513
+ const looksLikeFile = /\.[a-z0-9]{1,8}$/i.test(trimmed);
9514
+ if (keyIsCsvData) {
9515
+ out.push({ inputPath, value: trimmed, isCsvData: true });
9516
+ } else if (endsWithCsv || looksLikeFile && (0, import_node_fs10.existsSync)((0, import_node_path12.resolve)(trimmed))) {
9517
+ out.push({ inputPath, value: trimmed, isCsvData: false });
9518
+ }
9519
+ return;
9520
+ }
9521
+ if (Array.isArray(value)) {
9522
+ value.forEach(
9523
+ (entry, index) => collectLocalFileInputRefs(entry, `${inputPath}[${index}]`, key, out)
9524
+ );
9525
+ return;
9526
+ }
9527
+ if (looksLikeStagedFileRef(value)) {
9528
+ return;
9529
+ }
9530
+ if (value && typeof value === "object") {
9531
+ for (const [childKey, child] of Object.entries(value)) {
9532
+ collectLocalFileInputRefs(
9533
+ child,
9534
+ inputPath ? `${inputPath}.${childKey}` : childKey,
9535
+ childKey,
9536
+ out
9537
+ );
9538
+ }
9539
+ }
9540
+ }
9541
+ function preflightLocalFileInputs(runtimeInput) {
9542
+ if (CSV_DATA_INPUT_KEY in runtimeInput) {
9543
+ const csvValue = runtimeInput[CSV_DATA_INPUT_KEY];
9544
+ if (typeof csvValue === "string" && csvValue.trim().length === 0) {
9545
+ throw new DeeplineError(
9546
+ `Input ${CSV_DATA_INPUT_KEY} is an empty string. Provide a path to a CSV file (or a CSV URL). No run was created.`,
9547
+ void 0,
9548
+ "PLAY_INPUT_FILE_PREFLIGHT",
9549
+ { inputPath: CSV_DATA_INPUT_KEY, reason: "csv_empty_string" }
9550
+ );
9551
+ }
9552
+ }
9553
+ const refs = [];
9554
+ for (const [key, value] of Object.entries(runtimeInput)) {
9555
+ collectLocalFileInputRefs(value, key, key, refs);
9556
+ }
9557
+ for (const ref of refs) {
9558
+ const absolutePath = (0, import_node_path12.resolve)(ref.value);
9559
+ if (!(0, import_node_fs10.existsSync)(absolutePath)) {
9560
+ throw new DeeplineError(
9561
+ `Input ${ref.inputPath} references a local file that does not exist: ${ref.value} (resolved to ${absolutePath}). No run was created.`,
9562
+ void 0,
9563
+ "PLAY_INPUT_FILE_PREFLIGHT",
9564
+ { inputPath: ref.inputPath, path: ref.value, resolved: absolutePath }
9565
+ );
9566
+ }
9567
+ let stat4;
9568
+ try {
9569
+ stat4 = (0, import_node_fs10.statSync)(absolutePath);
9570
+ } catch (error) {
9571
+ throw new DeeplineError(
9572
+ `Input ${ref.inputPath} references a local file that is not readable: ${ref.value} (${error instanceof Error ? error.message : String(error)}). No run was created.`,
9573
+ void 0,
9574
+ "PLAY_INPUT_FILE_PREFLIGHT",
9575
+ { inputPath: ref.inputPath, path: ref.value }
9576
+ );
9577
+ }
9578
+ if (!stat4.isFile()) {
9579
+ throw new DeeplineError(
9580
+ `Input ${ref.inputPath} references ${ref.value}, which is not a file. No run was created.`,
9581
+ void 0,
9582
+ "PLAY_INPUT_FILE_PREFLIGHT",
9583
+ { inputPath: ref.inputPath, path: ref.value }
9584
+ );
9585
+ }
9586
+ if (!ref.isCsvData) {
9587
+ continue;
9588
+ }
9589
+ preflightCsvDataInput(ref, absolutePath);
9590
+ }
9591
+ }
9592
+ function preflightCsvDataInput(ref, absolutePath) {
9593
+ let content;
9594
+ try {
9595
+ content = (0, import_node_fs10.readFileSync)(absolutePath, "utf-8");
9596
+ } catch (error) {
9597
+ throw new DeeplineError(
9598
+ `Input ${ref.inputPath} CSV ${ref.value} is not readable: ${error instanceof Error ? error.message : String(error)}. No run was created.`,
9599
+ void 0,
9600
+ "PLAY_INPUT_FILE_PREFLIGHT",
9601
+ { inputPath: ref.inputPath, path: ref.value }
9602
+ );
9603
+ }
9604
+ let records;
9605
+ try {
9606
+ records = (0, import_sync5.parse)(content, {
9607
+ bom: true,
9608
+ columns: false,
9609
+ skip_empty_lines: true,
9610
+ relax_column_count: true,
9611
+ // STRICT RFC quoting: an unclosed quote or ragged quoting is a hard
9612
+ // error rather than being swallowed into one giant field.
9613
+ relax_quotes: false,
9614
+ trim: true
9615
+ });
9616
+ } catch (error) {
9617
+ throw new DeeplineError(
9618
+ `Input ${ref.inputPath} could not be parsed as CSV (${ref.value}): ${error instanceof Error ? error.message : String(error)}. This usually means an unclosed quote or ragged quoting. No run was created.`,
9619
+ void 0,
9620
+ "PLAY_INPUT_FILE_PREFLIGHT",
9621
+ { inputPath: ref.inputPath, path: ref.value }
9622
+ );
9623
+ }
9624
+ if (records.length === 0) {
9625
+ throw new DeeplineError(
9626
+ `${ref.value} has a header row but no data rows. No run was created.`,
9627
+ void 0,
9628
+ "PLAY_INPUT_FILE_PREFLIGHT",
9629
+ { inputPath: ref.inputPath, path: ref.value }
9630
+ );
9631
+ }
9632
+ const header = records[0];
9633
+ const seen = /* @__PURE__ */ new Map();
9634
+ for (let i = 0; i < header.length; i++) {
9635
+ const raw = header[i] ?? "";
9636
+ const name = raw.trim();
9637
+ if (name.length === 0) continue;
9638
+ const prior = seen.get(name);
9639
+ if (prior) {
9640
+ throw new DeeplineError(
9641
+ `Input ${ref.inputPath} (${ref.value}) has a duplicate CSV header "${name}" (columns ${prior.index + 1} and ${i + 1}). The second column would silently overwrite the first. Rename one of them. No run was created.`,
9642
+ void 0,
9643
+ "PLAY_INPUT_FILE_PREFLIGHT",
9644
+ {
9645
+ inputPath: ref.inputPath,
9646
+ path: ref.value,
9647
+ duplicateHeader: name,
9648
+ columns: [prior.index + 1, i + 1],
9649
+ rawSpellings: [prior.raw, raw]
9650
+ }
9651
+ );
9652
+ }
9653
+ seen.set(name, { index: i, raw });
9654
+ }
9655
+ const dataRowCount = records.length - 1;
9656
+ if (dataRowCount < 1) {
9657
+ throw new DeeplineError(
9658
+ `${ref.value} has a header row but no data rows. No run was created.`,
9659
+ void 0,
9660
+ "PLAY_INPUT_FILE_PREFLIGHT",
9661
+ { inputPath: ref.inputPath, path: ref.value }
9662
+ );
9663
+ }
9664
+ }
9377
9665
  function namedRunNeedsPlayDefinition(input2) {
9378
9666
  return input2.revisionSelector === "latest" || inputContainsLocalFilePath(input2.runtimeInput);
9379
9667
  }
@@ -9608,6 +9896,21 @@ function isRetryablePendingStartFailure(status) {
9608
9896
  if (status.runId && status.runId !== "pending") return false;
9609
9897
  return isTransientPlayStreamError(new Error(playStatusErrorText(status)));
9610
9898
  }
9899
+ function pendingStartFailureStatus(input2) {
9900
+ const message = input2.error instanceof Error ? input2.error.message : String(input2.error);
9901
+ return {
9902
+ runId: "pending",
9903
+ name: input2.playName,
9904
+ playName: input2.playName,
9905
+ dashboardUrl: input2.dashboardUrl,
9906
+ status: "failed",
9907
+ progress: {
9908
+ status: "failed",
9909
+ logs: [],
9910
+ error: message
9911
+ }
9912
+ };
9913
+ }
9611
9914
  var TERMINAL_PLAY_STATUSES2 = /* @__PURE__ */ new Set([
9612
9915
  "completed",
9613
9916
  "failed",
@@ -10387,6 +10690,23 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
10387
10690
  progress: input2.progress
10388
10691
  });
10389
10692
  }
10693
+ if (!lastKnownWorkflowId && isTransientPlayStreamError(error)) {
10694
+ recordCliTrace({
10695
+ phase: "cli.play_start_stream_transient_failure",
10696
+ ms: Date.now() - startedAt,
10697
+ ok: false,
10698
+ playName: input2.playName,
10699
+ eventCount,
10700
+ firstRunIdMs,
10701
+ lastPhase,
10702
+ reason: error instanceof Error ? error.message : String(error)
10703
+ });
10704
+ return pendingStartFailureStatus({
10705
+ playName: input2.playName,
10706
+ dashboardUrl,
10707
+ error
10708
+ });
10709
+ }
10390
10710
  throw error;
10391
10711
  } finally {
10392
10712
  if (timeout) {
@@ -10438,6 +10758,28 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
10438
10758
  function formatInteger(value) {
10439
10759
  return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : String(value ?? "-");
10440
10760
  }
10761
+ var RUN_ERROR_DISPLAY_MAX_CHARS = 2e3;
10762
+ function truncateErrorForDisplay(message, runId, maxChars = RUN_ERROR_DISPLAY_MAX_CHARS) {
10763
+ const compact = message.replace(/\s+/g, " ").trim();
10764
+ if (compact.length <= maxChars) {
10765
+ return compact;
10766
+ }
10767
+ const slice = compact.slice(0, maxChars);
10768
+ const lastSpace = slice.lastIndexOf(" ");
10769
+ const wordBoundary = lastSpace > 0 ? slice.slice(0, lastSpace) : slice;
10770
+ const fullErrorHint = runId ? ` (full error: deepline runs get ${runId} --full)` : " (full error: deepline runs get <runId> --full)";
10771
+ return `${wordBoundary}\u2026${fullErrorHint}`;
10772
+ }
10773
+ function clampJsonPreviewText(json, maxChars) {
10774
+ if (json.length <= maxChars) {
10775
+ return json;
10776
+ }
10777
+ const slice = json.slice(0, maxChars);
10778
+ const lastNewline = slice.lastIndexOf("\n");
10779
+ const head = lastNewline > 0 ? slice.slice(0, lastNewline) : slice;
10780
+ return `${head}
10781
+ ... truncated; use --json for full output`;
10782
+ }
10441
10783
  var BULKY_RETURN_KEYS = /* @__PURE__ */ new Set([
10442
10784
  "contract",
10443
10785
  "staticPipeline",
@@ -10522,8 +10864,7 @@ function formatJsonPreview(value) {
10522
10864
  return [];
10523
10865
  }
10524
10866
  const MAX_CHARS = 4e3;
10525
- const truncated = json.length > MAX_CHARS ? `${json.slice(0, MAX_CHARS)}
10526
- ... truncated; use --json for full output` : json;
10867
+ const truncated = clampJsonPreviewText(json, MAX_CHARS);
10527
10868
  return truncated.split("\n").map((line) => ` ${line}`);
10528
10869
  }
10529
10870
  function formatReturnValue(result) {
@@ -11054,6 +11395,9 @@ function readRecordArray(value) {
11054
11395
  function readRecord(value) {
11055
11396
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
11056
11397
  }
11398
+ function readNonNegativeInteger(value) {
11399
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : null;
11400
+ }
11057
11401
  function formatSummaryScalar(value) {
11058
11402
  if (typeof value === "number") {
11059
11403
  return Number.isFinite(value) ? formatInteger(Math.trunc(value)) : null;
@@ -11086,7 +11430,25 @@ function formatPackageDatasetSummaryLines(summary, indent2 = " ") {
11086
11430
  return [];
11087
11431
  }
11088
11432
  const lines = [];
11089
- const parts = formatSummaryScalarParts(record, /* @__PURE__ */ new Set(["columnStats"]));
11433
+ const rowCounts = readRecord(record.rowCounts);
11434
+ if (rowCounts) {
11435
+ const persisted = readNonNegativeInteger(rowCounts.persisted);
11436
+ const succeeded = readNonNegativeInteger(rowCounts.succeeded);
11437
+ const failed = readNonNegativeInteger(rowCounts.failed);
11438
+ if (persisted !== null) {
11439
+ lines.push(
11440
+ `${indent2}rows: ${formatDatasetRowCountsLine({
11441
+ persisted,
11442
+ succeeded: succeeded ?? persisted,
11443
+ failed: failed ?? 0
11444
+ })}`
11445
+ );
11446
+ }
11447
+ }
11448
+ const parts = formatSummaryScalarParts(
11449
+ record,
11450
+ /* @__PURE__ */ new Set(["columnStats", "rowCounts"])
11451
+ );
11090
11452
  if (parts.length > 0) {
11091
11453
  lines.push(`${indent2}summary: ${parts.join(" ")}`);
11092
11454
  }
@@ -11147,7 +11509,7 @@ function buildRunPackageTextLines(packaged) {
11147
11509
  ];
11148
11510
  const runError = typeof run.error === "string" && run.error.trim() ? run.error.trim() : null;
11149
11511
  if (runError && (status === "failed" || status === "cancelled")) {
11150
- lines.push(` error: ${runError.slice(0, 200)}`);
11512
+ lines.push(` error: ${truncateErrorForDisplay(runError, runId)}`);
11151
11513
  }
11152
11514
  for (const step of readRecordArray(packaged.steps)) {
11153
11515
  const output2 = step.output && typeof step.output === "object" && !Array.isArray(step.output) ? step.output : null;
@@ -11232,7 +11594,7 @@ function writePlayResult(status, jsonOutput, options) {
11232
11594
  lines.push(...buildInsufficientCreditsSummaryLines({ status, billing }));
11233
11595
  }
11234
11596
  const displayError = formatPlayErrorForDisplay(status, progressError) ?? progressError;
11235
- lines.push(` error: ${displayError.slice(0, 200)}`);
11597
+ lines.push(` error: ${truncateErrorForDisplay(displayError, runId)}`);
11236
11598
  }
11237
11599
  const renderedServerView = renderServerResultView(status.resultView);
11238
11600
  if (result) {
@@ -11403,12 +11765,51 @@ async function fetchBackingDatasetRows(input2) {
11403
11765
  source: `${input2.rowsInfo.source ?? "result.rows"} -> /api/v2/plays/${playName}/sheet?tableNamespace=${tableNamespace}`
11404
11766
  };
11405
11767
  }
11768
+ function resolveDatasetByName(available, datasetPath) {
11769
+ const target = datasetPath.trim();
11770
+ const exact = available.find((info) => info.source === target);
11771
+ if (exact) {
11772
+ return exact;
11773
+ }
11774
+ const byNamespace = available.find(
11775
+ (info) => info.tableNamespace && info.tableNamespace === target
11776
+ );
11777
+ if (byNamespace) {
11778
+ return byNamespace;
11779
+ }
11780
+ const trailing = target.split(".").filter(Boolean).at(-1);
11781
+ if (!trailing) {
11782
+ return null;
11783
+ }
11784
+ const byTrailing = available.find(
11785
+ (info) => info.tableNamespace === trailing || typeof info.source === "string" && info.source.split(".").filter(Boolean).at(-1) === trailing
11786
+ );
11787
+ if (byTrailing) {
11788
+ return byTrailing;
11789
+ }
11790
+ if (target.split(".").filter(Boolean)[0] === "result") {
11791
+ const recovered = available.filter((info) => info.recovered);
11792
+ if (recovered.length === 1) {
11793
+ return recovered[0];
11794
+ }
11795
+ if (recovered.length > 1) {
11796
+ const names = recovered.map((info) => info.tableNamespace ?? info.source).filter((name) => typeof name === "string");
11797
+ throw new DeeplineError(
11798
+ `Run returned multiple recovered datasets; '${target}' is ambiguous. Choose one with --dataset <path>: ${names.join(", ")}.`,
11799
+ void 0,
11800
+ "RUN_EXPORT_DATASET_AMBIGUOUS",
11801
+ { dataset: target, available: names }
11802
+ );
11803
+ }
11804
+ }
11805
+ return null;
11806
+ }
11406
11807
  async function exportPlayStatusRows(client2, status, outPath, options = {}) {
11407
11808
  if (!outPath) {
11408
11809
  return null;
11409
11810
  }
11410
11811
  const availableRows = collectSerializedDatasetRowsInfos(status);
11411
- const rowsInfo = options.datasetPath ? availableRows.find((info) => info.source === options.datasetPath) ?? null : availableRows.length === 1 ? availableRows[0] : null;
11812
+ const rowsInfo = options.datasetPath ? resolveDatasetByName(availableRows, options.datasetPath) ?? null : availableRows.length === 1 ? availableRows[0] : null;
11412
11813
  if (!rowsInfo && options.datasetPath) {
11413
11814
  const available = availableRows.map((info) => info.source).filter((source) => typeof source === "string");
11414
11815
  throw new DeeplineError(
@@ -12154,6 +12555,12 @@ async function handleFileBackedRun(options) {
12154
12555
  () => (0, import_node_fs10.readFileSync)(absolutePlayPath, "utf-8")
12155
12556
  );
12156
12557
  const runtimeInput = options.input ? { ...options.input } : {};
12558
+ try {
12559
+ preflightLocalFileInputs(runtimeInput);
12560
+ } catch (error) {
12561
+ console.error(error instanceof Error ? error.message : String(error));
12562
+ return 1;
12563
+ }
12157
12564
  let graph;
12158
12565
  try {
12159
12566
  graph = await traceCliSpan(
@@ -12311,6 +12718,12 @@ async function handleNamedRun(options) {
12311
12718
  const progress = getActiveCliProgress() ?? createCliProgress(!options.jsonOutput);
12312
12719
  const playName = options.target.name;
12313
12720
  const runtimeInput = options.input ? { ...options.input } : {};
12721
+ try {
12722
+ preflightLocalFileInputs(runtimeInput);
12723
+ } catch (error) {
12724
+ console.error(error instanceof Error ? error.message : String(error));
12725
+ return 1;
12726
+ }
12314
12727
  const needsPlayDefinition = namedRunNeedsPlayDefinition({
12315
12728
  runtimeInput,
12316
12729
  revisionSelector: options.revisionSelector