deepline 0.1.235 → 0.1.237

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.
@@ -3828,6 +3828,9 @@ function createWorkerPacingResolver(req: RunRequest): WorkerPacingResolver {
3828
3828
  if (builtin) {
3829
3829
  return Promise.resolve({
3830
3830
  ...builtin,
3831
+ // Builtin pacing has no per-org coordinator rate scope. Keep the
3832
+ // required resolver contract explicit so the Governor treats it as
3833
+ // intentionally unscoped rather than receiving a malformed policy.
3831
3834
  r: null,
3832
3835
  retrySafeTransientHttp: false,
3833
3836
  });
@@ -108,10 +108,10 @@ export const SDK_RELEASE = {
108
108
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
109
109
  // 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
110
110
  // automatically without blocking their current command.
111
- version: '0.1.235',
111
+ version: '0.1.237',
112
112
  apiContract: '2026-06-dataset-handle-results-hard-cutover',
113
113
  supportPolicy: {
114
- latest: '0.1.235',
114
+ latest: '0.1.237',
115
115
  minimumSupported: '0.1.53',
116
116
  deprecatedBelow: '0.1.219',
117
117
  commandMinimumSupported: [
@@ -12,6 +12,7 @@ import {
12
12
  isPlayRowExecutionSuspendedError,
13
13
  } from './suspension';
14
14
  import { getToolHttpErrorReceiptFailureKind } from './tool-http-errors';
15
+ import { isLegacyRepairableWorkReceiptError } from './work-receipt-state-machine';
15
16
  import type { WorkReceiptFailureKind } from './work-receipts';
16
17
  import {
17
18
  PLAY_RUNTIME_WORK_RECEIPT_LEASE_TTL_MS,
@@ -63,8 +64,24 @@ export function runtimeReceiptFailureKindForError(
63
64
  const toolFailureKind = getToolHttpErrorReceiptFailureKind(error);
64
65
  if (toolFailureKind) return toolFailureKind;
65
66
 
66
- return isPlayExecutionSuspendedError(error) ||
67
+ if (
68
+ isPlayExecutionSuspendedError(error) ||
67
69
  isPlayRowExecutionSuspendedError(error)
70
+ ) {
71
+ return 'repairable';
72
+ }
73
+
74
+ // Transport/timeout failures never reach a `ToolHttpError` (there was no HTTP
75
+ // response to classify), so they would otherwise default to `terminal` and
76
+ // poison the durable receipt: the next run would replay the cached transport
77
+ // failure instead of re-executing. These are transient infra failures, so
78
+ // mark them repairable. `isLegacyRepairableWorkReceiptError` already encodes
79
+ // the exact transient-string contract (5xx, "transport failed calling",
80
+ // "runtime api call timed out") while excluding hard billing caps, and the
81
+ // read-side SQL predicate uses the same rules, so write and read stay aligned.
82
+ const message =
83
+ error instanceof Error ? error.message : error != null ? String(error) : '';
84
+ return isLegacyRepairableWorkReceiptError(message)
68
85
  ? 'repairable'
69
86
  : 'terminal';
70
87
  }
@@ -1,6 +1,19 @@
1
1
  const encoder = typeof TextEncoder === 'undefined' ? null : new TextEncoder();
2
2
 
3
3
  export const TERMINAL_RUN_RESULT_MAX_BYTES = 2_500_000;
4
+ /**
5
+ * Convex hard-caps a single document at 1 MiB and rejects nesting deeper than
6
+ * 16 levels. A run-ledger `run.completed` / `run.failed` event embeds its
7
+ * `result` inside one `playRunEvents` document, so the ledger-bound result must
8
+ * stay comfortably under that ceiling (the event also carries seq/type/
9
+ * timestamps and the surrounding doc envelope). Keep well below 1 MiB so no
10
+ * single event can wedge the append mutation with `Value is too large (>1MiB)`.
11
+ * Historically a failed run embedded its full `PlayRunnerResult` — including a
12
+ * `checkpoint` whose `completedToolBatches[*].toolResponse.raw` held megabytes
13
+ * of provider payload — which is durable in scheduler Postgres and never read
14
+ * back from Convex, yet deterministically wedged the append.
15
+ */
16
+ export const LEDGER_TERMINAL_RESULT_MAX_BYTES = 768 * 1024;
4
17
  export const CUSTOMER_OUTPUT_VALUE_MAX_BYTES = 1 * 1024 * 1024;
5
18
  export const CUSTOMER_OUTPUT_TOTAL_MAX_BYTES = 2 * 1024 * 1024;
6
19
  /** Inline Postgres receipt contract: one serialized output is at most 10 MiB. */
@@ -86,6 +99,81 @@ export function persistableTerminalRunResult(value: unknown): unknown {
86
99
  return valueWithinJsonByteLimit(value, TERMINAL_RUN_RESULT_MAX_BYTES);
87
100
  }
88
101
 
102
+ /**
103
+ * Replay-only keys a terminal `PlayRunnerResult` carries for the scheduler
104
+ * (Postgres) data plane. These are NEVER read back from the Convex run ledger:
105
+ * resume rehydrates the checkpoint/suspension from scheduler Postgres
106
+ * (`work_runs.checkpoint_json` / `suspension_json`), and customer reads use the
107
+ * run's `output`. Embedding them in a `playRunEvents` document only bloats the
108
+ * write and can exceed Convex's 1 MiB / 16-level document limits.
109
+ */
110
+ const LEDGER_STRIPPED_TERMINAL_RESULT_KEYS = [
111
+ 'checkpoint',
112
+ 'suspension',
113
+ ] as const;
114
+
115
+ /** A tiny reference descriptor left in place of a dropped over-limit result. */
116
+ export type LedgerTerminalResultRef = {
117
+ __kind: 'deepline.ledger_terminal_result_ref.v1';
118
+ /** Where the full terminal result is durably stored. */
119
+ store: 'scheduler_postgres';
120
+ /** Column on the scheduler `work_runs` row that holds the full result. */
121
+ key: 'terminal_result_json';
122
+ /** Serialized byte size of the result that was elided from the ledger. */
123
+ bytes: number;
124
+ reason: 'terminal_result_exceeds_ledger_limit';
125
+ };
126
+
127
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
128
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
129
+ }
130
+
131
+ /**
132
+ * Shape a terminal run result for the Convex run ledger. Control-plane only:
133
+ *
134
+ * 1. Strip replay-only keys ({@link LEDGER_STRIPPED_TERMINAL_RESULT_KEYS}) from
135
+ * the top-level result object — the scheduler Postgres data plane owns them.
136
+ * 2. If what remains still exceeds {@link LEDGER_TERMINAL_RESULT_MAX_BYTES},
137
+ * replace the whole result with a {@link LedgerTerminalResultRef} pointing at
138
+ * the durable scheduler copy, so the ledger event stays small and the
139
+ * Convex append cannot wedge on an oversized/over-nested document.
140
+ *
141
+ * The full result (including its checkpoint) remains durable in scheduler
142
+ * Postgres regardless; this only governs what the ledger embeds.
143
+ */
144
+ export function terminalRunResultForLedger(value: unknown): unknown {
145
+ if (value === undefined || value === null) {
146
+ return value;
147
+ }
148
+ let candidate = value;
149
+ if (isPlainObject(value)) {
150
+ const stripped: Record<string, unknown> = {};
151
+ for (const [key, entry] of Object.entries(value)) {
152
+ if (
153
+ (LEDGER_STRIPPED_TERMINAL_RESULT_KEYS as readonly string[]).includes(
154
+ key,
155
+ )
156
+ ) {
157
+ continue;
158
+ }
159
+ stripped[key] = entry;
160
+ }
161
+ candidate = stripped;
162
+ }
163
+ const bytes = jsonByteLength(candidate);
164
+ if (bytes <= LEDGER_TERMINAL_RESULT_MAX_BYTES) {
165
+ return candidate;
166
+ }
167
+ const ref: LedgerTerminalResultRef = {
168
+ __kind: 'deepline.ledger_terminal_result_ref.v1',
169
+ store: 'scheduler_postgres',
170
+ key: 'terminal_result_json',
171
+ bytes,
172
+ reason: 'terminal_result_exceeds_ledger_limit',
173
+ };
174
+ return ref;
175
+ }
176
+
89
177
  export function assertCustomerOutputValueWithinLimit(input: {
90
178
  value: unknown;
91
179
  path: string;
@@ -1,6 +1,16 @@
1
1
  import { RECEIPT_STATUS_CODE } from './receipt-status';
2
2
  import { workReceiptFailureKindCode } from './work-receipts';
3
3
 
4
+ // This predicate MUST stay behaviorally identical to
5
+ // `isLegacyRepairableWorkReceiptError` in ./work-receipt-state-machine.ts.
6
+ // Write-side classification and this read-side reclaim gate share one contract:
7
+ // a receipt is repairable if its failure_kind is repairable, OR it is a legacy
8
+ // terminal receipt whose error text proves a transient-class failure (5xx,
9
+ // transport, timeout, or a transient billing-plane outage). The
10
+ // billing_unavailable clause recovers the poisoned backlog written during a
11
+ // billing-plane incident, where transient 503s were formatted with the hard
12
+ // "billing cap" template — it is checked BEFORE the hard-billing exclusions so
13
+ // those receipts re-execute on the next run instead of replaying the failure.
4
14
  export function workReceiptRepairableFailurePredicateSql(
5
15
  receiptTable: string,
6
16
  ): string {
@@ -9,13 +19,20 @@ export function workReceiptRepairableFailurePredicateSql(
9
19
  OR (
10
20
  ${receiptTable}.failure_kind = ${workReceiptFailureKindCode('terminal')}::smallint
11
21
  AND ${receiptTable}.error IS NOT NULL
12
- AND lower(${receiptTable}.error) NOT LIKE '%billing cap%'
13
- AND lower(${receiptTable}.error) NOT LIKE '%insufficient credits%'
14
- AND lower(${receiptTable}.error) NOT LIKE '%monthly billing limit%'
15
22
  AND (
16
- ${receiptTable}.error ~* '(^|[^0-9])5[0-9]{2}([^0-9]|$)'
17
- OR lower(${receiptTable}.error) LIKE '%transport failed calling%'
18
- OR lower(${receiptTable}.error) LIKE '%runtime api call timed out%'
23
+ lower(${receiptTable}.error) LIKE '%billing_unavailable%'
24
+ OR lower(${receiptTable}.error) LIKE '%billing is temporarily unavailable%'
25
+ OR lower(${receiptTable}.error) LIKE '%billing temporarily unavailable%'
26
+ OR (
27
+ lower(${receiptTable}.error) NOT LIKE '%billing cap%'
28
+ AND lower(${receiptTable}.error) NOT LIKE '%insufficient credits%'
29
+ AND lower(${receiptTable}.error) NOT LIKE '%monthly billing limit%'
30
+ AND (
31
+ ${receiptTable}.error ~* '(^|[^0-9])5[0-9]{2}([^0-9]|$)'
32
+ OR lower(${receiptTable}.error) LIKE '%transport failed calling%'
33
+ OR lower(${receiptTable}.error) LIKE '%runtime api call timed out%'
34
+ )
35
+ )
19
36
  )
20
37
  )
21
38
  )`;
@@ -52,10 +52,40 @@ function isInsufficientCreditsBilling(
52
52
  return billing?.kind === 'insufficient_credits';
53
53
  }
54
54
 
55
+ /**
56
+ * A transient failure of the Deepline billing PLANE (Convex outage, OCC
57
+ * exhaustion) surfaced as HTTP 503 `BILLING_UNAVAILABLE` with `retryable: true`
58
+ * and `failure_description` stating no credits were charged. This is NOT a hard
59
+ * credit/cap denial: a funded org is temporarily blocked because our billing
60
+ * store hiccuped, so it must be repairable on the next run rather than cached as
61
+ * a terminal failure. See `billingUnavailableResponse` in
62
+ * src/lib/integrations/execute-billing.ts. This class is the exact incident
63
+ * poison closed by issues #2561/#2706: without this carve-out a billing-plane
64
+ * outage classifies every affected tool call as `hard_billing_error` ->
65
+ * `terminal` and the next run replays the cached failure instead of retrying.
66
+ */
67
+ export function isTransientBillingFailurePayload(
68
+ payload: Record<string, unknown> | null,
69
+ ): boolean {
70
+ if (!payload) return false;
71
+ const code = String(payload.code ?? payload.error_code ?? '').toUpperCase();
72
+ if (code === 'BILLING_UNAVAILABLE') return true;
73
+ // Any billing-origin payload that explicitly declares itself retryable is a
74
+ // billing-infra hiccup, never a hard denial (denials are never retryable).
75
+ const category = String(
76
+ payload.error_category ?? payload.errorCategory ?? '',
77
+ ).toLowerCase();
78
+ return category === 'billing' && payload.retryable === true;
79
+ }
80
+
55
81
  function isHardBillingFailurePayload(
56
82
  payload: Record<string, unknown> | null,
57
83
  ): payload is Record<string, unknown> {
58
84
  if (!payload) return false;
85
+ // Billing-plane outages (503 BILLING_UNAVAILABLE) are transient infra, not
86
+ // hard denials. Never let them reach the terminal, run-fatal hard-billing
87
+ // path — they must stay repairable so the next run retries.
88
+ if (isTransientBillingFailurePayload(payload)) return false;
59
89
  const category = String(
60
90
  payload.error_category ?? payload.errorCategory ?? '',
61
91
  ).toLowerCase();
@@ -100,6 +130,10 @@ function normalizeHardBillingPayload(
100
130
  function receiptFailureKindForToolErrorPayload(
101
131
  payload: Record<string, unknown> | null,
102
132
  ): WorkReceiptFailureKind {
133
+ // A transient billing-plane outage (503 BILLING_UNAVAILABLE) charged nothing
134
+ // and is explicitly retryable. It must be repairable so the next run
135
+ // re-executes instead of replaying the cached billing-infra failure.
136
+ if (isTransientBillingFailurePayload(payload)) return 'repairable';
103
137
  const code = getStringField(payload, 'code')?.toUpperCase();
104
138
  // A credential connection can be added or replaced between play runs. Keep
105
139
  // the failure terminal within its owning run, but let a later run reclaim
@@ -22,6 +22,21 @@ export function isLegacyRepairableWorkReceiptError(
22
22
  ): boolean {
23
23
  const message = error?.trim().toLowerCase() ?? '';
24
24
  if (!message) return false;
25
+ // Transient billing-PLANE outages (503 BILLING_UNAVAILABLE) were historically
26
+ // formatted with the hard "billing cap exceeded" template (see
27
+ // formatHardBillingFailureMessage), so the receipts written during a
28
+ // billing-plane incident carry a "billing cap" string even though they are
29
+ // retryable infra failures that charged nothing. Recognize them BEFORE the
30
+ // hard-billing exclusion below so the poisoned backlog re-executes on the next
31
+ // run instead of replaying the cached failure. Real hard denials never carry a
32
+ // BILLING_UNAVAILABLE code or "temporarily unavailable" text.
33
+ if (
34
+ message.includes('billing_unavailable') ||
35
+ message.includes('billing is temporarily unavailable') ||
36
+ message.includes('billing temporarily unavailable')
37
+ ) {
38
+ return true;
39
+ }
25
40
  if (
26
41
  message.includes('billing cap') ||
27
42
  message.includes('insufficient credits') ||
package/dist/cli/index.js CHANGED
@@ -625,10 +625,10 @@ var SDK_RELEASE = {
625
625
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
626
626
  // 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
627
627
  // automatically without blocking their current command.
628
- version: "0.1.235",
628
+ version: "0.1.237",
629
629
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
630
630
  supportPolicy: {
631
- latest: "0.1.235",
631
+ latest: "0.1.237",
632
632
  minimumSupported: "0.1.53",
633
633
  deprecatedBelow: "0.1.219",
634
634
  commandMinimumSupported: [
@@ -1456,6 +1456,7 @@ function createSecretRedactionContext(initialValues = []) {
1456
1456
 
1457
1457
  // ../shared_libs/play-runtime/output-size-limits.ts
1458
1458
  var encoder = typeof TextEncoder === "undefined" ? null : new TextEncoder();
1459
+ var LEDGER_TERMINAL_RESULT_MAX_BYTES = 768 * 1024;
1459
1460
  var CUSTOMER_OUTPUT_VALUE_MAX_BYTES = 1 * 1024 * 1024;
1460
1461
  var CUSTOMER_OUTPUT_TOTAL_MAX_BYTES = 2 * 1024 * 1024;
1461
1462
  var RUNTIME_RECEIPT_OUTPUT_MAX_BYTES = 10 * 1024 * 1024;
@@ -12080,6 +12081,9 @@ function getRunIdFromLiveEvent(event) {
12080
12081
  const packageRunId = getRunRecordFromPackage(payload)?.id;
12081
12082
  return typeof packageRunId === "string" && packageRunId.trim() ? packageRunId : null;
12082
12083
  }
12084
+ function eventConfirmsPlayRunLaunch(event) {
12085
+ return event.type === "play.run.status" && getEventPayload(event).launchConfirmed === true;
12086
+ }
12083
12087
  function getStatusFromLiveEvent(event) {
12084
12088
  if (event.type !== "play.run.status" && event.type !== "play.run.snapshot" && event.type !== "play.run.final_status") {
12085
12089
  return null;
@@ -12506,70 +12510,6 @@ async function waitForPlayCompletionByStream(input2) {
12506
12510
  }
12507
12511
  return null;
12508
12512
  };
12509
- const watchViaObserveTransport = async () => {
12510
- const controller = new AbortController();
12511
- let timedOut = false;
12512
- const remaining = remainingWaitMs();
12513
- const timeout = remaining === null ? null : setTimeout(
12514
- () => {
12515
- timedOut = true;
12516
- controller.abort();
12517
- },
12518
- Math.max(1, remaining)
12519
- );
12520
- try {
12521
- for await (const event of input2.client.observeRunEvents(
12522
- input2.workflowId,
12523
- {
12524
- signal: controller.signal,
12525
- fallback: "none",
12526
- onNotice: input2.jsonOutput ? void 0 : (message) => input2.progress.writeLine(message)
12527
- }
12528
- )) {
12529
- const terminal2 = await handleLiveEvent(event);
12530
- if (terminal2) {
12531
- return terminal2;
12532
- }
12533
- }
12534
- } finally {
12535
- if (timeout) {
12536
- clearTimeout(timeout);
12537
- }
12538
- }
12539
- const terminal = await fetchTerminalStatus();
12540
- if (terminal) {
12541
- return terminal;
12542
- }
12543
- if (timedOut) {
12544
- assertPlayWaitNotTimedOut({ ...input2, lastPhase });
12545
- }
12546
- throw new DeeplineError(
12547
- `Run observation for ${input2.workflowId} ended before a terminal status.`,
12548
- void 0,
12549
- "PLAY_LIVE_STREAM_ENDED",
12550
- { runId: input2.workflowId, workflowId: input2.workflowId }
12551
- );
12552
- };
12553
- try {
12554
- return await watchViaObserveTransport();
12555
- } catch (error) {
12556
- if (!(error instanceof RunObserveTransportUnavailableError)) {
12557
- throw error;
12558
- }
12559
- if (!input2.jsonOutput) {
12560
- input2.progress.writeLine(
12561
- `[play watch] live subscription unavailable (${error.reason}); falling back to SSE tail (support window, ADR-0008)`
12562
- );
12563
- }
12564
- recordCliTrace({
12565
- phase: "cli.play_observe_transport_fallback",
12566
- ms: Date.now() - input2.startedAt,
12567
- ok: true,
12568
- playName: input2.playName,
12569
- workflowId: input2.workflowId,
12570
- reason: error.reason
12571
- });
12572
- }
12573
12513
  const streamOneWindow = async () => {
12574
12514
  const controller = new AbortController();
12575
12515
  let timedOut = false;
@@ -12702,6 +12642,7 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
12702
12642
  let launchedRevisionId = input2.request.revisionId?.trim() || null;
12703
12643
  let eventCount = 0;
12704
12644
  let firstRunIdMs = null;
12645
+ let launchConfirmed = false;
12705
12646
  let lastPhase = null;
12706
12647
  const timeout = input2.waitTimeoutMs === null ? null : setTimeout(
12707
12648
  () => {
@@ -12752,6 +12693,9 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
12752
12693
  input2.onRunStarted?.(eventRunId);
12753
12694
  }
12754
12695
  }
12696
+ if (eventConfirmsPlayRunLaunch(event) && eventRunId === lastKnownWorkflowId) {
12697
+ launchConfirmed = true;
12698
+ }
12755
12699
  const workflowId = lastKnownWorkflowId || "pending";
12756
12700
  if (workflowId !== "pending" && !emittedDashboardUrl) {
12757
12701
  if (!input2.jsonOutput) {
@@ -12828,6 +12772,16 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
12828
12772
  const finalStatus = getFinalStatusFromLiveEvent(event);
12829
12773
  if (finalStatus) {
12830
12774
  lastKnownWorkflowId ||= finalStatus.runId ?? "";
12775
+ if (finalStatus.status === "failed" && !finalStatus.runId) {
12776
+ return {
12777
+ ...finalStatus,
12778
+ runId: "pending",
12779
+ name: input2.playName,
12780
+ playName: input2.playName,
12781
+ dashboardUrl,
12782
+ ...launchedRevisionId ? { revisionId: launchedRevisionId } : {}
12783
+ };
12784
+ }
12831
12785
  const canonicalTerminal = input2.request.profile === "absurd" ? await fetchKnownTerminalStatus() : {
12832
12786
  ...finalStatus,
12833
12787
  dashboardUrl,
@@ -12859,6 +12813,34 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
12859
12813
  });
12860
12814
  return canonicalTerminal;
12861
12815
  }
12816
+ if (lastKnownWorkflowId && launchConfirmed) {
12817
+ if (timeout) {
12818
+ clearTimeout(timeout);
12819
+ }
12820
+ controller.abort();
12821
+ recordCliTrace({
12822
+ phase: "cli.play_start_stream_handoff_to_tail",
12823
+ ms: Date.now() - startedAt,
12824
+ ok: true,
12825
+ playName: input2.playName,
12826
+ workflowId: lastKnownWorkflowId,
12827
+ eventCount,
12828
+ firstRunIdMs,
12829
+ lastPhase
12830
+ });
12831
+ return waitForPlayCompletionByStream({
12832
+ client: input2.client,
12833
+ playName: input2.playName,
12834
+ workflowId: lastKnownWorkflowId,
12835
+ dashboardUrl,
12836
+ jsonOutput: input2.jsonOutput,
12837
+ emitLogs: input2.emitLogs,
12838
+ waitTimeoutMs: input2.waitTimeoutMs,
12839
+ startedAt,
12840
+ state,
12841
+ progress: input2.progress
12842
+ });
12843
+ }
12862
12844
  }
12863
12845
  } catch (error) {
12864
12846
  if (timedOut) {
@@ -14031,7 +14013,7 @@ function playRunPackageTextLedgerIncomplete(pkg) {
14031
14013
  }
14032
14014
  async function resolvePlayRunOutputStatus(input2) {
14033
14015
  const runId = input2.status.runId;
14034
- if (!runId) {
14016
+ if (!runId || runId === "pending") {
14035
14017
  return input2.status;
14036
14018
  }
14037
14019
  const streamedPackage = getPlayRunPackage(input2.status);
@@ -26381,7 +26363,9 @@ function printPlayLikeToolExecuteError(toolId, tool) {
26381
26363
  console.error(playLikeToolExecuteErrorMessage(toolId, tool));
26382
26364
  }
26383
26365
  function registerToolsCommands(program) {
26384
- const tools = program.command("tools").description("Search, describe, and execute atomic provider tools.").addHelpText(
26366
+ const tools = program.command("tools").description(
26367
+ "Search by intent or structured filters, describe, and execute atomic provider tools."
26368
+ ).addHelpText(
26385
26369
  "after",
26386
26370
  `
26387
26371
  Concepts:
@@ -26420,30 +26404,34 @@ Examples:
26420
26404
  ...options.json ? ["--json"] : []
26421
26405
  ]);
26422
26406
  });
26423
- const addToolSearchCommand = (command) => command.description("Search available tools.").addHelpText(
26407
+ const addToolSearchCommand = (command) => command.description(
26408
+ "Search available tools by intent query or structured filters."
26409
+ ).addHelpText(
26424
26410
  "after",
26425
26411
  `
26426
26412
  Notes:
26427
26413
  Ranked discovery for atomic provider/API operations. Results include tool ids
26428
26414
  that can be passed to deepline tools describe or deepline tools execute.
26415
+ Provide a query, or omit it only when --categories and/or --search_terms is
26416
+ supplied. Providing neither is a usage error. Both filters accept
26417
+ comma-separated values. Use a provider name in the query; --prefix is not a
26418
+ supported option.
26429
26419
  The grep alias is the same ranked retrieval surface with a more literal name
26430
26420
  for agents that are filtering a registry rather than choosing a workflow.
26431
26421
 
26432
26422
  Examples:
26433
26423
  deepline tools search email
26424
+ deepline tools search --categories company_search --search_terms "investors,funding"
26434
26425
  deepline tools grep "company enrichment" --categories enrichment --json
26435
26426
  deepline tools search verifier --search-mode v2 --json
26436
26427
  `
26437
26428
  ).option(
26438
26429
  "--categories <categories>",
26439
- "Comma-separated categories to filter ranked search"
26430
+ "Comma-separated categories; may be used without a query"
26440
26431
  ).option(
26441
26432
  "--search_terms <terms>",
26442
- "Structured search terms for ranked search"
26443
- ).option(
26444
- "--search-terms <terms>",
26445
- "Structured search terms for ranked search"
26446
- ).option("--search-mode <mode>", "Ranked search mode: v1 or v2").option("--include-search-debug", "Include ranked search debug metadata").option("--compact", "Drop verbose fields from JSON output").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (query, options) => {
26433
+ "Comma-separated structured search terms; may be used without a query"
26434
+ ).option("--search-terms <terms>", "Alias for --search_terms").option("--search-mode <mode>", "Ranked search mode: v1 or v2").option("--include-search-debug", "Include ranked search debug metadata").option("--compact", "Drop verbose fields from JSON output").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (query, options) => {
26447
26435
  process.exitCode = await searchTools(query, {
26448
26436
  json: options.json,
26449
26437
  compact: options.compact,
@@ -28390,6 +28378,9 @@ function sdkSkillsVersionPath(baseUrl) {
28390
28378
  function legacySdkSkillsVersionPath(baseUrl) {
28391
28379
  return (0, import_node_path18.join)((0, import_node_path18.dirname)(sdkCliStateDirPath(baseUrl)), "sdk-skills", ".version");
28392
28380
  }
28381
+ function unavailableSkillsNoticePath(baseUrl) {
28382
+ return (0, import_node_path18.join)(sdkCliStateDirPath(baseUrl), "skills-sync-unavailable-version");
28383
+ }
28393
28384
  function readSdkSkillsLocalVersion(baseUrl) {
28394
28385
  const pluginVersion = readPluginSkillsVersion();
28395
28386
  if (pluginVersion) return pluginVersion;
@@ -28407,6 +28398,29 @@ function writeLocalSkillsVersion(baseUrl, version) {
28407
28398
  (0, import_node_fs16.writeFileSync)(path, `${version}
28408
28399
  `, "utf-8");
28409
28400
  }
28401
+ function writeUnavailableSkillsNotice(baseUrl, remoteVersion, skillNames) {
28402
+ const path = unavailableSkillsNoticePath(baseUrl);
28403
+ try {
28404
+ if ((0, import_node_fs16.existsSync)(path) && (0, import_node_fs16.readFileSync)(path, "utf-8").trim() === remoteVersion) {
28405
+ return;
28406
+ }
28407
+ (0, import_node_fs16.mkdirSync)((0, import_node_path18.dirname)(path), { recursive: true });
28408
+ (0, import_node_fs16.writeFileSync)(path, `${remoteVersion}
28409
+ `, "utf-8");
28410
+ } catch {
28411
+ }
28412
+ const manualCommand = `npx ${buildSkillsInstallArgs(baseUrl, skillNames).join(" ")}`;
28413
+ writeSdkSkillsStatusLine(
28414
+ `Deepline agent skills are out of date, but neither \`bunx\` nor \`npx\` is available. Install Node.js/npm or Bun, then run:
28415
+ ${manualCommand}`
28416
+ );
28417
+ }
28418
+ function clearUnavailableSkillsNotice(baseUrl) {
28419
+ try {
28420
+ (0, import_node_fs16.unlinkSync)(unavailableSkillsNoticePath(baseUrl));
28421
+ } catch {
28422
+ }
28423
+ }
28410
28424
  function sortedUniqueSkillNames(names) {
28411
28425
  return [...new Set(names.map((name) => name.trim()).filter(Boolean))].sort(
28412
28426
  (a, b) => a.localeCompare(b)
@@ -28482,24 +28496,24 @@ function shellQuote5(arg) {
28482
28496
  return `'${arg.replace(/'/g, `'\\''`)}'`;
28483
28497
  }
28484
28498
  function resolveSkillsInstallCommands(baseUrl, skillNames = DEFAULT_SDK_SKILL_NAMES) {
28485
- const npxArgs = buildSkillsInstallArgs(baseUrl, skillNames);
28486
- const npxInstall = {
28487
- command: "npx",
28488
- args: npxArgs,
28489
- manualCommand: `npx ${npxArgs.map(shellQuote5).join(" ")}`
28490
- };
28499
+ const commands = [];
28491
28500
  if (hasCommand("bunx")) {
28492
28501
  const bunxArgs = buildBunxSkillsInstallArgs(baseUrl, skillNames);
28493
- return [
28494
- {
28495
- command: "bunx",
28496
- args: bunxArgs,
28497
- manualCommand: `bunx ${bunxArgs.map(shellQuote5).join(" ")}`
28498
- },
28499
- npxInstall
28500
- ];
28502
+ commands.push({
28503
+ command: "bunx",
28504
+ args: bunxArgs,
28505
+ manualCommand: `bunx ${bunxArgs.map(shellQuote5).join(" ")}`
28506
+ });
28501
28507
  }
28502
- return [npxInstall];
28508
+ if (hasCommand("npx")) {
28509
+ const npxArgs = buildSkillsInstallArgs(baseUrl, skillNames);
28510
+ commands.push({
28511
+ command: "npx",
28512
+ args: npxArgs,
28513
+ manualCommand: `npx ${npxArgs.map(shellQuote5).join(" ")}`
28514
+ });
28515
+ }
28516
+ return commands;
28503
28517
  }
28504
28518
  function runOneSkillsInstall(install) {
28505
28519
  return new Promise((resolve14) => {
@@ -28532,9 +28546,9 @@ function runOneSkillsInstall(install) {
28532
28546
  });
28533
28547
  });
28534
28548
  }
28535
- async function runSkillsInstall(baseUrl, skillNames) {
28549
+ async function runSkillsInstall(installs) {
28536
28550
  const failures = [];
28537
- for (const install of resolveSkillsInstallCommands(baseUrl, skillNames)) {
28551
+ for (const install of installs) {
28538
28552
  const result = await runOneSkillsInstall(install);
28539
28553
  if (result.ok) return true;
28540
28554
  failures.push(result);
@@ -28624,11 +28638,17 @@ async function syncSdkSkillsIfNeeded(baseUrl, options = {}) {
28624
28638
  remoteSkillNames.length > 0 ? remoteSkillNames : DEFAULT_SDK_SKILL_NAMES
28625
28639
  );
28626
28640
  if (skillNames.length === 0) return;
28641
+ const installs = resolveSkillsInstallCommands(baseUrl, skillNames);
28642
+ if (installs.length === 0) {
28643
+ writeUnavailableSkillsNotice(baseUrl, update.remoteVersion, skillNames);
28644
+ return;
28645
+ }
28627
28646
  writeSdkSkillsStatusLine("Deepline skills changed; syncing agent skills...");
28628
- const installed = await runSkillsInstall(baseUrl, skillNames);
28647
+ const installed = await runSkillsInstall(installs);
28629
28648
  if (!installed) return;
28630
28649
  runLegacySkillsCleanup();
28631
28650
  writeLocalSkillsVersion(baseUrl, update.remoteVersion);
28651
+ clearUnavailableSkillsNotice(baseUrl);
28632
28652
  writeSdkSkillsStatusLine("Deepline agent skills are up to date.");
28633
28653
  }
28634
28654
 
@@ -610,10 +610,10 @@ var SDK_RELEASE = {
610
610
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
611
611
  // 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
612
612
  // automatically without blocking their current command.
613
- version: "0.1.235",
613
+ version: "0.1.237",
614
614
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
615
615
  supportPolicy: {
616
- latest: "0.1.235",
616
+ latest: "0.1.237",
617
617
  minimumSupported: "0.1.53",
618
618
  deprecatedBelow: "0.1.219",
619
619
  commandMinimumSupported: [
@@ -1441,6 +1441,7 @@ function createSecretRedactionContext(initialValues = []) {
1441
1441
 
1442
1442
  // ../shared_libs/play-runtime/output-size-limits.ts
1443
1443
  var encoder = typeof TextEncoder === "undefined" ? null : new TextEncoder();
1444
+ var LEDGER_TERMINAL_RESULT_MAX_BYTES = 768 * 1024;
1444
1445
  var CUSTOMER_OUTPUT_VALUE_MAX_BYTES = 1 * 1024 * 1024;
1445
1446
  var CUSTOMER_OUTPUT_TOTAL_MAX_BYTES = 2 * 1024 * 1024;
1446
1447
  var RUNTIME_RECEIPT_OUTPUT_MAX_BYTES = 10 * 1024 * 1024;
@@ -12109,6 +12110,9 @@ function getRunIdFromLiveEvent(event) {
12109
12110
  const packageRunId = getRunRecordFromPackage(payload)?.id;
12110
12111
  return typeof packageRunId === "string" && packageRunId.trim() ? packageRunId : null;
12111
12112
  }
12113
+ function eventConfirmsPlayRunLaunch(event) {
12114
+ return event.type === "play.run.status" && getEventPayload(event).launchConfirmed === true;
12115
+ }
12112
12116
  function getStatusFromLiveEvent(event) {
12113
12117
  if (event.type !== "play.run.status" && event.type !== "play.run.snapshot" && event.type !== "play.run.final_status") {
12114
12118
  return null;
@@ -12535,70 +12539,6 @@ async function waitForPlayCompletionByStream(input2) {
12535
12539
  }
12536
12540
  return null;
12537
12541
  };
12538
- const watchViaObserveTransport = async () => {
12539
- const controller = new AbortController();
12540
- let timedOut = false;
12541
- const remaining = remainingWaitMs();
12542
- const timeout = remaining === null ? null : setTimeout(
12543
- () => {
12544
- timedOut = true;
12545
- controller.abort();
12546
- },
12547
- Math.max(1, remaining)
12548
- );
12549
- try {
12550
- for await (const event of input2.client.observeRunEvents(
12551
- input2.workflowId,
12552
- {
12553
- signal: controller.signal,
12554
- fallback: "none",
12555
- onNotice: input2.jsonOutput ? void 0 : (message) => input2.progress.writeLine(message)
12556
- }
12557
- )) {
12558
- const terminal2 = await handleLiveEvent(event);
12559
- if (terminal2) {
12560
- return terminal2;
12561
- }
12562
- }
12563
- } finally {
12564
- if (timeout) {
12565
- clearTimeout(timeout);
12566
- }
12567
- }
12568
- const terminal = await fetchTerminalStatus();
12569
- if (terminal) {
12570
- return terminal;
12571
- }
12572
- if (timedOut) {
12573
- assertPlayWaitNotTimedOut({ ...input2, lastPhase });
12574
- }
12575
- throw new DeeplineError(
12576
- `Run observation for ${input2.workflowId} ended before a terminal status.`,
12577
- void 0,
12578
- "PLAY_LIVE_STREAM_ENDED",
12579
- { runId: input2.workflowId, workflowId: input2.workflowId }
12580
- );
12581
- };
12582
- try {
12583
- return await watchViaObserveTransport();
12584
- } catch (error) {
12585
- if (!(error instanceof RunObserveTransportUnavailableError)) {
12586
- throw error;
12587
- }
12588
- if (!input2.jsonOutput) {
12589
- input2.progress.writeLine(
12590
- `[play watch] live subscription unavailable (${error.reason}); falling back to SSE tail (support window, ADR-0008)`
12591
- );
12592
- }
12593
- recordCliTrace({
12594
- phase: "cli.play_observe_transport_fallback",
12595
- ms: Date.now() - input2.startedAt,
12596
- ok: true,
12597
- playName: input2.playName,
12598
- workflowId: input2.workflowId,
12599
- reason: error.reason
12600
- });
12601
- }
12602
12542
  const streamOneWindow = async () => {
12603
12543
  const controller = new AbortController();
12604
12544
  let timedOut = false;
@@ -12731,6 +12671,7 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
12731
12671
  let launchedRevisionId = input2.request.revisionId?.trim() || null;
12732
12672
  let eventCount = 0;
12733
12673
  let firstRunIdMs = null;
12674
+ let launchConfirmed = false;
12734
12675
  let lastPhase = null;
12735
12676
  const timeout = input2.waitTimeoutMs === null ? null : setTimeout(
12736
12677
  () => {
@@ -12781,6 +12722,9 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
12781
12722
  input2.onRunStarted?.(eventRunId);
12782
12723
  }
12783
12724
  }
12725
+ if (eventConfirmsPlayRunLaunch(event) && eventRunId === lastKnownWorkflowId) {
12726
+ launchConfirmed = true;
12727
+ }
12784
12728
  const workflowId = lastKnownWorkflowId || "pending";
12785
12729
  if (workflowId !== "pending" && !emittedDashboardUrl) {
12786
12730
  if (!input2.jsonOutput) {
@@ -12857,6 +12801,16 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
12857
12801
  const finalStatus = getFinalStatusFromLiveEvent(event);
12858
12802
  if (finalStatus) {
12859
12803
  lastKnownWorkflowId ||= finalStatus.runId ?? "";
12804
+ if (finalStatus.status === "failed" && !finalStatus.runId) {
12805
+ return {
12806
+ ...finalStatus,
12807
+ runId: "pending",
12808
+ name: input2.playName,
12809
+ playName: input2.playName,
12810
+ dashboardUrl,
12811
+ ...launchedRevisionId ? { revisionId: launchedRevisionId } : {}
12812
+ };
12813
+ }
12860
12814
  const canonicalTerminal = input2.request.profile === "absurd" ? await fetchKnownTerminalStatus() : {
12861
12815
  ...finalStatus,
12862
12816
  dashboardUrl,
@@ -12888,6 +12842,34 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
12888
12842
  });
12889
12843
  return canonicalTerminal;
12890
12844
  }
12845
+ if (lastKnownWorkflowId && launchConfirmed) {
12846
+ if (timeout) {
12847
+ clearTimeout(timeout);
12848
+ }
12849
+ controller.abort();
12850
+ recordCliTrace({
12851
+ phase: "cli.play_start_stream_handoff_to_tail",
12852
+ ms: Date.now() - startedAt,
12853
+ ok: true,
12854
+ playName: input2.playName,
12855
+ workflowId: lastKnownWorkflowId,
12856
+ eventCount,
12857
+ firstRunIdMs,
12858
+ lastPhase
12859
+ });
12860
+ return waitForPlayCompletionByStream({
12861
+ client: input2.client,
12862
+ playName: input2.playName,
12863
+ workflowId: lastKnownWorkflowId,
12864
+ dashboardUrl,
12865
+ jsonOutput: input2.jsonOutput,
12866
+ emitLogs: input2.emitLogs,
12867
+ waitTimeoutMs: input2.waitTimeoutMs,
12868
+ startedAt,
12869
+ state,
12870
+ progress: input2.progress
12871
+ });
12872
+ }
12891
12873
  }
12892
12874
  } catch (error) {
12893
12875
  if (timedOut) {
@@ -14060,7 +14042,7 @@ function playRunPackageTextLedgerIncomplete(pkg) {
14060
14042
  }
14061
14043
  async function resolvePlayRunOutputStatus(input2) {
14062
14044
  const runId = input2.status.runId;
14063
- if (!runId) {
14045
+ if (!runId || runId === "pending") {
14064
14046
  return input2.status;
14065
14047
  }
14066
14048
  const streamedPackage = getPlayRunPackage(input2.status);
@@ -26429,7 +26411,9 @@ function printPlayLikeToolExecuteError(toolId, tool) {
26429
26411
  console.error(playLikeToolExecuteErrorMessage(toolId, tool));
26430
26412
  }
26431
26413
  function registerToolsCommands(program) {
26432
- const tools = program.command("tools").description("Search, describe, and execute atomic provider tools.").addHelpText(
26414
+ const tools = program.command("tools").description(
26415
+ "Search by intent or structured filters, describe, and execute atomic provider tools."
26416
+ ).addHelpText(
26433
26417
  "after",
26434
26418
  `
26435
26419
  Concepts:
@@ -26468,30 +26452,34 @@ Examples:
26468
26452
  ...options.json ? ["--json"] : []
26469
26453
  ]);
26470
26454
  });
26471
- const addToolSearchCommand = (command) => command.description("Search available tools.").addHelpText(
26455
+ const addToolSearchCommand = (command) => command.description(
26456
+ "Search available tools by intent query or structured filters."
26457
+ ).addHelpText(
26472
26458
  "after",
26473
26459
  `
26474
26460
  Notes:
26475
26461
  Ranked discovery for atomic provider/API operations. Results include tool ids
26476
26462
  that can be passed to deepline tools describe or deepline tools execute.
26463
+ Provide a query, or omit it only when --categories and/or --search_terms is
26464
+ supplied. Providing neither is a usage error. Both filters accept
26465
+ comma-separated values. Use a provider name in the query; --prefix is not a
26466
+ supported option.
26477
26467
  The grep alias is the same ranked retrieval surface with a more literal name
26478
26468
  for agents that are filtering a registry rather than choosing a workflow.
26479
26469
 
26480
26470
  Examples:
26481
26471
  deepline tools search email
26472
+ deepline tools search --categories company_search --search_terms "investors,funding"
26482
26473
  deepline tools grep "company enrichment" --categories enrichment --json
26483
26474
  deepline tools search verifier --search-mode v2 --json
26484
26475
  `
26485
26476
  ).option(
26486
26477
  "--categories <categories>",
26487
- "Comma-separated categories to filter ranked search"
26478
+ "Comma-separated categories; may be used without a query"
26488
26479
  ).option(
26489
26480
  "--search_terms <terms>",
26490
- "Structured search terms for ranked search"
26491
- ).option(
26492
- "--search-terms <terms>",
26493
- "Structured search terms for ranked search"
26494
- ).option("--search-mode <mode>", "Ranked search mode: v1 or v2").option("--include-search-debug", "Include ranked search debug metadata").option("--compact", "Drop verbose fields from JSON output").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (query, options) => {
26481
+ "Comma-separated structured search terms; may be used without a query"
26482
+ ).option("--search-terms <terms>", "Alias for --search_terms").option("--search-mode <mode>", "Ranked search mode: v1 or v2").option("--include-search-debug", "Include ranked search debug metadata").option("--compact", "Drop verbose fields from JSON output").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (query, options) => {
26495
26483
  process.exitCode = await searchTools(query, {
26496
26484
  json: options.json,
26497
26485
  compact: options.compact,
@@ -28299,7 +28287,7 @@ import {
28299
28287
  readFileSync as readFileSync13,
28300
28288
  renameSync,
28301
28289
  rmSync as rmSync4,
28302
- unlinkSync,
28290
+ unlinkSync as unlinkSync2,
28303
28291
  writeFileSync as writeFileSync15
28304
28292
  } from "fs";
28305
28293
  import { homedir as homedir11 } from "os";
@@ -28307,7 +28295,13 @@ import { dirname as dirname13, isAbsolute as isAbsolute2, join as join14, relati
28307
28295
 
28308
28296
  // src/cli/skills-sync.ts
28309
28297
  import { spawn as spawn3, spawnSync as spawnSync2 } from "child_process";
28310
- import { existsSync as existsSync11, mkdirSync as mkdirSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync14 } from "fs";
28298
+ import {
28299
+ existsSync as existsSync11,
28300
+ mkdirSync as mkdirSync9,
28301
+ readFileSync as readFileSync12,
28302
+ unlinkSync,
28303
+ writeFileSync as writeFileSync14
28304
+ } from "fs";
28311
28305
  import { dirname as dirname12, join as join13 } from "path";
28312
28306
 
28313
28307
  // ../shared_libs/cli/install-commands.json
@@ -28447,6 +28441,9 @@ function sdkSkillsVersionPath(baseUrl) {
28447
28441
  function legacySdkSkillsVersionPath(baseUrl) {
28448
28442
  return join13(dirname12(sdkCliStateDirPath(baseUrl)), "sdk-skills", ".version");
28449
28443
  }
28444
+ function unavailableSkillsNoticePath(baseUrl) {
28445
+ return join13(sdkCliStateDirPath(baseUrl), "skills-sync-unavailable-version");
28446
+ }
28450
28447
  function readSdkSkillsLocalVersion(baseUrl) {
28451
28448
  const pluginVersion = readPluginSkillsVersion();
28452
28449
  if (pluginVersion) return pluginVersion;
@@ -28464,6 +28461,29 @@ function writeLocalSkillsVersion(baseUrl, version) {
28464
28461
  writeFileSync14(path, `${version}
28465
28462
  `, "utf-8");
28466
28463
  }
28464
+ function writeUnavailableSkillsNotice(baseUrl, remoteVersion, skillNames) {
28465
+ const path = unavailableSkillsNoticePath(baseUrl);
28466
+ try {
28467
+ if (existsSync11(path) && readFileSync12(path, "utf-8").trim() === remoteVersion) {
28468
+ return;
28469
+ }
28470
+ mkdirSync9(dirname12(path), { recursive: true });
28471
+ writeFileSync14(path, `${remoteVersion}
28472
+ `, "utf-8");
28473
+ } catch {
28474
+ }
28475
+ const manualCommand = `npx ${buildSkillsInstallArgs(baseUrl, skillNames).join(" ")}`;
28476
+ writeSdkSkillsStatusLine(
28477
+ `Deepline agent skills are out of date, but neither \`bunx\` nor \`npx\` is available. Install Node.js/npm or Bun, then run:
28478
+ ${manualCommand}`
28479
+ );
28480
+ }
28481
+ function clearUnavailableSkillsNotice(baseUrl) {
28482
+ try {
28483
+ unlinkSync(unavailableSkillsNoticePath(baseUrl));
28484
+ } catch {
28485
+ }
28486
+ }
28467
28487
  function sortedUniqueSkillNames(names) {
28468
28488
  return [...new Set(names.map((name) => name.trim()).filter(Boolean))].sort(
28469
28489
  (a, b) => a.localeCompare(b)
@@ -28539,24 +28559,24 @@ function shellQuote5(arg) {
28539
28559
  return `'${arg.replace(/'/g, `'\\''`)}'`;
28540
28560
  }
28541
28561
  function resolveSkillsInstallCommands(baseUrl, skillNames = DEFAULT_SDK_SKILL_NAMES) {
28542
- const npxArgs = buildSkillsInstallArgs(baseUrl, skillNames);
28543
- const npxInstall = {
28544
- command: "npx",
28545
- args: npxArgs,
28546
- manualCommand: `npx ${npxArgs.map(shellQuote5).join(" ")}`
28547
- };
28562
+ const commands = [];
28548
28563
  if (hasCommand("bunx")) {
28549
28564
  const bunxArgs = buildBunxSkillsInstallArgs(baseUrl, skillNames);
28550
- return [
28551
- {
28552
- command: "bunx",
28553
- args: bunxArgs,
28554
- manualCommand: `bunx ${bunxArgs.map(shellQuote5).join(" ")}`
28555
- },
28556
- npxInstall
28557
- ];
28565
+ commands.push({
28566
+ command: "bunx",
28567
+ args: bunxArgs,
28568
+ manualCommand: `bunx ${bunxArgs.map(shellQuote5).join(" ")}`
28569
+ });
28558
28570
  }
28559
- return [npxInstall];
28571
+ if (hasCommand("npx")) {
28572
+ const npxArgs = buildSkillsInstallArgs(baseUrl, skillNames);
28573
+ commands.push({
28574
+ command: "npx",
28575
+ args: npxArgs,
28576
+ manualCommand: `npx ${npxArgs.map(shellQuote5).join(" ")}`
28577
+ });
28578
+ }
28579
+ return commands;
28560
28580
  }
28561
28581
  function runOneSkillsInstall(install) {
28562
28582
  return new Promise((resolve14) => {
@@ -28589,9 +28609,9 @@ function runOneSkillsInstall(install) {
28589
28609
  });
28590
28610
  });
28591
28611
  }
28592
- async function runSkillsInstall(baseUrl, skillNames) {
28612
+ async function runSkillsInstall(installs) {
28593
28613
  const failures = [];
28594
- for (const install of resolveSkillsInstallCommands(baseUrl, skillNames)) {
28614
+ for (const install of installs) {
28595
28615
  const result = await runOneSkillsInstall(install);
28596
28616
  if (result.ok) return true;
28597
28617
  failures.push(result);
@@ -28681,11 +28701,17 @@ async function syncSdkSkillsIfNeeded(baseUrl, options = {}) {
28681
28701
  remoteSkillNames.length > 0 ? remoteSkillNames : DEFAULT_SDK_SKILL_NAMES
28682
28702
  );
28683
28703
  if (skillNames.length === 0) return;
28704
+ const installs = resolveSkillsInstallCommands(baseUrl, skillNames);
28705
+ if (installs.length === 0) {
28706
+ writeUnavailableSkillsNotice(baseUrl, update.remoteVersion, skillNames);
28707
+ return;
28708
+ }
28684
28709
  writeSdkSkillsStatusLine("Deepline skills changed; syncing agent skills...");
28685
- const installed = await runSkillsInstall(baseUrl, skillNames);
28710
+ const installed = await runSkillsInstall(installs);
28686
28711
  if (!installed) return;
28687
28712
  runLegacySkillsCleanup();
28688
28713
  writeLocalSkillsVersion(baseUrl, update.remoteVersion);
28714
+ clearUnavailableSkillsNotice(baseUrl);
28689
28715
  writeSdkSkillsStatusLine("Deepline agent skills are up to date.");
28690
28716
  }
28691
28717
 
@@ -28914,7 +28940,7 @@ function clearAutoUpdateFailure(plan) {
28914
28940
  const path = autoUpdateFailurePath(plan);
28915
28941
  if (!path) return;
28916
28942
  try {
28917
- unlinkSync(path);
28943
+ unlinkSync2(path);
28918
28944
  } catch {
28919
28945
  }
28920
28946
  }
package/dist/index.js CHANGED
@@ -424,10 +424,10 @@ var SDK_RELEASE = {
424
424
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
425
425
  // 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
426
426
  // automatically without blocking their current command.
427
- version: "0.1.235",
427
+ version: "0.1.237",
428
428
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
429
429
  supportPolicy: {
430
- latest: "0.1.235",
430
+ latest: "0.1.237",
431
431
  minimumSupported: "0.1.53",
432
432
  deprecatedBelow: "0.1.219",
433
433
  commandMinimumSupported: [
@@ -1255,6 +1255,7 @@ function createSecretRedactionContext(initialValues = []) {
1255
1255
 
1256
1256
  // ../shared_libs/play-runtime/output-size-limits.ts
1257
1257
  var encoder = typeof TextEncoder === "undefined" ? null : new TextEncoder();
1258
+ var LEDGER_TERMINAL_RESULT_MAX_BYTES = 768 * 1024;
1258
1259
  var CUSTOMER_OUTPUT_VALUE_MAX_BYTES = 1 * 1024 * 1024;
1259
1260
  var CUSTOMER_OUTPUT_TOTAL_MAX_BYTES = 2 * 1024 * 1024;
1260
1261
  var RUNTIME_RECEIPT_OUTPUT_MAX_BYTES = 10 * 1024 * 1024;
package/dist/index.mjs CHANGED
@@ -354,10 +354,10 @@ var SDK_RELEASE = {
354
354
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
355
355
  // 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
356
356
  // automatically without blocking their current command.
357
- version: "0.1.235",
357
+ version: "0.1.237",
358
358
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
359
359
  supportPolicy: {
360
- latest: "0.1.235",
360
+ latest: "0.1.237",
361
361
  minimumSupported: "0.1.53",
362
362
  deprecatedBelow: "0.1.219",
363
363
  commandMinimumSupported: [
@@ -1185,6 +1185,7 @@ function createSecretRedactionContext(initialValues = []) {
1185
1185
 
1186
1186
  // ../shared_libs/play-runtime/output-size-limits.ts
1187
1187
  var encoder = typeof TextEncoder === "undefined" ? null : new TextEncoder();
1188
+ var LEDGER_TERMINAL_RESULT_MAX_BYTES = 768 * 1024;
1188
1189
  var CUSTOMER_OUTPUT_VALUE_MAX_BYTES = 1 * 1024 * 1024;
1189
1190
  var CUSTOMER_OUTPUT_TOTAL_MAX_BYTES = 2 * 1024 * 1024;
1190
1191
  var RUNTIME_RECEIPT_OUTPUT_MAX_BYTES = 10 * 1024 * 1024;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.235",
3
+ "version": "0.1.237",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {