deepline 0.3.313 → 0.3.315

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.
Files changed (109) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +12 -0
  2. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  3. package/dist/bundling-sources/sdk/src/types.ts +10 -0
  4. package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +50 -0
  5. package/dist/bundling-sources/shared_libs/play-runtime/run-event-query.ts +15 -2
  6. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-launch-transport.ts +210 -2
  7. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +226 -14
  8. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +4 -18
  9. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +5 -0
  10. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +94 -14
  11. package/dist/bundling-sources/shared_libs/plays/docflow-binding-owner.ts +61 -11
  12. package/dist/cli/{auth-LNA6WCQA.mjs → auth-OVWOO5NP.mjs} +4 -4
  13. package/dist/cli/auth-P2TTZU36.js +31 -0
  14. package/dist/cli/{billing-GJJUUCOZ.js → billing-SRZL7UOH.js} +70 -70
  15. package/dist/cli/{billing-D37QNIE3.mjs → billing-W3OXKWNE.mjs} +4 -4
  16. package/dist/cli/{chunk-HY2SSFRI.js → chunk-6BJACIVK.js} +1 -1
  17. package/dist/cli/{chunk-BS4GAUMH.mjs → chunk-AO4IWPP7.mjs} +33 -8
  18. package/dist/cli/{chunk-DOI3UBGV.mjs → chunk-AZWQJAQF.mjs} +1 -1
  19. package/dist/cli/{chunk-XFEPRIW7.mjs → chunk-B473QUQU.mjs} +4 -4
  20. package/dist/cli/{chunk-P4N7233B.js → chunk-C5WSTXUB.js} +247 -222
  21. package/dist/cli/{chunk-4UXNEC5C.js → chunk-FGAK6FET.js} +26 -26
  22. package/dist/cli/{chunk-3ZC4PHJ6.mjs → chunk-FTJQO757.mjs} +1 -1
  23. package/dist/cli/{chunk-UQBMMN4U.mjs → chunk-GK67FYMS.mjs} +5 -5
  24. package/dist/cli/{chunk-CSH4PKPJ.mjs → chunk-HNKHMHJ7.mjs} +1 -1
  25. package/dist/cli/{chunk-YZ5PP6TX.mjs → chunk-IGE7EJJY.mjs} +23 -3
  26. package/dist/cli/{chunk-HM2TSTCT.mjs → chunk-JF4O5OMH.mjs} +1 -1
  27. package/dist/cli/{chunk-N2DXWS6W.mjs → chunk-L5TSFCJ6.mjs} +2 -2
  28. package/dist/cli/{chunk-6G7ZYA6E.js → chunk-LOWURQMF.js} +50 -50
  29. package/dist/cli/{chunk-FJUIXWZF.js → chunk-OJP4FCCX.js} +48 -48
  30. package/dist/cli/{chunk-BLMO3JMZ.mjs → chunk-OUIIYFG3.mjs} +2 -2
  31. package/dist/cli/{chunk-MHETTUWR.js → chunk-OYJCNCUW.js} +2 -2
  32. package/dist/cli/{chunk-JG2ZTDZV.js → chunk-PPONZ6IE.js} +15 -15
  33. package/dist/cli/{chunk-QCXDLOGA.mjs → chunk-R3IFZDJL.mjs} +2 -2
  34. package/dist/cli/{chunk-KNGTN7NE.js → chunk-SNFSO2AN.js} +6 -6
  35. package/dist/cli/{chunk-RDUS2EDE.mjs → chunk-TQ3F77AC.mjs} +3 -3
  36. package/dist/cli/{chunk-I4FHFGMA.js → chunk-TQK662WG.js} +60 -60
  37. package/dist/cli/{chunk-OTA5C7DD.mjs → chunk-TZ5KUDPP.mjs} +1 -1
  38. package/dist/cli/{chunk-FSZTWQFK.js → chunk-URVZGYGG.js} +4 -4
  39. package/dist/cli/{chunk-VSAYSOVC.js → chunk-VYK23G2C.js} +222 -202
  40. package/dist/cli/{chunk-GQXEHX5X.js → chunk-Y42UERUY.js} +6 -6
  41. package/dist/cli/{chunk-QQMKX7JW.js → chunk-YL42TF53.js} +6 -6
  42. package/dist/cli/client-CXFPFCIG.js +20 -0
  43. package/dist/cli/{client-6RVRZ4OY.mjs → client-WQ3J5BSH.mjs} +3 -3
  44. package/dist/cli/{csv-SBOHBKEG.mjs → csv-36ARCBNQ.mjs} +3 -3
  45. package/dist/cli/{csv-2UHRG67O.js → csv-CENMEWDH.js} +12 -12
  46. package/dist/cli/{db-VRE7CLIQ.js → db-6DQZG6NB.js} +20 -20
  47. package/dist/cli/{db-TUFTPYEA.mjs → db-JU7XLKQM.mjs} +4 -4
  48. package/dist/cli/{describe-WCBZTSOF.mjs → describe-34ALEEFO.mjs} +8 -8
  49. package/dist/cli/{describe-GS5A2XH4.js → describe-VUD4AFBY.js} +15 -15
  50. package/dist/cli/{executions-ENMOOKWH.js → executions-H2UXS6FP.js} +6 -6
  51. package/dist/cli/{executions-UTN3O372.mjs → executions-H66BMN6N.mjs} +4 -4
  52. package/dist/cli/{feedback-PXBA3AG7.mjs → feedback-Q7LXXLSH.mjs} +3 -3
  53. package/dist/cli/{feedback-BLN6SIRG.js → feedback-YSYFQSAB.js} +6 -6
  54. package/dist/cli/git-SYFCYIFP.js +23 -0
  55. package/dist/cli/{git-YDMIRW3Q.mjs → git-XV43NSWN.mjs} +4 -4
  56. package/dist/cli/index.js +74 -74
  57. package/dist/cli/index.mjs +31 -31
  58. package/dist/cli/{legacy-noops-CWTUWTDB.js → legacy-noops-RGDW6SCA.js} +7 -7
  59. package/dist/cli/{legacy-noops-OVG2SICQ.mjs → legacy-noops-Z2DTZQN3.mjs} +4 -4
  60. package/dist/cli/{monitors-T63OPBTY.js → monitors-I75KZ2BP.js} +85 -85
  61. package/dist/cli/{monitors-ATIDXJA4.mjs → monitors-OFQ56O4S.mjs} +4 -4
  62. package/dist/cli/{org-7RVAPMRN.js → org-75AH6RIB.js} +60 -60
  63. package/dist/cli/{org-NXKNIM3H.mjs → org-ZYMCU6XO.mjs} +4 -4
  64. package/dist/cli/{play-B62OVHD4.mjs → play-BKXM76DV.mjs} +7 -7
  65. package/dist/cli/play-OKXUWPMO.js +152 -0
  66. package/dist/cli/{providers-U7B7JEYY.mjs → providers-GWG2WFIY.mjs} +4 -4
  67. package/dist/cli/{providers-EITVEGXD.js → providers-Z77MM7ID.js} +7 -7
  68. package/dist/cli/{quickstart-VSWTYNJM.mjs → quickstart-BGVIV374.mjs} +3 -3
  69. package/dist/cli/{quickstart-G64F2XRU.js → quickstart-I4RECIQC.js} +5 -5
  70. package/dist/cli/search-3KISUDDT.js +26 -0
  71. package/dist/cli/{search-SKYTFKKF.mjs → search-TIKBP3RE.mjs} +5 -5
  72. package/dist/cli/{secrets-JSFHSCGM.js → secrets-SNGRI4KS.js} +13 -13
  73. package/dist/cli/{secrets-4DNS7KWS.mjs → secrets-YCHKLWKC.mjs} +4 -4
  74. package/dist/cli/{sessions-HB7RQ4XN.mjs → sessions-IRDG6ZRX.mjs} +4 -4
  75. package/dist/cli/{sessions-MLTYPPCG.js → sessions-WXLADGDX.js} +5 -5
  76. package/dist/cli/{settings-3PCKNBPU.js → settings-72J4BVF6.js} +74 -74
  77. package/dist/cli/{settings-BT5WCU65.mjs → settings-KZALJDXW.mjs} +7 -7
  78. package/dist/cli/{setup-EIWDTMCK.mjs → setup-4KCA7ESV.mjs} +6 -6
  79. package/dist/cli/{setup-EYZWTDJI.js → setup-FFB3KLFD.js} +41 -41
  80. package/dist/cli/{skills-AQ3ORN2C.mjs → skills-MQZPYUQ3.mjs} +4 -4
  81. package/dist/cli/skills-XGOK4GRW.js +28 -0
  82. package/dist/cli/tools-LEAJ7EPW.js +38 -0
  83. package/dist/cli/{tools-OFTB2BGP.mjs → tools-M3WKRZ5T.mjs} +6 -6
  84. package/dist/cli/{update-ERQTFSAR.mjs → update-QJMNYGNF.mjs} +6 -6
  85. package/dist/cli/update-ZN6LDUHS.js +28 -0
  86. package/dist/cli/{workflow-6D6NHUH4.js → workflow-35UUOZ5E.js} +15 -15
  87. package/dist/cli/{workflow-SRLWGGBM.mjs → workflow-6T2EUFCN.mjs} +5 -5
  88. package/dist/{client-CtVdLZux.d.mts → client-CvpZsBqt.d.mts} +12 -0
  89. package/dist/{client-OBwghxPC.d.ts → client-Dh_4Ph1R.d.ts} +12 -0
  90. package/dist/index.d.mts +2 -2
  91. package/dist/index.d.ts +2 -2
  92. package/dist/index.js +6 -6
  93. package/dist/index.mjs +3 -3
  94. package/dist/install-integrity.json +76 -76
  95. package/dist/plays/run-request.d.mts +1 -1
  96. package/dist/plays/run-request.d.ts +1 -1
  97. package/dist/release.d.mts +1 -1
  98. package/dist/release.d.ts +1 -1
  99. package/dist/release.js +2 -2
  100. package/dist/release.mjs +1 -1
  101. package/package.json +1 -1
  102. package/dist/cli/auth-56NYRTQK.js +0 -31
  103. package/dist/cli/client-XUTMYJDL.js +0 -20
  104. package/dist/cli/git-26D3VD2I.js +0 -23
  105. package/dist/cli/play-WM3NZVE7.js +0 -152
  106. package/dist/cli/search-TXQJJQWY.js +0 -26
  107. package/dist/cli/skills-73T4BSDE.js +0 -28
  108. package/dist/cli/tools-ZGSOHWBY.js +0 -38
  109. package/dist/cli/update-L4V5TF7I.js +0 -28
