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.
@@ -210,10 +210,14 @@ var SDK_RELEASE = {
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
212
  // 0.1.101 ships retryable play artifact publish failures and CI retry hardening.
213
- version: "0.1.101",
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",
214
218
  apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
215
219
  supportPolicy: {
216
- latest: "0.1.101",
220
+ latest: "0.1.102",
217
221
  minimumSupported: "0.1.53",
218
222
  deprecatedBelow: "0.1.53"
219
223
  }
@@ -364,6 +368,22 @@ var HttpClient = class {
364
368
  parsed = body;
365
369
  }
366
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
+ }
367
387
  const errorValue = typeof parsed === "object" && parsed && "error" in parsed ? parsed.error : void 0;
368
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}`;
369
389
  throw new DeeplineError(msg, response.status, "API_ERROR", {
@@ -424,6 +444,22 @@ var HttpClient = class {
424
444
  }
425
445
  if (!response.ok) {
426
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
+ }
427
463
  const parsed = parseResponseBody(body);
428
464
  throw new DeeplineError(
429
465
  apiErrorMessage(parsed, response.status),
@@ -489,6 +525,31 @@ function parseResponseBody(body) {
489
525
  return body;
490
526
  }
491
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
+ }
492
553
  function apiErrorMessage(parsed, status) {
493
554
  const errorValue = typeof parsed === "object" && parsed && "error" in parsed ? parsed.error : void 0;
494
555
  if (typeof errorValue === "string") {
@@ -584,7 +645,7 @@ function isTransientPlayStreamError(error) {
584
645
  return error.statusCode >= 500 && error.statusCode < 600;
585
646
  }
586
647
  const text = error instanceof Error ? error.message : String(error);
587
- 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(
588
649
  text
589
650
  );
590
651
  }
@@ -816,6 +877,10 @@ function buildSnapshotFromLedger(snapshot) {
816
877
  return {
817
878
  runId: snapshot.runId,
818
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,
819
884
  updatedAt: snapshot.updatedAt ?? snapshot.finishedAt ?? snapshot.startedAt ?? null,
820
885
  logs: snapshot.logTail,
821
886
  totalLogCount: snapshot.totalLogCount,
@@ -4538,14 +4603,25 @@ import { writeFileSync as writeFileSync4 } from "fs";
4538
4603
  import { resolve as resolve4 } from "path";
4539
4604
 
4540
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
+ }
4541
4613
  function datasetSummaryPercentText(numerator, denominator) {
4542
4614
  return denominator > 0 ? `${numerator}/${denominator} (${Math.round(100 * numerator / denominator)}%)` : "0/0 (0%)";
4543
4615
  }
4544
4616
  function readCount(value) {
4545
4617
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : 0;
4546
4618
  }
4547
- function formatDatasetExecutionStats(raw, denominator) {
4548
- 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 = {
4549
4625
  queued: datasetSummaryPercentText(readCount(raw.queued), denominator),
4550
4626
  running: datasetSummaryPercentText(readCount(raw.running), denominator),
4551
4627
  "completed:executed": datasetSummaryPercentText(
@@ -4566,6 +4642,17 @@ function formatDatasetExecutionStats(raw, denominator) {
4566
4642
  ),
4567
4643
  failed: datasetSummaryPercentText(readCount(raw.failed), denominator)
4568
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;
4569
4656
  }
4570
4657
 
4571
4658
  // src/cli/dataset-stats.ts
@@ -4715,7 +4802,8 @@ function canonicalRowsInfoFromCandidate(input2) {
4715
4802
  complete: rows2.length === totalRows2,
4716
4803
  source: candidate.source,
4717
4804
  datasetId: typeof candidate.value.datasetId === "string" ? candidate.value.datasetId : null,
4718
- 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 } : {}
4719
4807
  };
4720
4808
  }
4721
4809
  if (candidate.serializedOnly) {
@@ -4865,18 +4953,46 @@ function collectCanonicalRowsInfos(statusOrResult) {
4865
4953
  }
4866
4954
  return infos;
4867
4955
  }
4868
- function collectSerializedDatasetRowsInfos(statusOrResult) {
4956
+ function collectPackagedStepDatasetCandidates(statusOrResult) {
4869
4957
  const root = isRecord3(statusOrResult) ? statusOrResult : null;
4870
- const result = isRecord3(root?.result) ? root.result : root;
4871
- if (!result) {
4958
+ if (!root) {
4872
4959
  return [];
4873
4960
  }
4961
+ const pkg = isRecord3(root.package) ? root.package : root;
4962
+ const steps = Array.isArray(pkg.steps) ? pkg.steps : [];
4874
4963
  const candidates = [];
4875
- collectDatasetCandidates({
4876
- value: result,
4877
- path: "result",
4878
- output: candidates
4879
- });
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));
4880
4996
  const seen = /* @__PURE__ */ new Set();
4881
4997
  const infos = [];
4882
4998
  for (const candidate of candidates) {
@@ -5575,9 +5691,11 @@ import {
5575
5691
  readFileSync as readFileSync6,
5576
5692
  readdirSync,
5577
5693
  realpathSync,
5694
+ statSync as statSync2,
5578
5695
  writeFileSync as writeFileSync7
5579
5696
  } from "fs";
5580
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";
5581
5699
 
5582
5700
  // src/plays/bundle-play-file.ts
5583
5701
  import { tmpdir as tmpdir2 } from "os";
@@ -9390,6 +9508,177 @@ function inputContainsLocalFilePath(value) {
9390
9508
  }
9391
9509
  return false;
9392
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
+ }
9393
9682
  function namedRunNeedsPlayDefinition(input2) {
9394
9683
  return input2.revisionSelector === "latest" || inputContainsLocalFilePath(input2.runtimeInput);
9395
9684
  }
@@ -9624,6 +9913,21 @@ function isRetryablePendingStartFailure(status) {
9624
9913
  if (status.runId && status.runId !== "pending") return false;
9625
9914
  return isTransientPlayStreamError(new Error(playStatusErrorText(status)));
9626
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
+ }
9627
9931
  var TERMINAL_PLAY_STATUSES2 = /* @__PURE__ */ new Set([
9628
9932
  "completed",
9629
9933
  "failed",
@@ -10403,6 +10707,23 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
10403
10707
  progress: input2.progress
10404
10708
  });
10405
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
+ }
10406
10727
  throw error;
10407
10728
  } finally {
10408
10729
  if (timeout) {
@@ -10454,6 +10775,28 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
10454
10775
  function formatInteger(value) {
10455
10776
  return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : String(value ?? "-");
10456
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
+ }
10457
10800
  var BULKY_RETURN_KEYS = /* @__PURE__ */ new Set([
10458
10801
  "contract",
10459
10802
  "staticPipeline",
@@ -10538,8 +10881,7 @@ function formatJsonPreview(value) {
10538
10881
  return [];
10539
10882
  }
10540
10883
  const MAX_CHARS = 4e3;
10541
- const truncated = json.length > MAX_CHARS ? `${json.slice(0, MAX_CHARS)}
10542
- ... truncated; use --json for full output` : json;
10884
+ const truncated = clampJsonPreviewText(json, MAX_CHARS);
10543
10885
  return truncated.split("\n").map((line) => ` ${line}`);
10544
10886
  }
10545
10887
  function formatReturnValue(result) {
@@ -11070,6 +11412,9 @@ function readRecordArray(value) {
11070
11412
  function readRecord(value) {
11071
11413
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
11072
11414
  }
11415
+ function readNonNegativeInteger(value) {
11416
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : null;
11417
+ }
11073
11418
  function formatSummaryScalar(value) {
11074
11419
  if (typeof value === "number") {
11075
11420
  return Number.isFinite(value) ? formatInteger(Math.trunc(value)) : null;
@@ -11102,7 +11447,25 @@ function formatPackageDatasetSummaryLines(summary, indent2 = " ") {
11102
11447
  return [];
11103
11448
  }
11104
11449
  const lines = [];
11105
- 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
+ );
11106
11469
  if (parts.length > 0) {
11107
11470
  lines.push(`${indent2}summary: ${parts.join(" ")}`);
11108
11471
  }
@@ -11163,7 +11526,7 @@ function buildRunPackageTextLines(packaged) {
11163
11526
  ];
11164
11527
  const runError = typeof run.error === "string" && run.error.trim() ? run.error.trim() : null;
11165
11528
  if (runError && (status === "failed" || status === "cancelled")) {
11166
- lines.push(` error: ${runError.slice(0, 200)}`);
11529
+ lines.push(` error: ${truncateErrorForDisplay(runError, runId)}`);
11167
11530
  }
11168
11531
  for (const step of readRecordArray(packaged.steps)) {
11169
11532
  const output2 = step.output && typeof step.output === "object" && !Array.isArray(step.output) ? step.output : null;
@@ -11248,7 +11611,7 @@ function writePlayResult(status, jsonOutput, options) {
11248
11611
  lines.push(...buildInsufficientCreditsSummaryLines({ status, billing }));
11249
11612
  }
11250
11613
  const displayError = formatPlayErrorForDisplay(status, progressError) ?? progressError;
11251
- lines.push(` error: ${displayError.slice(0, 200)}`);
11614
+ lines.push(` error: ${truncateErrorForDisplay(displayError, runId)}`);
11252
11615
  }
11253
11616
  const renderedServerView = renderServerResultView(status.resultView);
11254
11617
  if (result) {
@@ -11419,12 +11782,51 @@ async function fetchBackingDatasetRows(input2) {
11419
11782
  source: `${input2.rowsInfo.source ?? "result.rows"} -> /api/v2/plays/${playName}/sheet?tableNamespace=${tableNamespace}`
11420
11783
  };
11421
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
+ }
11422
11824
  async function exportPlayStatusRows(client2, status, outPath, options = {}) {
11423
11825
  if (!outPath) {
11424
11826
  return null;
11425
11827
  }
11426
11828
  const availableRows = collectSerializedDatasetRowsInfos(status);
11427
- 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;
11428
11830
  if (!rowsInfo && options.datasetPath) {
11429
11831
  const available = availableRows.map((info) => info.source).filter((source) => typeof source === "string");
11430
11832
  throw new DeeplineError(
@@ -12170,6 +12572,12 @@ async function handleFileBackedRun(options) {
12170
12572
  () => readFileSync6(absolutePlayPath, "utf-8")
12171
12573
  );
12172
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
+ }
12173
12581
  let graph;
12174
12582
  try {
12175
12583
  graph = await traceCliSpan(
@@ -12327,6 +12735,12 @@ async function handleNamedRun(options) {
12327
12735
  const progress = getActiveCliProgress() ?? createCliProgress(!options.jsonOutput);
12328
12736
  const playName = options.target.name;
12329
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
+ }
12330
12744
  const needsPlayDefinition = namedRunNeedsPlayDefinition({
12331
12745
  runtimeInput,
12332
12746
  revisionSelector: options.revisionSelector
@@ -19506,7 +19920,7 @@ import {
19506
19920
  mkdirSync as mkdirSync5,
19507
19921
  readdirSync as readdirSync2,
19508
19922
  readFileSync as readFileSync8,
19509
- statSync as statSync2,
19923
+ statSync as statSync3,
19510
19924
  writeFileSync as writeFileSync10
19511
19925
  } from "fs";
19512
19926
  import { homedir as homedir6 } from "os";
@@ -19580,7 +19994,7 @@ function installedSdkSkillHasStalePositionalExecuteExamples() {
19580
19994
  const scan = (dir) => {
19581
19995
  for (const entry of readdirSync2(dir)) {
19582
19996
  const path = join13(dir, entry);
19583
- const stat4 = statSync2(path);
19997
+ const stat4 = statSync3(path);
19584
19998
  if (stat4.isDirectory()) {
19585
19999
  if (scan(path)) return true;
19586
20000
  continue;