cozyclay 2.1.1 → 3.0.0

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 (577) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +40 -113
  4. package/bin/agent/agent-runner.mjs +9 -10
  5. package/bin/agent/cliproxy-provider.mjs +135 -0
  6. package/bin/agent/credential-store.mjs +30 -7
  7. package/bin/agent/pi-tools.mjs +4 -1
  8. package/bin/agent/provider-keys.mjs +32 -0
  9. package/bin/agent/provider-login.mjs +110 -0
  10. package/bin/agent/provider-routes.mjs +99 -0
  11. package/bin/agent/providers.mjs +19 -97
  12. package/bin/agent/script-tool.mjs +17 -4
  13. package/bin/agent/studio-motion-reference.mjs +23 -0
  14. package/bin/agent/studio-prompt.mjs +1 -1
  15. package/bin/agent/studio-rebase-text.mjs +32 -0
  16. package/bin/agent/studio-rebase.mjs +26 -39
  17. package/bin/agent/studio-tools.mjs +9 -28
  18. package/bin/agent/tool-arguments.mjs +49 -0
  19. package/bin/agent/turn-frames.mjs +66 -0
  20. package/bin/codex-auth.mjs +9 -2
  21. package/bin/cozyclay.mjs +10 -116
  22. package/bin/live/cli.mjs +3 -3
  23. package/bin/mcp-runtime.mjs +2 -12
  24. package/bin/package-signature.mjs +0 -4
  25. package/dist/app/index.html +7 -6
  26. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  27. package/dist/assets/analytics-C9sUnndd.js +1 -0
  28. package/dist/assets/app-CCh--1Hc.js +5087 -0
  29. package/dist/assets/app-f-fkV5uP.css +1 -0
  30. package/dist/assets/compile-worker-CkxiD6lH.js +5 -0
  31. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  32. package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-CC4xe4HM.js} +1 -1
  33. package/dist/assets/{landing-ChUvB_zL.js → landing-DjHD8u7k.js} +1 -1
  34. package/dist/assets/scene-asset-sync-Hx_FzdOH.js +20 -0
  35. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  36. package/dist/assets/workflow-C4yKH1p4.js +179 -0
  37. package/dist/cozyclay-package.json +1 -1
  38. package/dist/index.html +3 -3
  39. package/dist/sw.js +3 -6
  40. package/dist/workflow/index.html +5 -4
  41. package/mcp/LIVE-PROTOCOL.md +7 -47
  42. package/mcp/README.md +16 -26
  43. package/mcp/live-hub.mjs +6 -10
  44. package/mcp/package.json +1 -3
  45. package/mcp/tool-handlers.mjs +4 -168
  46. package/package.json +3 -22
  47. package/src/App.jsx +150 -2624
  48. package/src/agent-progress.js +79 -0
  49. package/src/agent-turn-mark.js +30 -0
  50. package/src/analytics.js +1 -19
  51. package/src/app-context.js +5 -0
  52. package/src/app-stage.jsx +2 -34
  53. package/src/asset-pane.jsx +6 -5
  54. package/src/camera-block.js +21 -0
  55. package/src/camera-direction.js +120 -0
  56. package/src/character-kind.js +3 -9
  57. package/src/command-bus.js +8 -6
  58. package/src/commands/cast.js +5 -34
  59. package/src/commands/elements/character.js +1 -1
  60. package/src/commands/elements/motion.js +1 -6
  61. package/src/commands/elements/object.js +6 -2
  62. package/src/commands/elements/shot.js +4 -1
  63. package/src/commands/elements.js +38 -14
  64. package/src/commands/index.js +3 -1
  65. package/src/commands/kits.js +37 -0
  66. package/src/commands/motion-timing.js +57 -0
  67. package/src/commands/motion.js +9 -152
  68. package/src/commands/motion3-compile.js +65 -0
  69. package/src/commands/motion3.js +207 -0
  70. package/src/commands/objects.js +6 -4
  71. package/src/commands/shot.js +49 -11
  72. package/src/commands/stage.js +14 -1
  73. package/src/commands/view.js +4 -1
  74. package/src/document-mirrors.js +42 -0
  75. package/src/document-store.js +4 -0
  76. package/src/domains/cast.js +19 -194
  77. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  78. package/src/domains/motion/take-source.js +51 -0
  79. package/src/domains/motion.js +60 -1838
  80. package/src/domains/scenes.js +12 -55
  81. package/src/domains/shots.js +2 -2
  82. package/src/domains/stage.js +4 -2
  83. package/src/draft-ghost.js +46 -0
  84. package/src/draft-ghost.jsx +129 -0
  85. package/src/dualview.jsx +4 -3
  86. package/src/effects/EffectsLayer.jsx +112 -0
  87. package/src/effects/effect-meshes.js +232 -0
  88. package/src/effects/effect-record.js +103 -0
  89. package/src/effects/emitter.js +59 -0
  90. package/src/effects/simulate.js +261 -0
  91. package/src/effects/state.js +59 -0
  92. package/src/execution-telemetry.js +2 -3
  93. package/src/first-success-guide.jsx +5 -5
  94. package/src/ghost-set.js +257 -0
  95. package/src/held-object-edit.jsx +113 -0
  96. package/src/held-object-pose.js +175 -0
  97. package/src/hierarchy-model.js +1 -1
  98. package/src/hierarchy-panel.jsx +1 -1
  99. package/src/imported-mesh.jsx +103 -0
  100. package/src/kits/held.js +84 -0
  101. package/src/kits/index.js +70 -0
  102. package/src/kits/smoking.js +57 -0
  103. package/src/light-blockers.js +93 -0
  104. package/src/motion/beat-board-model.js +84 -0
  105. package/src/motion/beat-board.css +124 -0
  106. package/src/motion/beat-board.jsx +59 -0
  107. package/src/motion/center-of-mass.js +66 -0
  108. package/src/motion/fix-collisions.js +850 -0
  109. package/src/motion/physics-review.js +118 -0
  110. package/src/{ardy → motion}/physics-support.js +1 -76
  111. package/src/motion/rig-snapshot.js +22 -0
  112. package/src/motion/take-segments.jsx +140 -0
  113. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  114. package/src/{ardy → motion}/timeline.css +1 -2
  115. package/src/{ardy → motion}/timeline.jsx +42 -413
  116. package/src/motion/waypoints.js +76 -0
  117. package/src/motion3/beats.js +83 -0
  118. package/src/motion3/body.js +158 -0
  119. package/src/motion3/bones.js +47 -0
  120. package/src/motion3/carried.js +47 -0
  121. package/src/motion3/compile-job.js +75 -0
  122. package/src/motion3/compile-worker.js +10 -0
  123. package/src/motion3/compiler/anchors.js +105 -0
  124. package/src/motion3/compiler/arm-orient.js +128 -0
  125. package/src/motion3/compiler/channels.js +125 -0
  126. package/src/motion3/compiler/compile.js +205 -0
  127. package/src/motion3/compiler/gait-rhythm.js +97 -0
  128. package/src/motion3/compiler/grip.js +171 -0
  129. package/src/motion3/compiler/head-steady.js +48 -0
  130. package/src/motion3/compiler/hold.js +134 -0
  131. package/src/motion3/compiler/idle.js +113 -0
  132. package/src/motion3/compiler/limbs.js +170 -0
  133. package/src/motion3/compiler/math.js +72 -0
  134. package/src/motion3/compiler/pose.js +162 -0
  135. package/src/motion3/compiler/quat.js +49 -0
  136. package/src/motion3/compiler/skeleton.js +74 -0
  137. package/src/motion3/compiler/validate-common.js +32 -0
  138. package/src/motion3/compiler/validate-goals.js +118 -0
  139. package/src/motion3/compiler/validate-objects.js +143 -0
  140. package/src/motion3/compiler/validate-parts.js +116 -0
  141. package/src/motion3/compiler/validate.js +122 -0
  142. package/src/motion3/compiler/vocab.js +73 -0
  143. package/src/motion3/compiler/walk-pelvis.js +275 -0
  144. package/src/motion3/compiler/walk.js +207 -0
  145. package/src/motion3/compiler/wrist-clamp.js +32 -0
  146. package/src/motion3/delta.js +47 -0
  147. package/src/motion3/describe.js +174 -0
  148. package/src/motion3/docs.js +27 -0
  149. package/src/motion3/edit-carry.js +21 -0
  150. package/src/motion3/fix/ease.js +16 -0
  151. package/src/motion3/fix/ground.js +37 -0
  152. package/src/motion3/fix/index.js +52 -0
  153. package/src/motion3/fix/push.js +71 -0
  154. package/src/motion3/fix/reach.js +46 -0
  155. package/src/motion3/fix/retime.js +79 -0
  156. package/src/motion3/fix/self.js +211 -0
  157. package/src/motion3/fix/slide.js +70 -0
  158. package/src/motion3/fix/turn.js +51 -0
  159. package/src/motion3/follow.js +39 -0
  160. package/src/motion3/free-motion.js +173 -0
  161. package/src/motion3/grip-choice.js +130 -0
  162. package/src/motion3/hold-plan.js +157 -0
  163. package/src/motion3/json-patch.js +77 -0
  164. package/src/motion3/key-aim.js +105 -0
  165. package/src/motion3/key-edit.js +82 -0
  166. package/src/motion3/lenient.js +127 -0
  167. package/src/motion3/object-keys.js +236 -0
  168. package/src/motion3/object-pose.js +182 -0
  169. package/src/motion3/object-tracks.js +12 -0
  170. package/src/motion3/pipeline.js +102 -0
  171. package/src/motion3/playback.js +51 -0
  172. package/src/motion3/probes.js +91 -0
  173. package/src/motion3/qa/body-checks.js +34 -0
  174. package/src/motion3/qa/check-names.js +14 -0
  175. package/src/motion3/qa/checks.js +121 -0
  176. package/src/motion3/qa/contact-checks.js +51 -0
  177. package/src/motion3/qa/explain.js +80 -0
  178. package/src/motion3/qa/fmt.js +5 -0
  179. package/src/motion3/qa/foot-checks.js +72 -0
  180. package/src/motion3/qa/geometry.js +77 -0
  181. package/src/motion3/qa/goals.js +191 -0
  182. package/src/motion3/qa/ground.js +60 -0
  183. package/src/motion3/qa/hints-contact.js +179 -0
  184. package/src/motion3/qa/hints-goal.js +160 -0
  185. package/src/motion3/qa/hints-knee.js +24 -0
  186. package/src/motion3/qa/hints-motion.js +209 -0
  187. package/src/motion3/qa/hints-object.js +91 -0
  188. package/src/motion3/qa/index.js +42 -0
  189. package/src/motion3/qa/key-checks.js +82 -0
  190. package/src/motion3/qa/near.js +96 -0
  191. package/src/motion3/qa/object-checks.js +135 -0
  192. package/src/motion3/qa/patch.js +161 -0
  193. package/src/motion3/qa/severity.js +38 -0
  194. package/src/motion3/qa/spec-checks.js +65 -0
  195. package/src/motion3/qa/style.js +218 -0
  196. package/src/motion3/qa/wrist-checks.js +69 -0
  197. package/src/motion3/qa/wrist-obs.js +60 -0
  198. package/src/motion3/refs.js +120 -0
  199. package/src/motion3/repair-tune.js +121 -0
  200. package/src/motion3/repair.js +83 -0
  201. package/src/motion3/report.js +67 -0
  202. package/src/motion3/rigs/index.js +14 -0
  203. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  204. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  205. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  206. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  207. package/src/motion3/scene.js +60 -0
  208. package/src/motion3/schema/brief.js +89 -0
  209. package/src/motion3/schema/fields.js +44 -0
  210. package/src/motion3/schema/spec-examples.js +46 -0
  211. package/src/motion3/schema/topics.js +111 -0
  212. package/src/motion3/schema/types.js +147 -0
  213. package/src/motion3/search.js +117 -0
  214. package/src/motion3/second-hand.js +112 -0
  215. package/src/motion3/skills/basic.js +82 -0
  216. package/src/motion3/skills/book.js +76 -0
  217. package/src/motion3/skills/car.js +145 -0
  218. package/src/motion3/skills/frames-auto.js +29 -0
  219. package/src/motion3/skills/index.js +83 -0
  220. package/src/motion3/skills/info.js +65 -0
  221. package/src/motion3/skills/smoke-kit.js +109 -0
  222. package/src/motion3/skills/smoke.js +160 -0
  223. package/src/motion3/spec-store.js +59 -0
  224. package/src/motion3/take-edit.js +155 -0
  225. package/src/motion3/take.js +63 -0
  226. package/src/motion3/timeline.js +127 -0
  227. package/src/motion3/views.js +121 -0
  228. package/src/motion3/walk-plan.js +119 -0
  229. package/src/object-catalog.jsx +7 -1
  230. package/src/object-colors.js +98 -0
  231. package/src/object-gizmo-parts.js +115 -0
  232. package/src/object-gizmo.jsx +37 -122
  233. package/src/object-glow.js +139 -0
  234. package/src/object-hidden.js +33 -0
  235. package/src/object-look.js +196 -0
  236. package/src/object-size.js +10 -0
  237. package/src/object-travel.js +71 -6
  238. package/src/panels/CameraPanel.jsx +7 -7
  239. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  240. package/src/panels/LightPanel.jsx +25 -4
  241. package/src/panels/ObjectLookPanel.jsx +88 -0
  242. package/src/panels/PosePanel.jsx +2 -17
  243. package/src/panels/RigControlPanel.jsx +5 -53
  244. package/src/panels/SubjectBox.jsx +4 -1
  245. package/src/panels/details.css +14 -0
  246. package/src/posestudio.jsx +4 -27
  247. package/src/project-browser.jsx +3 -4
  248. package/src/project-resources.js +18 -31
  249. package/src/project.js +13 -104
  250. package/src/props.jsx +25 -280
  251. package/src/rail-dot-drag.js +23 -0
  252. package/src/render-passes.js +10 -0
  253. package/src/resource-status.jsx +1 -0
  254. package/src/result-modal.jsx +4 -19
  255. package/src/room.jsx +20 -12
  256. package/src/scene-assets.js +8 -6
  257. package/src/scene-light-place.js +72 -0
  258. package/src/scene-lights.jsx +148 -0
  259. package/src/scene-objects.js +31 -139
  260. package/src/scenes.js +15 -105
  261. package/src/semantic-edit.js +2 -5
  262. package/src/set-pieces.jsx +183 -0
  263. package/src/settings-menu.jsx +3 -11
  264. package/src/shell/BottomDock.jsx +9 -51
  265. package/src/shell/DetailsSlot.jsx +16 -200
  266. package/src/shell/GhostsMenuGroup.jsx +74 -0
  267. package/src/shell/LibrarySlot.jsx +1 -3
  268. package/src/shell/MenuBar.jsx +3 -3
  269. package/src/shell/PreferencesDialog.jsx +15 -264
  270. package/src/shell/PreferencesSlot.jsx +2 -8
  271. package/src/shell/TopBar.jsx +5 -84
  272. package/src/shell/ViewportToolbar.jsx +10 -70
  273. package/src/shell/agent-glass.css +19 -6
  274. package/src/shell/details-selection.js +42 -0
  275. package/src/shell/ghosts-menu.css +22 -0
  276. package/src/shell/glass-regions.css +29 -3
  277. package/src/shell/preferences.css +6 -1
  278. package/src/shot-authoring.js +4 -7
  279. package/src/soft-shadows.js +37 -0
  280. package/src/stage-attach-frames.js +12 -12
  281. package/src/stage-canvas-rigs.jsx +3 -3
  282. package/src/stage-capture.jsx +42 -8
  283. package/src/stage-characters.jsx +22 -216
  284. package/src/stage-editor-gizmos.jsx +6 -6
  285. package/src/stage-environment.js +93 -0
  286. package/src/stage-hdr-builtins.js +107 -0
  287. package/src/stage-hdr-image.js +116 -0
  288. package/src/stage-hdr-library.js +70 -0
  289. package/src/stage-hdr-map.js +94 -0
  290. package/src/stage-hdr.js +130 -0
  291. package/src/stage-lighting.js +149 -0
  292. package/src/stage-occlusion.js +210 -0
  293. package/src/stage-sky.jsx +159 -0
  294. package/src/stage-startup-defaults.js +5 -9
  295. package/src/studio-actions.js +12 -14
  296. package/src/studio-agent-commands.js +19 -16
  297. package/src/studio-agent-motion.js +9 -9
  298. package/src/studio-agent-protocol.js +41 -71
  299. package/src/studio-app-binding.js +25 -45
  300. package/src/studio-asset-list.js +17 -0
  301. package/src/studio-contact-sheet.js +37 -5
  302. package/src/studio-elements.js +27 -7
  303. package/src/studio-entity-bounds.js +18 -0
  304. package/src/studio-error-codes.js +8 -0
  305. package/src/studio-export-framing.js +152 -0
  306. package/src/studio-lighting-check.js +103 -0
  307. package/src/studio-motion-visual.js +150 -0
  308. package/src/studio-schema-errors.js +80 -0
  309. package/src/studio-support-gap.js +74 -0
  310. package/src/styles.css +1 -19
  311. package/src/timeline-defaults.js +5 -0
  312. package/src/timeline-extent.js +8 -20
  313. package/src/tutorial-walk.js +17 -0
  314. package/src/ui-scrub.js +1 -1
  315. package/src/use-shot-camera-tracks.js +12 -0
  316. package/src/view-ghosts.js +97 -0
  317. package/src/view-ghosts.jsx +49 -0
  318. package/src/workflow/AgentPanel.jsx +47 -166
  319. package/src/workflow/AgentProgressCard.jsx +107 -0
  320. package/src/workflow/AgentReplyText.jsx +37 -0
  321. package/src/workflow/AgentTurnSteps.jsx +84 -0
  322. package/src/workflow/LoginPanel.jsx +140 -0
  323. package/src/workflow/WorkflowBuilder.jsx +7 -23
  324. package/src/workflow/agent-client.js +54 -51
  325. package/src/workflow/agent-panel.css +71 -1
  326. package/src/workflow/agent-progress-card.css +167 -0
  327. package/src/workflow/agent-progress-view.js +47 -0
  328. package/src/workflow/agent-turn-steps.css +113 -0
  329. package/src/workflow/agent-turn.js +191 -0
  330. package/src/workflow/login-client.js +34 -0
  331. package/src/workflow/motion-input.js +18 -31
  332. package/src/workflow/node-schema.js +1 -1
  333. package/src/workflow/reply-markdown.js +69 -0
  334. package/src/workflow/scene-asset-sync.js +8 -8
  335. package/tools/agent-session-stats.mjs +61 -0
  336. package/tools/dev-full.mjs +5 -56
  337. package/tools/eslint-baseline.json +12 -13
  338. package/tools/knip-baseline.json +0 -179
  339. package/tools/loc-baseline.json +31 -81
  340. package/tools/motion3/extract-body.mjs +103 -0
  341. package/tools/motion3/extract-rig.mjs +85 -0
  342. package/tools/process-supervisor.mjs +0 -88
  343. package/tools/run-tests.mjs +8 -124
  344. package/bin/agent/motion-runtime.mjs +0 -76
  345. package/dist/ardy/cskel27-rest.json +0 -557
  346. package/dist/assets/analytics-C9pMpW7s.js +0 -1
  347. package/dist/assets/app-B_2XB9rw.css +0 -1
  348. package/dist/assets/app-DAuvKPP8.js +0 -4920
  349. package/dist/assets/config-Bx7pYsD9.js +0 -1
  350. package/dist/assets/config-DeoFTYzb.css +0 -1
  351. package/dist/assets/demo-CvKuBgy5.js +0 -1
  352. package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
  353. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  354. package/dist/assets/ticket-BPk2Ni95.js +0 -1
  355. package/dist/assets/workflow-jJRBR94Z.js +0 -179
  356. package/dist/demo/walk-then-stop.npz +0 -0
  357. package/mcp/ardy-prompts.mjs +0 -218
  358. package/src/ardy/auto-fix-panel.css +0 -108
  359. package/src/ardy/auto-physics.js +0 -1194
  360. package/src/ardy/client.js +0 -153
  361. package/src/ardy/convert.js +0 -313
  362. package/src/ardy/cskel27-neutral.js +0 -39
  363. package/src/ardy/cskel27.js +0 -77
  364. package/src/ardy/export.js +0 -165
  365. package/src/ardy/fix-collisions.js +0 -2257
  366. package/src/ardy/foot-lock.js +0 -230
  367. package/src/ardy/motion-calibration.js +0 -118
  368. package/src/ardy/motion-edit.js +0 -213
  369. package/src/ardy/motion-url.js +0 -55
  370. package/src/ardy/mp4-duration.js +0 -102
  371. package/src/ardy/npz.js +0 -595
  372. package/src/ardy/physics-panel.css +0 -21
  373. package/src/ardy/physics-panel.jsx +0 -89
  374. package/src/ardy/physics-refine.js +0 -111
  375. package/src/ardy/physics-review.js +0 -567
  376. package/src/ardy/physics-temporal.js +0 -54
  377. package/src/ardy/platform-fit-panel.css +0 -64
  378. package/src/ardy/platform-fit-panel.jsx +0 -41
  379. package/src/ardy/platform-fit.js +0 -326
  380. package/src/ardy/playback.js +0 -711
  381. package/src/ardy/pose-pin.js +0 -97
  382. package/src/ardy/prompt-clips.js +0 -30
  383. package/src/ardy/retime.js +0 -211
  384. package/src/ardy/root-drop.js +0 -369
  385. package/src/ardy/timeline-resize.js +0 -13
  386. package/src/ardy/to-cskel27.js +0 -225
  387. package/src/ardy/trim.js +0 -33
  388. package/src/ardy/waypoints.js +0 -369
  389. package/src/ardy-bridge-contract.js +0 -281
  390. package/src/domains/motion/motion-video-capture.js +0 -664
  391. package/src/i2v-motion-client.js +0 -77
  392. package/src/i2v-motion-studio.jsx +0 -180
  393. package/src/line-edit.js +0 -1348
  394. package/src/motion/generation.js +0 -105
  395. package/src/motion-readiness-ui.jsx +0 -67
  396. package/src/motion-readiness.js +0 -40
  397. package/src/motion-resources.js +0 -177
  398. package/src/motion-store.js +0 -65
  399. package/src/motion-trail.js +0 -601
  400. package/src/multimodel-ingest.js +0 -394
  401. package/src/panels/PromptBlocksPanel.jsx +0 -444
  402. package/src/panels/VideoCapturePanel.jsx +0 -181
  403. package/src/pose-extract/browser.js +0 -42
  404. package/src/pose-extract/detector.js +0 -100
  405. package/src/pose-extract/fit.js +0 -227
  406. package/src/pose-extract/image-frame.js +0 -51
  407. package/src/pose-extract/index.js +0 -58
  408. package/src/pose-extract/keyframes.js +0 -74
  409. package/src/pose-extract/landmarks.js +0 -162
  410. package/src/pose-extract/mirror.js +0 -153
  411. package/src/pose-extract/take.js +0 -131
  412. package/src/pose-extract/video-frames.js +0 -91
  413. package/src/take-recipe.js +0 -165
  414. package/src/trail-key-conflicts.js +0 -39
  415. package/src/trail-pick.js +0 -59
  416. package/src/use-i2v-capture-modal.js +0 -15
  417. package/tools/ardy/BRIDGE.md +0 -37
  418. package/tools/ardy/GVHMR-NPZ.md +0 -68
  419. package/tools/ardy/README.md +0 -124
  420. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  421. package/tools/ardy/artifacts.mjs +0 -34
  422. package/tools/ardy/bridge.mjs +0 -1732
  423. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  424. package/tools/ardy/cclay_constrained_generate.py +0 -561
  425. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  426. package/tools/ardy/cclay_motion_edit.py +0 -510
  427. package/tools/ardy/cclay_sequence_generate.py +0 -705
  428. package/tools/ardy/cclay_worker.py +0 -304
  429. package/tools/ardy/dump-npz.py +0 -205
  430. package/tools/ardy/extract-rest.mjs +0 -299
  431. package/tools/ardy/extract.mjs +0 -507
  432. package/tools/ardy/footage.mjs +0 -467
  433. package/tools/ardy/gvhmr-floor.mjs +0 -102
  434. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  435. package/tools/ardy/gvhmr_fastpath.py +0 -168
  436. package/tools/ardy/gvhmr_trajectory.py +0 -240
  437. package/tools/ardy/merge-text-encoder.py +0 -160
  438. package/tools/ardy/mocap-metrics.mjs +0 -200
  439. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  440. package/tools/ardy/motion-stabilize.mjs +0 -348
  441. package/tools/ardy/npz.mjs +0 -414
  442. package/tools/ardy/pose-to-npz.mjs +0 -106
  443. package/tools/ardy/prompt-limits.mjs +0 -5
  444. package/tools/ardy/run-edit-on-box.sh +0 -73
  445. package/tools/ardy/run-local.mjs +0 -233
  446. package/tools/ardy/run-on-box.sh +0 -593
  447. package/tools/ardy/run-sequence-on-box.sh +0 -177
  448. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  449. package/tools/ardy/runners/index.mjs +0 -18
  450. package/tools/ardy/runners/local.mjs +0 -455
  451. package/tools/ardy/runners/proc.mjs +0 -125
  452. package/tools/ardy/runners/remote.mjs +0 -252
  453. package/tools/ardy/setup-local.mjs +0 -189
  454. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  455. package/tools/ardy/setup-text-encoder.py +0 -306
  456. package/tools/ardy/smpl-cskel27.mjs +0 -178
  457. package/tools/ardy/visual-qa.mjs +0 -196
  458. package/tools/ardy/vq-car.mjs +0 -52
  459. package/tools/bench/EXP3.md +0 -61
  460. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  461. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  462. package/tools/bench/cclay_bench_runner.py +0 -42
  463. package/tools/bench/cube-contact.mjs +0 -162
  464. package/tools/bench/exp3.mjs +0 -152
  465. package/tools/bench/extract-bench-lib.mjs +0 -216
  466. package/tools/bench/extract-bench.mjs +0 -302
  467. package/tools/bench/fal-generate.mjs +0 -88
  468. package/tools/bench/fit/README.md +0 -189
  469. package/tools/bench/fit/camera.mjs +0 -59
  470. package/tools/bench/fit/contact.mjs +0 -419
  471. package/tools/bench/fit/footlock.mjs +0 -234
  472. package/tools/bench/fit/motion.mjs +0 -85
  473. package/tools/bench/fit/pin.mjs +0 -27
  474. package/tools/bench/fit/remote.mjs +0 -105
  475. package/tools/bench/fit-bench.mjs +0 -126
  476. package/tools/bench/fit-sanity.mjs +0 -45
  477. package/tools/bench/metrics.mjs +0 -313
  478. package/tools/bench/obs/depth.mjs +0 -258
  479. package/tools/bench/obs/extrinsics.mjs +0 -175
  480. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  481. package/tools/bench/obs/ground.mjs +0 -294
  482. package/tools/bench/obs/heading.mjs +0 -151
  483. package/tools/bench/obs/ladder.mjs +0 -555
  484. package/tools/bench/obs/mannequin-betas.json +0 -124
  485. package/tools/bench/obs/remote.mjs +0 -83
  486. package/tools/bench/obs/rest_joints.py +0 -52
  487. package/tools/bench/obs/ybot-targets.mjs +0 -49
  488. package/tools/bench/obs-bench.mjs +0 -520
  489. package/tools/bench/score.mjs +0 -343
  490. package/tools/bench/summarize.mjs +0 -86
  491. package/tools/demo-worker/README.md +0 -137
  492. package/tools/demo-worker/api-client.mjs +0 -233
  493. package/tools/demo-worker/generate.mjs +0 -301
  494. package/tools/demo-worker/index.mjs +0 -466
  495. package/tools/demo-worker/sign-core.mjs +0 -112
  496. package/tools/demo-worker/sign.mjs +0 -161
  497. package/tools/gt-render/browser.mjs +0 -182
  498. package/tools/gt-render/camera-math.mjs +0 -276
  499. package/tools/gt-render/page.mjs +0 -267
  500. package/tools/gt-render/render.mjs +0 -514
  501. package/tools/gt-render/scene-box.mjs +0 -31
  502. package/tools/gt-render/take-transform.mjs +0 -87
  503. package/tools/kimodo/compare-seam.mjs +0 -108
  504. package/tools/kimodo/constraints.mjs +0 -244
  505. package/tools/kimodo/edit.mjs +0 -259
  506. package/tools/kimodo/effector-constraints.mjs +0 -200
  507. package/tools/kimodo/generate.mjs +0 -646
  508. package/tools/kimodo/local-output.mjs +0 -265
  509. package/tools/kimodo/measure-pose.mjs +0 -65
  510. package/tools/kimodo/measure-preserve.mjs +0 -899
  511. package/tools/kimodo/measure-waypoints.mjs +0 -55
  512. package/tools/kimodo/mlx-generate.py +0 -111
  513. package/tools/kimodo/pose-constraints.mjs +0 -248
  514. package/tools/kimodo/preserve-mask.mjs +0 -543
  515. package/tools/kimodo/read-npz.mjs +0 -189
  516. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  517. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  518. package/tools/kimodo/runner.mjs +0 -284
  519. package/tools/kimodo/seam-batch.mjs +0 -77
  520. package/tools/kimodo/setup-local.mjs +0 -30
  521. package/tools/kimodo/setup-on-box.sh +0 -339
  522. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  523. package/tools/morphgs/assets/gen_truth.py +0 -39
  524. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  525. package/tools/morphgs/assets/playback-check.mjs +0 -30
  526. package/tools/morphgs/demo-gate.sh +0 -26
  527. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  528. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  529. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  530. package/tools/morphgs/setup-on-cluster.sh +0 -126
  531. package/tools/projflow/driver.py +0 -1069
  532. package/tools/projflow/generate.mjs +0 -805
  533. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  534. package/tools/projflow/line-edit-job.mjs +0 -547
  535. package/tools/projflow/replay.mjs +0 -452
  536. package/tools/projflow/runner.mjs +0 -269
  537. package/tools/projflow/service.mjs +0 -742
  538. package/tools/track/DESIGN.md +0 -513
  539. package/tools/track/backfill-provenance.mjs +0 -116
  540. package/tools/track/budget.mjs +0 -8
  541. package/tools/track/check-rig.mjs +0 -209
  542. package/tools/track/diagnostics.schema.json +0 -60
  543. package/tools/track/export-rig.mjs +0 -256
  544. package/tools/track/fallback.mjs +0 -7
  545. package/tools/track/fk-parity-fixture.mjs +0 -161
  546. package/tools/track/gate.mjs +0 -349
  547. package/tools/track/masks.mjs +0 -318
  548. package/tools/track/metrics.mjs +0 -201
  549. package/tools/track/publish-obs.mjs +0 -108
  550. package/tools/track/py/check_env.py +0 -61
  551. package/tools/track/py/eval_lr.py +0 -229
  552. package/tools/track/py/lr_viterbi.py +0 -294
  553. package/tools/track/py/masks.py +0 -405
  554. package/tools/track/py/objective.py +0 -382
  555. package/tools/track/py/rig.py +0 -508
  556. package/tools/track/py/scene.py +0 -326
  557. package/tools/track/py/test_joint_indices.py +0 -123
  558. package/tools/track/py/test_lr_viterbi.py +0 -195
  559. package/tools/track/py/test_masks.py +0 -229
  560. package/tools/track/py/test_rig.py +0 -288
  561. package/tools/track/py/test_scene.py +0 -354
  562. package/tools/track/py/test_track.py +0 -328
  563. package/tools/track/py/track.py +0 -411
  564. package/tools/track/remote.mjs +0 -140
  565. package/tools/track/rig-dump.mjs +0 -227
  566. package/tools/track/run-box-tests.mjs +0 -14
  567. package/tools/track/run-box.mjs +0 -168
  568. package/tools/track/setup-box.sh +0 -59
  569. package/tools/track/study-2d.mjs +0 -728
  570. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  571. /package/src/{ardy → motion}/ground.js +0 -0
  572. /package/src/{ardy → motion}/ik-drag.js +0 -0
  573. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  574. /package/src/{ardy → motion}/ik.js +0 -0
  575. /package/src/{ardy → motion}/physics-surface.js +0 -0
  576. /package/src/{ardy → motion}/playback-clock.js +0 -0
  577. /package/src/{ardy → motion}/range-pin.js +0 -0
