deepline 0.1.100 → 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
@@ -232,10 +232,15 @@ var SDK_RELEASE = {
232
232
  // 0.1.94 is claimed by PR #1527 — this watch-render fix ships as 0.1.95.
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
- version: "0.1.100",
235
+ // 0.1.101 ships retryable play artifact publish failures and CI retry hardening.
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",
236
241
  apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
237
242
  supportPolicy: {
238
- latest: "0.1.100",
243
+ latest: "0.1.102",
239
244
  minimumSupported: "0.1.53",
240
245
  deprecatedBelow: "0.1.53"
241
246
  }
@@ -386,6 +391,22 @@ var HttpClient = class {
386
391
  parsed = body;
387
392
  }
388
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
+ }
389
410
  const errorValue = typeof parsed === "object" && parsed && "error" in parsed ? parsed.error : void 0;
390
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}`;
391
412
  throw new DeeplineError(msg, response.status, "API_ERROR", {
@@ -446,6 +467,22 @@ var HttpClient = class {
446
467
  }
447
468
  if (!response.ok) {
448
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
+ }
449
486
  const parsed = parseResponseBody(body);
450
487
  throw new DeeplineError(
451
488
  apiErrorMessage(parsed, response.status),
@@ -511,6 +548,31 @@ function parseResponseBody(body) {
511
548
  return body;
512
549
  }
513
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
+ }
514
576
  function apiErrorMessage(parsed, status) {
515
577
  const errorValue = typeof parsed === "object" && parsed && "error" in parsed ? parsed.error : void 0;
516
578
  if (typeof errorValue === "string") {
@@ -606,7 +668,7 @@ function isTransientPlayStreamError(error) {
606
668
  return error.statusCode >= 500 && error.statusCode < 600;
607
669
  }
608
670
  const text = error instanceof Error ? error.message : String(error);
609
- 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(
610
672
  text
611
673
  );
612
674
  }
@@ -838,6 +900,10 @@ function buildSnapshotFromLedger(snapshot) {
838
900
  return {
839
901
  runId: snapshot.runId,
840
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,
841
907
  updatedAt: snapshot.updatedAt ?? snapshot.finishedAt ?? snapshot.startedAt ?? null,
842
908
  logs: snapshot.logTail,
843
909
  totalLogCount: snapshot.totalLogCount,
@@ -4548,14 +4614,25 @@ var import_node_fs5 = require("fs");
4548
4614
  var import_node_path6 = require("path");
4549
4615
 
4550
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
+ }
4551
4624
  function datasetSummaryPercentText(numerator, denominator) {
4552
4625
  return denominator > 0 ? `${numerator}/${denominator} (${Math.round(100 * numerator / denominator)}%)` : "0/0 (0%)";
4553
4626
  }
4554
4627
  function readCount(value) {
4555
4628
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : 0;
4556
4629
  }
4557
- function formatDatasetExecutionStats(raw, denominator) {
4558
- 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 = {
4559
4636
  queued: datasetSummaryPercentText(readCount(raw.queued), denominator),
4560
4637
  running: datasetSummaryPercentText(readCount(raw.running), denominator),
4561
4638
  "completed:executed": datasetSummaryPercentText(
@@ -4576,6 +4653,17 @@ function formatDatasetExecutionStats(raw, denominator) {
4576
4653
  ),
4577
4654
  failed: datasetSummaryPercentText(readCount(raw.failed), denominator)
4578
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;
4579
4667
  }
4580
4668
 
4581
4669
  // src/cli/dataset-stats.ts
@@ -4725,7 +4813,8 @@ function canonicalRowsInfoFromCandidate(input2) {
4725
4813
  complete: rows2.length === totalRows2,
4726
4814
  source: candidate.source,
4727
4815
  datasetId: typeof candidate.value.datasetId === "string" ? candidate.value.datasetId : null,
4728
- 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 } : {}
4729
4818
  };
4730
4819
  }
4731
4820
  if (candidate.serializedOnly) {
@@ -4875,18 +4964,46 @@ function collectCanonicalRowsInfos(statusOrResult) {
4875
4964
  }
4876
4965
  return infos;
4877
4966
  }
4878
- function collectSerializedDatasetRowsInfos(statusOrResult) {
4967
+ function collectPackagedStepDatasetCandidates(statusOrResult) {
4879
4968
  const root = isRecord3(statusOrResult) ? statusOrResult : null;
4880
- const result = isRecord3(root?.result) ? root.result : root;
4881
- if (!result) {
4969
+ if (!root) {
4882
4970
  return [];
4883
4971
  }
4972
+ const pkg = isRecord3(root.package) ? root.package : root;
4973
+ const steps = Array.isArray(pkg.steps) ? pkg.steps : [];
4884
4974
  const candidates = [];
4885
- collectDatasetCandidates({
4886
- value: result,
4887
- path: "result",
4888
- output: candidates
4889
- });
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));
4890
5007
  const seen = /* @__PURE__ */ new Set();
4891
5008
  const infos = [];
4892
5009
  for (const candidate of candidates) {
@@ -5575,6 +5692,7 @@ var import_node_path13 = require("path");
5575
5692
  var import_node_crypto3 = require("crypto");
5576
5693
  var import_node_fs10 = require("fs");
5577
5694
  var import_node_path12 = require("path");
5695
+ var import_sync5 = require("csv-parse/sync");
5578
5696
 
5579
5697
  // src/plays/bundle-play-file.ts
5580
5698
  var import_node_os6 = require("os");
@@ -9373,6 +9491,177 @@ function inputContainsLocalFilePath(value) {
9373
9491
  }
9374
9492
  return false;
9375
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
+ }
9376
9665
  function namedRunNeedsPlayDefinition(input2) {
9377
9666
  return input2.revisionSelector === "latest" || inputContainsLocalFilePath(input2.runtimeInput);
9378
9667
  }
@@ -9607,6 +9896,21 @@ function isRetryablePendingStartFailure(status) {
9607
9896
  if (status.runId && status.runId !== "pending") return false;
9608
9897
  return isTransientPlayStreamError(new Error(playStatusErrorText(status)));
9609
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
+ }
9610
9914
  var TERMINAL_PLAY_STATUSES2 = /* @__PURE__ */ new Set([
9611
9915
  "completed",
9612
9916
  "failed",
@@ -10386,6 +10690,23 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
10386
10690
  progress: input2.progress
10387
10691
  });
10388
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
+ }
10389
10710
  throw error;
10390
10711
  } finally {
10391
10712
  if (timeout) {
@@ -10437,6 +10758,28 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
10437
10758
  function formatInteger(value) {
10438
10759
  return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : String(value ?? "-");
10439
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
+ }
10440
10783
  var BULKY_RETURN_KEYS = /* @__PURE__ */ new Set([
10441
10784
  "contract",
10442
10785
  "staticPipeline",
@@ -10521,8 +10864,7 @@ function formatJsonPreview(value) {
10521
10864
  return [];
10522
10865
  }
10523
10866
  const MAX_CHARS = 4e3;
10524
- const truncated = json.length > MAX_CHARS ? `${json.slice(0, MAX_CHARS)}
10525
- ... truncated; use --json for full output` : json;
10867
+ const truncated = clampJsonPreviewText(json, MAX_CHARS);
10526
10868
  return truncated.split("\n").map((line) => ` ${line}`);
10527
10869
  }
10528
10870
  function formatReturnValue(result) {
@@ -11053,6 +11395,9 @@ function readRecordArray(value) {
11053
11395
  function readRecord(value) {
11054
11396
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
11055
11397
  }
11398
+ function readNonNegativeInteger(value) {
11399
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : null;
11400
+ }
11056
11401
  function formatSummaryScalar(value) {
11057
11402
  if (typeof value === "number") {
11058
11403
  return Number.isFinite(value) ? formatInteger(Math.trunc(value)) : null;
@@ -11085,7 +11430,25 @@ function formatPackageDatasetSummaryLines(summary, indent2 = " ") {
11085
11430
  return [];
11086
11431
  }
11087
11432
  const lines = [];
11088
- 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
+ );
11089
11452
  if (parts.length > 0) {
11090
11453
  lines.push(`${indent2}summary: ${parts.join(" ")}`);
11091
11454
  }
@@ -11146,7 +11509,7 @@ function buildRunPackageTextLines(packaged) {
11146
11509
  ];
11147
11510
  const runError = typeof run.error === "string" && run.error.trim() ? run.error.trim() : null;
11148
11511
  if (runError && (status === "failed" || status === "cancelled")) {
11149
- lines.push(` error: ${runError.slice(0, 200)}`);
11512
+ lines.push(` error: ${truncateErrorForDisplay(runError, runId)}`);
11150
11513
  }
11151
11514
  for (const step of readRecordArray(packaged.steps)) {
11152
11515
  const output2 = step.output && typeof step.output === "object" && !Array.isArray(step.output) ? step.output : null;
@@ -11231,7 +11594,7 @@ function writePlayResult(status, jsonOutput, options) {
11231
11594
  lines.push(...buildInsufficientCreditsSummaryLines({ status, billing }));
11232
11595
  }
11233
11596
  const displayError = formatPlayErrorForDisplay(status, progressError) ?? progressError;
11234
- lines.push(` error: ${displayError.slice(0, 200)}`);
11597
+ lines.push(` error: ${truncateErrorForDisplay(displayError, runId)}`);
11235
11598
  }
11236
11599
  const renderedServerView = renderServerResultView(status.resultView);
11237
11600
  if (result) {
@@ -11402,12 +11765,51 @@ async function fetchBackingDatasetRows(input2) {
11402
11765
  source: `${input2.rowsInfo.source ?? "result.rows"} -> /api/v2/plays/${playName}/sheet?tableNamespace=${tableNamespace}`
11403
11766
  };
11404
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
+ }
11405
11807
  async function exportPlayStatusRows(client2, status, outPath, options = {}) {
11406
11808
  if (!outPath) {
11407
11809
  return null;
11408
11810
  }
11409
11811
  const availableRows = collectSerializedDatasetRowsInfos(status);
11410
- 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;
11411
11813
  if (!rowsInfo && options.datasetPath) {
11412
11814
  const available = availableRows.map((info) => info.source).filter((source) => typeof source === "string");
11413
11815
  throw new DeeplineError(
@@ -12153,6 +12555,12 @@ async function handleFileBackedRun(options) {
12153
12555
  () => (0, import_node_fs10.readFileSync)(absolutePlayPath, "utf-8")
12154
12556
  );
12155
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
+ }
12156
12564
  let graph;
12157
12565
  try {
12158
12566
  graph = await traceCliSpan(
@@ -12310,6 +12718,12 @@ async function handleNamedRun(options) {
12310
12718
  const progress = getActiveCliProgress() ?? createCliProgress(!options.jsonOutput);
12311
12719
  const playName = options.target.name;
12312
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
+ }
12313
12727
  const needsPlayDefinition = namedRunNeedsPlayDefinition({
12314
12728
  runtimeInput,
12315
12729
  revisionSelector: options.revisionSelector