deepline 0.3.408 → 0.3.410

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 (217) hide show
  1. package/dist/bundling-sources/sdk/src/agent-runtime.ts +18 -1
  2. package/dist/bundling-sources/sdk/src/client.ts +212 -334
  3. package/dist/bundling-sources/sdk/src/compat.ts +45 -2
  4. package/dist/bundling-sources/sdk/src/db/query.ts +9 -10
  5. package/dist/bundling-sources/sdk/src/http.ts +38 -20
  6. package/dist/bundling-sources/sdk/src/index.ts +4 -5
  7. package/dist/bundling-sources/sdk/src/play.ts +10 -10
  8. package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +6 -4
  9. package/dist/bundling-sources/sdk/src/plays/check-local-play-graph.ts +114 -0
  10. package/dist/bundling-sources/sdk/src/plays/client.ts +431 -0
  11. package/dist/bundling-sources/sdk/src/plays/local-play-execution.ts +2 -2
  12. package/dist/bundling-sources/sdk/src/plays/run-request.ts +106 -16
  13. package/dist/bundling-sources/sdk/src/release.ts +13 -7
  14. package/dist/bundling-sources/sdk/src/runs/read-errors.ts +8 -0
  15. package/dist/bundling-sources/sdk/src/runs/watch-polling.ts +124 -13
  16. package/dist/bundling-sources/sdk/src/tool-calls.ts +108 -19
  17. package/dist/bundling-sources/sdk/src/types.ts +18 -0
  18. package/dist/bundling-sources/shared_libs/integration-contracts/execute-retry-contract.ts +37 -0
  19. package/dist/bundling-sources/shared_libs/integration-contracts/package.json +3 -0
  20. package/dist/bundling-sources/shared_libs/play-data-plane/r2.ts +3 -1
  21. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +277 -76
  22. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +6 -0
  23. package/dist/bundling-sources/shared_libs/play-runtime/dataset-row-input.ts +124 -0
  24. package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +6 -62
  25. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +1 -0
  26. package/dist/bundling-sources/shared_libs/play-runtime/durable-tool-identity.ts +67 -0
  27. package/dist/bundling-sources/shared_libs/play-runtime/execute-server-timing.ts +64 -6
  28. package/dist/bundling-sources/shared_libs/play-runtime/freshness-receipt-execution.ts +1 -5
  29. package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +1 -1
  30. package/dist/bundling-sources/shared_libs/play-runtime/play-artifact-runtime-contract.ts +3 -0
  31. package/dist/bundling-sources/shared_libs/play-runtime/play-http-request.ts +10 -0
  32. package/dist/bundling-sources/shared_libs/play-runtime/query-result-dataset.ts +3 -0
  33. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/egress-probe.ts +3 -0
  34. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +26 -16
  35. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-heartbeat.ts +36 -11
  36. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-terminal-push.ts +35 -0
  37. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-launch-transport.ts +21 -0
  38. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +21 -6
  39. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-session-execution.ts +11 -2
  40. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +85 -20
  41. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +71 -13
  42. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-execution-queries.ts +100 -0
  43. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-pool.ts +9 -5
  44. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +337 -146
  45. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +26 -12
  46. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +3 -1
  47. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +425 -102
  48. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-dispatch-authority.ts +4 -0
  49. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +4 -0
  50. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +9 -0
  51. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-capacity.ts +181 -124
  52. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-worker-wake.ts +152 -0
  53. package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +5 -3
  54. package/dist/bundling-sources/shared_libs/play-runtime/tool-cache-policy.ts +31 -0
  55. package/dist/bundling-sources/shared_libs/play-runtime/transient-service-error.ts +10 -0
  56. package/dist/bundling-sources/shared_libs/plays/ambiguous-write-error.ts +25 -0
  57. package/dist/bundling-sources/shared_libs/plays/artifact-types.ts +4 -0
  58. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +63 -68
  59. package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +129 -63
  60. package/dist/bundling-sources/shared_libs/plays/contracts.ts +14 -0
  61. package/dist/bundling-sources/shared_libs/plays/enrich-play-compiler.ts +58 -16
  62. package/dist/bundling-sources/shared_libs/plays/fetch-http-error.ts +50 -0
  63. package/dist/bundling-sources/shared_libs/plays/source-authoring-edition.ts +351 -0
  64. package/dist/bundling-sources/shared_libs/plays/tool-list-getter-mode.ts +9 -0
  65. package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +1 -1
  66. package/dist/cli/{auth-UVD3CUBB.mjs → auth-LLXLIILR.mjs} +6 -6
  67. package/dist/cli/auth-UG4WOYGL.js +40 -0
  68. package/dist/cli/{billing-BZVTXXLM.mjs → billing-5FVXZYAC.mjs} +8 -8
  69. package/dist/cli/{billing-BZVYAF6T.js → billing-NFPK7GYP.js} +84 -84
  70. package/dist/cli/{chunk-ZAAKSJAS.js → chunk-27GJKC5Q.js} +17 -10
  71. package/dist/cli/{chunk-UHNDVD3P.js → chunk-2H4EY7KH.js} +100 -41
  72. package/dist/cli/{chunk-WTT6CLED.mjs → chunk-2YR7BIIC.mjs} +5 -2
  73. package/dist/cli/{chunk-3ZB6IIA7.js → chunk-36AHOWVS.js} +26 -26
  74. package/dist/cli/{chunk-OSDXE27V.mjs → chunk-4PG4ARL2.mjs} +17 -10
  75. package/dist/cli/{chunk-LOYISF7S.mjs → chunk-4XQLVUC7.mjs} +738 -512
  76. package/dist/cli/{chunk-J5PTX66Y.js → chunk-5MLCBXXU.js} +8 -1
  77. package/dist/cli/chunk-5TUGWDEB.js +313 -0
  78. package/dist/cli/{chunk-D6FFBSM5.js → chunk-5UUQAGJS.js} +5 -2
  79. package/dist/cli/{chunk-4CFULMRB.mjs → chunk-5V5Y37BC.mjs} +2 -2
  80. package/dist/cli/{chunk-PMVFAYL7.mjs → chunk-63ILLN5H.mjs} +2 -2
  81. package/dist/cli/{chunk-AUMD2VXP.js → chunk-63S7X326.js} +1032 -806
  82. package/dist/cli/{chunk-4QFT5LGV.mjs → chunk-6PGATQ6L.mjs} +6 -3
  83. package/dist/cli/{chunk-6BJK4EJ3.mjs → chunk-AARWVK4C.mjs} +2132 -65
  84. package/dist/cli/{chunk-FERRDNMO.js → chunk-AD47BXN2.js} +5 -5
  85. package/dist/cli/{chunk-4RNDKFNQ.js → chunk-D37FEBES.js} +2 -2
  86. package/dist/cli/{chunk-S6R3OX7B.js → chunk-E4CABWXL.js} +25 -2
  87. package/dist/cli/{chunk-NXGCO4JB.mjs → chunk-E4IXDAZI.mjs} +5 -0
  88. package/dist/cli/chunk-EQ4Z7RJW.mjs +35 -0
  89. package/dist/cli/chunk-FR2ILQ4G.mjs +8 -0
  90. package/dist/cli/{chunk-QUAQX3FQ.js → chunk-FVLNYDI4.js} +128 -376
  91. package/dist/cli/{chunk-BRJUESQV.js → chunk-FZJAM576.js} +50 -50
  92. package/dist/cli/{chunk-BZAMOCPA.mjs → chunk-IMZ56EOH.mjs} +3 -3
  93. package/dist/cli/{chunk-IOHRWLQE.mjs → chunk-IPG42SD2.mjs} +452 -138
  94. package/dist/cli/chunk-J33TSWO5.js +8 -0
  95. package/dist/cli/{chunk-XXLXOUUU.mjs → chunk-JNXJZ577.mjs} +1 -1
  96. package/dist/cli/{chunk-JI2B4RZP.mjs → chunk-KCC3FKIW.mjs} +71 -12
  97. package/dist/cli/{chunk-6IKARAYD.mjs → chunk-L2VKAASU.mjs} +23 -0
  98. package/dist/cli/{chunk-PYZZMAJQ.mjs → chunk-LFSHGNT2.mjs} +8 -1
  99. package/dist/cli/{chunk-2XXXFPTD.js → chunk-LTFL4JRN.js} +426 -721
  100. package/dist/cli/{chunk-YHPYUZZX.mjs → chunk-MAMVGOSC.mjs} +1 -1
  101. package/dist/cli/{chunk-URACNNLO.mjs → chunk-MN2OWLFA.mjs} +2 -2
  102. package/dist/cli/{chunk-Q3PUHXOY.js → chunk-NF4V5DTY.js} +12 -12
  103. package/dist/cli/{chunk-Q2E4E4FJ.js → chunk-NPBJI7RE.js} +8 -8
  104. package/dist/cli/chunk-NTVLGHFM.js +501 -0
  105. package/dist/cli/{chunk-HQXL5526.mjs → chunk-NUX2QHNC.mjs} +1 -1
  106. package/dist/cli/{chunk-E3PJF6YP.js → chunk-OBB4QXHA.js} +19 -4
  107. package/dist/cli/{chunk-SEFMGO3Z.js → chunk-OQQOXSYA.js} +2184 -117
  108. package/dist/cli/chunk-OZ6E3IRZ.js +6084 -0
  109. package/dist/cli/{chunk-7P5AA3SN.js → chunk-PPDBRJRW.js} +6 -1
  110. package/dist/cli/{chunk-IWBXJDZA.mjs → chunk-PZSLPBDA.mjs} +2 -2
  111. package/dist/cli/{chunk-ILHRA7AD.js → chunk-QADCFSGH.js} +7 -7
  112. package/dist/cli/{chunk-ECVLPS67.js → chunk-QJI5FIOW.js} +170 -49
  113. package/dist/cli/{chunk-I3DHIXVJ.mjs → chunk-QL22Y56M.mjs} +149 -28
  114. package/dist/cli/{chunk-SD7MXAJA.mjs → chunk-RFB25DSQ.mjs} +36 -284
  115. package/dist/cli/chunk-T44IIOT6.mjs +501 -0
  116. package/dist/cli/chunk-VK2LZ5IP.js +35 -0
  117. package/dist/cli/chunk-VKKVSFRD.js +62 -0
  118. package/dist/cli/{chunk-RWCY6YI2.js → chunk-VYKV5XHL.js} +644 -330
  119. package/dist/cli/{chunk-GSK6NHVM.js → chunk-WU4U6VSB.js} +5 -5
  120. package/dist/cli/chunk-XHLKJOTN.mjs +313 -0
  121. package/dist/cli/{chunk-MJELBQBU.mjs → chunk-XIHWSXSV.mjs} +330 -625
  122. package/dist/cli/chunk-XNZJKRJA.mjs +6084 -0
  123. package/dist/cli/chunk-ZCALLYF6.mjs +62 -0
  124. package/dist/cli/{chunk-ZBABPK6Y.js → chunk-ZFZ427HN.js} +11 -8
  125. package/dist/cli/{chunk-SJVVBWRL.mjs → chunk-ZKYEW55U.mjs} +16 -1
  126. package/dist/cli/command-docs.js +2 -2
  127. package/dist/cli/command-docs.mjs +1 -1
  128. package/dist/cli/{csv-XLGLHJ6P.js → csv-7U3FJYKZ.js} +14 -14
  129. package/dist/cli/{csv-WPSXG6ZZ.mjs → csv-ZWLDMM4A.mjs} +5 -5
  130. package/dist/cli/{db-5PTNJG4X.js → db-DNCGV5PC.js} +42 -47
  131. package/dist/cli/{db-ABS57ABJ.mjs → db-IMYXVMGI.mjs} +15 -20
  132. package/dist/cli/{describe-PVWQJ2PB.mjs → describe-Q23DWJUK.mjs} +20 -15
  133. package/dist/cli/{describe-EZS6PQXA.js → describe-S34JUX5W.js} +27 -22
  134. package/dist/cli/describe-core.d.mts +2 -2
  135. package/dist/cli/describe-core.d.ts +2 -2
  136. package/dist/cli/describe-core.js +6 -6
  137. package/dist/cli/describe-core.mjs +5 -5
  138. package/dist/cli/{executions-OKJGATQZ.mjs → executions-A73EUGSI.mjs} +8 -8
  139. package/dist/cli/{executions-FNTAFQ7Y.js → executions-XOMPX2YJ.js} +10 -10
  140. package/dist/cli/{feedback-W27HDMRR.mjs → feedback-N4GJ2POI.mjs} +11 -7
  141. package/dist/cli/{feedback-6YBOMSUA.js → feedback-QTCHNJJH.js} +13 -9
  142. package/dist/cli/{git-YOOUEL6M.mjs → git-4Q3JIQYZ.mjs} +6 -6
  143. package/dist/cli/git-I6CPBEVG.js +24 -0
  144. package/dist/cli/index.js +240 -352
  145. package/dist/cli/index.mjs +163 -275
  146. package/dist/cli/{legacy-noops-V6OKW7VR.mjs → legacy-noops-3SOVGFF7.mjs} +6 -6
  147. package/dist/cli/{legacy-noops-2VZ7XT6C.js → legacy-noops-4OSHZM34.js} +9 -9
  148. package/dist/cli/{monitors-FBWGVIUS.js → monitors-LVE2KZNG.js} +94 -94
  149. package/dist/cli/{monitors-P2YYWTWT.mjs → monitors-QP2XID2B.mjs} +8 -8
  150. package/dist/cli/{org-DVMG6H3C.mjs → org-IVRETVQA.mjs} +8 -8
  151. package/dist/cli/{org-3XUSVX5T.js → org-XFLK5IAP.js} +68 -68
  152. package/dist/cli/{play-GNC5CQAS.mjs → play-DE7VH6NU.mjs} +533 -98
  153. package/dist/cli/{play-YG5L4BWQ.js → play-XXTPPP23.js} +1067 -632
  154. package/dist/cli/{providers-7PSWG2OV.js → providers-DLD3I4JF.js} +11 -11
  155. package/dist/cli/{providers-JR33SLWL.mjs → providers-GX2NIZQQ.mjs} +8 -8
  156. package/dist/cli/{quickstart-VUOBQYIQ.mjs → quickstart-72B4L4PK.mjs} +5 -5
  157. package/dist/cli/{quickstart-FDVZ5BL3.js → quickstart-XNS3HSWC.js} +7 -7
  158. package/dist/cli/search-6NCH4HB5.js +30 -0
  159. package/dist/cli/{search-JZYLO33K.mjs → search-SUTRANJI.mjs} +9 -9
  160. package/dist/cli/{secrets-VS266FVA.js → secrets-6OBLOVEV.js} +17 -17
  161. package/dist/cli/{secrets-3MCYBT4I.mjs → secrets-FPFXXNUH.mjs} +8 -8
  162. package/dist/cli/sessions-235532DR.js +16 -0
  163. package/dist/cli/{sessions-ELLLT23E.mjs → sessions-R25I4QKN.mjs} +6 -6
  164. package/dist/cli/{settings-PZCKAFII.mjs → settings-AOP6F5PZ.mjs} +12 -12
  165. package/dist/cli/{settings-7364NMMA.js → settings-VL5FOFPN.js} +79 -79
  166. package/dist/cli/{setup-PMWYLCT6.js → setup-BSC67YT3.js} +45 -44
  167. package/dist/cli/{setup-A7GQ4KIA.mjs → setup-ZMPHBHVJ.mjs} +11 -10
  168. package/dist/cli/skills-3IWLYYRL.js +29 -0
  169. package/dist/cli/{skills-WS6BIFPQ.mjs → skills-KF65TVFO.mjs} +7 -7
  170. package/dist/cli/tools-LR66SPYU.js +44 -0
  171. package/dist/cli/{tools-BR4DOZA2.mjs → tools-SX4XIPM7.mjs} +19 -14
  172. package/dist/cli/{update-D2B7UNFT.mjs → update-QRMFRAQQ.mjs} +11 -9
  173. package/dist/cli/update-SP7UEO53.js +31 -0
  174. package/dist/cli/{workflow-PQXC2R47.mjs → workflow-FOEEEB7G.mjs} +44 -20
  175. package/dist/cli/{workflow-6ODTCYXU.js → workflow-KOCK2FLW.js} +54 -30
  176. package/dist/{client-Z9Pye1Wm.d.mts → client-74k4IPh6.d.mts} +137 -54
  177. package/dist/{client-BcvhDfJJ.d.ts → client-C1IK1f8c.d.ts} +137 -54
  178. package/dist/{compiler-manifest-Ycx-hoAr.d.mts → compiler-manifest-lzWpbV3w.d.mts} +83 -66
  179. package/dist/{compiler-manifest-Ycx-hoAr.d.ts → compiler-manifest-lzWpbV3w.d.ts} +83 -66
  180. package/dist/{index-CZXgiN3E.d.mts → index-C8Cy-YTm.d.ts} +5 -1
  181. package/dist/{index-NPG5pqxQ.d.ts → index-CtNK-0nI.d.mts} +5 -1
  182. package/dist/index.d.mts +47 -9
  183. package/dist/index.d.ts +47 -9
  184. package/dist/index.js +17 -14
  185. package/dist/index.mjs +13 -10
  186. package/dist/install-integrity.json +120 -104
  187. package/dist/plays/bundle-play-file.d.mts +3 -3
  188. package/dist/plays/bundle-play-file.d.ts +3 -3
  189. package/dist/plays/bundle-play-file.mjs +5 -4
  190. package/dist/plays/csv-data-input.js +3 -3
  191. package/dist/plays/csv-data-input.mjs +2 -2
  192. package/dist/plays/play-description.d.mts +2 -2
  193. package/dist/plays/play-description.d.ts +2 -2
  194. package/dist/plays/run-request.d.mts +2 -2
  195. package/dist/plays/run-request.d.ts +2 -2
  196. package/dist/plays/run-request.js +11 -3
  197. package/dist/plays/run-request.mjs +10 -2
  198. package/dist/release.d.mts +15 -6
  199. package/dist/release.d.ts +15 -6
  200. package/dist/release.js +2 -2
  201. package/dist/release.mjs +1 -1
  202. package/package.json +2 -2
  203. package/dist/cli/auth-3GB2K636.js +0 -40
  204. package/dist/cli/chunk-67RWR34T.mjs +0 -155
  205. package/dist/cli/chunk-CLJGSOVM.mjs +0 -1716
  206. package/dist/cli/chunk-HQY2SG3K.js +0 -83
  207. package/dist/cli/chunk-OSFWFV72.js +0 -155
  208. package/dist/cli/chunk-Q3LFI3VH.js +0 -392
  209. package/dist/cli/chunk-WPAB4DCU.mjs +0 -83
  210. package/dist/cli/chunk-YMOFWHGO.mjs +0 -392
  211. package/dist/cli/chunk-Z4VNJOF7.js +0 -1716
  212. package/dist/cli/git-YKUXXJOR.js +0 -24
  213. package/dist/cli/search-PPPYC5OZ.js +0 -30
  214. package/dist/cli/sessions-TEHEWW7O.js +0 -16
  215. package/dist/cli/skills-SYS7VRRK.js +0 -29
  216. package/dist/cli/tools-KK4BKVQR.js +0 -39
  217. package/dist/cli/update-2IV5JTX2.js +0 -29
