deepline 0.1.219 → 0.1.221

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.
@@ -26,7 +26,7 @@ export type SdkCompatibilityResponse = {
26
26
  auto_update?: {
27
27
  should_auto_update: boolean;
28
28
  required: boolean;
29
- reason: 'required' | 'patch_lag' | 'rollback_forced' | null;
29
+ reason: 'required' | 'deprecated' | 'patch_lag' | 'rollback_forced' | null;
30
30
  patch_lag: number | null;
31
31
  patch_lag_threshold: number;
32
32
  update_command: string;
@@ -106,12 +106,14 @@ export const SDK_RELEASE = {
106
106
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
107
107
  // fields shipped in 0.1.153.
108
108
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
109
- version: '0.1.219',
109
+ // 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
110
+ // automatically without blocking their current command.
111
+ version: '0.1.221',
110
112
  apiContract: '2026-06-dataset-handle-results-hard-cutover',
111
113
  supportPolicy: {
112
- latest: '0.1.219',
114
+ latest: '0.1.221',
113
115
  minimumSupported: '0.1.53',
114
- deprecatedBelow: '0.1.53',
116
+ deprecatedBelow: '0.1.219',
115
117
  commandMinimumSupported: [
116
118
  {
117
119
  command: 'enrich',
@@ -997,6 +997,27 @@ export interface PlayCheckResult {
997
997
  summary?: string;
998
998
  artifactHash?: string | null;
999
999
  graphHash?: string | null;
1000
+ /** SHA-256 of the exact source bytes checked by Deepline. */
1001
+ sourceHash?: string | null;
1002
+ /** Enforceable byte budgets measured during cloud preflight. */
1003
+ limits?: {
1004
+ revisionStorage: {
1005
+ usedBytes: number;
1006
+ limitBytes: number;
1007
+ withinLimit: boolean;
1008
+ breakdown: {
1009
+ sourceCodeBytes: number;
1010
+ staticPipelineBytes: number;
1011
+ bindingsBytes: number;
1012
+ descriptionBytes: number;
1013
+ };
1014
+ };
1015
+ bundle: {
1016
+ usedBytes: number;
1017
+ limitBytes: number;
1018
+ withinLimit: boolean;
1019
+ };
1020
+ };
1000
1021
  }
1001
1022
 
1002
1023
  /** Severity of a {@link PlayCheckIssue}. `error` fails the check; `warning` does not. */
@@ -8741,6 +8741,7 @@ export class PlayContextImpl {
8741
8741
  const httpFailureAttempt = httpFailureAttempts.next({
8742
8742
  toolId,
8743
8743
  status: response.status,
8744
+ bodyText: text,
8744
8745
  transientHttpRetrySafe: retrySafeTransientHttp,
8745
8746
  });
8746
8747
  const failure = classifyToolExecuteHttpFailure({
@@ -57,6 +57,13 @@ const DAYTONA_INFRASTRUCTURE_RETRY_PATTERN =
57
57
  /\b(?:Request failed with status code (?:408|429|500|502|503|504)|ECONNRESET|ECONNREFUSED|ETIMEDOUT|UND_ERR_CONNECT_TIMEOUT|fetch failed|socket hang up|Daytona sandbox create failed across|Daytona sandbox create did not complete)\b/i;
58
58
 
59
59
  type DaytonaExecution = { exitCode: number | null; result: string };
60
+ type DaytonaLookupFailure = {
61
+ errorName: string;
62
+ errorCode: string | null;
63
+ httpStatus: number | null;
64
+ requestId: string | null;
65
+ detail: string;
66
+ };
60
67
  type DaytonaUploadAttemptTiming = {
61
68
  attempt: number;
62
69
  sandboxId: string;
@@ -98,6 +105,72 @@ export const daytonaSdkClientFactory = {
98
105
  },
99
106
  };
100
107
 
108
+ function stringProperty(value: unknown, key: string): string | null {
109
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
110
+ const candidate = (value as Record<string, unknown>)[key];
111
+ return typeof candidate === 'string' && candidate.trim()
112
+ ? candidate.trim()
113
+ : null;
114
+ }
115
+
116
+ function numberProperty(value: unknown, key: string): number | null {
117
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
118
+ const candidate = (value as Record<string, unknown>)[key];
119
+ return typeof candidate === 'number' && Number.isFinite(candidate)
120
+ ? candidate
121
+ : null;
122
+ }
123
+
124
+ function redactDaytonaErrorDetail(value: string): string {
125
+ return value
126
+ .replace(
127
+ /authorization\s*[=:]\s*bearer\s+[^\s,;]+/gi,
128
+ 'Authorization=Bearer [redacted]',
129
+ )
130
+ .replace(
131
+ /(authorization\s*[=:]\s*)(?!bearer\s+\[redacted\])[^\s,;]+/gi,
132
+ '$1[redacted]',
133
+ )
134
+ .replace(/(bearer\s+)[^\s,;]+/gi, '$1[redacted]')
135
+ .replace(
136
+ /((?:api[_-]?key|token)\s*[=:]\s*)[^\s,;]+/gi,
137
+ '$1[redacted]',
138
+ )
139
+ .replace(/\s+/g, ' ')
140
+ .trim()
141
+ .slice(0, 1_000);
142
+ }
143
+
144
+ /**
145
+ * Preserve the actionable shape of a Daytona control-plane failure without
146
+ * copying credentials or an arbitrarily large upstream response into logs.
147
+ */
148
+ export function describeDaytonaLookupFailure(
149
+ error: unknown,
150
+ ): DaytonaLookupFailure {
151
+ const record = error && typeof error === 'object' ? error : null;
152
+ const response = record ? (record as Record<string, unknown>).response : null;
153
+ const headers =
154
+ response && typeof response === 'object'
155
+ ? (response as Record<string, unknown>).headers
156
+ : null;
157
+ const message = error instanceof Error ? error.message : String(error);
158
+ return {
159
+ errorName: error instanceof Error ? error.name || 'Error' : 'NonErrorThrow',
160
+ errorCode: stringProperty(record, 'code'),
161
+ httpStatus:
162
+ numberProperty(record, 'status') ??
163
+ numberProperty(record, 'statusCode') ??
164
+ numberProperty(response, 'status'),
165
+ requestId:
166
+ stringProperty(record, 'requestId') ??
167
+ stringProperty(record, 'request_id') ??
168
+ stringProperty(headers, 'x-request-id') ??
169
+ stringProperty(headers, 'x-daytona-request-id'),
170
+ detail: redactDaytonaErrorDetail(message),
171
+ };
172
+ }
173
+
101
174
  /**
102
175
  * Best-effort deletion of an orphaned Daytona sandbox by id (push-execution B4).
103
176
  *
@@ -189,16 +262,16 @@ export async function inspectDetachedDaytonaRunner(input: {
189
262
  : { stage: 'result_missing', detail: null },
190
263
  };
191
264
  } catch (error) {
192
- const detail = error instanceof Error ? error.message : String(error);
265
+ const failure = describeDaytonaLookupFailure(error);
193
266
  console.warn('[play-runner.daytona.detached_inspect_failed]', {
194
267
  sandboxId: input.sandboxId,
195
268
  cmdId: input.cmdId,
196
- error: detail,
269
+ failure,
197
270
  });
198
271
  return {
199
272
  exitCode: null,
200
273
  result: null,
201
- diagnosis: { stage: 'sandbox_lookup_failed', detail },
274
+ diagnosis: { stage: 'sandbox_lookup_failed', detail: failure.detail },
202
275
  };
203
276
  }
204
277
  }
@@ -10,7 +10,7 @@ const PRIVATE_KEY_RE =
10
10
  // example `absurd-key-rotation-check`) and need an assignment context before
11
11
  // they are sensitive; HIGH_ENTROPY_ASSIGNMENT_RE handles that case below.
12
12
  const COMMON_SECRET_RE =
13
- /\b(?:sk|pk|rk|pat|ghp|github_pat|xox[baprs])[A-Za-z0-9_./+=:-]{12,}\b/gi;
13
+ /\b(?:(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9_./+=:-]{12,}|(?:pat|ghp|github_pat)_[A-Za-z0-9_./+=:-]{12,}|xox[baprs]-[A-Za-z0-9_./+=:-]{12,})\b/gi;
14
14
  const GENERIC_PREFIXED_SECRET_RE =
15
15
  /\b(?:key|token|secret|api[_-]?key)_[A-Za-z0-9_./+=:-]{12,}\b/gi;
16
16
  const URL_SECRET_VALUE_RE =
@@ -5,6 +5,14 @@ import {
5
5
  } from './tool-http-errors';
6
6
 
7
7
  export const TOOL_EXECUTE_TRANSIENT_HTTP_MAX_ATTEMPTS = 2;
8
+ // A provider may need to finish the original request before it can replay the
9
+ // result for this key. With no Retry-After signal, these two polls give the
10
+ // original request ten seconds to finish without repeatedly hammering it.
11
+ export const TOOL_EXECUTE_IDEMPOTENCY_IN_PROGRESS_RETRY_DELAYS_MS = [
12
+ 5_000, 5_000,
13
+ ] as const;
14
+ export const TOOL_EXECUTE_IDEMPOTENCY_IN_PROGRESS_MAX_ATTEMPTS =
15
+ TOOL_EXECUTE_IDEMPOTENCY_IN_PROGRESS_RETRY_DELAYS_MS.length + 1;
8
16
  export const TOOL_EXECUTE_RATE_LIMIT_MAX_ATTEMPTS = 8;
9
17
  export const TOOL_EXECUTE_BARE_RATE_LIMIT_MAX_ATTEMPTS = 2;
10
18
  export const TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS = 3;
@@ -13,6 +21,13 @@ export const TOOL_EXECUTE_RETRY_DELAY_FALLBACK_MS = 1_000;
13
21
  export const TOOL_EXECUTE_RETRY_DELAY_MAX_MS = 5_000;
14
22
  export const TOOL_EXECUTE_BARE_RATE_LIMIT_BACKPRESSURE_MS = 60_000;
15
23
  export const TOOL_EXECUTE_AUTH_SCOPE_CHANGED_CODE = 'AUTH_SCOPE_CHANGED';
24
+ /**
25
+ * A provider/action-local outcome contract emits this only when a provider has
26
+ * explicitly said that the same idempotency key is still executing. It is not
27
+ * a generic HTTP 409 policy.
28
+ */
29
+ export const TOOL_EXECUTE_IDEMPOTENCY_IN_PROGRESS_CODE =
30
+ 'UPSTREAM_IDEMPOTENCY_IN_PROGRESS';
16
31
 
17
32
  export class ToolExecuteAuthScopeChangedError extends Error {
18
33
  readonly code = TOOL_EXECUTE_AUTH_SCOPE_CHANGED_CODE;
@@ -29,6 +44,7 @@ export type ToolExecuteHttpRetryDecision = {
29
44
  attemptCap: number;
30
45
  reason:
31
46
  | 'rate_limit'
47
+ | 'idempotency_in_progress'
32
48
  | 'retry_safe_transient_5xx'
33
49
  | 'unsafe_transient_5xx'
34
50
  | 'hard_billing_error'
@@ -49,6 +65,7 @@ export type ToolExecuteHttpFailureAttemptTracker = {
49
65
  next(input: {
50
66
  toolId: string;
51
67
  status: number;
68
+ bodyText?: string;
52
69
  transientHttpRetrySafe?: boolean;
53
70
  }): number;
54
71
  };
@@ -65,6 +82,29 @@ function parseJsonObject(text: string): Record<string, unknown> | null {
65
82
  }
66
83
  }
67
84
 
85
+ function isIdempotencyInProgressResponse(input: {
86
+ status: number;
87
+ bodyText: string;
88
+ }): boolean {
89
+ if (input.status !== 409) return false;
90
+ const body = parseJsonObject(input.bodyText);
91
+ return (
92
+ body?.code === TOOL_EXECUTE_IDEMPOTENCY_IN_PROGRESS_CODE &&
93
+ body.error_category === 'idempotency_in_progress'
94
+ );
95
+ }
96
+
97
+ function idempotencyInProgressRetryDelayMs(attempt: number): number {
98
+ return (
99
+ TOOL_EXECUTE_IDEMPOTENCY_IN_PROGRESS_RETRY_DELAYS_MS[
100
+ Math.min(
101
+ Math.max(0, attempt - 1),
102
+ TOOL_EXECUTE_IDEMPOTENCY_IN_PROGRESS_RETRY_DELAYS_MS.length - 1,
103
+ )
104
+ ] ?? TOOL_EXECUTE_RETRY_DELAY_FALLBACK_MS
105
+ );
106
+ }
107
+
68
108
  export function parseToolExecuteAuthScopeChangedError(input: {
69
109
  status: number;
70
110
  bodyText: string;
@@ -84,6 +124,7 @@ export function parseToolExecuteAuthScopeChangedError(input: {
84
124
 
85
125
  function decideToolExecuteHttpRetry(input: {
86
126
  status: number;
127
+ idempotencyInProgress?: boolean;
87
128
  hardBillingFailure?: boolean;
88
129
  hasRetryAfterHeader?: boolean;
89
130
  transientHttpRetrySafe?: boolean;
@@ -110,6 +151,13 @@ function decideToolExecuteHttpRetry(input: {
110
151
  reason: 'rate_limit',
111
152
  };
112
153
  }
154
+ if (input.idempotencyInProgress) {
155
+ return {
156
+ retryable: true,
157
+ attemptCap: TOOL_EXECUTE_IDEMPOTENCY_IN_PROGRESS_MAX_ATTEMPTS,
158
+ reason: 'idempotency_in_progress',
159
+ };
160
+ }
113
161
  if (input.status >= 500 && input.status < 600) {
114
162
  if (!input.transientHttpRetrySafe) {
115
163
  return {
@@ -137,6 +185,7 @@ export function createToolExecuteHttpFailureAttemptTracker(): ToolExecuteHttpFai
137
185
  number
138
186
  > = {
139
187
  rate_limit: 0,
188
+ idempotency_in_progress: 0,
140
189
  retry_safe_transient_5xx: 0,
141
190
  unsafe_transient_5xx: 0,
142
191
  hard_billing_error: 0,
@@ -147,6 +196,10 @@ export function createToolExecuteHttpFailureAttemptTracker(): ToolExecuteHttpFai
147
196
  next(input) {
148
197
  const decision = decideToolExecuteHttpRetry({
149
198
  status: input.status,
199
+ idempotencyInProgress: isIdempotencyInProgressResponse({
200
+ status: input.status,
201
+ bodyText: input.bodyText ?? '',
202
+ }),
150
203
  hasRetryAfterHeader: true,
151
204
  transientHttpRetrySafe: input.transientHttpRetrySafe === true,
152
205
  });
@@ -186,8 +239,10 @@ export function classifyToolExecuteHttpFailure(input: {
186
239
  const hasRetryAfterHeader =
187
240
  typeof input.retryAfterHeader === 'string' &&
188
241
  input.retryAfterHeader.trim().length > 0;
242
+ const idempotencyInProgress = isIdempotencyInProgressResponse(input);
189
243
  const initialRetryDecision = decideToolExecuteHttpRetry({
190
244
  status: input.status,
245
+ idempotencyInProgress,
191
246
  hasRetryAfterHeader,
192
247
  transientHttpRetrySafe,
193
248
  });
@@ -208,6 +263,7 @@ export function classifyToolExecuteHttpFailure(input: {
208
263
  const hardBillingFailure = isHardBillingToolHttpError(error);
209
264
  const retryDecision = decideToolExecuteHttpRetry({
210
265
  status: input.status,
266
+ idempotencyInProgress,
211
267
  hardBillingFailure,
212
268
  hasRetryAfterHeader,
213
269
  transientHttpRetrySafe,
@@ -238,9 +294,12 @@ export function classifyToolExecuteHttpFailure(input: {
238
294
  TOOL_EXECUTE_RETRY_DELAY_FALLBACK_MS * input.attempt,
239
295
  ),
240
296
  )
241
- : retryAfterMs > 0
242
- ? Math.min(TOOL_EXECUTE_RETRY_DELAY_MAX_MS, retryAfterMs)
243
- : TOOL_EXECUTE_RETRY_DELAY_FALLBACK_MS;
297
+ : retryDecision.reason === 'idempotency_in_progress' &&
298
+ !hasRetryAfterHeader
299
+ ? idempotencyInProgressRetryDelayMs(input.attempt)
300
+ : retryAfterMs > 0
301
+ ? Math.min(TOOL_EXECUTE_RETRY_DELAY_MAX_MS, retryAfterMs)
302
+ : TOOL_EXECUTE_RETRY_DELAY_FALLBACK_MS;
244
303
  return {
245
304
  ...retryDecision,
246
305
  error,
package/dist/cli/index.js CHANGED
@@ -623,12 +623,14 @@ var SDK_RELEASE = {
623
623
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
624
624
  // fields shipped in 0.1.153.
625
625
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
626
- version: "0.1.219",
626
+ // 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
627
+ // automatically without blocking their current command.
628
+ version: "0.1.221",
627
629
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
628
630
  supportPolicy: {
629
- latest: "0.1.219",
631
+ latest: "0.1.221",
630
632
  minimumSupported: "0.1.53",
631
- deprecatedBelow: "0.1.53",
633
+ deprecatedBelow: "0.1.219",
632
634
  commandMinimumSupported: [
633
635
  {
634
636
  command: "enrich",
@@ -1364,7 +1366,7 @@ var SECRET_REDACTION_PLACEHOLDER = "[REDACTED_SECRET]";
1364
1366
  var BEARER_TOKEN_RE = /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}\b/g;
1365
1367
  var JWT_RE = /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g;
1366
1368
  var PRIVATE_KEY_RE = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g;
1367
- var COMMON_SECRET_RE = /\b(?:sk|pk|rk|pat|ghp|github_pat|xox[baprs])[A-Za-z0-9_./+=:-]{12,}\b/gi;
1369
+ var COMMON_SECRET_RE = /\b(?:(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9_./+=:-]{12,}|(?:pat|ghp|github_pat)_[A-Za-z0-9_./+=:-]{12,}|xox[baprs]-[A-Za-z0-9_./+=:-]{12,})\b/gi;
1368
1370
  var GENERIC_PREFIXED_SECRET_RE = /\b(?:key|token|secret|api[_-]?key)_[A-Za-z0-9_./+=:-]{12,}\b/gi;
1369
1371
  var URL_SECRET_VALUE_RE = /\b(?:sk|pk|rk|pat|ghp|github_pat|xox)[A-Za-z0-9_./+=:-]{12,}\b/i;
1370
1372
  var GENERIC_SECRET_ASSIGNMENT_RE = /\b(?:api[_-]?key|token|secret|password)\b\s*[:=]\s*["']?[^"',\s]{12,}["']?/gi;
@@ -14646,6 +14648,73 @@ function parsePlayCheckOptions(args) {
14646
14648
  const jsonOutput = argsWantJson(args);
14647
14649
  return { target, jsonOutput };
14648
14650
  }
14651
+ function parsePlayPublishOptions(args) {
14652
+ const target = args[0];
14653
+ if (!target) {
14654
+ throw new Error(
14655
+ "Usage: deepline plays publish <play-file.ts|play-name> [--expected-artifact <hash>] [--dry-run] [--latest|--revision-id <id>] [--json]"
14656
+ );
14657
+ }
14658
+ let revisionId;
14659
+ let useLatest = false;
14660
+ let expectedArtifactHash;
14661
+ let dryRun = false;
14662
+ for (let index = 1; index < args.length; index += 1) {
14663
+ const arg = args[index];
14664
+ if (arg === "--revision-id") {
14665
+ revisionId = args[++index];
14666
+ if (!revisionId) throw new Error("--revision-id requires a revision id.");
14667
+ continue;
14668
+ }
14669
+ if (arg === "--latest") {
14670
+ useLatest = true;
14671
+ continue;
14672
+ }
14673
+ if (arg === "--expected-artifact") {
14674
+ expectedArtifactHash = args[++index];
14675
+ if (!expectedArtifactHash) {
14676
+ throw new Error("--expected-artifact requires an artifact hash.");
14677
+ }
14678
+ continue;
14679
+ }
14680
+ if (arg.startsWith("--expected-artifact=")) {
14681
+ expectedArtifactHash = arg.slice("--expected-artifact=".length);
14682
+ if (!expectedArtifactHash) {
14683
+ throw new Error("--expected-artifact requires an artifact hash.");
14684
+ }
14685
+ continue;
14686
+ }
14687
+ if (arg === "--dry-run") {
14688
+ dryRun = true;
14689
+ continue;
14690
+ }
14691
+ if (arg === "--json") continue;
14692
+ throw new Error(`Unknown plays publish option: ${arg}`);
14693
+ }
14694
+ if (revisionId && useLatest) {
14695
+ throw new Error("Choose only one live target: --latest or --revision-id.");
14696
+ }
14697
+ return {
14698
+ target,
14699
+ revisionId,
14700
+ useLatest,
14701
+ expectedArtifactHash,
14702
+ dryRun,
14703
+ jsonOutput: argsWantJson(args)
14704
+ };
14705
+ }
14706
+ function formatByteBudget(usedBytes, limitBytes) {
14707
+ return `${usedBytes.toLocaleString()} B / ${limitBytes.toLocaleString()} B`;
14708
+ }
14709
+ function printPlayCheckLimits(limits) {
14710
+ if (!limits) return;
14711
+ console.log(
14712
+ ` revision storage: ${formatByteBudget(limits.revisionStorage.usedBytes, limits.revisionStorage.limitBytes)}`
14713
+ );
14714
+ console.log(
14715
+ ` bundle: ${formatByteBudget(limits.bundle.usedBytes, limits.bundle.limitBytes)}`
14716
+ );
14717
+ }
14649
14718
  function shouldUseLocalOnlyPlayCheck() {
14650
14719
  const value = process.env.DEEPLINE_PLAY_CHECK_LOCAL_ONLY?.trim().toLowerCase();
14651
14720
  return value === "1" || value === "true" || value === "yes" || value === "on";
@@ -14958,7 +15027,11 @@ async function handlePlayCheck(args) {
14958
15027
  );
14959
15028
  } else {
14960
15029
  console.log(`\u2713 ${playName} passed local play check`);
15030
+ console.log(` source: ${graph.root.artifact.sourceHash.slice(0, 12)}`);
14961
15031
  console.log(` artifact: ${result2.artifactHash.slice(0, 12)}`);
15032
+ console.log(
15033
+ ` publish: deepline plays publish ${shellQuote2(options.target)} --expected-artifact ${result2.artifactHash}`
15034
+ );
14962
15035
  printToolGetterHints(result2.toolGetterHints);
14963
15036
  }
14964
15037
  return 0;
@@ -15000,6 +15073,15 @@ async function handlePlayCheck(args) {
15000
15073
  if (enrichedResult.artifactHash) {
15001
15074
  console.log(` artifact: ${enrichedResult.artifactHash.slice(0, 12)}`);
15002
15075
  }
15076
+ if (enrichedResult.sourceHash) {
15077
+ console.log(` source: ${enrichedResult.sourceHash.slice(0, 12)}`);
15078
+ }
15079
+ printPlayCheckLimits(enrichedResult.limits);
15080
+ if (enrichedResult.artifactHash) {
15081
+ console.log(
15082
+ ` publish: deepline plays publish ${shellQuote2(options.target)} --expected-artifact ${enrichedResult.artifactHash}`
15083
+ );
15084
+ }
15003
15085
  printPlayTriggers(enrichedResult.triggers);
15004
15086
  printRecognizedSummary(enrichedResult.recognized);
15005
15087
  printPlayCheckIssues(enrichedResult.issues, (line) => console.log(line));
@@ -16356,28 +16438,15 @@ async function handlePlayDescribe(args) {
16356
16438
  return 0;
16357
16439
  }
16358
16440
  async function handlePlayPublish(args) {
16359
- const playName = args[0];
16360
- if (!playName) {
16361
- console.error(
16362
- "Usage: deepline plays publish <play-file.ts|play-name> [--latest|--revision-id <id>] [--json]"
16363
- );
16364
- return 1;
16365
- }
16366
- let revisionId;
16367
- let useLatest = false;
16368
- for (let index = 1; index < args.length; index += 1) {
16369
- const arg = args[index];
16370
- if (arg === "--revision-id" && args[index + 1]) {
16371
- revisionId = args[++index];
16372
- }
16373
- if (arg === "--latest") {
16374
- useLatest = true;
16375
- }
16376
- }
16377
- if (revisionId && useLatest) {
16378
- console.error("Choose only one live target: --latest or --revision-id.");
16379
- return 1;
16441
+ let options;
16442
+ try {
16443
+ options = parsePlayPublishOptions(args);
16444
+ } catch (error) {
16445
+ console.error(error instanceof Error ? error.message : String(error));
16446
+ return 2;
16380
16447
  }
16448
+ const { target: playName, useLatest } = options;
16449
+ let revisionId = options.revisionId;
16381
16450
  const client2 = new DeeplineClient();
16382
16451
  if (isFileTarget(playName)) {
16383
16452
  if (revisionId || useLatest) {
@@ -16394,6 +16463,66 @@ async function handlePlayPublish(args) {
16394
16463
  () => collectBundledPlayGraph((0, import_node_path11.resolve)(playName))
16395
16464
  );
16396
16465
  assertBundledPlayGraphDescriptions(graph);
16466
+ } catch (error) {
16467
+ console.error(error instanceof Error ? error.message : String(error));
16468
+ return 1;
16469
+ }
16470
+ const rootPlayName = graph.root.playName ?? extractPlayName(graph.root.sourceCode, graph.root.filePath);
16471
+ const artifactHash = graph.root.artifact.artifactHash;
16472
+ if (options.expectedArtifactHash && options.expectedArtifactHash !== artifactHash) {
16473
+ const result3 = {
16474
+ ok: false,
16475
+ code: "PLAY_ARTIFACT_HASH_MISMATCH",
16476
+ message: "Refusing to publish: the local file no longer produces the artifact checked earlier.",
16477
+ expectedArtifactHash: options.expectedArtifactHash,
16478
+ currentArtifactHash: artifactHash,
16479
+ next: `deepline plays check ${shellQuote2(playName)}`
16480
+ };
16481
+ if (options.jsonOutput) {
16482
+ process.stdout.write(`${JSON.stringify(result3)}
16483
+ `);
16484
+ } else {
16485
+ console.error(result3.message);
16486
+ console.error(` checked artifact: ${result3.expectedArtifactHash}`);
16487
+ console.error(` current artifact: ${result3.currentArtifactHash}`);
16488
+ console.error(` run: ${result3.next}`);
16489
+ }
16490
+ return 7;
16491
+ }
16492
+ const dryRun = {
16493
+ ok: true,
16494
+ dryRun: true,
16495
+ name: rootPlayName,
16496
+ sourcePath: graph.root.filePath,
16497
+ sourceHash: graph.root.artifact.sourceHash,
16498
+ artifactHash,
16499
+ graphHash: graph.root.artifact.graphHash,
16500
+ importedPlayCount: Math.max(0, graph.nodes.size - 1),
16501
+ plannedMutation: "register revision and promote it live",
16502
+ expectedArtifactHash: options.expectedArtifactHash ?? null
16503
+ };
16504
+ if (options.dryRun) {
16505
+ if (options.jsonOutput) {
16506
+ process.stdout.write(`${JSON.stringify(dryRun)}
16507
+ `);
16508
+ } else {
16509
+ console.log(`Dry run: ${rootPlayName}`);
16510
+ console.log(` source: ${dryRun.sourceHash}`);
16511
+ console.log(` artifact: ${dryRun.artifactHash}`);
16512
+ console.log(` would: ${dryRun.plannedMutation}`);
16513
+ }
16514
+ return 0;
16515
+ }
16516
+ let previousLive = null;
16517
+ try {
16518
+ previousLive = (await client2.getPlay(rootPlayName)).play.liveRevision ?? null;
16519
+ } catch (error) {
16520
+ if (!(error instanceof DeeplineError) || error.statusCode !== 404) {
16521
+ console.error(error instanceof Error ? error.message : String(error));
16522
+ return 5;
16523
+ }
16524
+ }
16525
+ try {
16397
16526
  await traceCliSpan(
16398
16527
  "cli.play_publish_compile_manifests",
16399
16528
  { targetKind: "file", nodeCount: graph.nodes.size },
@@ -16408,13 +16537,12 @@ async function handlePlayPublish(args) {
16408
16537
  console.error(error instanceof Error ? error.message : String(error));
16409
16538
  return 1;
16410
16539
  }
16411
- const rootPlayName = graph.root.playName ?? extractPlayName(graph.root.sourceCode, graph.root.filePath);
16412
16540
  const published = await traceCliSpan(
16413
16541
  "cli.play_publish_register_root",
16414
16542
  {
16415
16543
  targetKind: "file",
16416
16544
  playName: rootPlayName,
16417
- artifactHash: graph.root.artifact.artifactHash
16545
+ artifactHash
16418
16546
  },
16419
16547
  () => client2.registerPlayArtifact({
16420
16548
  name: rootPlayName,
@@ -16426,17 +16554,63 @@ async function handlePlayPublish(args) {
16426
16554
  publish: true
16427
16555
  })
16428
16556
  );
16429
- process.stdout.write(
16430
- `${JSON.stringify({
16431
- success: true,
16432
- name: rootPlayName,
16433
- liveVersion: published.version ?? null,
16434
- revisionId: published.revisionId ?? null,
16435
- triggerMetadata: published.triggerMetadata ?? null,
16436
- triggerBindings: published.triggerBindings ?? []
16437
- })}
16438
- `
16439
- );
16557
+ const result2 = {
16558
+ success: true,
16559
+ name: rootPlayName,
16560
+ sourcePath: graph.root.filePath,
16561
+ sourceHash: graph.root.artifact.sourceHash,
16562
+ artifactHash,
16563
+ liveVersion: published.version ?? null,
16564
+ revisionId: published.revisionId ?? null,
16565
+ previousLiveVersion: previousLive?.version ?? null,
16566
+ previousArtifactHash: previousLive?.artifactHash ?? null,
16567
+ sourceBytes: {
16568
+ before: previousLive?.sourceCode ? Buffer.byteLength(previousLive.sourceCode, "utf8") : null,
16569
+ after: Buffer.byteLength(graph.root.sourceCode, "utf8")
16570
+ },
16571
+ triggerMetadata: published.triggerMetadata ?? null,
16572
+ triggerBindings: published.triggerBindings ?? []
16573
+ };
16574
+ if (options.jsonOutput) {
16575
+ process.stdout.write(`${JSON.stringify(result2)}
16576
+ `);
16577
+ } else {
16578
+ console.log(
16579
+ `\u2713 Published ${rootPlayName} as v${result2.liveVersion ?? "?"}`
16580
+ );
16581
+ console.log(` source: ${result2.sourceHash.slice(0, 12)}`);
16582
+ console.log(` artifact: ${result2.artifactHash.slice(0, 12)}`);
16583
+ if (result2.previousLiveVersion !== null) {
16584
+ console.log(
16585
+ ` previous live: v${result2.previousLiveVersion} (${result2.previousArtifactHash?.slice(0, 12) ?? "unknown artifact"})`
16586
+ );
16587
+ }
16588
+ if (result2.sourceBytes.before !== null) {
16589
+ console.log(
16590
+ ` source bytes: ${result2.sourceBytes.before.toLocaleString()} \u2192 ${result2.sourceBytes.after.toLocaleString()}`
16591
+ );
16592
+ }
16593
+ console.log(
16594
+ ` revisions: deepline plays versions --name ${rootPlayName}`
16595
+ );
16596
+ }
16597
+ return 0;
16598
+ }
16599
+ if (options.expectedArtifactHash) {
16600
+ console.error("--expected-artifact only applies to a local play file.");
16601
+ return 2;
16602
+ }
16603
+ if (options.dryRun) {
16604
+ const result2 = {
16605
+ ok: true,
16606
+ dryRun: true,
16607
+ name: playName,
16608
+ plannedMutation: "promote a saved revision live",
16609
+ revisionId: revisionId ?? null,
16610
+ latest: useLatest
16611
+ };
16612
+ process.stdout.write(`${JSON.stringify(result2)}
16613
+ `);
16440
16614
  return 0;
16441
16615
  }
16442
16616
  const resolvedName = parseReferencedPlayTarget2(playName).playName;
@@ -16800,7 +16974,9 @@ Notes:
16800
16974
  Mutates cloud state. For a saved play, --latest or --revision-id promotes an
16801
16975
  existing saved revision live.
16802
16976
  Local .play.ts publishing bundles the file, validates it, saves a revision,
16803
- and promotes that revision live.
16977
+ and promotes that revision live. Use --expected-artifact with the hash from
16978
+ plays check to refuse publishing bytes that changed after validation.
16979
+ --dry-run bundles only and prints the exact artifact that would be published.
16804
16980
  Running a local file with plays run does not publish it.
16805
16981
 
16806
16982
  Examples:
@@ -16808,27 +16984,40 @@ Examples:
16808
16984
  deepline plays set-live my-play --revision-id <revision-id> --json
16809
16985
  deepline plays set-live my.play.ts --json
16810
16986
  deepline plays publish my.play.ts --json
16987
+ deepline plays check my.play.ts
16988
+ deepline plays publish my.play.ts --expected-artifact <artifact-hash>
16989
+ deepline plays publish my.play.ts --dry-run --json
16811
16990
  `
16812
16991
  );
16813
16992
  addPublishHelp(
16814
16993
  play.command("publish <target>").description(
16815
16994
  "Promote a saved play revision or publish a local play file."
16816
16995
  )
16817
- ).option("--latest", "Promote the newest saved revision").option("--revision-id <id>", "Revision to promote").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (target, options) => {
16996
+ ).option("--latest", "Promote the newest saved revision").option("--revision-id <id>", "Revision to promote").option(
16997
+ "--expected-artifact <hash>",
16998
+ "Require the local file to produce this checked artifact hash"
16999
+ ).option("--dry-run", "Bundle and show the planned publish without mutation").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (target, options) => {
16818
17000
  process.exitCode = await handlePlayPublish([
16819
17001
  target,
16820
17002
  ...options.latest ? ["--latest"] : [],
16821
17003
  ...options.revisionId ? ["--revision-id", options.revisionId] : [],
17004
+ ...options.expectedArtifact ? ["--expected-artifact", options.expectedArtifact] : [],
17005
+ ...options.dryRun ? ["--dry-run"] : [],
16822
17006
  ...options.json ? ["--json"] : []
16823
17007
  ]);
16824
17008
  });
16825
17009
  addPublishHelp(
16826
17010
  play.command("set-live <target>").description("Promote a saved revision or publish a local play file.")
16827
- ).option("--latest", "Promote the newest saved revision").option("--revision-id <id>", "Revision to promote").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (target, options) => {
17011
+ ).option("--latest", "Promote the newest saved revision").option("--revision-id <id>", "Revision to promote").option(
17012
+ "--expected-artifact <hash>",
17013
+ "Require the local file to produce this checked artifact hash"
17014
+ ).option("--dry-run", "Bundle and show the planned publish without mutation").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (target, options) => {
16828
17015
  process.exitCode = await handlePlayPublish([
16829
17016
  target,
16830
17017
  ...options.latest ? ["--latest"] : [],
16831
17018
  ...options.revisionId ? ["--revision-id", options.revisionId] : [],
17019
+ ...options.expectedArtifact ? ["--expected-artifact", options.expectedArtifact] : [],
17020
+ ...options.dryRun ? ["--dry-run"] : [],
16832
17021
  ...options.json ? ["--json"] : []
16833
17022
  ]);
16834
17023
  });
@@ -28940,7 +29129,7 @@ async function maybeAutoUpdateAndRelaunch(response) {
28940
29129
  if (plan.kind === "source") {
28941
29130
  return false;
28942
29131
  }
28943
- const label = autoUpdate.reason === "rollback_forced" ? "has a server rollback pending and needs the latest rollback-aware CLI" : autoUpdate.required ? "requires an update" : "is more than the supported auto-update lag behind";
29132
+ const label = autoUpdate.reason === "rollback_forced" ? "has a server rollback pending and needs the latest rollback-aware CLI" : autoUpdate.reason === "deprecated" ? "is deprecated and will update automatically" : autoUpdate.required ? "requires an update" : "is more than the supported auto-update lag behind";
28944
29133
  process.stderr.write(
28945
29134
  `Deepline SDK/CLI ${label}; running ${plan.manualCommand}
28946
29135
  `
@@ -608,12 +608,14 @@ var SDK_RELEASE = {
608
608
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
609
609
  // fields shipped in 0.1.153.
610
610
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
611
- version: "0.1.219",
611
+ // 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
612
+ // automatically without blocking their current command.
613
+ version: "0.1.221",
612
614
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
613
615
  supportPolicy: {
614
- latest: "0.1.219",
616
+ latest: "0.1.221",
615
617
  minimumSupported: "0.1.53",
616
- deprecatedBelow: "0.1.53",
618
+ deprecatedBelow: "0.1.219",
617
619
  commandMinimumSupported: [
618
620
  {
619
621
  command: "enrich",
@@ -1349,7 +1351,7 @@ var SECRET_REDACTION_PLACEHOLDER = "[REDACTED_SECRET]";
1349
1351
  var BEARER_TOKEN_RE = /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}\b/g;
1350
1352
  var JWT_RE = /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g;
1351
1353
  var PRIVATE_KEY_RE = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g;
1352
- var COMMON_SECRET_RE = /\b(?:sk|pk|rk|pat|ghp|github_pat|xox[baprs])[A-Za-z0-9_./+=:-]{12,}\b/gi;
1354
+ var COMMON_SECRET_RE = /\b(?:(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9_./+=:-]{12,}|(?:pat|ghp|github_pat)_[A-Za-z0-9_./+=:-]{12,}|xox[baprs]-[A-Za-z0-9_./+=:-]{12,})\b/gi;
1353
1355
  var GENERIC_PREFIXED_SECRET_RE = /\b(?:key|token|secret|api[_-]?key)_[A-Za-z0-9_./+=:-]{12,}\b/gi;
1354
1356
  var URL_SECRET_VALUE_RE = /\b(?:sk|pk|rk|pat|ghp|github_pat|xox)[A-Za-z0-9_./+=:-]{12,}\b/i;
1355
1357
  var GENERIC_SECRET_ASSIGNMENT_RE = /\b(?:api[_-]?key|token|secret|password)\b\s*[:=]\s*["']?[^"',\s]{12,}["']?/gi;
@@ -14675,6 +14677,73 @@ function parsePlayCheckOptions(args) {
14675
14677
  const jsonOutput = argsWantJson(args);
14676
14678
  return { target, jsonOutput };
14677
14679
  }
14680
+ function parsePlayPublishOptions(args) {
14681
+ const target = args[0];
14682
+ if (!target) {
14683
+ throw new Error(
14684
+ "Usage: deepline plays publish <play-file.ts|play-name> [--expected-artifact <hash>] [--dry-run] [--latest|--revision-id <id>] [--json]"
14685
+ );
14686
+ }
14687
+ let revisionId;
14688
+ let useLatest = false;
14689
+ let expectedArtifactHash;
14690
+ let dryRun = false;
14691
+ for (let index = 1; index < args.length; index += 1) {
14692
+ const arg = args[index];
14693
+ if (arg === "--revision-id") {
14694
+ revisionId = args[++index];
14695
+ if (!revisionId) throw new Error("--revision-id requires a revision id.");
14696
+ continue;
14697
+ }
14698
+ if (arg === "--latest") {
14699
+ useLatest = true;
14700
+ continue;
14701
+ }
14702
+ if (arg === "--expected-artifact") {
14703
+ expectedArtifactHash = args[++index];
14704
+ if (!expectedArtifactHash) {
14705
+ throw new Error("--expected-artifact requires an artifact hash.");
14706
+ }
14707
+ continue;
14708
+ }
14709
+ if (arg.startsWith("--expected-artifact=")) {
14710
+ expectedArtifactHash = arg.slice("--expected-artifact=".length);
14711
+ if (!expectedArtifactHash) {
14712
+ throw new Error("--expected-artifact requires an artifact hash.");
14713
+ }
14714
+ continue;
14715
+ }
14716
+ if (arg === "--dry-run") {
14717
+ dryRun = true;
14718
+ continue;
14719
+ }
14720
+ if (arg === "--json") continue;
14721
+ throw new Error(`Unknown plays publish option: ${arg}`);
14722
+ }
14723
+ if (revisionId && useLatest) {
14724
+ throw new Error("Choose only one live target: --latest or --revision-id.");
14725
+ }
14726
+ return {
14727
+ target,
14728
+ revisionId,
14729
+ useLatest,
14730
+ expectedArtifactHash,
14731
+ dryRun,
14732
+ jsonOutput: argsWantJson(args)
14733
+ };
14734
+ }
14735
+ function formatByteBudget(usedBytes, limitBytes) {
14736
+ return `${usedBytes.toLocaleString()} B / ${limitBytes.toLocaleString()} B`;
14737
+ }
14738
+ function printPlayCheckLimits(limits) {
14739
+ if (!limits) return;
14740
+ console.log(
14741
+ ` revision storage: ${formatByteBudget(limits.revisionStorage.usedBytes, limits.revisionStorage.limitBytes)}`
14742
+ );
14743
+ console.log(
14744
+ ` bundle: ${formatByteBudget(limits.bundle.usedBytes, limits.bundle.limitBytes)}`
14745
+ );
14746
+ }
14678
14747
  function shouldUseLocalOnlyPlayCheck() {
14679
14748
  const value = process.env.DEEPLINE_PLAY_CHECK_LOCAL_ONLY?.trim().toLowerCase();
14680
14749
  return value === "1" || value === "true" || value === "yes" || value === "on";
@@ -14987,7 +15056,11 @@ async function handlePlayCheck(args) {
14987
15056
  );
14988
15057
  } else {
14989
15058
  console.log(`\u2713 ${playName} passed local play check`);
15059
+ console.log(` source: ${graph.root.artifact.sourceHash.slice(0, 12)}`);
14990
15060
  console.log(` artifact: ${result2.artifactHash.slice(0, 12)}`);
15061
+ console.log(
15062
+ ` publish: deepline plays publish ${shellQuote2(options.target)} --expected-artifact ${result2.artifactHash}`
15063
+ );
14991
15064
  printToolGetterHints(result2.toolGetterHints);
14992
15065
  }
14993
15066
  return 0;
@@ -15029,6 +15102,15 @@ async function handlePlayCheck(args) {
15029
15102
  if (enrichedResult.artifactHash) {
15030
15103
  console.log(` artifact: ${enrichedResult.artifactHash.slice(0, 12)}`);
15031
15104
  }
15105
+ if (enrichedResult.sourceHash) {
15106
+ console.log(` source: ${enrichedResult.sourceHash.slice(0, 12)}`);
15107
+ }
15108
+ printPlayCheckLimits(enrichedResult.limits);
15109
+ if (enrichedResult.artifactHash) {
15110
+ console.log(
15111
+ ` publish: deepline plays publish ${shellQuote2(options.target)} --expected-artifact ${enrichedResult.artifactHash}`
15112
+ );
15113
+ }
15032
15114
  printPlayTriggers(enrichedResult.triggers);
15033
15115
  printRecognizedSummary(enrichedResult.recognized);
15034
15116
  printPlayCheckIssues(enrichedResult.issues, (line) => console.log(line));
@@ -16385,28 +16467,15 @@ async function handlePlayDescribe(args) {
16385
16467
  return 0;
16386
16468
  }
16387
16469
  async function handlePlayPublish(args) {
16388
- const playName = args[0];
16389
- if (!playName) {
16390
- console.error(
16391
- "Usage: deepline plays publish <play-file.ts|play-name> [--latest|--revision-id <id>] [--json]"
16392
- );
16393
- return 1;
16394
- }
16395
- let revisionId;
16396
- let useLatest = false;
16397
- for (let index = 1; index < args.length; index += 1) {
16398
- const arg = args[index];
16399
- if (arg === "--revision-id" && args[index + 1]) {
16400
- revisionId = args[++index];
16401
- }
16402
- if (arg === "--latest") {
16403
- useLatest = true;
16404
- }
16405
- }
16406
- if (revisionId && useLatest) {
16407
- console.error("Choose only one live target: --latest or --revision-id.");
16408
- return 1;
16470
+ let options;
16471
+ try {
16472
+ options = parsePlayPublishOptions(args);
16473
+ } catch (error) {
16474
+ console.error(error instanceof Error ? error.message : String(error));
16475
+ return 2;
16409
16476
  }
16477
+ const { target: playName, useLatest } = options;
16478
+ let revisionId = options.revisionId;
16410
16479
  const client2 = new DeeplineClient();
16411
16480
  if (isFileTarget(playName)) {
16412
16481
  if (revisionId || useLatest) {
@@ -16423,6 +16492,66 @@ async function handlePlayPublish(args) {
16423
16492
  () => collectBundledPlayGraph(resolve8(playName))
16424
16493
  );
16425
16494
  assertBundledPlayGraphDescriptions(graph);
16495
+ } catch (error) {
16496
+ console.error(error instanceof Error ? error.message : String(error));
16497
+ return 1;
16498
+ }
16499
+ const rootPlayName = graph.root.playName ?? extractPlayName(graph.root.sourceCode, graph.root.filePath);
16500
+ const artifactHash = graph.root.artifact.artifactHash;
16501
+ if (options.expectedArtifactHash && options.expectedArtifactHash !== artifactHash) {
16502
+ const result3 = {
16503
+ ok: false,
16504
+ code: "PLAY_ARTIFACT_HASH_MISMATCH",
16505
+ message: "Refusing to publish: the local file no longer produces the artifact checked earlier.",
16506
+ expectedArtifactHash: options.expectedArtifactHash,
16507
+ currentArtifactHash: artifactHash,
16508
+ next: `deepline plays check ${shellQuote2(playName)}`
16509
+ };
16510
+ if (options.jsonOutput) {
16511
+ process.stdout.write(`${JSON.stringify(result3)}
16512
+ `);
16513
+ } else {
16514
+ console.error(result3.message);
16515
+ console.error(` checked artifact: ${result3.expectedArtifactHash}`);
16516
+ console.error(` current artifact: ${result3.currentArtifactHash}`);
16517
+ console.error(` run: ${result3.next}`);
16518
+ }
16519
+ return 7;
16520
+ }
16521
+ const dryRun = {
16522
+ ok: true,
16523
+ dryRun: true,
16524
+ name: rootPlayName,
16525
+ sourcePath: graph.root.filePath,
16526
+ sourceHash: graph.root.artifact.sourceHash,
16527
+ artifactHash,
16528
+ graphHash: graph.root.artifact.graphHash,
16529
+ importedPlayCount: Math.max(0, graph.nodes.size - 1),
16530
+ plannedMutation: "register revision and promote it live",
16531
+ expectedArtifactHash: options.expectedArtifactHash ?? null
16532
+ };
16533
+ if (options.dryRun) {
16534
+ if (options.jsonOutput) {
16535
+ process.stdout.write(`${JSON.stringify(dryRun)}
16536
+ `);
16537
+ } else {
16538
+ console.log(`Dry run: ${rootPlayName}`);
16539
+ console.log(` source: ${dryRun.sourceHash}`);
16540
+ console.log(` artifact: ${dryRun.artifactHash}`);
16541
+ console.log(` would: ${dryRun.plannedMutation}`);
16542
+ }
16543
+ return 0;
16544
+ }
16545
+ let previousLive = null;
16546
+ try {
16547
+ previousLive = (await client2.getPlay(rootPlayName)).play.liveRevision ?? null;
16548
+ } catch (error) {
16549
+ if (!(error instanceof DeeplineError) || error.statusCode !== 404) {
16550
+ console.error(error instanceof Error ? error.message : String(error));
16551
+ return 5;
16552
+ }
16553
+ }
16554
+ try {
16426
16555
  await traceCliSpan(
16427
16556
  "cli.play_publish_compile_manifests",
16428
16557
  { targetKind: "file", nodeCount: graph.nodes.size },
@@ -16437,13 +16566,12 @@ async function handlePlayPublish(args) {
16437
16566
  console.error(error instanceof Error ? error.message : String(error));
16438
16567
  return 1;
16439
16568
  }
16440
- const rootPlayName = graph.root.playName ?? extractPlayName(graph.root.sourceCode, graph.root.filePath);
16441
16569
  const published = await traceCliSpan(
16442
16570
  "cli.play_publish_register_root",
16443
16571
  {
16444
16572
  targetKind: "file",
16445
16573
  playName: rootPlayName,
16446
- artifactHash: graph.root.artifact.artifactHash
16574
+ artifactHash
16447
16575
  },
16448
16576
  () => client2.registerPlayArtifact({
16449
16577
  name: rootPlayName,
@@ -16455,17 +16583,63 @@ async function handlePlayPublish(args) {
16455
16583
  publish: true
16456
16584
  })
16457
16585
  );
16458
- process.stdout.write(
16459
- `${JSON.stringify({
16460
- success: true,
16461
- name: rootPlayName,
16462
- liveVersion: published.version ?? null,
16463
- revisionId: published.revisionId ?? null,
16464
- triggerMetadata: published.triggerMetadata ?? null,
16465
- triggerBindings: published.triggerBindings ?? []
16466
- })}
16467
- `
16468
- );
16586
+ const result2 = {
16587
+ success: true,
16588
+ name: rootPlayName,
16589
+ sourcePath: graph.root.filePath,
16590
+ sourceHash: graph.root.artifact.sourceHash,
16591
+ artifactHash,
16592
+ liveVersion: published.version ?? null,
16593
+ revisionId: published.revisionId ?? null,
16594
+ previousLiveVersion: previousLive?.version ?? null,
16595
+ previousArtifactHash: previousLive?.artifactHash ?? null,
16596
+ sourceBytes: {
16597
+ before: previousLive?.sourceCode ? Buffer.byteLength(previousLive.sourceCode, "utf8") : null,
16598
+ after: Buffer.byteLength(graph.root.sourceCode, "utf8")
16599
+ },
16600
+ triggerMetadata: published.triggerMetadata ?? null,
16601
+ triggerBindings: published.triggerBindings ?? []
16602
+ };
16603
+ if (options.jsonOutput) {
16604
+ process.stdout.write(`${JSON.stringify(result2)}
16605
+ `);
16606
+ } else {
16607
+ console.log(
16608
+ `\u2713 Published ${rootPlayName} as v${result2.liveVersion ?? "?"}`
16609
+ );
16610
+ console.log(` source: ${result2.sourceHash.slice(0, 12)}`);
16611
+ console.log(` artifact: ${result2.artifactHash.slice(0, 12)}`);
16612
+ if (result2.previousLiveVersion !== null) {
16613
+ console.log(
16614
+ ` previous live: v${result2.previousLiveVersion} (${result2.previousArtifactHash?.slice(0, 12) ?? "unknown artifact"})`
16615
+ );
16616
+ }
16617
+ if (result2.sourceBytes.before !== null) {
16618
+ console.log(
16619
+ ` source bytes: ${result2.sourceBytes.before.toLocaleString()} \u2192 ${result2.sourceBytes.after.toLocaleString()}`
16620
+ );
16621
+ }
16622
+ console.log(
16623
+ ` revisions: deepline plays versions --name ${rootPlayName}`
16624
+ );
16625
+ }
16626
+ return 0;
16627
+ }
16628
+ if (options.expectedArtifactHash) {
16629
+ console.error("--expected-artifact only applies to a local play file.");
16630
+ return 2;
16631
+ }
16632
+ if (options.dryRun) {
16633
+ const result2 = {
16634
+ ok: true,
16635
+ dryRun: true,
16636
+ name: playName,
16637
+ plannedMutation: "promote a saved revision live",
16638
+ revisionId: revisionId ?? null,
16639
+ latest: useLatest
16640
+ };
16641
+ process.stdout.write(`${JSON.stringify(result2)}
16642
+ `);
16469
16643
  return 0;
16470
16644
  }
16471
16645
  const resolvedName = parseReferencedPlayTarget2(playName).playName;
@@ -16829,7 +17003,9 @@ Notes:
16829
17003
  Mutates cloud state. For a saved play, --latest or --revision-id promotes an
16830
17004
  existing saved revision live.
16831
17005
  Local .play.ts publishing bundles the file, validates it, saves a revision,
16832
- and promotes that revision live.
17006
+ and promotes that revision live. Use --expected-artifact with the hash from
17007
+ plays check to refuse publishing bytes that changed after validation.
17008
+ --dry-run bundles only and prints the exact artifact that would be published.
16833
17009
  Running a local file with plays run does not publish it.
16834
17010
 
16835
17011
  Examples:
@@ -16837,27 +17013,40 @@ Examples:
16837
17013
  deepline plays set-live my-play --revision-id <revision-id> --json
16838
17014
  deepline plays set-live my.play.ts --json
16839
17015
  deepline plays publish my.play.ts --json
17016
+ deepline plays check my.play.ts
17017
+ deepline plays publish my.play.ts --expected-artifact <artifact-hash>
17018
+ deepline plays publish my.play.ts --dry-run --json
16840
17019
  `
16841
17020
  );
16842
17021
  addPublishHelp(
16843
17022
  play.command("publish <target>").description(
16844
17023
  "Promote a saved play revision or publish a local play file."
16845
17024
  )
16846
- ).option("--latest", "Promote the newest saved revision").option("--revision-id <id>", "Revision to promote").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (target, options) => {
17025
+ ).option("--latest", "Promote the newest saved revision").option("--revision-id <id>", "Revision to promote").option(
17026
+ "--expected-artifact <hash>",
17027
+ "Require the local file to produce this checked artifact hash"
17028
+ ).option("--dry-run", "Bundle and show the planned publish without mutation").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (target, options) => {
16847
17029
  process.exitCode = await handlePlayPublish([
16848
17030
  target,
16849
17031
  ...options.latest ? ["--latest"] : [],
16850
17032
  ...options.revisionId ? ["--revision-id", options.revisionId] : [],
17033
+ ...options.expectedArtifact ? ["--expected-artifact", options.expectedArtifact] : [],
17034
+ ...options.dryRun ? ["--dry-run"] : [],
16851
17035
  ...options.json ? ["--json"] : []
16852
17036
  ]);
16853
17037
  });
16854
17038
  addPublishHelp(
16855
17039
  play.command("set-live <target>").description("Promote a saved revision or publish a local play file.")
16856
- ).option("--latest", "Promote the newest saved revision").option("--revision-id <id>", "Revision to promote").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (target, options) => {
17040
+ ).option("--latest", "Promote the newest saved revision").option("--revision-id <id>", "Revision to promote").option(
17041
+ "--expected-artifact <hash>",
17042
+ "Require the local file to produce this checked artifact hash"
17043
+ ).option("--dry-run", "Bundle and show the planned publish without mutation").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (target, options) => {
16857
17044
  process.exitCode = await handlePlayPublish([
16858
17045
  target,
16859
17046
  ...options.latest ? ["--latest"] : [],
16860
17047
  ...options.revisionId ? ["--revision-id", options.revisionId] : [],
17048
+ ...options.expectedArtifact ? ["--expected-artifact", options.expectedArtifact] : [],
17049
+ ...options.dryRun ? ["--dry-run"] : [],
16861
17050
  ...options.json ? ["--json"] : []
16862
17051
  ]);
16863
17052
  });
@@ -28997,7 +29186,7 @@ async function maybeAutoUpdateAndRelaunch(response) {
28997
29186
  if (plan.kind === "source") {
28998
29187
  return false;
28999
29188
  }
29000
- const label = autoUpdate.reason === "rollback_forced" ? "has a server rollback pending and needs the latest rollback-aware CLI" : autoUpdate.required ? "requires an update" : "is more than the supported auto-update lag behind";
29189
+ const label = autoUpdate.reason === "rollback_forced" ? "has a server rollback pending and needs the latest rollback-aware CLI" : autoUpdate.reason === "deprecated" ? "is deprecated and will update automatically" : autoUpdate.required ? "requires an update" : "is more than the supported auto-update lag behind";
29001
29190
  process.stderr.write(
29002
29191
  `Deepline SDK/CLI ${label}; running ${plan.manualCommand}
29003
29192
  `
package/dist/index.d.mts CHANGED
@@ -945,6 +945,27 @@ interface PlayCheckResult {
945
945
  summary?: string;
946
946
  artifactHash?: string | null;
947
947
  graphHash?: string | null;
948
+ /** SHA-256 of the exact source bytes checked by Deepline. */
949
+ sourceHash?: string | null;
950
+ /** Enforceable byte budgets measured during cloud preflight. */
951
+ limits?: {
952
+ revisionStorage: {
953
+ usedBytes: number;
954
+ limitBytes: number;
955
+ withinLimit: boolean;
956
+ breakdown: {
957
+ sourceCodeBytes: number;
958
+ staticPipelineBytes: number;
959
+ bindingsBytes: number;
960
+ descriptionBytes: number;
961
+ };
962
+ };
963
+ bundle: {
964
+ usedBytes: number;
965
+ limitBytes: number;
966
+ withinLimit: boolean;
967
+ };
968
+ };
948
969
  }
949
970
  /** Severity of a {@link PlayCheckIssue}. `error` fails the check; `warning` does not. */
950
971
  type PlayCheckIssueSeverity = 'error' | 'warning';
package/dist/index.d.ts CHANGED
@@ -945,6 +945,27 @@ interface PlayCheckResult {
945
945
  summary?: string;
946
946
  artifactHash?: string | null;
947
947
  graphHash?: string | null;
948
+ /** SHA-256 of the exact source bytes checked by Deepline. */
949
+ sourceHash?: string | null;
950
+ /** Enforceable byte budgets measured during cloud preflight. */
951
+ limits?: {
952
+ revisionStorage: {
953
+ usedBytes: number;
954
+ limitBytes: number;
955
+ withinLimit: boolean;
956
+ breakdown: {
957
+ sourceCodeBytes: number;
958
+ staticPipelineBytes: number;
959
+ bindingsBytes: number;
960
+ descriptionBytes: number;
961
+ };
962
+ };
963
+ bundle: {
964
+ usedBytes: number;
965
+ limitBytes: number;
966
+ withinLimit: boolean;
967
+ };
968
+ };
948
969
  }
949
970
  /** Severity of a {@link PlayCheckIssue}. `error` fails the check; `warning` does not. */
950
971
  type PlayCheckIssueSeverity = 'error' | 'warning';
package/dist/index.js CHANGED
@@ -422,12 +422,14 @@ var SDK_RELEASE = {
422
422
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
423
423
  // fields shipped in 0.1.153.
424
424
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
425
- version: "0.1.219",
425
+ // 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
426
+ // automatically without blocking their current command.
427
+ version: "0.1.221",
426
428
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
427
429
  supportPolicy: {
428
- latest: "0.1.219",
430
+ latest: "0.1.221",
429
431
  minimumSupported: "0.1.53",
430
- deprecatedBelow: "0.1.53",
432
+ deprecatedBelow: "0.1.219",
431
433
  commandMinimumSupported: [
432
434
  {
433
435
  command: "enrich",
@@ -1163,7 +1165,7 @@ var SECRET_REDACTION_PLACEHOLDER = "[REDACTED_SECRET]";
1163
1165
  var BEARER_TOKEN_RE = /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}\b/g;
1164
1166
  var JWT_RE = /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g;
1165
1167
  var PRIVATE_KEY_RE = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g;
1166
- var COMMON_SECRET_RE = /\b(?:sk|pk|rk|pat|ghp|github_pat|xox[baprs])[A-Za-z0-9_./+=:-]{12,}\b/gi;
1168
+ var COMMON_SECRET_RE = /\b(?:(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9_./+=:-]{12,}|(?:pat|ghp|github_pat)_[A-Za-z0-9_./+=:-]{12,}|xox[baprs]-[A-Za-z0-9_./+=:-]{12,})\b/gi;
1167
1169
  var GENERIC_PREFIXED_SECRET_RE = /\b(?:key|token|secret|api[_-]?key)_[A-Za-z0-9_./+=:-]{12,}\b/gi;
1168
1170
  var URL_SECRET_VALUE_RE = /\b(?:sk|pk|rk|pat|ghp|github_pat|xox)[A-Za-z0-9_./+=:-]{12,}\b/i;
1169
1171
  var GENERIC_SECRET_ASSIGNMENT_RE = /\b(?:api[_-]?key|token|secret|password)\b\s*[:=]\s*["']?[^"',\s]{12,}["']?/gi;
package/dist/index.mjs CHANGED
@@ -352,12 +352,14 @@ var SDK_RELEASE = {
352
352
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
353
353
  // fields shipped in 0.1.153.
354
354
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
355
- version: "0.1.219",
355
+ // 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
356
+ // automatically without blocking their current command.
357
+ version: "0.1.221",
356
358
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
357
359
  supportPolicy: {
358
- latest: "0.1.219",
360
+ latest: "0.1.221",
359
361
  minimumSupported: "0.1.53",
360
- deprecatedBelow: "0.1.53",
362
+ deprecatedBelow: "0.1.219",
361
363
  commandMinimumSupported: [
362
364
  {
363
365
  command: "enrich",
@@ -1093,7 +1095,7 @@ var SECRET_REDACTION_PLACEHOLDER = "[REDACTED_SECRET]";
1093
1095
  var BEARER_TOKEN_RE = /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}\b/g;
1094
1096
  var JWT_RE = /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g;
1095
1097
  var PRIVATE_KEY_RE = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g;
1096
- var COMMON_SECRET_RE = /\b(?:sk|pk|rk|pat|ghp|github_pat|xox[baprs])[A-Za-z0-9_./+=:-]{12,}\b/gi;
1098
+ var COMMON_SECRET_RE = /\b(?:(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9_./+=:-]{12,}|(?:pat|ghp|github_pat)_[A-Za-z0-9_./+=:-]{12,}|xox[baprs]-[A-Za-z0-9_./+=:-]{12,})\b/gi;
1097
1099
  var GENERIC_PREFIXED_SECRET_RE = /\b(?:key|token|secret|api[_-]?key)_[A-Za-z0-9_./+=:-]{12,}\b/gi;
1098
1100
  var URL_SECRET_VALUE_RE = /\b(?:sk|pk|rk|pat|ghp|github_pat|xox)[A-Za-z0-9_./+=:-]{12,}\b/i;
1099
1101
  var GENERIC_SECRET_ASSIGNMENT_RE = /\b(?:api[_-]?key|token|secret|password)\b\s*[:=]\s*["']?[^"',\s]{12,}["']?/gi;
@@ -353,6 +353,17 @@ body {
353
353
  border: 1px solid var(--red);
354
354
  background: rgba(248,81,73,0.06);
355
355
  }
356
+ .tool-detail-toggle {
357
+ margin-top: 8px;
358
+ background: var(--bg-tertiary);
359
+ border: 1px solid var(--border);
360
+ border-radius: 4px;
361
+ color: var(--blue);
362
+ cursor: pointer;
363
+ font-size: 12px;
364
+ padding: 4px 8px;
365
+ }
366
+ .tool-detail-toggle:hover { border-color: var(--blue); }
356
367
 
357
368
  /* Result card */
358
369
  .result-card {
@@ -1334,7 +1334,7 @@ function renderTimeline(timeline) {
1334
1334
  <div class="tool-detail" id="detail-${i}">
1335
1335
  <div class="tool-detail-section">
1336
1336
  <div class="label">Input</div>
1337
- <pre>${esc(formatInput(e.tool, e.input))}</pre>
1337
+ ${formatToolInputHtml(e.tool, e.input, i)}
1338
1338
  </div>
1339
1339
  ${e.result_content != null ? `<div class="tool-detail-section">
1340
1340
  <div class="label">Result</div>
@@ -1347,8 +1347,17 @@ function renderTimeline(timeline) {
1347
1347
  return html;
1348
1348
  }
1349
1349
 
1350
+ function isShellCommandTool(tool) {
1351
+ const normalized = String(tool || '').toLowerCase().replace(/[^a-z]/g, '');
1352
+ return ['bash', 'exec', 'execcommand', 'runcommand', 'shell'].includes(normalized);
1353
+ }
1354
+
1355
+ function shellCommand(input) {
1356
+ return input.command || input.cmd || input.CommandLine || null;
1357
+ }
1358
+
1350
1359
  function formatInput(tool, input) {
1351
- if (tool === 'Bash') return input.command || JSON.stringify(input, null, 2);
1360
+ if (isShellCommandTool(tool)) return shellCommand(input) || JSON.stringify(input, null, 2);
1352
1361
  if (tool === 'Write') {
1353
1362
  let s = 'file: ' + (input.file_path || input.path || '') + '\n\n';
1354
1363
  if (input.content != null) s += '--- content ---\n' + String(input.content);
@@ -1364,6 +1373,39 @@ function formatInput(tool, input) {
1364
1373
  return JSON.stringify(input, null, 2);
1365
1374
  }
1366
1375
 
1376
+ function isLongShellCommand(command) {
1377
+ return command.length > 600 || command.split('\n').length > 10;
1378
+ }
1379
+
1380
+ function shellCommandPreview(command) {
1381
+ const lines = command.split('\n');
1382
+ let preview = lines.slice(0, 10).join('\n');
1383
+ if (preview.length > 600) preview = preview.slice(0, 600);
1384
+ return preview + '\n...[full command hidden]';
1385
+ }
1386
+
1387
+ function formatToolInputHtml(tool, input, entryIndex) {
1388
+ const formatted = formatInput(tool, input);
1389
+ if (!isShellCommandTool(tool) || !isLongShellCommand(formatted)) {
1390
+ return `<pre>${esc(formatted)}</pre>`;
1391
+ }
1392
+ const previewId = `command-preview-${entryIndex}`;
1393
+ const fullId = `command-full-${entryIndex}`;
1394
+ return `<pre id="${previewId}">${esc(shellCommandPreview(formatted))}</pre>
1395
+ <button class="tool-detail-toggle" onclick="toggleCommandInput('${previewId}', '${fullId}', this)">Show full command</button>
1396
+ <pre id="${fullId}" hidden>${esc(formatted)}</pre>`;
1397
+ }
1398
+
1399
+ function toggleCommandInput(previewId, fullId, toggle) {
1400
+ const preview = document.getElementById(previewId);
1401
+ const full = document.getElementById(fullId);
1402
+ if (!preview || !full) return;
1403
+ const showFull = full.hidden;
1404
+ full.hidden = !showFull;
1405
+ preview.hidden = showFull;
1406
+ toggle.textContent = showFull ? 'Show less' : 'Show full command';
1407
+ }
1408
+
1367
1409
  function toggleReasoning(id, toggle) {
1368
1410
  const el = document.getElementById(id);
1369
1411
  const collapsed = el.classList.toggle('collapsed');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.219",
3
+ "version": "0.1.221",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {