deepline 0.3.476 → 0.3.478

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 (171) hide show
  1. package/dist/bundling-sources/sdk/src/client-core.ts +5 -4
  2. package/dist/bundling-sources/sdk/src/http-core.ts +84 -3
  3. package/dist/bundling-sources/sdk/src/http.ts +2 -0
  4. package/dist/bundling-sources/sdk/src/play.ts +8 -2
  5. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  6. package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +18 -0
  7. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +33 -0
  8. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +10 -2
  9. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +77 -1
  10. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +1 -0
  11. package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +3 -0
  12. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +11 -0
  13. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +13 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +77 -4
  15. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-session-execution.ts +83 -1
  16. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +7 -0
  17. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-compute-usage.ts +6 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +215 -22
  19. package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +42 -0
  20. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +13 -12
  21. package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +5 -2
  22. package/dist/bundling-sources/shared_libs/plays/sandbox-runtime-limits.ts +37 -12
  23. package/dist/bundling-sources/shared_libs/product-notifications/billing-sales.ts +1 -0
  24. package/dist/bundling-sources/shared_libs/product-notifications/catalog.ts +29 -0
  25. package/dist/bundling-sources/shared_libs/product-notifications/events.ts +15 -0
  26. package/dist/cli/{auth-A22724UX.mjs → auth-SA4DJCSO.mjs} +5 -5
  27. package/dist/cli/auth-U5SNVC5F.js +41 -0
  28. package/dist/cli/{billing-T5Q6OO6M.js → billing-462SAHWX.js} +107 -87
  29. package/dist/cli/{billing-IIBEZ6J7.mjs → billing-Z4D35MV5.mjs} +34 -14
  30. package/dist/cli/{chunk-NPNQCPFP.mjs → chunk-2FTUPGTN.mjs} +2 -2
  31. package/dist/cli/{chunk-2WEPQSA2.mjs → chunk-2JS5DNVX.mjs} +5 -5
  32. package/dist/cli/{chunk-L45IECBZ.js → chunk-2MSKG4ER.js} +7 -6
  33. package/dist/cli/{chunk-JU2KBLOR.mjs → chunk-3LTLQAU4.mjs} +4 -4
  34. package/dist/cli/{chunk-ZV4OWYLZ.mjs → chunk-3V7INABZ.mjs} +2 -2
  35. package/dist/cli/{chunk-VPAVNEDQ.mjs → chunk-4C3TMIF3.mjs} +4 -4
  36. package/dist/cli/{chunk-H3GOAZHV.mjs → chunk-4RJRGQ5Y.mjs} +2 -2
  37. package/dist/cli/{chunk-AS3VECCY.js → chunk-4RV7U3RP.js} +7 -7
  38. package/dist/cli/{chunk-UHTJWDYB.mjs → chunk-645RNF2Y.mjs} +1 -1
  39. package/dist/cli/{chunk-TEJSRJNH.js → chunk-6EEMW7YP.js} +8 -8
  40. package/dist/cli/{chunk-ZOXV6WFI.mjs → chunk-75DAGCEX.mjs} +1 -1
  41. package/dist/cli/{chunk-NTJDZURV.mjs → chunk-7HTWT3Z4.mjs} +2 -2
  42. package/dist/cli/chunk-7I4B6V2S.mjs +127 -0
  43. package/dist/cli/{chunk-MZSLFWOT.js → chunk-7UFFWPQ2.js} +13 -9
  44. package/dist/cli/{chunk-5FBCMLJW.js → chunk-A5LPDB6N.js} +6 -6
  45. package/dist/cli/{chunk-4YWYLHED.mjs → chunk-ABBP5ZCI.mjs} +3 -3
  46. package/dist/cli/{chunk-IC3BZJR7.js → chunk-AQYSZYNE.js} +8 -8
  47. package/dist/cli/{chunk-WTPRRKZA.js → chunk-AYW242XF.js} +37 -37
  48. package/dist/cli/{chunk-CFSCHLNQ.mjs → chunk-BLAZVZVD.mjs} +23 -5
  49. package/dist/cli/{chunk-KGLIDLJ2.js → chunk-D2LKEQKI.js} +5 -5
  50. package/dist/cli/{chunk-4WMIMSSI.mjs → chunk-F2SJYUBW.mjs} +5 -5
  51. package/dist/cli/{chunk-KCP6XNRR.mjs → chunk-F44O4PEP.mjs} +1 -1
  52. package/dist/cli/{chunk-EBWYNNTJ.js → chunk-HMUBLAT4.js} +5 -5
  53. package/dist/cli/{chunk-UYA7LJGC.mjs → chunk-IL5AF6AR.mjs} +4 -2
  54. package/dist/cli/{chunk-B7PEXXRW.js → chunk-IWYWZXCA.js} +53 -53
  55. package/dist/cli/{chunk-PGNLD6L6.js → chunk-J3ZYRN5L.js} +10 -10
  56. package/dist/cli/{chunk-VKBZLV2K.mjs → chunk-JH775FF6.mjs} +1 -1
  57. package/dist/cli/{chunk-5AEEINTU.js → chunk-KBBZXWXF.js} +63 -11
  58. package/dist/cli/{chunk-FVPT44IJ.mjs → chunk-M5USVQ33.mjs} +55 -3
  59. package/dist/cli/{chunk-2YHDNHRR.mjs → chunk-N573OK4W.mjs} +4 -4
  60. package/dist/cli/{chunk-WFVEMDQL.js → chunk-OKAZHDYN.js} +4 -2
  61. package/dist/cli/{chunk-J4FCTFQS.mjs → chunk-PHBOI24Y.mjs} +10 -10
  62. package/dist/cli/{chunk-D7EQ4PI5.mjs → chunk-PLJV6SX4.mjs} +1 -1
  63. package/dist/cli/{chunk-YQOWMQEW.mjs → chunk-PPHHPAQV.mjs} +1 -1
  64. package/dist/cli/{chunk-CBVCCXIY.js → chunk-PZHRNSMU.js} +21 -21
  65. package/dist/cli/{chunk-LJFIUT5R.mjs → chunk-QB3Q73LP.mjs} +2 -2
  66. package/dist/cli/{chunk-ULKGYVYP.js → chunk-QVZNUQAP.js} +2 -2
  67. package/dist/cli/{chunk-A6BETJ2A.js → chunk-S3BKOCVB.js} +1 -1
  68. package/dist/cli/{chunk-F5PYBZ2H.mjs → chunk-S7UEKKOX.mjs} +2 -1
  69. package/dist/cli/{chunk-W3BHZUPL.js → chunk-SCHE5QRN.js} +3 -3
  70. package/dist/cli/{chunk-YEJDFZJP.js → chunk-SXY4DOVA.js} +16 -16
  71. package/dist/cli/{chunk-LZCYDU6Q.mjs → chunk-TLF3KGWG.mjs} +2 -2
  72. package/dist/cli/{chunk-5PYSYW63.js → chunk-TQWI7LJX.js} +26 -26
  73. package/dist/cli/{chunk-7HAUVMRF.js → chunk-TW3TTKW5.js} +3 -3
  74. package/dist/cli/{chunk-FK7SBMSZ.js → chunk-UXQVSDFP.js} +3 -3
  75. package/dist/cli/{chunk-HSANVBVL.js → chunk-V7I65ME7.js} +2 -2
  76. package/dist/cli/{chunk-6YZYCKWD.js → chunk-VE25VUTE.js} +70 -52
  77. package/dist/cli/{chunk-VLYUFZUT.js → chunk-XFY3JOP2.js} +9 -9
  78. package/dist/cli/{chunk-MYYOCJZ2.js → chunk-XOORR6P3.js} +93 -93
  79. package/dist/cli/chunk-YGCKMI5P.js +127 -0
  80. package/dist/cli/{chunk-3A7TDUZV.mjs → chunk-ZBSUXXZS.mjs} +13 -9
  81. package/dist/cli/{chunk-NREO7TT4.mjs → chunk-ZSU3C4ST.mjs} +2 -2
  82. package/dist/cli/command-docs.js +2 -2
  83. package/dist/cli/command-docs.mjs +1 -1
  84. package/dist/cli/{csv-GPVCETS2.mjs → csv-3SNWBO5F.mjs} +4 -4
  85. package/dist/cli/{csv-FXOQ2PBE.js → csv-LAYTDYIF.js} +13 -13
  86. package/dist/cli/{db-F5UJTBGP.mjs → db-72JKGQSK.mjs} +6 -6
  87. package/dist/cli/{db-LXYV4GPZ.js → db-OOUOSURL.js} +27 -27
  88. package/dist/cli/describe-7HYV44MO.js +48 -0
  89. package/dist/cli/{describe-GYWCI7I6.mjs → describe-KDPNOEUJ.mjs} +17 -17
  90. package/dist/cli/describe-core.d.mts +3 -3
  91. package/dist/cli/describe-core.d.ts +3 -3
  92. package/dist/cli/{executions-6NL2YW76.mjs → executions-O6JS3UKX.mjs} +6 -6
  93. package/dist/cli/{executions-6SJKTY2L.js → executions-RJNMGJEE.js} +8 -8
  94. package/dist/cli/{feedback-E3HULSQD.js → feedback-FXB6NV3Y.js} +8 -8
  95. package/dist/cli/{feedback-7AMQCNTB.mjs → feedback-T3JH3CWC.mjs} +4 -4
  96. package/dist/cli/git-5LTKPXGN.js +25 -0
  97. package/dist/cli/{git-74IS2EFH.mjs → git-6KAQSH5J.mjs} +5 -5
  98. package/dist/cli/index.js +161 -142
  99. package/dist/cli/index.mjs +65 -46
  100. package/dist/cli/{legacy-noops-52FFRTRZ.mjs → legacy-noops-7HDGEGTK.mjs} +5 -5
  101. package/dist/cli/{legacy-noops-ALWUQPKS.js → legacy-noops-PAOGRKUK.js} +8 -8
  102. package/dist/cli/{monitors-Z4ADOGTY.js → monitors-KQFI7TVJ.js} +91 -91
  103. package/dist/cli/{monitors-VTJIWWW2.mjs → monitors-LE2AYK3Z.mjs} +7 -7
  104. package/dist/cli/{org-2BTEKDF5.js → org-J6XFHDJE.js} +72 -72
  105. package/dist/cli/{org-7GU6H7JL.mjs → org-Z4DMJ72I.mjs} +6 -6
  106. package/dist/cli/{pages-I6PFQGEU.mjs → pages-5LYS6Y4I.mjs} +6 -6
  107. package/dist/cli/{pages-MG36E7RY.js → pages-FIXBZQJG.js} +17 -17
  108. package/dist/cli/{play-VN52S3RV.js → play-D7WPXADH.js} +604 -680
  109. package/dist/cli/{play-7KFSLJX2.mjs → play-GILXXIN7.mjs} +45 -121
  110. package/dist/cli/{providers-W5XLII66.js → providers-KN7YACT6.js} +9 -9
  111. package/dist/cli/{providers-D46JDEL2.mjs → providers-OQWL4Q4W.mjs} +6 -6
  112. package/dist/cli/{quickstart-4GTXBZHY.mjs → quickstart-2LRIORFM.mjs} +4 -4
  113. package/dist/cli/{quickstart-TIZAC63S.js → quickstart-M2OBP3W3.js} +6 -6
  114. package/dist/cli/{run-FOVEMYYX.js → run-6YZZO7VM.js} +36 -36
  115. package/dist/cli/{run-LE3D4LVU.mjs → run-GYRBKQZT.mjs} +22 -22
  116. package/dist/cli/search-OVKWA4AV.js +32 -0
  117. package/dist/cli/{search-6YL6WJMQ.mjs → search-VUPK6BNU.mjs} +7 -7
  118. package/dist/cli/{secrets-CSTTTMZH.js → secrets-RE3UMZB7.js} +15 -15
  119. package/dist/cli/{secrets-D66U6RTT.mjs → secrets-ULRGU7NM.mjs} +6 -6
  120. package/dist/cli/{sessions-XIO3OUER.mjs → sessions-2ZZVJ3P2.mjs} +5 -5
  121. package/dist/cli/{sessions-45M2NRAA.js → sessions-T4UMHNDW.js} +6 -6
  122. package/dist/cli/{settings-N42CVKSK.js → settings-BRV6AYFM.js} +78 -78
  123. package/dist/cli/{settings-R33X2D5T.mjs → settings-PDY4TJU6.mjs} +11 -11
  124. package/dist/cli/{setup-CBBC4FQP.mjs → setup-CXL6C5Z5.mjs} +9 -9
  125. package/dist/cli/{setup-HREDVNF2.js → setup-JONYJYBK.js} +44 -44
  126. package/dist/cli/{skills-4X6WS2PT.mjs → skills-TOMVQD5O.mjs} +7 -7
  127. package/dist/cli/skills-UK5AK35I.js +33 -0
  128. package/dist/cli/{tools-YR46YFR2.mjs → tools-B2LJK4MM.mjs} +16 -16
  129. package/dist/cli/tools-HH6E5G7J.js +55 -0
  130. package/dist/cli/{update-5CYAZX5X.mjs → update-CD6T2REC.mjs} +9 -9
  131. package/dist/cli/update-EGXT3EIJ.js +33 -0
  132. package/dist/cli/{workflow-M7AA722W.js → workflow-PZML43E2.js} +17 -17
  133. package/dist/cli/{workflow-NKQ3CWPK.mjs → workflow-ZAOBP6I7.mjs} +7 -7
  134. package/dist/{client-AjmfA5nR.d.mts → client-B1s1yJdT.d.mts} +1 -1
  135. package/dist/{client-DMyY-QL8.d.ts → client-DqUseRzd.d.ts} +1 -1
  136. package/dist/{client-core-C705vFyV.d.ts → client-core-Bd7tP9Uz.d.ts} +45 -6
  137. package/dist/{client-core-CpBbvLEX.d.mts → client-core-BijQEQ70.d.mts} +45 -6
  138. package/dist/{compiler-manifest-hqIAukR0.d.mts → compiler-manifest-B1m5tRzz.d.mts} +23 -17
  139. package/dist/{compiler-manifest-hqIAukR0.d.ts → compiler-manifest-B1m5tRzz.d.ts} +23 -17
  140. package/dist/{errors-CRO2qQ6k.d.ts → errors-3g71OMXy.d.ts} +1 -1
  141. package/dist/{errors-DIHVetNY.d.mts → errors-QiC6am-q.d.mts} +1 -1
  142. package/dist/{index-DVZ_X4Gb.d.mts → index-Biz5toO4.d.mts} +1 -1
  143. package/dist/{index-B8DVoeYt.d.ts → index-CIBiwzhK.d.ts} +1 -1
  144. package/dist/index.d.mts +15 -9
  145. package/dist/index.d.ts +15 -9
  146. package/dist/index.js +9 -9
  147. package/dist/index.mjs +6 -6
  148. package/dist/install-integrity.json +112 -110
  149. package/dist/plays/bundle-play-file.d.mts +3 -3
  150. package/dist/plays/bundle-play-file.d.ts +3 -3
  151. package/dist/plays/bundle-play-file.mjs +2 -1
  152. package/dist/plays/play-description.d.mts +2 -2
  153. package/dist/plays/play-description.d.ts +2 -2
  154. package/dist/plays/run-request.d.mts +2 -2
  155. package/dist/plays/run-request.d.ts +2 -2
  156. package/dist/release.d.mts +1 -1
  157. package/dist/release.d.ts +1 -1
  158. package/dist/release.js +2 -2
  159. package/dist/release.mjs +1 -1
  160. package/dist/server.d.mts +4 -4
  161. package/dist/server.d.ts +4 -4
  162. package/dist/server.js +5 -5
  163. package/dist/server.mjs +3 -3
  164. package/package.json +1 -1
  165. package/dist/cli/auth-2ZY57Q7W.js +0 -41
  166. package/dist/cli/describe-IB7SUIUT.js +0 -48
  167. package/dist/cli/git-A2QIIMW5.js +0 -25
  168. package/dist/cli/search-HN6SVDQU.js +0 -32
  169. package/dist/cli/skills-QZ6HZX2T.js +0 -33
  170. package/dist/cli/tools-KFIH7UWB.js +0 -55
  171. package/dist/cli/update-VZLEJLWJ.js +0 -33