@@ -1,742 +0,0 @@
1
- /**
2
- * service.mjs — the resident (warm) ProjFlow child, contract C11.
3
- *
4
- * WHY THIS EXISTS. Gate GP3 measured a line edit at 7.6-8.6 s wall, of which
5
- * 3.9 s is loading the same 625 MB checkpoint the previous edit already loaded
6
- * and 1.3 s is the actual sample. The detail loop ("drag, look, drag again") is
7
- * ProjFlow's job now, so the load has to be paid once per SESSION rather than
8
- * once per drag. That is the entire idea: one `ssh <host> python driver.py
9
- * --serve` child, owned by this module, fed one NDJSON request per line.
10
- *
11
- * WHAT THIS MODULE IS NOT. It is not a scheduler, not a pool and not a server.
12
- * There is no port, no tunnel and nothing listening on the box — the transport
13
- * is the ssh child's own stdin/stdout, which means the resident inherits ssh's
14
- * authentication and dies with the pipe. One child, one request in flight, a
15
- * queue for the rest.
16
- *
17
- * THE COLD PATH IS THE CONTRACT OF RECORD. Every failure here — a dead child, a
18
- * response whose id does not match the request, a timeout, a driver error
19
- * envelope — turns into a `ResidentError`, and generate.mjs answers a
20
- * ResidentError by running the cold spawn path it always had. So the worst a
21
- * broken resident can do is make an edit as slow as it is today. That is why
22
- * the failure paths below are aggressive rather than forgiving: there is no
23
- * situation in which retrying inside the warm path beats falling back.
24
- *
25
- * TWO KINDS OF FAILURE, DELIBERATELY TREATED DIFFERENTLY:
26
- *
27
- * TRANSPORT failures (child died, id mismatch, unsolicited line, timeout)
28
- * mean the stream can no longer be trusted to pair responses with
29
- * requests. The child is killed and restarted with backoff.
30
- * ENVELOPE failures ({"ok": false} — a bad track, a frame range past the end
31
- * of the source) mean the driver understood us perfectly and refused. The
32
- * child is HEALTHY and stays up; the caller still falls back cold, where
33
- * the same request produces the same named error from the path the
34
- * contract is written against.
35
- *
36
- * RESTART POLICY. 1 s, 5 s, 25 s, capped, and only while a session looks
37
- * recoverable: after MAX_CONSECUTIVE_FAILURES the background loop gives up and
38
- * the service goes idle, so a box that is simply gone does not leave a retry
39
- * loop spawning ssh forever. The next edit starts it again on demand, which is
40
- * the right trigger — someone is at the keyboard.
41
- *
42
- * ENV
43
- * CCLAY_PROJFLOW_RESIDENT=0 disable the resident entirely (cold only)
44
- * CCLAY_PROJFLOW_RESIDENT_READY_MS how long to wait for the model load
45
- * CCLAY_PROJFLOW_RESIDENT_REQ_MS per-request ceiling on the warm path
46
- */
47
-
48
- import { spawn } from "node:child_process";
49
- import { randomBytes } from "node:crypto";
50
- import { dirname, join } from "node:path";
51
- import { fileURLToPath } from "node:url";
52
-
53
- const HERE = dirname(fileURLToPath(import.meta.url));
54
- const DRIVER = join(HERE, "driver.py");
55
-
56
- /** The same ssh options generate.mjs uses for the cold path, plus the keepalive
57
- * that matters more here: this child is expected to sit idle between edits. */
58
- const SSH_OPTS = [
59
- "-o", "BatchMode=yes",
60
- "-o", "ConnectTimeout=10",
61
- "-o", "ServerAliveInterval=30",
62
- "-o", "ServerAliveCountMax=6",
63
- ];
64
-
65
- /** Bumped in lockstep with driver.py's PROTOCOL_VERSION. A resident left over
66
- * from an earlier build answers with the old number and is refused rather than
67
- * trusted, which is the whole reason the field exists.
68
- *
69
- * NOT bumped for `seamPin` (2026-08-29), and the reason is worth stating because
70
- * it looks like an omission. The version guards FRAMING and field MEANING: an
71
- * old resident handed the new field ignores it, a new resident handed a request
72
- * without it applies its own default, and in both directions every existing
73
- * field still means exactly what it meant. Bumping would have been the safer
74
- * instinct and was measurably wrong — a long-lived wrapper process holds this
75
- * constant in memory while it re-ships driver.py from DISK on every resident
76
- * start, so a bump makes the freshly shipped driver announce a number its own
77
- * launcher refuses, and the supervisor crash-loops the box until the wrapper
78
- * itself is restarted. Bump this only when a stale resident would produce a
79
- * WRONG answer rather than an older one, and expect to restart every wrapper. */
80
- export const PROTOCOL_VERSION = 1;
81
-
82
- /** 1 s, 5 s, 25 s, then 25 s forever. Short enough that a transient ssh blip
83
- * costs one cold edit, long enough that a box being rebooted is not hammered. */
84
- export const BACKOFF_MS = Object.freeze([1_000, 5_000, 25_000]);
85
- export const MAX_CONSECUTIVE_FAILURES = 6;
86
-
87
- /** scp + ssh + a 3.9 s model load, with room for a cold page cache. */
88
- const DEFAULT_READY_MS = 45_000;
89
- /** A 100-step edit samples in ~1.3 s; the ceiling exists to bound a hung child,
90
- * not to be a realistic budget. */
91
- const DEFAULT_REQUEST_MS = 120_000;
92
- const PING_MS = 10_000;
93
-
94
- /**
95
- * A failure that means "use the cold path".
96
- *
97
- * `kind` is the only field callers branch on and it exists for one decision:
98
- * whether the child is still trustworthy. `transport` kills and restarts it;
99
- * `envelope` and `disabled` leave it alone.
100
- */
101
- export class ResidentError extends Error {
102
- constructor(message, { kind = "transport", cause } = {}) {
103
- super(message);
104
- this.name = "ResidentError";
105
- this.kind = kind;
106
- if (cause !== undefined) this.cause = cause;
107
- }
108
- }
109
-
110
- /** How long to wait before the Nth consecutive restart (N counted from 1).
111
- * `schedule` exists so a service and this function cannot end up with two
112
- * spellings of the same clamp; only tests ever pass one. */
113
- export function backoffDelay(failures, schedule = BACKOFF_MS) {
114
- const index = Math.max(0, Math.floor(failures) - 1);
115
- return schedule[Math.min(index, schedule.length - 1)];
116
- }
117
-
118
- /** The resident is opt-OUT: it is the fast path and the cold path is always
119
- * underneath it, so the default is on and the env var is an escape hatch for
120
- * bisecting a bad session. */
121
- export function residentEnabled(env = process.env) {
122
- const value = env.CCLAY_PROJFLOW_RESIDENT;
123
- return !(value === "0" || value === "false" || value === "off");
124
- }
125
-
126
- function positiveInt(value, fallback) {
127
- const number = Number(value);
128
- return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
129
- }
130
-
131
- /* ------------------------------------------------------------------------- */
132
- /* the transport */
133
- /* ------------------------------------------------------------------------- */
134
-
135
- function runOnce(argv, timeoutMs) {
136
- return new Promise((resolve) => {
137
- const child = spawn(argv[0], argv.slice(1), { stdio: ["ignore", "pipe", "pipe"] });
138
- let stdout = "";
139
- let stderr = "";
140
- const timer = setTimeout(() => child.kill("SIGKILL"), timeoutMs);
141
- child.stdout.on("data", (chunk) => (stdout += chunk));
142
- child.stderr.on("data", (chunk) => (stderr += chunk));
143
- child.on("error", (error) => {
144
- clearTimeout(timer);
145
- resolve({ code: -1, stdout, stderr: `${stderr}${error.message}` });
146
- });
147
- child.on("close", (code) => {
148
- clearTimeout(timer);
149
- resolve({ code, stdout, stderr });
150
- });
151
- });
152
- }
153
-
154
- /**
155
- * Ship the CURRENT driver.py and start it in serve mode.
156
- *
157
- * The deploy is the cold path's mechanism, unchanged: scp the file that is in
158
- * the tree right now, to a path this process generated. Nothing is installed on
159
- * the box and nothing survives the session — the remote copy is removed when the
160
- * service stops, and if the local process dies without stopping cleanly, a
161
- * single file in /tmp is the whole residue.
162
- *
163
- * `env HOME=... exec python` rather than a bare assignment: `exec` guarantees
164
- * the ssh channel owns the python process directly, so when this end of the
165
- * pipe closes, sshd's SIGHUP lands on the driver rather than on a shell that
166
- * would leave it orphaned holding 800 MiB of VRAM.
167
- */
168
- async function defaultStartChild({ host, python, repo, boxHome, driverPath, onLine }) {
169
- const remoteDriver = `/tmp/cclay-projflow-serve-${Date.now()}-${randomBytes(4).toString("hex")}.py`;
170
- const pushed = await runOnce(["scp", ...SSH_OPTS, driverPath, `${host}:${remoteDriver}`], 60_000);
171
- if (pushed.code !== 0) {
172
- throw new ResidentError(
173
- `could not copy the serve driver to ${host} (exit ${pushed.code}): ${pushed.stderr.trim()}`
174
- );
175
- }
176
- const remote = `exec env HOME=${boxHome} ${python} ${remoteDriver} --serve --repo ${JSON.stringify(repo)}`;
177
- onLine?.(`projflow-resident: starting ${host}:${remoteDriver}`);
178
- const child = spawn("ssh", [...SSH_OPTS, host, remote], { stdio: ["pipe", "pipe", "pipe"] });
179
- child.cleanupRemote = async () => {
180
- await runOnce(["ssh", ...SSH_OPTS, host, `rm -f ${remoteDriver}`], 20_000);
181
- };
182
- return child;
183
- }
184
-
185
- /* ------------------------------------------------------------------------- */
186
- /* the service */
187
- /* ------------------------------------------------------------------------- */
188
-
189
- /**
190
- * One resident child and everything that can go wrong with it.
191
- *
192
- * @param {object} config
193
- * @param {string} config.host ssh destination
194
- * @param {string} config.python the scout venv's python on the box
195
- * @param {string} config.repo the ProjFlow clone on the box
196
- * @param {string} config.boxHome HOME override (keeps CLIP's cache in the scout dir)
197
- * @param {Function} [config.startChild] injected for tests: returns a child
198
- * process-like object with piped stdio. The default scps driver.py and spawns
199
- * `ssh <host> python driver.py --serve`.
200
- */
201
- export function createResidentService({
202
- host,
203
- python,
204
- repo,
205
- boxHome,
206
- driverPath = DRIVER,
207
- startChild = defaultStartChild,
208
- // The schedule is a parameter for one reason: the restart state machine is
209
- // worth testing and 1 + 5 + 25 seconds is not a test. Production never passes
210
- // it.
211
- backoff = BACKOFF_MS,
212
- readyTimeoutMs = positiveInt(process.env.CCLAY_PROJFLOW_RESIDENT_READY_MS, DEFAULT_READY_MS),
213
- requestTimeoutMs = positiveInt(process.env.CCLAY_PROJFLOW_RESIDENT_REQ_MS, DEFAULT_REQUEST_MS),
214
- onLine,
215
- registerExitHook = true,
216
- } = {}) {
217
- if (!host) throw new Error("createResidentService: host is required");
218
-
219
- let child = null;
220
- let state = "idle"; // idle | starting | ready | backoff | stopped
221
- let readyPromise = null;
222
- let readyInfo = null;
223
- let failures = 0;
224
- let restartTimer = null;
225
- let pending = null; // the single in-flight request
226
- let readyWaiters = [];
227
- let queue = Promise.resolve();
228
- let counter = 0;
229
- let stdoutBuffer = "";
230
-
231
- /**
232
- * Does the warm child count as work this process is waiting for?
233
- *
234
- * It must, while a request is in flight or the model is loading — otherwise
235
- * node would exit out from under an awaited edit, because the only pending
236
- * handle is a pipe. It must NOT while the child is merely warm, or every
237
- * short-lived process that ever ran an edit would hang forever holding an
238
- * idle ssh open. So the child is ref'd exactly for the duration of the work
239
- * and unref'd the rest of the time; the exit hook does the killing.
240
- */
241
- function keepAlive(on) {
242
- if (!child) return;
243
- const handles = [child, child.stdin, child.stdout, child.stderr];
244
- for (const handle of handles) {
245
- try {
246
- if (on) handle?.ref?.();
247
- else handle?.unref?.();
248
- } catch {
249
- // A stream that is already destroyed has nothing to ref.
250
- }
251
- }
252
- }
253
-
254
- const say = (text) => {
255
- try {
256
- (pending?.onLine || onLine)?.(text);
257
- } catch {
258
- // A status sink that throws must not take the service with it.
259
- }
260
- };
261
-
262
- /** Kill the child and forget it. `reason` is null for an orderly stop. */
263
- function tearDown(reason) {
264
- const dying = child;
265
- child = null;
266
- readyPromise = null;
267
- readyInfo = null;
268
- stdoutBuffer = "";
269
- const error = new ResidentError(reason || "the resident was stopped", { kind: "transport" });
270
- if (pending) {
271
- const failed = pending;
272
- pending = null;
273
- clearTimeout(failed.timer);
274
- failed.reject(error);
275
- }
276
- const waiting = readyWaiters;
277
- readyWaiters = [];
278
- for (const waiter of waiting) waiter.reject(error);
279
- if (dying) {
280
- dying.removeAllListeners?.("close");
281
- dying.removeAllListeners?.("error");
282
- try {
283
- dying.stdin?.end?.();
284
- } catch {
285
- // The pipe may already be gone; killing is what matters.
286
- }
287
- try {
288
- dying.kill?.("SIGKILL");
289
- } catch {
290
- // Same.
291
- }
292
- // Best-effort: the remote /tmp copy of the driver. A failure here is
293
- // one stale file, never a failed edit, so it is never awaited.
294
- Promise.resolve(dying.cleanupRemote?.()).catch(() => {});
295
- }
296
- }
297
-
298
- /** A transport failure: drop the child, count it, and schedule a restart so
299
- * the NEXT edit is warm even though this one goes cold. */
300
- function fail(reason) {
301
- if (state === "stopped") return;
302
- tearDown(reason);
303
- failures += 1;
304
- if (failures >= MAX_CONSECUTIVE_FAILURES) {
305
- // The box is not coming back on its own. Go idle rather than keep an
306
- // ssh retry loop alive; the next request restarts on demand.
307
- state = "idle";
308
- say(`projflow-resident: giving up after ${failures} failed starts (${reason}); edits stay cold`);
309
- return;
310
- }
311
- const delay = backoffDelay(failures, backoff);
312
- state = "backoff";
313
- say(`projflow-resident: ${reason}; restarting in ${delay / 1000}s`);
314
- clearTimeout(restartTimer);
315
- restartTimer = setTimeout(() => {
316
- restartTimer = null;
317
- if (state !== "backoff") return;
318
- state = "idle";
319
- // Fire and forget: a background restart that fails just schedules the
320
- // next one, and nobody is awaiting this.
321
- start().catch(() => {});
322
- }, delay);
323
- // The timer must never hold the process open — a warm model is a
324
- // convenience, not a reason for `node` to refuse to exit.
325
- restartTimer.unref?.();
326
- }
327
-
328
- function handleMessage(message) {
329
- if (message && message.type === "ready") {
330
- if (readyInfo) {
331
- fail("the resident announced itself twice");
332
- return;
333
- }
334
- readyInfo = message;
335
- const waiting = readyWaiters;
336
- readyWaiters = [];
337
- for (const waiter of waiting) waiter.resolve(message);
338
- return;
339
- }
340
- if (!pending) {
341
- // Nothing is in flight, so this line cannot be an answer to anything.
342
- // The two sides no longer agree about framing.
343
- fail(`the resident sent an unsolicited response (id ${JSON.stringify(message?.id)})`);
344
- return;
345
- }
346
- if (message?.id !== pending.id) {
347
- // THE stale-response guard: pairing this with the in-flight request
348
- // would hand the caller another edit's motion.
349
- const wanted = pending.id;
350
- fail(`response id ${JSON.stringify(message?.id)} does not match request ${JSON.stringify(wanted)}`);
351
- return;
352
- }
353
- const settled = pending;
354
- pending = null;
355
- clearTimeout(settled.timer);
356
- keepAlive(false);
357
- if (message.ok) {
358
- settled.resolve(message.result);
359
- return;
360
- }
361
- // The driver refused the request. It is still healthy, so it stays up and
362
- // only this request falls back.
363
- const detail = message.error?.message || "the resident refused the request";
364
- settled.reject(new ResidentError(detail, { kind: "envelope" }));
365
- }
366
-
367
- function attach(spawned) {
368
- spawned.stdout?.setEncoding?.("utf8");
369
- spawned.stdout?.on("data", (chunk) => {
370
- stdoutBuffer += chunk;
371
- let newline = stdoutBuffer.indexOf("\n");
372
- while (newline >= 0) {
373
- const text = stdoutBuffer.slice(0, newline).trim();
374
- stdoutBuffer = stdoutBuffer.slice(newline + 1);
375
- if (text) {
376
- let message;
377
- try {
378
- message = JSON.parse(text);
379
- } catch {
380
- // driver.py points fd 1 at stderr before it imports anything,
381
- // so a non-JSON line on stdout means something wrote past that
382
- // guard and the stream is no longer a protocol.
383
- fail(`the resident wrote a non-protocol line to stdout: ${text.slice(0, 120)}`);
384
- return;
385
- }
386
- handleMessage(message);
387
- if (!child) return; // handleMessage tore the child down
388
- }
389
- newline = stdoutBuffer.indexOf("\n");
390
- }
391
- });
392
- spawned.stderr?.setEncoding?.("utf8");
393
- spawned.stderr?.on("data", (chunk) => {
394
- for (const text of String(chunk).split("\n")) if (text.trim()) say(text);
395
- });
396
- spawned.on("error", (error) => fail(`the resident child errored: ${error.message}`));
397
- spawned.on("close", (code, signal) => {
398
- if (state === "stopped") return;
399
- fail(`the resident exited (code ${code}${signal ? `, ${signal}` : ""})`);
400
- });
401
- }
402
-
403
- /**
404
- * Start the child and resolve once it has announced a compatible protocol.
405
- *
406
- * Memoised, and the memo is CLEARED by `fail` (through tearDown), so a start
407
- * that failed is never handed to the next caller as a cached rejection — the
408
- * next edit gets a fresh attempt or a fast "in backoff" refusal, both of
409
- * which are honest answers.
410
- */
411
- function start() {
412
- if (state === "stopped") return Promise.reject(new ResidentError("the resident is stopped"));
413
- // Backoff means "we already know this is broken, and the clock has not run
414
- // out". Starting anyway would spend the backoff's whole purpose — and it
415
- // would add a start attempt to an edit that should already be running
416
- // cold. Only the restart timer moves the state out of backoff.
417
- if (state === "backoff") {
418
- return Promise.reject(new ResidentError("the resident is restarting; this edit goes cold"));
419
- }
420
- if (readyPromise) return readyPromise;
421
- state = "starting";
422
- const attempt = (async () => {
423
- const spawned = await startChild({ host, python, repo, boxHome, driverPath, onLine: say });
424
- child = spawned;
425
- attach(spawned);
426
- keepAlive(true); // the model load is work this process is waiting on
427
- const announced = readyInfo ?? await new Promise((resolve, reject) => {
428
- const timer = setTimeout(
429
- () => fail(`the resident did not become ready within ${readyTimeoutMs} ms`),
430
- readyTimeoutMs
431
- );
432
- // NOT unref'd, unlike the restart timer below: this one is the
433
- // promise's own guarantee that it settles, and a process whose only
434
- // remaining work is an awaited start must not exit out from under it.
435
- readyWaiters.push({
436
- resolve: (message) => {
437
- clearTimeout(timer);
438
- resolve(message);
439
- },
440
- reject: (error) => {
441
- clearTimeout(timer);
442
- reject(error);
443
- },
444
- });
445
- });
446
- if (Number(announced.protocol) !== PROTOCOL_VERSION) {
447
- throw new ResidentError(
448
- `the resident speaks protocol ${announced.protocol}, this build speaks ${PROTOCOL_VERSION}`
449
- );
450
- }
451
- state = "ready";
452
- failures = 0;
453
- if (!pending) keepAlive(false);
454
- say(
455
- `projflow-resident: ready on ${announced.device ?? "?"} in ${announced.loadSeconds ?? "?"}s ` +
456
- `(protocol ${announced.protocol})`
457
- );
458
- return announced;
459
- })().catch((error) => {
460
- const wrapped = error instanceof ResidentError
461
- ? error
462
- : new ResidentError(`the resident failed to start: ${error.message}`, { cause: error });
463
- // `fail` is idempotent enough for this: when the child died on its own
464
- // it has already run, and a second call only costs another backoff
465
- // slot on a service that is not going to be used until it restarts.
466
- if (state === "starting" || state === "ready") fail(wrapped.message);
467
- throw wrapped;
468
- });
469
- readyPromise = attempt;
470
- return attempt;
471
- }
472
-
473
- function send(payload, timeoutMs, lineSink) {
474
- return new Promise((resolve, reject) => {
475
- if (!child || state !== "ready") {
476
- reject(new ResidentError("the resident is not ready"));
477
- return;
478
- }
479
- counter += 1;
480
- const id = `r${counter}`;
481
- const timer = setTimeout(() => {
482
- // A timeout is a TRANSPORT failure, not a slow answer we can wait
483
- // out: if the response arrives later it would be paired with the
484
- // next request, which is exactly the bug the ids exist to prevent.
485
- fail(`request ${id} timed out after ${timeoutMs} ms`);
486
- }, timeoutMs);
487
- // Ref'd on purpose (see the ready timeout): this timer is what makes an
488
- // awaited edit always settle, warm child or not.
489
- pending = { id, resolve, reject, timer, onLine: lineSink };
490
- keepAlive(true);
491
- try {
492
- child.stdin.write(`${JSON.stringify({ id, ...payload })}\n`);
493
- } catch (error) {
494
- fail(`could not write to the resident: ${error.message}`);
495
- }
496
- });
497
- }
498
-
499
- /**
500
- * Queue one request against the child, starting it if necessary.
501
- *
502
- * Serialised on purpose. The bridge sends one line edit at a time today, but
503
- * "today" is not a guarantee, and a second writer would interleave lines on
504
- * one stdin.
505
- */
506
- function request(payload, { timeoutMs = requestTimeoutMs, onLine: lineSink } = {}) {
507
- if (state === "stopped") return Promise.reject(new ResidentError("the resident is stopped"));
508
- // A child that is in backoff is a child we already know is broken. Waiting
509
- // for it would add its backoff to an edit that could already be running
510
- // cold.
511
- if (state === "backoff") {
512
- return Promise.reject(new ResidentError("the resident is restarting; this edit goes cold", { kind: "transport" }));
513
- }
514
- const run = async () => {
515
- await start();
516
- return send(payload, timeoutMs, lineSink);
517
- };
518
- // `.then(run, run)` rather than `.then(run)`: one failed request must not
519
- // poison the queue for the next one.
520
- const result = queue.then(run, run);
521
- queue = result.then(() => {}, () => {});
522
- return result;
523
- }
524
-
525
- function stop() {
526
- state = "stopped";
527
- clearTimeout(restartTimer);
528
- restartTimer = null;
529
- tearDown(null);
530
- LIVE.delete(service);
531
- }
532
-
533
- const service = {
534
- key: JSON.stringify({ host, python, repo, boxHome }),
535
- host,
536
- request,
537
- start,
538
- stop,
539
- state: () => state,
540
- failures: () => failures,
541
- isReady: () => state === "ready" && Boolean(child),
542
- info: () => readyInfo,
543
- /** A ping is the only way to claim a resident is up: `state === "ready"`
544
- * says the child announced itself, an answered ping says it is still
545
- * there. probeHealth reports the second, never the first. */
546
- ping: (timeoutMs = PING_MS) => request({ type: "ping" }, { timeoutMs }),
547
- };
548
-
549
- if (registerExitHook) armExitCleanup(service);
550
- return service;
551
- }
552
-
553
- /* ------------------------------------------------------------------------- */
554
- /* process lifetime */
555
- /* ------------------------------------------------------------------------- */
556
-
557
- const LIVE = new Set();
558
- let hooksArmed = false;
559
-
560
- /**
561
- * Kill every resident when this process goes away.
562
- *
563
- * `exit` covers the normal case. The signal handlers are more careful than they
564
- * look: adding a SIGTERM listener SUPPRESSES node's default termination, so a
565
- * handler installed by a library would silently make the bridge unkillable.
566
- * When nobody else is listening we kill the children, remove ourselves and
567
- * re-raise the signal so the default behaviour still happens; when the host
568
- * process has its own handler we only do the cleanup and let it decide.
569
- */
570
- function armExitCleanup(service) {
571
- LIVE.add(service);
572
- if (hooksArmed) return;
573
- hooksArmed = true;
574
- const killAll = () => {
575
- for (const live of LIVE) {
576
- try {
577
- live.stop();
578
- } catch {
579
- // Nothing useful to do on the way out.
580
- }
581
- }
582
- LIVE.clear();
583
- };
584
- process.once("exit", killAll);
585
- for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
586
- const hostHandles = process.listenerCount(signal) > 0;
587
- const handler = () => {
588
- killAll();
589
- if (!hostHandles) {
590
- process.removeListener(signal, handler);
591
- process.kill(process.pid, signal);
592
- }
593
- };
594
- process.on(signal, handler);
595
- }
596
- }
597
-
598
- /* ------------------------------------------------------------------------- */
599
- /* the shared instance */
600
- /* ------------------------------------------------------------------------- */
601
-
602
- let shared = null;
603
-
604
- /** The process-wide resident for one box configuration, created on demand.
605
- * Changing the box (or the venv, or the repo) replaces it: a warm child pointed
606
- * at the wrong repo is worse than no child at all. */
607
- export function getResidentService(config) {
608
- const key = JSON.stringify({
609
- host: config.host,
610
- python: config.python,
611
- repo: config.repo,
612
- boxHome: config.boxHome,
613
- });
614
- if (shared && shared.key !== key) {
615
- shared.stop();
616
- shared = null;
617
- }
618
- if (!shared) shared = createResidentService(config);
619
- return shared;
620
- }
621
-
622
- /** The existing resident, or null. Used by probeHealth, which must never START
623
- * a model load just to answer a health question. */
624
- export function peekResidentService(config) {
625
- if (!shared) return null;
626
- if (!config) return shared;
627
- const key = JSON.stringify({
628
- host: config.host,
629
- python: config.python,
630
- repo: config.repo,
631
- boxHome: config.boxHome,
632
- });
633
- return shared.key === key ? shared : null;
634
- }
635
-
636
- export function stopResidentService() {
637
- if (shared) shared.stop();
638
- shared = null;
639
- }
640
-
641
- /* ------------------------------------------------------------------------- */
642
- /* base64 float32 arrays */
643
- /* ------------------------------------------------------------------------- */
644
-
645
- /** A Float32Array as the {shape, dtype, data} blob driver.py decodes. Little
646
- * endian is the wire format and every machine either side of this is; the
647
- * dtype name travels so the driver can refuse rather than reinterpret. */
648
- export function encodeFloat32(data, shape) {
649
- if (!(data instanceof Float32Array)) throw new TypeError("encodeFloat32: data must be a Float32Array");
650
- const count = shape.reduce((total, dim) => total * dim, 1);
651
- if (count !== data.length) {
652
- throw new Error(`encodeFloat32: shape (${shape.join(",")}) needs ${count} values, got ${data.length}`);
653
- }
654
- return {
655
- shape: [...shape],
656
- dtype: "float32",
657
- data: Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString("base64"),
658
- };
659
- }
660
-
661
- /** The inverse, with the shape checked rather than trusted: a payload whose
662
- * byte count disagrees with its own shape is a corrupt response, not a motion. */
663
- export function decodeFloat32(blob) {
664
- if (!blob || typeof blob !== "object") throw new ResidentError("the resident returned no array");
665
- const shape = Array.isArray(blob.shape) ? blob.shape.map(Number) : null;
666
- if (!shape || shape.some((dim) => !Number.isInteger(dim) || dim < 0)) {
667
- throw new ResidentError(`the resident returned a bad shape ${JSON.stringify(blob.shape)}`);
668
- }
669
- if (blob.dtype && blob.dtype !== "float32") {
670
- throw new ResidentError(`the resident returned dtype ${blob.dtype}; this path is float32 only`);
671
- }
672
- const bytes = Buffer.from(String(blob.data || ""), "base64");
673
- const count = shape.reduce((total, dim) => total * dim, 1);
674
- if (bytes.length !== count * 4) {
675
- throw new ResidentError(
676
- `the resident returned ${bytes.length} bytes for shape (${shape.join(",")}), expected ${count * 4}`
677
- );
678
- }
679
- // A copy, not a view: Buffer.from(base64) has no alignment guarantee and a
680
- // Float32Array view over an odd byteOffset throws.
681
- const data = new Float32Array(count);
682
- for (let index = 0; index < count; index += 1) data[index] = bytes.readFloatLE(index * 4);
683
- return { data, shape };
684
- }
685
-
686
- /* ------------------------------------------------------------------------- */
687
- /* one warm line edit */
688
- /* ------------------------------------------------------------------------- */
689
-
690
- /**
691
- * Run one line edit on the warm child.
692
- *
693
- * Everything a one-shot invocation carries travels in the request, because the
694
- * resident has no per-run argv: the C6 line document, the source motion as a
695
- * base64 float32 blob, the step count (or the preview flag), the ridge, the
696
- * preserve knobs, the seed and the cfg. What comes back is the same
697
- * `(T,22,3)` motion and the same meta the one-shot writes to
698
- * `<out>.meta.json`.
699
- *
700
- * @throws {ResidentError} on ANY failure. The caller's answer to that is the
701
- * cold path — never a retry here.
702
- */
703
- export async function residentLineEdit({
704
- service,
705
- source,
706
- sourceShape,
707
- line,
708
- steps,
709
- preview = false,
710
- ridge,
711
- preserveStride,
712
- preserveMargin,
713
- seamPin,
714
- seed,
715
- cfg,
716
- timeoutMs,
717
- onLine,
718
- } = {}) {
719
- if (!service) throw new ResidentError("residentLineEdit: no resident service");
720
- const payload = {
721
- type: "lineEdit",
722
- line,
723
- source: encodeFloat32(source, sourceShape),
724
- preview: Boolean(preview),
725
- steps: Number(steps),
726
- ridge: Number(ridge),
727
- preserveStride: Number(preserveStride),
728
- preserveMargin: Number(preserveMargin),
729
- };
730
- if (seamPin !== undefined) payload.seamPin = Number(seamPin);
731
- // Omitted rather than defaulted on this side: driver.py owns the defaults
732
- // (0 and 3.0) and two places spelling them is how they drift.
733
- if (seed !== undefined) payload.seed = Number(seed);
734
- if (cfg !== undefined) payload.cfg = Number(cfg);
735
-
736
- const result = await service.request(payload, { timeoutMs, onLine });
737
- const { data, shape } = decodeFloat32(result);
738
- if (!result.meta || typeof result.meta !== "object") {
739
- throw new ResidentError("the resident returned a motion with no metadata");
740
- }
741
- return { positions: data, shape, meta: result.meta };
742
- }