@nodetool-ai/websocket 0.7.0-rc.23 → 0.8.2

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 (1101) hide show
  1. package/README.md +50 -0
  2. package/dist/.nodetool-build-stamp +1 -0
  3. package/dist/agent/run-node-tool.d.ts +39 -0
  4. package/dist/agent/run-node-tool.d.ts.map +1 -0
  5. package/dist/agent/run-node-tool.js +55 -0
  6. package/dist/agent/run-node-tool.js.map +1 -0
  7. package/dist/chat-prediction-forwarder.d.ts +13 -0
  8. package/dist/chat-prediction-forwarder.d.ts.map +1 -0
  9. package/dist/chat-prediction-forwarder.js +22 -0
  10. package/dist/chat-prediction-forwarder.js.map +1 -0
  11. package/dist/chat-tool-call-repair.d.ts +45 -0
  12. package/dist/chat-tool-call-repair.d.ts.map +1 -0
  13. package/dist/chat-tool-call-repair.js +100 -0
  14. package/dist/chat-tool-call-repair.js.map +1 -0
  15. package/dist/chat-turn-registry.d.ts +169 -0
  16. package/dist/chat-turn-registry.d.ts.map +1 -0
  17. package/dist/chat-turn-registry.js +326 -0
  18. package/dist/chat-turn-registry.js.map +1 -0
  19. package/dist/collection-api.d.ts +2 -2
  20. package/dist/collection-api.d.ts.map +1 -1
  21. package/dist/collection-api.js +82 -14
  22. package/dist/collection-api.js.map +1 -1
  23. package/dist/configured-providers.d.ts +52 -0
  24. package/dist/configured-providers.d.ts.map +1 -0
  25. package/dist/configured-providers.js +85 -0
  26. package/dist/configured-providers.js.map +1 -0
  27. package/dist/cors.d.ts +22 -0
  28. package/dist/cors.d.ts.map +1 -0
  29. package/dist/cors.js +91 -0
  30. package/dist/cors.js.map +1 -0
  31. package/dist/credit-gate.d.ts +36 -0
  32. package/dist/credit-gate.d.ts.map +1 -0
  33. package/dist/credit-gate.js +76 -0
  34. package/dist/credit-gate.js.map +1 -0
  35. package/dist/custom-providers.d.ts +40 -0
  36. package/dist/custom-providers.d.ts.map +1 -0
  37. package/dist/custom-providers.js +134 -0
  38. package/dist/custom-providers.js.map +1 -0
  39. package/dist/debug-sessions.d.ts +10 -0
  40. package/dist/debug-sessions.d.ts.map +1 -0
  41. package/dist/debug-sessions.js +9 -0
  42. package/dist/debug-sessions.js.map +1 -0
  43. package/dist/drain.d.ts +34 -0
  44. package/dist/drain.d.ts.map +1 -0
  45. package/dist/drain.js +68 -0
  46. package/dist/drain.js.map +1 -0
  47. package/dist/e2e-server.d.ts +30 -0
  48. package/dist/e2e-server.d.ts.map +1 -0
  49. package/dist/e2e-server.js +111 -0
  50. package/dist/e2e-server.js.map +1 -0
  51. package/dist/error-traces.d.ts +56 -0
  52. package/dist/error-traces.d.ts.map +1 -0
  53. package/dist/error-traces.js +179 -0
  54. package/dist/error-traces.js.map +1 -0
  55. package/dist/example-workflows.d.ts +20 -0
  56. package/dist/example-workflows.d.ts.map +1 -0
  57. package/dist/example-workflows.js +123 -0
  58. package/dist/example-workflows.js.map +1 -0
  59. package/dist/external-mcp.d.ts +58 -0
  60. package/dist/external-mcp.d.ts.map +1 -0
  61. package/dist/external-mcp.js +288 -0
  62. package/dist/external-mcp.js.map +1 -0
  63. package/dist/fake-runtime.d.ts +114 -0
  64. package/dist/fake-runtime.d.ts.map +1 -0
  65. package/dist/fake-runtime.js +307 -0
  66. package/dist/fake-runtime.js.map +1 -0
  67. package/dist/file-api.d.ts +4 -7
  68. package/dist/file-api.d.ts.map +1 -1
  69. package/dist/file-api.js +122 -39
  70. package/dist/file-api.js.map +1 -1
  71. package/dist/frontend-renderer-registry.d.ts +75 -0
  72. package/dist/frontend-renderer-registry.d.ts.map +1 -0
  73. package/dist/frontend-renderer-registry.js +111 -0
  74. package/dist/frontend-renderer-registry.js.map +1 -0
  75. package/dist/generation-recovery.d.ts +13 -0
  76. package/dist/generation-recovery.d.ts.map +1 -0
  77. package/dist/generation-recovery.js +219 -0
  78. package/dist/generation-recovery.js.map +1 -0
  79. package/dist/headless-job-runner.d.ts +71 -0
  80. package/dist/headless-job-runner.d.ts.map +1 -0
  81. package/dist/headless-job-runner.js +198 -0
  82. package/dist/headless-job-runner.js.map +1 -0
  83. package/dist/http-api.d.ts +157 -24
  84. package/dist/http-api.d.ts.map +1 -1
  85. package/dist/http-api.js +628 -646
  86. package/dist/http-api.js.map +1 -1
  87. package/dist/http-body-schemas.d.ts +109 -0
  88. package/dist/http-body-schemas.d.ts.map +1 -0
  89. package/dist/http-body-schemas.js +146 -0
  90. package/dist/http-body-schemas.js.map +1 -0
  91. package/dist/index.d.ts +10 -3
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +10 -3
  94. package/dist/index.js.map +1 -1
  95. package/dist/interrupted-jobs.d.ts +14 -0
  96. package/dist/interrupted-jobs.d.ts.map +1 -0
  97. package/dist/interrupted-jobs.js +28 -0
  98. package/dist/interrupted-jobs.js.map +1 -0
  99. package/dist/job-control.d.ts +57 -0
  100. package/dist/job-control.d.ts.map +1 -0
  101. package/dist/job-control.js +134 -0
  102. package/dist/job-control.js.map +1 -0
  103. package/dist/job-queue.d.ts +47 -0
  104. package/dist/job-queue.d.ts.map +1 -0
  105. package/dist/job-queue.js +52 -0
  106. package/dist/job-queue.js.map +1 -0
  107. package/dist/job-run-registry.d.ts +176 -0
  108. package/dist/job-run-registry.d.ts.map +1 -0
  109. package/dist/job-run-registry.js +312 -0
  110. package/dist/job-run-registry.js.map +1 -0
  111. package/dist/lib/app-build-service.d.ts +16 -0
  112. package/dist/lib/app-build-service.d.ts.map +1 -0
  113. package/dist/lib/app-build-service.js +39 -0
  114. package/dist/lib/app-build-service.js.map +1 -0
  115. package/dist/lib/app-debug-service.d.ts +16 -0
  116. package/dist/lib/app-debug-service.d.ts.map +1 -0
  117. package/dist/lib/app-debug-service.js +48 -0
  118. package/dist/lib/app-debug-service.js.map +1 -0
  119. package/dist/lib/app-deployment-service.d.ts +82 -0
  120. package/dist/lib/app-deployment-service.d.ts.map +1 -0
  121. package/dist/lib/app-deployment-service.js +282 -0
  122. package/dist/lib/app-deployment-service.js.map +1 -0
  123. package/dist/lib/app-instances-service.d.ts +1284 -0
  124. package/dist/lib/app-instances-service.d.ts.map +1 -0
  125. package/dist/lib/app-instances-service.js +378 -0
  126. package/dist/lib/app-instances-service.js.map +1 -0
  127. package/dist/lib/app-run-cancellation.d.ts +4 -0
  128. package/dist/lib/app-run-cancellation.d.ts.map +1 -0
  129. package/dist/lib/app-run-cancellation.js +28 -0
  130. package/dist/lib/app-run-cancellation.js.map +1 -0
  131. package/dist/lib/app-run-server.d.ts +7 -0
  132. package/dist/lib/app-run-server.d.ts.map +1 -0
  133. package/dist/lib/app-run-server.js +6 -0
  134. package/dist/lib/app-run-server.js.map +1 -0
  135. package/dist/lib/app-session-scope.d.ts +91 -0
  136. package/dist/lib/app-session-scope.d.ts.map +1 -0
  137. package/dist/lib/app-session-scope.js +91 -0
  138. package/dist/lib/app-session-scope.js.map +1 -0
  139. package/dist/lib/applications-service.d.ts +354 -0
  140. package/dist/lib/applications-service.d.ts.map +1 -0
  141. package/dist/lib/applications-service.js +507 -0
  142. package/dist/lib/applications-service.js.map +1 -0
  143. package/dist/lib/asset-duration.d.ts +7 -0
  144. package/dist/lib/asset-duration.d.ts.map +1 -0
  145. package/dist/lib/asset-duration.js +24 -0
  146. package/dist/lib/asset-duration.js.map +1 -0
  147. package/dist/lib/asset-export.d.ts +34 -0
  148. package/dist/lib/asset-export.d.ts.map +1 -0
  149. package/dist/lib/asset-export.js +113 -0
  150. package/dist/lib/asset-export.js.map +1 -0
  151. package/dist/lib/asset-model-interface.d.ts +41 -0
  152. package/dist/lib/asset-model-interface.d.ts.map +1 -0
  153. package/dist/lib/asset-model-interface.js +74 -0
  154. package/dist/lib/asset-model-interface.js.map +1 -0
  155. package/dist/lib/asset-paths.d.ts +49 -1
  156. package/dist/lib/asset-paths.d.ts.map +1 -1
  157. package/dist/lib/asset-paths.js +127 -31
  158. package/dist/lib/asset-paths.js.map +1 -1
  159. package/dist/lib/asset-response.d.ts +12 -0
  160. package/dist/lib/asset-response.d.ts.map +1 -0
  161. package/dist/lib/asset-response.js +89 -0
  162. package/dist/lib/asset-response.js.map +1 -0
  163. package/dist/lib/audio-peaks.d.ts +23 -0
  164. package/dist/lib/audio-peaks.d.ts.map +1 -0
  165. package/dist/lib/audio-peaks.js +335 -0
  166. package/dist/lib/audio-peaks.js.map +1 -0
  167. package/dist/lib/bridge.d.ts +9 -1
  168. package/dist/lib/bridge.d.ts.map +1 -1
  169. package/dist/lib/bridge.js +27 -4
  170. package/dist/lib/bridge.js.map +1 -1
  171. package/dist/lib/compression.d.ts +3 -0
  172. package/dist/lib/compression.d.ts.map +1 -0
  173. package/dist/lib/compression.js +3 -0
  174. package/dist/lib/compression.js.map +1 -0
  175. package/dist/lib/document-model-interfaces.d.ts +30 -0
  176. package/dist/lib/document-model-interfaces.d.ts.map +1 -0
  177. package/dist/lib/document-model-interfaces.js +296 -0
  178. package/dist/lib/document-model-interfaces.js.map +1 -0
  179. package/dist/lib/env-config.d.ts +9 -0
  180. package/dist/lib/env-config.d.ts.map +1 -0
  181. package/dist/lib/env-config.js +18 -0
  182. package/dist/lib/env-config.js.map +1 -0
  183. package/dist/lib/example-apps.d.ts +52 -0
  184. package/dist/lib/example-apps.d.ts.map +1 -0
  185. package/dist/lib/example-apps.js +192 -0
  186. package/dist/lib/example-apps.js.map +1 -0
  187. package/dist/lib/example-games.d.ts +5 -0
  188. package/dist/lib/example-games.d.ts.map +1 -0
  189. package/dist/lib/example-games.js +13 -0
  190. package/dist/lib/example-games.js.map +1 -0
  191. package/dist/lib/example-recipes.d.ts +33 -0
  192. package/dist/lib/example-recipes.d.ts.map +1 -0
  193. package/dist/lib/example-recipes.js +213 -0
  194. package/dist/lib/example-recipes.js.map +1 -0
  195. package/dist/lib/example-storyboards.d.ts +40 -0
  196. package/dist/lib/example-storyboards.d.ts.map +1 -0
  197. package/dist/lib/example-storyboards.js +131 -0
  198. package/dist/lib/example-storyboards.js.map +1 -0
  199. package/dist/lib/example-thumbnail.d.ts +21 -0
  200. package/dist/lib/example-thumbnail.d.ts.map +1 -0
  201. package/dist/lib/example-thumbnail.js +50 -0
  202. package/dist/lib/example-thumbnail.js.map +1 -0
  203. package/dist/lib/example-timelines.d.ts +9 -0
  204. package/dist/lib/example-timelines.d.ts.map +1 -0
  205. package/dist/lib/example-timelines.js +25 -0
  206. package/dist/lib/example-timelines.js.map +1 -0
  207. package/dist/lib/extension-dist.d.ts +24 -0
  208. package/dist/lib/extension-dist.d.ts.map +1 -0
  209. package/dist/lib/extension-dist.js +67 -0
  210. package/dist/lib/extension-dist.js.map +1 -0
  211. package/dist/lib/external-asset-lookup.d.ts +30 -0
  212. package/dist/lib/external-asset-lookup.d.ts.map +1 -0
  213. package/dist/lib/external-asset-lookup.js +99 -0
  214. package/dist/lib/external-asset-lookup.js.map +1 -0
  215. package/dist/lib/external-assets.d.ts +13 -0
  216. package/dist/lib/external-assets.d.ts.map +1 -0
  217. package/dist/lib/external-assets.js +46 -0
  218. package/dist/lib/external-assets.js.map +1 -0
  219. package/dist/lib/fly-replay.d.ts +34 -0
  220. package/dist/lib/fly-replay.d.ts.map +1 -0
  221. package/dist/lib/fly-replay.js +111 -0
  222. package/dist/lib/fly-replay.js.map +1 -0
  223. package/dist/lib/http-rate-limit.d.ts +32 -0
  224. package/dist/lib/http-rate-limit.d.ts.map +1 -0
  225. package/dist/lib/http-rate-limit.js +47 -0
  226. package/dist/lib/http-rate-limit.js.map +1 -0
  227. package/dist/lib/http-tracing.d.ts +12 -0
  228. package/dist/lib/http-tracing.d.ts.map +1 -0
  229. package/dist/lib/http-tracing.js +81 -0
  230. package/dist/lib/http-tracing.js.map +1 -0
  231. package/dist/lib/instance-id.d.ts +25 -0
  232. package/dist/lib/instance-id.d.ts.map +1 -0
  233. package/dist/lib/instance-id.js +44 -0
  234. package/dist/lib/instance-id.js.map +1 -0
  235. package/dist/lib/link-codes.d.ts +82 -0
  236. package/dist/lib/link-codes.d.ts.map +1 -0
  237. package/dist/lib/link-codes.js +100 -0
  238. package/dist/lib/link-codes.js.map +1 -0
  239. package/dist/lib/local-asset-upload.d.ts +5 -0
  240. package/dist/lib/local-asset-upload.d.ts.map +1 -0
  241. package/dist/lib/local-asset-upload.js +69 -0
  242. package/dist/lib/local-asset-upload.js.map +1 -0
  243. package/dist/lib/local-file-access.d.ts +31 -0
  244. package/dist/lib/local-file-access.d.ts.map +1 -0
  245. package/dist/lib/local-file-access.js +169 -0
  246. package/dist/lib/local-file-access.js.map +1 -0
  247. package/dist/lib/localhost-trust.d.ts +59 -0
  248. package/dist/lib/localhost-trust.d.ts.map +1 -0
  249. package/dist/lib/localhost-trust.js +139 -0
  250. package/dist/lib/localhost-trust.js.map +1 -0
  251. package/dist/lib/mcp-mount.d.ts +25 -0
  252. package/dist/lib/mcp-mount.d.ts.map +1 -0
  253. package/dist/lib/mcp-mount.js +32 -0
  254. package/dist/lib/mcp-mount.js.map +1 -0
  255. package/dist/lib/media-size.d.ts +21 -0
  256. package/dist/lib/media-size.d.ts.map +1 -0
  257. package/dist/lib/media-size.js +48 -0
  258. package/dist/lib/media-size.js.map +1 -0
  259. package/dist/lib/media.d.ts +33 -0
  260. package/dist/lib/media.d.ts.map +1 -0
  261. package/dist/lib/media.js +110 -0
  262. package/dist/lib/media.js.map +1 -0
  263. package/dist/lib/package-asset-export.d.ts +83 -0
  264. package/dist/lib/package-asset-export.d.ts.map +1 -0
  265. package/dist/lib/package-asset-export.js +212 -0
  266. package/dist/lib/package-asset-export.js.map +1 -0
  267. package/dist/lib/package-assets.d.ts +41 -0
  268. package/dist/lib/package-assets.d.ts.map +1 -0
  269. package/dist/lib/package-assets.js +155 -0
  270. package/dist/lib/package-assets.js.map +1 -0
  271. package/dist/lib/project-delete.d.ts +11 -0
  272. package/dist/lib/project-delete.d.ts.map +1 -0
  273. package/dist/lib/project-delete.js +67 -0
  274. package/dist/lib/project-delete.js.map +1 -0
  275. package/dist/lib/project-document-copy.d.ts +31 -0
  276. package/dist/lib/project-document-copy.d.ts.map +1 -0
  277. package/dist/lib/project-document-copy.js +473 -0
  278. package/dist/lib/project-document-copy.js.map +1 -0
  279. package/dist/lib/public-app-assets.d.ts +8 -0
  280. package/dist/lib/public-app-assets.d.ts.map +1 -0
  281. package/dist/lib/public-app-assets.js +77 -0
  282. package/dist/lib/public-app-assets.js.map +1 -0
  283. package/dist/lib/public-routes.d.ts +102 -0
  284. package/dist/lib/public-routes.d.ts.map +1 -0
  285. package/dist/lib/public-routes.js +168 -0
  286. package/dist/lib/public-routes.js.map +1 -0
  287. package/dist/lib/public-workflow-share.d.ts +20 -0
  288. package/dist/lib/public-workflow-share.d.ts.map +1 -0
  289. package/dist/lib/public-workflow-share.js +45 -0
  290. package/dist/lib/public-workflow-share.js.map +1 -0
  291. package/dist/lib/run-trace-live-delivery.d.ts +25 -0
  292. package/dist/lib/run-trace-live-delivery.d.ts.map +1 -0
  293. package/dist/lib/run-trace-live-delivery.js +91 -0
  294. package/dist/lib/run-trace-live-delivery.js.map +1 -0
  295. package/dist/lib/runtime-detection.d.ts +17 -0
  296. package/dist/lib/runtime-detection.d.ts.map +1 -0
  297. package/dist/lib/runtime-detection.js +90 -0
  298. package/dist/lib/runtime-detection.js.map +1 -0
  299. package/dist/lib/storage-access.d.ts +21 -0
  300. package/dist/lib/storage-access.d.ts.map +1 -0
  301. package/dist/lib/storage-access.js +74 -0
  302. package/dist/lib/storage-access.js.map +1 -0
  303. package/dist/lib/storage.d.ts +9 -0
  304. package/dist/lib/storage.d.ts.map +1 -0
  305. package/dist/lib/storage.js +28 -0
  306. package/dist/lib/storage.js.map +1 -0
  307. package/dist/lib/storyboard-export.d.ts +64 -0
  308. package/dist/lib/storyboard-export.d.ts.map +1 -0
  309. package/dist/lib/storyboard-export.js +183 -0
  310. package/dist/lib/storyboard-export.js.map +1 -0
  311. package/dist/lib/style-presets.d.ts +34 -0
  312. package/dist/lib/style-presets.d.ts.map +1 -0
  313. package/dist/lib/style-presets.js +62 -0
  314. package/dist/lib/style-presets.js.map +1 -0
  315. package/dist/lib/temp-url-resolver.d.ts +35 -0
  316. package/dist/lib/temp-url-resolver.d.ts.map +1 -0
  317. package/dist/lib/temp-url-resolver.js +88 -0
  318. package/dist/lib/temp-url-resolver.js.map +1 -0
  319. package/dist/lib/test-ui-run-routes.d.ts +8 -0
  320. package/dist/lib/test-ui-run-routes.d.ts.map +1 -0
  321. package/dist/lib/test-ui-run-routes.js +42 -0
  322. package/dist/lib/test-ui-run-routes.js.map +1 -0
  323. package/dist/lib/thumbnail.d.ts +74 -0
  324. package/dist/lib/thumbnail.d.ts.map +1 -0
  325. package/dist/lib/thumbnail.js +305 -0
  326. package/dist/lib/thumbnail.js.map +1 -0
  327. package/dist/lib/timeline-bundle.d.ts +1821 -0
  328. package/dist/lib/timeline-bundle.d.ts.map +1 -0
  329. package/dist/lib/timeline-bundle.js +342 -0
  330. package/dist/lib/timeline-bundle.js.map +1 -0
  331. package/dist/lib/video-proxy.d.ts +114 -0
  332. package/dist/lib/video-proxy.d.ts.map +1 -0
  333. package/dist/lib/video-proxy.js +546 -0
  334. package/dist/lib/video-proxy.js.map +1 -0
  335. package/dist/lib/wire-values.d.ts +31 -0
  336. package/dist/lib/wire-values.d.ts.map +1 -0
  337. package/dist/lib/wire-values.js +49 -0
  338. package/dist/lib/wire-values.js.map +1 -0
  339. package/dist/lib/workflow-bundle.d.ts +102 -0
  340. package/dist/lib/workflow-bundle.d.ts.map +1 -0
  341. package/dist/lib/workflow-bundle.js +306 -0
  342. package/dist/lib/workflow-bundle.js.map +1 -0
  343. package/dist/lib/workflow-service.d.ts +82 -0
  344. package/dist/lib/workflow-service.d.ts.map +1 -0
  345. package/dist/lib/workflow-service.js +197 -0
  346. package/dist/lib/workflow-service.js.map +1 -0
  347. package/dist/lib/workflow-workspace.d.ts +20 -0
  348. package/dist/lib/workflow-workspace.d.ts.map +1 -0
  349. package/dist/lib/workflow-workspace.js +65 -0
  350. package/dist/lib/workflow-workspace.js.map +1 -0
  351. package/dist/lib/ws-connection-health.d.ts +10 -0
  352. package/dist/lib/ws-connection-health.d.ts.map +1 -0
  353. package/dist/lib/ws-connection-health.js +38 -0
  354. package/dist/lib/ws-connection-health.js.map +1 -0
  355. package/dist/lib/ws-rate-limit.d.ts +22 -0
  356. package/dist/lib/ws-rate-limit.d.ts.map +1 -0
  357. package/dist/lib/ws-rate-limit.js +45 -0
  358. package/dist/lib/ws-rate-limit.js.map +1 -0
  359. package/dist/lib/ws-upgrade.d.ts +40 -0
  360. package/dist/lib/ws-upgrade.d.ts.map +1 -0
  361. package/dist/lib/ws-upgrade.js +66 -0
  362. package/dist/lib/ws-upgrade.js.map +1 -0
  363. package/dist/lib/zip-stream.d.ts +46 -0
  364. package/dist/lib/zip-stream.d.ts.map +1 -0
  365. package/dist/lib/zip-stream.js +140 -0
  366. package/dist/lib/zip-stream.js.map +1 -0
  367. package/dist/mcp-agent-tools.d.ts +56 -0
  368. package/dist/mcp-agent-tools.d.ts.map +1 -0
  369. package/dist/mcp-agent-tools.js +1147 -0
  370. package/dist/mcp-agent-tools.js.map +1 -0
  371. package/dist/mcp-client-config.d.ts +64 -0
  372. package/dist/mcp-client-config.d.ts.map +1 -0
  373. package/dist/mcp-client-config.js +353 -0
  374. package/dist/mcp-client-config.js.map +1 -0
  375. package/dist/mcp-server.d.ts +56 -7
  376. package/dist/mcp-server.d.ts.map +1 -1
  377. package/dist/mcp-server.js +255 -762
  378. package/dist/mcp-server.js.map +1 -1
  379. package/dist/mcp-tool-deps.d.ts +20 -0
  380. package/dist/mcp-tool-deps.d.ts.map +1 -0
  381. package/dist/mcp-tool-deps.js +61 -0
  382. package/dist/mcp-tool-deps.js.map +1 -0
  383. package/dist/messagepack.d.ts +7 -0
  384. package/dist/messagepack.d.ts.map +1 -0
  385. package/dist/messagepack.js +19 -0
  386. package/dist/messagepack.js.map +1 -0
  387. package/dist/model-download-runtime.d.ts +9 -0
  388. package/dist/model-download-runtime.d.ts.map +1 -0
  389. package/dist/model-download-runtime.js +77 -0
  390. package/dist/model-download-runtime.js.map +1 -0
  391. package/dist/model-execution-availability.d.ts +14 -0
  392. package/dist/model-execution-availability.d.ts.map +1 -0
  393. package/dist/model-execution-availability.js +161 -0
  394. package/dist/model-execution-availability.js.map +1 -0
  395. package/dist/models-api.d.ts +61 -5
  396. package/dist/models-api.d.ts.map +1 -1
  397. package/dist/models-api.js +182 -689
  398. package/dist/models-api.js.map +1 -1
  399. package/dist/node-registry-setup.d.ts +84 -0
  400. package/dist/node-registry-setup.d.ts.map +1 -0
  401. package/dist/node-registry-setup.js +163 -0
  402. package/dist/node-registry-setup.js.map +1 -0
  403. package/dist/oauth/cimd.d.ts +28 -0
  404. package/dist/oauth/cimd.d.ts.map +1 -0
  405. package/dist/oauth/cimd.js +177 -0
  406. package/dist/oauth/cimd.js.map +1 -0
  407. package/dist/oauth/gate.d.ts +38 -0
  408. package/dist/oauth/gate.d.ts.map +1 -0
  409. package/dist/oauth/gate.js +77 -0
  410. package/dist/oauth/gate.js.map +1 -0
  411. package/dist/oauth/metadata.d.ts +35 -0
  412. package/dist/oauth/metadata.d.ts.map +1 -0
  413. package/dist/oauth/metadata.js +56 -0
  414. package/dist/oauth/metadata.js.map +1 -0
  415. package/dist/oauth/pending-store.d.ts +79 -0
  416. package/dist/oauth/pending-store.d.ts.map +1 -0
  417. package/dist/oauth/pending-store.js +133 -0
  418. package/dist/oauth/pending-store.js.map +1 -0
  419. package/dist/oauth/pkce.d.ts +16 -0
  420. package/dist/oauth/pkce.d.ts.map +1 -0
  421. package/dist/oauth/pkce.js +29 -0
  422. package/dist/oauth/pkce.js.map +1 -0
  423. package/dist/oauth/validate.d.ts +25 -0
  424. package/dist/oauth/validate.d.ts.map +1 -0
  425. package/dist/oauth/validate.js +62 -0
  426. package/dist/oauth/validate.js.map +1 -0
  427. package/dist/oauth/www-authenticate.d.ts +9 -0
  428. package/dist/oauth/www-authenticate.d.ts.map +1 -0
  429. package/dist/oauth/www-authenticate.js +33 -0
  430. package/dist/oauth/www-authenticate.js.map +1 -0
  431. package/dist/oauth-api.d.ts +19 -0
  432. package/dist/oauth-api.d.ts.map +1 -1
  433. package/dist/oauth-api.js +531 -12
  434. package/dist/oauth-api.js.map +1 -1
  435. package/dist/openai-api.d.ts.map +1 -1
  436. package/dist/openai-api.js +4 -9
  437. package/dist/openai-api.js.map +1 -1
  438. package/dist/pack-snapshot.d.ts +24 -0
  439. package/dist/pack-snapshot.d.ts.map +1 -0
  440. package/dist/pack-snapshot.js +35 -0
  441. package/dist/pack-snapshot.js.map +1 -0
  442. package/dist/plugins/websocket.d.ts +21 -4
  443. package/dist/plugins/websocket.d.ts.map +1 -1
  444. package/dist/plugins/websocket.js +106 -297
  445. package/dist/plugins/websocket.js.map +1 -1
  446. package/dist/python-bridge-gate.d.ts +28 -0
  447. package/dist/python-bridge-gate.d.ts.map +1 -0
  448. package/dist/python-bridge-gate.js +36 -0
  449. package/dist/python-bridge-gate.js.map +1 -0
  450. package/dist/resolve-media-urls.d.ts +17 -3
  451. package/dist/resolve-media-urls.d.ts.map +1 -1
  452. package/dist/resolve-media-urls.js +117 -31
  453. package/dist/resolve-media-urls.js.map +1 -1
  454. package/dist/resource-events.d.ts +28 -0
  455. package/dist/resource-events.d.ts.map +1 -0
  456. package/dist/resource-events.js +20 -0
  457. package/dist/resource-events.js.map +1 -0
  458. package/dist/routes/account.d.ts +100 -0
  459. package/dist/routes/account.d.ts.map +1 -0
  460. package/dist/routes/account.js +197 -0
  461. package/dist/routes/account.js.map +1 -0
  462. package/dist/routes/app-runs.d.ts +4 -0
  463. package/dist/routes/app-runs.d.ts.map +1 -0
  464. package/dist/routes/app-runs.js +173 -0
  465. package/dist/routes/app-runs.js.map +1 -0
  466. package/dist/routes/applications.d.ts +36 -0
  467. package/dist/routes/applications.d.ts.map +1 -0
  468. package/dist/routes/applications.js +183 -0
  469. package/dist/routes/applications.js.map +1 -0
  470. package/dist/routes/assets.d.ts +3 -3
  471. package/dist/routes/assets.d.ts.map +1 -1
  472. package/dist/routes/assets.js +78 -44
  473. package/dist/routes/assets.js.map +1 -1
  474. package/dist/routes/atlascloud-webhook.d.ts +93 -0
  475. package/dist/routes/atlascloud-webhook.d.ts.map +1 -0
  476. package/dist/routes/atlascloud-webhook.js +369 -0
  477. package/dist/routes/atlascloud-webhook.js.map +1 -0
  478. package/dist/routes/collections.d.ts.map +1 -1
  479. package/dist/routes/config.d.ts +26 -0
  480. package/dist/routes/config.d.ts.map +1 -0
  481. package/dist/routes/config.js +64 -0
  482. package/dist/routes/config.js.map +1 -0
  483. package/dist/routes/documents.d.ts +27 -0
  484. package/dist/routes/documents.d.ts.map +1 -0
  485. package/dist/routes/documents.js +155 -0
  486. package/dist/routes/documents.js.map +1 -0
  487. package/dist/routes/fal-credits.d.ts +4 -0
  488. package/dist/routes/fal-credits.d.ts.map +1 -0
  489. package/dist/routes/fal-credits.js +101 -0
  490. package/dist/routes/fal-credits.js.map +1 -0
  491. package/dist/routes/fal-pricing-estimate.d.ts +4 -0
  492. package/dist/routes/fal-pricing-estimate.d.ts.map +1 -0
  493. package/dist/routes/fal-pricing-estimate.js +114 -0
  494. package/dist/routes/fal-pricing-estimate.js.map +1 -0
  495. package/dist/routes/fal-pricing.d.ts +4 -0
  496. package/dist/routes/fal-pricing.d.ts.map +1 -0
  497. package/dist/routes/fal-pricing.js +93 -0
  498. package/dist/routes/fal-pricing.js.map +1 -0
  499. package/dist/routes/fal-webhook.d.ts +102 -0
  500. package/dist/routes/fal-webhook.d.ts.map +1 -0
  501. package/dist/routes/fal-webhook.js +379 -0
  502. package/dist/routes/fal-webhook.js.map +1 -0
  503. package/dist/routes/files.d.ts.map +1 -1
  504. package/dist/routes/files.js +4 -3
  505. package/dist/routes/files.js.map +1 -1
  506. package/dist/routes/health.d.ts +1 -0
  507. package/dist/routes/health.d.ts.map +1 -1
  508. package/dist/routes/health.js +41 -7
  509. package/dist/routes/health.js.map +1 -1
  510. package/dist/routes/integrations.d.ts +39 -0
  511. package/dist/routes/integrations.d.ts.map +1 -0
  512. package/dist/routes/integrations.js +211 -0
  513. package/dist/routes/integrations.js.map +1 -0
  514. package/dist/routes/js-scripts.d.ts +32 -0
  515. package/dist/routes/js-scripts.d.ts.map +1 -0
  516. package/dist/routes/js-scripts.js +498 -0
  517. package/dist/routes/js-scripts.js.map +1 -0
  518. package/dist/routes/kie-credits.d.ts +4 -0
  519. package/dist/routes/kie-credits.d.ts.map +1 -0
  520. package/dist/routes/kie-credits.js +93 -0
  521. package/dist/routes/kie-credits.js.map +1 -0
  522. package/dist/routes/kie-pricing.d.ts +4 -0
  523. package/dist/routes/kie-pricing.d.ts.map +1 -0
  524. package/dist/routes/kie-pricing.js +45 -0
  525. package/dist/routes/kie-pricing.js.map +1 -0
  526. package/dist/routes/kie-webhook.d.ts +4 -0
  527. package/dist/routes/kie-webhook.d.ts.map +1 -0
  528. package/dist/routes/kie-webhook.js +70 -0
  529. package/dist/routes/kie-webhook.js.map +1 -0
  530. package/dist/routes/nodes.d.ts.map +1 -1
  531. package/dist/routes/nodes.js +29 -51
  532. package/dist/routes/nodes.js.map +1 -1
  533. package/dist/routes/oauth-as.d.ts +30 -0
  534. package/dist/routes/oauth-as.d.ts.map +1 -0
  535. package/dist/routes/oauth-as.js +412 -0
  536. package/dist/routes/oauth-as.js.map +1 -0
  537. package/dist/routes/oauth.d.ts.map +1 -1
  538. package/dist/routes/openai.d.ts.map +1 -1
  539. package/dist/routes/public-apps.d.ts +27 -0
  540. package/dist/routes/public-apps.d.ts.map +1 -0
  541. package/dist/routes/public-apps.js +99 -0
  542. package/dist/routes/public-apps.js.map +1 -0
  543. package/dist/routes/public-shared-workflows.d.ts +16 -0
  544. package/dist/routes/public-shared-workflows.d.ts.map +1 -0
  545. package/dist/routes/public-shared-workflows.js +55 -0
  546. package/dist/routes/public-shared-workflows.js.map +1 -0
  547. package/dist/routes/run-spans.d.ts +4 -0
  548. package/dist/routes/run-spans.d.ts.map +1 -0
  549. package/dist/routes/run-spans.js +53 -0
  550. package/dist/routes/run-spans.js.map +1 -0
  551. package/dist/routes/sandbox-modules.d.ts +17 -0
  552. package/dist/routes/sandbox-modules.d.ts.map +1 -0
  553. package/dist/routes/sandbox-modules.js +85 -0
  554. package/dist/routes/sandbox-modules.js.map +1 -0
  555. package/dist/routes/sdk-v1.d.ts +24 -0
  556. package/dist/routes/sdk-v1.d.ts.map +1 -0
  557. package/dist/routes/sdk-v1.js +320 -0
  558. package/dist/routes/sdk-v1.js.map +1 -0
  559. package/dist/routes/storage.d.ts.map +1 -1
  560. package/dist/routes/storyboards.d.ts +16 -0
  561. package/dist/routes/storyboards.d.ts.map +1 -0
  562. package/dist/routes/storyboards.js +97 -0
  563. package/dist/routes/storyboards.js.map +1 -0
  564. package/dist/routes/timeline-animations.d.ts +23 -0
  565. package/dist/routes/timeline-animations.d.ts.map +1 -0
  566. package/dist/routes/timeline-animations.js +99 -0
  567. package/dist/routes/timeline-animations.js.map +1 -0
  568. package/dist/routes/timeline-audio-bake.d.ts +51 -0
  569. package/dist/routes/timeline-audio-bake.d.ts.map +1 -0
  570. package/dist/routes/timeline-audio-bake.js +110 -0
  571. package/dist/routes/timeline-audio-bake.js.map +1 -0
  572. package/dist/routes/timeline-fonts.d.ts +51 -0
  573. package/dist/routes/timeline-fonts.d.ts.map +1 -0
  574. package/dist/routes/timeline-fonts.js +195 -0
  575. package/dist/routes/timeline-fonts.js.map +1 -0
  576. package/dist/routes/timeline-isolate-subject.d.ts +55 -0
  577. package/dist/routes/timeline-isolate-subject.d.ts.map +1 -0
  578. package/dist/routes/timeline-isolate-subject.js +128 -0
  579. package/dist/routes/timeline-isolate-subject.js.map +1 -0
  580. package/dist/routes/timeline-track-object.d.ts +39 -0
  581. package/dist/routes/timeline-track-object.d.ts.map +1 -0
  582. package/dist/routes/timeline-track-object.js +97 -0
  583. package/dist/routes/timeline-track-object.js.map +1 -0
  584. package/dist/routes/timelines.d.ts +18 -0
  585. package/dist/routes/timelines.d.ts.map +1 -0
  586. package/dist/routes/timelines.js +191 -0
  587. package/dist/routes/timelines.js.map +1 -0
  588. package/dist/routes/workflows.d.ts +6 -9
  589. package/dist/routes/workflows.d.ts.map +1 -1
  590. package/dist/routes/workflows.js +100 -11
  591. package/dist/routes/workflows.js.map +1 -1
  592. package/dist/routes/workspace.d.ts.map +1 -1
  593. package/dist/run-supervisor.d.ts +30 -0
  594. package/dist/run-supervisor.d.ts.map +1 -0
  595. package/dist/run-supervisor.js +86 -0
  596. package/dist/run-supervisor.js.map +1 -0
  597. package/dist/sandbox-catalog.d.ts +28 -0
  598. package/dist/sandbox-catalog.d.ts.map +1 -0
  599. package/dist/sandbox-catalog.js +74 -0
  600. package/dist/sandbox-catalog.js.map +1 -0
  601. package/dist/screenshot-projects.d.ts +27 -0
  602. package/dist/screenshot-projects.d.ts.map +1 -0
  603. package/dist/screenshot-projects.js +880 -0
  604. package/dist/screenshot-projects.js.map +1 -0
  605. package/dist/screenshot-server.js +950 -47
  606. package/dist/screenshot-server.js.map +1 -1
  607. package/dist/sdk/sdk-cached-model-inventory.d.ts +21 -0
  608. package/dist/sdk/sdk-cached-model-inventory.d.ts.map +1 -0
  609. package/dist/sdk/sdk-cached-model-inventory.js +51 -0
  610. package/dist/sdk/sdk-cached-model-inventory.js.map +1 -0
  611. package/dist/sdk/sdk-capabilities-service.d.ts +32 -0
  612. package/dist/sdk/sdk-capabilities-service.d.ts.map +1 -0
  613. package/dist/sdk/sdk-capabilities-service.js +47 -0
  614. package/dist/sdk/sdk-capabilities-service.js.map +1 -0
  615. package/dist/sdk/sdk-execution-readiness-probe.d.ts +19 -0
  616. package/dist/sdk/sdk-execution-readiness-probe.d.ts.map +1 -0
  617. package/dist/sdk/sdk-execution-readiness-probe.js +90 -0
  618. package/dist/sdk/sdk-execution-readiness-probe.js.map +1 -0
  619. package/dist/sdk/sdk-execution-target-readiness.d.ts +15 -0
  620. package/dist/sdk/sdk-execution-target-readiness.d.ts.map +1 -0
  621. package/dist/sdk/sdk-execution-target-readiness.js +50 -0
  622. package/dist/sdk/sdk-execution-target-readiness.js.map +1 -0
  623. package/dist/sdk/sdk-feature-flags.d.ts +18 -0
  624. package/dist/sdk/sdk-feature-flags.d.ts.map +1 -0
  625. package/dist/sdk/sdk-feature-flags.js +30 -0
  626. package/dist/sdk/sdk-feature-flags.js.map +1 -0
  627. package/dist/sdk/sdk-huggingface-download-state.d.ts +15 -0
  628. package/dist/sdk/sdk-huggingface-download-state.d.ts.map +1 -0
  629. package/dist/sdk/sdk-huggingface-download-state.js +25 -0
  630. package/dist/sdk/sdk-huggingface-download-state.js.map +1 -0
  631. package/dist/sdk/sdk-live-runner-registry.d.ts +21 -0
  632. package/dist/sdk/sdk-live-runner-registry.d.ts.map +1 -0
  633. package/dist/sdk/sdk-live-runner-registry.js +26 -0
  634. package/dist/sdk/sdk-live-runner-registry.js.map +1 -0
  635. package/dist/sdk/sdk-model-catalog-service.d.ts +20 -0
  636. package/dist/sdk/sdk-model-catalog-service.d.ts.map +1 -0
  637. package/dist/sdk/sdk-model-catalog-service.js +219 -0
  638. package/dist/sdk/sdk-model-catalog-service.js.map +1 -0
  639. package/dist/sdk/sdk-model-download-service.d.ts +39 -0
  640. package/dist/sdk/sdk-model-download-service.d.ts.map +1 -0
  641. package/dist/sdk/sdk-model-download-service.js +277 -0
  642. package/dist/sdk/sdk-model-download-service.js.map +1 -0
  643. package/dist/sdk/sdk-model-requirement-probe.d.ts +30 -0
  644. package/dist/sdk/sdk-model-requirement-probe.d.ts.map +1 -0
  645. package/dist/sdk/sdk-model-requirement-probe.js +120 -0
  646. package/dist/sdk/sdk-model-requirement-probe.js.map +1 -0
  647. package/dist/sdk/sdk-node-package-requirement-probe.d.ts +20 -0
  648. package/dist/sdk/sdk-node-package-requirement-probe.d.ts.map +1 -0
  649. package/dist/sdk/sdk-node-package-requirement-probe.js +44 -0
  650. package/dist/sdk/sdk-node-package-requirement-probe.js.map +1 -0
  651. package/dist/sdk/sdk-node-type-inventory-service.d.ts +11 -0
  652. package/dist/sdk/sdk-node-type-inventory-service.d.ts.map +1 -0
  653. package/dist/sdk/sdk-node-type-inventory-service.js +36 -0
  654. package/dist/sdk/sdk-node-type-inventory-service.js.map +1 -0
  655. package/dist/sdk/sdk-preflight-orchestrator.d.ts +54 -0
  656. package/dist/sdk/sdk-preflight-orchestrator.d.ts.map +1 -0
  657. package/dist/sdk/sdk-preflight-orchestrator.js +63 -0
  658. package/dist/sdk/sdk-preflight-orchestrator.js.map +1 -0
  659. package/dist/sdk/sdk-preflight-requirement-resolver.d.ts +35 -0
  660. package/dist/sdk/sdk-preflight-requirement-resolver.d.ts.map +1 -0
  661. package/dist/sdk/sdk-preflight-requirement-resolver.js +67 -0
  662. package/dist/sdk/sdk-preflight-requirement-resolver.js.map +1 -0
  663. package/dist/sdk/sdk-preflight-service.d.ts +61 -0
  664. package/dist/sdk/sdk-preflight-service.d.ts.map +1 -0
  665. package/dist/sdk/sdk-preflight-service.js +104 -0
  666. package/dist/sdk/sdk-preflight-service.js.map +1 -0
  667. package/dist/sdk/sdk-preflight-workflow-source.d.ts +18 -0
  668. package/dist/sdk/sdk-preflight-workflow-source.d.ts.map +1 -0
  669. package/dist/sdk/sdk-preflight-workflow-source.js +42 -0
  670. package/dist/sdk/sdk-preflight-workflow-source.js.map +1 -0
  671. package/dist/sdk/sdk-route-policy.d.ts +3 -0
  672. package/dist/sdk/sdk-route-policy.d.ts.map +1 -0
  673. package/dist/sdk/sdk-route-policy.js +18 -0
  674. package/dist/sdk/sdk-route-policy.js.map +1 -0
  675. package/dist/sdk/sdk-runtime-capabilities-service.d.ts +60 -0
  676. package/dist/sdk/sdk-runtime-capabilities-service.d.ts.map +1 -0
  677. package/dist/sdk/sdk-runtime-capabilities-service.js +30 -0
  678. package/dist/sdk/sdk-runtime-capabilities-service.js.map +1 -0
  679. package/dist/sdk/sdk-runtime-requirement-probe.d.ts +13 -0
  680. package/dist/sdk/sdk-runtime-requirement-probe.d.ts.map +1 -0
  681. package/dist/sdk/sdk-runtime-requirement-probe.js +55 -0
  682. package/dist/sdk/sdk-runtime-requirement-probe.js.map +1 -0
  683. package/dist/sdk/sdk-static-preflight-service.d.ts +66 -0
  684. package/dist/sdk/sdk-static-preflight-service.d.ts.map +1 -0
  685. package/dist/sdk/sdk-static-preflight-service.js +492 -0
  686. package/dist/sdk/sdk-static-preflight-service.js.map +1 -0
  687. package/dist/sdk/sdk-temporary-asset-service.d.ts +18 -0
  688. package/dist/sdk/sdk-temporary-asset-service.d.ts.map +1 -0
  689. package/dist/sdk/sdk-temporary-asset-service.js +33 -0
  690. package/dist/sdk/sdk-temporary-asset-service.js.map +1 -0
  691. package/dist/sdk/sdk-temporary-asset-upload-http-handler.d.ts +9 -0
  692. package/dist/sdk/sdk-temporary-asset-upload-http-handler.d.ts.map +1 -0
  693. package/dist/sdk/sdk-temporary-asset-upload-http-handler.js +65 -0
  694. package/dist/sdk/sdk-temporary-asset-upload-http-handler.js.map +1 -0
  695. package/dist/sdk/sdk-v1-handler-map.d.ts +44 -0
  696. package/dist/sdk/sdk-v1-handler-map.d.ts.map +1 -0
  697. package/dist/sdk/sdk-v1-handler-map.js +37 -0
  698. package/dist/sdk/sdk-v1-handler-map.js.map +1 -0
  699. package/dist/sdk/sdk-v1-service-error.d.ts +21 -0
  700. package/dist/sdk/sdk-v1-service-error.d.ts.map +1 -0
  701. package/dist/sdk/sdk-v1-service-error.js +62 -0
  702. package/dist/sdk/sdk-v1-service-error.js.map +1 -0
  703. package/dist/sdk/sdk-v1-service.d.ts +74 -0
  704. package/dist/sdk/sdk-v1-service.d.ts.map +1 -0
  705. package/dist/sdk/sdk-v1-service.js +219 -0
  706. package/dist/sdk/sdk-v1-service.js.map +1 -0
  707. package/dist/sdk/sdk-worker-readiness-adapter.d.ts +22 -0
  708. package/dist/sdk/sdk-worker-readiness-adapter.d.ts.map +1 -0
  709. package/dist/sdk/sdk-worker-readiness-adapter.js +29 -0
  710. package/dist/sdk/sdk-worker-readiness-adapter.js.map +1 -0
  711. package/dist/server.js +1091 -206
  712. package/dist/server.js.map +1 -1
  713. package/dist/session/app-run-fold.d.ts +7 -0
  714. package/dist/session/app-run-fold.d.ts.map +1 -0
  715. package/dist/session/app-run-fold.js +45 -0
  716. package/dist/session/app-run-fold.js.map +1 -0
  717. package/dist/session/asset-autosave.d.ts +80 -0
  718. package/dist/session/asset-autosave.d.ts.map +1 -0
  719. package/dist/session/asset-autosave.js +456 -0
  720. package/dist/session/asset-autosave.js.map +1 -0
  721. package/dist/session/chat-compaction.d.ts +107 -0
  722. package/dist/session/chat-compaction.d.ts.map +1 -0
  723. package/dist/session/chat-compaction.js +191 -0
  724. package/dist/session/chat-compaction.js.map +1 -0
  725. package/dist/session/chat-history.d.ts +75 -0
  726. package/dist/session/chat-history.d.ts.map +1 -0
  727. package/dist/session/chat-history.js +194 -0
  728. package/dist/session/chat-history.js.map +1 -0
  729. package/dist/session/chat-prompt.d.ts +45 -0
  730. package/dist/session/chat-prompt.d.ts.map +1 -0
  731. package/dist/session/chat-prompt.js +502 -0
  732. package/dist/session/chat-prompt.js.map +1 -0
  733. package/dist/session/chat-turn.d.ts +337 -0
  734. package/dist/session/chat-turn.d.ts.map +1 -0
  735. package/dist/session/chat-turn.js +3612 -0
  736. package/dist/session/chat-turn.js.map +1 -0
  737. package/dist/session/client-session.d.ts +59 -0
  738. package/dist/session/client-session.d.ts.map +1 -0
  739. package/dist/session/client-session.js +2 -0
  740. package/dist/session/client-session.js.map +1 -0
  741. package/dist/session/commands.d.ts +133 -0
  742. package/dist/session/commands.d.ts.map +1 -0
  743. package/dist/session/commands.js +784 -0
  744. package/dist/session/commands.js.map +1 -0
  745. package/dist/session/inference.d.ts +218 -0
  746. package/dist/session/inference.d.ts.map +1 -0
  747. package/dist/session/inference.js +1143 -0
  748. package/dist/session/inference.js.map +1 -0
  749. package/dist/session/job-execution.d.ts +484 -0
  750. package/dist/session/job-execution.d.ts.map +1 -0
  751. package/dist/session/job-execution.js +2379 -0
  752. package/dist/session/job-execution.js.map +1 -0
  753. package/dist/session/media-generation.d.ts +94 -0
  754. package/dist/session/media-generation.d.ts.map +1 -0
  755. package/dist/session/media-generation.js +234 -0
  756. package/dist/session/media-generation.js.map +1 -0
  757. package/dist/session/model-interfaces.d.ts +28 -0
  758. package/dist/session/model-interfaces.d.ts.map +1 -0
  759. package/dist/session/model-interfaces.js +133 -0
  760. package/dist/session/model-interfaces.js.map +1 -0
  761. package/dist/session/sanitize.d.ts +7 -0
  762. package/dist/session/sanitize.d.ts.map +1 -0
  763. package/dist/session/sanitize.js +64 -0
  764. package/dist/session/sanitize.js.map +1 -0
  765. package/dist/settings-registry.d.ts +6 -15
  766. package/dist/settings-registry.d.ts.map +1 -1
  767. package/dist/settings-registry.js +6 -103
  768. package/dist/settings-registry.js.map +1 -1
  769. package/dist/storage-api.d.ts +20 -0
  770. package/dist/storage-api.d.ts.map +1 -1
  771. package/dist/storage-api.js +260 -77
  772. package/dist/storage-api.js.map +1 -1
  773. package/dist/storage-retention.d.ts +14 -0
  774. package/dist/storage-retention.d.ts.map +1 -0
  775. package/dist/storage-retention.js +95 -0
  776. package/dist/storage-retention.js.map +1 -0
  777. package/dist/system-stats.d.ts +52 -0
  778. package/dist/system-stats.d.ts.map +1 -0
  779. package/dist/system-stats.js +134 -0
  780. package/dist/system-stats.js.map +1 -0
  781. package/dist/test-ui-server.d.ts +46 -0
  782. package/dist/test-ui-server.d.ts.map +1 -1
  783. package/dist/test-ui-server.js +207 -42
  784. package/dist/test-ui-server.js.map +1 -1
  785. package/dist/timeline-generation-recovery.d.ts +4 -0
  786. package/dist/timeline-generation-recovery.d.ts.map +1 -0
  787. package/dist/timeline-generation-recovery.js +72 -0
  788. package/dist/timeline-generation-recovery.js.map +1 -0
  789. package/dist/triggers/boot.d.ts +73 -0
  790. package/dist/triggers/boot.d.ts.map +1 -0
  791. package/dist/triggers/boot.js +149 -0
  792. package/dist/triggers/boot.js.map +1 -0
  793. package/dist/triggers/dispatcher.d.ts +234 -0
  794. package/dist/triggers/dispatcher.d.ts.map +1 -0
  795. package/dist/triggers/dispatcher.js +525 -0
  796. package/dist/triggers/dispatcher.js.map +1 -0
  797. package/dist/triggers/file-watch.d.ts +90 -0
  798. package/dist/triggers/file-watch.d.ts.map +1 -0
  799. package/dist/triggers/file-watch.js +405 -0
  800. package/dist/triggers/file-watch.js.map +1 -0
  801. package/dist/triggers/registration-sync.d.ts +34 -0
  802. package/dist/triggers/registration-sync.d.ts.map +1 -0
  803. package/dist/triggers/registration-sync.js +162 -0
  804. package/dist/triggers/registration-sync.js.map +1 -0
  805. package/dist/triggers/schedule-timing.d.ts +52 -0
  806. package/dist/triggers/schedule-timing.d.ts.map +1 -0
  807. package/dist/triggers/schedule-timing.js +90 -0
  808. package/dist/triggers/schedule-timing.js.map +1 -0
  809. package/dist/triggers/scheduler.d.ts +55 -0
  810. package/dist/triggers/scheduler.d.ts.map +1 -0
  811. package/dist/triggers/scheduler.js +122 -0
  812. package/dist/triggers/scheduler.js.map +1 -0
  813. package/dist/triggers/settle.d.ts +66 -0
  814. package/dist/triggers/settle.d.ts.map +1 -0
  815. package/dist/triggers/settle.js +99 -0
  816. package/dist/triggers/settle.js.map +1 -0
  817. package/dist/triggers/stores.d.ts +38 -0
  818. package/dist/triggers/stores.d.ts.map +1 -0
  819. package/dist/triggers/stores.js +168 -0
  820. package/dist/triggers/stores.js.map +1 -0
  821. package/dist/triggers/webhook-route.d.ts +49 -0
  822. package/dist/triggers/webhook-route.d.ts.map +1 -0
  823. package/dist/triggers/webhook-route.js +148 -0
  824. package/dist/triggers/webhook-route.js.map +1 -0
  825. package/dist/trpc/context.d.ts +52 -5
  826. package/dist/trpc/context.d.ts.map +1 -1
  827. package/dist/trpc/context.js +4 -1
  828. package/dist/trpc/context.js.map +1 -1
  829. package/dist/trpc/error-formatter.d.ts.map +1 -1
  830. package/dist/trpc/error-formatter.js +23 -2
  831. package/dist/trpc/error-formatter.js.map +1 -1
  832. package/dist/trpc/error-logging.d.ts +24 -0
  833. package/dist/trpc/error-logging.d.ts.map +1 -0
  834. package/dist/trpc/error-logging.js +48 -0
  835. package/dist/trpc/error-logging.js.map +1 -0
  836. package/dist/trpc/index.d.ts +2 -2
  837. package/dist/trpc/index.d.ts.map +1 -1
  838. package/dist/trpc/index.js +0 -2
  839. package/dist/trpc/index.js.map +1 -1
  840. package/dist/trpc/middleware.d.ts +6 -3
  841. package/dist/trpc/middleware.d.ts.map +1 -1
  842. package/dist/trpc/router.d.ts +100 -2540
  843. package/dist/trpc/router.d.ts.map +1 -1
  844. package/dist/trpc/router.js +58 -6
  845. package/dist/trpc/router.js.map +1 -1
  846. package/dist/trpc/routers/agent-access.d.ts +149 -0
  847. package/dist/trpc/routers/agent-access.d.ts.map +1 -0
  848. package/dist/trpc/routers/agent-access.js +224 -0
  849. package/dist/trpc/routers/agent-access.js.map +1 -0
  850. package/dist/trpc/routers/app-instances.d.ts +2269 -0
  851. package/dist/trpc/routers/app-instances.d.ts.map +1 -0
  852. package/dist/trpc/routers/app-instances.js +52 -0
  853. package/dist/trpc/routers/app-instances.js.map +1 -0
  854. package/dist/trpc/routers/app-runs.d.ts +1092 -0
  855. package/dist/trpc/routers/app-runs.d.ts.map +1 -0
  856. package/dist/trpc/routers/app-runs.js +28 -0
  857. package/dist/trpc/routers/app-runs.js.map +1 -0
  858. package/dist/trpc/routers/applications.d.ts +2212 -0
  859. package/dist/trpc/routers/applications.d.ts.map +1 -0
  860. package/dist/trpc/routers/applications.js +221 -0
  861. package/dist/trpc/routers/applications.js.map +1 -0
  862. package/dist/trpc/routers/assets.d.ts +194 -42
  863. package/dist/trpc/routers/assets.d.ts.map +1 -1
  864. package/dist/trpc/routers/assets.js +503 -105
  865. package/dist/trpc/routers/assets.js.map +1 -1
  866. package/dist/trpc/routers/bug-reports.d.ts +33 -0
  867. package/dist/trpc/routers/bug-reports.d.ts.map +1 -0
  868. package/dist/trpc/routers/bug-reports.js +69 -0
  869. package/dist/trpc/routers/bug-reports.js.map +1 -0
  870. package/dist/trpc/routers/code-gen.d.ts +99 -0
  871. package/dist/trpc/routers/code-gen.d.ts.map +1 -0
  872. package/dist/trpc/routers/code-gen.js +164 -0
  873. package/dist/trpc/routers/code-gen.js.map +1 -0
  874. package/dist/trpc/routers/collections.d.ts +1 -26
  875. package/dist/trpc/routers/collections.d.ts.map +1 -1
  876. package/dist/trpc/routers/collections.js +145 -78
  877. package/dist/trpc/routers/collections.js.map +1 -1
  878. package/dist/trpc/routers/costs.d.ts +40 -96
  879. package/dist/trpc/routers/costs.d.ts.map +1 -1
  880. package/dist/trpc/routers/costs.js +14 -89
  881. package/dist/trpc/routers/costs.js.map +1 -1
  882. package/dist/trpc/routers/credits.d.ts +110 -0
  883. package/dist/trpc/routers/credits.d.ts.map +1 -0
  884. package/dist/trpc/routers/credits.js +69 -0
  885. package/dist/trpc/routers/credits.js.map +1 -0
  886. package/dist/trpc/routers/custom-providers.d.ts +88 -0
  887. package/dist/trpc/routers/custom-providers.d.ts.map +1 -0
  888. package/dist/trpc/routers/custom-providers.js +140 -0
  889. package/dist/trpc/routers/custom-providers.js.map +1 -0
  890. package/dist/trpc/routers/documents.d.ts +61 -0
  891. package/dist/trpc/routers/documents.d.ts.map +1 -0
  892. package/dist/trpc/routers/documents.js +29 -0
  893. package/dist/trpc/routers/documents.js.map +1 -0
  894. package/dist/trpc/routers/error-traces.d.ts +132 -0
  895. package/dist/trpc/routers/error-traces.d.ts.map +1 -0
  896. package/dist/trpc/routers/error-traces.js +118 -0
  897. package/dist/trpc/routers/error-traces.js.map +1 -0
  898. package/dist/trpc/routers/extension.d.ts +24 -0
  899. package/dist/trpc/routers/extension.d.ts.map +1 -0
  900. package/dist/trpc/routers/extension.js +28 -0
  901. package/dist/trpc/routers/extension.js.map +1 -0
  902. package/dist/trpc/routers/external-mcp.d.ts +108 -0
  903. package/dist/trpc/routers/external-mcp.d.ts.map +1 -0
  904. package/dist/trpc/routers/external-mcp.js +70 -0
  905. package/dist/trpc/routers/external-mcp.js.map +1 -0
  906. package/dist/trpc/routers/files.d.ts +9 -2
  907. package/dist/trpc/routers/files.d.ts.map +1 -1
  908. package/dist/trpc/routers/files.js +67 -37
  909. package/dist/trpc/routers/files.js.map +1 -1
  910. package/dist/trpc/routers/fonts.d.ts +30 -0
  911. package/dist/trpc/routers/fonts.d.ts.map +1 -0
  912. package/dist/trpc/routers/fonts.js +112 -0
  913. package/dist/trpc/routers/fonts.js.map +1 -0
  914. package/dist/trpc/routers/gameDraftHistory.d.ts +8 -0
  915. package/dist/trpc/routers/gameDraftHistory.d.ts.map +1 -0
  916. package/dist/trpc/routers/gameDraftHistory.js +40 -0
  917. package/dist/trpc/routers/gameDraftHistory.js.map +1 -0
  918. package/dist/trpc/routers/games.d.ts +26502 -0
  919. package/dist/trpc/routers/games.d.ts.map +1 -0
  920. package/dist/trpc/routers/games.js +674 -0
  921. package/dist/trpc/routers/games.js.map +1 -0
  922. package/dist/trpc/routers/integrations.d.ts +86 -0
  923. package/dist/trpc/routers/integrations.d.ts.map +1 -0
  924. package/dist/trpc/routers/integrations.js +129 -0
  925. package/dist/trpc/routers/integrations.js.map +1 -0
  926. package/dist/trpc/routers/jobs.d.ts +117 -21
  927. package/dist/trpc/routers/jobs.d.ts.map +1 -1
  928. package/dist/trpc/routers/jobs.js +191 -30
  929. package/dist/trpc/routers/jobs.js.map +1 -1
  930. package/dist/trpc/routers/js-scripts.d.ts +410 -0
  931. package/dist/trpc/routers/js-scripts.d.ts.map +1 -0
  932. package/dist/trpc/routers/js-scripts.js +303 -0
  933. package/dist/trpc/routers/js-scripts.js.map +1 -0
  934. package/dist/trpc/routers/mcp-config.d.ts +4 -2
  935. package/dist/trpc/routers/mcp-config.d.ts.map +1 -1
  936. package/dist/trpc/routers/mcp-config.js +46 -206
  937. package/dist/trpc/routers/mcp-config.js.map +1 -1
  938. package/dist/trpc/routers/memories.d.ts +79 -0
  939. package/dist/trpc/routers/memories.d.ts.map +1 -0
  940. package/dist/trpc/routers/memories.js +66 -0
  941. package/dist/trpc/routers/memories.js.map +1 -0
  942. package/dist/trpc/routers/messages.d.ts +4 -77
  943. package/dist/trpc/routers/messages.d.ts.map +1 -1
  944. package/dist/trpc/routers/messages.js +9 -58
  945. package/dist/trpc/routers/messages.js.map +1 -1
  946. package/dist/trpc/routers/models.d.ts +1544 -249
  947. package/dist/trpc/routers/models.d.ts.map +1 -1
  948. package/dist/trpc/routers/models.js +624 -318
  949. package/dist/trpc/routers/models.js.map +1 -1
  950. package/dist/trpc/routers/nodes.d.ts +32 -16
  951. package/dist/trpc/routers/nodes.d.ts.map +1 -1
  952. package/dist/trpc/routers/nodes.js +70 -31
  953. package/dist/trpc/routers/nodes.js.map +1 -1
  954. package/dist/trpc/routers/packs.d.ts +184 -0
  955. package/dist/trpc/routers/packs.d.ts.map +1 -0
  956. package/dist/trpc/routers/packs.js +259 -0
  957. package/dist/trpc/routers/packs.js.map +1 -0
  958. package/dist/trpc/routers/projects.d.ts +422 -0
  959. package/dist/trpc/routers/projects.d.ts.map +1 -0
  960. package/dist/trpc/routers/projects.js +373 -0
  961. package/dist/trpc/routers/projects.js.map +1 -0
  962. package/dist/trpc/routers/resources.d.ts +120 -0
  963. package/dist/trpc/routers/resources.d.ts.map +1 -0
  964. package/dist/trpc/routers/resources.js +212 -0
  965. package/dist/trpc/routers/resources.js.map +1 -0
  966. package/dist/trpc/routers/runs.d.ts +472 -0
  967. package/dist/trpc/routers/runs.d.ts.map +1 -0
  968. package/dist/trpc/routers/runs.js +40 -0
  969. package/dist/trpc/routers/runs.js.map +1 -0
  970. package/dist/trpc/routers/scripts.d.ts +1116 -0
  971. package/dist/trpc/routers/scripts.d.ts.map +1 -0
  972. package/dist/trpc/routers/scripts.js +131 -0
  973. package/dist/trpc/routers/scripts.js.map +1 -0
  974. package/dist/trpc/routers/segmentation.d.ts +62 -0
  975. package/dist/trpc/routers/segmentation.d.ts.map +1 -0
  976. package/dist/trpc/routers/segmentation.js +146 -0
  977. package/dist/trpc/routers/segmentation.js.map +1 -0
  978. package/dist/trpc/routers/settings.d.ts +180 -10
  979. package/dist/trpc/routers/settings.d.ts.map +1 -1
  980. package/dist/trpc/routers/settings.js +125 -33
  981. package/dist/trpc/routers/settings.js.map +1 -1
  982. package/dist/trpc/routers/sketch.d.ts +868 -0
  983. package/dist/trpc/routers/sketch.d.ts.map +1 -0
  984. package/dist/trpc/routers/sketch.js +617 -0
  985. package/dist/trpc/routers/sketch.js.map +1 -0
  986. package/dist/trpc/routers/skills.d.ts +76 -22
  987. package/dist/trpc/routers/skills.d.ts.map +1 -1
  988. package/dist/trpc/routers/skills.js +195 -170
  989. package/dist/trpc/routers/skills.js.map +1 -1
  990. package/dist/trpc/routers/storage.d.ts +3 -30
  991. package/dist/trpc/routers/storage.d.ts.map +1 -1
  992. package/dist/trpc/routers/storage.js +60 -117
  993. package/dist/trpc/routers/storage.js.map +1 -1
  994. package/dist/trpc/routers/storyboards.d.ts +6639 -0
  995. package/dist/trpc/routers/storyboards.d.ts.map +1 -0
  996. package/dist/trpc/routers/storyboards.js +156 -0
  997. package/dist/trpc/routers/storyboards.js.map +1 -0
  998. package/dist/trpc/routers/threads.d.ts +33 -21
  999. package/dist/trpc/routers/threads.d.ts.map +1 -1
  1000. package/dist/trpc/routers/threads.js +36 -30
  1001. package/dist/trpc/routers/threads.js.map +1 -1
  1002. package/dist/trpc/routers/timeline.d.ts +9085 -0
  1003. package/dist/trpc/routers/timeline.d.ts.map +1 -0
  1004. package/dist/trpc/routers/timeline.js +669 -0
  1005. package/dist/trpc/routers/timeline.js.map +1 -0
  1006. package/dist/trpc/routers/triggers.d.ts +46 -0
  1007. package/dist/trpc/routers/triggers.d.ts.map +1 -0
  1008. package/dist/trpc/routers/triggers.js +117 -0
  1009. package/dist/trpc/routers/triggers.js.map +1 -0
  1010. package/dist/trpc/routers/users.d.ts +1 -14
  1011. package/dist/trpc/routers/users.d.ts.map +1 -1
  1012. package/dist/trpc/routers/users.js +1 -12
  1013. package/dist/trpc/routers/users.js.map +1 -1
  1014. package/dist/trpc/routers/worker.d.ts +243 -0
  1015. package/dist/trpc/routers/worker.d.ts.map +1 -0
  1016. package/dist/trpc/routers/worker.js +221 -0
  1017. package/dist/trpc/routers/worker.js.map +1 -0
  1018. package/dist/trpc/routers/workflows.d.ts +428 -187
  1019. package/dist/trpc/routers/workflows.d.ts.map +1 -1
  1020. package/dist/trpc/routers/workflows.js +347 -345
  1021. package/dist/trpc/routers/workflows.js.map +1 -1
  1022. package/dist/trpc/routers/workspace.d.ts +59 -33
  1023. package/dist/trpc/routers/workspace.d.ts.map +1 -1
  1024. package/dist/trpc/routers/workspace.js +230 -93
  1025. package/dist/trpc/routers/workspace.js.map +1 -1
  1026. package/dist/trpc/sandbox-coverage.d.ts +64 -0
  1027. package/dist/trpc/sandbox-coverage.d.ts.map +1 -0
  1028. package/dist/trpc/sandbox-coverage.js +1139 -0
  1029. package/dist/trpc/sandbox-coverage.js.map +1 -0
  1030. package/dist/websocket-client-session.d.ts +322 -0
  1031. package/dist/websocket-client-session.d.ts.map +1 -0
  1032. package/dist/websocket-client-session.js +1267 -0
  1033. package/dist/websocket-client-session.js.map +1 -0
  1034. package/dist/workflow-interface-service.d.ts +87 -0
  1035. package/dist/workflow-interface-service.d.ts.map +1 -0
  1036. package/dist/workflow-interface-service.js +131 -0
  1037. package/dist/workflow-interface-service.js.map +1 -0
  1038. package/dist/workspace-api.d.ts.map +1 -1
  1039. package/dist/workspace-api.js +33 -23
  1040. package/dist/workspace-api.js.map +1 -1
  1041. package/dist/ws-adapter.d.ts +46 -3
  1042. package/dist/ws-adapter.d.ts.map +1 -1
  1043. package/dist/ws-adapter.js +164 -26
  1044. package/dist/ws-adapter.js.map +1 -1
  1045. package/package.json +97 -36
  1046. package/dist/agent/agent-runtime.d.ts +0 -47
  1047. package/dist/agent/agent-runtime.d.ts.map +0 -1
  1048. package/dist/agent/agent-runtime.js +0 -579
  1049. package/dist/agent/agent-runtime.js.map +0 -1
  1050. package/dist/agent/codex-agent.d.ts +0 -41
  1051. package/dist/agent/codex-agent.d.ts.map +0 -1
  1052. package/dist/agent/codex-agent.js +0 -334
  1053. package/dist/agent/codex-agent.js.map +0 -1
  1054. package/dist/agent/index.d.ts +0 -6
  1055. package/dist/agent/index.d.ts.map +0 -1
  1056. package/dist/agent/index.js +0 -4
  1057. package/dist/agent/index.js.map +0 -1
  1058. package/dist/agent/llm-agent.d.ts +0 -51
  1059. package/dist/agent/llm-agent.d.ts.map +0 -1
  1060. package/dist/agent/llm-agent.js +0 -526
  1061. package/dist/agent/llm-agent.js.map +0 -1
  1062. package/dist/agent/mcp-tool-server.d.ts +0 -33
  1063. package/dist/agent/mcp-tool-server.d.ts.map +0 -1
  1064. package/dist/agent/mcp-tool-server.js +0 -292
  1065. package/dist/agent/mcp-tool-server.js.map +0 -1
  1066. package/dist/agent/opencode-agent.d.ts +0 -32
  1067. package/dist/agent/opencode-agent.d.ts.map +0 -1
  1068. package/dist/agent/opencode-agent.js +0 -332
  1069. package/dist/agent/opencode-agent.js.map +0 -1
  1070. package/dist/agent/pi-agent.d.ts +0 -42
  1071. package/dist/agent/pi-agent.d.ts.map +0 -1
  1072. package/dist/agent/pi-agent.js +0 -429
  1073. package/dist/agent/pi-agent.js.map +0 -1
  1074. package/dist/agent/sdk-provider.d.ts +0 -46
  1075. package/dist/agent/sdk-provider.d.ts.map +0 -1
  1076. package/dist/agent/sdk-provider.js +0 -50
  1077. package/dist/agent/sdk-provider.js.map +0 -1
  1078. package/dist/agent/socket-route.d.ts +0 -11
  1079. package/dist/agent/socket-route.d.ts.map +0 -1
  1080. package/dist/agent/socket-route.js +0 -262
  1081. package/dist/agent/socket-route.js.map +0 -1
  1082. package/dist/agent/transport.d.ts +0 -39
  1083. package/dist/agent/transport.d.ts.map +0 -1
  1084. package/dist/agent/transport.js +0 -17
  1085. package/dist/agent/transport.js.map +0 -1
  1086. package/dist/agent/types.d.ts +0 -11
  1087. package/dist/agent/types.d.ts.map +0 -1
  1088. package/dist/agent/types.js +0 -10
  1089. package/dist/agent/types.js.map +0 -1
  1090. package/dist/routes/jobs.d.ts +0 -16
  1091. package/dist/routes/jobs.d.ts.map +0 -1
  1092. package/dist/routes/jobs.js +0 -25
  1093. package/dist/routes/jobs.js.map +0 -1
  1094. package/dist/trpc/routers/sandboxes.d.ts +0 -64
  1095. package/dist/trpc/routers/sandboxes.d.ts.map +0 -1
  1096. package/dist/trpc/routers/sandboxes.js +0 -327
  1097. package/dist/trpc/routers/sandboxes.js.map +0 -1
  1098. package/dist/unified-websocket-runner.d.ts +0 -280
  1099. package/dist/unified-websocket-runner.d.ts.map +0 -1
  1100. package/dist/unified-websocket-runner.js +0 -3840
  1101. package/dist/unified-websocket-runner.js.map +0 -1
