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

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