@@ -59,6 +59,8 @@ export interface RunnerHeartbeatLoopOptions {
59
59
  executorToken: string;
60
60
  runId: string;
61
61
  leaseSeconds: number;
62
+ /** Keep managed startup heartbeats active until the scheduler parks the attempt. */
63
+ waitForSchedulerPark?: boolean;
62
64
  /**
63
65
  * Invoked once when the loop decides the runner must self-terminate (terminal
64
66
  * cancellation channel, or gateway unreachable past the lease window). The
@@ -109,6 +111,8 @@ export const RUNNER_HEARTBEAT_REQUEST_TIMEOUT_MS = 10_000;
109
111
  // The detached startup deadline is shorter than the normal 40s cadence.
110
112
  // Retry initial transport failures while customer execution remains fenced.
111
113
  const RUNNER_STARTUP_HEARTBEAT_INTERVAL_MS = 1_000;
114
+ const RUNNER_PARK_ACK_INTERVAL_MS = 100;
115
+ const RUNNER_PARK_ACK_FAST_WINDOW_MS = 1_000;
112
116
 
113
117
  export async function sendRunnerHeartbeat(
114
118
  input: {
@@ -324,6 +328,8 @@ export function startRunnerHeartbeatLoop(
324
328
  let heartbeatAttempt = 0;
325
329
  let lastSuccessAt = now();
326
330
  let handle: { unref?: () => void } | null = null;
331
+ let intervalMs = Math.min(cadenceMs, RUNNER_STARTUP_HEARTBEAT_INTERVAL_MS);
332
+ let fastParkAckUntil: number | null = null;
327
333
  let resolveFirstHeartbeat: (() => void) | null = null;
328
334
  const firstHeartbeat = new Promise<void>((resolve) => {
329
335
  resolveFirstHeartbeat = resolve;
@@ -333,6 +339,15 @@ export function startRunnerHeartbeatLoop(
333
339
  resolveExecutionAcknowledged = resolve;
334
340
  });
335
341
 
342
+ const setCadence = (ms: number) => {
343
+ if (!handle || intervalMs !== ms) {
344
+ if (handle) clearIntervalImpl(handle);
345
+ intervalMs = ms;
346
+ handle = setIntervalImpl(tick, ms);
347
+ }
348
+ if (!resolveFirstHeartbeat) handle.unref?.();
349
+ };
350
+
336
351
  const stopLoop = () => {
337
352
  if (stopped) return;
338
353
  stopped = true;
@@ -359,6 +374,12 @@ export function startRunnerHeartbeatLoop(
359
374
  leaseSeconds: options.leaseSeconds,
360
375
  attempt: heartbeatAttempt,
361
376
  })
377
+ .finally(() => {
378
+ // A caller may request the park acknowledgement as soon as the first
379
+ // heartbeat resolves. Release this completed transport before exposing
380
+ // that fence so the immediate request is not mistaken for an overlap.
381
+ inFlight = false;
382
+ })
362
383
  .then((result) => {
363
384
  if (stopped) return;
364
385
  if (result.ok === false) {
@@ -369,10 +390,7 @@ export function startRunnerHeartbeatLoop(
369
390
  if (resolveFirstHeartbeat) {
370
391
  resolveFirstHeartbeat();
371
392
  resolveFirstHeartbeat = null;
372
- // Restore the steady-state cadence only after durable startup succeeds.
373
- if (handle) clearIntervalImpl(handle);
374
- handle = setIntervalImpl(tick, cadenceMs);
375
- handle.unref?.();
393
+ fastParkAckUntil = now() + RUNNER_PARK_ACK_FAST_WINDOW_MS;
376
394
  }
377
395
  if (
378
396
  result.schedulerStatus === 'waiting' &&
@@ -381,6 +399,16 @@ export function startRunnerHeartbeatLoop(
381
399
  resolveExecutionAcknowledged();
382
400
  resolveExecutionAcknowledged = null;
383
401
  }
402
+ // One loop owns the managed startup handshake. A healthy, unparked
403
+ // attempt briefly uses the fast cadence; slow/legacy acknowledgements
404
+ // return to startup pacing, and confirmed parking restores lease pacing.
405
+ setCadence(
406
+ options.waitForSchedulerPark && resolveExecutionAcknowledged
407
+ ? fastParkAckUntil !== null && now() < fastParkAckUntil
408
+ ? RUNNER_PARK_ACK_INTERVAL_MS
409
+ : RUNNER_STARTUP_HEARTBEAT_INTERVAL_MS
410
+ : cadenceMs,
411
+ );
384
412
  })
385
413
  .catch((error) => {
386
414
  if (stopped) return;
@@ -395,24 +423,21 @@ export function startRunnerHeartbeatLoop(
395
423
  });
396
424
  return;
397
425
  }
426
+ if (options.waitForSchedulerPark && resolveExecutionAcknowledged) {
427
+ setCadence(RUNNER_STARTUP_HEARTBEAT_INTERVAL_MS);
428
+ }
398
429
  warn('heartbeat_failed_retrying', {
399
430
  runId: options.runId,
400
431
  sinceMs,
401
432
  leaseMs,
402
433
  error: message,
403
434
  });
404
- })
405
- .finally(() => {
406
- inFlight = false;
407
435
  });
408
436
  };
409
437
 
410
438
  // Keep this timer referenced until the first accepted heartbeat: a pending
411
439
  // Promise alone does not keep Node alive after an initial transport failure.
412
- handle = setIntervalImpl(
413
- tick,
414
- Math.min(cadenceMs, RUNNER_STARTUP_HEARTBEAT_INTERVAL_MS),
415
- );
440
+ setCadence(intervalMs);
416
441
  // Fire one immediately so a cancel that landed before the first interval is
417
442
  // observed within a cadence, not a full lease window.
418
443
  tick();
@@ -1,3 +1,4 @@
1
+ import { readFileSync, writeFileSync } from 'node:fs';
1
2
  import {
2
3
  RUNNER_TERMINAL_CHECKPOINT_DISPOSITION,
3
4
  type PlayRunnerEvent,
@@ -551,6 +552,8 @@ export interface PushRunnerTerminalOptions {
551
552
  result: PlayRunnerResult | PlayRunnerTerminalTransportResult;
552
553
  finalProgressEvents?: PlayRunnerEvent[];
553
554
  finalProgressLogOffset?: number;
555
+ /** Existing per-invocation completion marker, shared with crash recovery. */
556
+ runtimeCompletedPath?: string;
554
557
  /** Injectable for tests. Defaults to a bearer POST to the gateway. */
555
558
  fetchImpl?: typeof fetch;
556
559
  sleepImpl?: (ms: number) => Promise<void>;
@@ -722,6 +725,38 @@ export async function pushRunnerTerminal(
722
725
  if (response.ok) {
723
726
  await response.text().catch(() => '');
724
727
  reportTransport('ok', { status: response.status });
728
+ if (options.runtimeCompletedPath) {
729
+ try {
730
+ const marker: unknown = JSON.parse(
731
+ readFileSync(options.runtimeCompletedPath, 'utf8'),
732
+ );
733
+ if (
734
+ !marker ||
735
+ typeof marker !== 'object' ||
736
+ Array.isArray(marker) ||
737
+ !('completed' in marker) ||
738
+ marker.completed !== true
739
+ ) {
740
+ throw new Error('Invalid runtime completion marker');
741
+ }
742
+ // Preserve the original completion timestamp: the watchdog's
743
+ // settlement grace still starts when customer execution ended.
744
+ writeFileSync(
745
+ options.runtimeCompletedPath,
746
+ JSON.stringify({
747
+ ...marker,
748
+ terminalAcknowledgedRunId: options.runId,
749
+ }),
750
+ 'utf8',
751
+ );
752
+ } catch {
753
+ // The terminal is already durable. Failure to record this local
754
+ // handoff retains the existing first-write-wins epilogue POST.
755
+ warn('terminal_acknowledgement_marker_unavailable', {
756
+ runId: options.runId,
757
+ });
758
+ }
759
+ }
725
760
  return true;
726
761
  }
727
762
  const body = await response.text().catch(() => '');
@@ -361,6 +361,10 @@ function deadline(timeoutMs: number, signal?: AbortSignal) {
361
361
  export function createDaytonaLaunchClient(
362
362
  options: DaytonaClientOptions,
363
363
  cancellation?: AbortSignal,
364
+ onCreateStage?: (
365
+ stage: 'create:response' | 'create:started',
366
+ timing: Record<string, unknown>,
367
+ ) => void,
364
368
  ) {
365
369
  if (!options.apiKey?.trim())
366
370
  throw new Error('Daytona launch requires the configured API key.');
@@ -421,6 +425,7 @@ export function createDaytonaLaunchClient(
421
425
  }),
422
426
  );
423
427
  sent = true;
428
+ const requestStartedAt = performance.now();
424
429
  const response = await api.createSandbox(
425
430
  {
426
431
  name: params.name,
@@ -447,9 +452,25 @@ export function createDaytonaLaunchClient(
447
452
  { timeout: timeoutMs },
448
453
  );
449
454
  data = response.data;
455
+ try {
456
+ onCreateStage?.('create:response', {
457
+ createRequestMs: performance.now() - requestStartedAt,
458
+ initialState: daytonaSandboxStateForLog(data.state),
459
+ });
460
+ } catch {
461
+ // An advisory observer cannot select provider recovery or skip readiness.
462
+ }
450
463
  const sandbox = makeSandbox(data, () => phase.signal);
451
464
  waiting = sandbox;
465
+ const readyStartedAt = performance.now();
452
466
  await sandbox.waitUntilStarted(options.timeout ?? 60);
467
+ try {
468
+ onCreateStage?.('create:started', {
469
+ providerReadyWaitMs: performance.now() - readyStartedAt,
470
+ });
471
+ } catch {
472
+ // Provider success remains authoritative if its observer fails.
473
+ }
453
474
  // Future phases own fresh deadlines; none inherits this create scope.
454
475
  return makeSandbox(
455
476
  {
@@ -157,9 +157,10 @@ function nodeMaterializePayloadCommand(input: {
157
157
  * 2. push it — or a synthesized crash failure naming the exit code — to the
158
158
  * gateway's `runner_terminal` action.
159
159
  *
160
- * The gateway records terminals FIRST-WRITE-WINS and absurd wake events are
161
- * first-emit-wins, so a duplicate push after a successful in-runner push is a
162
- * no-op; this needs no coordination with the runner.
160
+ * A successful in-runner push records its acknowledgement in the existing
161
+ * per-invocation completion marker. Only that positive acknowledgement skips
162
+ * the epilogue POST. Older runners and missing/ambiguous acknowledgements
163
+ * retain the gateway's first-write-wins recovery.
163
164
  *
164
165
  * Exported (with the builder) for the unit test that executes the script
165
166
  * against a real local HTTP server.
@@ -178,6 +179,7 @@ const runtimeLimitMarkerPath = process.argv[5] || '';
178
179
  const oomKillBaselinePath = process.argv[6] || '';
179
180
  const memoryEventsPath = process.argv[7] || '/sys/fs/cgroup/memory.events';
180
181
  const terminationDiagnosticPath = process.argv[8] || '';
182
+ const runtimeCompletedPath = process.argv[9] || '';
181
183
  const exitCode = Number.isFinite(exitCodeRaw) ? exitCodeRaw : null;
182
184
  const MAX_OUTPUT_BYTES = ${DAYTONA_CRASH_PUSHER_OUTPUT_TAIL_MAX_BYTES};
183
185
  const MAX_POST_TERMINAL_DIAGNOSTIC_BYTES = ${RUNNER_POST_TERMINAL_DIAGNOSTIC_MAX_BYTES};
@@ -360,6 +362,19 @@ async function main() {
360
362
  writeDiagnostic('[crash-terminal] no push config; skipping');
361
363
  return;
362
364
  }
365
+ if (runtimeCompletedPath) {
366
+ try {
367
+ const marker = JSON.parse(fs.readFileSync(runtimeCompletedPath, 'utf8'));
368
+ if (
369
+ marker &&
370
+ marker.completed === true &&
371
+ marker.terminalAcknowledgedRunId === push.runId
372
+ ) {
373
+ writeDiagnostic('[crash-terminal] terminal already acknowledged; skipping');
374
+ return;
375
+ }
376
+ } catch {}
377
+ }
363
378
  const parsedResultEvent = readResultEventFromOutput();
364
379
  const parsedResult = parsedResultEvent && parsedResultEvent.result;
365
380
  // Exit codes are authored-program output too: a play may intentionally call
@@ -725,14 +740,14 @@ export async function stageRunnerPayload(input: {
725
740
  crashPusherPath,
726
741
  })} && 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}`;
727
742
  // Crash-containment epilogue: runs UNCONDITIONALLY after the runner exits and
728
- // pushes the parsed (or synthesized) terminal to the gateway so the parked
743
+ // pushes an unacknowledged parsed (or synthesized) terminal so the parked
729
744
  // worker wakes within seconds of ANY runner death — process.exit abuse, OOM
730
745
  // SIGKILL, or a crash before the in-process push. Idempotent against a
731
- // successful in-runner push (terminals are first-write-wins).
746
+ // successful in-runner push when its local acknowledgement was lost.
732
747
  // Its own retry diagnostics are kept in a separate log: appending them to
733
748
  // `outputPath` after a near-gateway-sized result could hide that result from
734
749
  // a later bounded-tail recovery read.
735
- 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(crashPusherLogPath)} 2>&1 || true; printf 'deepline runner output captured: %s\\n' ${shellQuote(outputPath)}; exit "$code"`;
750
+ 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"`;
736
751
 
737
752
  return {
738
753
  workDir: input.workDir,
@@ -233,6 +233,7 @@ export type DetachedDaytonaRunnerSupervisor = {
233
233
  export const DAYTONA_RUNNER_READY_TIMEOUT_MS =
234
234
  RUNTIME_RELIABILITY_POLICY.sandbox.runnerReadyTimeoutMs;
235
235
  const DAYTONA_RUNNER_READY_POLL_MS = 50;
236
+ const DAYTONA_RUNNER_READY_FAST_WINDOW_MS = 250;
236
237
 
237
238
  export type DetachedRunnerReadinessPort = (
238
239
  baselineHeartbeatAt?: string | null,
@@ -474,6 +475,7 @@ export async function confirmDetachedDaytonaRunnerReady(input: {
474
475
  );
475
476
  const pollMs = Math.max(1, input.pollMs ?? DAYTONA_RUNNER_READY_POLL_MS);
476
477
  const deadline = now() + timeoutMs;
478
+ const fastStartupDeadline = now() + DAYTONA_RUNNER_READY_FAST_WINDOW_MS;
477
479
  let schedulerDiagnosis = 'no_readiness_response';
478
480
  let schedulerReadinessResponses = 0;
479
481
  let retryDelayMs = pollMs;
@@ -491,8 +493,15 @@ export async function confirmDetachedDaytonaRunnerReady(input: {
491
493
  schedulerDiagnosis = 'runner_heartbeat_not_observed';
492
494
  }
493
495
  if (read.timedOut) break;
494
- await sleep(Math.min(retryDelayMs, Math.max(1, deadline - now())));
495
- retryDelayMs = Math.min(1_000, retryDelayMs * 2);
496
+ // A healthy store is waiting for a fresh heartbeat, not recovering from
497
+ // an outage. Avoid compounding its startup delay with the runner's own
498
+ // park-acknowledgement backoff. Only the first 250ms keeps the 50ms cadence;
499
+ // unavailable reads and longer initialization retain exponential backoff.
500
+ const fastStartupRead =
501
+ read.response !== null && now() < fastStartupDeadline;
502
+ const delayMs = fastStartupRead ? pollMs : retryDelayMs;
503
+ await sleep(Math.min(delayMs, Math.max(1, deadline - now())));
504
+ retryDelayMs = fastStartupRead ? pollMs : Math.min(1_000, retryDelayMs * 2);
496
505
  }
497
506
 
498
507
  const inspect = (sandbox: DaytonaSandbox) =>
@@ -308,8 +308,12 @@ class DaytonaUploadDeadlineBreachError extends Error {
308
308
  }
309
309
 
310
310
  export const daytonaSdkClientFactory = {
311
- create(input: DaytonaClientOptions, signal?: AbortSignal): DaytonaClient {
312
- return createDaytonaLaunchClient(input, signal);
311
+ create(
312
+ input: DaytonaClientOptions,
313
+ signal?: AbortSignal,
314
+ onCreateStage?: Parameters<typeof createDaytonaLaunchClient>[2],
315
+ ): DaytonaClient {
316
+ return createDaytonaLaunchClient(input, signal, onCreateStage);
313
317
  },
314
318
  /** Request-scoped native deadlines for detached recovery and cleanup. */
315
319
  createControl: createDaytonaControlClient,
@@ -1153,7 +1157,16 @@ function emitDaytonaStage(
1153
1157
  stage,
1154
1158
  ...extra,
1155
1159
  };
1156
- console.info('[play-runner.daytona.stage]', JSON.stringify(payload));
1160
+ try {
1161
+ console.info('[play-runner.daytona.stage]', JSON.stringify(payload));
1162
+ } catch {
1163
+ // Diagnostic delivery never changes resource ownership or command acknowledgement.
1164
+ try {
1165
+ console.error('[play-runner.daytona.stage.failed]', stage);
1166
+ } catch {
1167
+ // No log sink is available; execution still owns its normal lifecycle fences.
1168
+ }
1169
+ }
1157
1170
  }
1158
1171
 
1159
1172
  async function downloadDaytonaTextFile(
@@ -1326,6 +1339,8 @@ function prepareDaytonaExecution(
1326
1339
  const daytona = daytonaSdkClientFactory.create(
1327
1340
  daytonaOptions,
1328
1341
  callbacks?.cancellationSignal,
1342
+ (stage, timing) =>
1343
+ emitDaytonaStage(callbacks, input.context, stage, timing),
1329
1344
  );
1330
1345
  const bundlePromise = buildPlayRunnerBundle();
1331
1346
  const sandboxLifecycle = createOneShotDaytonaSandboxLifecycle({
@@ -1453,6 +1468,8 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1453
1468
  let cancellationRequested = false;
1454
1469
  let cancellationCleanupStarted = false;
1455
1470
  let activeAcquiredResource: AcquiredDaytonaSandbox | null = null;
1471
+ let resourceAccountingPending = false;
1472
+ let resourceAccountingFailed = false;
1456
1473
  const uploadDeadlineBreaches: DaytonaUploadAttemptTiming[] = [];
1457
1474
  const reportedSandboxIds = new Set<string>();
1458
1475
  const reportedSandboxEndTimes = new Map<string, number | null>();
@@ -1521,6 +1538,12 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1521
1538
  if (activeAcquiredResource && !activeAcquiredResource.billingEndedAt) {
1522
1539
  activeAcquiredResource.billingEndedAt = Date.now();
1523
1540
  }
1541
+ // Publication may commit after a lost response or lose its attempt fence.
1542
+ // Join it before deciding whether this backend still owns deletion.
1543
+ if (resourceAccountingPending || resourceAccountingFailed) {
1544
+ cancelExecution(new Error(DAYTONA_CANCELLED_ERROR));
1545
+ return;
1546
+ }
1524
1547
  if (cancellationCleanupStarted) {
1525
1548
  cancelExecution(new Error(DAYTONA_CANCELLED_ERROR));
1526
1549
  return;
@@ -1550,6 +1573,7 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1550
1573
  executionAttempt += 1
1551
1574
  ) {
1552
1575
  let sandboxForAttempt: DaytonaSandbox | null = null;
1576
+ let resourceAccounting: Promise<void> | null = null;
1553
1577
  const attemptStartedAt = Date.now();
1554
1578
  try {
1555
1579
  const acquiredSandbox = await Promise.race([
@@ -1562,15 +1586,27 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1562
1586
  acquiredSandbox.billingStartedAt - startedAt;
1563
1587
  const sandbox = acquiredSandbox.sandbox;
1564
1588
  sandboxForAttempt = sandbox;
1565
- await reportRuntimeResource(acquiredSandbox);
1566
- // Account for every acquired retry sandbox before customer code runs.
1567
- // This keeps resource persistence failures on the pre-side-effect
1568
- // side of the execution boundary and makes cancellation billing
1569
- // complete without a post-terminal write race.
1570
- await sandboxLifecycle.settlePendingCreates();
1571
- for (const acquired of sandboxLifecycle.acquiredSandboxes()) {
1572
- await reportRuntimeResource(acquired);
1573
- }
1589
+ // Payload upload and session allocation do not execute customer code.
1590
+ // Overlap them with accounting, but join every resource ACK before
1591
+ // command dispatch, retry, or cancellation cleanup.
1592
+ resourceAccountingPending = true;
1593
+ resourceAccounting = (async () => {
1594
+ await reportRuntimeResource(acquiredSandbox);
1595
+ await sandboxLifecycle.settlePendingCreates();
1596
+ for (const acquired of sandboxLifecycle.acquiredSandboxes()) {
1597
+ await reportRuntimeResource(acquired);
1598
+ }
1599
+ })();
1600
+ // Observe settlement immediately; the joins below propagate failures.
1601
+ void resourceAccounting.then(
1602
+ () => {
1603
+ resourceAccountingPending = false;
1604
+ },
1605
+ () => {
1606
+ resourceAccountingPending = false;
1607
+ resourceAccountingFailed = true;
1608
+ },
1609
+ );
1574
1610
  throwIfCancellationRequested();
1575
1611
 
1576
1612
  const { workdir: workDir } = loadDaytonaRunnerPathsConfig();
@@ -1586,6 +1622,7 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1586
1622
  0,
1587
1623
  Math.floor(config.context.runAttempt ?? 0),
1588
1624
  );
1625
+ let commandAcceptedAtMs: number | null = null;
1589
1626
  const detachedRunner = prepareDetachedDaytonaRunner({
1590
1627
  sandbox,
1591
1628
  sessionId,
@@ -1603,12 +1640,29 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1603
1640
  },
1604
1641
  cancellation: cancellationPromise,
1605
1642
  onCommandAccepted: async () => {
1643
+ commandAcceptedAtMs = Date.now();
1644
+ emitDaytonaStage(callbacks, config.context, 'execute:accepted', {
1645
+ elapsedMs: commandAcceptedAtMs - startedAt,
1646
+ });
1606
1647
  await callbacks?.onRunnerCommandAccepted?.({
1607
1648
  provider: 'daytona',
1608
1649
  sandboxId: sandbox.id,
1609
1650
  });
1610
1651
  },
1611
1652
  onReady: async (ready) => {
1653
+ const heartbeatReadyAtMs = Date.now();
1654
+ emitDaytonaStage(
1655
+ callbacks,
1656
+ config.context,
1657
+ 'execute:heartbeat_ready',
1658
+ {
1659
+ elapsedMs: heartbeatReadyAtMs - startedAt,
1660
+ commandAcceptedToHeartbeatMs:
1661
+ commandAcceptedAtMs === null
1662
+ ? null
1663
+ : heartbeatReadyAtMs - commandAcceptedAtMs,
1664
+ },
1665
+ );
1612
1666
  await callbacks?.onDetachedRunnerReady?.({
1613
1667
  provider: 'daytona',
1614
1668
  sandboxId: sandbox.id,
@@ -1649,8 +1703,13 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1649
1703
  sandbox,
1650
1704
  DAYTONA_UPLOAD_ATTEMPT_DEADLINE_MS,
1651
1705
  );
1706
+ const finishUpload = () => {
1707
+ uploadScope.dispose();
1708
+ if (uploadDeadlineTimer) clearTimeout(uploadDeadlineTimer);
1709
+ uploadDeadlineTimer = null;
1710
+ };
1652
1711
  try {
1653
- stagedPayload = await Promise.race([
1712
+ const upload = Promise.race([
1654
1713
  stageDaytonaRunnerPayload({
1655
1714
  sandbox: uploadScope.sandbox,
1656
1715
  bundlePromise,
@@ -1662,8 +1721,17 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1662
1721
  }),
1663
1722
  uploadDeadlinePromise,
1664
1723
  cancellationPromise,
1665
- ]);
1724
+ ])
1725
+ .then((payload) => {
1726
+ runtimeTiming.daytonaUploadMs = Date.now() - uploadStartedAt;
1727
+ return payload;
1728
+ })
1729
+ .finally(finishUpload);
1730
+ [stagedPayload] = await Promise.all([upload, resourceAccounting]);
1666
1731
  } catch (error) {
1732
+ // A failed or lost publication response keeps cleanup with the
1733
+ // scheduler, even when upload or cancellation failed first.
1734
+ await resourceAccounting;
1667
1735
  if (error instanceof DaytonaUploadDeadlineBreachError) {
1668
1736
  const latestTiming =
1669
1737
  error.attemptTimings[error.attemptTimings.length - 1];
@@ -1691,12 +1759,8 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1691
1759
  }
1692
1760
  throw error;
1693
1761
  } finally {
1694
- uploadScope.dispose();
1695
- if (uploadDeadlineTimer) {
1696
- clearTimeout(uploadDeadlineTimer);
1697
- }
1762
+ finishUpload();
1698
1763
  }
1699
- runtimeTiming.daytonaUploadMs = Date.now() - uploadStartedAt;
1700
1764
  throwIfCancellationRequested();
1701
1765
 
1702
1766
  emitDaytonaStage(callbacks, config.context, 'execute:start', {
@@ -1785,6 +1849,7 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1785
1849
  runtimeTiming,
1786
1850
  };
1787
1851
  } catch (error) {
1852
+ if (resourceAccounting) await resourceAccounting;
1788
1853
  if (cancellationRequested || callbacks?.cancellationSignal?.aborted) {
1789
1854
  onCancel();
1790
1855
  throw new Error(DAYTONA_CANCELLED_ERROR);
@@ -1862,7 +1927,7 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1862
1927
  );
1863
1928
  } finally {
1864
1929
  callbacks?.cancellationSignal?.removeEventListener('abort', onCancel);
1865
- if (cancellationRequested) {
1930
+ if (cancellationRequested && !resourceAccountingFailed) {
1866
1931
  await sandboxLifecycle.settlePendingCreates();
1867
1932
  for (const acquired of sandboxLifecycle.acquiredSandboxes()) {
1868
1933
  try {