@hraness/slopcamera 3.2.4

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 (441) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +60 -0
  3. package/PRIVACY.md +46 -0
  4. package/README.md +311 -0
  5. package/SECURITY.md +21 -0
  6. package/apps/desktop/README.md +966 -0
  7. package/apps/desktop/analysis/protocol.ts +223 -0
  8. package/apps/desktop/application/capability-binding.ts +594 -0
  9. package/apps/desktop/application/context.ts +97 -0
  10. package/apps/desktop/application/creative-iteration.ts +1385 -0
  11. package/apps/desktop/application/default-registry.ts +146 -0
  12. package/apps/desktop/application/directing-contract.ts +218 -0
  13. package/apps/desktop/application/directing-plan.ts +178 -0
  14. package/apps/desktop/application/errors.ts +18 -0
  15. package/apps/desktop/application/gateway-port.ts +867 -0
  16. package/apps/desktop/application/html-overlay-browser-runtime.ts +687 -0
  17. package/apps/desktop/application/html-overlay-integrity.ts +337 -0
  18. package/apps/desktop/application/html-overlay-renderer.ts +45 -0
  19. package/apps/desktop/application/index.ts +13 -0
  20. package/apps/desktop/application/operation-completion-checkpoint.ts +333 -0
  21. package/apps/desktop/application/operation-effects.ts +94 -0
  22. package/apps/desktop/application/operation.ts +224 -0
  23. package/apps/desktop/application/operations/analysis/faces.ts +244 -0
  24. package/apps/desktop/application/operations/analysis/music.ts +248 -0
  25. package/apps/desktop/application/operations/analysis/project-auto-zooms.ts +302 -0
  26. package/apps/desktop/application/operations/analysis/project-inactivity.ts +381 -0
  27. package/apps/desktop/application/operations/analysis/scenes.ts +272 -0
  28. package/apps/desktop/application/operations/analysis/shared.ts +202 -0
  29. package/apps/desktop/application/operations/derive/edit-batch.ts +967 -0
  30. package/apps/desktop/application/operations/derive/follow-faces.ts +454 -0
  31. package/apps/desktop/application/operations/edit/bind-candidate-revision.ts +91 -0
  32. package/apps/desktop/application/operations/edit/create-candidate-revision.ts +377 -0
  33. package/apps/desktop/application/operations/edit/create-revision.ts +255 -0
  34. package/apps/desktop/application/operations/edit/freeze-revision.ts +182 -0
  35. package/apps/desktop/application/operations/gateway/image.ts +41 -0
  36. package/apps/desktop/application/operations/gateway/index.ts +17 -0
  37. package/apps/desktop/application/operations/gateway/shared.ts +122 -0
  38. package/apps/desktop/application/operations/gateway/speech.ts +41 -0
  39. package/apps/desktop/application/operations/gateway/transcription.ts +42 -0
  40. package/apps/desktop/application/operations/gateway/video.ts +41 -0
  41. package/apps/desktop/application/operations/index.ts +31 -0
  42. package/apps/desktop/application/operations/iteration/create-candidate.ts +240 -0
  43. package/apps/desktop/application/operations/iteration/create-matrix.ts +117 -0
  44. package/apps/desktop/application/operations/iteration/select.ts +155 -0
  45. package/apps/desktop/application/operations/iteration/shared.ts +250 -0
  46. package/apps/desktop/application/operations/media/audio-effects.ts +376 -0
  47. package/apps/desktop/application/operations/media/binding.ts +79 -0
  48. package/apps/desktop/application/operations/media/capabilities.ts +112 -0
  49. package/apps/desktop/application/operations/media/color-grade.ts +343 -0
  50. package/apps/desktop/application/operations/media/html-overlay.ts +802 -0
  51. package/apps/desktop/application/operations/media/index.ts +8 -0
  52. package/apps/desktop/application/operations/media/ingest.ts +303 -0
  53. package/apps/desktop/application/operations/media/overlay.ts +729 -0
  54. package/apps/desktop/application/operations/media/shared.ts +845 -0
  55. package/apps/desktop/application/operations/media/transform-platform.ts +120 -0
  56. package/apps/desktop/application/operations/project/commit-edits.ts +2054 -0
  57. package/apps/desktop/application/operations/project/promote-selection.ts +495 -0
  58. package/apps/desktop/application/operations/project/snapshot.ts +95 -0
  59. package/apps/desktop/application/operations/recording/index.ts +5 -0
  60. package/apps/desktop/application/operations/recording/pause.ts +34 -0
  61. package/apps/desktop/application/operations/recording/resume.ts +34 -0
  62. package/apps/desktop/application/operations/recording/shared.ts +199 -0
  63. package/apps/desktop/application/operations/recording/start.ts +169 -0
  64. package/apps/desktop/application/operations/recording/stop.ts +34 -0
  65. package/apps/desktop/application/operations/render/bind-candidate-output.ts +324 -0
  66. package/apps/desktop/application/operations/render/materialize-selection.ts +322 -0
  67. package/apps/desktop/application/operations/render/project-caption-plan.ts +287 -0
  68. package/apps/desktop/application/operations/render/project-plan.ts +939 -0
  69. package/apps/desktop/application/operations/render/project-spatial-receipt.ts +70 -0
  70. package/apps/desktop/application/operations/render/project.ts +1922 -0
  71. package/apps/desktop/application/operations/shared.ts +67 -0
  72. package/apps/desktop/application/operations/slopcamera-portable.ts +1188 -0
  73. package/apps/desktop/application/operations/slopcamera-visuals.ts +784 -0
  74. package/apps/desktop/application/operations/spatial-project.ts +209 -0
  75. package/apps/desktop/application/operations/spatial-render.ts +415 -0
  76. package/apps/desktop/application/operations/spatial-scene.ts +97 -0
  77. package/apps/desktop/application/operations/studio.ts +143 -0
  78. package/apps/desktop/application/output-publication-lease.ts +163 -0
  79. package/apps/desktop/application/project-publication-lease.ts +233 -0
  80. package/apps/desktop/application/project-store.ts +196 -0
  81. package/apps/desktop/application/receipts.ts +533 -0
  82. package/apps/desktop/application/registry.ts +182 -0
  83. package/apps/desktop/application/spatial-assets.ts +675 -0
  84. package/apps/desktop/application/spatial-compositor-cadence.ts +23 -0
  85. package/apps/desktop/application/spatial-project-authority.ts +35 -0
  86. package/apps/desktop/application/spatial-project-lease.ts +126 -0
  87. package/apps/desktop/application/spatial-project-render-preparation.ts +225 -0
  88. package/apps/desktop/application/spatial-project-store.ts +286 -0
  89. package/apps/desktop/application/spatial-render-projection.ts +201 -0
  90. package/apps/desktop/application/spatial-render.ts +677 -0
  91. package/apps/desktop/application/spatial-spz.ts +44 -0
  92. package/apps/desktop/application/spatial-world-import.ts +135 -0
  93. package/apps/desktop/application/spatial-world-provenance.ts +46 -0
  94. package/apps/desktop/application/studio-port.ts +58 -0
  95. package/apps/desktop/application/verified-receipt-reconciliation.ts +1574 -0
  96. package/apps/desktop/capture/protocol.ts +689 -0
  97. package/apps/desktop/cli/analyzer.ts +406 -0
  98. package/apps/desktop/cli/args.ts +3181 -0
  99. package/apps/desktop/cli/asset-ingest.ts +747 -0
  100. package/apps/desktop/cli/atomic-render-effects.ts +210 -0
  101. package/apps/desktop/cli/atomic-render-platform.ts +205 -0
  102. package/apps/desktop/cli/atomic-render.ts +50 -0
  103. package/apps/desktop/cli/audio-analysis.ts +252 -0
  104. package/apps/desktop/cli/bounded-map.ts +39 -0
  105. package/apps/desktop/cli/bundle-service.ts +256 -0
  106. package/apps/desktop/cli/capabilities.ts +212 -0
  107. package/apps/desktop/cli/capture-bundle.ts +1117 -0
  108. package/apps/desktop/cli/command-host-resources.ts +251 -0
  109. package/apps/desktop/cli/commands.ts +6836 -0
  110. package/apps/desktop/cli/directing-blob.ts +291 -0
  111. package/apps/desktop/cli/directing-media.ts +517 -0
  112. package/apps/desktop/cli/directing-quote.ts +89 -0
  113. package/apps/desktop/cli/directing-service.ts +288 -0
  114. package/apps/desktop/cli/emoji-assets.ts +538 -0
  115. package/apps/desktop/cli/errors.ts +29 -0
  116. package/apps/desktop/cli/face-analysis-service.ts +940 -0
  117. package/apps/desktop/cli/filter-script.ts +76 -0
  118. package/apps/desktop/cli/gateway-application-port.ts +1142 -0
  119. package/apps/desktop/cli/gateway-credential.ts +81 -0
  120. package/apps/desktop/cli/gateway-media-artifacts.ts +723 -0
  121. package/apps/desktop/cli/gateway-media-catalog.ts +922 -0
  122. package/apps/desktop/cli/gateway-media-service.ts +3106 -0
  123. package/apps/desktop/cli/gateway-media-signature.ts +122 -0
  124. package/apps/desktop/cli/gateway-provider-options.ts +562 -0
  125. package/apps/desktop/cli/gateway-scene-provider.ts +391 -0
  126. package/apps/desktop/cli/help.ts +501 -0
  127. package/apps/desktop/cli/html-overlay-gpu.ts +97 -0
  128. package/apps/desktop/cli/html-overlay-renderer.ts +2478 -0
  129. package/apps/desktop/cli/io.ts +290 -0
  130. package/apps/desktop/cli/main.ts +123 -0
  131. package/apps/desktop/cli/media-effects-service.ts +885 -0
  132. package/apps/desktop/cli/media-ingest-model.ts +364 -0
  133. package/apps/desktop/cli/media-ingest-platform.ts +227 -0
  134. package/apps/desktop/cli/media-ingest-program.ts +200 -0
  135. package/apps/desktop/cli/media-ingest.ts +30 -0
  136. package/apps/desktop/cli/music-analysis-service.ts +434 -0
  137. package/apps/desktop/cli/mutation-lock.ts +530 -0
  138. package/apps/desktop/cli/native-assets.d.ts +12 -0
  139. package/apps/desktop/cli/native-media-runtime.macos.ts +7 -0
  140. package/apps/desktop/cli/overlay-playback.ts +177 -0
  141. package/apps/desktop/cli/paths.ts +265 -0
  142. package/apps/desktop/cli/portable-surface.ts +242 -0
  143. package/apps/desktop/cli/project-camera-receipt.ts +112 -0
  144. package/apps/desktop/cli/project-face-camera.ts +394 -0
  145. package/apps/desktop/cli/project-inactivity-service.ts +552 -0
  146. package/apps/desktop/cli/project-media-integrity.ts +121 -0
  147. package/apps/desktop/cli/project-renderer.ts +1526 -0
  148. package/apps/desktop/cli/project-service.ts +519 -0
  149. package/apps/desktop/cli/project-state-transaction.ts +520 -0
  150. package/apps/desktop/cli/query-limits.ts +1 -0
  151. package/apps/desktop/cli/recording-controller.ts +1567 -0
  152. package/apps/desktop/cli/recording-daemon.ts +931 -0
  153. package/apps/desktop/cli/recording-ref.ts +56 -0
  154. package/apps/desktop/cli/renamed-environment.ts +6 -0
  155. package/apps/desktop/cli/renderer.ts +1439 -0
  156. package/apps/desktop/cli/run-cli-test-helper.ts +60 -0
  157. package/apps/desktop/cli/scene-analysis-service.ts +879 -0
  158. package/apps/desktop/cli/spatial-compositor-verifier.ts +213 -0
  159. package/apps/desktop/cli/spatial-project-service.ts +90 -0
  160. package/apps/desktop/cli/spatial-scene-service.ts +105 -0
  161. package/apps/desktop/cli/spatial-world-service.ts +39 -0
  162. package/apps/desktop/cli/speech-analysis-service.ts +682 -0
  163. package/apps/desktop/cli/studio-assemble.ts +58 -0
  164. package/apps/desktop/cli/studio-command.ts +60 -0
  165. package/apps/desktop/cli/studio-custody.ts +60 -0
  166. package/apps/desktop/cli/studio-encode.ts +216 -0
  167. package/apps/desktop/cli/studio-exr.ts +52 -0
  168. package/apps/desktop/cli/studio-files.ts +179 -0
  169. package/apps/desktop/cli/studio-output-validation.ts +162 -0
  170. package/apps/desktop/cli/studio-process.ts +145 -0
  171. package/apps/desktop/cli/studio-runtime.ts +77 -0
  172. package/apps/desktop/cli/studio-scaffold.ts +70 -0
  173. package/apps/desktop/cli/studio-service.ts +301 -0
  174. package/apps/desktop/cli/studio-spatial-asset.ts +248 -0
  175. package/apps/desktop/cli/studio-template-names.ts +3 -0
  176. package/apps/desktop/cli/studio-workflow.ts +19 -0
  177. package/apps/desktop/cli/time.ts +25 -0
  178. package/apps/desktop/cli/workflow-code.ts +584 -0
  179. package/apps/desktop/cli/workflow-runs.ts +576 -0
  180. package/apps/desktop/code/advanced.ts +139 -0
  181. package/apps/desktop/code/application-node-planner.ts +1288 -0
  182. package/apps/desktop/code/compiler.ts +329 -0
  183. package/apps/desktop/code/contracts.ts +13 -0
  184. package/apps/desktop/code/creative-recipes.ts +94 -0
  185. package/apps/desktop/code/define-workflow.ts +115 -0
  186. package/apps/desktop/code/editing.ts +284 -0
  187. package/apps/desktop/code/file-candidate-provenance.ts +420 -0
  188. package/apps/desktop/code/file-candidate.ts +69 -0
  189. package/apps/desktop/code/graph-builder.ts +10 -0
  190. package/apps/desktop/code/host-resource-policy.ts +55 -0
  191. package/apps/desktop/code/plan-contracts.ts +159 -0
  192. package/apps/desktop/code/planning.ts +361 -0
  193. package/apps/desktop/code/public.ts +134 -0
  194. package/apps/desktop/code/reference-projection.ts +62 -0
  195. package/apps/desktop/code/run-contracts.ts +466 -0
  196. package/apps/desktop/code/run-store.ts +2478 -0
  197. package/apps/desktop/code/runtime-identity.ts +328 -0
  198. package/apps/desktop/code/scheduler.ts +3067 -0
  199. package/apps/desktop/code/semantic-builder.ts +1436 -0
  200. package/apps/desktop/code/source-bundle.ts +1641 -0
  201. package/apps/desktop/code/source-typecheck.ts +211 -0
  202. package/apps/desktop/code/testing.ts +79 -0
  203. package/apps/desktop/code/worker-client.ts +1675 -0
  204. package/apps/desktop/code/worker-effects.ts +141 -0
  205. package/apps/desktop/code/worker-entry.ts +435 -0
  206. package/apps/desktop/code/worker-lease-guardian.ts +179 -0
  207. package/apps/desktop/code/worker-process-identity.ts +484 -0
  208. package/apps/desktop/code/worker-protocol.ts +211 -0
  209. package/apps/desktop/code/workflow-effects.ts +132 -0
  210. package/apps/desktop/contracts/analysis.ts +949 -0
  211. package/apps/desktop/contracts/edit.ts +576 -0
  212. package/apps/desktop/contracts/index.ts +9 -0
  213. package/apps/desktop/contracts/media-effects.ts +139 -0
  214. package/apps/desktop/contracts/project-render.ts +644 -0
  215. package/apps/desktop/contracts/project.ts +740 -0
  216. package/apps/desktop/contracts/recording.ts +1434 -0
  217. package/apps/desktop/contracts/render.ts +334 -0
  218. package/apps/desktop/contracts/runtime.ts +155 -0
  219. package/apps/desktop/contracts/spatial-compositor.ts +58 -0
  220. package/apps/desktop/contracts/spatial-project.ts +159 -0
  221. package/apps/desktop/contracts/spatial-world.ts +48 -0
  222. package/apps/desktop/contracts/video-effects.ts +120 -0
  223. package/apps/desktop/core/alignment-apply.ts +400 -0
  224. package/apps/desktop/core/audio-alignment.ts +466 -0
  225. package/apps/desktop/core/auto-zoom.ts +209 -0
  226. package/apps/desktop/core/canonical-json.ts +14 -0
  227. package/apps/desktop/core/emoji.ts +210 -0
  228. package/apps/desktop/core/events.ts +409 -0
  229. package/apps/desktop/core/face-framing.ts +533 -0
  230. package/apps/desktop/core/face-tracks.ts +326 -0
  231. package/apps/desktop/core/inactivity.ts +137 -0
  232. package/apps/desktop/core/index.ts +30 -0
  233. package/apps/desktop/core/intervals.ts +123 -0
  234. package/apps/desktop/core/montage.ts +236 -0
  235. package/apps/desktop/core/music-analysis.ts +518 -0
  236. package/apps/desktop/core/plan.ts +244 -0
  237. package/apps/desktop/core/project-auto-zoom.ts +75 -0
  238. package/apps/desktop/core/project-camera-spatial.ts +676 -0
  239. package/apps/desktop/core/project-camera.ts +403 -0
  240. package/apps/desktop/core/project-captions.ts +888 -0
  241. package/apps/desktop/core/project-inactivity.ts +125 -0
  242. package/apps/desktop/core/project-metadata.ts +927 -0
  243. package/apps/desktop/core/project-plan.ts +375 -0
  244. package/apps/desktop/core/project-render-plan.ts +303 -0
  245. package/apps/desktop/core/project-time.ts +469 -0
  246. package/apps/desktop/core/render-plan.ts +442 -0
  247. package/apps/desktop/core/scene-cache.ts +174 -0
  248. package/apps/desktop/core/scene-sampling.ts +448 -0
  249. package/apps/desktop/core/spatial-compositor.ts +22 -0
  250. package/apps/desktop/core/spatial-durability.ts +99 -0
  251. package/apps/desktop/core/spatial-project.ts +169 -0
  252. package/apps/desktop/core/spatial-shot-clock.ts +32 -0
  253. package/apps/desktop/core/speech-analysis.ts +333 -0
  254. package/apps/desktop/core/storage.ts +970 -0
  255. package/apps/desktop/core/time-map.ts +91 -0
  256. package/apps/desktop/core/time.ts +61 -0
  257. package/apps/desktop/core/video-effects.ts +660 -0
  258. package/apps/desktop/dist/cli/NebulaSans-Bold-26se8aek.otf +0 -0
  259. package/apps/desktop/dist/cli/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
  260. package/apps/desktop/dist/cli/NebulaSans-Book-5ax05zvn.woff2 +0 -0
  261. package/apps/desktop/dist/cli/NebulaSans-Book-8cenzchw.otf +0 -0
  262. package/apps/desktop/dist/cli/main.js +5086 -0
  263. package/apps/desktop/html-overlay/catalog.ts +171 -0
  264. package/apps/desktop/html-overlay/contracts.ts +372 -0
  265. package/apps/desktop/html-overlay/execution-profile.ts +77 -0
  266. package/apps/desktop/html-overlay/index.ts +98 -0
  267. package/apps/desktop/html-overlay/libraries.ts +263 -0
  268. package/apps/desktop/html-overlay/random.ts +49 -0
  269. package/apps/desktop/html-overlay/runtime.ts +813 -0
  270. package/apps/desktop/html-overlay/scaffolds.ts +792 -0
  271. package/apps/desktop/html-overlay/spark-runtime.ts +88 -0
  272. package/apps/desktop/html-overlay/spatial-splat-runtime.ts +60 -0
  273. package/apps/desktop/html-overlay/spatial.ts +720 -0
  274. package/apps/desktop/studio/assets/README.md +39 -0
  275. package/apps/desktop/studio/assets/contracts.ts +129 -0
  276. package/apps/desktop/studio/assets/network.ts +64 -0
  277. package/apps/desktop/studio/assets/poly-haven.ts +148 -0
  278. package/apps/desktop/studio/drivers/blender_driver.py +466 -0
  279. package/apps/desktop/studio/drivers/cadquery_driver.py +176 -0
  280. package/apps/desktop/studio/education/driver.py +308 -0
  281. package/apps/desktop/studio/education/lesson.py +180 -0
  282. package/apps/desktop/studio/education/toolkit.py +249 -0
  283. package/apps/desktop/workflows/chaptered-demo.ts +48 -0
  284. package/apps/desktop/workflows/creative-iteration.ts +107 -0
  285. package/apps/desktop/workflows/creative-selection.ts +92 -0
  286. package/apps/desktop/workflows/fragments.ts +486 -0
  287. package/apps/desktop/workflows/index.ts +93 -0
  288. package/apps/desktop/workflows/polished-screen-demo.ts +128 -0
  289. package/apps/desktop/workflows/scene-direction.ts +12 -0
  290. package/apps/desktop/workflows/social-variants.ts +75 -0
  291. package/apps/desktop/workflows/talking-head-cleanup.ts +53 -0
  292. package/dist/NebulaSans-Bold-26se8aek.otf +0 -0
  293. package/dist/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
  294. package/dist/NebulaSans-Book-5ax05zvn.woff2 +0 -0
  295. package/dist/NebulaSans-Book-8cenzchw.otf +0 -0
  296. package/dist/cli.js +479 -0
  297. package/dist/code/advanced.js +270 -0
  298. package/dist/code/index.js +2240 -0
  299. package/dist/generate.js +24 -0
  300. package/dist/host-resources.js +26 -0
  301. package/dist/index-42zsesc1.js +2257 -0
  302. package/dist/index-7308egqr.js +48 -0
  303. package/dist/index-8txs6fkn.js +712 -0
  304. package/dist/index-mcy8z0br.js +192 -0
  305. package/dist/index-p63wavx0.js +2029 -0
  306. package/dist/index-r7gdhmsp.js +424 -0
  307. package/dist/index-sh6xbav6.js +1219 -0
  308. package/dist/index-z1w83f81.js +4 -0
  309. package/dist/index-z7239b4h.js +2363 -0
  310. package/dist/index-zfeg9hmw.js +2013 -0
  311. package/dist/index.js +241 -0
  312. package/dist/operations.js +29 -0
  313. package/dist/skill-install-dh0ntqba.js +10 -0
  314. package/dist/vectorize/worker.js +142 -0
  315. package/dist/workflow.js +16 -0
  316. package/docs/README.md +38 -0
  317. package/docs/studio.md +215 -0
  318. package/examples/studio/blender/README.md +26 -0
  319. package/examples/studio/blender/character.py +152 -0
  320. package/examples/studio/blender/cloth.py +101 -0
  321. package/examples/studio/blender/color_chart.py +29 -0
  322. package/examples/studio/blender/fluid.py +78 -0
  323. package/examples/studio/blender/import_model.py +74 -0
  324. package/examples/studio/blender/product.py +53 -0
  325. package/examples/studio/blender/shaded_street.py +481 -0
  326. package/examples/studio/blender/studio_scene.py +147 -0
  327. package/examples/studio/cadquery/README.md +13 -0
  328. package/examples/studio/cadquery/bracket.py +36 -0
  329. package/examples/studio/cadquery/import_step.py +12 -0
  330. package/examples/studio/education/README.md +46 -0
  331. package/examples/studio/education/lesson.json +99 -0
  332. package/examples/studio/education/scene.py +7 -0
  333. package/examples/studio/hybrid-scene.ts +96 -0
  334. package/examples/studio/native-workflow.ts +14 -0
  335. package/examples/studio/vgpu/README.md +64 -0
  336. package/examples/studio/vgpu/heat-field.mjs +290 -0
  337. package/package.json +304 -0
  338. package/packages/scene/src/index.ts +1 -0
  339. package/packages/scene/src/scene.ts +215 -0
  340. package/schema/diagram.schema.json +334 -0
  341. package/skills/slopcamera/SKILL.md +45 -0
  342. package/skills/slopcamera/agents/openai.yaml +4 -0
  343. package/skills/slopcamera/references/customization.md +77 -0
  344. package/skills/slopcamera/references/diagrams.md +153 -0
  345. package/skills/slopcamera/references/directed-scenes.md +42 -0
  346. package/skills/slopcamera/references/directing-video.md +18 -0
  347. package/skills/slopcamera/references/educational-video.md +26 -0
  348. package/skills/slopcamera/references/gateway-media.md +134 -0
  349. package/skills/slopcamera/references/install.md +62 -0
  350. package/skills/slopcamera/references/native-studio.md +31 -0
  351. package/skills/slopcamera/references/reference-led-3d.md +140 -0
  352. package/skills/slopcamera/references/rubber-stamp-examples/poster-example-1.jpg +0 -0
  353. package/skills/slopcamera/references/rubber-stamp-examples/poster-example-2.jpg +0 -0
  354. package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-1.png +0 -0
  355. package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-2.png +0 -0
  356. package/skills/slopcamera/references/rubber-stamp-field-notes.md +144 -0
  357. package/skills/slopcamera/references/vectorization.md +29 -0
  358. package/skills/slopcamera/references/video-projects.md +187 -0
  359. package/skills/slopcamera/references/visual-communication.md +120 -0
  360. package/skills/slopcamera/references/workflows-sdk.md +45 -0
  361. package/skills/slopcamera/scripts/compose-rubber-stamp-field-note.ts +229 -0
  362. package/src/artifacts.ts +106 -0
  363. package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
  364. package/src/assets/fonts/nebula-sans/NebulaSans-Bold.otf +0 -0
  365. package/src/assets/fonts/nebula-sans/NebulaSans-Bold.woff2 +0 -0
  366. package/src/assets/fonts/nebula-sans/NebulaSans-Book.otf +0 -0
  367. package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
  368. package/src/assets/fonts/nebula-sans/PROVENANCE.md +21 -0
  369. package/src/assets.d.ts +9 -0
  370. package/src/cli.ts +601 -0
  371. package/src/cloud-errors.ts +26 -0
  372. package/src/code/advanced.ts +10 -0
  373. package/src/code/boundary.ts +21 -0
  374. package/src/code/canonical-json.ts +129 -0
  375. package/src/code/compiler.ts +953 -0
  376. package/src/code/contracts.ts +576 -0
  377. package/src/code/define-workflow.ts +334 -0
  378. package/src/code/errors.ts +41 -0
  379. package/src/code/graph-builder.ts +945 -0
  380. package/src/code/index.ts +121 -0
  381. package/src/code/json-snapshot.ts +899 -0
  382. package/src/code/json-utf8.ts +88 -0
  383. package/src/code/portable-builder.ts +136 -0
  384. package/src/code/projection.ts +378 -0
  385. package/src/code/public-operations.ts +450 -0
  386. package/src/code/runtime.ts +580 -0
  387. package/src/code/sha256.ts +36 -0
  388. package/src/config.ts +180 -0
  389. package/src/desktop.ts +253 -0
  390. package/src/fs.ts +10 -0
  391. package/src/generate.ts +704 -0
  392. package/src/host-resource-posix.ts +161 -0
  393. package/src/host-resources.ts +1749 -0
  394. package/src/icons.ts +58 -0
  395. package/src/index.ts +200 -0
  396. package/src/label-layout.ts +130 -0
  397. package/src/layout.ts +413 -0
  398. package/src/lint.ts +127 -0
  399. package/src/mcp/boundary.ts +354 -0
  400. package/src/mcp/index.ts +33 -0
  401. package/src/mcp/server.ts +272 -0
  402. package/src/mcp/tools.ts +963 -0
  403. package/src/mcp/types.ts +71 -0
  404. package/src/operations.ts +775 -0
  405. package/src/parse.ts +861 -0
  406. package/src/process-environment.ts +17 -0
  407. package/src/render.ts +437 -0
  408. package/src/skill-install.ts +49 -0
  409. package/src/spatial-scene/authoring.ts +27 -0
  410. package/src/spatial-scene/camera-track.ts +83 -0
  411. package/src/spatial-scene/contracts.ts +277 -0
  412. package/src/spatial-scene/evaluate.ts +145 -0
  413. package/src/spatial-scene/gltf.ts +558 -0
  414. package/src/spatial-scene/identity.ts +260 -0
  415. package/src/spatial-scene/index.ts +10 -0
  416. package/src/spatial-scene/inspect.ts +68 -0
  417. package/src/spatial-scene/math.ts +280 -0
  418. package/src/spatial-scene/patch.ts +131 -0
  419. package/src/spatial-scene/time.ts +53 -0
  420. package/src/studio/contracts.ts +129 -0
  421. package/src/studio/index.ts +2 -0
  422. package/src/studio/plan.ts +156 -0
  423. package/src/studio/shared.ts +41 -0
  424. package/src/theme.ts +68 -0
  425. package/src/tldr.ts +474 -0
  426. package/src/types.ts +212 -0
  427. package/src/vectorize/archive.ts +101 -0
  428. package/src/vectorize/command.ts +797 -0
  429. package/src/vectorize/index.ts +27 -0
  430. package/src/vectorize/limits.ts +61 -0
  431. package/src/vectorize/metrics.ts +351 -0
  432. package/src/vectorize/pixels.ts +323 -0
  433. package/src/vectorize/supervisor.ts +339 -0
  434. package/src/vectorize/svg.ts +188 -0
  435. package/src/vectorize/tool.ts +542 -0
  436. package/src/vectorize/types.ts +113 -0
  437. package/src/vectorize/vectorize.ts +634 -0
  438. package/src/vectorize/worker-protocol.ts +40 -0
  439. package/src/vectorize/worker.ts +209 -0
  440. package/src/version.ts +1 -0
  441. package/src/workflow.ts +409 -0