@@ -0,0 +1,2379 @@
1
+ import { ROOT_CONTEXT, propagation, trace, isSpanContextValid } from "@opentelemetry/api";
2
+ import { getRegisteredRunTrace } from "@nodetool-ai/models";
3
+ import { ensureRunTraceTelemetry, openAppRunTrace, registerWorkflowRunTrace } from "@nodetool-ai/execution";
4
+ /**
5
+ * Everything between "run_job arrived" and "terminal status persisted": the
6
+ * admission gates, the concurrency queue, run start-up, the message stream,
7
+ * reconnect, cancel, and terminal-status persistence.
8
+ *
9
+ * Extracted from `WebSocketClientSession` (T3 of
10
+ * docs/websocket-runner-refactor-plan.md). It knows the connection only
11
+ * through {@link ClientSession}; what the seam does not carry arrives as
12
+ * {@link JobExecutionDeps}.
13
+ */
14
+ import { randomUUID } from "node:crypto";
15
+ import { operationTarget, parseInputStateKey } from "@nodetool-ai/app-runtime";
16
+ import { ApiErrorCode } from "../error-codes.js";
17
+ import { captureError } from "../error-traces.js";
18
+ import { admitSpend, releaseSpend, reserveSpend } from "../credit-gate.js";
19
+ import { JobConcurrencyQueue } from "../job-queue.js";
20
+ import { isBoolean, isFiniteNumber, isFunctionValue, isNonEmptyString, isNumber, isObjectLike, isRecord, isString } from "../lib/wire-values.js";
21
+ import { Graph, withExplicitNodeFlags } from "@nodetool-ai/kernel";
22
+ import { ExecutionSession, isExecutionPreflightError, isUnitBilledCapability, priceGeneration, toRawGraphInput } from "@nodetool-ai/execution";
23
+ import { persistableOutputs, withoutInlineAssetBytes } from "@nodetool-ai/execution/service";
24
+ import { createRunSupervisor } from "../run-supervisor.js";
25
+ import { jobRunRegistry } from "../job-run-registry.js";
26
+ import { Application, AppRunError, claimAppRun, ensureDefaultAppInstance, findAppRunByInvocation, getAppInstance, getAppRun, Asset, Job, listApplicationVersions, invocationIdInUse, releasedApplicationRelease, releasedApplicationVersion, reconcileAppRunCost, reserveInvocation, reserveAppRun, setAppRunInputs, settleAppRun, updateAppRunEstimate, settleInvocation, Workflow } from "@nodetool-ai/models";
27
+ import { getInstanceId } from "../lib/instance-id.js";
28
+ import { requestRemoteJobCancel } from "../job-control.js";
29
+ import { estimateWorkflowCost, nodeExpectedQuantity } from "@nodetool-ai/node-sdk/cost-estimate";
30
+ import { extractPricingParams } from "@nodetool-ai/node-sdk/pricing-params";
31
+ import { getModelUnitPrice } from "@nodetool-ai/model-pricing";
32
+ import { applicationReleaseResponse } from "@nodetool-ai/protocol/api-schemas/applications.js";
33
+ import { appRunSnapshot } from "@nodetool-ai/protocol/api-schemas/app-runs.js";
34
+ import { confineRunRequest, isRunRefusal } from "../lib/app-session-scope.js";
35
+ import { releaseBlockedReason } from "../lib/app-deployment-service.js";
36
+ import { autoSaveAssets, primaryTextOutputName } from "./asset-autosave.js";
37
+ import { createRuntimeContext } from "./model-interfaces.js";
38
+ import { createAppRunFold } from "./app-run-fold.js";
39
+ import { formatSanitizedError, sanitizeLargeText } from "./sanitize.js";
40
+ import { createLogger } from "@nodetool-ai/config";
41
+ const log = createLogger("nodetool.websocket.runner");
42
+ const TERMINAL_JOB_STATUSES = ["completed", "failed", "cancelled", "error"];
43
+ export const DEFAULT_RUN_JOB_EXECUTION_OPTIONS = Object.freeze({
44
+ persistence: "job",
45
+ eventDetail: "full",
46
+ assetPersistence: "auto"
47
+ });
48
+ export function resolveRunJobExecutionOptions(value, sdkDefaults = false) {
49
+ return {
50
+ persistence: value?.persistence === "session" ? "session" : "job",
51
+ eventDetail: value?.event_detail === "outputs" || value?.event_detail === "terminal"
52
+ ? value.event_detail
53
+ : "full",
54
+ assetPersistence: value?.asset_persistence === "temporary" ||
55
+ (value?.asset_persistence == null && sdkDefaults)
56
+ ? "temporary"
57
+ : "auto"
58
+ };
59
+ }
60
+ export function resolveRunJobUserId(requestUserId, connectionUserId) {
61
+ return requestUserId?.trim() || connectionUserId;
62
+ }
63
+ export function createRelayActivityWaiter(context, executionSettled, abortSignal) {
64
+ let pending = context.hasMessages();
65
+ let resolveWaiter = null;
66
+ let disposed = false;
67
+ const notify = () => {
68
+ pending = true;
69
+ const resolve = resolveWaiter;
70
+ resolveWaiter = null;
71
+ resolve?.();
72
+ };
73
+ const removeMessageListener = context.addMessageListener(notify);
74
+ const dispose = () => {
75
+ if (disposed)
76
+ return;
77
+ disposed = true;
78
+ removeMessageListener();
79
+ abortSignal?.removeEventListener("abort", onAbort);
80
+ };
81
+ const settle = () => {
82
+ notify();
83
+ dispose();
84
+ };
85
+ const onAbort = () => settle();
86
+ if (abortSignal?.aborted) {
87
+ settle();
88
+ }
89
+ else {
90
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
91
+ }
92
+ void executionSettled.then(settle, settle);
93
+ return async () => {
94
+ if (pending) {
95
+ pending = false;
96
+ return;
97
+ }
98
+ await new Promise((resolve) => {
99
+ resolveWaiter = resolve;
100
+ });
101
+ pending = false;
102
+ };
103
+ }
104
+ /** The shape `crypto.randomUUID()` produces, on either side of the wire. */
105
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
106
+ export class JobExecutionManager {
107
+ session;
108
+ deps;
109
+ constructor(session, deps) {
110
+ this.session = session;
111
+ this.deps = deps;
112
+ }
113
+ /**
114
+ * The runs in flight on this connection. Private since T8's D2: chat's runs
115
+ * and the suites that stage one go through the methods below, so every
116
+ * mutation of the connection's slot accounting happens in this file.
117
+ */
118
+ activeJobs = new Map();
119
+ /**
120
+ * Runs that arrived while {@link MAX_CONCURRENT_JOBS} runs were already in
121
+ * flight. They start automatically (FIFO) as active jobs finish.
122
+ */
123
+ jobQueue = new JobConcurrencyQueue();
124
+ dequeuedJobs = new Set();
125
+ /**
126
+ * Count of jobs that have passed the concurrency gate but haven't been added
127
+ * to {@link activeJobs} yet (startJob awaits graph hydration first). Counted
128
+ * toward the cap synchronously so two run_job commands arriving back-to-back
129
+ * can't both slip past `activeJobs.size` and exceed MAX_CONCURRENT_JOBS.
130
+ */
131
+ startingJobs = 0;
132
+ /**
133
+ * WS slot accounting, for leak accounting: after every run finishes both
134
+ * must be back to zero. Read by the reliability harness's ws-server driver,
135
+ * whose `cleanup-leaks` invariant can only assert what it can measure.
136
+ */
137
+ get slotCounters() {
138
+ return {
139
+ activeJobs: this.activeJobs.size,
140
+ startingJobs: this.startingJobs
141
+ };
142
+ }
143
+ /** Ids of the runs in flight on this connection. Read for diagnostics. */
144
+ activeJobIds() {
145
+ return [...this.activeJobs.keys()];
146
+ }
147
+ /**
148
+ * Register a run against this connection's slots. A chat turn that starts a
149
+ * workflow shares the connection's concurrency accounting, so its run is
150
+ * registered here rather than in a map of chat's own.
151
+ */
152
+ registerJob(jobId, active) {
153
+ this.activeJobs.set(jobId, active);
154
+ }
155
+ /** Drop a run without draining the queue — the caller's `finally` does that. */
156
+ dropJob(jobId) {
157
+ this.activeJobs.delete(jobId);
158
+ }
159
+ /** Drop a run and start whatever was queued behind it. */
160
+ releaseJob(jobId) {
161
+ this.activeJobs.delete(jobId);
162
+ this.drainQueue();
163
+ }
164
+ /** The run registered under `jobId`, if it is still in flight. */
165
+ getActiveJob(jobId) {
166
+ return this.activeJobs.get(jobId);
167
+ }
168
+ /** Whether a run is registered under `jobId`. */
169
+ hasActiveJob(jobId) {
170
+ return this.activeJobs.has(jobId);
171
+ }
172
+ /**
173
+ * Job ids of runs still executing on a previous connection's runner that
174
+ * THIS connection reattached to via `reconnect_job`. Client commands for
175
+ * them (`cancel_job`, `stop`, `stream_input`, `end_input_stream`,
176
+ * `update_node_properties`) are forwarded to the executing runner through
177
+ * the session's hooks.
178
+ *
179
+ * Ids, not session objects: holding the session would pin its frame buffer
180
+ * for this connection's whole life, defeating the registry's retention
181
+ * drop. Resolved through the registry at every use, so a dropped session
182
+ * simply resolves to null and its memory goes with it.
183
+ */
184
+ adoptedJobIds = new Set();
185
+ /**
186
+ * This connection's identity as a job-run delivery target. Separate object
187
+ * from the host's chat delivery target so a job session's `detach(target)`
188
+ * can never be confused with a chat session's — both guard on identity.
189
+ */
190
+ jobDeliveryTarget = {
191
+ deliver: (message) => this.deps.sendToSocket(message)
192
+ };
193
+ inferOutputType(value) {
194
+ if (value === null || value === undefined)
195
+ return "any";
196
+ if (isString(value))
197
+ return "str";
198
+ if (isNumber(value))
199
+ return Number.isInteger(value) ? "int" : "float";
200
+ if (isBoolean(value))
201
+ return "bool";
202
+ if (Array.isArray(value))
203
+ return "list";
204
+ if (isObjectLike(value))
205
+ return "dict";
206
+ return "any";
207
+ }
208
+ resolveOutputNodeForKey(active, outputKey) {
209
+ let fallback = null;
210
+ for (const raw of active.graph.nodes) {
211
+ const node = raw;
212
+ const id = isString(node.id) ? node.id : null;
213
+ if (!id)
214
+ continue;
215
+ const name = isString(node.name) ? node.name : id;
216
+ const type = isString(node.type) ? node.type : "";
217
+ if (name === outputKey || id === outputKey)
218
+ return { id, name };
219
+ if (type === "nodetool.output.Output" && !fallback)
220
+ fallback = { id, name };
221
+ }
222
+ return fallback;
223
+ }
224
+ async sendOutputUpdates(active, outputs) {
225
+ for (const [outputKey, values] of Object.entries(outputs)) {
226
+ const nodeRef = this.resolveOutputNodeForKey(active, outputKey) ?? {
227
+ id: outputKey,
228
+ name: outputKey
229
+ };
230
+ const seq = Array.isArray(values) ? values : [];
231
+ for (const rawValue of seq) {
232
+ const value = await active.context.normalizeOutputValue(rawValue);
233
+ await this.session.send({
234
+ type: "output_update",
235
+ node_id: nodeRef.id,
236
+ node_name: nodeRef.name,
237
+ output_name: "output",
238
+ value,
239
+ output_type: this.inferOutputType(value),
240
+ metadata: {},
241
+ workflow_id: active.workflowId,
242
+ job_id: active.jobId
243
+ });
244
+ }
245
+ }
246
+ }
247
+ async normalizeFinalOutputs(active, outputs) {
248
+ const normalized = {};
249
+ for (const [outputKey, values] of Object.entries(outputs)) {
250
+ normalized[outputKey] = [];
251
+ for (const value of Array.isArray(values) ? values : []) {
252
+ normalized[outputKey].push(await active.context.normalizeOutputValue(value));
253
+ }
254
+ }
255
+ return normalized;
256
+ }
257
+ /**
258
+ * The resilient session a frame's `job_id` belongs to: one this connection
259
+ * started, or one it adopted via `reconnect_job`.
260
+ */
261
+ resolveJobSession(jobId) {
262
+ if (!isNonEmptyString(jobId))
263
+ return null;
264
+ const active = this.activeJobs.get(jobId);
265
+ if (active?.runSession)
266
+ return active.runSession;
267
+ if (!this.adoptedJobIds.has(jobId))
268
+ return null;
269
+ // No user means no connect(), so this session can own no registered run.
270
+ // "Nothing running" is the honest answer; main looked the job up under a
271
+ // hardcoded "1", which could return another user's run.
272
+ const userId = this.session.userId;
273
+ if (userId === null)
274
+ return null;
275
+ return jobRunRegistry.get(userId, jobId);
276
+ }
277
+ /**
278
+ * Where a client command for `jobId` should act: this connection's own
279
+ * ExecutionSession, or — for a run this client reconnected to — the hooks
280
+ * of the session whose runner still owns it. Null when nothing is running.
281
+ */
282
+ resolveJobControl(jobId) {
283
+ const active = this.activeJobs.get(jobId);
284
+ if (active) {
285
+ const session = active.session;
286
+ return {
287
+ workflowId: active.workflowId,
288
+ hooks: {
289
+ cancel: () => session.cancel(),
290
+ pushInput: (input, value, handle) => session.pushInput(input, value, handle),
291
+ finishInputStream: (input, handle) => session.finishInputStream(input, handle),
292
+ updateNodeProperties: (nodeId, properties) => session.updateNodeProperties(nodeId, properties)
293
+ }
294
+ };
295
+ }
296
+ const lookupUserId = this.session.userId;
297
+ if (lookupUserId === null)
298
+ return null;
299
+ const registered = jobRunRegistry.get(lookupUserId, jobId);
300
+ if (registered && registered.status === "running") {
301
+ return { hooks: registered.hooks, workflowId: registered.workflowId };
302
+ }
303
+ return null;
304
+ }
305
+ /**
306
+ * Normalize a raw graph so that the kernel's NodeDescriptor contract is met.
307
+ * The web-UI / Python serialisation stores node properties under `data`;
308
+ * the kernel expects them under `properties`.
309
+ */
310
+ normalizeGraph(graph) {
311
+ const nodes = graph.nodes.map((n) => {
312
+ if (n.properties === undefined && n.data !== undefined) {
313
+ const { data, ...rest } = n;
314
+ return { ...rest, properties: data };
315
+ }
316
+ return n;
317
+ });
318
+ const edges = graph.edges.map((edge) => {
319
+ const rawEdgeType = edge.edge_type ?? edge.type;
320
+ const edge_type = rawEdgeType === "control" ? "control" : "data";
321
+ const { type, ...rest } = edge;
322
+ return { ...rest, edge_type };
323
+ });
324
+ return { nodes, edges };
325
+ }
326
+ async hydrateGraph(graph) {
327
+ const normalized = this.normalizeGraph(graph);
328
+ if (!this.session.resolveNodeType) {
329
+ // No registry resolver configured — behavior flags can only come from
330
+ // the saved graph itself; absent ones are explicitly defaulted off.
331
+ // `normalizeGraph` above moved a saved node's `data` to `properties`
332
+ // and settled `edge_type`; what a saved record still lacks is the
333
+ // declared string type of the four identity fields, so read them out
334
+ // rather than assert them.
335
+ const asGraphData = {
336
+ nodes: normalized.nodes.map((n) => ({
337
+ ...n,
338
+ id: String(n.id ?? ""),
339
+ type: String(n.type ?? "")
340
+ })),
341
+ edges: normalized.edges.map((e) => ({
342
+ ...e,
343
+ source: String(e.source ?? ""),
344
+ sourceHandle: String(e.sourceHandle ?? ""),
345
+ target: String(e.target ?? ""),
346
+ targetHandle: String(e.targetHandle ?? "")
347
+ }))
348
+ };
349
+ return withExplicitNodeFlags(asGraphData);
350
+ }
351
+ const hydrated = await Graph.loadFromDict(normalized, {
352
+ resolver: this.session.resolveNodeType
353
+ });
354
+ return {
355
+ nodes: [...hydrated.nodes],
356
+ edges: [...hydrated.edges]
357
+ };
358
+ }
359
+ getRawGraph(req) {
360
+ if (req.graph) {
361
+ return this.normalizeGraph(req.graph);
362
+ }
363
+ if (req.workflow_id) {
364
+ const userId = this.session.requireUserId();
365
+ const workflowId = req.workflow_id;
366
+ return (async () => {
367
+ const workflow = await Workflow.find(userId, workflowId);
368
+ if (!workflow)
369
+ throw new Error(`Workflow not found: ${workflowId}`);
370
+ return this.normalizeGraph(workflow.graph);
371
+ })();
372
+ }
373
+ throw new Error("workflow_id or graph is required");
374
+ }
375
+ /**
376
+ * Surface a clean terminal job_update when pre-run setup fails (typically
377
+ * because the Python bridge could not start). Without this the error would
378
+ * bubble up to handleCommand and be sent as a generic `invalid_command`
379
+ * envelope, which the UI does not associate with the job — the workflow
380
+ * appears to spin forever instead of failing.
381
+ */
382
+ async emitBeforeRunFailure(jobId, workflowId, err, persistJob) {
383
+ const errorMessage = err instanceof Error ? err.message : String(err);
384
+ const appRun = await findAppRunByInvocation(this.session.requireUserId(), jobId);
385
+ if (appRun) {
386
+ await settleAppRun(this.session.requireUserId(), appRun.id, {
387
+ status: "failed",
388
+ error: errorMessage
389
+ });
390
+ }
391
+ this.session.logError("beforeRunJob failed", err);
392
+ captureError(err, {
393
+ source: "job",
394
+ userId: this.session.userId,
395
+ context: { job_id: jobId, workflow_id: workflowId ?? "" }
396
+ });
397
+ await this.session.send({
398
+ type: "job_update",
399
+ status: "failed",
400
+ job_id: jobId,
401
+ workflow_id: workflowId,
402
+ error: errorMessage
403
+ });
404
+ if (!persistJob)
405
+ return;
406
+ try {
407
+ const job = (await Job.get(jobId));
408
+ if (job) {
409
+ job.markFailed(errorMessage);
410
+ await job.save();
411
+ }
412
+ }
413
+ catch (persistErr) {
414
+ this.session.logError("beforeRunJob failure persistence failed", persistErr);
415
+ }
416
+ }
417
+ /**
418
+ * Jobs occupying a concurrency slot: live + reserved-but-not-yet-registered.
419
+ *
420
+ * The cap counts a *user's* runs, not a socket's. A run detached from a
421
+ * dropped connection keeps executing, so counting only `activeJobs` would
422
+ * let a client reconnect its way past the cap — four abandoned sockets and
423
+ * a fresh one is eight concurrent runs. The registry is the cross-
424
+ * connection count; `activeJobs` contributes only the entries it does not
425
+ * already cover (a chat-triggered workflow run, which registers no
426
+ * session), or the same run would be counted twice.
427
+ */
428
+ get inFlightJobCount() {
429
+ let sessionless = 0;
430
+ for (const job of this.activeJobs.values()) {
431
+ if (!job.runSession)
432
+ sessionless += 1;
433
+ }
434
+ // A session with no user has no registered runs to count; the local
435
+ // counters are still the honest answer. Same reasoning as the registry
436
+ // lookups above.
437
+ const countUserId = this.session.userId;
438
+ return (this.startingJobs +
439
+ (countUserId === null ? 0 : jobRunRegistry.countRunning(countUserId)) +
440
+ sessionless);
441
+ }
442
+ /**
443
+ * Number of live (unfinished) runs currently executing for a workflow. Used
444
+ * to enforce the per-workflow concurrency limit so non-concurrent runs stay
445
+ * sequential and their live node updates don't clobber each other in the
446
+ * editor. Counted the same way as {@link inFlightJobCount}: the registry
447
+ * across connections, plus this connection's sessionless entries.
448
+ */
449
+ countActiveJobsForWorkflow(workflowId) {
450
+ if (!workflowId) {
451
+ return 0;
452
+ }
453
+ const workflowUserId = this.session.userId;
454
+ let count = workflowUserId === null
455
+ ? 0
456
+ : jobRunRegistry.countRunningForWorkflow(workflowUserId, workflowId);
457
+ for (const job of this.activeJobs.values()) {
458
+ if (!job.runSession && job.workflowId === workflowId && !job.finished) {
459
+ count++;
460
+ }
461
+ }
462
+ return count;
463
+ }
464
+ /**
465
+ * The per-workflow concurrency limit a given run is subject to: concurrency
466
+ * opt-in runs share the configurable {@link getMaxConcurrentRunsPerWorkflow}
467
+ * cap; everything else stays strictly sequential (one run per workflow) so
468
+ * live node updates don't clobber the editor.
469
+ */
470
+ perWorkflowLimitFor(req) {
471
+ return req.concurrent
472
+ ? this.deps.getMaxConcurrentRunsPerWorkflow()
473
+ : Promise.resolve(1);
474
+ }
475
+ /**
476
+ * Read-only view of the same admission counters used by runJob. This does
477
+ * not reserve a slot or mutate the queue; lifecycle preflight can therefore
478
+ * report likely queueing without changing current execution behavior.
479
+ */
480
+ async getSdkExecutionCapacitySnapshot(input) {
481
+ const [maxConcurrentJobs, maxConcurrentRunsForWorkflow] = await Promise.all([
482
+ this.deps.getMaxConcurrentJobs(),
483
+ this.perWorkflowLimitFor({ concurrent: input.concurrent })
484
+ ]);
485
+ const inFlightJobs = this.inFlightJobCount;
486
+ const workflowInFlightJobs = this.countActiveJobsForWorkflow(input.workflowId);
487
+ return {
488
+ inFlightJobs,
489
+ maxConcurrentJobs,
490
+ queuedJobs: this.jobQueue.size,
491
+ workflowInFlightJobs,
492
+ maxConcurrentRunsForWorkflow,
493
+ likelyQueued: inFlightJobs >= maxConcurrentJobs ||
494
+ workflowInFlightJobs >= maxConcurrentRunsForWorkflow
495
+ };
496
+ }
497
+ /**
498
+ * Best-effort pre-run cost estimate for a graph, in USD. Nodes the estimator
499
+ * cannot price contribute nothing, so the figure is a floor — good enough to
500
+ * stop a run that would obviously blow the budget, and never a reason to
501
+ * refuse one it cannot price.
502
+ */
503
+ estimateRunCost(req) {
504
+ try {
505
+ const estimate = this.estimateGraphCost(req);
506
+ if (!estimate)
507
+ return 0;
508
+ return Number.isFinite(estimate.total) ? estimate.total : 0;
509
+ }
510
+ catch (err) {
511
+ this.session.logError("run cost estimate failed", err);
512
+ return 0;
513
+ }
514
+ }
515
+ /**
516
+ * The graph estimate both gates read. One implementation so the budget gate,
517
+ * the credit gate and the editor's cost panel price a run the same way —
518
+ * including fan-out: a node asked for ten images is priced at ten, through
519
+ * the same `nodeExpectedQuantity` the panel uses.
520
+ */
521
+ estimateGraphCost(req) {
522
+ const nodes = req.graph?.nodes;
523
+ if (!nodes || !this.session.getNodeMetadata)
524
+ return null;
525
+ const priced = nodes.map((node) => ({
526
+ id: String(node.id),
527
+ type: String(node.type),
528
+ data: (node.data ?? {})
529
+ }));
530
+ return estimateWorkflowCost({
531
+ nodes: priced,
532
+ getMetadata: (nodeType) => this.session.getNodeMetadata?.(nodeType),
533
+ // Prices the model picked on a generic node (e.g. a FAL or kie model on
534
+ // nodetool.image.TextToImage), which node-type metadata alone cannot.
535
+ // Same lookup the editor's cost preview uses.
536
+ getModelPrice: getModelUnitPrice,
537
+ // A per-second model bills the clip it is asked for, so the duration
538
+ // and resolution the node states have to reach the price lookup.
539
+ getParams: (node) => extractPricingParams(node.data),
540
+ quantities: Object.fromEntries(priced.map((node) => [
541
+ node.id,
542
+ nodeExpectedQuantity(node.data)
543
+ ]))
544
+ });
545
+ }
546
+ /**
547
+ * The slice of a run that spends through NodeTool's managed provider —
548
+ * the only spend the credit balance meters. BYOK nodes are excluded on
549
+ * purpose: their cost rides the user's own keys.
550
+ */
551
+ estimateNodetoolSpend(req) {
552
+ try {
553
+ const estimate = this.estimateGraphCost(req);
554
+ if (!estimate) {
555
+ return { usesNodetool: false, estimatedUsd: 0, models: [] };
556
+ }
557
+ const items = estimate.items.filter((item) => item.provider === "nodetool");
558
+ const total = items.reduce((sum, item) => sum +
559
+ (Number.isFinite(item.estimated_cost) ? item.estimated_cost : 0), 0);
560
+ const models = [
561
+ ...new Set(items
562
+ .map((item) => item.model)
563
+ .filter((model) => Boolean(model)))
564
+ ];
565
+ return { usesNodetool: items.length > 0, estimatedUsd: total, models };
566
+ }
567
+ catch (err) {
568
+ this.session.logError("nodetool spend estimate failed", err);
569
+ return { usesNodetool: false, estimatedUsd: 0, models: [] };
570
+ }
571
+ }
572
+ /** Tell the client a run was refused, in the shape a failed job takes. */
573
+ refuseRun(req, jobId, code, error) {
574
+ this.session.sendDetached({
575
+ type: "job_update",
576
+ status: "failed",
577
+ job_id: jobId,
578
+ workflow_id: req.workflow_id ?? null,
579
+ error,
580
+ error_code: code
581
+ });
582
+ return false;
583
+ }
584
+ /**
585
+ * The version number a stale client claimed, if the app really has it —
586
+ * otherwise null, and the claim is unsupportable.
587
+ *
588
+ * Version history is per app and short (one row per publish), so listing it
589
+ * is cheap; the ceiling exists only because the model helper takes a limit,
590
+ * and it has to be high enough that an old claim is never truncated away by
591
+ * the newest releases.
592
+ */
593
+ async claimedApplicationVersion(applicationId, claimed, userId) {
594
+ if (claimed == null)
595
+ return null;
596
+ const versions = await listApplicationVersions(applicationId, 10_000, userId);
597
+ return versions.some((v) => v.version === claimed) ? claimed : null;
598
+ }
599
+ /**
600
+ * Gate an app's run on its spend budget. Runs of a published app execute with
601
+ * the creator's secrets, so this refuses before the job exists rather than
602
+ * reporting an overspend afterwards. Returns false when the run was refused
603
+ * (the client has already been told why).
604
+ *
605
+ * `application_id` arrives on the wire, so before any of that the app has to
606
+ * be one this connection's user owns. Honouring the id as sent let a client
607
+ * name a stranger's app and spend their budget — and pollute their release
608
+ * telemetry, which is the same ledger.
609
+ */
610
+ async admitApplicationRun(req) {
611
+ if (req.app_run_id || this.session.appSession) {
612
+ return this.admitDurableAppRun(req);
613
+ }
614
+ const applicationId = req.application_id;
615
+ if (!applicationId)
616
+ return true;
617
+ const jobId = req.job_id ?? randomUUID();
618
+ req.job_id = jobId;
619
+ // The connection's authenticated user, not `req.user_id`: the request body
620
+ // is the thing being authorized, so it cannot supply the identity that
621
+ // authorizes it.
622
+ const userId = this.session.requireUserId();
623
+ // Authorization sits outside the try below, which swallows a ledger outage
624
+ // on purpose. Metering fails open; ownership fails closed — a lookup this
625
+ // never completed is not permission to bill the app it names.
626
+ let owned = false;
627
+ try {
628
+ const application = await Application.findById(applicationId);
629
+ owned = application?.user_id === userId;
630
+ }
631
+ catch (err) {
632
+ this.session.logError("application ownership check failed", err);
633
+ }
634
+ if (!owned) {
635
+ log.warn("Run refused: application not owned by this user", {
636
+ applicationId,
637
+ jobId,
638
+ userId
639
+ });
640
+ // Applications are owned by one user and there is no path today that
641
+ // serves someone else's app to run — `releasedApplicationDocument`
642
+ // itself requires ownership — so refusing cannot break a legitimate
643
+ // run, and it is the only answer that keeps the budget a hard stop.
644
+ return this.refuseRun(req, jobId, ApiErrorCode.NOT_FOUND, "Application not found");
645
+ }
646
+ try {
647
+ const estimatedUsd = this.estimateRunCost(req);
648
+ // The client says whether this is a release run or a draft run; the
649
+ // server decides which release the ledger records. A number taken on
650
+ // faith would let a run bill itself to a version it never executed, and
651
+ // the ledger is also the release telemetry — so a claim is only honoured
652
+ // below once the server has found that version in the app's history.
653
+ const released = req.application_version == null
654
+ ? null
655
+ : await releasedApplicationVersion(applicationId, userId);
656
+ if (req.application_version != null && !released) {
657
+ // A release run of an app that has released nothing. The claim is
658
+ // unsupportable rather than merely stale, and letting it through would
659
+ // file the run in the telemetry ledger as a release that never shipped.
660
+ return this.refuseRun(req, jobId, ApiErrorCode.INVALID_INPUT, "This app has no released version to run");
661
+ }
662
+ // Which version the ledger row belongs to. The release is the default,
663
+ // because that is what a current client runs.
664
+ let version = released?.version ?? null;
665
+ if (released && released.version !== req.application_version) {
666
+ log.warn("Run claimed a version other than the released one", {
667
+ applicationId,
668
+ jobId,
669
+ claimed: req.application_version,
670
+ released: released.version
671
+ });
672
+ // A client that loaded the app before the newest release still holds
673
+ // that older snapshot and is about to execute it. Filing the run under
674
+ // the current release would credit v2's metrics and budget with a run
675
+ // of v1, so the row follows what actually ran — but only once the
676
+ // server has confirmed the claimed version is a real version of this
677
+ // app. That check is what keeps the client from picking its own
678
+ // attribution: it can name a version it once had, not one it invents.
679
+ const claimed = await this.claimedApplicationVersion(applicationId, req.application_version, userId);
680
+ if (!claimed) {
681
+ return this.refuseRun(req, jobId, ApiErrorCode.INVALID_INPUT, `This app has no version ${req.application_version} to run`);
682
+ }
683
+ version = claimed;
684
+ // The run proceeds, but the client is stale and has no other way to
685
+ // learn it: nothing in a job's updates mentions releases.
686
+ this.session.sendDetached({
687
+ type: "notification",
688
+ node_id: "",
689
+ severity: "warning",
690
+ workflow_id: req.workflow_id ?? null,
691
+ content: `Running version ${claimed} of this app; version ${released.version} has since been released. Reload to get the latest.`
692
+ });
693
+ }
694
+ // Reserving claims the run against the budget in the same transaction
695
+ // that checks it, so concurrent runs of one app cannot each read a total
696
+ // that excludes the others and all be admitted.
697
+ // The ledger holds one row per (application, invocation), so two runs
698
+ // racing on one id lose the insert rather than double-spend. The loser
699
+ // is refused like any other rejected run instead of surfacing a driver
700
+ // error to the visitor.
701
+ let decision;
702
+ try {
703
+ decision = await reserveInvocation({
704
+ applicationId,
705
+ version,
706
+ invocationId: jobId,
707
+ operationId: req.operation_id ?? undefined,
708
+ estimatedUsd,
709
+ requireFiniteBudget: this.session.appSession !== null
710
+ });
711
+ }
712
+ catch (error) {
713
+ log.warn("Application run ledger rejected the reservation", {
714
+ applicationId,
715
+ jobId,
716
+ error: String(error)
717
+ });
718
+ return this.refuseRun(req, jobId, ApiErrorCode.INVALID_INPUT, "This app run named a run id that is already in use");
719
+ }
720
+ if (!decision.allowed) {
721
+ log.warn("Run refused by application budget", {
722
+ applicationId,
723
+ jobId,
724
+ reason: decision.reason
725
+ });
726
+ return this.refuseRun(req, jobId, ApiErrorCode.BUDGET_EXCEEDED, decision.reason);
727
+ }
728
+ }
729
+ catch (err) {
730
+ // A ledger that is unavailable must not take runs down with it; the
731
+ // refusals above are the only paths that block, and they return rather
732
+ // than throw so an outage can never swallow one.
733
+ this.session.logError("application budget check failed", err);
734
+ }
735
+ return true;
736
+ }
737
+ async admitDurableAppRun(req) {
738
+ const userId = this.session.requireUserId();
739
+ const jobId = req.job_id ?? randomUUID();
740
+ req.job_id = jobId;
741
+ let claimedRunId = null;
742
+ try {
743
+ if (this.session.appSession && req.application_id) {
744
+ const release = await releasedApplicationRelease(req.application_id, userId);
745
+ if (!release) {
746
+ throw new Error("Application release not found");
747
+ }
748
+ const instance = await ensureDefaultAppInstance({
749
+ userId,
750
+ applicationId: req.application_id,
751
+ sourceId: `public:${req.application_id}:${release.version}`,
752
+ version: release.version,
753
+ name: "Visitor runs",
754
+ snapshot: appRunSnapshot.parse({
755
+ document: release.document,
756
+ workflow_graphs: Object.fromEntries(release.workflows.flatMap((item) => item.graph ? [[item.workflowId, item.graph]] : [])),
757
+ script_documents: {}
758
+ })
759
+ });
760
+ const decision = await reserveAppRun({
761
+ userId,
762
+ instanceId: instance.id,
763
+ invocationId: jobId,
764
+ operationId: req.operation_id ?? "",
765
+ origin: "public",
766
+ estimatedUsd: this.estimateRunCost(req),
767
+ requireFiniteBudget: true
768
+ });
769
+ if (!decision.allowed) {
770
+ return this.refuseRun(req, jobId, ApiErrorCode.BUDGET_EXCEEDED, decision.reason);
771
+ }
772
+ req.app_run_id = decision.run.id;
773
+ req.instance_id = instance.id;
774
+ }
775
+ const run = req.app_run_id
776
+ ? await getAppRun(userId, req.app_run_id)
777
+ : null;
778
+ const instance = req.instance_id
779
+ ? await getAppInstance(userId, req.instance_id)
780
+ : null;
781
+ if (!run ||
782
+ !instance ||
783
+ run.instance_id !== instance.id ||
784
+ run.invocation_id !== jobId) {
785
+ return this.refuseRun(req, jobId, ApiErrorCode.NOT_FOUND, "App run not found");
786
+ }
787
+ if (run.status !== "running") {
788
+ return this.refuseRun(req, jobId, ApiErrorCode.ALREADY_EXISTS, "App run has already finished");
789
+ }
790
+ const operation = instance.snapshot.document.operations.find((item) => item.id === run.operation_id);
791
+ if (!operation ||
792
+ operationTarget(operation).kind !== "workflow" ||
793
+ (req.operation_id && req.operation_id !== run.operation_id)) {
794
+ return this.refuseRun(req, jobId, ApiErrorCode.INVALID_INPUT, "Operation does not match this run");
795
+ }
796
+ const target = operationTarget(operation);
797
+ if (target.kind !== "workflow" ||
798
+ !target.workflowId ||
799
+ (req.workflow_id && req.workflow_id !== target.workflowId)) {
800
+ return this.refuseRun(req, jobId, ApiErrorCode.INVALID_INPUT, "Workflow does not match this run");
801
+ }
802
+ if (run.origin !== "public") {
803
+ const frozen = run.snapshot?.workflow_graphs[target.workflowId];
804
+ if (!frozen) {
805
+ return this.refuseRun(req, jobId, ApiErrorCode.INVALID_INPUT, "Run has no frozen workflow");
806
+ }
807
+ const overlays = new Map();
808
+ const inputs = instance.variables["__app_inputs"];
809
+ for (const [key, value] of Object.entries(isRecord(inputs) ? inputs : {})) {
810
+ const binding = parseInputStateKey(key);
811
+ if (!binding?.property || binding.operationId !== run.operation_id) {
812
+ continue;
813
+ }
814
+ const properties = overlays.get(binding.nodeId) ?? {};
815
+ properties[binding.property] = value;
816
+ overlays.set(binding.nodeId, properties);
817
+ }
818
+ req.graph = {
819
+ ...frozen,
820
+ nodes: frozen.nodes.map((node) => {
821
+ const overlay = overlays.get(node.id);
822
+ if (!overlay) {
823
+ return node;
824
+ }
825
+ const properties = isRecord(node.properties)
826
+ ? node.properties
827
+ : isRecord(node.data)
828
+ ? node.data
829
+ : {};
830
+ return { ...node, properties: { ...properties, ...overlay } };
831
+ })
832
+ };
833
+ await updateAppRunEstimate(userId, run.id, this.estimateRunCost(req));
834
+ }
835
+ if (!(await claimAppRun(userId, run.id, getInstanceId()))) {
836
+ return this.refuseRun(req, jobId, ApiErrorCode.ALREADY_EXISTS, "App run execution already started");
837
+ }
838
+ claimedRunId = run.id;
839
+ await setAppRunInputs(userId, run.id, req.params ?? {});
840
+ req.app_run_id = run.id;
841
+ req.instance_id = instance.id;
842
+ req.user_id = userId;
843
+ req.workflow_id = target.workflowId;
844
+ req.application_id = run.application_id;
845
+ req.application_version = run.version;
846
+ req.operation_id = run.operation_id;
847
+ return true;
848
+ }
849
+ catch (error) {
850
+ if (claimedRunId) {
851
+ try {
852
+ await settleAppRun(userId, claimedRunId, {
853
+ status: "failed",
854
+ actualUsd: 0,
855
+ knownLlmUsd: 0,
856
+ error: error instanceof Error
857
+ ? error.message
858
+ : "App run admission failed"
859
+ });
860
+ }
861
+ catch (settlementError) {
862
+ if (!(settlementError instanceof AppRunError) ||
863
+ settlementError.code !== "not_found") {
864
+ this.session.logError("app run admission settlement failed", settlementError);
865
+ }
866
+ }
867
+ }
868
+ this.session.logError("app run admission failed", error);
869
+ const code = error instanceof AppRunError && error.code === "budget_exceeded"
870
+ ? ApiErrorCode.BUDGET_EXCEEDED
871
+ : ApiErrorCode.INVALID_INPUT;
872
+ return this.refuseRun(req, jobId, code, error instanceof Error ? error.message : "App run admission failed");
873
+ }
874
+ }
875
+ /**
876
+ * Gate a run on the user's credit balance — but only the part of the run
877
+ * that spends through NodeTool's managed provider. A graph with no
878
+ * `nodetool` models passes untouched (BYOK stays unmetered); one with them
879
+ * is refused when it names a model this server does not sell, or when the
880
+ * balance is empty or can't cover the estimate. Estimates are floors, so an
881
+ * empty balance refuses even a 0-estimate nodetool call. Fails open on gate
882
+ * errors, like the application-budget gate above.
883
+ */
884
+ async admitCreditRun(req) {
885
+ const { usesNodetool, estimatedUsd, models } = this.estimateNodetoolSpend(req);
886
+ if (!usesNodetool)
887
+ return true;
888
+ // Pin the job id now so the reservation taken here can be released at the
889
+ // run's terminal state (and on cancel-while-queued) under the same key.
890
+ req.job_id ??= randomUUID();
891
+ const decision = await admitSpend(this.session.requireUserId(), estimatedUsd, models);
892
+ if (!decision.allowed) {
893
+ return this.refuseRun(req, req.job_id, decision.refusal === "model_not_allowed"
894
+ ? ApiErrorCode.MODEL_NOT_AVAILABLE
895
+ : ApiErrorCode.BUDGET_EXCEEDED, decision.reason);
896
+ }
897
+ reserveSpend(this.session.requireUserId(), req.job_id, estimatedUsd);
898
+ return true;
899
+ }
900
+ /**
901
+ * Narrow a run started by a deployed app's visitor to the one run the
902
+ * session allows, or refuse it.
903
+ *
904
+ * The connection authenticates as the app's owner, so `admitApplicationRun`
905
+ * below would happily approve anything it names — ownership is what that
906
+ * check tests, and a visitor's session passes it. This is the check that
907
+ * actually confines them, and it runs first: what comes out is built from
908
+ * the signed session and the release, not from the request.
909
+ *
910
+ * The run executes only the release version the signed session names. A
911
+ * later publish invalidates the session for execution, so the visitor must
912
+ * reload and mint a session for the new release rather than run its graphs.
913
+ */
914
+ async confineAppSessionRun(req) {
915
+ const scope = this.session.appSession;
916
+ if (!scope)
917
+ return req;
918
+ // The client names the run so it can follow its own frames, but the id it
919
+ // names is also the ledger key a job command is authorized against. An id
920
+ // that is already a job or already in a ledger would let a visitor file a
921
+ // row under someone else's run and then command it, so a taken id — and a
922
+ // shape that is not a generated id at all — is refused rather than
923
+ // silently replaced with one the client would never recognize.
924
+ const requested = req.job_id ?? "";
925
+ const jobId = requested === "" ? randomUUID() : requested;
926
+ const deny = (code, error) => {
927
+ this.refuseRun(req, jobId, code, error);
928
+ return null;
929
+ };
930
+ if (requested !== "") {
931
+ if (!UUID_PATTERN.test(requested)) {
932
+ return deny(ApiErrorCode.INVALID_INPUT, "This app run named an invalid run id");
933
+ }
934
+ if ((await Job.get(requested)) !== null ||
935
+ (await invocationIdInUse(requested))) {
936
+ return deny(ApiErrorCode.INVALID_INPUT, "This app run named a run id that is already in use");
937
+ }
938
+ }
939
+ const release = await releasedApplicationRelease(scope.applicationId, this.session.requireUserId());
940
+ if (!release) {
941
+ return deny(ApiErrorCode.NOT_FOUND, "This app is not available");
942
+ }
943
+ // A document that no longer parses and one a public link may not serve get
944
+ // the same answer, so the visitor learns nothing about which it hit.
945
+ const parsedRelease = applicationReleaseResponse.safeParse(release);
946
+ if (!parsedRelease.success || releaseBlockedReason(parsedRelease.data)) {
947
+ return deny(ApiErrorCode.NOT_FOUND, "This app is not available");
948
+ }
949
+ if (release.version !== scope.version) {
950
+ return deny(ApiErrorCode.INVALID_INPUT, "This app has been updated. Reload the page before running it.");
951
+ }
952
+ const confined = confineRunRequest(req, scope, release);
953
+ if (isRunRefusal(confined)) {
954
+ return deny(ApiErrorCode.INVALID_INPUT, confined.refused);
955
+ }
956
+ confined.job_id = jobId;
957
+ return confined;
958
+ }
959
+ /**
960
+ * Entry point for the "run_job" command. Starts the run immediately when the
961
+ * client is under its concurrency cap, otherwise queues it (FIFO) and emits a
962
+ * `queued` job update. Queued runs start automatically as active jobs finish.
963
+ */
964
+ async runJob(incoming) {
965
+ const req = await this.confineAppSessionRun(incoming);
966
+ if (!req)
967
+ return;
968
+ req._accepted_at_ms ??= performance.now();
969
+ if (!(await this.admitApplicationRun(req)))
970
+ return;
971
+ if (!(await this.admitCreditRun(req))) {
972
+ if (req.app_run_id) {
973
+ await settleAppRun(this.session.requireUserId(), req.app_run_id, {
974
+ status: "failed",
975
+ error: "Run refused by spending limit"
976
+ });
977
+ }
978
+ return;
979
+ }
980
+ const max = await this.deps.getMaxConcurrentJobs();
981
+ const perWorkflowMax = await this.perWorkflowLimitFor(req);
982
+ // Queue the run when over the global cap, or when this workflow already has
983
+ // its per-workflow limit of runs in flight — 1 for normal runs, or the
984
+ // configurable MAX_CONCURRENT_RUNS_PER_WORKFLOW for runs that opt into
985
+ // concurrency. Reserve the slot synchronously (after the awaits above) so
986
+ // two run_job commands can't both observe a free slot before either registers.
987
+ if (this.inFlightJobCount >= max ||
988
+ this.countActiveJobsForWorkflow(req.workflow_id) >= perWorkflowMax) {
989
+ await this.enqueueJob(req);
990
+ return;
991
+ }
992
+ this.startingJobs++;
993
+ await this.startJob(req);
994
+ }
995
+ /** Queue a run that can't start yet, persist it, and notify the client. */
996
+ async enqueueJob(req) {
997
+ const jobId = req.job_id ?? randomUUID();
998
+ req.job_id = jobId;
999
+ const position = this.jobQueue.enqueue(req);
1000
+ log.info("Job queued", { jobId, position });
1001
+ // Persist the queued run so it shows in jobs.list (Queue panel, reload,
1002
+ // other tabs). Best-effort, mirroring startJobInner's persistence. It flips
1003
+ // to "running" in startJobInner when a slot frees.
1004
+ if (resolveRunJobExecutionOptions(req.execution_options, req.require_terminal_result === true).persistence === "job") {
1005
+ try {
1006
+ const existing = await Job.get(jobId);
1007
+ if (!existing) {
1008
+ await Job.create({
1009
+ id: jobId,
1010
+ workflow_id: req.workflow_id ?? "",
1011
+ user_id: resolveRunJobUserId(req.user_id, this.session.requireUserId()),
1012
+ status: "queued",
1013
+ name: req.job_name ?? "",
1014
+ params: req.params ?? {},
1015
+ graph: req.graph ?? { nodes: [], edges: [] }
1016
+ });
1017
+ }
1018
+ }
1019
+ catch (err) {
1020
+ this.session.logError("enqueue persistence failed", err);
1021
+ }
1022
+ }
1023
+ this.session.sendDetached({
1024
+ type: "job_update",
1025
+ status: "queued",
1026
+ job_id: jobId,
1027
+ workflow_id: req.workflow_id ?? null,
1028
+ queue_position: position,
1029
+ message: `Queued (#${position})`
1030
+ });
1031
+ }
1032
+ /**
1033
+ * Start the next queued run (if any) after a job slot frees up, and refresh
1034
+ * the reported positions of the runs still waiting.
1035
+ */
1036
+ drainQueue() {
1037
+ void (async () => {
1038
+ const max = await this.deps
1039
+ .getMaxConcurrentJobs()
1040
+ .catch(() => this.deps.defaultMaxConcurrentJobs);
1041
+ const perWorkflowMax = await this.deps
1042
+ .getMaxConcurrentRunsPerWorkflow()
1043
+ .catch(() => this.deps.defaultMaxConcurrentRunsPerWorkflow);
1044
+ // Fill free slots with the first queued run whose workflow is still under
1045
+ // its per-workflow limit (1 for normal runs, perWorkflowMax for opted-in
1046
+ // concurrent runs). startJob registers the job before it returns, so the
1047
+ // next iteration sees it as in-flight.
1048
+ while (this.inFlightJobCount < max) {
1049
+ const candidate = this.jobQueue
1050
+ .positions()
1051
+ .find((p) => this.countActiveJobsForWorkflow(p.workflowId) <
1052
+ (p.concurrent ? perWorkflowMax : 1));
1053
+ if (!candidate) {
1054
+ break;
1055
+ }
1056
+ const next = this.jobQueue.remove(candidate.jobId);
1057
+ if (!next) {
1058
+ break;
1059
+ }
1060
+ // Reserve the slot synchronously, mirroring runJob, so a concurrent
1061
+ // run_job/drain can't also claim it before startJob registers.
1062
+ this.startingJobs++;
1063
+ const nextId = next.job_id;
1064
+ if (nextId) {
1065
+ this.dequeuedJobs.add(nextId);
1066
+ }
1067
+ try {
1068
+ await this.startJob(next);
1069
+ }
1070
+ catch (err) {
1071
+ // The dequeued job threw before it could register/stream. Don't
1072
+ // silently lose it: tell the client this run failed, then keep
1073
+ // draining so the rest of the queue still progresses.
1074
+ this.session.logError("startJob (from queue) failed", err);
1075
+ await this.session.send({
1076
+ type: "job_update",
1077
+ status: "failed",
1078
+ job_id: next.job_id ?? null,
1079
+ workflow_id: next.workflow_id ?? null,
1080
+ error: formatSanitizedError(err)
1081
+ });
1082
+ }
1083
+ finally {
1084
+ if (nextId) {
1085
+ this.dequeuedJobs.delete(nextId);
1086
+ }
1087
+ }
1088
+ }
1089
+ this.broadcastQueuePositions();
1090
+ })();
1091
+ }
1092
+ /** Push updated queue positions to every still-waiting run. */
1093
+ broadcastQueuePositions() {
1094
+ for (const { jobId, workflowId, position } of this.jobQueue.positions()) {
1095
+ this.session.sendDetached({
1096
+ type: "job_update",
1097
+ status: "queued",
1098
+ job_id: jobId,
1099
+ workflow_id: workflowId,
1100
+ queue_position: position,
1101
+ message: `Queued (#${position})`
1102
+ });
1103
+ }
1104
+ }
1105
+ async startJob(req) {
1106
+ // The caller (runJob/drainQueue) reserved a concurrency slot via
1107
+ // startingJobs++. Release it exactly once here: the slot is handed off to
1108
+ // activeJobs on successful registration, or freed on early return/throw.
1109
+ let slotReleased = false;
1110
+ const releaseSlot = () => {
1111
+ if (!slotReleased) {
1112
+ slotReleased = true;
1113
+ this.startingJobs = Math.max(0, this.startingJobs - 1);
1114
+ }
1115
+ };
1116
+ try {
1117
+ await this.startJobInner(req, releaseSlot);
1118
+ }
1119
+ catch (error) {
1120
+ if (req.app_run_id) {
1121
+ await this.emitBeforeRunFailure(req.job_id ?? "", req.workflow_id ?? null, error, true);
1122
+ return;
1123
+ }
1124
+ throw error;
1125
+ }
1126
+ finally {
1127
+ // Safety net: if startJobInner returned/threw without registering, the
1128
+ // slot is freed so it doesn't leak and permanently shrink the cap.
1129
+ releaseSlot();
1130
+ }
1131
+ }
1132
+ async startJobInner(req, releaseSlot) {
1133
+ const userId = resolveRunJobUserId(req.user_id, this.session.requireUserId());
1134
+ const workflowId = req.workflow_id ?? null;
1135
+ const jobId = req.job_id ?? randomUUID();
1136
+ let projectId = req.project_id ?? null;
1137
+ // Inline graphs do not require a database lookup. This matters for
1138
+ // hermetic callers that provide a workflow_id only as a correlation id.
1139
+ // Saved-workflow requests still resolve the persisted project owner here.
1140
+ if (workflowId && !req.graph) {
1141
+ const workflow = await Workflow.find(userId, workflowId);
1142
+ if (workflow) {
1143
+ if (projectId !== null && projectId !== workflow.project_id) {
1144
+ await this.emitBeforeRunFailure(jobId, workflowId, new Error("Workflow is owned by another project"), true);
1145
+ return;
1146
+ }
1147
+ projectId = workflow.project_id;
1148
+ }
1149
+ }
1150
+ const executionOptions = resolveRunJobExecutionOptions(req.execution_options, req.require_terminal_result === true);
1151
+ const acceptedAt = req._accepted_at_ms ?? performance.now();
1152
+ const preparationStartedAt = performance.now();
1153
+ let phaseStartedAt = preparationStartedAt;
1154
+ const rawGraph = await this.getRawGraph(req);
1155
+ const graphLoadedMs = performance.now() - phaseStartedAt;
1156
+ // Hydrate the graph (resolves node types from the registry)
1157
+ phaseStartedAt = performance.now();
1158
+ const graph = await this.hydrateGraph(rawGraph);
1159
+ const graphHydratedMs = performance.now() - phaseStartedAt;
1160
+ // The kernel keys terminal outputs by node.name. For SDK runs, align output
1161
+ // node names with their public interface names before execution so the
1162
+ // authoritative terminal snapshot addresses the same pins as output_update.
1163
+ if (req.require_terminal_result) {
1164
+ for (const node of graph.nodes) {
1165
+ if (!node.type.startsWith("nodetool.output."))
1166
+ continue;
1167
+ const properties = node.properties;
1168
+ const publicName = properties?.name;
1169
+ if (isString(publicName) && publicName.trim().length > 0) {
1170
+ node.name = publicName;
1171
+ }
1172
+ }
1173
+ }
1174
+ phaseStartedAt = performance.now();
1175
+ if (this.deps.beforeRunJob) {
1176
+ try {
1177
+ await this.deps.beforeRunJob(graph);
1178
+ }
1179
+ catch (err) {
1180
+ await this.emitBeforeRunFailure(jobId, workflowId, err, executionOptions.persistence === "job");
1181
+ return;
1182
+ }
1183
+ }
1184
+ const preRunMs = performance.now() - phaseStartedAt;
1185
+ const workspace = this.session.workspaceResolver
1186
+ ? await this.session.workspaceResolver(workflowId ?? null, userId)
1187
+ : null;
1188
+ const appRun = req.app_run_id
1189
+ ? await getAppRun(userId, req.app_run_id)
1190
+ : null;
1191
+ const context = createRuntimeContext({
1192
+ jobId,
1193
+ workflowId,
1194
+ projectId,
1195
+ userId,
1196
+ ...(appRun && {
1197
+ appRunContext: {
1198
+ userId,
1199
+ appRunId: appRun.id,
1200
+ instanceId: appRun.instance_id,
1201
+ traceId: appRun.trace_id,
1202
+ origin: appRun.origin
1203
+ }
1204
+ }),
1205
+ workspace,
1206
+ assetOutputMode: this.session.mode === "text" ? "data_uri" : "temp_url",
1207
+ persistOutputAssets: executionOptions.assetPersistence === "auto"
1208
+ });
1209
+ // Expose executor/node-type resolution on the context so that
1210
+ // sub-workflow nodes (WorkflowNode) can create child runners.
1211
+ context.setResolveExecutor((node) => this.session.resolveExecutor(node));
1212
+ if (this.session.resolveNodeType) {
1213
+ const resolverObj = isFunctionValue(this.session.resolveNodeType)
1214
+ ? { resolveNodeType: this.session.resolveNodeType }
1215
+ : this.session.resolveNodeType;
1216
+ context.setResolveNodeType((nodeType) => resolverObj.resolveNodeType(nodeType));
1217
+ }
1218
+ // Persistence runs BEFORE the session exists, because
1219
+ // `ExecutionSession.create()` starts the kernel: a job cancelled while it
1220
+ // sat in the queue must never execute, and the only way to guarantee that
1221
+ // is to check before anything can run. (The DB-only cancel path — tRPC
1222
+ // `jobs.cancel` — doesn't remove the job from `jobQueue`, so drainQueue
1223
+ // can still hand us a cancelled job.)
1224
+ phaseStartedAt = performance.now();
1225
+ // Which machine holds this run's session, for owner-aware reconnects and
1226
+ // cross-instance cancel. Null on a single-machine deployment.
1227
+ const instanceId = getInstanceId();
1228
+ if (executionOptions.persistence === "job") {
1229
+ try {
1230
+ const existing = await Job.get(jobId);
1231
+ if (existing) {
1232
+ if (existing.status === "cancelled") {
1233
+ if (appRun) {
1234
+ await settleAppRun(userId, appRun.id, { status: "cancelled" });
1235
+ }
1236
+ log.info("Skipping start of cancelled job", { jobId });
1237
+ // Nothing was registered in activeJobs yet — free the reserved
1238
+ // slot and promote any queued run, matching every other slot
1239
+ // release (streamJobMessages finally, the chat-run finally).
1240
+ // Without it a cancelled-while-queued job leaves its slot idle
1241
+ // and the next queued run stalls.
1242
+ releaseSlot();
1243
+ this.drainQueue();
1244
+ this.session.sendDetached({
1245
+ type: "job_update",
1246
+ status: "cancelled",
1247
+ job_id: jobId,
1248
+ workflow_id: workflowId
1249
+ });
1250
+ return;
1251
+ }
1252
+ // Was persisted as "queued" while waiting for a slot — flip it to
1253
+ // running now that it's actually starting. The stamp goes on here
1254
+ // too: the queued row may have been written by another instance.
1255
+ if (existing.status !== "running" ||
1256
+ existing.runner_instance !== instanceId) {
1257
+ existing.markRunning();
1258
+ existing.runner_instance = instanceId;
1259
+ await existing.save();
1260
+ }
1261
+ }
1262
+ else {
1263
+ await Job.create({
1264
+ id: jobId,
1265
+ workflow_id: workflowId ?? "",
1266
+ user_id: userId,
1267
+ status: "running",
1268
+ name: req.job_name ?? "",
1269
+ started_at: new Date().toISOString(),
1270
+ params: req.params ?? {},
1271
+ graph,
1272
+ project_id: projectId ?? "default",
1273
+ runner_instance: instanceId
1274
+ });
1275
+ }
1276
+ }
1277
+ catch (error) {
1278
+ this.session.logError("runJob persistence failed", error);
1279
+ // Persistence is best-effort in TS runtime mode.
1280
+ }
1281
+ }
1282
+ const persistenceMs = performance.now() - phaseStartedAt;
1283
+ // A5 (docs/RELIABILITY_TASKS.md Track A): the facade replaces the direct
1284
+ // `new WorkflowRunner` + later `runner.run()` pair — `graph` is already
1285
+ // hydrated/output-name-rewritten above, so this call re-hydrates it
1286
+ // (idempotent: `withExplicitNodeFlags`, used because this class has no
1287
+ // `NodeRegistry` of its own, passes every already-resolved field through
1288
+ // unchanged via `...node`) and starts the run immediately. `resolveExecutor`
1289
+ // is passed through as-is (not rebuilt from a registry+bridge) because
1290
+ // this class only ever holds the bootstrap-injected closure, never a
1291
+ // `NodeRegistry` instance.
1292
+ // Opt-in per request; a run that asked for no supervisor gets none, and a
1293
+ // supervisor that cannot be built leaves the run unsupervised rather than
1294
+ // failing it.
1295
+ const supervisor = await createRunSupervisor({
1296
+ supervise: req.supervise,
1297
+ supervisor: req.supervisor,
1298
+ context,
1299
+ defaultProvider: this.deps.defaults.provider,
1300
+ defaultModel: this.deps.defaults.model
1301
+ });
1302
+ const sessionOptions = {
1303
+ graph: toRawGraphInput(graph),
1304
+ resolveExecutor: (node) => this.session.resolveExecutor(node),
1305
+ bridgeFactory: async () => null,
1306
+ // This runner owns a long-lived shared bridge, so `bridgeFactory` hands
1307
+ // the session nothing to close. The run boundary still has to reach that
1308
+ // bridge — pass it explicitly.
1309
+ jobLifecycleBridge: this.session.pythonBridge ?? null,
1310
+ jobId,
1311
+ workflowId,
1312
+ context,
1313
+ params: req.params ?? {},
1314
+ projectId,
1315
+ validateNode: this.session.validateNode
1316
+ };
1317
+ if (supervisor) {
1318
+ sessionOptions.supervisor = supervisor;
1319
+ }
1320
+ // A graph this runtime cannot honour (unknown model, unregistered
1321
+ // provider, missing credential) is refused before the kernel starts.
1322
+ // Route it through the same terminal `job_update` a failed pre-run hook
1323
+ // uses: a bare throw reaches handleCommand as a generic `invalid_command`
1324
+ // the UI never associates with the job, so the run appears to spin
1325
+ // forever instead of failing with the reason.
1326
+ let finishAppTrace;
1327
+ try {
1328
+ await ensureRunTraceTelemetry();
1329
+ const incomingParent = req.traceparent ? trace.getSpanContext(propagation.extract(ROOT_CONTEXT, { traceparent: req.traceparent })) : undefined;
1330
+ const acceptedParent = incomingParent && isSpanContextValid(incomingParent) && !this.session.appSession && appRun && incomingParent.traceId === appRun.trace_id && await getRegisteredRunTrace(userId, incomingParent.traceId, appRun.id) ? incomingParent : undefined;
1331
+ finishAppTrace = appRun ? await openAppRunTrace(context, appRun, acceptedParent) : undefined;
1332
+ if (executionOptions.persistence === "job") {
1333
+ await registerWorkflowRunTrace(context, { jobId, workflowId: workflowId ?? null, inlineGraph: Boolean(req.graph), origin: this.session.appSession ? "public" : "ui" });
1334
+ }
1335
+ }
1336
+ catch (error) {
1337
+ await finishAppTrace?.("failed", error instanceof Error ? error.message : String(error));
1338
+ await this.emitBeforeRunFailure(jobId, workflowId, error, executionOptions.persistence === "job");
1339
+ return;
1340
+ }
1341
+ let session;
1342
+ try {
1343
+ session = await ExecutionSession.create(sessionOptions);
1344
+ }
1345
+ catch (err) {
1346
+ await finishAppTrace?.("failed", err instanceof Error ? err.message : String(err));
1347
+ if (!isExecutionPreflightError(err))
1348
+ throw err;
1349
+ await this.emitBeforeRunFailure(jobId, workflowId, err, executionOptions.persistence === "job");
1350
+ return;
1351
+ }
1352
+ const active = {
1353
+ jobId,
1354
+ workflowId,
1355
+ context,
1356
+ session,
1357
+ graph,
1358
+ finished: false,
1359
+ status: "running",
1360
+ requireTerminalResult: req.require_terminal_result === true,
1361
+ executionOptions,
1362
+ timings: {
1363
+ acceptedAt,
1364
+ queueMs: Math.max(0, preparationStartedAt - acceptedAt),
1365
+ graphLoadedMs,
1366
+ graphHydratedMs,
1367
+ preRunMs,
1368
+ persistenceMs,
1369
+ kernelStartedAt: performance.now()
1370
+ },
1371
+ applicationId: req.application_id ?? null,
1372
+ ...(appRun && { appRunFold: createAppRunFold(appRun) }),
1373
+ ...(finishAppTrace && { finishAppTrace })
1374
+ };
1375
+ // Decouple the run from this socket: from here on every frame carrying
1376
+ // this job_id is stamped with `job_seq` and buffered, so a client that
1377
+ // drops mid-run can `reconnect_job` from a fresh connection and replay
1378
+ // the tail — including the terminal job_update.
1379
+ //
1380
+ // Keyed on the connection's identity, not `userId`: every lookup
1381
+ // (`reconnect_job`, `cancel_job`, the slot counts) reads
1382
+ // `this.session.requireUserId()`, and a run opened under an explicit differing
1383
+ // `req.user_id` would be unreachable — no replay, and a cancel that
1384
+ // reports the job as not found.
1385
+ const runSession = jobRunRegistry.open(this.session.requireUserId(), jobId, workflowId, {
1386
+ cancel: () => session.cancel(),
1387
+ pushInput: (input, value, handle) => session.pushInput(input, value, handle),
1388
+ finishInputStream: (input, handle) => session.finishInputStream(input, handle),
1389
+ updateNodeProperties: (nodeId, properties) => session.updateNodeProperties(nodeId, properties)
1390
+ });
1391
+ runSession.attach(this.jobDeliveryTarget, runSession.lastSeq);
1392
+ active.runSession = runSession;
1393
+ this.activeJobs.set(jobId, active);
1394
+ // Slot ownership transfers from startingJobs to the registry's running
1395
+ // count now that the job is registered. Released before the DB check
1396
+ // below so the reservation and the registry entry never both count.
1397
+ releaseSlot();
1398
+ log.info("Job started", { jobId, workflowId });
1399
+ await this.settleRunAgainstLostConnection(runSession, jobId, executionOptions);
1400
+ // The run itself already started inside `ExecutionSession.create()`
1401
+ // above — `session.result` is the same never-rejecting terminal-result
1402
+ // promise `runner.run()` used to return.
1403
+ const executePromise = session.result;
1404
+ // `streamJobMessages` handles its own failures, but nothing awaits this
1405
+ // promise — attach a terminal handler so a bug there can never surface as
1406
+ // an unhandled rejection (which Node 22 turns into a process exit).
1407
+ active.streamTask = this.streamJobMessages(active, executePromise).catch((error) => {
1408
+ this.session.logError("job stream task failed", error);
1409
+ });
1410
+ }
1411
+ /**
1412
+ * A run whose start was mid-flight when `disconnect()` fired registers
1413
+ * AFTER that walk of `activeJobs`, so nothing detached it and nothing
1414
+ * armed its grace timer — it would execute unattended forever, delivering
1415
+ * into a dead target. Two fixes, both after registration:
1416
+ * - no live socket: detach, which starts the detach-grace countdown;
1417
+ * - the row already reads cancelled: `disconnect()`'s queue drain (or a
1418
+ * DB-only cancel) settled it while this run was starting, so cancel
1419
+ * the run rather than let it execute to completion under a row that
1420
+ * says it never did.
1421
+ */
1422
+ async settleRunAgainstLostConnection(runSession, jobId, executionOptions) {
1423
+ const socketGone = !this.deps.isSocketConnected();
1424
+ if (socketGone) {
1425
+ runSession.detach(this.jobDeliveryTarget);
1426
+ }
1427
+ if (executionOptions.persistence !== "job")
1428
+ return;
1429
+ try {
1430
+ const job = await Job.get(jobId);
1431
+ if (job?.status === "cancelled") {
1432
+ log.info("Job was cancelled while starting, cancelling the run", {
1433
+ jobId
1434
+ });
1435
+ runSession.cancel();
1436
+ }
1437
+ }
1438
+ catch (error) {
1439
+ this.session.logError("post-start cancellation check failed", error);
1440
+ }
1441
+ }
1442
+ async streamJobMessages(active, executePromise) {
1443
+ // The drain loop has awaited operations (Asset.paginate, normalizeOutputValue,
1444
+ // sendMessage) that can throw. Guarantee the job slot is released and the
1445
+ // queue drains no matter what — otherwise one throw permanently leaks a
1446
+ // MAX_CONCURRENT_JOBS slot and stalls every queued run.
1447
+ try {
1448
+ await this._streamJobMessagesInner(active, executePromise);
1449
+ }
1450
+ catch (error) {
1451
+ // Without this catch the rejection escaped entirely: the caller only
1452
+ // assigns `active.streamTask` and never awaits it, so Node's default
1453
+ // unhandledRejection behaviour terminated the process — and none of the
1454
+ // terminal bookkeeping below ran, leaving the DB row stuck at "running",
1455
+ // the client UI spinning, and the app ledger holding the estimate.
1456
+ const message = error instanceof Error ? error.message : String(error);
1457
+ this.session.logError("job message streaming failed", error);
1458
+ active.finished = true;
1459
+ active.status = "failed";
1460
+ active.error = message;
1461
+ try {
1462
+ await this.session.send({
1463
+ type: "job_update",
1464
+ status: "failed",
1465
+ job_id: active.jobId,
1466
+ workflow_id: active.workflowId,
1467
+ error: message
1468
+ });
1469
+ }
1470
+ catch (sendError) {
1471
+ this.session.logError("terminal job_update send failed", sendError);
1472
+ }
1473
+ await this.persistTerminalJobStatus(active);
1474
+ await this.settleApplicationInvocation(active);
1475
+ releaseSpend(this.session.requireUserId(), active.jobId);
1476
+ }
1477
+ finally {
1478
+ // Terminal: the session stops buffering and starts its retention
1479
+ // window, so a client reconnecting shortly after still gets the tail
1480
+ // (and the outcome) before the persisted row becomes the only source.
1481
+ active.runSession?.finish(active.status);
1482
+ this.activeJobs.delete(active.jobId);
1483
+ this.drainQueue();
1484
+ }
1485
+ }
1486
+ /**
1487
+ * Write the run's terminal status onto the persisted Job row. Skipped for
1488
+ * explicitly session-scoped runs, which own no row. Never throws —
1489
+ * persistence is best-effort and must not mask the run's own outcome.
1490
+ */
1491
+ async persistTerminalJobStatus(active, outputs) {
1492
+ if ((active.executionOptions?.persistence ??
1493
+ DEFAULT_RUN_JOB_EXECUTION_OPTIONS.persistence) !== "job") {
1494
+ return;
1495
+ }
1496
+ try {
1497
+ const job = (await Job.get(active.jobId));
1498
+ // A DB-only cancel (tRPC `jobs.cancel`) can finalize the row as cancelled
1499
+ // while the job is still executing in memory. Don't overwrite that with a
1500
+ // completed/failed status when the in-flight run finishes.
1501
+ if (job) {
1502
+ if (job.status !== "cancelled") {
1503
+ if (active.status === "completed") {
1504
+ job.markCompleted();
1505
+ if (outputs !== undefined) {
1506
+ job.metadata_json = {
1507
+ ...(job.metadata_json ?? {}),
1508
+ outputs: persistableOutputs(withoutInlineAssetBytes(outputs))
1509
+ };
1510
+ }
1511
+ }
1512
+ else if (active.status === "failed") {
1513
+ job.markFailed(active.error ?? "Unknown error");
1514
+ // The run's error is a string by now. A stack built here would
1515
+ // point at this method, not at the failure, so none is sent.
1516
+ captureError(active.error ?? "Unknown error", {
1517
+ source: "job",
1518
+ userId: job.user_id,
1519
+ context: {
1520
+ job_id: active.jobId,
1521
+ workflow_id: active.workflowId ?? ""
1522
+ }
1523
+ });
1524
+ }
1525
+ else if (active.status === "cancelled") {
1526
+ job.markCancelled();
1527
+ }
1528
+ }
1529
+ job.cost = this.runMeasuredCost(active);
1530
+ await job.save();
1531
+ }
1532
+ }
1533
+ catch (error) {
1534
+ this.session.logError("job persistence (final status) failed", error);
1535
+ }
1536
+ }
1537
+ /**
1538
+ * Close the app's ledger row at what the run actually cost. Until this lands
1539
+ * the run keeps counting against the budget at its estimate, which is the
1540
+ * conservative direction: a crash cannot free spend it may have incurred.
1541
+ * The total is what anything measured: charges a node reported for itself
1542
+ * plus generation the cost ledger priced. An absent total still means
1543
+ * "nothing measured this run", not "this run was free" — passing null keeps
1544
+ * the estimate standing rather than handing the spend back. Never throws.
1545
+ */
1546
+ async settleApplicationInvocation(active) {
1547
+ await active.finishAppTrace?.(active.status === "failed" ? "failed" : active.status === "cancelled" ? "cancelled" : "completed", active.error, this.runMeasuredCost(active));
1548
+ const identity = active.context.appRunContext;
1549
+ if (identity) {
1550
+ try {
1551
+ const settlement = {
1552
+ status: active.status === "failed"
1553
+ ? "failed"
1554
+ : active.status === "cancelled"
1555
+ ? "cancelled"
1556
+ : "completed",
1557
+ error: active.context.runTraceContext?.policy.contentSuppressed && active.error ? "Execution failed" : active.error ?? null,
1558
+ actualUsd: this.runMeasuredCost(active),
1559
+ outputs: active.appRunFold?.outputs() ?? {},
1560
+ documents: active.context.getAppRunDocuments(),
1561
+ contentSuppressed: active.context.runTraceContext?.policy.contentSuppressed,
1562
+ secretValues: [...active.context.getResolvedSecretValues()]
1563
+ };
1564
+ const llmCost = active.context.getAppRunLlmCost();
1565
+ if (llmCost !== null) {
1566
+ settlement.knownLlmUsd = llmCost;
1567
+ }
1568
+ await settleAppRun(identity.userId, identity.appRunId, settlement);
1569
+ await reconcileAppRunCost(identity.userId, identity.appRunId);
1570
+ return;
1571
+ }
1572
+ catch (error) {
1573
+ if (!(error instanceof AppRunError) || error.code !== "not_found") {
1574
+ this.session.logError("app run settlement failed", error);
1575
+ }
1576
+ }
1577
+ return;
1578
+ }
1579
+ if (!active.applicationId)
1580
+ return;
1581
+ try {
1582
+ await settleInvocation(active.applicationId, active.jobId, this.runMeasuredCost(active), active.status === "failed"
1583
+ ? "failed"
1584
+ : active.status === "cancelled"
1585
+ ? "cancelled"
1586
+ : "completed");
1587
+ }
1588
+ catch (error) {
1589
+ this.session.logError("application invocation settlement failed", error);
1590
+ }
1591
+ }
1592
+ async _streamJobMessagesInner(active, executePromise) {
1593
+ let terminalSeen = false;
1594
+ let terminalWithResultSeen = false;
1595
+ let outputUpdateSeen = false;
1596
+ let finalOutputs = {};
1597
+ // Per-node arrival counter for `generation_complete.index` within this job.
1598
+ // The function is scoped to one job, so keying by node_id alone yields a
1599
+ // per-(job_id, node_id) monotonic index. DB-ordering reconciliation is a
1600
+ // later step (RFC Decision 8); this is the in-memory arrival order.
1601
+ const generationIndexByNode = new Map();
1602
+ // Arrival positions already autosaved in THIS run, keyed `${nodeId} ${index}`.
1603
+ // A single generation_complete can persist several assets (a `list[image]`
1604
+ // output, or media + text), so dedupe by the event's arrival index — NOT by
1605
+ // a total asset count, which would under-save the next event (RFC D8).
1606
+ const autosavedSlots = new Set();
1607
+ // Cross-run replay dedupe: the `generation_index` values already persisted
1608
+ // for a node by a PRIOR run. Warmed with ONE `Asset.paginate` on a node's
1609
+ // first generation_complete, then reused for every later variant — so an
1610
+ // N-variant run does one query per node, not one per variant (RFC D8).
1611
+ const persistedIndexByNode = new Map();
1612
+ // The kernel opens every run with the same running update. Authoritative
1613
+ // SDK runs relay that one and avoid an otherwise duplicate WebSocket
1614
+ // frame. Legacy clients keep the eager acknowledgement.
1615
+ let runningSeen = false;
1616
+ if (!active.requireTerminalResult) {
1617
+ await this.session.send({
1618
+ type: "job_update",
1619
+ status: "running",
1620
+ job_id: active.jobId,
1621
+ workflow_id: active.workflowId
1622
+ });
1623
+ runningSeen = true;
1624
+ }
1625
+ // Guard the framing contract for authoritative runs: a terminal update
1626
+ // must never be the first job_update a client sees. The kernel emits
1627
+ // running before it can fail, but a run that dies before the kernel
1628
+ // starts — or a future reordering there — must not silently drop the
1629
+ // acknowledgement.
1630
+ const ensureRunningFrame = async () => {
1631
+ if (runningSeen)
1632
+ return;
1633
+ runningSeen = true;
1634
+ await this.session.send({
1635
+ type: "job_update",
1636
+ status: "running",
1637
+ job_id: active.jobId,
1638
+ workflow_id: active.workflowId
1639
+ });
1640
+ };
1641
+ const executionSettled = executePromise
1642
+ .then((result) => {
1643
+ active.status = result.status;
1644
+ active.error = result.error;
1645
+ finalOutputs = result.outputs ?? {};
1646
+ })
1647
+ .catch((err) => {
1648
+ this.session.logError("job execution failed", err);
1649
+ active.status = "failed";
1650
+ active.error = formatSanitizedError(err);
1651
+ })
1652
+ .finally(() => {
1653
+ active.finished = true;
1654
+ });
1655
+ const waitForActivity = createRelayActivityWaiter(active.context, executionSettled);
1656
+ const graphNodes = active.graph.nodes ?? [];
1657
+ const graphNodeMap = new Map();
1658
+ for (const n of graphNodes) {
1659
+ if (isString(n.id)) {
1660
+ graphNodeMap.set(n.id, n);
1661
+ }
1662
+ }
1663
+ while (!active.finished || active.context.hasMessages()) {
1664
+ while (active.context.hasMessages()) {
1665
+ const msg = active.context.popMessage();
1666
+ if (!msg)
1667
+ break;
1668
+ const outbound = { ...msg };
1669
+ outbound.job_id ??= active.jobId;
1670
+ outbound.workflow_id ??= active.workflowId;
1671
+ // Leave a nullish error untouched (the kernel stamps `error: null` on
1672
+ // every update) — only sanitize a real error value. Formatting null here
1673
+ // would ship the literal string "null" to clients.
1674
+ if (outbound.error != null) {
1675
+ outbound.error = formatSanitizedError(outbound.error);
1676
+ }
1677
+ if (outbound.type === "notification" && isString(outbound.content)) {
1678
+ outbound.content = sanitizeLargeText(outbound.content);
1679
+ }
1680
+ // Every message, not just node updates: a `prediction` is where
1681
+ // ledger-priced generation spend (Replicate, Gemini, OpenAI, …)
1682
+ // reports itself, and this is the path whose terminal status and app
1683
+ // settlement read the run's measured cost.
1684
+ this._handleNodeProviderCost(active, outbound);
1685
+ if (outbound.type === "node_update" && outbound.status === "error") {
1686
+ log.error("Node error", {
1687
+ jobId: active.jobId,
1688
+ nodeId: outbound.node_id,
1689
+ error: outbound.error
1690
+ });
1691
+ }
1692
+ else if (outbound.type === "job_update" &&
1693
+ outbound.status === "failed") {
1694
+ log.error("Job failed", {
1695
+ jobId: active.jobId,
1696
+ error: outbound.error
1697
+ });
1698
+ }
1699
+ // Skip messages for constant/input nodes — they produce trivial
1700
+ // outputs that don't need to be relayed to the frontend.
1701
+ if (outbound.type === "output_update" ||
1702
+ outbound.type === "node_update" ||
1703
+ outbound.type === "generation_complete") {
1704
+ const nodeId = String(outbound.node_id ?? "");
1705
+ const node = graphNodeMap.get(nodeId);
1706
+ const nodeType = isString(node?.type) ? node.type : "";
1707
+ // Skip constant and input nodes entirely
1708
+ if (nodeType.startsWith("nodetool.constant.") ||
1709
+ nodeType.startsWith("nodetool.input.")) {
1710
+ continue;
1711
+ }
1712
+ const meta = this.session.getNodeMetadata?.(nodeType);
1713
+ // Stamp an arrival-order `index` on generation_complete, keyed per
1714
+ // (job_id, node_id) (the function is job-scoped, so node_id alone
1715
+ // suffices). job_id/workflow_id were already backfilled by the
1716
+ // outbound spread above.
1717
+ if (outbound.type === "generation_complete") {
1718
+ const arrivalIndex = generationIndexByNode.get(nodeId) ?? 0;
1719
+ outbound.index = arrivalIndex;
1720
+ generationIndexByNode.set(nodeId, arrivalIndex + 1);
1721
+ // Autosave one generation per generation_complete on the RAW outputs
1722
+ // (before the normalize at the bottom of this block strips inline
1723
+ // bytes), tagged { jobId, nodeId, index }. This is the autosave
1724
+ // cutover (RFC §7, D3): persistence is driven per generation event,
1725
+ // not by the terminal node_update{completed} — so an N-execution
1726
+ // run persists N distinct generations.
1727
+ //
1728
+ // Replay dedupe (D8) is keyed on the event's arrival `index`, NOT on
1729
+ // a total asset count: a single generation_complete can persist
1730
+ // several assets (a `list[image]` output, or media + a text asset),
1731
+ // so a count-vs-index gate would under-save the very first run. Two
1732
+ // guards, both keyed by (nodeId, index):
1733
+ // - in-run: skip if this arrival slot was already saved this run;
1734
+ // - cross-run: skip if an asset for (jobId, nodeId) already carries
1735
+ // metadata.generation_index === arrivalIndex (a reconnect replay
1736
+ // re-streams the same events with arrivalIndex back at 0..N-1).
1737
+ // Server-only (D9): this is the websocket runner; the browser never
1738
+ // reaches runJob, so no browser autosave is introduced here.
1739
+ if (active.executionOptions.assetPersistence === "auto" &&
1740
+ meta?.auto_save_asset &&
1741
+ outbound.outputs != null) {
1742
+ const userId = this.session.requireUserId();
1743
+ const slotKey = `${nodeId} ${arrivalIndex}`;
1744
+ // Warm the cross-run replay set once per node (on its first
1745
+ // generation_complete), then reconcile every later variant
1746
+ // against the in-memory set — one DB read per node, not per slot.
1747
+ let persistedIndices = persistedIndexByNode.get(nodeId);
1748
+ if (persistedIndices === undefined) {
1749
+ persistedIndices = new Set();
1750
+ // Best-effort like every other persistence on this path: a
1751
+ // DB-free run (session persistence in the reliability harness,
1752
+ // a misconfigured deployment) must degrade to skipping the
1753
+ // replay dedupe, not kill the drain loop and fail the job.
1754
+ try {
1755
+ const [persisted] = await Asset.paginate(userId, {
1756
+ jobId: active.jobId,
1757
+ nodeId,
1758
+ limit: 1000
1759
+ });
1760
+ for (const a of persisted) {
1761
+ const gi = a.metadata?.generation_index;
1762
+ if (isNumber(gi))
1763
+ persistedIndices.add(gi);
1764
+ }
1765
+ }
1766
+ catch (err) {
1767
+ log.warn("generation replay-dedupe read failed", {
1768
+ nodeId,
1769
+ error: err instanceof Error ? err.message : String(err)
1770
+ });
1771
+ }
1772
+ persistedIndexByNode.set(nodeId, persistedIndices);
1773
+ }
1774
+ if (!autosavedSlots.has(slotKey) &&
1775
+ !persistedIndices.has(arrivalIndex)) {
1776
+ autosavedSlots.add(slotKey);
1777
+ try {
1778
+ await autoSaveAssets(outbound.outputs, {
1779
+ userId,
1780
+ workflowId: active.workflowId,
1781
+ jobId: active.jobId,
1782
+ nodeId,
1783
+ textOutputName: primaryTextOutputName(meta),
1784
+ generationIndex: arrivalIndex,
1785
+ properties: outbound.properties ?? undefined,
1786
+ nodeType: isString(outbound.node_type)
1787
+ ? outbound.node_type
1788
+ : undefined
1789
+ });
1790
+ }
1791
+ catch (err) {
1792
+ log.warn("autoSaveAssets error", { error: String(err) });
1793
+ }
1794
+ }
1795
+ }
1796
+ }
1797
+ // Relay output_update for display-sink nodes (Output, Preview) and
1798
+ // for streaming or auto-saving generative nodes (FAL / Replicate /
1799
+ // Kie / …) so the client receives one event per yielded item — the
1800
+ // UI accumulates and renders each generation as it arrives. The
1801
+ // Preview node re-emits each chunk it receives on its own terminal
1802
+ // `output` handle; relaying those is what lets the preview stream
1803
+ // incrementally instead of collapsing to the final value.
1804
+ if (outbound.type === "output_update") {
1805
+ const isDisplaySink = nodeType.includes("Output") || nodeType.endsWith(".Preview");
1806
+ const isStreamingLeaf = Boolean(meta?.is_streaming_output) ||
1807
+ Boolean(meta?.auto_save_asset);
1808
+ if (!isDisplaySink && !isStreamingLeaf)
1809
+ continue;
1810
+ outputUpdateSeen = true;
1811
+ }
1812
+ const isNodeError = outbound.type === "node_update" && outbound.status === "error";
1813
+ if (!isNodeError &&
1814
+ (active.executionOptions.eventDetail === "terminal" ||
1815
+ (active.executionOptions.eventDetail === "outputs" &&
1816
+ (outbound.type === "node_update" ||
1817
+ outbound.type === "generation_complete")))) {
1818
+ continue;
1819
+ }
1820
+ // Materialize binary assets to temp URLs before sending over WebSocket
1821
+ if (outbound.type === "node_update" && outbound.result != null) {
1822
+ outbound.result = await active.context.normalizeOutputValue(outbound.result);
1823
+ }
1824
+ if (outbound.type === "output_update" && outbound.value != null) {
1825
+ outbound.value = await active.context.normalizeOutputValue(outbound.value);
1826
+ }
1827
+ // Normalize generation_complete.outputs the same way node_update.result
1828
+ // is treated (raw bytes → temp URLs) before sending over the wire.
1829
+ if (outbound.type === "generation_complete" &&
1830
+ outbound.outputs != null) {
1831
+ outbound.outputs = await active.context.normalizeOutputValue(outbound.outputs);
1832
+ }
1833
+ }
1834
+ if (outbound.type === "edge_update" &&
1835
+ active.executionOptions.eventDetail !== "full") {
1836
+ continue;
1837
+ }
1838
+ const status = outbound.type === "job_update" ? String(outbound.status ?? "") : "";
1839
+ const suppressProvisionalCompletion = (Boolean(active.context.appRunContext) &&
1840
+ TERMINAL_JOB_STATUSES.includes(status)) ||
1841
+ (active.requireTerminalResult &&
1842
+ status === "completed" &&
1843
+ outbound.result === undefined);
1844
+ if (!suppressProvisionalCompletion) {
1845
+ if (outbound.type === "job_update") {
1846
+ if (status === "running") {
1847
+ runningSeen = true;
1848
+ }
1849
+ else if (TERMINAL_JOB_STATUSES.includes(status)) {
1850
+ if (active.context.appRunContext) {
1851
+ await executionSettled;
1852
+ await this.settleApplicationInvocation(active);
1853
+ }
1854
+ await ensureRunningFrame();
1855
+ }
1856
+ }
1857
+ active.appRunFold?.fold(outbound);
1858
+ await this.session.send(outbound);
1859
+ }
1860
+ if (outbound.type === "job_update" && !suppressProvisionalCompletion) {
1861
+ if (TERMINAL_JOB_STATUSES.includes(status)) {
1862
+ terminalSeen = true;
1863
+ if (outbound.result !== undefined) {
1864
+ terminalWithResultSeen = true;
1865
+ }
1866
+ }
1867
+ }
1868
+ }
1869
+ if (!active.finished) {
1870
+ await waitForActivity();
1871
+ }
1872
+ }
1873
+ // The authoritative terminal snapshot is consumed in every event-detail
1874
+ // mode. Keep it just as client-safe as streamed output_update values;
1875
+ // otherwise Outputs/Full can replace a working temp URL with raw media.
1876
+ if (Object.keys(finalOutputs).length > 0) {
1877
+ finalOutputs = await this.normalizeFinalOutputs(active, finalOutputs);
1878
+ }
1879
+ if (active.appRunFold) {
1880
+ for (const [key, values] of Object.entries(finalOutputs)) {
1881
+ const nodeId = this.resolveOutputNodeForKey(active, key)?.id ?? key;
1882
+ for (const [index, value] of values.entries()) {
1883
+ active.appRunFold.fold({
1884
+ type: "output_update",
1885
+ job_id: active.jobId,
1886
+ node_id: nodeId,
1887
+ value,
1888
+ disposition: index === 0 ? "replace" : "append",
1889
+ done: true
1890
+ });
1891
+ }
1892
+ }
1893
+ }
1894
+ if (active.executionOptions.eventDetail !== "terminal" &&
1895
+ !outputUpdateSeen &&
1896
+ Object.keys(finalOutputs).length > 0) {
1897
+ await this.sendOutputUpdates(active, finalOutputs);
1898
+ }
1899
+ const relayCompletedAt = performance.now();
1900
+ if (active.context.appRunContext) {
1901
+ await this.settleApplicationInvocation(active);
1902
+ }
1903
+ if (!terminalSeen ||
1904
+ (!terminalWithResultSeen && Object.keys(finalOutputs).length > 0)) {
1905
+ await ensureRunningFrame();
1906
+ await this.session.send({
1907
+ type: "job_update",
1908
+ status: active.status,
1909
+ job_id: active.jobId,
1910
+ workflow_id: active.workflowId,
1911
+ error: active.error,
1912
+ result: { outputs: finalOutputs }
1913
+ });
1914
+ }
1915
+ const terminalDeliveredAt = performance.now();
1916
+ log.info("Job completed", {
1917
+ jobId: active.jobId,
1918
+ status: active.status,
1919
+ executionOptions: active.executionOptions,
1920
+ timings: {
1921
+ queueMs: active.timings.queueMs,
1922
+ graphLoadedMs: active.timings.graphLoadedMs,
1923
+ graphHydratedMs: active.timings.graphHydratedMs,
1924
+ preRunMs: active.timings.preRunMs,
1925
+ persistenceMs: active.timings.persistenceMs,
1926
+ executionAndRelayMs: Math.max(0, relayCompletedAt - active.timings.kernelStartedAt),
1927
+ terminalDeliveryMs: Math.max(0, terminalDeliveredAt - relayCompletedAt),
1928
+ totalMs: Math.max(0, terminalDeliveredAt - active.timings.acceptedAt)
1929
+ }
1930
+ });
1931
+ await this.persistTerminalJobStatus(active, finalOutputs);
1932
+ await this.settleApplicationInvocation(active);
1933
+ releaseSpend(this.session.requireUserId(), active.jobId);
1934
+ // Slot release + queue drain happen in the streamJobMessages wrapper's
1935
+ // finally, so they run even if the drain loop above throws.
1936
+ }
1937
+ async reconnectJob(jobId, workflowId, lastSeq = 0) {
1938
+ // A resilient session is the authoritative answer: the run may be
1939
+ // executing on another connection's runner right now, or have finished
1940
+ // while this client was away — either way the seq-stamped buffer holds
1941
+ // exactly what was missed. Adopt it so this connection's `cancel_job` /
1942
+ // `stream_input` / `stop` reach the runner that owns the ExecutionSession.
1943
+ const registered = jobRunRegistry.get(this.session.requireUserId(), jobId);
1944
+ if (registered) {
1945
+ const { replay, incomplete } = registered.attach(this.jobDeliveryTarget, lastSeq);
1946
+ if (registered.status === "running") {
1947
+ this.adoptedJobIds.add(jobId);
1948
+ }
1949
+ // Header first, then the missed tail; live frames queue behind them on
1950
+ // the session's ordered delivery chain.
1951
+ await registered.deliverReplay(this.jobDeliveryTarget, [
1952
+ {
1953
+ type: "job_resumed",
1954
+ job_id: jobId,
1955
+ workflow_id: workflowId ?? registered.workflowId ?? null,
1956
+ status: registered.status,
1957
+ last_seq: registered.lastSeq,
1958
+ replay_count: replay.length,
1959
+ replay_incomplete: incomplete
1960
+ },
1961
+ ...replay
1962
+ ]);
1963
+ return;
1964
+ }
1965
+ const active = this.activeJobs.get(jobId);
1966
+ if (!active) {
1967
+ // No session and no in-memory job: the run ended long enough ago that
1968
+ // retention elapsed, or this process never had it. A row that already
1969
+ // reached a settled outcome is echoed verbatim — a completed run stays
1970
+ // completed, and the replay-unavailable note rides alongside as an
1971
+ // `error` string explaining only the missing events.
1972
+ //
1973
+ // Every other row status (queued, scheduled, running) is reported as
1974
+ // failed: nothing is left that could
1975
+ // ever send this client another frame, and reporting the row as-is
1976
+ // parks the UI in a state that never settles — a `queued` row from a
1977
+ // dead connection's drained queue reads as "running" with a live Stop
1978
+ // button forever.
1979
+ // Ownership rule as in the jobs router: another user's row is
1980
+ // indistinguishable from a missing one — it must be neither reported
1981
+ // nor settled below.
1982
+ const row = (await Job.get(jobId));
1983
+ const job = row && row.user_id === this.session.requireUserId() ? row : null;
1984
+ const settled = job != null &&
1985
+ (job.status === "completed" ||
1986
+ job.status === "failed" ||
1987
+ job.status === "cancelled");
1988
+ const replayUnavailable = job != null && job.status !== "failed" && job.status !== "cancelled";
1989
+ // A non-settled row with no session and no in-memory job is a zombie:
1990
+ // nothing is left that could ever finish it. Persist the failure when
1991
+ // this instance owns the row (or nothing claims it), so the row stops
1992
+ // advertising an in-flight run — otherwise every reload rediscovers it,
1993
+ // reattaches, and re-reports the same loss forever. A row claimed by
1994
+ // another instance is left alone: on a multi-instance deployment this
1995
+ // connection may simply have been balanced away from a run that is
1996
+ // still executing.
1997
+ if (job && !settled) {
1998
+ const instanceId = getInstanceId();
1999
+ const ownedHere = !job.runner_instance ||
2000
+ !instanceId ||
2001
+ job.runner_instance === instanceId;
2002
+ if (ownedHere) {
2003
+ try {
2004
+ job.markFailed("Run was lost after the execution connection went away.");
2005
+ await job.save();
2006
+ }
2007
+ catch (error) {
2008
+ this.session.logError("stale job row cleanup failed", error);
2009
+ }
2010
+ }
2011
+ }
2012
+ await this.session.send({
2013
+ type: "job_update",
2014
+ status: settled ? job.status : "failed",
2015
+ job_id: jobId,
2016
+ workflow_id: workflowId ?? job?.workflow_id ?? null,
2017
+ ...(job
2018
+ ? replayUnavailable
2019
+ ? {
2020
+ error: "Job event replay is unavailable after the execution connection was lost."
2021
+ }
2022
+ : job.error
2023
+ ? { error: job.error }
2024
+ : {}
2025
+ : { error: `Job ${jobId} not found` })
2026
+ });
2027
+ return;
2028
+ }
2029
+ await this.session.send({
2030
+ type: "job_update",
2031
+ status: active.status,
2032
+ job_id: jobId,
2033
+ workflow_id: workflowId ?? active.workflowId
2034
+ });
2035
+ for (const status of Object.values(active.context.getNodeStatuses())) {
2036
+ await this.session.send({
2037
+ ...status,
2038
+ job_id: jobId,
2039
+ workflow_id: workflowId ?? active.workflowId
2040
+ });
2041
+ }
2042
+ for (const status of Object.values(active.context.getEdgeStatuses())) {
2043
+ await this.session.send({
2044
+ ...status,
2045
+ job_id: jobId,
2046
+ workflow_id: workflowId ?? active.workflowId
2047
+ });
2048
+ }
2049
+ }
2050
+ async cancelJob(jobId, workflowId) {
2051
+ if (!jobId) {
2052
+ return { error: "No job_id provided" };
2053
+ }
2054
+ // A run that's still queued has no ActiveJob yet — drop it from the queue
2055
+ // and tell the client it's cancelled before it ever starts.
2056
+ const queued = this.jobQueue.remove(jobId);
2057
+ if (queued) {
2058
+ if (queued.app_run_id) {
2059
+ await settleAppRun(this.session.requireUserId(), queued.app_run_id, {
2060
+ status: "cancelled"
2061
+ });
2062
+ }
2063
+ releaseSpend(this.session.requireUserId(), jobId);
2064
+ const cancelledWorkflowId = queued.workflow_id ?? workflowId ?? null;
2065
+ // Mark the persisted queued row cancelled so it leaves the queue in
2066
+ // jobs.list too (not just the in-memory queue).
2067
+ if (resolveRunJobExecutionOptions(queued.execution_options, queued.require_terminal_result === true).persistence === "job") {
2068
+ try {
2069
+ const job = await Job.get(jobId);
2070
+ if (job) {
2071
+ job.markCancelled();
2072
+ await job.save();
2073
+ }
2074
+ }
2075
+ catch (err) {
2076
+ this.session.logError("cancel persistence failed", err);
2077
+ }
2078
+ }
2079
+ await this.session.send({
2080
+ type: "job_update",
2081
+ status: "cancelled",
2082
+ job_id: jobId,
2083
+ workflow_id: cancelledWorkflowId
2084
+ });
2085
+ this.broadcastQueuePositions();
2086
+ return {
2087
+ message: "Queued job cancelled",
2088
+ job_id: jobId,
2089
+ workflow_id: cancelledWorkflowId
2090
+ };
2091
+ }
2092
+ const active = this.activeJobs.get(jobId);
2093
+ if (!active) {
2094
+ // Not ours, but possibly still running on the connection that started
2095
+ // it (this client reconnected after a drop). Cancel through the
2096
+ // session's hooks and persist the row here — the owning runner's own
2097
+ // terminal bookkeeping still runs, and its `job_update` reaches this
2098
+ // client over the session it just adopted.
2099
+ const registered = jobRunRegistry.get(this.session.requireUserId(), jobId);
2100
+ if (registered && registered.status === "running") {
2101
+ registered.cancel();
2102
+ try {
2103
+ const job = await Job.get(jobId);
2104
+ if (job && job.status !== "cancelled") {
2105
+ job.markCancelled();
2106
+ await job.save();
2107
+ }
2108
+ }
2109
+ catch (err) {
2110
+ this.session.logError("cancel persistence failed", err);
2111
+ }
2112
+ return {
2113
+ message: "Job cancellation requested",
2114
+ job_id: jobId,
2115
+ workflow_id: workflowId ?? registered.workflowId ?? ""
2116
+ };
2117
+ }
2118
+ // Nothing local holds it. With more than one instance the run may be
2119
+ // executing on another machine: write the cancellation to its row, which
2120
+ // the owning instance's poller picks up.
2121
+ const remote = await requestRemoteJobCancel(this.session.requireUserId(), jobId);
2122
+ if (remote.cancelled) {
2123
+ return {
2124
+ message: "Job cancellation requested",
2125
+ job_id: jobId,
2126
+ workflow_id: workflowId ?? remote.workflowId ?? ""
2127
+ };
2128
+ }
2129
+ return {
2130
+ error: "Job not found or already completed",
2131
+ job_id: jobId,
2132
+ workflow_id: workflowId ?? ""
2133
+ };
2134
+ }
2135
+ if (active.session) {
2136
+ active.session.cancel();
2137
+ }
2138
+ active.status = "cancelled";
2139
+ // Persist the cancellation to the DB right away, mirroring the queued
2140
+ // branch and the tRPC jobs.cancel path. The runner's own cleanup can lag
2141
+ // (it drains in-flight messages before its .finally() persists), so
2142
+ // without this the persisted row stays "running" and jobs.list — which the
2143
+ // Queue panel reads from — keeps reporting the job as running even though
2144
+ // the toolbar Stop already fired.
2145
+ //
2146
+ // Deliberately NO eager `job_update cancelled` frame here: the kernel's
2147
+ // own terminal frame relays through the drain loop AFTER the node-level
2148
+ // terminal updates, and an out-of-band frame ahead of them tells the
2149
+ // client the job is over while nodes still read "running" — the exact
2150
+ // lifecycle violation the reliability harness's mid-run-cancel journeys
2151
+ // pin (`lifecycle.running-after-job-terminal`), and what left canvas
2152
+ // nodes stuck spinning after a Stop. The `cancel_job` RPC response is the
2153
+ // immediate acknowledgement; the ordered terminal arrives a beat later.
2154
+ if ((active.executionOptions?.persistence ??
2155
+ DEFAULT_RUN_JOB_EXECUTION_OPTIONS.persistence) === "job") {
2156
+ try {
2157
+ const job = await Job.get(jobId);
2158
+ if (job && job.status !== "cancelled") {
2159
+ job.markCancelled();
2160
+ await job.save();
2161
+ }
2162
+ }
2163
+ catch (err) {
2164
+ this.session.logError("cancel persistence failed", err);
2165
+ }
2166
+ }
2167
+ const cancelledWorkflowId = workflowId ?? active.workflowId ?? null;
2168
+ // Do NOT set active.finished = true here. Let the runner's cancellation
2169
+ // propagate through executePromise's .finally() callback so that
2170
+ // streamJobMessages can drain remaining messages and persist job state.
2171
+ return {
2172
+ message: "Job cancellation requested",
2173
+ job_id: jobId,
2174
+ workflow_id: cancelledWorkflowId ?? ""
2175
+ };
2176
+ }
2177
+ /**
2178
+ * `stop` for one run. The run is cancelled here when this connection owns
2179
+ * it; otherwise it is executing on the connection that started it — this
2180
+ * client only reconnected to it — so it is cancelled through its registered
2181
+ * session, or, when nothing local holds it, through its row.
2182
+ */
2183
+ async stopJob(jobId) {
2184
+ const active = this.activeJobs.get(jobId);
2185
+ if (active) {
2186
+ active.session.cancel();
2187
+ active.status = "cancelled";
2188
+ return;
2189
+ }
2190
+ const registered = jobRunRegistry.get(this.session.requireUserId(), jobId);
2191
+ if (registered && registered.status === "running") {
2192
+ registered.cancel();
2193
+ return;
2194
+ }
2195
+ await requestRemoteJobCancel(this.session.requireUserId(), jobId);
2196
+ }
2197
+ getStatus(jobId) {
2198
+ if (jobId) {
2199
+ const active = this.activeJobs.get(jobId);
2200
+ if (!active) {
2201
+ return { status: "not_found", job_id: jobId };
2202
+ }
2203
+ return {
2204
+ status: active.status,
2205
+ job_id: active.jobId,
2206
+ workflow_id: active.workflowId
2207
+ };
2208
+ }
2209
+ return {
2210
+ active_jobs: Array.from(this.activeJobs.values()).map((job) => ({
2211
+ job_id: job.jobId,
2212
+ workflow_id: job.workflowId,
2213
+ status: job.status
2214
+ }))
2215
+ };
2216
+ }
2217
+ /**
2218
+ * Accumulate provider cost from a completed node_update into the job total.
2219
+ *
2220
+ * The ledger row is *not* written here. `ExecutionSession` attaches
2221
+ * `attachRunCostLedger` to the same context, so every surface — this server,
2222
+ * the CLI, the debug harness — records the charge once, from one
2223
+ * implementation. Writing it again here would double-count every FAL and kie
2224
+ * generation.
2225
+ */
2226
+ _handleNodeProviderCost(active, outbound) {
2227
+ if (outbound.type === "prediction") {
2228
+ this._handlePredictionCost(active, outbound);
2229
+ return;
2230
+ }
2231
+ if (outbound.type !== "node_update" ||
2232
+ outbound.status !== "completed" ||
2233
+ outbound.provider_cost == null) {
2234
+ return;
2235
+ }
2236
+ const providerCost = outbound.provider_cost;
2237
+ const amount = providerCost.amount;
2238
+ if (isFiniteNumber(amount)) {
2239
+ (active.selfReportedCostNodeIds ??= new Set()).add(String(outbound.node_id ?? ""));
2240
+ active.providerCostTotal = (active.providerCostTotal ?? 0) + amount;
2241
+ }
2242
+ else {
2243
+ // A non-finite amount (NaN/Infinity from a buggy provider call) can't
2244
+ // be accumulated above, and JSON can't even represent it faithfully
2245
+ // (`JSON.stringify(NaN)` silently becomes `null`). Rather than ship a
2246
+ // `provider_cost` the wire contract calls a real number, drop it — the
2247
+ // rest of the `node_update` still reports normally.
2248
+ delete outbound.provider_cost;
2249
+ }
2250
+ }
2251
+ /**
2252
+ * Accumulate a completed unit-billed `prediction` into the run total, priced
2253
+ * the way the cost ledger prices it. Replicate, Gemini, OpenAI, MiniMax and
2254
+ * ElevenLabs generation reports no `provider_cost` of its own, so without
2255
+ * this a run on those providers settled as "nothing measured" and stayed
2256
+ * booked at its estimate.
2257
+ */
2258
+ _handlePredictionCost(active, outbound) {
2259
+ if (outbound.status !== "completed")
2260
+ return;
2261
+ const capability = isString(outbound.capability)
2262
+ ? outbound.capability
2263
+ : null;
2264
+ if (!isUnitBilledCapability(capability))
2265
+ return;
2266
+ const provider = isString(outbound.provider) ? outbound.provider : "";
2267
+ const model = isString(outbound.model) ? outbound.model : "";
2268
+ if (!provider || !model)
2269
+ return;
2270
+ const priced = priceGeneration({
2271
+ // Pricing is a pure function of provider, model, quantity and params —
2272
+ // `priceGeneration` never reads `userId`, so this must not demand one.
2273
+ // `requireUserId()` here turned cost accounting into a throw on any
2274
+ // session that has not connected. The guard stays on the paths that do
2275
+ // key on identity: registry lookups and spend reservation.
2276
+ userId: this.session.userId ?? "",
2277
+ provider,
2278
+ model,
2279
+ capability,
2280
+ quantity: 1,
2281
+ params: isRecord(outbound.params) ? outbound.params : {}
2282
+ });
2283
+ if (!priced || !isFiniteNumber(priced.cost))
2284
+ return;
2285
+ const nodeId = String(outbound.node_id ?? "");
2286
+ const byNode = (active.predictionCostByNode ??= new Map());
2287
+ byNode.set(nodeId, (byNode.get(nodeId) ?? 0) + priced.cost);
2288
+ }
2289
+ /**
2290
+ * What this run cost, as far as anything measured it: node-reported provider
2291
+ * charges plus ledger-priced generation on nodes that reported none. Null
2292
+ * when nothing measured — which keeps an app invocation standing at its
2293
+ * estimate rather than handing the spend back.
2294
+ */
2295
+ runMeasuredCost(active) {
2296
+ const selfReported = active.selfReportedCostNodeIds;
2297
+ let total = active.providerCostTotal ?? 0;
2298
+ let measured = active.providerCostTotal != null;
2299
+ for (const [nodeId, cost] of active.predictionCostByNode ?? []) {
2300
+ // The provider's own number wins over the catalog's estimate for the
2301
+ // same node — counting both would double-bill a FAL or kie generation.
2302
+ if (selfReported?.has(nodeId))
2303
+ continue;
2304
+ total += cost;
2305
+ measured = true;
2306
+ }
2307
+ return measured && total > 0 ? total : null;
2308
+ }
2309
+ /**
2310
+ * Release everything this connection still holds on the job side: detach the
2311
+ * resilient runs (they keep executing and buffering), cancel the ones nobody
2312
+ * is left to receive, and mark the queued rows cancelled. Called from the
2313
+ * host's `disconnect`.
2314
+ */
2315
+ async cancelAll() {
2316
+ // A run with a resilient session survives the socket: detach it (frames
2317
+ // keep buffering for replay, and `streamJobMessages` keeps draining on
2318
+ // this now-socketless runner) instead of cancelling. The session's
2319
+ // detach-grace timer bounds how long it may run unattended. A run without
2320
+ // one — a chat-triggered workflow, whose owning turn is cancelled anyway
2321
+ // — is cancelled as before: nobody is left to receive its output.
2322
+ for (const [jobId, job] of this.activeJobs) {
2323
+ if (job.runSession) {
2324
+ job.runSession.detach(this.jobDeliveryTarget);
2325
+ continue;
2326
+ }
2327
+ job.session?.cancel();
2328
+ this.activeJobs.delete(jobId);
2329
+ }
2330
+ for (const adoptedId of this.adoptedJobIds) {
2331
+ jobRunRegistry
2332
+ .get(this.session.requireUserId(), adoptedId)
2333
+ ?.detach(this.jobDeliveryTarget);
2334
+ }
2335
+ this.adoptedJobIds.clear();
2336
+ // Drain runs that were still queued (never started): the client is gone,
2337
+ // so they will never run. Mark their persisted rows cancelled instead of
2338
+ // leaving them as orphaned "scheduled" jobs in jobs.list.
2339
+ for (let queued = this.jobQueue.dequeue(); queued; queued = this.jobQueue.dequeue()) {
2340
+ const queuedId = queued.job_id;
2341
+ if (!queuedId)
2342
+ continue;
2343
+ try {
2344
+ if (queued.app_run_id) {
2345
+ await settleAppRun(this.session.requireUserId(), queued.app_run_id, {
2346
+ status: "cancelled"
2347
+ });
2348
+ }
2349
+ const job = await Job.get(queuedId);
2350
+ if (job) {
2351
+ job.markCancelled();
2352
+ await job.save();
2353
+ }
2354
+ }
2355
+ catch (err) {
2356
+ this.session.logError("disconnect queue cancellation failed", err);
2357
+ }
2358
+ }
2359
+ for (const dequeuedId of this.dequeuedJobs) {
2360
+ try {
2361
+ const run = await findAppRunByInvocation(this.session.requireUserId(), dequeuedId);
2362
+ if (run) {
2363
+ await settleAppRun(this.session.requireUserId(), run.id, {
2364
+ status: "cancelled"
2365
+ });
2366
+ }
2367
+ const job = await Job.get(dequeuedId);
2368
+ if (job) {
2369
+ job.markCancelled();
2370
+ await job.save();
2371
+ }
2372
+ }
2373
+ catch (err) {
2374
+ this.session.logError("disconnect dequeued-job cancellation failed", err);
2375
+ }
2376
+ }
2377
+ }
2378
+ }
2379
+ //# sourceMappingURL=job-execution.js.map