cozyclay 2.1.0 → 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 (583) hide show
  1. package/CHANGELOG.md +65 -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 +22 -6
  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-BlT33CGf.js → first-shot-handoff-CC4xe4HM.js} +1 -1
  33. package/dist/assets/{landing-BSesrCpL.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/llms.txt +14 -0
  40. package/dist/sw.js +3 -6
  41. package/dist/workflow/index.html +5 -4
  42. package/mcp/LIVE-PROTOCOL.md +7 -47
  43. package/mcp/README.md +16 -26
  44. package/mcp/live-hub.mjs +6 -10
  45. package/mcp/package.json +1 -3
  46. package/mcp/tool-handlers.mjs +4 -168
  47. package/package.json +3 -22
  48. package/src/App.jsx +174 -2639
  49. package/src/agent-progress.js +79 -0
  50. package/src/agent-turn-mark.js +30 -0
  51. package/src/analytics.js +1 -19
  52. package/src/app-context.js +5 -0
  53. package/src/app-stage.jsx +2 -34
  54. package/src/asset-pane.jsx +6 -5
  55. package/src/camera-block.js +21 -0
  56. package/src/camera-direction.js +120 -0
  57. package/src/character-kind.js +3 -9
  58. package/src/command-bus.js +8 -6
  59. package/src/commands/cast.js +5 -34
  60. package/src/commands/elements/character.js +1 -1
  61. package/src/commands/elements/motion.js +1 -6
  62. package/src/commands/elements/object.js +6 -2
  63. package/src/commands/elements/shot.js +4 -1
  64. package/src/commands/elements.js +38 -14
  65. package/src/commands/index.js +3 -1
  66. package/src/commands/kits.js +37 -0
  67. package/src/commands/motion-timing.js +57 -0
  68. package/src/commands/motion.js +9 -152
  69. package/src/commands/motion3-compile.js +65 -0
  70. package/src/commands/motion3.js +207 -0
  71. package/src/commands/objects.js +6 -4
  72. package/src/commands/shot.js +49 -11
  73. package/src/commands/stage.js +14 -1
  74. package/src/commands/view.js +4 -1
  75. package/src/document-mirrors.js +42 -0
  76. package/src/document-store.js +4 -0
  77. package/src/domains/cast.js +19 -194
  78. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  79. package/src/domains/motion/take-source.js +51 -0
  80. package/src/domains/motion.js +60 -1838
  81. package/src/domains/scenes.js +12 -55
  82. package/src/domains/shots.js +2 -2
  83. package/src/domains/stage.js +4 -2
  84. package/src/draft-ghost.js +46 -0
  85. package/src/draft-ghost.jsx +129 -0
  86. package/src/dualview.jsx +4 -3
  87. package/src/effects/EffectsLayer.jsx +112 -0
  88. package/src/effects/effect-meshes.js +232 -0
  89. package/src/effects/effect-record.js +103 -0
  90. package/src/effects/emitter.js +59 -0
  91. package/src/effects/simulate.js +261 -0
  92. package/src/effects/state.js +59 -0
  93. package/src/execution-telemetry.js +2 -3
  94. package/src/first-success-guide.jsx +5 -5
  95. package/src/ghost-set.js +257 -0
  96. package/src/held-object-edit.jsx +113 -0
  97. package/src/held-object-pose.js +175 -0
  98. package/src/hierarchy-model.js +1 -1
  99. package/src/hierarchy-panel.jsx +1 -1
  100. package/src/imported-mesh.jsx +103 -0
  101. package/src/kits/held.js +84 -0
  102. package/src/kits/index.js +70 -0
  103. package/src/kits/smoking.js +57 -0
  104. package/src/light-blockers.js +93 -0
  105. package/src/motion/beat-board-model.js +84 -0
  106. package/src/motion/beat-board.css +124 -0
  107. package/src/motion/beat-board.jsx +59 -0
  108. package/src/motion/center-of-mass.js +66 -0
  109. package/src/motion/fix-collisions.js +850 -0
  110. package/src/motion/physics-review.js +118 -0
  111. package/src/{ardy → motion}/physics-support.js +1 -76
  112. package/src/motion/rig-snapshot.js +22 -0
  113. package/src/motion/take-segments.jsx +140 -0
  114. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  115. package/src/{ardy → motion}/timeline.css +1 -2
  116. package/src/{ardy → motion}/timeline.jsx +48 -419
  117. package/src/motion/waypoints.js +76 -0
  118. package/src/motion3/beats.js +83 -0
  119. package/src/motion3/body.js +158 -0
  120. package/src/motion3/bones.js +47 -0
  121. package/src/motion3/carried.js +47 -0
  122. package/src/motion3/compile-job.js +75 -0
  123. package/src/motion3/compile-worker.js +10 -0
  124. package/src/motion3/compiler/anchors.js +105 -0
  125. package/src/motion3/compiler/arm-orient.js +128 -0
  126. package/src/motion3/compiler/channels.js +125 -0
  127. package/src/motion3/compiler/compile.js +205 -0
  128. package/src/motion3/compiler/gait-rhythm.js +97 -0
  129. package/src/motion3/compiler/grip.js +171 -0
  130. package/src/motion3/compiler/head-steady.js +48 -0
  131. package/src/motion3/compiler/hold.js +134 -0
  132. package/src/motion3/compiler/idle.js +113 -0
  133. package/src/motion3/compiler/limbs.js +170 -0
  134. package/src/motion3/compiler/math.js +72 -0
  135. package/src/motion3/compiler/pose.js +162 -0
  136. package/src/motion3/compiler/quat.js +49 -0
  137. package/src/motion3/compiler/skeleton.js +74 -0
  138. package/src/motion3/compiler/validate-common.js +32 -0
  139. package/src/motion3/compiler/validate-goals.js +118 -0
  140. package/src/motion3/compiler/validate-objects.js +143 -0
  141. package/src/motion3/compiler/validate-parts.js +116 -0
  142. package/src/motion3/compiler/validate.js +122 -0
  143. package/src/motion3/compiler/vocab.js +73 -0
  144. package/src/motion3/compiler/walk-pelvis.js +275 -0
  145. package/src/motion3/compiler/walk.js +207 -0
  146. package/src/motion3/compiler/wrist-clamp.js +32 -0
  147. package/src/motion3/delta.js +47 -0
  148. package/src/motion3/describe.js +174 -0
  149. package/src/motion3/docs.js +27 -0
  150. package/src/motion3/edit-carry.js +21 -0
  151. package/src/motion3/fix/ease.js +16 -0
  152. package/src/motion3/fix/ground.js +37 -0
  153. package/src/motion3/fix/index.js +52 -0
  154. package/src/motion3/fix/push.js +71 -0
  155. package/src/motion3/fix/reach.js +46 -0
  156. package/src/motion3/fix/retime.js +79 -0
  157. package/src/motion3/fix/self.js +211 -0
  158. package/src/motion3/fix/slide.js +70 -0
  159. package/src/motion3/fix/turn.js +51 -0
  160. package/src/motion3/follow.js +39 -0
  161. package/src/motion3/free-motion.js +173 -0
  162. package/src/motion3/grip-choice.js +130 -0
  163. package/src/motion3/hold-plan.js +157 -0
  164. package/src/motion3/json-patch.js +77 -0
  165. package/src/motion3/key-aim.js +105 -0
  166. package/src/motion3/key-edit.js +82 -0
  167. package/src/motion3/lenient.js +127 -0
  168. package/src/motion3/object-keys.js +236 -0
  169. package/src/motion3/object-pose.js +182 -0
  170. package/src/motion3/object-tracks.js +12 -0
  171. package/src/motion3/pipeline.js +102 -0
  172. package/src/motion3/playback.js +51 -0
  173. package/src/motion3/probes.js +91 -0
  174. package/src/motion3/qa/body-checks.js +34 -0
  175. package/src/motion3/qa/check-names.js +14 -0
  176. package/src/motion3/qa/checks.js +121 -0
  177. package/src/motion3/qa/contact-checks.js +51 -0
  178. package/src/motion3/qa/explain.js +80 -0
  179. package/src/motion3/qa/fmt.js +5 -0
  180. package/src/motion3/qa/foot-checks.js +72 -0
  181. package/src/motion3/qa/geometry.js +77 -0
  182. package/src/motion3/qa/goals.js +191 -0
  183. package/src/motion3/qa/ground.js +60 -0
  184. package/src/motion3/qa/hints-contact.js +179 -0
  185. package/src/motion3/qa/hints-goal.js +160 -0
  186. package/src/motion3/qa/hints-knee.js +24 -0
  187. package/src/motion3/qa/hints-motion.js +209 -0
  188. package/src/motion3/qa/hints-object.js +91 -0
  189. package/src/motion3/qa/index.js +42 -0
  190. package/src/motion3/qa/key-checks.js +82 -0
  191. package/src/motion3/qa/near.js +96 -0
  192. package/src/motion3/qa/object-checks.js +135 -0
  193. package/src/motion3/qa/patch.js +161 -0
  194. package/src/motion3/qa/severity.js +38 -0
  195. package/src/motion3/qa/spec-checks.js +65 -0
  196. package/src/motion3/qa/style.js +218 -0
  197. package/src/motion3/qa/wrist-checks.js +69 -0
  198. package/src/motion3/qa/wrist-obs.js +60 -0
  199. package/src/motion3/refs.js +120 -0
  200. package/src/motion3/repair-tune.js +121 -0
  201. package/src/motion3/repair.js +83 -0
  202. package/src/motion3/report.js +67 -0
  203. package/src/motion3/rigs/index.js +14 -0
  204. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  205. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  206. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  207. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  208. package/src/motion3/scene.js +60 -0
  209. package/src/motion3/schema/brief.js +89 -0
  210. package/src/motion3/schema/fields.js +44 -0
  211. package/src/motion3/schema/spec-examples.js +46 -0
  212. package/src/motion3/schema/topics.js +111 -0
  213. package/src/motion3/schema/types.js +147 -0
  214. package/src/motion3/search.js +117 -0
  215. package/src/motion3/second-hand.js +112 -0
  216. package/src/motion3/skills/basic.js +82 -0
  217. package/src/motion3/skills/book.js +76 -0
  218. package/src/motion3/skills/car.js +145 -0
  219. package/src/motion3/skills/frames-auto.js +29 -0
  220. package/src/motion3/skills/index.js +83 -0
  221. package/src/motion3/skills/info.js +65 -0
  222. package/src/motion3/skills/smoke-kit.js +109 -0
  223. package/src/motion3/skills/smoke.js +160 -0
  224. package/src/motion3/spec-store.js +59 -0
  225. package/src/motion3/take-edit.js +155 -0
  226. package/src/motion3/take.js +63 -0
  227. package/src/motion3/timeline.js +127 -0
  228. package/src/motion3/views.js +121 -0
  229. package/src/motion3/walk-plan.js +119 -0
  230. package/src/object-catalog.jsx +7 -1
  231. package/src/object-colors.js +98 -0
  232. package/src/object-gizmo-parts.js +115 -0
  233. package/src/object-gizmo.jsx +37 -122
  234. package/src/object-glow.js +139 -0
  235. package/src/object-hidden.js +33 -0
  236. package/src/object-look.js +196 -0
  237. package/src/object-size.js +10 -0
  238. package/src/object-travel.js +71 -6
  239. package/src/panels/CameraPanel.jsx +7 -7
  240. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  241. package/src/panels/LightPanel.jsx +25 -4
  242. package/src/panels/ObjectLookPanel.jsx +88 -0
  243. package/src/panels/PoseImagePanel.jsx +1 -1
  244. package/src/panels/PosePanel.jsx +2 -17
  245. package/src/panels/RigControlPanel.jsx +5 -53
  246. package/src/panels/SubjectBox.jsx +4 -1
  247. package/src/panels/details.css +14 -0
  248. package/src/posestudio.jsx +4 -27
  249. package/src/project-browser.jsx +3 -4
  250. package/src/project-resources.js +18 -31
  251. package/src/project.js +13 -104
  252. package/src/props.jsx +25 -280
  253. package/src/rail-dot-drag.js +23 -0
  254. package/src/render-passes.js +10 -0
  255. package/src/resource-status.jsx +1 -0
  256. package/src/result-modal.jsx +4 -19
  257. package/src/room.jsx +20 -12
  258. package/src/scene-assets.js +8 -6
  259. package/src/scene-light-place.js +72 -0
  260. package/src/scene-lights.jsx +148 -0
  261. package/src/scene-objects.js +31 -139
  262. package/src/scenes.js +15 -105
  263. package/src/semantic-edit.js +2 -5
  264. package/src/set-pieces.jsx +183 -0
  265. package/src/settings-menu.jsx +3 -11
  266. package/src/shell/BottomDock.jsx +11 -53
  267. package/src/shell/CompositionGuideButton.jsx +20 -0
  268. package/src/shell/DetailsSlot.jsx +16 -200
  269. package/src/shell/GhostsMenuGroup.jsx +74 -0
  270. package/src/shell/LibrarySlot.jsx +1 -3
  271. package/src/shell/MenuBar.jsx +3 -3
  272. package/src/shell/PreferencesDialog.jsx +15 -264
  273. package/src/shell/PreferencesSlot.jsx +2 -8
  274. package/src/shell/ShotPreviewHeader.jsx +17 -0
  275. package/src/shell/TopBar.jsx +5 -83
  276. package/src/shell/ViewportToolbar.jsx +23 -93
  277. package/src/shell/agent-glass.css +19 -6
  278. package/src/shell/details-selection.js +42 -0
  279. package/src/shell/ghosts-menu.css +22 -0
  280. package/src/shell/glass-regions.css +29 -3
  281. package/src/shell/preferences.css +6 -1
  282. package/src/shell/viewport.css +12 -0
  283. package/src/shot-authoring.js +4 -7
  284. package/src/soft-shadows.js +37 -0
  285. package/src/stage-attach-frames.js +12 -12
  286. package/src/stage-canvas-rigs.jsx +3 -3
  287. package/src/stage-capture.jsx +42 -8
  288. package/src/stage-characters.jsx +22 -216
  289. package/src/stage-editor-gizmos.jsx +6 -6
  290. package/src/stage-environment.js +93 -0
  291. package/src/stage-hdr-builtins.js +107 -0
  292. package/src/stage-hdr-image.js +116 -0
  293. package/src/stage-hdr-library.js +70 -0
  294. package/src/stage-hdr-map.js +94 -0
  295. package/src/stage-hdr.js +130 -0
  296. package/src/stage-lighting.js +149 -0
  297. package/src/stage-occlusion.js +210 -0
  298. package/src/stage-sky.jsx +159 -0
  299. package/src/stage-startup-defaults.js +5 -9
  300. package/src/studio-actions.js +12 -14
  301. package/src/studio-agent-commands.js +46 -16
  302. package/src/studio-agent-motion.js +9 -9
  303. package/src/studio-agent-protocol.js +46 -73
  304. package/src/studio-app-binding.js +29 -45
  305. package/src/studio-asset-list.js +17 -0
  306. package/src/studio-contact-sheet.js +37 -5
  307. package/src/studio-elements.js +27 -7
  308. package/src/studio-entity-bounds.js +18 -0
  309. package/src/studio-error-codes.js +8 -0
  310. package/src/studio-export-framing.js +152 -0
  311. package/src/studio-lighting-check.js +103 -0
  312. package/src/studio-motion-visual.js +150 -0
  313. package/src/studio-schema-errors.js +80 -0
  314. package/src/studio-support-gap.js +74 -0
  315. package/src/styles.css +1 -19
  316. package/src/timeline-defaults.js +5 -0
  317. package/src/timeline-extent.js +8 -20
  318. package/src/tutorial-walk.js +17 -0
  319. package/src/ui-scrub.js +1 -1
  320. package/src/use-shot-camera-tracks.js +12 -0
  321. package/src/view-ghosts.js +97 -0
  322. package/src/view-ghosts.jsx +49 -0
  323. package/src/workflow/AgentPanel.jsx +47 -166
  324. package/src/workflow/AgentProgressCard.jsx +107 -0
  325. package/src/workflow/AgentReplyText.jsx +37 -0
  326. package/src/workflow/AgentTurnSteps.jsx +84 -0
  327. package/src/workflow/LoginPanel.jsx +140 -0
  328. package/src/workflow/WorkflowBuilder.jsx +7 -23
  329. package/src/workflow/agent-client.js +54 -51
  330. package/src/workflow/agent-panel.css +71 -1
  331. package/src/workflow/agent-progress-card.css +167 -0
  332. package/src/workflow/agent-progress-view.js +47 -0
  333. package/src/workflow/agent-turn-steps.css +113 -0
  334. package/src/workflow/agent-turn.js +191 -0
  335. package/src/workflow/login-client.js +34 -0
  336. package/src/workflow/motion-input.js +18 -31
  337. package/src/workflow/node-schema.js +1 -1
  338. package/src/workflow/reply-markdown.js +69 -0
  339. package/src/workflow/scene-asset-sync.js +8 -8
  340. package/tools/agent-session-stats.mjs +61 -0
  341. package/tools/dev-full.mjs +5 -56
  342. package/tools/eslint-baseline.json +12 -13
  343. package/tools/headless-studio.mjs +85 -0
  344. package/tools/knip-baseline.json +0 -179
  345. package/tools/loc-baseline.json +33 -83
  346. package/tools/motion3/extract-body.mjs +103 -0
  347. package/tools/motion3/extract-rig.mjs +85 -0
  348. package/tools/process-supervisor.mjs +0 -88
  349. package/tools/run-tests.mjs +9 -124
  350. package/bin/agent/motion-runtime.mjs +0 -76
  351. package/dist/ardy/cskel27-rest.json +0 -557
  352. package/dist/assets/analytics-B41FmPYI.js +0 -1
  353. package/dist/assets/app-Do-Og5Pu.css +0 -1
  354. package/dist/assets/app-DwP-tl2x.js +0 -4920
  355. package/dist/assets/config-Bx7pYsD9.js +0 -1
  356. package/dist/assets/config-DeoFTYzb.css +0 -1
  357. package/dist/assets/demo-Dt0qZfS7.js +0 -1
  358. package/dist/assets/scene-asset-sync-BCWDJB1d.js +0 -19
  359. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  360. package/dist/assets/ticket-jqMhqL88.js +0 -1
  361. package/dist/assets/workflow-Br-K7Srs.js +0 -179
  362. package/dist/demo/walk-then-stop.npz +0 -0
  363. package/mcp/ardy-prompts.mjs +0 -218
  364. package/src/ardy/auto-fix-panel.css +0 -108
  365. package/src/ardy/auto-physics.js +0 -1194
  366. package/src/ardy/client.js +0 -153
  367. package/src/ardy/convert.js +0 -313
  368. package/src/ardy/cskel27-neutral.js +0 -39
  369. package/src/ardy/cskel27.js +0 -77
  370. package/src/ardy/export.js +0 -165
  371. package/src/ardy/fix-collisions.js +0 -2257
  372. package/src/ardy/foot-lock.js +0 -230
  373. package/src/ardy/motion-calibration.js +0 -118
  374. package/src/ardy/motion-edit.js +0 -213
  375. package/src/ardy/motion-url.js +0 -55
  376. package/src/ardy/mp4-duration.js +0 -102
  377. package/src/ardy/npz.js +0 -595
  378. package/src/ardy/physics-panel.css +0 -21
  379. package/src/ardy/physics-panel.jsx +0 -89
  380. package/src/ardy/physics-refine.js +0 -111
  381. package/src/ardy/physics-review.js +0 -567
  382. package/src/ardy/physics-temporal.js +0 -54
  383. package/src/ardy/platform-fit-panel.css +0 -64
  384. package/src/ardy/platform-fit-panel.jsx +0 -41
  385. package/src/ardy/platform-fit.js +0 -326
  386. package/src/ardy/playback.js +0 -711
  387. package/src/ardy/pose-pin.js +0 -97
  388. package/src/ardy/prompt-clips.js +0 -30
  389. package/src/ardy/retime.js +0 -211
  390. package/src/ardy/root-drop.js +0 -369
  391. package/src/ardy/timeline-resize.js +0 -13
  392. package/src/ardy/to-cskel27.js +0 -225
  393. package/src/ardy/trim.js +0 -33
  394. package/src/ardy/waypoints.js +0 -369
  395. package/src/ardy-bridge-contract.js +0 -281
  396. package/src/domains/motion/motion-video-capture.js +0 -664
  397. package/src/i2v-motion-client.js +0 -77
  398. package/src/i2v-motion-studio.jsx +0 -180
  399. package/src/line-edit.js +0 -1348
  400. package/src/motion/generation.js +0 -105
  401. package/src/motion-readiness-ui.jsx +0 -67
  402. package/src/motion-readiness.js +0 -40
  403. package/src/motion-resources.js +0 -177
  404. package/src/motion-store.js +0 -65
  405. package/src/motion-trail.js +0 -601
  406. package/src/multimodel-ingest.js +0 -394
  407. package/src/panels/PromptBlocksPanel.jsx +0 -444
  408. package/src/panels/VideoCapturePanel.jsx +0 -181
  409. package/src/pose-extract/browser.js +0 -42
  410. package/src/pose-extract/detector.js +0 -100
  411. package/src/pose-extract/fit.js +0 -227
  412. package/src/pose-extract/image-frame.js +0 -51
  413. package/src/pose-extract/index.js +0 -58
  414. package/src/pose-extract/keyframes.js +0 -74
  415. package/src/pose-extract/landmarks.js +0 -162
  416. package/src/pose-extract/mirror.js +0 -153
  417. package/src/pose-extract/take.js +0 -131
  418. package/src/pose-extract/video-frames.js +0 -91
  419. package/src/take-recipe.js +0 -165
  420. package/src/trail-key-conflicts.js +0 -39
  421. package/src/trail-pick.js +0 -59
  422. package/src/use-i2v-capture-modal.js +0 -15
  423. package/tools/ardy/BRIDGE.md +0 -37
  424. package/tools/ardy/GVHMR-NPZ.md +0 -68
  425. package/tools/ardy/README.md +0 -124
  426. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  427. package/tools/ardy/artifacts.mjs +0 -34
  428. package/tools/ardy/bridge.mjs +0 -1732
  429. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  430. package/tools/ardy/cclay_constrained_generate.py +0 -561
  431. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  432. package/tools/ardy/cclay_motion_edit.py +0 -510
  433. package/tools/ardy/cclay_sequence_generate.py +0 -705
  434. package/tools/ardy/cclay_worker.py +0 -304
  435. package/tools/ardy/dump-npz.py +0 -205
  436. package/tools/ardy/extract-rest.mjs +0 -299
  437. package/tools/ardy/extract.mjs +0 -507
  438. package/tools/ardy/footage.mjs +0 -467
  439. package/tools/ardy/gvhmr-floor.mjs +0 -102
  440. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  441. package/tools/ardy/gvhmr_fastpath.py +0 -168
  442. package/tools/ardy/gvhmr_trajectory.py +0 -240
  443. package/tools/ardy/merge-text-encoder.py +0 -160
  444. package/tools/ardy/mocap-metrics.mjs +0 -200
  445. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  446. package/tools/ardy/motion-stabilize.mjs +0 -348
  447. package/tools/ardy/npz.mjs +0 -414
  448. package/tools/ardy/pose-to-npz.mjs +0 -106
  449. package/tools/ardy/prompt-limits.mjs +0 -5
  450. package/tools/ardy/run-edit-on-box.sh +0 -73
  451. package/tools/ardy/run-local.mjs +0 -233
  452. package/tools/ardy/run-on-box.sh +0 -593
  453. package/tools/ardy/run-sequence-on-box.sh +0 -177
  454. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  455. package/tools/ardy/runners/index.mjs +0 -18
  456. package/tools/ardy/runners/local.mjs +0 -455
  457. package/tools/ardy/runners/proc.mjs +0 -125
  458. package/tools/ardy/runners/remote.mjs +0 -252
  459. package/tools/ardy/setup-local.mjs +0 -189
  460. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  461. package/tools/ardy/setup-text-encoder.py +0 -306
  462. package/tools/ardy/smpl-cskel27.mjs +0 -178
  463. package/tools/ardy/visual-qa.mjs +0 -196
  464. package/tools/ardy/vq-car.mjs +0 -52
  465. package/tools/bench/EXP3.md +0 -61
  466. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  467. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  468. package/tools/bench/cclay_bench_runner.py +0 -42
  469. package/tools/bench/cube-contact.mjs +0 -162
  470. package/tools/bench/exp3.mjs +0 -152
  471. package/tools/bench/extract-bench-lib.mjs +0 -216
  472. package/tools/bench/extract-bench.mjs +0 -302
  473. package/tools/bench/fal-generate.mjs +0 -88
  474. package/tools/bench/fit/README.md +0 -189
  475. package/tools/bench/fit/camera.mjs +0 -59
  476. package/tools/bench/fit/contact.mjs +0 -419
  477. package/tools/bench/fit/footlock.mjs +0 -234
  478. package/tools/bench/fit/motion.mjs +0 -85
  479. package/tools/bench/fit/pin.mjs +0 -27
  480. package/tools/bench/fit/remote.mjs +0 -105
  481. package/tools/bench/fit-bench.mjs +0 -126
  482. package/tools/bench/fit-sanity.mjs +0 -45
  483. package/tools/bench/metrics.mjs +0 -313
  484. package/tools/bench/obs/depth.mjs +0 -258
  485. package/tools/bench/obs/extrinsics.mjs +0 -175
  486. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  487. package/tools/bench/obs/ground.mjs +0 -294
  488. package/tools/bench/obs/heading.mjs +0 -151
  489. package/tools/bench/obs/ladder.mjs +0 -555
  490. package/tools/bench/obs/mannequin-betas.json +0 -124
  491. package/tools/bench/obs/remote.mjs +0 -83
  492. package/tools/bench/obs/rest_joints.py +0 -52
  493. package/tools/bench/obs/ybot-targets.mjs +0 -49
  494. package/tools/bench/obs-bench.mjs +0 -520
  495. package/tools/bench/score.mjs +0 -343
  496. package/tools/bench/summarize.mjs +0 -86
  497. package/tools/demo-worker/README.md +0 -137
  498. package/tools/demo-worker/api-client.mjs +0 -233
  499. package/tools/demo-worker/generate.mjs +0 -301
  500. package/tools/demo-worker/index.mjs +0 -466
  501. package/tools/demo-worker/sign-core.mjs +0 -112
  502. package/tools/demo-worker/sign.mjs +0 -161
  503. package/tools/gt-render/browser.mjs +0 -159
  504. package/tools/gt-render/camera-math.mjs +0 -276
  505. package/tools/gt-render/page.mjs +0 -267
  506. package/tools/gt-render/render.mjs +0 -514
  507. package/tools/gt-render/scene-box.mjs +0 -31
  508. package/tools/gt-render/take-transform.mjs +0 -87
  509. package/tools/kimodo/compare-seam.mjs +0 -108
  510. package/tools/kimodo/constraints.mjs +0 -244
  511. package/tools/kimodo/edit.mjs +0 -259
  512. package/tools/kimodo/effector-constraints.mjs +0 -200
  513. package/tools/kimodo/generate.mjs +0 -646
  514. package/tools/kimodo/local-output.mjs +0 -265
  515. package/tools/kimodo/measure-pose.mjs +0 -65
  516. package/tools/kimodo/measure-preserve.mjs +0 -899
  517. package/tools/kimodo/measure-waypoints.mjs +0 -55
  518. package/tools/kimodo/mlx-generate.py +0 -111
  519. package/tools/kimodo/pose-constraints.mjs +0 -248
  520. package/tools/kimodo/preserve-mask.mjs +0 -543
  521. package/tools/kimodo/read-npz.mjs +0 -189
  522. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  523. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  524. package/tools/kimodo/runner.mjs +0 -284
  525. package/tools/kimodo/seam-batch.mjs +0 -77
  526. package/tools/kimodo/setup-local.mjs +0 -30
  527. package/tools/kimodo/setup-on-box.sh +0 -339
  528. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  529. package/tools/morphgs/assets/gen_truth.py +0 -39
  530. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  531. package/tools/morphgs/assets/playback-check.mjs +0 -30
  532. package/tools/morphgs/demo-gate.sh +0 -26
  533. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  534. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  535. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  536. package/tools/morphgs/setup-on-cluster.sh +0 -126
  537. package/tools/projflow/driver.py +0 -1069
  538. package/tools/projflow/generate.mjs +0 -805
  539. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  540. package/tools/projflow/line-edit-job.mjs +0 -547
  541. package/tools/projflow/replay.mjs +0 -452
  542. package/tools/projflow/runner.mjs +0 -269
  543. package/tools/projflow/service.mjs +0 -742
  544. package/tools/track/DESIGN.md +0 -513
  545. package/tools/track/backfill-provenance.mjs +0 -116
  546. package/tools/track/budget.mjs +0 -8
  547. package/tools/track/check-rig.mjs +0 -209
  548. package/tools/track/diagnostics.schema.json +0 -60
  549. package/tools/track/export-rig.mjs +0 -256
  550. package/tools/track/fallback.mjs +0 -7
  551. package/tools/track/fk-parity-fixture.mjs +0 -161
  552. package/tools/track/gate.mjs +0 -349
  553. package/tools/track/masks.mjs +0 -318
  554. package/tools/track/metrics.mjs +0 -201
  555. package/tools/track/publish-obs.mjs +0 -108
  556. package/tools/track/py/check_env.py +0 -61
  557. package/tools/track/py/eval_lr.py +0 -229
  558. package/tools/track/py/lr_viterbi.py +0 -294
  559. package/tools/track/py/masks.py +0 -405
  560. package/tools/track/py/objective.py +0 -382
  561. package/tools/track/py/rig.py +0 -508
  562. package/tools/track/py/scene.py +0 -326
  563. package/tools/track/py/test_joint_indices.py +0 -123
  564. package/tools/track/py/test_lr_viterbi.py +0 -195
  565. package/tools/track/py/test_masks.py +0 -229
  566. package/tools/track/py/test_rig.py +0 -288
  567. package/tools/track/py/test_scene.py +0 -354
  568. package/tools/track/py/test_track.py +0 -328
  569. package/tools/track/py/track.py +0 -411
  570. package/tools/track/remote.mjs +0 -140
  571. package/tools/track/rig-dump.mjs +0 -227
  572. package/tools/track/run-box-tests.mjs +0 -14
  573. package/tools/track/run-box.mjs +0 -168
  574. package/tools/track/setup-box.sh +0 -59
  575. package/tools/track/study-2d.mjs +0 -728
  576. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  577. /package/src/{ardy → motion}/ground.js +0 -0
  578. /package/src/{ardy → motion}/ik-drag.js +0 -0
  579. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  580. /package/src/{ardy → motion}/ik.js +0 -0
  581. /package/src/{ardy → motion}/physics-surface.js +0 -0
  582. /package/src/{ardy → motion}/playback-clock.js +0 -0
  583. /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
- }