@@ -0,0 +1,1675 @@
1
+ import { Effect, Exit, Fiber } from "effect";
2
+ import { WorkerEffectRuntime, workerBoundary, workerValidation, type WorkerCompletion, type WorkerFailure } from "./worker-effects";
3
+ import { createHash, randomUUID } from "node:crypto";
4
+ import { spawn, type ChildProcess } from "node:child_process";
5
+ import {
6
+ chmod,
7
+ mkdir,
8
+ mkdtemp,
9
+ readFile,
10
+ rm,
11
+ writeFile,
12
+ } from "node:fs/promises";
13
+ import {
14
+ createServer,
15
+ type Server,
16
+ type Socket,
17
+ } from "node:net";
18
+ import { tmpdir } from "node:os";
19
+ import { dirname, join, resolve } from "node:path";
20
+
21
+ import { z } from "zod";
22
+
23
+ import { ApplicationError } from "../application/errors";
24
+ import type { OperationDiscovery } from "../application/registry";
25
+ import { canonicalJson } from "../core/canonical-json";
26
+ import {
27
+ AuthoredWorkflowGraphV1Schema,
28
+ JsonValueSchema,
29
+ normalizeAuthoredWorkflowGraph,
30
+ type JsonValue,
31
+ } from "./contracts";
32
+ import {
33
+ CODE_WORKER_PROTOCOL,
34
+ CodeWorkerRequestSchema,
35
+ MAX_CODE_WORKER_DIAGNOSTIC_BARRIER_BYTES,
36
+ WorkerFrameDecoder,
37
+ encodeCodeWorkerDiagnosticBarrier,
38
+ encodeWorkerFrame,
39
+ type CodeWorkerMessage,
40
+ type CodeWorkerRequest,
41
+ type CodeWorkerResponse,
42
+ } from "./worker-protocol";
43
+ import { captureWorkerProcessStartIdentity } from "./worker-process-identity";
44
+
45
+ const DEFAULT_TIMEOUT_MS = 30_000;
46
+ const DEFAULT_MAXIMUM_DIAGNOSTIC_BYTES = 64 * 1024;
47
+ const CODE_WORKER_CANCEL_GRACE_MS = 250;
48
+ export const MAX_CODE_WORKER_POOL_SIZE = 4;
49
+
50
+ const WorkerBuiltWorkflowSchema = z.strictObject({
51
+ graph: AuthoredWorkflowGraphV1Schema,
52
+ input: JsonValueSchema,
53
+ });
54
+
55
+ const WorkerCancellationAcknowledgementSchema = z.strictObject({
56
+ cancellation: z.enum(["active", "queued", "missing"]),
57
+ });
58
+
59
+ class CodeWorkerCancellationError extends ApplicationError {
60
+ readonly workerReusable: boolean;
61
+
62
+ constructor(nodeKey: string, workerReusable: boolean) {
63
+ super("cancelled", `Compute node ${nodeKey} was cancelled.`);
64
+ this.workerReusable = workerReusable;
65
+ }
66
+ }
67
+
68
+ export interface CodeWorkerDiagnostics {
69
+ readonly stderr: string;
70
+ readonly stdout: string;
71
+ }
72
+
73
+ export interface CodeWorkerBuildResult {
74
+ readonly diagnostics: CodeWorkerDiagnostics;
75
+ readonly graph: z.infer<typeof AuthoredWorkflowGraphV1Schema>;
76
+ readonly input: JsonValue;
77
+ }
78
+
79
+ export interface CodeWorkerBundle {
80
+ readonly bytes: Uint8Array;
81
+ readonly externalImports: readonly {
82
+ readonly resolvedPath: string;
83
+ readonly specifier: string;
84
+ }[];
85
+ readonly sha256: string;
86
+ }
87
+
88
+ export interface StartCodeWorkerOptions {
89
+ readonly bunExecutable?: string;
90
+ readonly bundle: CodeWorkerBundle;
91
+ readonly inheritedHostResourceFileDescriptor?: number;
92
+ readonly maximumDiagnosticBytes?: number;
93
+ readonly temporaryRoot?: string;
94
+ readonly timeoutMs?: number;
95
+ readonly workerEntryPath?: string;
96
+ }
97
+
98
+ export interface BuildInCodeWorkerOptions extends StartCodeWorkerOptions {
99
+ readonly registry: {
100
+ list(): readonly OperationDiscovery[];
101
+ };
102
+ readonly workflowInput: unknown;
103
+ }
104
+
105
+ export interface CodeWorkerComputeRequest {
106
+ readonly abortSignal?: AbortSignal;
107
+ readonly computeKey: string;
108
+ readonly inheritedHostResourceFileDescriptor?: number;
109
+ readonly input: JsonValue;
110
+ readonly nodeKey: string;
111
+ readonly replayAcknowledged: boolean;
112
+ readonly timeoutMs?: number;
113
+ }
114
+
115
+ export interface CodeWorkerSession {
116
+ build(
117
+ registry: { list(): readonly OperationDiscovery[] },
118
+ workflowInput: unknown,
119
+ inputMode?: "parsed" | "raw",
120
+ ): Promise<CodeWorkerBuildResult>;
121
+ close(): Promise<void>;
122
+ compute(request: CodeWorkerComputeRequest): Promise<JsonValue>;
123
+ diagnostics(): CodeWorkerDiagnostics;
124
+ readonly bundleSha256: string;
125
+ readonly generation: number;
126
+ readonly processId: number;
127
+ readonly processStartIdentity: string;
128
+ }
129
+
130
+ export interface CodeWorkerPool {
131
+ readonly bundleSha256: string;
132
+ close(): Promise<void>;
133
+ execute(request: CodeWorkerComputeRequest): Promise<JsonValue>;
134
+ readonly size: number;
135
+ }
136
+
137
+ export interface StartCodeWorkerPoolOptions extends StartCodeWorkerOptions {
138
+ readonly expectedBuild: Pick<CodeWorkerBuildResult, "graph" | "input">;
139
+ readonly initialWorker?: {
140
+ readonly build: Pick<CodeWorkerBuildResult, "graph" | "input">;
141
+ readonly session: CodeWorkerSession;
142
+ };
143
+ readonly maximumWorkers: number;
144
+ readonly registry: {
145
+ list(): readonly OperationDiscovery[];
146
+ };
147
+ readonly workflowInput: unknown;
148
+ readonly workflowInputMode?: "parsed" | "raw";
149
+ }
150
+
151
+ interface WaitingMessage {
152
+ readonly accept: (message: CodeWorkerMessage) => boolean;
153
+ readonly reject: (error: Error) => void;
154
+ readonly resolve: (message: CodeWorkerMessage) => void;
155
+ readonly completion: WorkerCompletion<CodeWorkerMessage>;
156
+ }
157
+
158
+ interface WaitingDiagnosticBarrier {
159
+ readonly reject: (error: Error) => void;
160
+ readonly resolve: () => void;
161
+ stderrObserved: boolean;
162
+ stdoutObserved: boolean;
163
+ readonly completion: WorkerCompletion<void>;
164
+ }
165
+
166
+ interface WaitingWorker {
167
+ readonly abortHandler?: () => void;
168
+ readonly abortSignal?: AbortSignal;
169
+ readonly reject: (error: Error) => void;
170
+ readonly resolve: (worker: CodeWorkerSession) => void;
171
+ }
172
+
173
+ function positiveBound(value: number | undefined, fallback: number, label: string): number {
174
+ const resolved = value ?? fallback;
175
+ if (!Number.isSafeInteger(resolved) || resolved < 1) {
176
+ throw new ApplicationError("usage", `${label} must be a positive safe integer.`);
177
+ }
178
+ return resolved;
179
+ }
180
+
181
+ function writeProtocol(stream: Socket, message: unknown): Promise<void> {
182
+ const frame = encodeWorkerFrame(message);
183
+ return new Promise((resolveWrite, rejectWrite) => {
184
+ stream.write(frame, error => {
185
+ if (error === null || error === undefined) resolveWrite();
186
+ else rejectWrite(error);
187
+ });
188
+ });
189
+ }
190
+
191
+ function workerFailure(message: CodeWorkerResponse & { readonly status: "error" }): ApplicationError {
192
+ switch (message.error.code) {
193
+ case "cancelled":
194
+ return new ApplicationError("cancelled", message.error.message);
195
+ case "unknown-compute":
196
+ return new ApplicationError("unsupported-plan", message.error.message);
197
+ case "execution":
198
+ return new ApplicationError("invalid-data", message.error.message);
199
+ case "invalid-output":
200
+ case "invalid-request":
201
+ return new ApplicationError("invalid-data", message.error.message);
202
+ case "internal":
203
+ return new ApplicationError("internal", message.error.message);
204
+ }
205
+ }
206
+
207
+ export function consumeCodeWorkerDiagnosticBarriers(
208
+ input: Uint8Array,
209
+ stream: "stderr" | "stdout",
210
+ barriers: readonly string[],
211
+ ): {
212
+ readonly diagnostics: Buffer<ArrayBufferLike>;
213
+ readonly observed: readonly string[];
214
+ } {
215
+ let diagnostics: Buffer<ArrayBufferLike> = Buffer.from(input);
216
+ const observed: string[] = [];
217
+ for (const barrier of barriers) {
218
+ const marker = Buffer.from(encodeCodeWorkerDiagnosticBarrier(barrier, stream));
219
+ const offset = diagnostics.indexOf(marker);
220
+ if (offset === -1) continue;
221
+ diagnostics = Buffer.concat([
222
+ diagnostics.subarray(0, offset),
223
+ diagnostics.subarray(offset + marker.byteLength),
224
+ ]);
225
+ observed.push(barrier);
226
+ }
227
+ return { diagnostics, observed: Object.freeze(observed) };
228
+ }
229
+
230
+ export async function waitForCodeWorkerResponseDiagnostics(
231
+ response: Promise<CodeWorkerMessage>,
232
+ diagnostics: Promise<void>,
233
+ requestId: string,
234
+ ): Promise<CodeWorkerResponse> {
235
+ const result = await response;
236
+ if (result.kind !== "response") {
237
+ throw new ApplicationError(
238
+ "invalid-data",
239
+ "Code worker returned a non-response message.",
240
+ );
241
+ }
242
+ if (result.diagnosticBarrier !== requestId) {
243
+ throw new ApplicationError(
244
+ "invalid-data",
245
+ "Code-worker diagnostic barrier identity mismatch.",
246
+ );
247
+ }
248
+ await diagnostics;
249
+ return result;
250
+ }
251
+
252
+ class RunningCodeWorker implements CodeWorkerSession {
253
+ readonly #effects = new WorkerEffectRuntime();
254
+ readonly #child: ChildProcess;
255
+ readonly #cleanupDirectories: readonly string[];
256
+ readonly #decoder = new WorkerFrameDecoder();
257
+ readonly #diagnosticBarriers = new Map<string, WaitingDiagnosticBarrier>();
258
+ readonly #maximumDiagnosticBytes: number;
259
+ readonly #protocol: Socket;
260
+ readonly #queued: CodeWorkerMessage[] = [];
261
+ readonly #timeoutMs: number;
262
+ readonly #waiting: WaitingMessage[] = [];
263
+ #buildCompleted = false;
264
+ #buildStarted = false;
265
+ #closed = false;
266
+ #closePromise: Promise<void> | undefined;
267
+ #fatal: Error | undefined;
268
+ #preparationGuardian: CodeWorkerLeaseGuardian | undefined;
269
+ #stderr: Buffer<ArrayBufferLike> = Buffer.alloc(0);
270
+ #stdout: Buffer<ArrayBufferLike> = Buffer.alloc(0);
271
+ bundleSha256 = "";
272
+ generation = 0;
273
+ readonly processId: number;
274
+ readonly processStartIdentity: string;
275
+
276
+ constructor(options: {
277
+ readonly child: ChildProcess;
278
+ readonly cleanupDirectories: readonly string[];
279
+ readonly maximumDiagnosticBytes: number;
280
+ readonly preparationGuardian: CodeWorkerLeaseGuardian;
281
+ readonly processStartIdentity: string;
282
+ readonly protocol: Socket;
283
+ readonly timeoutMs: number;
284
+ }) {
285
+ this.#child = options.child;
286
+ this.#cleanupDirectories = options.cleanupDirectories;
287
+ this.#maximumDiagnosticBytes = options.maximumDiagnosticBytes;
288
+ this.#preparationGuardian = options.preparationGuardian;
289
+ this.#protocol = options.protocol;
290
+ this.#timeoutMs = options.timeoutMs;
291
+ const processId = options.child.pid;
292
+ if (processId === undefined || processId < 2) {
293
+ throw new ApplicationError("subprocess", "Code worker has no valid process ID.");
294
+ }
295
+ this.processId = processId;
296
+ this.processStartIdentity = options.processStartIdentity;
297
+ this.#attach();
298
+ }
299
+
300
+ async initialize(expected: {
301
+ readonly bunRevision: string;
302
+ readonly bunVersion: string;
303
+ readonly bundleSha256: string;
304
+ readonly workerEntrySha256: string;
305
+ }): Promise<void> {
306
+ const hello = await this.#effects.run(this.#nextMessage(message => message.kind === "hello", this.#timeoutMs));
307
+ if (hello.kind !== "hello" || hello.bundleSha256 !== expected.bundleSha256) {
308
+ throw new ApplicationError(
309
+ "invalid-data",
310
+ "Code-worker handshake bundle identity mismatch.",
311
+ );
312
+ }
313
+ if (
314
+ hello.bunVersion !== expected.bunVersion
315
+ || hello.bunRevision !== expected.bunRevision
316
+ ) {
317
+ throw new ApplicationError(
318
+ "incompatible",
319
+ "Code-worker child Bun runtime attestation mismatch.",
320
+ );
321
+ }
322
+ if (hello.workerEntrySha256 !== expected.workerEntrySha256) {
323
+ throw new ApplicationError(
324
+ "incompatible",
325
+ "Code-worker implementation entry-byte attestation mismatch.",
326
+ );
327
+ }
328
+ this.bundleSha256 = hello.bundleSha256;
329
+ this.generation = hello.generation;
330
+ }
331
+
332
+ diagnostics(): CodeWorkerDiagnostics {
333
+ return {
334
+ stderr: new TextDecoder().decode(this.#stderr),
335
+ stdout: new TextDecoder().decode(this.#stdout),
336
+ };
337
+ }
338
+
339
+ async build(
340
+ registry: { list(): readonly OperationDiscovery[] },
341
+ workflowInput: unknown,
342
+ inputMode: "parsed" | "raw" = "raw",
343
+ ): Promise<CodeWorkerBuildResult> {
344
+ if (this.#buildStarted) {
345
+ throw new ApplicationError(
346
+ "conflict",
347
+ "Code-worker session already consumed its guarded authored build.",
348
+ );
349
+ }
350
+ this.#buildStarted = true;
351
+ const guardian = this.#preparationGuardian;
352
+ const request = this.#request(CodeWorkerRequestSchema.parse({
353
+ action: "build",
354
+ generation: this.generation,
355
+ inputMode,
356
+ kind: "request",
357
+ protocol: CODE_WORKER_PROTOCOL,
358
+ registry: [...registry.list()],
359
+ requestId: this.#requestId("build"),
360
+ workflowInput: JsonValueSchema.parse(workflowInput),
361
+ }), this.#timeoutMs);
362
+ if (guardian === undefined) {
363
+ const built = WorkerBuiltWorkflowSchema.parse(await request);
364
+ return {
365
+ diagnostics: this.diagnostics(),
366
+ graph: built.graph,
367
+ input: built.input,
368
+ };
369
+ }
370
+ let output: JsonValue;
371
+ try {
372
+ output = await Promise.race([
373
+ request,
374
+ guardian.failure,
375
+ ]);
376
+ } catch (error) {
377
+ try {
378
+ await this.close();
379
+ } catch (retirementError) {
380
+ throw new AggregateError(
381
+ [error, retirementError],
382
+ "Code-worker preparation failed and the worker could not be observably retired.",
383
+ );
384
+ }
385
+ throw error;
386
+ }
387
+ try {
388
+ await guardian.complete();
389
+ this.#preparationGuardian = undefined;
390
+ } catch (error) {
391
+ try {
392
+ await this.close();
393
+ } catch (retirementError) {
394
+ throw new AggregateError(
395
+ [error, retirementError],
396
+ "Code-worker preparation guardian failed and the worker could not be observably retired.",
397
+ );
398
+ }
399
+ throw error;
400
+ }
401
+ const built = WorkerBuiltWorkflowSchema.parse(output);
402
+ this.#buildCompleted = true;
403
+ return {
404
+ diagnostics: this.diagnostics(),
405
+ graph: built.graph,
406
+ input: built.input,
407
+ };
408
+ }
409
+
410
+ compute(request: CodeWorkerComputeRequest): Promise<JsonValue> {
411
+ return this.#queueCompute(request);
412
+ }
413
+
414
+ async #queueCompute(request: CodeWorkerComputeRequest): Promise<JsonValue> {
415
+ if (!this.#buildCompleted) throw new ApplicationError(
416
+ "conflict", "Code-worker compute requires one completed guarded authored build.",
417
+ );
418
+ let acquired = false;
419
+ const completion = this.#effects.completion<JsonValue>(0, () => undefined);
420
+ const abort = () => {
421
+ if (!acquired) completion.fail(new CodeWorkerCancellationError(request.nodeKey, true));
422
+ };
423
+ request.abortSignal?.addEventListener("abort", abort, { once: true });
424
+ if (request.abortSignal?.aborted) abort();
425
+ // A cancelled caller can leave immediately. Its ordered turn remains owned
426
+ // until predecessors settle, so later requests cannot overtake active work.
427
+ const fiber = this.#effects.fork(this.#effects.serial.withPermits(1)(Effect.gen(this, function*() {
428
+ acquired = true;
429
+ yield* workerValidation("admission", () => {
430
+ if (request.abortSignal?.aborted) throw new CodeWorkerCancellationError(request.nodeKey, true);
431
+ });
432
+ return yield* this.#computeProgram(request);
433
+ }).pipe(Effect.ensuring(Effect.sync(() => request.abortSignal?.removeEventListener("abort", abort))))));
434
+ fiber.addObserver(exit => completion.complete(exit));
435
+ return await this.#effects.run(completion.await);
436
+ }
437
+
438
+ #computeProgram(request: CodeWorkerComputeRequest): Effect.Effect<JsonValue, WorkerFailure> {
439
+ return Effect.gen(this, function*() {
440
+ const timeoutMs = yield* workerValidation("compute", () => {
441
+ if (request.abortSignal?.aborted) throw new CodeWorkerCancellationError(request.nodeKey, true);
442
+ return positiveBound(request.timeoutMs, this.#timeoutMs, "Code compute timeout");
443
+ });
444
+ const requestId = this.#requestId("compute");
445
+ const message = yield* workerValidation("protocol", () => CodeWorkerRequestSchema.parse({
446
+ action: "compute", computeKey: request.computeKey, generation: this.generation,
447
+ input: JsonValueSchema.parse(request.input), kind: "request", nodeKey: request.nodeKey,
448
+ protocol: CODE_WORKER_PROTOCOL, replayAcknowledged: request.replayAcknowledged, requestId,
449
+ }));
450
+ // The request remains owned until its actual terminal frame/diagnostics,
451
+ // even when a queued cancellation permits its caller to return earlier.
452
+ const computation = this.#effects.fork(this.#requestProgram(message, timeoutMs));
453
+ if (request.abortSignal === undefined) return yield* Fiber.join(computation);
454
+ const abortSignal = request.abortSignal;
455
+ const forced = this.#effects.completion<JsonValue>(0, () => undefined);
456
+ let abortStarted = false;
457
+ let forceStarted = false;
458
+ let settled = false;
459
+ let cancelAlarm: (() => void) | undefined;
460
+ const reusable = () => new CodeWorkerCancellationError(request.nodeKey, true);
461
+ const force = () => {
462
+ if (forceStarted || settled) return;
463
+ forceStarted = true;
464
+ cancelAlarm?.();
465
+ const error = new CodeWorkerCancellationError(request.nodeKey, false);
466
+ this.#rejectAll(error);
467
+ this.#child.kill("SIGKILL");
468
+ forced.fail(error);
469
+ };
470
+ const abort = () => {
471
+ if (abortStarted || settled) return;
472
+ abortStarted = true;
473
+ cancelAlarm = this.#effects.alarm(CODE_WORKER_CANCEL_GRACE_MS, force);
474
+ const cancellation = Effect.gen(this, function*() {
475
+ const request = yield* workerValidation("protocol", () => CodeWorkerRequestSchema.parse({
476
+ action: "cancel", generation: this.generation, kind: "request", protocol: CODE_WORKER_PROTOCOL,
477
+ requestId: this.#requestId("cancel"), targetRequestId: requestId,
478
+ }));
479
+ const output = yield* this.#requestProgram(request, this.#timeoutMs);
480
+ return yield* workerValidation("protocol", () => WorkerCancellationAcknowledgementSchema.parse(output));
481
+ });
482
+ const cancellationFiber = this.#effects.fork(Effect.match(cancellation, {
483
+ onFailure: () => { force(); },
484
+ onSuccess: acknowledgement => {
485
+ cancelAlarm?.();
486
+ cancelAlarm = undefined;
487
+ if (settled) return;
488
+ if (acknowledgement.cancellation === "active") {
489
+ cancelAlarm = this.#effects.alarm(CODE_WORKER_CANCEL_GRACE_MS, force);
490
+ } else {
491
+ forced.fail(reusable());
492
+ }
493
+ },
494
+ }));
495
+ cancellationFiber.addObserver(exit => { if (Exit.isFailure(exit)) force(); });
496
+ };
497
+ return yield* Effect.scoped(Effect.gen(this, function*() {
498
+ yield* Effect.acquireRelease(Effect.sync(() => {
499
+ abortSignal.addEventListener("abort", abort, { once: true });
500
+ if (abortSignal.aborted) abort();
501
+ }), () => Effect.sync(() => {
502
+ settled = true;
503
+ cancelAlarm?.();
504
+ abortSignal.removeEventListener("abort", abort);
505
+ }));
506
+ const outcome = yield* Effect.either(Effect.raceFirst(Fiber.join(computation), forced.await));
507
+ return yield* workerValidation("compute", () => {
508
+ if (outcome._tag === "Left") {
509
+ const error = outcome.left.cause;
510
+ if (error instanceof CodeWorkerCancellationError) throw error;
511
+ if (abortSignal.aborted) throw new CodeWorkerCancellationError(
512
+ request.nodeKey,
513
+ this.#fatal === undefined && this.#child.exitCode === null && this.#child.signalCode === null,
514
+ );
515
+ throw error;
516
+ }
517
+ if (abortSignal.aborted) throw reusable();
518
+ return JsonValueSchema.parse(outcome.right);
519
+ });
520
+ }));
521
+ });
522
+ }
523
+
524
+ close(): Promise<void> {
525
+ this.#closePromise ??= this.#close().finally(async () => await this.#effects.close());
526
+ return this.#closePromise;
527
+ }
528
+
529
+ async #close(): Promise<void> {
530
+ // Publish closure before the asynchronous shutdown handshake so no racing
531
+ // caller can enqueue work behind shutdown.
532
+ this.#closed = true;
533
+ if (
534
+ this.#fatal === undefined
535
+ && this.#child.exitCode === null
536
+ && this.#child.signalCode === null
537
+ ) {
538
+ try {
539
+ await this.#request(CodeWorkerRequestSchema.parse({
540
+ action: "shutdown",
541
+ generation: this.generation,
542
+ kind: "request",
543
+ protocol: CODE_WORKER_PROTOCOL,
544
+ requestId: this.#requestId("shutdown"),
545
+ }), this.#timeoutMs, true);
546
+ } catch {
547
+ this.#child.kill("SIGTERM");
548
+ }
549
+ }
550
+ if (this.#child.exitCode === null && this.#child.signalCode === null) {
551
+ if (!await this.#waitForExit(100)) {
552
+ this.#child.kill("SIGTERM");
553
+ }
554
+ if (!await this.#waitForExit(500)) {
555
+ this.#child.kill("SIGKILL");
556
+ }
557
+ if (!await this.#waitForExit(1_000)) {
558
+ const error = new ApplicationError(
559
+ "subprocess",
560
+ "Code worker did not exit after SIGKILL; its temporary files were retained.",
561
+ );
562
+ const preparationGuardian = this.#preparationGuardian;
563
+ if (preparationGuardian !== undefined) {
564
+ preparationGuardian.retireDetached();
565
+ this.#preparationGuardian = undefined;
566
+ }
567
+ this.#rejectAll(error);
568
+ this.#detachProcessHandles();
569
+ throw error;
570
+ }
571
+ }
572
+ const preparationGuardian = this.#preparationGuardian;
573
+ this.#rejectAll(new ApplicationError("cancelled", "Code-worker session closed."));
574
+ this.#detachProcessHandles();
575
+ const cleanup = Promise.all(this.#cleanupDirectories.map(async directory => {
576
+ await rm(directory, { force: true, recursive: true });
577
+ })).then(() => undefined);
578
+ const guardianCompletion = preparationGuardian === undefined
579
+ ? Promise.resolve()
580
+ : preparationGuardian.complete();
581
+ if (preparationGuardian !== undefined) {
582
+ this.#preparationGuardian = undefined;
583
+ }
584
+ const settled = await Promise.allSettled([cleanup, guardianCompletion]);
585
+ const failures: unknown[] = [];
586
+ for (const result of settled) {
587
+ if (result.status === "rejected") failures.push(result.reason as unknown);
588
+ }
589
+ if (failures.length === 1) throw failures[0];
590
+ if (failures.length > 1) {
591
+ throw new AggregateError(
592
+ failures,
593
+ "Code-worker files and preparation guardian both failed to close.",
594
+ );
595
+ }
596
+ }
597
+
598
+ #detachProcessHandles(): void {
599
+ this.#protocol.destroy();
600
+ this.#child.stdin?.destroy();
601
+ this.#child.stdout?.destroy();
602
+ this.#child.stderr?.destroy();
603
+ this.#child.unref();
604
+ }
605
+
606
+ async #waitForExit(timeoutMs: number): Promise<boolean> {
607
+ if (this.#child.exitCode !== null || this.#child.signalCode !== null) return true;
608
+ const completion = this.#effects.completion<boolean>(timeoutMs, () => completion.succeed(false));
609
+ const onExit = () => completion.succeed(true);
610
+ this.#child.once("exit", onExit);
611
+ completion.activate();
612
+ if (this.#child.exitCode !== null || this.#child.signalCode !== null) onExit();
613
+ return await this.#effects.run(completion.await.pipe(Effect.ensuring(Effect.sync(() => {
614
+ this.#child.off("exit", onExit);
615
+ }))));
616
+ }
617
+
618
+ #attach(): void {
619
+ this.#protocol.on("data", (chunk: Buffer) => {
620
+ try {
621
+ for (const message of this.#decoder.push(chunk)) this.#deliver(message);
622
+ } catch (error) {
623
+ this.#rejectAll(new ApplicationError(
624
+ "invalid-data",
625
+ `Invalid code-worker protocol: ${String(error)}`,
626
+ ));
627
+ this.#child.kill("SIGKILL");
628
+ }
629
+ });
630
+ this.#protocol.on("error", error => this.#rejectAll(
631
+ new ApplicationError("subprocess", `Code-worker protocol failed: ${error.message}`),
632
+ ));
633
+ this.#child.stdout?.on("data", (chunk: Buffer) => {
634
+ this.#recordDiagnostic("stdout", chunk);
635
+ });
636
+ this.#child.stderr?.on("data", (chunk: Buffer) => {
637
+ this.#recordDiagnostic("stderr", chunk);
638
+ });
639
+ this.#child.on("error", error => this.#rejectAll(
640
+ new ApplicationError("subprocess", `Could not start code worker: ${error.message}`),
641
+ ));
642
+ this.#child.on("exit", (code, signal) => {
643
+ if (this.#closed) return;
644
+ this.#rejectAll(new ApplicationError(
645
+ "subprocess",
646
+ `Code worker exited before completing (code ${String(code)}, signal ${String(signal)}).`,
647
+ { ...this.diagnostics() },
648
+ ));
649
+ });
650
+ }
651
+
652
+ #deliver(message: CodeWorkerMessage): void {
653
+ const index = this.#waiting.findIndex(waiter => waiter.accept(message));
654
+ if (index === -1) {
655
+ this.#queued.push(message);
656
+ return;
657
+ }
658
+ const [waiter] = this.#waiting.splice(index, 1);
659
+ if (waiter !== undefined) {
660
+ waiter.resolve(message);
661
+ }
662
+ }
663
+
664
+ #nextMessage(
665
+ accept: (message: CodeWorkerMessage) => boolean,
666
+ timeoutMs: number,
667
+ ): Effect.Effect<CodeWorkerMessage, WorkerFailure> {
668
+ if (this.#fatal !== undefined) return workerValidation("protocol", () => { throw this.#fatal; });
669
+ const index = this.#queued.findIndex(accept);
670
+ if (index !== -1) {
671
+ const message = this.#queued.splice(index, 1)[0];
672
+ if (message !== undefined) return Effect.succeed(message);
673
+ }
674
+ const completion = this.#effects.completion<CodeWorkerMessage>(timeoutMs, () => {
675
+ const index = this.#waiting.indexOf(waiter);
676
+ if (index === -1) return;
677
+ this.#waiting.splice(index, 1);
678
+ const error = new ApplicationError("subprocess", "Timed out waiting for the code worker.");
679
+ completion.fail(error);
680
+ this.#rejectAll(error);
681
+ this.#child.kill("SIGKILL");
682
+ });
683
+ const waiter: WaitingMessage = { accept, completion, reject: completion.fail, resolve: completion.succeed };
684
+ this.#waiting.push(waiter);
685
+ completion.activate();
686
+ return completion.await;
687
+ }
688
+
689
+ async #request(message: CodeWorkerRequest, timeoutMs: number, allowClosed = false): Promise<JsonValue> {
690
+ return await this.#effects.run(this.#requestProgram(message, timeoutMs, allowClosed));
691
+ }
692
+
693
+ #requestProgram(message: CodeWorkerRequest, timeoutMs: number, allowClosed = false): Effect.Effect<JsonValue, WorkerFailure> {
694
+ return Effect.gen(this, function*() {
695
+ yield* workerValidation("protocol", () => {
696
+ if (this.#closed && !allowClosed) throw new ApplicationError("conflict", "Code-worker session is already closed.");
697
+ });
698
+ // Reserve both native tables before writing. Native callbacks choose the
699
+ // winner synchronously; the Effect continuations only observe that choice.
700
+ const response = this.#nextMessage(candidate => (
701
+ candidate.kind === "response" && candidate.requestId === message.requestId
702
+ ), timeoutMs);
703
+ const diagnostics = this.#waitForDiagnosticBarrier(message.requestId, timeoutMs);
704
+ const outcome = yield* Effect.exit(Effect.gen(this, function*() {
705
+ yield* workerBoundary("protocol", () => writeProtocol(this.#protocol, message));
706
+ const candidate = yield* response;
707
+ const result = yield* workerValidation("protocol", () => {
708
+ if (candidate.kind !== "response") throw new ApplicationError("invalid-data", "Code worker returned a non-response message.");
709
+ if (candidate.diagnosticBarrier !== message.requestId) throw new ApplicationError("invalid-data", "Code-worker response diagnostic barrier identity mismatch.");
710
+ return candidate;
711
+ });
712
+ yield* diagnostics;
713
+ return yield* workerValidation("protocol", () => {
714
+ if (result.status === "error") throw workerFailure(result);
715
+ return JsonValueSchema.parse(result.output);
716
+ });
717
+ }));
718
+ if (Exit.isFailure(outcome)) {
719
+ this.#cancelDiagnosticBarrier(message.requestId, new ApplicationError("subprocess", "Code-worker request failed."));
720
+ }
721
+ return yield* outcome;
722
+ });
723
+ }
724
+
725
+ #rejectAll(error: Error): void {
726
+ this.#fatal ??= error;
727
+ for (const waiter of this.#waiting.splice(0)) {
728
+ waiter.reject(error);
729
+ }
730
+ for (const [barrier, waiter] of this.#diagnosticBarriers) {
731
+ this.#diagnosticBarriers.delete(barrier);
732
+ waiter.reject(error);
733
+ }
734
+ }
735
+
736
+ #waitForDiagnosticBarrier(barrier: string, timeoutMs: number): Effect.Effect<void, WorkerFailure> {
737
+ if (this.#fatal !== undefined) return workerValidation("protocol", () => { throw this.#fatal; });
738
+ if (this.#diagnosticBarriers.has(barrier)) {
739
+ return workerValidation("protocol", () => {
740
+ throw new ApplicationError(
741
+ "internal", `Duplicate code-worker diagnostic barrier: ${barrier}`,
742
+ );
743
+ });
744
+ }
745
+ const completion = this.#effects.completion<void>(timeoutMs, () => {
746
+ const waiter = this.#diagnosticBarriers.get(barrier);
747
+ if (waiter === undefined) return;
748
+ this.#diagnosticBarriers.delete(barrier);
749
+ const error = new ApplicationError("subprocess", "Timed out waiting for code-worker diagnostics.");
750
+ completion.fail(error);
751
+ this.#rejectAll(error);
752
+ this.#child.kill("SIGKILL");
753
+ });
754
+ this.#diagnosticBarriers.set(barrier, {
755
+ completion, reject: completion.fail, resolve: () => completion.succeed(undefined),
756
+ stderrObserved: false, stdoutObserved: false,
757
+ });
758
+ completion.activate();
759
+ return completion.await;
760
+ }
761
+
762
+ #cancelDiagnosticBarrier(barrier: string, error: Error): void {
763
+ const waiter = this.#diagnosticBarriers.get(barrier);
764
+ if (waiter === undefined) return;
765
+ this.#diagnosticBarriers.delete(barrier);
766
+ waiter.reject(error);
767
+ }
768
+
769
+ #recordDiagnostic(stream: "stderr" | "stdout", chunk: Buffer): void {
770
+ let value: Buffer<ArrayBufferLike> = Buffer.concat([
771
+ stream === "stderr" ? this.#stderr : this.#stdout,
772
+ chunk,
773
+ ]);
774
+ const pendingBarriers = [...this.#diagnosticBarriers].filter(([, waiter]) => (
775
+ stream === "stderr" ? !waiter.stderrObserved : !waiter.stdoutObserved
776
+ ));
777
+ const consumed = consumeCodeWorkerDiagnosticBarriers(
778
+ value,
779
+ stream,
780
+ pendingBarriers.map(([barrier]) => barrier),
781
+ );
782
+ value = consumed.diagnostics;
783
+ for (const barrier of consumed.observed) {
784
+ const waiter = this.#diagnosticBarriers.get(barrier);
785
+ if (waiter === undefined) continue;
786
+ if (stream === "stderr") waiter.stderrObserved = true;
787
+ else waiter.stdoutObserved = true;
788
+ if (!waiter.stderrObserved || !waiter.stdoutObserved) continue;
789
+ this.#diagnosticBarriers.delete(barrier);
790
+ waiter.resolve();
791
+ }
792
+ const pendingBarrierCount = [...this.#diagnosticBarriers.values()].filter(waiter => (
793
+ stream === "stderr" ? !waiter.stderrObserved : !waiter.stdoutObserved
794
+ )).length;
795
+ const maximumBufferedBytes = this.#maximumDiagnosticBytes + (
796
+ pendingBarrierCount * MAX_CODE_WORKER_DIAGNOSTIC_BARRIER_BYTES
797
+ );
798
+ if (stream === "stderr") this.#stderr = value;
799
+ else this.#stdout = value;
800
+ if (value.byteLength <= maximumBufferedBytes) return;
801
+ const label = stream === "stderr" ? "stderr" : "stdout";
802
+ if (stream === "stderr") {
803
+ this.#stderr = value.subarray(0, this.#maximumDiagnosticBytes);
804
+ } else {
805
+ this.#stdout = value.subarray(0, this.#maximumDiagnosticBytes);
806
+ }
807
+ this.#rejectAll(new ApplicationError(
808
+ "subprocess",
809
+ `Code-worker ${label} exceeded its diagnostic bound.`,
810
+ ));
811
+ this.#child.kill("SIGKILL");
812
+ }
813
+
814
+ #requestId(prefix: string): string {
815
+ return `${prefix}_${randomUUID().replaceAll("-", "")}`;
816
+ }
817
+ }
818
+
819
+ interface CodeWorkerLeaseGuardian {
820
+ complete(): Promise<void>;
821
+ readonly failure: Promise<never>;
822
+ retireDetached(): void;
823
+ }
824
+
825
+ function childExit(child: ChildProcess): Promise<{
826
+ readonly code: number | null;
827
+ readonly signal: NodeJS.Signals | null;
828
+ }> {
829
+ if (child.exitCode !== null || child.signalCode !== null) {
830
+ return Promise.resolve({ code: child.exitCode, signal: child.signalCode });
831
+ }
832
+ return new Promise(resolveExit => {
833
+ child.once("exit", (code, signal) => resolveExit({ code, signal }));
834
+ });
835
+ }
836
+
837
+ async function startCodeWorkerLeaseGuardian(options: {
838
+ readonly bunExecutable: string;
839
+ readonly inheritedFileDescriptor: number;
840
+ readonly workerProcessId: number;
841
+ readonly workerStartIdentity: string;
842
+ }): Promise<CodeWorkerLeaseGuardian> {
843
+ if (
844
+ process.platform === "win32"
845
+ || options.inheritedFileDescriptor === 0
846
+ ) {
847
+ return {
848
+ complete: () => Promise.resolve(),
849
+ failure: new Promise<never>(() => undefined),
850
+ retireDetached: () => undefined,
851
+ };
852
+ }
853
+ if (options.inheritedFileDescriptor < 3) {
854
+ throw new ApplicationError(
855
+ "internal",
856
+ "Code-worker host-resource lease descriptor is unsafe to inherit.",
857
+ );
858
+ }
859
+ const guardianEntryPath = join(import.meta.dir, "worker-lease-guardian.ts");
860
+ const guardian = spawn(
861
+ options.bunExecutable,
862
+ [
863
+ "run",
864
+ guardianEntryPath,
865
+ "--worker-pid",
866
+ String(options.workerProcessId),
867
+ "--worker-start-identity",
868
+ options.workerStartIdentity,
869
+ ],
870
+ {
871
+ env: {
872
+ LANG: "C.UTF-8",
873
+ LC_ALL: "C.UTF-8",
874
+ PATH: dirname(options.bunExecutable),
875
+ },
876
+ stdio: ["pipe", "pipe", "pipe", options.inheritedFileDescriptor],
877
+ },
878
+ );
879
+ if (
880
+ guardian.stdin === null
881
+ || guardian.stdout === null
882
+ || guardian.stderr === null
883
+ ) {
884
+ guardian.kill("SIGKILL");
885
+ throw new ApplicationError(
886
+ "subprocess",
887
+ "Code-worker lease guardian did not expose its control streams.",
888
+ );
889
+ }
890
+ let diagnostics = Buffer.alloc(0);
891
+ guardian.stderr.on("data", (chunk: Uint8Array) => {
892
+ diagnostics = Buffer.concat([diagnostics, chunk]).subarray(0, 2_000);
893
+ });
894
+ let settlementRequested: "complete" | "retire" | undefined;
895
+ const exit = childExit(guardian);
896
+ try {
897
+ await new Promise<void>((resolveReady, rejectReady) => {
898
+ let readyBytes = Buffer.alloc(0);
899
+ let settled = false;
900
+ const settle = (error?: Error) => {
901
+ if (settled) return;
902
+ settled = true;
903
+ clearTimeout(timeout);
904
+ guardian.stdout?.removeListener("data", onData);
905
+ if (error === undefined) resolveReady();
906
+ else rejectReady(error);
907
+ };
908
+ const onData = (chunk: Uint8Array) => {
909
+ readyBytes = Buffer.concat([readyBytes, chunk]);
910
+ if (readyBytes.byteLength > 64) {
911
+ settle(new ApplicationError(
912
+ "subprocess",
913
+ "Code-worker lease guardian exceeded its handshake bound.",
914
+ ));
915
+ return;
916
+ }
917
+ const text = readyBytes.toString("utf8");
918
+ if (text === "ready\n") settle();
919
+ else if (!"ready\n".startsWith(text)) {
920
+ settle(new ApplicationError(
921
+ "subprocess",
922
+ "Code-worker lease guardian returned an invalid handshake.",
923
+ ));
924
+ }
925
+ };
926
+ const timeout = setTimeout(() => {
927
+ settle(new ApplicationError(
928
+ "subprocess",
929
+ "Timed out waiting for the code-worker lease guardian.",
930
+ ));
931
+ }, 2_000);
932
+ guardian.stdout?.on("data", onData);
933
+ void exit.then(result => settle(new ApplicationError(
934
+ "subprocess",
935
+ `Code-worker lease guardian exited during handshake (code ${String(result.code)}, signal ${String(result.signal)}).`,
936
+ )));
937
+ });
938
+ guardian.stdout.resume();
939
+ } catch (error) {
940
+ guardian.kill("SIGTERM");
941
+ await exit;
942
+ throw error;
943
+ }
944
+ const failure = new Promise<never>((_resolve, reject) => {
945
+ void exit.then(result => {
946
+ if (settlementRequested !== undefined) return;
947
+ const detail = new TextDecoder().decode(diagnostics).trim();
948
+ reject(new ApplicationError(
949
+ "subprocess",
950
+ `Code-worker lease guardian exited before compute settled (code ${String(result.code)}, signal ${String(result.signal)})${detail === "" ? "." : `: ${detail}`}`,
951
+ ));
952
+ });
953
+ });
954
+ // The guardian can fail between its ready byte and the first guarded
955
+ // operation. Mark the promise observed immediately; later races still
956
+ // receive the original rejection.
957
+ void failure.catch(() => undefined);
958
+
959
+ return {
960
+ complete: async () => {
961
+ if (settlementRequested === "retire") {
962
+ throw new ApplicationError(
963
+ "conflict",
964
+ "Code-worker lease guardian already owns detached retirement.",
965
+ );
966
+ }
967
+ if (settlementRequested === undefined) {
968
+ settlementRequested = "complete";
969
+ if (guardian.exitCode === null && guardian.signalCode === null) {
970
+ await new Promise<void>((resolveWrite, rejectWrite) => {
971
+ const control = guardian.stdin;
972
+ if (control === null) {
973
+ rejectWrite(new Error("Code-worker lease guardian control pipe is missing."));
974
+ return;
975
+ }
976
+ const onError = (error: Error) => rejectWrite(error);
977
+ control.once("error", onError);
978
+ control.end("complete\n", () => {
979
+ control.removeListener("error", onError);
980
+ resolveWrite();
981
+ });
982
+ }).catch(error => {
983
+ if (guardian.exitCode === null && guardian.signalCode === null) throw error;
984
+ });
985
+ }
986
+ }
987
+ const result = await exit;
988
+ if (result.code !== 0) {
989
+ const detail = new TextDecoder().decode(diagnostics).trim();
990
+ throw new ApplicationError(
991
+ "subprocess",
992
+ `Code-worker lease guardian failed (code ${String(result.code)}, signal ${String(result.signal)})${detail === "" ? "." : `: ${detail}`}`,
993
+ );
994
+ }
995
+ },
996
+ failure,
997
+ retireDetached: () => {
998
+ if (settlementRequested !== undefined) return;
999
+ settlementRequested = "retire";
1000
+ // Closing the control pipe without `complete` transfers exact worker
1001
+ // kill-and-observe ownership to the guardian. Detach every parent-side
1002
+ // handle so an uninterruptible worker cannot keep this CLI alive; the
1003
+ // guardian retains the inherited kernel lease until exit is observable.
1004
+ guardian.stdin?.destroy();
1005
+ guardian.stdout?.destroy();
1006
+ guardian.stderr?.destroy();
1007
+ guardian.unref();
1008
+ },
1009
+ };
1010
+ }
1011
+
1012
+ class RunningCodeWorkerPool implements CodeWorkerPool {
1013
+ readonly #effects = new WorkerEffectRuntime();
1014
+ readonly #bunExecutable: string;
1015
+ readonly #executions = new Set<Fiber.RuntimeFiber<Exit.Exit<JsonValue, WorkerFailure>, never>>();
1016
+ readonly #idle: CodeWorkerSession[];
1017
+ readonly #leased = new Set<CodeWorkerSession>();
1018
+ readonly #live: Set<CodeWorkerSession>;
1019
+ readonly #retiring = new Map<CodeWorkerSession, Promise<void>>();
1020
+ readonly #waiting: WaitingWorker[] = [];
1021
+ #closed = false;
1022
+ #closePromise: Promise<void> | undefined;
1023
+ readonly bundleSha256: string;
1024
+ readonly size: number;
1025
+
1026
+ constructor(
1027
+ bundleSha256: string,
1028
+ workers: readonly CodeWorkerSession[],
1029
+ bunExecutable: string,
1030
+ ) {
1031
+ this.#bunExecutable = bunExecutable;
1032
+ this.bundleSha256 = bundleSha256;
1033
+ this.#idle = [...workers];
1034
+ this.#live = new Set(workers);
1035
+ this.size = workers.length;
1036
+ }
1037
+
1038
+ async execute(request: CodeWorkerComputeRequest): Promise<JsonValue> {
1039
+ const admission = this.#acquire(request.abortSignal);
1040
+ const fiber = this.#effects.fork(Effect.exit(this.#executeProgram(request, admission)));
1041
+ this.#executions.add(fiber);
1042
+ fiber.addObserver(() => { this.#executions.delete(fiber); });
1043
+ return await this.#effects.run(Effect.flatMap(Fiber.join(fiber), exit => exit));
1044
+ }
1045
+
1046
+ #executeProgram(request: CodeWorkerComputeRequest, admission: Effect.Effect<CodeWorkerSession, WorkerFailure>): Effect.Effect<JsonValue, WorkerFailure> {
1047
+ return Effect.gen(this, function*() {
1048
+ const worker = yield* admission;
1049
+ const retire = workerBoundary("retirement", () => this.#retire(worker));
1050
+ const started = yield* Effect.either(workerBoundary("admission", () => startCodeWorkerLeaseGuardian({
1051
+ bunExecutable: this.#bunExecutable,
1052
+ inheritedFileDescriptor: request.inheritedHostResourceFileDescriptor ?? 0,
1053
+ workerProcessId: worker.processId,
1054
+ workerStartIdentity: worker.processStartIdentity,
1055
+ })));
1056
+ if (started._tag === "Left") {
1057
+ const retired = yield* Effect.either(retire);
1058
+ return yield* workerValidation("admission", () => {
1059
+ if (retired._tag === "Left") throw new AggregateError(
1060
+ [started.left.cause, retired.left.cause],
1061
+ "Code-worker guardian startup failed and the worker could not be observably retired.",
1062
+ );
1063
+ throw started.left.cause;
1064
+ });
1065
+ }
1066
+ const guardian = started.right;
1067
+ const complete = workerBoundary("retirement", () => guardian.complete());
1068
+ const computation = yield* Effect.either(Effect.raceFirst(
1069
+ workerBoundary("compute", () => worker.compute(request)),
1070
+ workerBoundary("retirement", () => guardian.failure),
1071
+ ));
1072
+ if (computation._tag === "Left") {
1073
+ const error = computation.left.cause;
1074
+ if (error instanceof CodeWorkerCancellationError && error.workerReusable) {
1075
+ const completed = yield* Effect.either(complete);
1076
+ if (completed._tag === "Left") {
1077
+ const retired = yield* Effect.either(retire);
1078
+ return yield* workerValidation("retirement", () => {
1079
+ if (retired._tag === "Left") throw new AggregateError(
1080
+ [error, completed.left.cause, retired.left.cause],
1081
+ "Cancelled code worker lost its guardian and could not be observably retired.",
1082
+ );
1083
+ throw new AggregateError([error, completed.left.cause], "Cancelled code worker's lease guardian failed.");
1084
+ });
1085
+ }
1086
+ if (this.#closed) yield* retire;
1087
+ else this.#release(worker);
1088
+ return yield* Effect.fail(computation.left);
1089
+ }
1090
+ // Only confirmed native retirement permits guardian completion.
1091
+ const retired = yield* Effect.either(retire);
1092
+ if (retired._tag === "Left") {
1093
+ guardian.retireDetached();
1094
+ return yield* workerValidation("retirement", () => {
1095
+ throw new AggregateError(
1096
+ [error, retired.left.cause], "Code worker failed and could not be observably retired.",
1097
+ );
1098
+ });
1099
+ }
1100
+ const completed = yield* Effect.either(complete);
1101
+ if (completed._tag === "Left") return yield* workerValidation("retirement", () => {
1102
+ throw new AggregateError(
1103
+ [error, completed.left.cause], "Code worker failed after its lease guardian also failed.",
1104
+ );
1105
+ });
1106
+ return yield* Effect.fail(computation.left);
1107
+ }
1108
+ const output = computation.right;
1109
+ if (request.abortSignal?.aborted) {
1110
+ const completed = yield* Effect.either(complete);
1111
+ if (completed._tag === "Left") {
1112
+ const retired = yield* Effect.either(retire);
1113
+ return yield* workerValidation("retirement", () => {
1114
+ if (retired._tag === "Left") throw new AggregateError(
1115
+ [completed.left.cause, retired.left.cause], "Cancelled code worker failed guardian completion and retirement.",
1116
+ );
1117
+ throw completed.left.cause;
1118
+ });
1119
+ }
1120
+ if (this.#closed) yield* retire;
1121
+ else this.#release(worker);
1122
+ return yield* workerValidation("compute", () => { throw new CodeWorkerCancellationError(request.nodeKey, true); });
1123
+ }
1124
+ if (this.#closed) {
1125
+ const retired = yield* Effect.either(retire);
1126
+ if (retired._tag === "Left") {
1127
+ guardian.retireDetached();
1128
+ return yield* workerValidation("retirement", () => {
1129
+ throw new AggregateError(
1130
+ [retired.left.cause], "Completed code worker could not be observably retired.",
1131
+ );
1132
+ });
1133
+ }
1134
+ const completed = yield* Effect.either(complete);
1135
+ if (completed._tag === "Left") return yield* workerValidation("retirement", () => {
1136
+ throw new AggregateError(
1137
+ [completed.left.cause], "Retired code worker's lease guardian failed to complete.",
1138
+ );
1139
+ });
1140
+ return output;
1141
+ }
1142
+ const completed = yield* Effect.either(complete);
1143
+ if (completed._tag === "Left") {
1144
+ const retired = yield* Effect.either(retire);
1145
+ if (retired._tag === "Left") {
1146
+ guardian.retireDetached();
1147
+ return yield* workerValidation("retirement", () => {
1148
+ throw new AggregateError(
1149
+ [completed.left.cause, retired.left.cause], "Code-worker completion failed and the worker could not be observably retired.",
1150
+ );
1151
+ });
1152
+ }
1153
+ return yield* Effect.fail(completed.left);
1154
+ }
1155
+ this.#release(worker);
1156
+ return output;
1157
+ });
1158
+ }
1159
+
1160
+ close(): Promise<void> {
1161
+ this.#closePromise ??= this.#close().finally(async () => await this.#effects.close());
1162
+ return this.#closePromise;
1163
+ }
1164
+
1165
+ async #close(): Promise<void> {
1166
+ this.#closed = true;
1167
+ const error = new ApplicationError("cancelled", "Code-worker pool closed.");
1168
+ this.#rejectWaiting(error);
1169
+ this.#idle.splice(0);
1170
+ const retirements = [...this.#live].map(worker => this.#retire(worker));
1171
+ const retirementSettled = Promise.allSettled([
1172
+ ...new Set([...retirements, ...this.#retiring.values()]),
1173
+ ]);
1174
+ // Execution failures belong to their callers. Closure joins them only so
1175
+ // every guardian and inherited lease settles before the pool returns.
1176
+ await this.#effects.run(Effect.forEach([...this.#executions], fiber => Effect.asVoid(Fiber.join(fiber)), { concurrency: "unbounded", discard: true }));
1177
+ const failure = (await retirementSettled).find(
1178
+ result => result.status === "rejected",
1179
+ );
1180
+ if (failure?.status === "rejected") throw failure.reason;
1181
+ }
1182
+
1183
+ #acquire(abortSignal: AbortSignal | undefined): Effect.Effect<CodeWorkerSession, WorkerFailure> {
1184
+ if (this.#closed) {
1185
+ return workerValidation("admission", () => { throw new ApplicationError("conflict", "Code-worker pool is closed."); });
1186
+ }
1187
+ if (abortSignal?.aborted === true) {
1188
+ return workerValidation("admission", () => {
1189
+ throw new ApplicationError(
1190
+ "cancelled", "Compute was cancelled before a code worker became available.",
1191
+ );
1192
+ });
1193
+ }
1194
+ const worker = this.#idle.shift();
1195
+ if (worker !== undefined) {
1196
+ this.#leased.add(worker);
1197
+ return Effect.succeed(worker);
1198
+ }
1199
+ if (this.#live.size === 0) {
1200
+ return workerValidation("admission", () => {
1201
+ throw new ApplicationError(
1202
+ "subprocess", "No healthy code worker remains in the pool.",
1203
+ );
1204
+ });
1205
+ }
1206
+ const completion = this.#effects.completion<CodeWorkerSession>(0, () => undefined);
1207
+ const waiting: WaitingWorker = {
1208
+ ...(abortSignal === undefined ? {} : {
1209
+ abortSignal,
1210
+ abortHandler: () => {
1211
+ const index = this.#waiting.indexOf(waiting);
1212
+ if (index === -1) return;
1213
+ this.#waiting.splice(index, 1);
1214
+ completion.fail(new ApplicationError("cancelled", "Compute was cancelled while waiting for a code worker."));
1215
+ },
1216
+ }),
1217
+ reject: completion.fail,
1218
+ resolve: completion.succeed,
1219
+ };
1220
+ if (waiting.abortHandler !== undefined) abortSignal?.addEventListener("abort", waiting.abortHandler, { once: true });
1221
+ this.#waiting.push(waiting);
1222
+ if (abortSignal?.aborted) waiting.abortHandler?.();
1223
+ return completion.await;
1224
+ }
1225
+
1226
+ #release(worker: CodeWorkerSession): void {
1227
+ if (!this.#leased.delete(worker) || !this.#live.has(worker)) return;
1228
+ const waiting = this.#waiting.shift();
1229
+ if (waiting === undefined) {
1230
+ this.#idle.push(worker);
1231
+ return;
1232
+ }
1233
+ this.#detachWaiting(waiting);
1234
+ this.#leased.add(worker);
1235
+ waiting.resolve(worker);
1236
+ }
1237
+
1238
+ #retire(worker: CodeWorkerSession): Promise<void> {
1239
+ const existing = this.#retiring.get(worker);
1240
+ if (existing !== undefined) return existing;
1241
+ this.#leased.delete(worker);
1242
+ this.#live.delete(worker);
1243
+ const idleIndex = this.#idle.indexOf(worker);
1244
+ if (idleIndex !== -1) this.#idle.splice(idleIndex, 1);
1245
+ const retirement = Promise.resolve().then(async () => {
1246
+ try {
1247
+ await worker.close();
1248
+ } finally {
1249
+ if (this.#live.size === 0 && !this.#closed) {
1250
+ this.#rejectWaiting(new ApplicationError(
1251
+ "subprocess",
1252
+ "No healthy code worker remains in the pool.",
1253
+ ));
1254
+ }
1255
+ }
1256
+ });
1257
+ this.#retiring.set(worker, retirement);
1258
+ void retirement.finally(() => {
1259
+ this.#retiring.delete(worker);
1260
+ }).catch(() => undefined);
1261
+ return retirement;
1262
+ }
1263
+
1264
+ #detachWaiting(waiting: WaitingWorker): void {
1265
+ if (waiting.abortHandler !== undefined) {
1266
+ waiting.abortSignal?.removeEventListener("abort", waiting.abortHandler);
1267
+ }
1268
+ }
1269
+
1270
+ #rejectWaiting(error: Error): void {
1271
+ for (const waiting of this.#waiting.splice(0)) {
1272
+ this.#detachWaiting(waiting);
1273
+ waiting.reject(error);
1274
+ }
1275
+ }
1276
+ }
1277
+
1278
+ function assertExpectedBuild(
1279
+ actualInput: Pick<CodeWorkerBuildResult, "graph" | "input">,
1280
+ expectedInput: Pick<CodeWorkerBuildResult, "graph" | "input">,
1281
+ ): void {
1282
+ const actual = WorkerBuiltWorkflowSchema.parse({
1283
+ graph: normalizeAuthoredWorkflowGraph(actualInput.graph),
1284
+ input: actualInput.input,
1285
+ });
1286
+ const expected = WorkerBuiltWorkflowSchema.parse({
1287
+ graph: normalizeAuthoredWorkflowGraph(expectedInput.graph),
1288
+ input: expectedInput.input,
1289
+ });
1290
+ if (canonicalJson(actual) !== canonicalJson(expected)) {
1291
+ throw new ApplicationError(
1292
+ "incompatible",
1293
+ "Code worker rebuilt a different workflow graph or normalized input.",
1294
+ );
1295
+ }
1296
+ }
1297
+
1298
+ function listenProtocolServer(server: Server, socketPath: string): Promise<void> {
1299
+ return new Promise((resolveListen, rejectListen) => {
1300
+ const onError = (error: Error) => {
1301
+ server.off("listening", onListening);
1302
+ rejectListen(error);
1303
+ };
1304
+ const onListening = () => {
1305
+ server.off("error", onError);
1306
+ resolveListen();
1307
+ };
1308
+ server.once("error", onError);
1309
+ server.once("listening", onListening);
1310
+ server.listen(socketPath);
1311
+ });
1312
+ }
1313
+
1314
+ function acceptProtocolSocket(
1315
+ server: Server,
1316
+ child: ChildProcess,
1317
+ timeoutMs: number,
1318
+ ): Promise<Socket> {
1319
+ return new Promise((resolveSocket, rejectSocket) => {
1320
+ let settled = false;
1321
+ const finish = (result: { readonly error: Error } | { readonly socket: Socket }) => {
1322
+ if (settled) return;
1323
+ settled = true;
1324
+ clearTimeout(timer);
1325
+ server.off("connection", onConnection);
1326
+ server.off("error", onError);
1327
+ child.off("error", onChildError);
1328
+ child.off("exit", onExit);
1329
+ if ("error" in result) rejectSocket(result.error);
1330
+ else resolveSocket(result.socket);
1331
+ };
1332
+ const onConnection = (socket: Socket) => finish({ socket });
1333
+ const onError = (error: Error) => finish({ error });
1334
+ const onChildError = (error: Error) => finish({
1335
+ error: new ApplicationError(
1336
+ "subprocess",
1337
+ `Could not start code worker: ${error.message}`,
1338
+ ),
1339
+ });
1340
+ const onExit = (code: number | null, signal: NodeJS.Signals | null) => finish({
1341
+ error: new ApplicationError(
1342
+ "subprocess",
1343
+ `Code worker exited before connecting its protocol socket (code ${String(code)}, signal ${String(signal)}).`,
1344
+ ),
1345
+ });
1346
+ const timer = setTimeout(() => finish({
1347
+ error: new ApplicationError(
1348
+ "subprocess",
1349
+ "Timed out waiting for the code worker protocol connection.",
1350
+ ),
1351
+ }), timeoutMs);
1352
+ server.once("connection", onConnection);
1353
+ server.once("error", onError);
1354
+ child.once("error", onChildError);
1355
+ child.once("exit", onExit);
1356
+ });
1357
+ }
1358
+
1359
+ function releaseWorkerStartupGate(
1360
+ child: ChildProcess,
1361
+ timeoutMs: number,
1362
+ ): Promise<void> {
1363
+ return new Promise((resolveGate, rejectGate) => {
1364
+ const control = child.stdin;
1365
+ if (control === null) {
1366
+ rejectGate(new ApplicationError(
1367
+ "subprocess",
1368
+ "Code worker did not expose its guardian startup gate.",
1369
+ ));
1370
+ return;
1371
+ }
1372
+ let settled = false;
1373
+ const settle = (error?: Error) => {
1374
+ if (settled) return;
1375
+ settled = true;
1376
+ clearTimeout(timer);
1377
+ control.removeListener("error", onError);
1378
+ child.removeListener("exit", onExit);
1379
+ if (error === undefined) resolveGate();
1380
+ else rejectGate(error);
1381
+ };
1382
+ const onError = (error: Error) => settle(new ApplicationError(
1383
+ "subprocess",
1384
+ `Code-worker guardian startup gate failed: ${error.message}`,
1385
+ ));
1386
+ const onExit = (code: number | null, signal: NodeJS.Signals | null) => settle(
1387
+ new ApplicationError(
1388
+ "subprocess",
1389
+ `Code worker exited before its guardian startup gate opened (code ${String(code)}, signal ${String(signal)}).`,
1390
+ ),
1391
+ );
1392
+ const timer = setTimeout(() => settle(new ApplicationError(
1393
+ "subprocess",
1394
+ "Timed out opening the code-worker guardian startup gate.",
1395
+ )), Math.min(timeoutMs, 2_000));
1396
+ control.once("error", onError);
1397
+ child.once("exit", onExit);
1398
+ control.end("guardian-ready\n", () => settle());
1399
+ });
1400
+ }
1401
+
1402
+ export async function startCodeWorker(
1403
+ options: StartCodeWorkerOptions,
1404
+ ): Promise<CodeWorkerSession> {
1405
+ const timeoutMs = positiveBound(
1406
+ options.timeoutMs,
1407
+ DEFAULT_TIMEOUT_MS,
1408
+ "Code worker timeout",
1409
+ );
1410
+ const maximumDiagnosticBytes = positiveBound(
1411
+ options.maximumDiagnosticBytes,
1412
+ DEFAULT_MAXIMUM_DIAGNOSTIC_BYTES,
1413
+ "Code worker diagnostic byte limit",
1414
+ );
1415
+ const inheritedHostResourceFileDescriptor =
1416
+ options.inheritedHostResourceFileDescriptor ?? 0;
1417
+ if (
1418
+ !Number.isSafeInteger(inheritedHostResourceFileDescriptor)
1419
+ || inheritedHostResourceFileDescriptor < 0
1420
+ || (
1421
+ inheritedHostResourceFileDescriptor > 0
1422
+ && inheritedHostResourceFileDescriptor < 3
1423
+ )
1424
+ ) {
1425
+ throw new ApplicationError(
1426
+ "usage",
1427
+ "Code-worker preparation requires one safe inherited host-resource descriptor.",
1428
+ );
1429
+ }
1430
+ const bunExecutable = options.bunExecutable ?? process.execPath;
1431
+ if (bunExecutable === "") {
1432
+ throw new ApplicationError(
1433
+ "unavailable",
1434
+ "Code mode requires an explicit Bun 1.3.14 executable for its trusted worker process.",
1435
+ );
1436
+ }
1437
+ const workerEntryPath = resolve(
1438
+ options.workerEntryPath ?? join(import.meta.dir, "worker-entry.ts"),
1439
+ );
1440
+ const workerEntrySha256 = createHash("sha256")
1441
+ .update(await readFile(workerEntryPath))
1442
+ .digest("hex");
1443
+ const temporaryParent = resolve(options.temporaryRoot ?? tmpdir());
1444
+ await mkdir(temporaryParent, { mode: 0o700, recursive: true });
1445
+ const directory = await mkdtemp(join(temporaryParent, "slopcamera-code-worker-"));
1446
+ const protocolDirectory = await mkdtemp(join(tmpdir(), "iw-"));
1447
+ const socketPath = join(protocolDirectory, "s");
1448
+ let child: ChildProcess | undefined;
1449
+ let protocol: Socket | undefined;
1450
+ let server: Server | undefined;
1451
+ let session: RunningCodeWorker | undefined;
1452
+ let preparationGuardian: CodeWorkerLeaseGuardian | undefined;
1453
+ try {
1454
+ const bundlePath = join(directory, "workflow.bundle.js");
1455
+ await writeFile(bundlePath, options.bundle.bytes, { flag: "wx", mode: 0o600 });
1456
+ await writeFile(join(directory, "tsconfig.json"), `${canonicalJson({
1457
+ compilerOptions: {
1458
+ baseUrl: ".",
1459
+ paths: Object.fromEntries(options.bundle.externalImports.map(external => [
1460
+ external.specifier,
1461
+ [external.resolvedPath],
1462
+ ])),
1463
+ },
1464
+ })}\n`, { flag: "wx", mode: 0o600 });
1465
+
1466
+ server = createServer();
1467
+ await listenProtocolServer(server, socketPath);
1468
+ await chmod(socketPath, 0o600);
1469
+ child = spawn(
1470
+ bunExecutable,
1471
+ [
1472
+ "run",
1473
+ workerEntryPath,
1474
+ "--bundle",
1475
+ bundlePath,
1476
+ "--sha256",
1477
+ options.bundle.sha256,
1478
+ "--socket",
1479
+ socketPath,
1480
+ "--preparation-lease-fd",
1481
+ inheritedHostResourceFileDescriptor === 0 ? "0" : "3",
1482
+ ],
1483
+ {
1484
+ cwd: directory,
1485
+ env: {
1486
+ LANG: "C.UTF-8",
1487
+ LC_ALL: "C.UTF-8",
1488
+ PATH: dirname(bunExecutable),
1489
+ TMPDIR: directory,
1490
+ },
1491
+ stdio: [
1492
+ "pipe",
1493
+ "pipe",
1494
+ "pipe",
1495
+ ...(inheritedHostResourceFileDescriptor === 0
1496
+ ? []
1497
+ : [inheritedHostResourceFileDescriptor]),
1498
+ ],
1499
+ },
1500
+ );
1501
+ const workerProcessId = child.pid;
1502
+ if (workerProcessId === undefined || workerProcessId < 2) {
1503
+ throw new ApplicationError("subprocess", "Code worker has no valid process ID.");
1504
+ }
1505
+ const workerStartIdentity = process.platform === "win32"
1506
+ ? "0".repeat(64)
1507
+ : captureWorkerProcessStartIdentity(workerProcessId);
1508
+ preparationGuardian = await startCodeWorkerLeaseGuardian({
1509
+ bunExecutable,
1510
+ inheritedFileDescriptor: inheritedHostResourceFileDescriptor,
1511
+ workerProcessId,
1512
+ workerStartIdentity,
1513
+ });
1514
+ const startupGate = releaseWorkerStartupGate(child, timeoutMs);
1515
+ void startupGate.catch(() => undefined);
1516
+ await Promise.race([startupGate, preparationGuardian.failure]);
1517
+ const acceptedProtocol = acceptProtocolSocket(server, child, timeoutMs);
1518
+ void acceptedProtocol.catch(() => undefined);
1519
+ protocol = await Promise.race([
1520
+ acceptedProtocol,
1521
+ preparationGuardian.failure,
1522
+ ]);
1523
+ server.close();
1524
+ server = undefined;
1525
+ session = new RunningCodeWorker({
1526
+ child,
1527
+ cleanupDirectories: [directory, protocolDirectory],
1528
+ maximumDiagnosticBytes,
1529
+ preparationGuardian,
1530
+ processStartIdentity: workerStartIdentity,
1531
+ protocol,
1532
+ timeoutMs,
1533
+ });
1534
+ await Promise.race([
1535
+ session.initialize({
1536
+ bunRevision: Bun.revision,
1537
+ bunVersion: Bun.version,
1538
+ bundleSha256: options.bundle.sha256,
1539
+ workerEntrySha256,
1540
+ }),
1541
+ preparationGuardian.failure,
1542
+ ]);
1543
+ return session;
1544
+ } catch (error) {
1545
+ server?.close();
1546
+ if (session !== undefined) {
1547
+ await session.close().catch(() => undefined);
1548
+ } else {
1549
+ protocol?.destroy();
1550
+ const pendingChild = child;
1551
+ let childExitObserved = pendingChild === undefined
1552
+ || pendingChild.exitCode !== null
1553
+ || pendingChild.signalCode !== null;
1554
+ if (
1555
+ pendingChild !== undefined
1556
+ && !childExitObserved
1557
+ ) {
1558
+ pendingChild.kill("SIGKILL");
1559
+ childExitObserved = await new Promise<boolean>(resolveExit => {
1560
+ let settled = false;
1561
+ const settle = (observed: boolean) => {
1562
+ if (settled) return;
1563
+ settled = true;
1564
+ clearTimeout(timeout);
1565
+ pendingChild.off("exit", onExit);
1566
+ resolveExit(observed);
1567
+ };
1568
+ const onExit = () => settle(true);
1569
+ const timeout = setTimeout(() => settle(false), 2_000);
1570
+ pendingChild.once("exit", onExit);
1571
+ if (
1572
+ pendingChild.exitCode !== null
1573
+ || pendingChild.signalCode !== null
1574
+ ) settle(true);
1575
+ });
1576
+ }
1577
+ if (preparationGuardian !== undefined) {
1578
+ if (childExitObserved) {
1579
+ await preparationGuardian.complete().catch(() => undefined);
1580
+ } else if (pendingChild !== undefined) {
1581
+ preparationGuardian.retireDetached();
1582
+ // Retirement ownership has moved to the detached guardian. Drop all
1583
+ // parent-side worker handles as well so an uninterruptible child
1584
+ // cannot keep this CLI alive while the guardian retains the lease.
1585
+ pendingChild.stdin?.destroy();
1586
+ pendingChild.stdout?.destroy();
1587
+ pendingChild.stderr?.destroy();
1588
+ pendingChild.unref();
1589
+ }
1590
+ }
1591
+ await Promise.all([
1592
+ rm(directory, { force: true, recursive: true }),
1593
+ rm(protocolDirectory, { force: true, recursive: true }),
1594
+ ]);
1595
+ }
1596
+ throw error;
1597
+ }
1598
+ }
1599
+
1600
+ export async function buildInCodeWorker(
1601
+ options: BuildInCodeWorkerOptions,
1602
+ ): Promise<CodeWorkerBuildResult> {
1603
+ const session = await startCodeWorker(options);
1604
+ try {
1605
+ return await session.build(options.registry, options.workflowInput);
1606
+ } finally {
1607
+ await session.close();
1608
+ }
1609
+ }
1610
+
1611
+ export async function startCodeWorkerPool(
1612
+ options: StartCodeWorkerPoolOptions,
1613
+ ): Promise<CodeWorkerPool> {
1614
+ const requestedWorkers = positiveBound(
1615
+ options.maximumWorkers,
1616
+ MAX_CODE_WORKER_POOL_SIZE,
1617
+ "Code worker pool size",
1618
+ );
1619
+ const size = Math.min(requestedWorkers, MAX_CODE_WORKER_POOL_SIZE);
1620
+ const registrySnapshot = Object.freeze([...options.registry.list()]);
1621
+ const registry = {
1622
+ list: () => registrySnapshot,
1623
+ };
1624
+ const workers: CodeWorkerSession[] = [];
1625
+ try {
1626
+ if (options.initialWorker !== undefined) {
1627
+ workers.push(options.initialWorker.session);
1628
+ if (options.initialWorker.session.bundleSha256 !== options.bundle.sha256) {
1629
+ throw new ApplicationError(
1630
+ "incompatible",
1631
+ "Initial code worker bundle does not match the pool bundle.",
1632
+ );
1633
+ }
1634
+ assertExpectedBuild(options.initialWorker.build, options.expectedBuild);
1635
+ }
1636
+ const started = await Promise.allSettled(
1637
+ Array.from({ length: size - workers.length }, async () => {
1638
+ const worker = await startCodeWorker(options);
1639
+ try {
1640
+ const built = await worker.build(
1641
+ registry,
1642
+ options.workflowInput,
1643
+ options.workflowInputMode,
1644
+ );
1645
+ assertExpectedBuild(built, options.expectedBuild);
1646
+ return worker;
1647
+ } catch (error) {
1648
+ await worker.close().catch(() => undefined);
1649
+ throw error;
1650
+ }
1651
+ }),
1652
+ );
1653
+ for (const result of started) {
1654
+ if (result.status === "fulfilled") workers.push(result.value);
1655
+ }
1656
+ const failure = started.find(result => result.status === "rejected");
1657
+ if (failure?.status === "rejected") throw failure.reason;
1658
+ if (workers.length !== size) {
1659
+ throw new ApplicationError(
1660
+ "internal",
1661
+ "Code-worker pool did not initialize its bounded worker set.",
1662
+ );
1663
+ }
1664
+ return new RunningCodeWorkerPool(
1665
+ options.bundle.sha256,
1666
+ workers,
1667
+ options.bunExecutable ?? process.execPath,
1668
+ );
1669
+ } catch (error) {
1670
+ await Promise.all(workers.map(async worker => {
1671
+ await worker.close().catch(() => undefined);
1672
+ }));
1673
+ throw error;
1674
+ }
1675
+ }