@@ -654,6 +654,8 @@ function searchParams(options: ToolSearchOptions): URLSearchParams {
654
654
  export type RunsListOptions = {
655
655
  play?: string;
656
656
  status?: string;
657
+ /** Mermaid node id; returns that node's latest settled input/output per run. Requires play. */
658
+ node?: string;
657
659
  limit?: number;
658
660
  /** Zero-based page offset. Requires `play` because status-only inventory is not paginated. */
659
661
  offset?: number;
@@ -4599,6 +4601,16 @@ export class DeeplineClient {
4599
4601
  if (status) {
4600
4602
  params.set('status', status);
4601
4603
  }
4604
+ const node = options.node?.trim();
4605
+ if (node) {
4606
+ if (!playName)
4607
+ throw new Error('runs.list options.node requires options.play.');
4608
+ if (node.length > 300)
4609
+ throw new Error(
4610
+ 'runs.list options.node must be at most 300 characters.',
4611
+ );
4612
+ params.set('node', node);
4613
+ }
4602
4614
  if (typeof options.limit === 'number' && Number.isFinite(options.limit)) {
4603
4615
  params.set('limit', String(Math.max(1, Math.floor(options.limit))));
4604
4616
  }
@@ -200,7 +200,7 @@ export const SDK_RELEASE = {
200
200
  // getters keep their established compatibility behavior.
201
201
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
202
202
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
203
- version: '0.3.313',
203
+ version: '0.3.315',
204
204
  updateSummary:
205
205
  'Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.',
206
206
  packageCapabilities: {
@@ -1314,6 +1314,16 @@ export interface PlayRunListItem {
1314
1314
  billingTotalCredits?: number;
1315
1315
  /** Configured per-run Deepline credit cap, when available. */
1316
1316
  billingMaxCreditsPerRun?: number | null;
1317
+ /** Latest settled Mermaid-node invocation when `runs.list` is filtered by `node`. */
1318
+ nodeObservation?: {
1319
+ attempt: number;
1320
+ status: 'completed' | 'failed';
1321
+ inputs: Record<string, unknown> | null;
1322
+ outputs: Record<string, unknown> | null;
1323
+ error: string | null;
1324
+ inputsTruncated: boolean;
1325
+ outputsTruncated: boolean;
1326
+ } | null;
1317
1327
  /** Metadata attached to the workflow. */
1318
1328
  memo: {
1319
1329
  /** Organization that owns this run. */
@@ -95,6 +95,56 @@ export function isPlayRunRecoveryError(
95
95
  );
96
96
  }
97
97
 
98
+ const RECOVERY_LOG_DETAIL_MAX_KEYS = 24;
99
+ const RECOVERY_LOG_DETAIL_MAX_KEY_CHARS = 64;
100
+ const RECOVERY_LOG_DETAIL_MAX_STRING_CHARS = 200;
101
+
102
+ export type PlayRunRecoveryLogDetail = Readonly<
103
+ Record<string, string | number | boolean | null>
104
+ >;
105
+
106
+ /**
107
+ * Provider facts an adapter attached to the cause of a recovery error, for the
108
+ * scheduler's defer log only. The decision's `reason` stays the single durable
109
+ * recovery fact: this detail is never persisted, never returned to a caller,
110
+ * and never read to choose a recovery action. It is bounded here, at the edge
111
+ * that logs it, whatever the adapter supplied. Total by construction, because
112
+ * a failure to describe a deferral must not alter the deferral.
113
+ */
114
+ export function playRunRecoveryLogDetail(
115
+ error: unknown,
116
+ ): PlayRunRecoveryLogDetail | null {
117
+ try {
118
+ if (!isPlayRunRecoveryError(error)) return null;
119
+ const cause: unknown = error.cause;
120
+ if (!cause || typeof cause !== 'object') return null;
121
+ const supplied = (cause as { recoveryLogDetail?: unknown })
122
+ .recoveryLogDetail;
123
+ if (!supplied || typeof supplied !== 'object' || Array.isArray(supplied)) {
124
+ return null;
125
+ }
126
+ const detail: Record<string, string | number | boolean | null> = {};
127
+ for (const [key, value] of Object.entries(supplied).slice(
128
+ 0,
129
+ RECOVERY_LOG_DETAIL_MAX_KEYS,
130
+ )) {
131
+ const name = key.slice(0, RECOVERY_LOG_DETAIL_MAX_KEY_CHARS);
132
+ if (typeof value === 'string') {
133
+ detail[name] = value.slice(0, RECOVERY_LOG_DETAIL_MAX_STRING_CHARS);
134
+ } else if (
135
+ value === null ||
136
+ typeof value === 'boolean' ||
137
+ (typeof value === 'number' && Number.isFinite(value))
138
+ ) {
139
+ detail[name] = value;
140
+ }
141
+ }
142
+ return Object.keys(detail).length > 0 ? detail : null;
143
+ } catch {
144
+ return null;
145
+ }
146
+ }
147
+
98
148
  function errorCode(error: unknown): string | number | null {
99
149
  if (!error || typeof error !== 'object') return null;
100
150
  const code = (error as { code?: unknown }).code;
@@ -535,8 +535,21 @@ function parseDsl(input: string): RunEventPredicate {
535
535
  /^(==|!=|>=|<=|>|<|\b(?:eq|ne|gte|gt|lte|lt|in|exists)\b)/.exec(
536
536
  input.slice(position),
537
537
  );
538
- if (!operation)
539
- fail('Run event DSL requires a supported comparison operator.');
538
+ if (!operation) {
539
+ const token =
540
+ /^(?:[=!<>~]+|[A-Za-z_]+)/.exec(input.slice(position))?.[0] ??
541
+ input[position] ??
542
+ '<end>';
543
+ if (token === '=') {
544
+ const corrected = `${input.slice(0, position)}==${input.slice(position + 1)}`;
545
+ fail(
546
+ `Run event DSL does not support "=" at character ${position + 1}. For equality, replace this "=" with "==": ${JSON.stringify(corrected)}.`,
547
+ );
548
+ }
549
+ fail(
550
+ `Run event DSL does not support ${JSON.stringify(token)} after ${JSON.stringify(match[0])} at character ${position + 1}. Use ==, !=, >, >=, <, <=, in or exists.`,
551
+ );
552
+ }
540
553
  position += operation[0].length;
541
554
  const symbols: Record<string, string> = {
542
555
  '==': 'eq',
@@ -22,14 +22,216 @@ import {
22
22
  DaytonaControlTimeoutError,
23
23
  } from './daytona-control-transport';
24
24
 
25
+ // Diagnostics below are log-only. They never select an error, a retry, or a
26
+ // recovery decision, and they stay out of every persisted run document.
27
+ const DAYTONA_LOGGED_SANDBOX_STATES: ReadonlySet<string> = new Set([
28
+ 'creating',
29
+ 'restoring',
30
+ 'destroyed',
31
+ 'destroying',
32
+ 'started',
33
+ 'stopped',
34
+ 'starting',
35
+ 'stopping',
36
+ 'error',
37
+ 'build_failed',
38
+ 'pending_build',
39
+ 'building_snapshot',
40
+ 'unknown',
41
+ 'pulling_snapshot',
42
+ 'archived',
43
+ 'archiving',
44
+ 'resizing',
45
+ 'snapshotting',
46
+ 'forking',
47
+ ]);
48
+ const DAYTONA_LOGGED_ERROR_NAMES: ReadonlySet<string> = new Set([
49
+ 'DaytonaError',
50
+ 'DaytonaNotFoundError',
51
+ 'DaytonaRateLimitError',
52
+ 'DaytonaAuthenticationError',
53
+ 'DaytonaAuthorizationError',
54
+ 'DaytonaConflictError',
55
+ 'DaytonaValidationError',
56
+ 'DaytonaTimeoutError',
57
+ 'DaytonaConnectionError',
58
+ 'DaytonaControlTimeoutError',
59
+ 'AbortError',
60
+ 'TimeoutError',
61
+ 'TypeError',
62
+ 'Error',
63
+ ]);
64
+ const DAYTONA_LOGGED_PROVIDER_TEXT_MAX_CHARS = 160;
65
+
66
+ /** A provider state from Daytona's published enum, or `unrecognized`. */
67
+ export function daytonaSandboxStateForLog(state: unknown): string | null {
68
+ if (state === undefined || state === null) return null;
69
+ return typeof state === 'string' && DAYTONA_LOGGED_SANDBOX_STATES.has(state)
70
+ ? state
71
+ : 'unrecognized';
72
+ }
73
+
74
+ /** A known error class name, or `other`; never a provider-authored string. */
75
+ export function daytonaErrorClassForLog(error: unknown): string {
76
+ const name = error instanceof Error ? error.name : null;
77
+ return name !== null && DAYTONA_LOGGED_ERROR_NAMES.has(name) ? name : 'other';
78
+ }
79
+
80
+ /** The HTTP status carried by a Daytona error, when it has a plausible one. */
81
+ export function daytonaErrorStatusForLog(error: unknown): number | null {
82
+ if (!error || typeof error !== 'object') return null;
83
+ const status = (error as { statusCode?: unknown }).statusCode;
84
+ return typeof status === 'number' &&
85
+ Number.isInteger(status) &&
86
+ status >= 100 &&
87
+ status <= 599
88
+ ? status
89
+ : null;
90
+ }
91
+
92
+ /**
93
+ * The machine-readable code on a Daytona or transport error (`ERR_CANCELED`,
94
+ * `ECONNRESET`, ...). Only the conventional constant shape is logged, so a
95
+ * provider-authored sentence in that field is dropped rather than copied.
96
+ */
97
+ export function daytonaErrorCodeForLog(error: unknown): string | null {
98
+ if (!error || typeof error !== 'object') return null;
99
+ const record = error as { errorCode?: unknown; code?: unknown };
100
+ const code = record.errorCode ?? record.code;
101
+ return typeof code === 'string' && /^[A-Z][A-Z0-9_]{1,39}$/.test(code)
102
+ ? code
103
+ : null;
104
+ }
105
+
106
+ /**
107
+ * Credential redaction for a Daytona control-plane error message. Shared by
108
+ * the backend's lookup-failure description and the provider text below.
109
+ */
110
+ export function redactDaytonaErrorDetail(value: string): string {
111
+ return value
112
+ .replace(
113
+ /authorization\s*[=:]\s*bearer\s+[^\s,;]+/gi,
114
+ 'Authorization=Bearer [redacted]',
115
+ )
116
+ .replace(
117
+ /(authorization\s*[=:]\s*)(?!bearer\s+\[redacted\])[^\s,;]+/gi,
118
+ '$1[redacted]',
119
+ )
120
+ .replace(/(bearer\s+)[^\s,;]+/gi, '$1[redacted]')
121
+ .replace(/((?:api[_-]?key|token)\s*[=:]\s*)[^\s,;]+/gi, '$1[redacted]')
122
+ .replace(/\s+/g, ' ')
123
+ .trim()
124
+ .slice(0, 1_000);
125
+ }
126
+
127
+ /**
128
+ * Provider free text (for example a sandbox `errorReason`) for logs. This
129
+ * field is written by the provider, so it is redacted more strictly than the
130
+ * shared function alone: URL userinfo, any authorization scheme, password,
131
+ * secret, token, signature, credential and key pairs in query, header or JSON
132
+ * form, email addresses, and opaque identifiers such as organization ids. It
133
+ * is then cut to one short fixed bound so an upstream response is never
134
+ * copied wholesale.
135
+ */
136
+ export function boundedDaytonaProviderTextForLog(
137
+ value: unknown,
138
+ ): string | null {
139
+ if (typeof value !== 'string') return null;
140
+ const strict = value
141
+ .replace(/(\b[a-z][a-z0-9+.-]*:\/\/)[^\s/@]+@/gi, '$1[redacted]@')
142
+ .replace(
143
+ /(authorization\s*[=:]\s*["']?)(?:[a-z]+\s+)?[^\s,;"']+/gi,
144
+ '$1[redacted]',
145
+ )
146
+ .replace(/\b(basic|bearer)\s+[A-Za-z0-9+/=._~-]{8,}/gi, '$1 [redacted]')
147
+ .replace(
148
+ /(["']?[\w.-]*(?:password|passwd|secret|token|signature|credential|api[_-]?key|access[_-]?key)[\w.-]*["']?\s*[=:]\s*)(?:"[^"]*"|'[^']*'|[^\s&,;"'}\]]+)/gi,
149
+ '$1[redacted]',
150
+ )
151
+ .replace(
152
+ /[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+/g,
153
+ '[redacted-email]',
154
+ )
155
+ .replace(
156
+ /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi,
157
+ '[redacted-id]',
158
+ )
159
+ .replace(
160
+ /\b(?=[A-Za-z0-9_-]*\d)(?=[A-Za-z0-9_-]*[A-Za-z])[A-Za-z0-9_-]{24,}\b/g,
161
+ '[redacted-id]',
162
+ );
163
+ const bounded = redactDaytonaErrorDetail(strict).slice(
164
+ 0,
165
+ DAYTONA_LOGGED_PROVIDER_TEXT_MAX_CHARS,
166
+ );
167
+ return bounded || null;
168
+ }
169
+
170
+ /** What this client knew when a sent create ended without a verdict. */
171
+ export type DaytonaLaunchOutcomeDiagnostic = {
172
+ /** Whether the create request returned a response before the deadline. */
173
+ createResponseReceived: boolean;
174
+ /**
175
+ * Provider sandbox id from the create response, when one was returned. It
176
+ * is not named `sandboxId`: lifecycle tooling reads that key on a stage
177
+ * event as an acquired sandbox, and this one never started.
178
+ */
179
+ createResponseSandboxId: string | null;
180
+ /** Last provider state this client saw while waiting for `started`. */
181
+ lastObservedState: string | null;
182
+ providerErrorReason: string | null;
183
+ causeClass: string;
184
+ /** The cause's machine code, such as `ERR_CANCELED` or `ECONNRESET`. */
185
+ causeErrno: string | null;
186
+ causeStatus: number | null;
187
+ };
188
+
189
+ /**
190
+ * Describe an unknown create outcome for logs. Total by construction: if the
191
+ * description cannot be built, the caller still throws the same error without
192
+ * one, because a different error here would release the reservation and send
193
+ * a second create.
194
+ */
195
+ function describeUnknownLaunchOutcome(input: {
196
+ data: SandboxData | undefined;
197
+ waiting: Sandbox | undefined;
198
+ error: unknown;
199
+ }): DaytonaLaunchOutcomeDiagnostic | null {
200
+ try {
201
+ const { data, waiting, error } = input;
202
+ return {
203
+ createResponseReceived: data !== undefined,
204
+ createResponseSandboxId: data?.id ? String(data.id).slice(0, 64) : null,
205
+ lastObservedState: daytonaSandboxStateForLog(
206
+ waiting?.state ?? data?.state,
207
+ ),
208
+ providerErrorReason: boundedDaytonaProviderTextForLog(
209
+ waiting?.errorReason ?? data?.errorReason,
210
+ ),
211
+ causeClass: daytonaErrorClassForLog(error),
212
+ causeErrno: daytonaErrorCodeForLog(error),
213
+ causeStatus: daytonaErrorStatusForLog(error),
214
+ };
215
+ } catch {
216
+ return null;
217
+ }
218
+ }
219
+
25
220
  /** A sent create may have committed even when no response reached the worker. */
26
221
  export class DaytonaLaunchOutcomeUnknownError extends Error {
27
- constructor(cause: unknown) {
222
+ /** Log-only; absent when the error was not raised by the launch client. */
223
+ readonly diagnostic: DaytonaLaunchOutcomeDiagnostic | null;
224
+
225
+ constructor(
226
+ cause: unknown,
227
+ diagnostic: DaytonaLaunchOutcomeDiagnostic | null = null,
228
+ ) {
28
229
  super(
29
230
  'Daytona named create outcome is unknown; reconcile the same operation before another launch.',
30
231
  { cause },
31
232
  );
32
233
  this.name = 'DaytonaLaunchOutcomeUnknownError';
234
+ this.diagnostic = diagnostic;
33
235
  }
34
236
  }
35
237
 
@@ -208,6 +410,8 @@ export function createDaytonaLaunchClient(
208
410
  const phase = deadline(timeoutMs, cancellation);
209
411
  let sent = false;
210
412
  let data: SandboxData | undefined;
413
+ // Read only to describe an unknown outcome in logs.
414
+ let waiting: Sandbox | undefined;
211
415
  try {
212
416
  phase.signal.throwIfAborted();
213
417
  const api = new SandboxApi(
@@ -244,6 +448,7 @@ export function createDaytonaLaunchClient(
244
448
  );
245
449
  data = response.data;
246
450
  const sandbox = makeSandbox(data, () => phase.signal);
451
+ waiting = sandbox;
247
452
  await sandbox.waitUntilStarted(options.timeout ?? 60);
248
453
  // Future phases own fresh deadlines; none inherits this create scope.
249
454
  return makeSandbox(
@@ -270,7 +475,10 @@ export function createDaytonaLaunchClient(
270
475
  error instanceof DaytonaError &&
271
476
  [400, 401, 403, 429].includes(error.statusCode ?? 0);
272
477
  if (sent && !confirmedRejection)
273
- throw new DaytonaLaunchOutcomeUnknownError(error);
478
+ throw new DaytonaLaunchOutcomeUnknownError(
479
+ error,
480
+ describeUnknownLaunchOutcome({ data, waiting, error }),
481
+ );
274
482
  throw error;
275
483
  } finally {
276
484
  phase.dispose();