@@ -12,6 +12,7 @@ import type {
12
12
  PlaySettingsPatch,
13
13
  PlaySettingsPathsPage,
14
14
  } from '../../shared_libs/plays/settings-contract';
15
+ import type { PlaySandboxRuntimeDeclaration } from '../../shared_libs/plays/sandbox-runtime-limits';
15
16
  import type {
16
17
  PlayTriggerConfigUpdate,
17
18
  PlayTriggerConfigUpdateResult,
@@ -2419,6 +2420,8 @@ export type BillingRecentUsageEntry = {
2419
2420
  export type BillingUsageOptions = {
2420
2421
  /** Return the one exact call identified by `executeTool().job_id`. */
2421
2422
  requestId?: string;
2423
+ /** Return only the workspace identity and recent activity page. */
2424
+ recentOnly?: boolean;
2422
2425
  /** Bound the recent-call page. Ignored for an exact requestId lookup. */
2423
2426
  limit?: number;
2424
2427
  offset?: number;
@@ -2555,10 +2558,7 @@ export type BillingNamespace = {
2555
2558
 
2556
2559
  export type BillingQuoteRequest = {
2557
2560
  quantity: number;
2558
- sandbox_runtime: {
2559
- timeout?: string;
2560
- size?: 'standard' | 'large' | 'xlarge';
2561
- };
2561
+ sandbox_runtime: PlaySandboxRuntimeDeclaration;
2562
2562
  };
2563
2563
 
2564
2564
  export type BillingQuoteResult = {
@@ -6466,6 +6466,7 @@ export class DeeplineClient {
6466
6466
  }
6467
6467
  params.set('request_id', requestId);
6468
6468
  } else {
6469
+ if (options.recentOnly) params.set('recent_only', 'true');
6469
6470
  if (options.limit !== undefined) {
6470
6471
  params.set('recent_limit', String(options.limit));
6471
6472
  }
@@ -218,14 +218,95 @@ function mapNetworkError(
218
218
  code: 'NETWORK_ABORTED',
219
219
  };
220
220
  }
221
+ const causeCode = networkCauseCode(error);
221
222
  return {
222
- message: error?.message
223
- ? `Unable to connect to ${baseUrl}. ${error.message}`
224
- : `Unable to connect to ${baseUrl}. Is the computer able to access the url?`,
223
+ message: connectionFailureMessage(baseUrl, error),
225
224
  code: 'NETWORK_ERROR',
225
+ ...(causeCode ? { details: { causeCode } } : {}),
226
226
  };
227
227
  }
228
228
 
229
+ const BLOCKED_EGRESS_CAUSE_CODES = new Set([
230
+ 'EPERM',
231
+ 'EACCES',
232
+ 'ENOTFOUND',
233
+ 'EAI_AGAIN',
234
+ 'ENETUNREACH',
235
+ 'EHOSTUNREACH',
236
+ 'ETIMEDOUT',
237
+ 'UND_ERR_CONNECT_TIMEOUT',
238
+ ]);
239
+
240
+ /**
241
+ * The first system error code in a transport failure's cause chain. Node's
242
+ * fetch reports only "fetch failed"; the reason (ENOTFOUND, ECONNREFUSED,
243
+ * EPERM, ...) is on `cause`, or on `errors` when every address was tried.
244
+ */
245
+ export function networkCauseCode(error: unknown): string | undefined {
246
+ const seen = new Set<unknown>();
247
+ const queue: unknown[] = [error];
248
+ while (queue.length > 0) {
249
+ const current = queue.shift();
250
+ if (!current || typeof current !== 'object' || seen.has(current)) continue;
251
+ seen.add(current);
252
+ const record = current as Record<string, unknown>;
253
+ if (
254
+ typeof record.code === 'string' &&
255
+ /^[A-Z][A-Z0-9_]+$/.test(record.code)
256
+ )
257
+ return record.code;
258
+ queue.push(record.cause);
259
+ if (Array.isArray(record.errors)) queue.push(...record.errors);
260
+ }
261
+ return undefined;
262
+ }
263
+
264
+ function isLoopbackHost(hostname: string): boolean {
265
+ return (
266
+ hostname === 'localhost' ||
267
+ hostname === '[::1]' ||
268
+ hostname.startsWith('127.') ||
269
+ hostname.endsWith('.localhost')
270
+ );
271
+ }
272
+
273
+ /**
274
+ * One line naming the host, the transport error, its underlying cause code,
275
+ * and — when the cause is how blocked egress presents — what to check.
276
+ */
277
+ export function connectionFailureMessage(
278
+ baseUrl: string,
279
+ error: Error | null,
280
+ ): string {
281
+ if (!error?.message) {
282
+ return `Unable to connect to ${baseUrl}. Is the computer able to access the url?`;
283
+ }
284
+ const causeCode = networkCauseCode(error);
285
+ let hostname = '';
286
+ try {
287
+ hostname = new URL(baseUrl).hostname;
288
+ } catch {
289
+ // An unparseable base URL still reports the transport failure.
290
+ }
291
+ const blocked =
292
+ hostname !== '' &&
293
+ !isLoopbackHost(hostname) &&
294
+ causeCode !== undefined &&
295
+ (BLOCKED_EGRESS_CAUSE_CODES.has(causeCode) || causeCode === 'ECONNREFUSED');
296
+ // Without a code, the cause's own message is still more than "fetch failed".
297
+ const causeMessage =
298
+ error.cause instanceof Error ? error.cause.message.slice(0, 200) : '';
299
+ const cause = causeCode ?? causeMessage;
300
+ return [
301
+ `Unable to connect to ${baseUrl}. ${error.message}${cause && !error.message.includes(cause) ? ` (${cause})` : ''}.`,
302
+ ...(blocked
303
+ ? [
304
+ `A sandbox, firewall, or proxy may be blocking ${hostname}; allow outbound HTTPS to it and retry.`,
305
+ ]
306
+ : []),
307
+ ].join(' ');
308
+ }
309
+
229
310
  function describeRequestTarget(path: string): string {
230
311
  const toolExecuteMatch = path.match(
231
312
  /^\/api\/v2\/integrations\/([^/?#]+)\/execute(?:[/?#]|$)/,
@@ -3,6 +3,8 @@ import { HttpClient as HttpClientCore } from './http-core';
3
3
  import { readSdkSkillsLocalVersion } from './skills-version';
4
4
  import type { ResolvedConfig } from './types';
5
5
 
6
+ export { connectionFailureMessage, networkCauseCode } from './http-core';
7
+
6
8
  export class HttpClient extends HttpClientCore {
7
9
  constructor(config: ResolvedConfig) {
8
10
  super(config, readSdkSkillsLocalVersion);
@@ -185,8 +185,14 @@ export type PlayFetchResponse = PlayAuthoringFetchResponse;
185
185
  * The changed row is delivered as the listener event's `after` in the play function's input.
186
186
  *
187
187
  * The default Play runtime is 30 minutes with the `standard` 1 GiB / 1 CPU
188
- * profile. Select `large` (4 GiB / 2 CPU) or `xlarge` (8 GiB / 4 CPU)
189
- * through `runtime.size` when a step needs more memory.
188
+ * profile. Set `runtime.size` to `standard` or `1gb` (1 GiB / 1 CPU),
189
+ * `large` or `4gb` (4 GiB / 2 CPU), or `xlarge` or `8gb` (8 GiB / 4 CPU).
190
+ * Each alias selects the same resource profile and compute price.
191
+ *
192
+ * ```ts
193
+ * export const config = { runtime: { size: '4gb' } };
194
+ * ```
195
+ *
190
196
  * Compute pricing follows the workspace plan; use `deepline billing quote`
191
197
  * for the session ceiling.
192
198
  * Set `runtime.timeout` for longer bounded runs; duration values are whole
@@ -205,7 +205,7 @@ export const SDK_RELEASE = {
205
205
  // getters keep their established compatibility behavior.
206
206
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
207
207
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
208
- version: '0.3.476',
208
+ version: '0.3.478',
209
209
  updateSummary: {
210
210
  introducedIn: '0.3.90',
211
211
  summary:
@@ -255,6 +255,24 @@
255
255
  "failureAlert": "github_workflow_run",
256
256
  "domainReportRequired": true
257
257
  },
258
+ {
259
+ "id": "github.prebuilt-benchmarks",
260
+ "name": "Prebuilt benchmarks",
261
+ "scheduler": "github_actions",
262
+ "cadence": "0 14 * * 1",
263
+ "timezone": "UTC",
264
+ "source": ".github/workflows/prebuilt-benchmarks.yml",
265
+ "locator": "schedule:",
266
+ "owner": "evals",
267
+ "criticality": "reporting",
268
+ "purpose": "Run each prebuilt play on its fixed labeled sample in a disposable Production workspace with the published CLI and publish found, exact, recall, precision and settled credits.",
269
+ "successDefinition": "Every benchmark exports its rows, scores them, settles within its credit cap, and the summary uploads; the disposable workspace is deleted.",
270
+ "failureDefinition": "Workspace bootstrap, dataset load, a play run, export, scoring, a credit-cap overrun, or workspace deletion fails.",
271
+ "startGraceMs": 600000,
272
+ "runTimeoutMs": 7200000,
273
+ "failureAlert": "github_workflow_run",
274
+ "domainReportRequired": true
275
+ },
258
276
  {
259
277
  "id": "github.fly-pr-preview-cleanup",
260
278
  "name": "Reconcile Fly PR Preview Capacity",
@@ -2281,6 +2281,39 @@ export async function releaseRateStateViaAppRuntime(
2281
2281
  });
2282
2282
  }
2283
2283
 
2284
+ /** Persist the final success batch without acquiring another provider permit. */
2285
+ export async function flushRateStateFeedbackViaAppRuntime(
2286
+ context: WorkerRuntimeApiContext,
2287
+ input: PlayRunnerRateStateReleaseInput,
2288
+ signal: AbortSignal,
2289
+ ): Promise<void> {
2290
+ const batch = input.concurrencyObservationBatch;
2291
+ if (!batch)
2292
+ throw new Error('Rate-state feedback is missing its batch identity.');
2293
+ const result = await postAppRuntimeApi<{
2294
+ acknowledgedObservationBatch?: typeof batch;
2295
+ }>(
2296
+ {
2297
+ ...context,
2298
+ connectionRetryBudgetMs: 0,
2299
+ signal: context.signal
2300
+ ? AbortSignal.any([context.signal, signal])
2301
+ : signal,
2302
+ },
2303
+ { action: 'rate_state_release', ...input },
2304
+ );
2305
+ const acknowledged = result.acknowledgedObservationBatch;
2306
+ if (
2307
+ acknowledged?.observerId !== batch.observerId ||
2308
+ acknowledged.laneId !== batch.laneId ||
2309
+ acknowledged.sequence !== batch.sequence
2310
+ ) {
2311
+ throw new Error(
2312
+ 'Rate-state receiver did not acknowledge the success batch.',
2313
+ );
2314
+ }
2315
+ }
2316
+
2284
2317
  export async function penalizeRateStateViaAppRuntime(
2285
2318
  context: WorkerRuntimeApiContext,
2286
2319
  input: PlayRunnerRateStatePenalizeInput,
@@ -19656,11 +19656,19 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19656
19656
  statusCode: 504,
19657
19657
  },
19658
19658
  ));
19659
+ // The gateway admits a recovery key only under the executor token's
19660
+ // run, which is the root run even inside an inline child. Root runs
19661
+ // keep their original key (executor run == logical run); an inline
19662
+ // child keeps its own logical identity inside the invocation key.
19663
+ const asyncRecoveryScope = this.currentExecutionScope;
19659
19664
  const asyncRecoveryKey = (attempt: number) =>
19660
19665
  asyncRecoveryInvocationKey
19661
19666
  ? buildRuntimeExecutionRecoveryKey({
19662
- parentRunId: this.currentRunId,
19663
- invocationKey: `${asyncRecoveryInvocationKey}:attempt:${attempt}`,
19667
+ parentRunId: asyncRecoveryScope.placement.executorRunId,
19668
+ invocationKey:
19669
+ asyncRecoveryScope.placement.kind === 'inline_child'
19670
+ ? `${asyncRecoveryInvocationKey}:inline-child:${asyncRecoveryScope.logical.runId}:attempt:${attempt}`
19671
+ : `${asyncRecoveryInvocationKey}:attempt:${attempt}`,
19664
19672
  })
19665
19673
  : null;
19666
19674
  // This is deliberately a claim about only the immediately preceding
@@ -2,6 +2,7 @@ import {
2
2
  acquireRateStateViaAppRuntime,
3
3
  AppRuntimeApiTransportError,
4
4
  capacityShedRetryAfterMs,
5
+ flushRateStateFeedbackViaAppRuntime,
5
6
  penalizeRateStateViaAppRuntime,
6
7
  releaseRateStateViaAppRuntime,
7
8
  RateStateValidationUnavailableError,
@@ -54,6 +55,7 @@ export const RATE_ACQUIRE_MAX_WAIT_MS = 300_000;
54
55
  */
55
56
  export const PROVIDER_ADMISSION_WAIT_BUDGET_MS = 120_000;
56
57
  const PROVIDER_NO_PROGRESS_WARNING_MS = 180_000;
58
+ const SUCCESS_FEEDBACK_DRAIN_TIMEOUT_MS = 10_000;
57
59
 
58
60
  /**
59
61
  * Permits leased per app-runtime round trip for the direct Node substrate.
@@ -77,6 +79,10 @@ export interface AppRuntimeRatePort {
77
79
  penalize(
78
80
  input: Parameters<typeof penalizeRateStateViaAppRuntime>[1],
79
81
  ): ReturnType<typeof penalizeRateStateViaAppRuntime>;
82
+ flushFeedback?(
83
+ input: Parameters<typeof flushRateStateFeedbackViaAppRuntime>[1],
84
+ signal: AbortSignal,
85
+ ): Promise<void>;
80
86
  validate?(
81
87
  input: Parameters<typeof validateRateStateViaAppRuntime>[1],
82
88
  ): ReturnType<typeof validateRateStateViaAppRuntime>;
@@ -87,6 +93,8 @@ function contextRatePort(context: WorkerRuntimeApiContext): AppRuntimeRatePort {
87
93
  acquire: (input) => acquireRateStateViaAppRuntime(context, input),
88
94
  release: (input) => releaseRateStateViaAppRuntime(context, input),
89
95
  penalize: (input) => penalizeRateStateViaAppRuntime(context, input),
96
+ flushFeedback: (input, signal) =>
97
+ flushRateStateFeedbackViaAppRuntime(context, input, signal),
90
98
  validate: (input) => validateRateStateViaAppRuntime(context, input),
91
99
  };
92
100
  }
@@ -296,6 +304,16 @@ export class AppRuntimeRateStateBackend implements RateStateBackend {
296
304
  private readonly effectiveRps = new Map<string, number>();
297
305
  private readonly pendingSuccesses = new Map<string, number>();
298
306
  private readonly successFeedbackFlights = new Map<string, SuccessFeedback>();
307
+ private readonly successFeedbackScopes = new Map<
308
+ string,
309
+ {
310
+ bucketId: string;
311
+ rateScopeToken?: string | null;
312
+ rules: readonly PacingRule[];
313
+ }
314
+ >();
315
+ private readonly successFeedbackSupported = new Set<string>();
316
+ private successFeedbackDrain: Promise<void> | null = null;
299
317
  /**
300
318
  * Completion samples travel with the next lease refill. This keeps the
301
319
  * Gradient2 checkpoint current without adding one gateway request per row.
@@ -1072,7 +1090,22 @@ export class AppRuntimeRateStateBackend implements RateStateBackend {
1072
1090
  : rawResponse.waitMs,
1073
1091
  }
1074
1092
  : rawResponse;
1075
- this.acknowledgeSuccessFeedback(successKey, feedback);
1093
+ const acknowledged = response.acknowledgedObservationBatch;
1094
+ const submitted = feedback.concurrencyObservationBatch;
1095
+ if (
1096
+ acknowledged === undefined ||
1097
+ (submitted &&
1098
+ acknowledged?.observerId === submitted.observerId &&
1099
+ acknowledged.laneId === submitted.laneId &&
1100
+ acknowledged.sequence === submitted.sequence)
1101
+ ) {
1102
+ this.acknowledgeSuccessFeedback(successKey, feedback);
1103
+ }
1104
+ if (response.successFeedbackSupported === true) {
1105
+ this.successFeedbackSupported.add(bucketId);
1106
+ } else {
1107
+ this.successFeedbackSupported.delete(bucketId);
1108
+ }
1076
1109
  this.recordCooldown(bucketId, response);
1077
1110
  if (response.scopeValidationSupported === true)
1078
1111
  this.validationScopes.add(bucketId);
@@ -1559,11 +1592,17 @@ export class AppRuntimeRateStateBackend implements RateStateBackend {
1559
1592
 
1560
1593
  observeSuccess(input: {
1561
1594
  bucketId: string;
1595
+ rateScopeToken?: string | null;
1562
1596
  rules: readonly PacingRule[];
1563
1597
  latencyMs?: number | null;
1564
1598
  }): void {
1565
1599
  this.providerStalls.delete(input.bucketId);
1566
1600
  const key = pendingSuccessKey(input.bucketId, input.rules);
1601
+ this.successFeedbackScopes.set(key, {
1602
+ bucketId: input.bucketId,
1603
+ rateScopeToken: input.rateScopeToken,
1604
+ rules: input.rules,
1605
+ });
1567
1606
  this.pendingSuccesses.set(
1568
1607
  key,
1569
1608
  Math.min(1_000_000, (this.pendingSuccesses.get(key) ?? 0) + 1),
@@ -1593,6 +1632,43 @@ export class AppRuntimeRateStateBackend implements RateStateBackend {
1593
1632
  this.pendingConcurrencyObservations.set(key, pending);
1594
1633
  }
1595
1634
 
1635
+ async flushSuccessFeedback(): Promise<void> {
1636
+ if (!this.port.flushFeedback) return;
1637
+ if (this.successFeedbackDrain) return await this.successFeedbackDrain;
1638
+ const signal = AbortSignal.timeout(SUCCESS_FEEDBACK_DRAIN_TIMEOUT_MS);
1639
+ const drain = async () => {
1640
+ for (const [key, scope] of this.successFeedbackScopes) {
1641
+ // Older receivers retain their existing piggyback behavior. An explicit
1642
+ // capability and matching batch acknowledgement protect rolling deploys.
1643
+ if (!this.successFeedbackSupported.has(scope.bucketId)) continue;
1644
+ for (;;) {
1645
+ if (signal.aborted) throw abortReason(signal);
1646
+ const feedback = this.snapshotSuccessFeedback(key);
1647
+ if (!feedback.concurrencyObservationBatch) break;
1648
+ await this.port.flushFeedback!(
1649
+ {
1650
+ ...scope,
1651
+ rules: [...scope.rules],
1652
+ leaseIds: [],
1653
+ ...feedback,
1654
+ schedulerSchema: this.schedulerSchema,
1655
+ },
1656
+ signal,
1657
+ );
1658
+ // A lost response leaves this exact frozen payload available to retry.
1659
+ this.acknowledgeSuccessFeedback(key, feedback);
1660
+ }
1661
+ this.successFeedbackScopes.delete(key);
1662
+ }
1663
+ };
1664
+ this.successFeedbackDrain = drain();
1665
+ try {
1666
+ await this.successFeedbackDrain;
1667
+ } finally {
1668
+ this.successFeedbackDrain = null;
1669
+ }
1670
+ }
1671
+
1596
1672
  private newPendingConcurrencyObservations(
1597
1673
  key: string,
1598
1674
  observations: Array<{ kind: 'success'; latencyMs: number }> = [],
@@ -868,6 +868,7 @@ export function createPlayExecutionGovernor(
868
868
  if (!rules) return;
869
869
  input.rateState.observeSuccess?.({
870
870
  bucketId: rateScope[0],
871
+ ...(rateScope[1] ? { rateScopeToken: rateScope[1] } : {}),
871
872
  rules,
872
873
  latencyMs: success.latencyMs,
873
874
  });
@@ -197,6 +197,9 @@ export interface RateStateBackend {
197
197
  /** Provider residence measured at the HTTP boundary. */
198
198
  latencyMs?: number | null;
199
199
  }): void;
200
+
201
+ /** Bounded final feedback write after the invocation's provider work settles. */
202
+ flushSuccessFeedback?(): Promise<void>;
200
203
  }
201
204
 
202
205
  const NOOP_PERMIT: PacingPermit = {
@@ -90,6 +90,13 @@ export type PlayRunnerRateStateAcquireResult = {
90
90
  scopeValidationSupported?: boolean;
91
91
  /** This gateway rechecks signed provider permits on integration execute. */
92
92
  dispatchValidationSupported?: boolean;
93
+ /** This receiver acknowledges replay-safe success batches on release. */
94
+ successFeedbackSupported?: boolean;
95
+ /**
96
+ * Exact committed feedback identity; null leaves the batch pending for replay.
97
+ * Absent preserves implicit acknowledgement from older receivers.
98
+ */
99
+ acknowledgedObservationBatch?: PlayRunnerRateStateConcurrencyObservationBatch | null;
93
100
  /**
94
101
  * Gateway clock used to produce `scheduledAtMs`. Runners translate the
95
102
  * schedule into relative delays so cross-machine clock skew cannot compress
@@ -138,6 +145,10 @@ export type PlayRunnerRateStateReleaseInput = {
138
145
  leaseIds: string[];
139
146
  /** Idempotently refund rate reservations that never reached provider fetch. */
140
147
  refundReservationIds?: string[];
148
+ /** Flush confirmed outcomes without reserving another provider permit. */
149
+ observedSuccesses?: number;
150
+ concurrencyObservations?: PlayRunnerRateStateConcurrencyObservation[];
151
+ concurrencyObservationBatch?: PlayRunnerRateStateConcurrencyObservationBatch;
141
152
  schedulerSchema?: string | null;
142
153
  };
143
154
 
@@ -3788,6 +3788,19 @@ async function run(
3788
3788
  } finally {
3789
3789
  clearInterval(workCounterTimer);
3790
3790
  emitWorkCounters();
3791
+ try {
3792
+ await rateStateBackend.rateState?.flushSuccessFeedback?.();
3793
+ } catch (feedbackError) {
3794
+ // Feedback is advisory: preserve the Play's result or original failure.
3795
+ emitWorkerLog(
3796
+ `[warn] runtime.provider_success_feedback_flush_failed: ${
3797
+ feedbackError instanceof Error
3798
+ ? feedbackError.message
3799
+ : String(feedbackError)
3800
+ }`,
3801
+ options,
3802
+ );
3803
+ }
3791
3804
  }
3792
3805
  }
3793
3806
 
@@ -83,6 +83,23 @@ export type StagedDaytonaPayload = {
83
83
  startupDiagnosticPath: string;
84
84
  };
85
85
 
86
+ /** No payload or customer command has been sent when this is raised. */
87
+ export class DaytonaNodeUnavailableError extends Error {
88
+ constructor(
89
+ readonly diagnostic: {
90
+ reason: 'command_failed' | 'empty_output';
91
+ exitCode: number | null;
92
+ attempts: number;
93
+ responseBytes: number;
94
+ },
95
+ ) {
96
+ super(
97
+ `Daytona Node executable was not ready before payload staging (${diagnostic.reason}, exit ${diagnostic.exitCode ?? 'unknown'}).`,
98
+ );
99
+ this.name = 'DaytonaNodeUnavailableError';
100
+ }
101
+ }
102
+
86
103
  export type RemoteRunnerPayloadSandbox = {
87
104
  id: string;
88
105
  /** Daytona target (region) the sandbox runs in, when the provider reports it. */
@@ -129,10 +146,11 @@ function nodeMaterializePayloadCommand(input: {
129
146
  artifactCodePath: string;
130
147
  artifactSourceMapPath: string;
131
148
  crashPusherPath: string;
149
+ nodeBinaryPath?: string;
132
150
  }): string {
133
151
  const script = buildDaytonaPayloadMaterializerSource();
134
152
  return [
135
- 'node',
153
+ input.nodeBinaryPath ? shellQuote(input.nodeBinaryPath) : 'node',
136
154
  '-e',
137
155
  shellQuote(script),
138
156
  shellQuote(input.envelopePath),
@@ -583,6 +601,8 @@ export function withRunnerSandboxProvider(
583
601
  export async function stageRunnerPayload(input: {
584
602
  sandbox: RemoteRunnerPayloadSandbox;
585
603
  bundlePromise: Promise<string>;
604
+ /** A provider-resolved executable, independent of detached-shell PATH. */
605
+ nodeBinaryPath?: string;
586
606
  config: PlayRunnerExecutionConfig;
587
607
  /** Provider-selected burst ceiling; the stored profile remains a reservation. */
588
608
  memoryLimitMiB?: number;
@@ -736,8 +756,11 @@ export async function stageRunnerPayload(input: {
736
756
  typeof sandboxTarget === 'string' && sandboxTarget.trim()
737
757
  ? `DEEPLINE_SANDBOX_TARGET=${shellQuote(sandboxTarget.trim())} `
738
758
  : '';
759
+ const nodeCommand = input.nodeBinaryPath
760
+ ? shellQuote(input.nodeBinaryPath)
761
+ : 'node';
739
762
  const watchedRunnerCommand = [
740
- 'node',
763
+ nodeCommand,
741
764
  '-e',
742
765
  shellQuote(
743
766
  buildDaytonaRuntimeWatchdogSource(
@@ -766,6 +789,7 @@ export async function stageRunnerPayload(input: {
766
789
  artifactCodePath,
767
790
  artifactSourceMapPath,
768
791
  crashPusherPath,
792
+ nodeBinaryPath: input.nodeBinaryPath,
769
793
  })} && DEEPLINE_PLAY_RUNNER_RUNTIME_STARTED_PATH=${shellQuote(runtimeStartedPath)} DEEPLINE_PLAY_RUNNER_RUNTIME_COMPLETED_PATH=${shellQuote(runtimeCompletedPath)} DEEPLINE_PLAY_RUNNER_PROGRESS_EVENT_PATH=${shellQuote(progressEventPath)} DEEPLINE_PLAY_RUNNER_STARTUP_DIAGNOSTIC_PATH=${shellQuote(startupDiagnosticPath)} DEEPLINE_PLAY_RUNNER_STDOUT_EVENT_MODE=compact UV_THREADPOOL_SIZE=${PLAY_RUNNER_UV_THREADPOOL_SIZE} ${runtimeReliabilityEnv}${runnerTraceEnv}${sandboxTargetEnv}${watchedRunnerCommand}`;
770
794
  // Crash-containment epilogue: runs UNCONDITIONALLY after the runner exits and
771
795
  // pushes an unacknowledged parsed (or synthesized) terminal so the parked
@@ -775,7 +799,7 @@ export async function stageRunnerPayload(input: {
775
799
  // Its own retry diagnostics are kept in a separate log: appending them to
776
800
  // `outputPath` after a near-gateway-sized result could hide that result from
777
801
  // a later bounded-tail recovery read.
778
- const command = `rm -f ${shellQuote(outputPath)} ${shellQuote(crashPusherLogPath)} ${shellQuote(exitCodePath)} ${shellQuote(oomKillBaselinePath)} ${shellQuote(runnerPidPath)} ${shellQuote(runtimeStartedPath)} ${shellQuote(runtimeCompletedPath)} ${shellQuote(runtimeLimitMarkerPath)} ${shellQuote(terminationDiagnosticPath)} ${shellQuote(memoryLedgerPath)} ${shellQuote(`${memoryLedgerPath}.tmp`)} ${shellQuote(memoryPressurePath)} ${shellQuote(progressEventPath)} ${shellQuote(`${progressEventPath}.*`)} ${shellQuote(startupDiagnosticPath)}; ( awk '$1 == "oom_kill" { print $2 }' /sys/fs/cgroup/memory.events 2>/dev/null || true ) > ${shellQuote(oomKillBaselinePath)}; ( ${runnerCommand} ) > ${shellQuote(outputPath)} 2>&1; code=$?; printf '%s' "$code" > ${shellQuote(exitCodePath)}; ${runtimeReliabilityEnv}node ${shellQuote(crashPusherPath)} ${shellQuote(configPath)} "$code" ${shellQuote(outputPath)} ${shellQuote(runtimeLimitMarkerPath)} ${shellQuote(oomKillBaselinePath)} /sys/fs/cgroup/memory.events ${shellQuote(terminationDiagnosticPath)} ${shellQuote(runtimeCompletedPath)} > ${shellQuote(crashPusherLogPath)} 2>&1 || true; printf 'deepline runner output captured: %s\\n' ${shellQuote(outputPath)}; exit "$code"`;
802
+ const command = `rm -f ${shellQuote(outputPath)} ${shellQuote(crashPusherLogPath)} ${shellQuote(exitCodePath)} ${shellQuote(oomKillBaselinePath)} ${shellQuote(runnerPidPath)} ${shellQuote(runtimeStartedPath)} ${shellQuote(runtimeCompletedPath)} ${shellQuote(runtimeLimitMarkerPath)} ${shellQuote(terminationDiagnosticPath)} ${shellQuote(memoryLedgerPath)} ${shellQuote(`${memoryLedgerPath}.tmp`)} ${shellQuote(memoryPressurePath)} ${shellQuote(progressEventPath)} ${shellQuote(`${progressEventPath}.*`)} ${shellQuote(startupDiagnosticPath)}; ( awk '$1 == "oom_kill" { print $2 }' /sys/fs/cgroup/memory.events 2>/dev/null || true ) > ${shellQuote(oomKillBaselinePath)}; ( ${runnerCommand} ) > ${shellQuote(outputPath)} 2>&1; code=$?; printf '%s' "$code" > ${shellQuote(exitCodePath)}; ${runtimeReliabilityEnv}${nodeCommand} ${shellQuote(crashPusherPath)} ${shellQuote(configPath)} "$code" ${shellQuote(outputPath)} ${shellQuote(runtimeLimitMarkerPath)} ${shellQuote(oomKillBaselinePath)} /sys/fs/cgroup/memory.events ${shellQuote(terminationDiagnosticPath)} ${shellQuote(runtimeCompletedPath)} > ${shellQuote(crashPusherLogPath)} 2>&1 || true; printf 'deepline runner output captured: %s\\n' ${shellQuote(outputPath)}; exit "$code"`;
779
803
 
780
804
  return {
781
805
  workDir: input.workDir,
@@ -793,12 +817,61 @@ export async function stageRunnerPayload(input: {
793
817
  }
794
818
 
795
819
  export async function stageDaytonaRunnerPayload(
796
- input: Omit<Parameters<typeof stageRunnerPayload>[0], 'sandbox'> & {
820
+ input: Omit<
821
+ Parameters<typeof stageRunnerPayload>[0],
822
+ 'sandbox' | 'nodeBinaryPath'
823
+ > & {
797
824
  sandbox: DaytonaSandbox;
798
825
  },
799
826
  ): Promise<StagedDaytonaPayload> {
827
+ // Toolbox commands and detached sessions need not share PATH. Bind the
828
+ // provider's actual Node executable before constructing either launcher.
829
+ const nodeStartedAt = Date.now();
830
+ // A started sandbox may still be bringing its toolbox/runtime online. Retry
831
+ // only an unsuccessful or empty readiness read on this same sandbox before
832
+ // handing the pre-code failure back to the existing startup recovery owner.
833
+ const retryDelaysMs = [0, 150, 350] as const;
834
+ let node!: Awaited<ReturnType<DaytonaSandbox['process']['executeCommand']>>;
835
+ for (const [attempt, delayMs] of retryDelaysMs.entries()) {
836
+ if (delayMs) await new Promise((resolve) => setTimeout(resolve, delayMs));
837
+ node = await input.sandbox.process.executeCommand(
838
+ 'node -p process.execPath',
839
+ input.workDir,
840
+ {},
841
+ 5,
842
+ );
843
+ if (node.exitCode === 0 && node.result.trim() !== '') break;
844
+ const diagnostic = {
845
+ reason: node.exitCode === 0 ? 'empty_output' : 'command_failed',
846
+ exitCode:
847
+ typeof node.exitCode === 'number' && Number.isInteger(node.exitCode)
848
+ ? node.exitCode
849
+ : null,
850
+ attempts: attempt + 1,
851
+ responseBytes: Buffer.byteLength(node.result),
852
+ } satisfies DaytonaNodeUnavailableError['diagnostic'];
853
+ input.emitStage('runtime:node_unavailable', diagnostic);
854
+ if (attempt === retryDelaysMs.length - 1) {
855
+ throw new DaytonaNodeUnavailableError(diagnostic);
856
+ }
857
+ }
858
+ const nodeBinaryPath = node.result.trim();
859
+ if (
860
+ node.exitCode !== 0 ||
861
+ !nodeBinaryPath.startsWith('/') ||
862
+ /[\r\n\0]/.test(nodeBinaryPath) ||
863
+ nodeBinaryPath.length > 4_096
864
+ ) {
865
+ throw new Error(
866
+ 'Daytona did not confirm an absolute Node executable path for this sandbox.',
867
+ );
868
+ }
869
+ input.emitStage('runtime:node_resolved', {
870
+ elapsedMs: Date.now() - nodeStartedAt,
871
+ });
800
872
  return await stageRunnerPayload({
801
873
  ...input,
874
+ nodeBinaryPath,
802
875
  sandbox: {
803
876
  id: input.sandbox.id,
804
877
  target: