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.
@@ -209,10 +209,15 @@ var SDK_RELEASE = {
209
209
  // 0.1.94 is claimed by PR #1527 — this watch-render fix ships as 0.1.95.
210
210
  // 0.1.98 ships the duplicate-browser-tab fix (default-browser detection).
211
211
  // 0.1.99 ships prebuilt job-change source-column preservation and validation fixes.
212
- version: "0.1.100",
212
+ // 0.1.101 ships retryable play artifact publish failures and CI retry hardening.
213
+ // 0.1.102 ships the job-change ledger fixes: recovered-dataset export on
214
+ // failed runs, persisted/succeeded/failed row counts, strict local CSV
215
+ // preflight (existence, data rows, quotes, duplicate headers), HTML error
216
+ // scrubbing, and word-boundary watch truncation.
217
+ version: "0.1.102",
213
218
  apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
214
219
  supportPolicy: {
215
- latest: "0.1.100",
220
+ latest: "0.1.102",
216
221
  minimumSupported: "0.1.53",
217
222
  deprecatedBelow: "0.1.53"
218
223
  }
@@ -363,6 +368,22 @@ var HttpClient = class {
363
368
  parsed = body;
364
369
  }
365
370
  if (!response.ok) {
371
+ const htmlError = detectHtmlErrorBody(
372
+ body,
373
+ response.headers.get("content-type")
374
+ );
375
+ if (htmlError) {
376
+ throw new DeeplineError(
377
+ htmlError.message(response.status),
378
+ response.status,
379
+ "API_ERROR",
380
+ {
381
+ htmlErrorPage: true,
382
+ ...htmlError.title ? { title: htmlError.title } : {},
383
+ ...htmlError.workerThrewException ? { workerThrewException: true } : {}
384
+ }
385
+ );
386
+ }
366
387
  const errorValue = typeof parsed === "object" && parsed && "error" in parsed ? parsed.error : void 0;
367
388
  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}`;
368
389
  throw new DeeplineError(msg, response.status, "API_ERROR", {
@@ -423,6 +444,22 @@ var HttpClient = class {
423
444
  }
424
445
  if (!response.ok) {
425
446
  const body = await response.text();
447
+ const htmlError = detectHtmlErrorBody(
448
+ body,
449
+ response.headers.get("content-type")
450
+ );
451
+ if (htmlError) {
452
+ throw new DeeplineError(
453
+ htmlError.message(response.status),
454
+ response.status,
455
+ "API_ERROR",
456
+ {
457
+ htmlErrorPage: true,
458
+ ...htmlError.title ? { title: htmlError.title } : {},
459
+ ...htmlError.workerThrewException ? { workerThrewException: true } : {}
460
+ }
461
+ );
462
+ }
426
463
  const parsed = parseResponseBody(body);
427
464
  throw new DeeplineError(
428
465
  apiErrorMessage(parsed, response.status),
@@ -488,6 +525,31 @@ function parseResponseBody(body) {
488
525
  return body;
489
526
  }
490
527
  }
528
+ function detectHtmlErrorBody(body, contentType) {
529
+ const trimmed = body.trim();
530
+ const lower = trimmed.toLowerCase();
531
+ const isHtml = (contentType ?? "").toLowerCase().includes("text/html") || lower.startsWith("<!doctype") || lower.startsWith("<html");
532
+ if (!isHtml) {
533
+ return null;
534
+ }
535
+ const titleMatch = trimmed.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
536
+ const title = titleMatch?.[1]?.replace(/\s+/g, " ").trim() || void 0;
537
+ const workerThrewException = /worker threw exception/i.test(trimmed);
538
+ return {
539
+ title,
540
+ workerThrewException,
541
+ message: (status) => {
542
+ const segments = [`HTTP ${status}`];
543
+ if (workerThrewException) {
544
+ segments.push("Worker threw exception");
545
+ }
546
+ if (title) {
547
+ segments.push(title);
548
+ }
549
+ return `${segments.join(": ")} (Cloudflare HTML error page suppressed)`;
550
+ }
551
+ };
552
+ }
491
553
  function apiErrorMessage(parsed, status) {
492
554
  const errorValue = typeof parsed === "object" && parsed && "error" in parsed ? parsed.error : void 0;
493
555
  if (typeof errorValue === "string") {
@@ -583,7 +645,7 @@ function isTransientPlayStreamError(error) {
583
645
  return error.statusCode >= 500 && error.statusCode < 600;
584
646
  }
585
647
  const text = error instanceof Error ? error.message : String(error);
586
- return /auth validation backend timed out|fetch failed|eaddrnotavail|econnreset|etimedout|eai_again|socket hang up/i.test(
648
+ 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(
587
649
  text
588
650
  );
589
651
  }
@@ -815,6 +877,10 @@ function buildSnapshotFromLedger(snapshot) {
815
877
  return {
816
878
  runId: snapshot.runId,
817
879
  status: normalizePlayRunLiveStatus(snapshot.status),
880
+ createdAt: snapshot.createdAt ?? null,
881
+ startedAt: snapshot.startedAt ?? null,
882
+ finishedAt: snapshot.finishedAt ?? null,
883
+ durationMs: snapshot.durationMs ?? null,
818
884
  updatedAt: snapshot.updatedAt ?? snapshot.finishedAt ?? snapshot.startedAt ?? null,
819
885
  logs: snapshot.logTail,
820
886
  totalLogCount: snapshot.totalLogCount,
@@ -4537,14 +4603,25 @@ import { writeFileSync as writeFileSync4 } from "fs";
4537
4603
  import { resolve as resolve4 } from "path";
4538
4604
 
4539
4605
  // ../shared_libs/plays/dataset-summary.ts
4606
+ function formatDatasetRowCountsLine(counts) {
4607
+ const { persisted, succeeded, failed } = counts;
4608
+ if (succeeded === persisted && failed === 0) {
4609
+ return `${persisted} persisted`;
4610
+ }
4611
+ return `${persisted} persisted (${succeeded} succeeded, ${failed} failed)`;
4612
+ }
4540
4613
  function datasetSummaryPercentText(numerator, denominator) {
4541
4614
  return denominator > 0 ? `${numerator}/${denominator} (${Math.round(100 * numerator / denominator)}%)` : "0/0 (0%)";
4542
4615
  }
4543
4616
  function readCount(value) {
4544
4617
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : 0;
4545
4618
  }
4546
- function formatDatasetExecutionStats(raw, denominator) {
4547
- return {
4619
+ function executionAttemptTotal(raw) {
4620
+ return readCount(raw.queued) + readCount(raw.running) + readCount(raw.completed) + readCount(raw.cached) + readCount(raw.skipped) + readCount(raw.missed) + readCount(raw.failed);
4621
+ }
4622
+ function formatDatasetExecutionStats(raw, _persistedRowTotal) {
4623
+ const denominator = executionAttemptTotal(raw);
4624
+ const stats = {
4548
4625
  queued: datasetSummaryPercentText(readCount(raw.queued), denominator),
4549
4626
  running: datasetSummaryPercentText(readCount(raw.running), denominator),
4550
4627
  "completed:executed": datasetSummaryPercentText(
@@ -4565,6 +4642,17 @@ function formatDatasetExecutionStats(raw, denominator) {
4565
4642
  ),
4566
4643
  failed: datasetSummaryPercentText(readCount(raw.failed), denominator)
4567
4644
  };
4645
+ if (Object.values(stats).some((text) => {
4646
+ const match = /\((\d+)%\)/.exec(text);
4647
+ return match ? Number(match[1]) > 100 : false;
4648
+ })) {
4649
+ throw new Error(
4650
+ `formatDatasetExecutionStats produced a >100% execution stat; column counts are corrupt: ${JSON.stringify(
4651
+ raw
4652
+ )}`
4653
+ );
4654
+ }
4655
+ return stats;
4568
4656
  }
4569
4657
 
4570
4658
  // src/cli/dataset-stats.ts
@@ -4714,7 +4802,8 @@ function canonicalRowsInfoFromCandidate(input2) {
4714
4802
  complete: rows2.length === totalRows2,
4715
4803
  source: candidate.source,
4716
4804
  datasetId: typeof candidate.value.datasetId === "string" ? candidate.value.datasetId : null,
4717
- tableNamespace: typeof candidate.value.tableNamespace === "string" ? candidate.value.tableNamespace : null
4805
+ tableNamespace: typeof candidate.value.tableNamespace === "string" ? candidate.value.tableNamespace : null,
4806
+ ...candidate.value.recovered === true ? { recovered: true } : {}
4718
4807
  };
4719
4808
  }
4720
4809
  if (candidate.serializedOnly) {
@@ -4864,18 +4953,46 @@ function collectCanonicalRowsInfos(statusOrResult) {
4864
4953
  }
4865
4954
  return infos;
4866
4955
  }
4867
- function collectSerializedDatasetRowsInfos(statusOrResult) {
4956
+ function collectPackagedStepDatasetCandidates(statusOrResult) {
4868
4957
  const root = isRecord3(statusOrResult) ? statusOrResult : null;
4869
- const result = isRecord3(root?.result) ? root.result : root;
4870
- if (!result) {
4958
+ if (!root) {
4871
4959
  return [];
4872
4960
  }
4961
+ const pkg = isRecord3(root.package) ? root.package : root;
4962
+ const steps = Array.isArray(pkg.steps) ? pkg.steps : [];
4873
4963
  const candidates = [];
4874
- collectDatasetCandidates({
4875
- value: result,
4876
- path: "result",
4877
- output: candidates
4878
- });
4964
+ for (const step of steps) {
4965
+ if (!isRecord3(step) || !isRecord3(step.output)) {
4966
+ continue;
4967
+ }
4968
+ const output2 = step.output;
4969
+ if (!isPackagedDatasetOutput(output2)) {
4970
+ continue;
4971
+ }
4972
+ const source = typeof output2.path === "string" && output2.path.trim() ? output2.path.trim() : typeof step.id === "string" ? step.id : null;
4973
+ if (!source) {
4974
+ continue;
4975
+ }
4976
+ candidates.push({
4977
+ source,
4978
+ value: output2,
4979
+ total: output2.rowCount ?? (isRecord3(output2.preview) ? output2.preview.totalRows : void 0) ?? (isRecord3(step.progress) ? step.progress.total : void 0)
4980
+ });
4981
+ }
4982
+ return candidates;
4983
+ }
4984
+ function collectSerializedDatasetRowsInfos(statusOrResult) {
4985
+ const root = isRecord3(statusOrResult) ? statusOrResult : null;
4986
+ const result = isRecord3(root?.result) ? root.result : root;
4987
+ const candidates = [];
4988
+ if (result) {
4989
+ collectDatasetCandidates({
4990
+ value: result,
4991
+ path: "result",
4992
+ output: candidates
4993
+ });
4994
+ }
4995
+ candidates.push(...collectPackagedStepDatasetCandidates(statusOrResult));
4879
4996
  const seen = /* @__PURE__ */ new Set();
4880
4997
  const infos = [];
4881
4998
  for (const candidate of candidates) {
@@ -5574,9 +5691,11 @@ import {
5574
5691
  readFileSync as readFileSync6,
5575
5692
  readdirSync,
5576
5693
  realpathSync,
5694
+ statSync as statSync2,
5577
5695
  writeFileSync as writeFileSync7
5578
5696
  } from "fs";
5579
5697
  import { basename as basename3, dirname as dirname8, join as join7, resolve as resolve10 } from "path";
5698
+ import { parse as parseCsvSync2 } from "csv-parse/sync";
5580
5699
 
5581
5700
  // src/plays/bundle-play-file.ts
5582
5701
  import { tmpdir as tmpdir2 } from "os";
@@ -9389,6 +9508,177 @@ function inputContainsLocalFilePath(value) {
9389
9508
  }
9390
9509
  return false;
9391
9510
  }
9511
+ function isUrlValue(value) {
9512
+ return /^[a-z][a-z0-9+.-]*:\/\//i.test(value.trim());
9513
+ }
9514
+ function looksLikeStagedFileRef(value) {
9515
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
9516
+ return false;
9517
+ }
9518
+ const record = value;
9519
+ return typeof record.contentHash === "string" || typeof record.contentBase64 === "string" || typeof record.logicalPath === "string" && typeof record.bytes === "number";
9520
+ }
9521
+ var CSV_DATA_INPUT_KEY = "csv";
9522
+ function collectLocalFileInputRefs(value, inputPath, key, out) {
9523
+ if (typeof value === "string") {
9524
+ const trimmed = value.trim();
9525
+ if (!trimmed || isUrlValue(trimmed)) {
9526
+ return;
9527
+ }
9528
+ const keyIsCsvData = key === CSV_DATA_INPUT_KEY;
9529
+ const endsWithCsv = /\.csv$/i.test(trimmed);
9530
+ const looksLikeFile = /\.[a-z0-9]{1,8}$/i.test(trimmed);
9531
+ if (keyIsCsvData) {
9532
+ out.push({ inputPath, value: trimmed, isCsvData: true });
9533
+ } else if (endsWithCsv || looksLikeFile && existsSync7(resolve10(trimmed))) {
9534
+ out.push({ inputPath, value: trimmed, isCsvData: false });
9535
+ }
9536
+ return;
9537
+ }
9538
+ if (Array.isArray(value)) {
9539
+ value.forEach(
9540
+ (entry, index) => collectLocalFileInputRefs(entry, `${inputPath}[${index}]`, key, out)
9541
+ );
9542
+ return;
9543
+ }
9544
+ if (looksLikeStagedFileRef(value)) {
9545
+ return;
9546
+ }
9547
+ if (value && typeof value === "object") {
9548
+ for (const [childKey, child] of Object.entries(value)) {
9549
+ collectLocalFileInputRefs(
9550
+ child,
9551
+ inputPath ? `${inputPath}.${childKey}` : childKey,
9552
+ childKey,
9553
+ out
9554
+ );
9555
+ }
9556
+ }
9557
+ }
9558
+ function preflightLocalFileInputs(runtimeInput) {
9559
+ if (CSV_DATA_INPUT_KEY in runtimeInput) {
9560
+ const csvValue = runtimeInput[CSV_DATA_INPUT_KEY];
9561
+ if (typeof csvValue === "string" && csvValue.trim().length === 0) {
9562
+ throw new DeeplineError(
9563
+ `Input ${CSV_DATA_INPUT_KEY} is an empty string. Provide a path to a CSV file (or a CSV URL). No run was created.`,
9564
+ void 0,
9565
+ "PLAY_INPUT_FILE_PREFLIGHT",
9566
+ { inputPath: CSV_DATA_INPUT_KEY, reason: "csv_empty_string" }
9567
+ );
9568
+ }
9569
+ }
9570
+ const refs = [];
9571
+ for (const [key, value] of Object.entries(runtimeInput)) {
9572
+ collectLocalFileInputRefs(value, key, key, refs);
9573
+ }
9574
+ for (const ref of refs) {
9575
+ const absolutePath = resolve10(ref.value);
9576
+ if (!existsSync7(absolutePath)) {
9577
+ throw new DeeplineError(
9578
+ `Input ${ref.inputPath} references a local file that does not exist: ${ref.value} (resolved to ${absolutePath}). No run was created.`,
9579
+ void 0,
9580
+ "PLAY_INPUT_FILE_PREFLIGHT",
9581
+ { inputPath: ref.inputPath, path: ref.value, resolved: absolutePath }
9582
+ );
9583
+ }
9584
+ let stat4;
9585
+ try {
9586
+ stat4 = statSync2(absolutePath);
9587
+ } catch (error) {
9588
+ throw new DeeplineError(
9589
+ `Input ${ref.inputPath} references a local file that is not readable: ${ref.value} (${error instanceof Error ? error.message : String(error)}). No run was created.`,
9590
+ void 0,
9591
+ "PLAY_INPUT_FILE_PREFLIGHT",
9592
+ { inputPath: ref.inputPath, path: ref.value }
9593
+ );
9594
+ }
9595
+ if (!stat4.isFile()) {
9596
+ throw new DeeplineError(
9597
+ `Input ${ref.inputPath} references ${ref.value}, which is not a file. No run was created.`,
9598
+ void 0,
9599
+ "PLAY_INPUT_FILE_PREFLIGHT",
9600
+ { inputPath: ref.inputPath, path: ref.value }
9601
+ );
9602
+ }
9603
+ if (!ref.isCsvData) {
9604
+ continue;
9605
+ }
9606
+ preflightCsvDataInput(ref, absolutePath);
9607
+ }
9608
+ }
9609
+ function preflightCsvDataInput(ref, absolutePath) {
9610
+ let content;
9611
+ try {
9612
+ content = readFileSync6(absolutePath, "utf-8");
9613
+ } catch (error) {
9614
+ throw new DeeplineError(
9615
+ `Input ${ref.inputPath} CSV ${ref.value} is not readable: ${error instanceof Error ? error.message : String(error)}. No run was created.`,
9616
+ void 0,
9617
+ "PLAY_INPUT_FILE_PREFLIGHT",
9618
+ { inputPath: ref.inputPath, path: ref.value }
9619
+ );
9620
+ }
9621
+ let records;
9622
+ try {
9623
+ records = parseCsvSync2(content, {
9624
+ bom: true,
9625
+ columns: false,
9626
+ skip_empty_lines: true,
9627
+ relax_column_count: true,
9628
+ // STRICT RFC quoting: an unclosed quote or ragged quoting is a hard
9629
+ // error rather than being swallowed into one giant field.
9630
+ relax_quotes: false,
9631
+ trim: true
9632
+ });
9633
+ } catch (error) {
9634
+ throw new DeeplineError(
9635
+ `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.`,
9636
+ void 0,
9637
+ "PLAY_INPUT_FILE_PREFLIGHT",
9638
+ { inputPath: ref.inputPath, path: ref.value }
9639
+ );
9640
+ }
9641
+ if (records.length === 0) {
9642
+ throw new DeeplineError(
9643
+ `${ref.value} has a header row but no data rows. No run was created.`,
9644
+ void 0,
9645
+ "PLAY_INPUT_FILE_PREFLIGHT",
9646
+ { inputPath: ref.inputPath, path: ref.value }
9647
+ );
9648
+ }
9649
+ const header = records[0];
9650
+ const seen = /* @__PURE__ */ new Map();
9651
+ for (let i = 0; i < header.length; i++) {
9652
+ const raw = header[i] ?? "";
9653
+ const name = raw.trim();
9654
+ if (name.length === 0) continue;
9655
+ const prior = seen.get(name);
9656
+ if (prior) {
9657
+ throw new DeeplineError(
9658
+ `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.`,
9659
+ void 0,
9660
+ "PLAY_INPUT_FILE_PREFLIGHT",
9661
+ {
9662
+ inputPath: ref.inputPath,
9663
+ path: ref.value,
9664
+ duplicateHeader: name,
9665
+ columns: [prior.index + 1, i + 1],
9666
+ rawSpellings: [prior.raw, raw]
9667
+ }
9668
+ );
9669
+ }
9670
+ seen.set(name, { index: i, raw });
9671
+ }
9672
+ const dataRowCount = records.length - 1;
9673
+ if (dataRowCount < 1) {
9674
+ throw new DeeplineError(
9675
+ `${ref.value} has a header row but no data rows. No run was created.`,
9676
+ void 0,
9677
+ "PLAY_INPUT_FILE_PREFLIGHT",
9678
+ { inputPath: ref.inputPath, path: ref.value }
9679
+ );
9680
+ }
9681
+ }
9392
9682
  function namedRunNeedsPlayDefinition(input2) {
9393
9683
  return input2.revisionSelector === "latest" || inputContainsLocalFilePath(input2.runtimeInput);
9394
9684
  }
@@ -9623,6 +9913,21 @@ function isRetryablePendingStartFailure(status) {
9623
9913
  if (status.runId && status.runId !== "pending") return false;
9624
9914
  return isTransientPlayStreamError(new Error(playStatusErrorText(status)));
9625
9915
  }
9916
+ function pendingStartFailureStatus(input2) {
9917
+ const message = input2.error instanceof Error ? input2.error.message : String(input2.error);
9918
+ return {
9919
+ runId: "pending",
9920
+ name: input2.playName,
9921
+ playName: input2.playName,
9922
+ dashboardUrl: input2.dashboardUrl,
9923
+ status: "failed",
9924
+ progress: {
9925
+ status: "failed",
9926
+ logs: [],
9927
+ error: message
9928
+ }
9929
+ };
9930
+ }
9626
9931
  var TERMINAL_PLAY_STATUSES2 = /* @__PURE__ */ new Set([
9627
9932
  "completed",
9628
9933
  "failed",
@@ -10402,6 +10707,23 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
10402
10707
  progress: input2.progress
10403
10708
  });
10404
10709
  }
10710
+ if (!lastKnownWorkflowId && isTransientPlayStreamError(error)) {
10711
+ recordCliTrace({
10712
+ phase: "cli.play_start_stream_transient_failure",
10713
+ ms: Date.now() - startedAt,
10714
+ ok: false,
10715
+ playName: input2.playName,
10716
+ eventCount,
10717
+ firstRunIdMs,
10718
+ lastPhase,
10719
+ reason: error instanceof Error ? error.message : String(error)
10720
+ });
10721
+ return pendingStartFailureStatus({
10722
+ playName: input2.playName,
10723
+ dashboardUrl,
10724
+ error
10725
+ });
10726
+ }
10405
10727
  throw error;
10406
10728
  } finally {
10407
10729
  if (timeout) {
@@ -10453,6 +10775,28 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
10453
10775
  function formatInteger(value) {
10454
10776
  return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : String(value ?? "-");
10455
10777
  }
10778
+ var RUN_ERROR_DISPLAY_MAX_CHARS = 2e3;
10779
+ function truncateErrorForDisplay(message, runId, maxChars = RUN_ERROR_DISPLAY_MAX_CHARS) {
10780
+ const compact = message.replace(/\s+/g, " ").trim();
10781
+ if (compact.length <= maxChars) {
10782
+ return compact;
10783
+ }
10784
+ const slice = compact.slice(0, maxChars);
10785
+ const lastSpace = slice.lastIndexOf(" ");
10786
+ const wordBoundary = lastSpace > 0 ? slice.slice(0, lastSpace) : slice;
10787
+ const fullErrorHint = runId ? ` (full error: deepline runs get ${runId} --full)` : " (full error: deepline runs get <runId> --full)";
10788
+ return `${wordBoundary}\u2026${fullErrorHint}`;
10789
+ }
10790
+ function clampJsonPreviewText(json, maxChars) {
10791
+ if (json.length <= maxChars) {
10792
+ return json;
10793
+ }
10794
+ const slice = json.slice(0, maxChars);
10795
+ const lastNewline = slice.lastIndexOf("\n");
10796
+ const head = lastNewline > 0 ? slice.slice(0, lastNewline) : slice;
10797
+ return `${head}
10798
+ ... truncated; use --json for full output`;
10799
+ }
10456
10800
  var BULKY_RETURN_KEYS = /* @__PURE__ */ new Set([
10457
10801
  "contract",
10458
10802
  "staticPipeline",
@@ -10537,8 +10881,7 @@ function formatJsonPreview(value) {
10537
10881
  return [];
10538
10882
  }
10539
10883
  const MAX_CHARS = 4e3;
10540
- const truncated = json.length > MAX_CHARS ? `${json.slice(0, MAX_CHARS)}
10541
- ... truncated; use --json for full output` : json;
10884
+ const truncated = clampJsonPreviewText(json, MAX_CHARS);
10542
10885
  return truncated.split("\n").map((line) => ` ${line}`);
10543
10886
  }
10544
10887
  function formatReturnValue(result) {
@@ -11069,6 +11412,9 @@ function readRecordArray(value) {
11069
11412
  function readRecord(value) {
11070
11413
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
11071
11414
  }
11415
+ function readNonNegativeInteger(value) {
11416
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : null;
11417
+ }
11072
11418
  function formatSummaryScalar(value) {
11073
11419
  if (typeof value === "number") {
11074
11420
  return Number.isFinite(value) ? formatInteger(Math.trunc(value)) : null;
@@ -11101,7 +11447,25 @@ function formatPackageDatasetSummaryLines(summary, indent2 = " ") {
11101
11447
  return [];
11102
11448
  }
11103
11449
  const lines = [];
11104
- const parts = formatSummaryScalarParts(record, /* @__PURE__ */ new Set(["columnStats"]));
11450
+ const rowCounts = readRecord(record.rowCounts);
11451
+ if (rowCounts) {
11452
+ const persisted = readNonNegativeInteger(rowCounts.persisted);
11453
+ const succeeded = readNonNegativeInteger(rowCounts.succeeded);
11454
+ const failed = readNonNegativeInteger(rowCounts.failed);
11455
+ if (persisted !== null) {
11456
+ lines.push(
11457
+ `${indent2}rows: ${formatDatasetRowCountsLine({
11458
+ persisted,
11459
+ succeeded: succeeded ?? persisted,
11460
+ failed: failed ?? 0
11461
+ })}`
11462
+ );
11463
+ }
11464
+ }
11465
+ const parts = formatSummaryScalarParts(
11466
+ record,
11467
+ /* @__PURE__ */ new Set(["columnStats", "rowCounts"])
11468
+ );
11105
11469
  if (parts.length > 0) {
11106
11470
  lines.push(`${indent2}summary: ${parts.join(" ")}`);
11107
11471
  }
@@ -11162,7 +11526,7 @@ function buildRunPackageTextLines(packaged) {
11162
11526
  ];
11163
11527
  const runError = typeof run.error === "string" && run.error.trim() ? run.error.trim() : null;
11164
11528
  if (runError && (status === "failed" || status === "cancelled")) {
11165
- lines.push(` error: ${runError.slice(0, 200)}`);
11529
+ lines.push(` error: ${truncateErrorForDisplay(runError, runId)}`);
11166
11530
  }
11167
11531
  for (const step of readRecordArray(packaged.steps)) {
11168
11532
  const output2 = step.output && typeof step.output === "object" && !Array.isArray(step.output) ? step.output : null;
@@ -11247,7 +11611,7 @@ function writePlayResult(status, jsonOutput, options) {
11247
11611
  lines.push(...buildInsufficientCreditsSummaryLines({ status, billing }));
11248
11612
  }
11249
11613
  const displayError = formatPlayErrorForDisplay(status, progressError) ?? progressError;
11250
- lines.push(` error: ${displayError.slice(0, 200)}`);
11614
+ lines.push(` error: ${truncateErrorForDisplay(displayError, runId)}`);
11251
11615
  }
11252
11616
  const renderedServerView = renderServerResultView(status.resultView);
11253
11617
  if (result) {
@@ -11418,12 +11782,51 @@ async function fetchBackingDatasetRows(input2) {
11418
11782
  source: `${input2.rowsInfo.source ?? "result.rows"} -> /api/v2/plays/${playName}/sheet?tableNamespace=${tableNamespace}`
11419
11783
  };
11420
11784
  }
11785
+ function resolveDatasetByName(available, datasetPath) {
11786
+ const target = datasetPath.trim();
11787
+ const exact = available.find((info) => info.source === target);
11788
+ if (exact) {
11789
+ return exact;
11790
+ }
11791
+ const byNamespace = available.find(
11792
+ (info) => info.tableNamespace && info.tableNamespace === target
11793
+ );
11794
+ if (byNamespace) {
11795
+ return byNamespace;
11796
+ }
11797
+ const trailing = target.split(".").filter(Boolean).at(-1);
11798
+ if (!trailing) {
11799
+ return null;
11800
+ }
11801
+ const byTrailing = available.find(
11802
+ (info) => info.tableNamespace === trailing || typeof info.source === "string" && info.source.split(".").filter(Boolean).at(-1) === trailing
11803
+ );
11804
+ if (byTrailing) {
11805
+ return byTrailing;
11806
+ }
11807
+ if (target.split(".").filter(Boolean)[0] === "result") {
11808
+ const recovered = available.filter((info) => info.recovered);
11809
+ if (recovered.length === 1) {
11810
+ return recovered[0];
11811
+ }
11812
+ if (recovered.length > 1) {
11813
+ const names = recovered.map((info) => info.tableNamespace ?? info.source).filter((name) => typeof name === "string");
11814
+ throw new DeeplineError(
11815
+ `Run returned multiple recovered datasets; '${target}' is ambiguous. Choose one with --dataset <path>: ${names.join(", ")}.`,
11816
+ void 0,
11817
+ "RUN_EXPORT_DATASET_AMBIGUOUS",
11818
+ { dataset: target, available: names }
11819
+ );
11820
+ }
11821
+ }
11822
+ return null;
11823
+ }
11421
11824
  async function exportPlayStatusRows(client2, status, outPath, options = {}) {
11422
11825
  if (!outPath) {
11423
11826
  return null;
11424
11827
  }
11425
11828
  const availableRows = collectSerializedDatasetRowsInfos(status);
11426
- const rowsInfo = options.datasetPath ? availableRows.find((info) => info.source === options.datasetPath) ?? null : availableRows.length === 1 ? availableRows[0] : null;
11829
+ const rowsInfo = options.datasetPath ? resolveDatasetByName(availableRows, options.datasetPath) ?? null : availableRows.length === 1 ? availableRows[0] : null;
11427
11830
  if (!rowsInfo && options.datasetPath) {
11428
11831
  const available = availableRows.map((info) => info.source).filter((source) => typeof source === "string");
11429
11832
  throw new DeeplineError(
@@ -12169,6 +12572,12 @@ async function handleFileBackedRun(options) {
12169
12572
  () => readFileSync6(absolutePlayPath, "utf-8")
12170
12573
  );
12171
12574
  const runtimeInput = options.input ? { ...options.input } : {};
12575
+ try {
12576
+ preflightLocalFileInputs(runtimeInput);
12577
+ } catch (error) {
12578
+ console.error(error instanceof Error ? error.message : String(error));
12579
+ return 1;
12580
+ }
12172
12581
  let graph;
12173
12582
  try {
12174
12583
  graph = await traceCliSpan(
@@ -12326,6 +12735,12 @@ async function handleNamedRun(options) {
12326
12735
  const progress = getActiveCliProgress() ?? createCliProgress(!options.jsonOutput);
12327
12736
  const playName = options.target.name;
12328
12737
  const runtimeInput = options.input ? { ...options.input } : {};
12738
+ try {
12739
+ preflightLocalFileInputs(runtimeInput);
12740
+ } catch (error) {
12741
+ console.error(error instanceof Error ? error.message : String(error));
12742
+ return 1;
12743
+ }
12329
12744
  const needsPlayDefinition = namedRunNeedsPlayDefinition({
12330
12745
  runtimeInput,
12331
12746
  revisionSelector: options.revisionSelector
@@ -19505,7 +19920,7 @@ import {
19505
19920
  mkdirSync as mkdirSync5,
19506
19921
  readdirSync as readdirSync2,
19507
19922
  readFileSync as readFileSync8,
19508
- statSync as statSync2,
19923
+ statSync as statSync3,
19509
19924
  writeFileSync as writeFileSync10
19510
19925
  } from "fs";
19511
19926
  import { homedir as homedir6 } from "os";
@@ -19579,7 +19994,7 @@ function installedSdkSkillHasStalePositionalExecuteExamples() {
19579
19994
  const scan = (dir) => {
19580
19995
  for (const entry of readdirSync2(dir)) {
19581
19996
  const path = join13(dir, entry);
19582
- const stat4 = statSync2(path);
19997
+ const stat4 = statSync3(path);
19583
19998
  if (stat4.isDirectory()) {
19584
19999
  if (scan(path)) return true;
19585
20000
  continue;