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
package/CHANGELOG.md CHANGED
@@ -2,6 +2,71 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 3.0.0 - 2026-10-19
6
+
7
+ Motion is computed, not generated. The Studio agent writes a motion spec (key
8
+ poses, skills, walks, contacts and goals) and a deterministic compiler in the
9
+ browser bakes it frame by frame and measures it with QA. The agent compiles,
10
+ reads the numbers and fixes the spec in seconds, with no GPU host.
11
+
12
+ Breaking
13
+
14
+ - The motion-generation backend (ARDY with Kimodo) is removed, and with it
15
+ text-to-motion generation, AI video, video motion capture, line edit,
16
+ AutoPhysics, platform fit and the collision fixer. `npm run dev` starts no
17
+ motion bridge and `CCLAY_KIMODO_HOST` is no longer read.
18
+ - Motion is stored as a new take, `cozyclay.take.v3`: per-bone rotations on
19
+ the X Bot and Y Bot rigs, the hips' path, object tracks and the spec that
20
+ made it. A 2.x project still opens, but its generated (npz) motion is
21
+ dropped when the project is read; compile new motion for its characters.
22
+
23
+ Motion compiler
24
+
25
+ - `motion.compile`, `motion.qa`, `motion.sample`, `motion.describe` and
26
+ `motion.keyObject`. Specs combine keys, generators and skills (`standIdle`,
27
+ `walkTo`, `sitInSeat`, `exitCar`, `reachGrip`, `smokeCigarette`), with
28
+ two-bone IK limbs, finger presets and hand anchors on the world, the body,
29
+ bones, moving objects or the mouth.
30
+ - QA measures collisions with scene objects and the body's own skin, foot
31
+ slides, floating, reach, balance, elbow, knee and wrist limits and pops, and
32
+ names each issue's cause with a hint. Hints become patches that
33
+ `repair` rounds apply; `goals` (near, above, moved, still, speed, ordering)
34
+ state what the motion must do.
35
+ - Held props: grab and release, carried groups, keyed and falling objects,
36
+ prop kits and aim keys; the arm follows what the hand holds.
37
+ - The walk rolls heel to toe, sets its hips from the legs, keeps an uneven
38
+ natural rhythm, and scales its knees, ankles, arm swing and trunk with its
39
+ pace. A held pose breathes and shifts its weight.
40
+ - Compiles run in a Web Worker, off the editor's main thread. The Sequencer
41
+ cuts, retimes and trims an installed take.
42
+
43
+ Look
44
+
45
+ - Practical point and spot lights, glow, lighting looks
46
+ (`stage.applyLighting`), sky light with ambient occlusion and HDR
47
+ environment maps.
48
+ - Deterministic effects: smoke, exhale, steam, fire, sparks, dust, rain, snow
49
+ and a motion trail.
50
+ - Characters default to white clay; the outline ink is at half strength.
51
+
52
+ Agent and Studio
53
+
54
+ - The agent's motion drafts show while it works: as beats on the timeline,
55
+ as a ghost on the character and in the panel, which also shows the running
56
+ turn's reasoning and steps. Replies render their Markdown.
57
+ - Contact sheets pick beat frames, add views that show depth, and draw
58
+ trails and onion-skin ghosts; the viewport has ghosts too.
59
+ - The agent cuts for a new view and lays a camera move as one rail.
60
+ - Connect a model runs the agent runtime's own provider login; keys are
61
+ checked before they are kept. Supervisor review is off by default.
62
+ - Objects scale down to 1 cm, inspect reports each object's world box,
63
+ verify reports support gaps, and a follow dolly goes up to 40 m/s.
64
+
65
+ ## 2.1.1 - 2026-10-13
66
+
67
+ - Include the sampled framing fix, embedded composition-guide controls, and
68
+ playground guide coverage merged after the 2.1.0 package was published.
69
+
5
70
  ## 2.1.0 - 2026-10-12
6
71
 
7
72
  Storyboard and Animation modes. A project is now either a storyboard (a row of
package/README.md CHANGED
@@ -42,7 +42,7 @@ That is the whole install. Not sure yet? **[Try it in the browser first](https:/
42
42
  npx cozyclay --scene city-block
43
43
  ```
44
44
 
45
- The studio ships seeded with a pre-generated motion clip, so you can scrub the timeline, drive the cameras and draw a dolly rail straight away — generating *new* motion is optional and uses the Kimodo bridge when configured.
45
+ The camera tutorial seeds a compiled walk, so you can scrub the timeline, drive the cameras and draw a dolly rail straight away. New motion is compiled in the browser from a motion spec — no GPU box or sidecar needed.
46
46
 
47
47
  ## Demo
48
48
 
@@ -57,9 +57,8 @@ https://github.com/user-attachments/assets/1d0113e5-6922-443d-affc-1bdabc666247
57
57
  | **Cut and move the camera** | Add shots on the timeline, draw a dolly rail on the Top-View, set speed, height and crane, and preview the move through the shot camera. Each shot carries a **Target model** (Seedance 2.5, Kling 2, Veo 3, self-hosted MiniMax-H3) and is flagged when the cut runs past that model's limits. |
58
58
  | **Export for AI video** | One **Export ▾** menu: a keyframe pack (first/last frame, clip, camera JSON, prompt, README) as a zip, an mp4 of the shot, depth + normal conditioning passes, a storyboard contact sheet, and an OTIO cut list. Seedance 2.5 reads the pack's greybox clip as its white-model reference video, and MiniMax H3, Wan 3.0, LTX Desktop and fal render-to-real accept the same clip. The **Shot Prompt** turns the framing into a structured prompt for the model you picked. |
59
59
  | **Undo anything** | Every scene mutation goes through one history store: a drag, a scrub, an inspector edit, an agent's camera key is exactly one undo entry. `Esc` cancels an in-flight drag and restores the pre-drag transform. |
60
- | **Generate motion** | Pose characters and export poses, sequence multi-phase motion as Prompt Blocks on a resizable timeline, send them to Kimodo, then play the result back with sparse IK correction where the generated motion needs fixing. Draw over a joint's trail to reshape a take, keep most of it and regenerate a window, and step back through its history. |
61
- | **Capture motion from video or a photo** | Drop a clip or a still: the GPU box runs [GVHMR](https://github.com/zju3dv/GVHMR) and the result is retargeted onto the character with stabilisation, contact correction and a quality gate. |
62
- | **Direct it with an AI** | Open the **Agent panel** (`Cmd/Ctrl+B`), sign in with your ChatGPT account, and ask for a shot in plain language; the conversation survives reloads and you can paste a reference screenshot into it. Or connect Claude — or any MCP client — or drive the Studio from a terminal with `cclay live`. All three place the cast, frame "a low wide profile", generate multi-phase motion, and the viewport moves in front of you. See [AI control](#ai-control). |
60
+ | **Compile motion** | The agent writes a motion spec — key poses, hand anchors, props, walks, hinged doors — and a deterministic compiler bakes every frame on the X Bot or Y Bot rig, then QA measures collisions, foot slide, reach and the contacts the spec declares ([`docs/motion3-agent-guide.md`](docs/motion3-agent-guide.md)). A compiled take is saved as its spec and replays identically; sparse IK keys and range pins correct any frame on top. |
61
+ | **Direct it with an AI** | Open the **Agent panel** (`Cmd/Ctrl+B`), sign in with your ChatGPT account, and ask for a shot in plain language; the conversation survives reloads and you can paste a reference screenshot into it. Or connect Claude — or any MCP client — or drive the Studio from a terminal with `cclay live`. All three place the cast, frame "a low wide profile", compile motion, and the viewport moves in front of you. See [AI control](#ai-control). |
63
62
 
64
63
  ## Modes
65
64
 
@@ -68,9 +67,9 @@ A project is either a **Storyboard** or an **Animation**. Pick it on the start s
68
67
  | | Storyboard | Animation |
69
68
  | --- | --- | --- |
70
69
  | **Choose it when** | You want a row of still panels: framing, who stands where, a caption and a hold per panel | You want moving shots: motion on a timeline, camera moves, takes |
71
- | **Timeline** | The **Board** dock tab: one card per panel with a greybox thumbnail, caption, hold, duplicate, delete and drag-to-reorder | Shots on the timeline, with motion and Prompt Blocks |
70
+ | **Timeline** | The **Board** dock tab: one card per panel with a greybox thumbnail, caption, hold, duplicate, delete and drag-to-reorder | Shots on the timeline, with the compiled take |
72
71
  | **Cast** | Rigged characters or capsule figures; each panel can keep its own placement ("Use stage placement" clears it) | Rigged characters with Pose and Motion mode; capsule figures for blocking |
73
- | **Motion tools** | Hidden: no Generate group, no Motion mode (`4`); the agent has no `generate_motion` and the MCP motion tools answer `NOT_IN_MODE` | Available |
72
+ | **Motion tools** | Hidden: no Motion mode (`4`) | Available |
74
73
  | **Agent** | **Agent · Storyboard** builds one panel per request and never generates motion | The full Studio tool set |
75
74
  | **Exports** | **Stylize** a panel through the image route, **Send to Workflow** (the stylized image or the greybox) from its card, plus the Export ▾ menu: **Contact sheet (PNG)**, **Panel pack (zip)**, **All panels (zip)**, **Animatic (mp4)**, **Cut list (OTIO)** and **Depth + normal (PNG)**; File ▸ **Export as Animation project…** turns the panels into an Animation. There is no keyframe pack or shot video in a storyboard | Export ▾: keyframe pack, mp4, depth + normal passes, contact sheet, OTIO cut list |
76
75
 
@@ -101,18 +100,7 @@ npm install
101
100
  npm run dev
102
101
  ```
103
102
 
104
- Open `http://127.0.0.1:5180/app/` for the Studio (the root redirects there) and `http://127.0.0.1:5180/workflow/` for the Workflow canvas. `npm run dev` starts the studio together with its local Kimodo bridge once `CCLAY_KIMODO_HOST` points at a GPU box; without that variable it starts the studio alone and says so. `npm run dev:ui` starts the browser UI alone in every case.
105
-
106
- ### Motion generation
107
-
108
- Generating new motion needs Kimodo on a machine you can reach. Point the Studio at it and install the worker once:
109
-
110
- ```bash
111
- CCLAY_KIMODO_HOST=user@your-gpu-box npm run kimodo:setup
112
- CCLAY_KIMODO_HOST=user@your-gpu-box npx cozyclay
113
- ```
114
-
115
- The installer detects OS, architecture, RAM and CUDA and picks the best-supported backend for that host — kimodo-mlx on a big Apple Silicon machine, kimodo.cpp with Metal or CPU otherwise, the upstream PyTorch stack on NVIDIA. An SSH-accessible NVIDIA box is the classic target and the only route with the full feature set; the same box runs GVHMR for video and photo mocap. Routes, overrides and local-only limits are in [`docs/kimodo-setup.md`](docs/kimodo-setup.md).
103
+ Open `http://127.0.0.1:5180/app/` for the Studio (the root redirects there) and `http://127.0.0.1:5180/workflow/` for the Workflow canvas. `npm run dev` starts the studio together with its local OAuth and agent sidecar; `npm run dev:ui` starts the browser UI alone.
116
104
 
117
105
  ## AI control
118
106
 
@@ -153,8 +141,8 @@ The studio ships an [MCP](https://modelcontextprotocol.io) server, so any MCP cl
153
141
 
154
142
  Drop that into `claude_desktop_config.json` (or any MCP client config) and restart the client. The first run automatically installs the MCP SDK's 95-package tree; opening the studio never waits on it, so those dependencies are fetched only when you actually want the server.
155
143
 
156
- - **Editor open?** Tool calls move the visible viewport — camera, cast, set, generated motion, prompt blocks on the timeline.
157
- - **No editor?** Scene and project tools run headless: block scenes, derive film vocabulary ("wide shot · right profile · knee level · 24mm"), render AI video prompts, and write `.cclayproject` files. `capture_frame`, `set_prompt_blocks`, `generate_motion`, and `apply_batch` require the live editor.
144
+ - **Editor open?** Tool calls move the visible viewport — camera, cast, set, compiled motion on the timeline.
145
+ - **No editor?** Scene and project tools run headless: block scenes, derive film vocabulary ("wide shot · right profile · knee level · 24mm"), render AI video prompts, and write `.cclayproject` files. `capture_frame` and `apply_batch` require the live editor.
158
146
 
159
147
  Tools, transports and the live-control protocol are documented in [`mcp/README.md`](mcp/README.md).
160
148
 
@@ -195,13 +183,13 @@ Every control's home is recorded in [`docs/studio-ui-ia.md`](docs/studio-ui-ia.m
195
183
 
196
184
  | Read | For |
197
185
  | --- | --- |
198
- | [`docs/kimodo-setup.md`](docs/kimodo-setup.md) | Installing Kimodo: the route table, CUDA over SSH, local MLX/Metal/CPU generation and what each route supports |
186
+ | [`docs/motion3-agent-guide.md`](docs/motion3-agent-guide.md) | How the agent writes a motion spec, compiles it and reads QA |
199
187
  | [`docs/agent-cli.md`](docs/agent-cli.md) | Driving the Studio from a terminal with `cclay live` |
200
188
  | [`docs/agent-prompt-to-scene.md`](docs/agent-prompt-to-scene.md) | The ask / infer / omit rule the Agent follows when turning a video prompt into blocking, and the one-panel-per-request rule in storyboards |
201
189
  | [`mcp/README.md`](mcp/README.md) | MCP tools, transports, and the live-control protocol |
202
190
  | [`docs/studio-ui-ia.md`](docs/studio-ui-ia.md) | Where every Studio control lives and why |
203
191
  | [`docs/privacy.md`](docs/privacy.md) | The full analytics disclosure: events, identifiers, opt-out, internal QA marking |
204
- | [`workers/api/README.md`](workers/api/README.md) | The hosted demo on cozyclay.org: queue policy, the Cloudflare Worker and the GPU-box poller |
192
+ | [`workers/api/README.md`](workers/api/README.md) | The cozyclay.org API Worker: sign-in and the hosted video-model route |
205
193
  | [`docs/releasing.md`](docs/releasing.md) | Publishing a signed npm release |
206
194
  | [`CHANGELOG.md`](CHANGELOG.md) | What changed in each release |
207
195
 
@@ -217,7 +205,7 @@ Longer reads on the site: [Greybox to AI video](https://cozyclay.org/greybox-to-
217
205
  | `npm run test:objects` | Gizmo interaction in a real browser — needs `npm run dev:ui` in another shell |
218
206
  | `npm run test:theme` / `test:appearance` / `test:layout` | UI theme, appearance, layout |
219
207
  | `npm run test:lifecycle` | Dev-server process lifecycle |
220
- | `npm run test:ardy` | Motion conversion, playback, and IK pipeline |
208
+ | `npm run test:ardy` | IK pipeline, range pins and timeline helpers |
221
209
  | `node tools/run-tests.mjs` | Every Node verification file (what CI runs) |
222
210
  | `npm run qa:browser -- node test/qa-camera-tutorial-browser.mjs` | The seven-step tutorial driven with real input, beacons pinned to their controls |
223
211
  | `QA_URL=… CDP_PORT=… OUT=/tmp/count npm run qa:browser -- node tools/qa/studio-control-count.mjs` | Simultaneously visible controls per mode (budget: Scene ≤35 / Camera ≤38 / Motion ≤52) |
@@ -235,7 +223,7 @@ npm run qa:browser -- node <qa-script>
235
223
 
236
224
  Found something broken, or want a feature? [Open an issue](https://github.com/NomaDamas/CozyClay/issues) — bug reports with a repro are the most useful thing you can send. Contributions are accepted under `AGPL-3.0-or-later`.
237
225
 
238
- **Repository hygiene.** Generated motion archives, QA output, build output, logs and local runtime artifacts are not source files and must not be committed. Keep `tools/ardy/out/`, `artifacts/`, `dist/`, `.gjc/` and `.npz` files local.
226
+ **Repository hygiene.** Generated motion archives, QA output, build output, logs and local runtime artifacts are not source files and must not be committed. Keep `artifacts/`, `dist/`, `.gjc/` and `.npz` files local.
239
227
 
240
228
  Every runtime library except the two agent packages (`@earendil-works/pi-ai`, `@earendil-works/pi-agent-core`) intentionally lives in `devDependencies`, because the published npm package ships the prebuilt `dist/`, so `npx cozyclay` must not install the studio's dependency tree.
241
229
 
@@ -250,12 +238,6 @@ cclay telemetry off
250
238
 
251
239
  `COZYCLAY_TELEMETRY=0` or `DO_NOT_TRACK=1` disables collection for a launch; **Settings ▾ → Privacy** is the same switch inside the Studio. The event list, what each identifier is, how internal QA traffic is marked and the first-edit definition are in [`docs/privacy.md`](docs/privacy.md).
252
240
 
253
- ## Hosted demo
254
-
255
- Installing a GPU motion backend is the hard part, so `cozyclay.org` also runs a queued demo: a visitor writes one prompt, gets a ticket link, and a GPU box owned by the maintainer generates the motion and uploads it — the result opens in the studio itself. The composer pages, the Cloudflare Worker queue API and the GPU-box poller all live in this repository; the queue policy and runbooks are in [`workers/api/README.md`](workers/api/README.md).
256
-
257
241
  ## License & credits
258
242
 
259
243
  GNU Affero General Public License v3.0 or later — see [`LICENSE`](LICENSE) and the transition details in [`LICENSING.md`](LICENSING.md). Modified network services must offer their users the corresponding source. Third-party projects retain their own licenses and copyright; see [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md).
260
-
261
- The hosted demo worker may use an externally installed [NVIDIA ARDY](https://github.com/nv-tlabs/ardy) runtime. ARDY is a separate third-party project owned and maintained by NVIDIA; it is not included in this repository, and CozyClay is not affiliated with or endorsed by NVIDIA. The local Studio uses Kimodo instead.
@@ -10,6 +10,7 @@ import { createAgentTools, SYSTEM_PROMPT, pickWorkspace } from "./agent-tools.mj
10
10
  import { createVideoAdapters, validateFalVideoRequest } from "./video-adapters.mjs";
11
11
  import { createSessionStore, transcriptFromHistory } from "./session-store.mjs";
12
12
  import { createAgentRunner } from "./agent-runner.mjs";
13
+ import { createProviderRoutes } from "./provider-routes.mjs";
13
14
  import { STUDIO_SYSTEM_PROMPT_STORYBOARD } from "./studio-prompt.mjs";
14
15
 
15
16
  // Values the codex backend accepts for reasoning.effort (its own 400 lists them).
@@ -85,9 +86,9 @@ const agentFailureCode = (error, signal, tool = false) => advisory(() => {
85
86
  * One user message is one editor transaction: every Studio turn whose editor
86
87
  * reports a previsMode opens agent.turn.begin and closes agent.turn.finish, so
87
88
  * all of its mutations land as ONE history entry and one receipt frame. A
88
- * context without a previsMode comes from an editor that predates turn
89
- * transactions and runs unwrapped. rollback() cancels the open transaction
90
- * (restoring the pre-turn document) and reopens it for the rest of the turn.
89
+ * context without a previsMode runs unwrapped (an editor that predates turn
90
+ * transactions). mark() records where a script starts; rollback() undoes only
91
+ * the edits after it and keeps the turn open, so earlier edits still land.
91
92
  */
92
93
  const agentTurn = ({ hub, context, turnId, admission }) => {
93
94
  const wrapped = Boolean(context.scene?.previsMode);
@@ -102,12 +103,11 @@ const agentTurn = ({ hub, context, turnId, admission }) => {
102
103
  };
103
104
  const open = async () => { await control("agent.turn.begin"); admission.turnId = turnId; };
104
105
  return {
106
+ mark: async () => (admission.turnId === turnId ? control("agent.turn.mark") : null),
105
107
  async rollback() {
106
108
  if (admission.turnId !== turnId) return null;
107
- delete admission.turnId;
108
- const receipt = await control("agent.turn.cancel");
109
+ const receipt = await control("agent.turn.rollback");
109
110
  await admission.refresh();
110
- await open();
111
111
  return receipt;
112
112
  },
113
113
  run: start => async function* (input) {
@@ -119,7 +119,7 @@ const agentTurn = ({ hub, context, turnId, admission }) => {
119
119
  if (frame.type === "done") terminal = frame; else yield frame;
120
120
  }
121
121
  } finally {
122
- // A rollback whose reopen failed has already closed the transaction.
122
+ // Finish only the transaction this run opened (a rollback keeps it open).
123
123
  if (admission.turnId === turnId) {
124
124
  delete admission.turnId;
125
125
  const receipt = await control("agent.turn.finish");
@@ -381,9 +381,10 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
381
381
  try { if (await auth.getAccessToken()) return true; } catch { /* an unavailable ChatGPT token is not a credential */ }
382
382
  const registry = await ensureWorkflowModels();
383
383
  if (typeof registry?.getAuth !== "function") return false;
384
- const { PROVIDERS } = await import("./providers.mjs");
385
- for (const provider of PROVIDERS) {
386
- try { if (await registry.getAuth(provider.id)) return true; } catch { /* one broken provider does not break the gate */ }
384
+ // the curated providers and any other one a pi login stored a credential for (#pi-login)
385
+ const [{ PROVIDERS }, { readCredentials }] = await Promise.all([import("./providers.mjs"), import("./provider-keys.mjs")]);
386
+ for (const id of new Set([...PROVIDERS.map((provider) => provider.id), ...Object.keys(readCredentials())])) {
387
+ try { if (await registry.getAuth(id)) return true; } catch { /* one broken provider does not break the gate */ }
387
388
  }
388
389
  return false;
389
390
  };
@@ -395,9 +396,9 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
395
396
  const studioOwnerTokens = new Map();
396
397
  const parseCookies = req => Object.fromEntries(String(req.headers.cookie || "").split(";").map(part => part.trim().split("=")).filter(([key, value]) => key && value).map(([key, value]) => [key, decodeURIComponent(value)]));
397
398
  const pruneStudioSessions = () => {
398
- const now = clock();
399
- for (const [id, session] of studioSessions) if (!session.activeJobId && now - session.updatedAt > 600_000) { studioSessions.delete(id); studioOwnerTokens.delete(id); for (const turn of session.turns.keys()) studioEvents.delete(turn); }
400
- const retired = [...studioSessions.entries()].filter(([, session]) => !session.activeJobId).sort((a, b) => a[1].updatedAt - b[1].updatedAt);
399
+ const now = clock(), idle = session => !(session.activeTurnId && session.controller && !session.controller.signal.aborted); // a running turn keeps its session
400
+ for (const [id, session] of studioSessions) if (idle(session) && now - session.updatedAt > 600_000) { studioSessions.delete(id); studioOwnerTokens.delete(id); for (const turn of session.turns.keys()) studioEvents.delete(turn); }
401
+ const retired = [...studioSessions.entries()].filter(([, session]) => idle(session)).sort((a, b) => a[1].updatedAt - b[1].updatedAt);
401
402
  while (studioSessions.size > 256 && retired.length) { const [id, session] = retired.shift(); studioSessions.delete(id); studioOwnerTokens.delete(id); for (const turn of session.turns.keys()) studioEvents.delete(turn); }
402
403
  };
403
404
  const studioOwner = (req, sessionId, create = false) => {
@@ -425,13 +426,16 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
425
426
  record.next = value.eventSeq;
426
427
  const previous = record.events.at(-1);
427
428
  if (value.type === "job.progress" && previous?.type === "job.progress" && previous.jobId === value.jobId) record.events[record.events.length - 1] = value;
429
+ // The panel's live frames (turn-frames.mjs, a script's nested calls) keep one replay slot per run of them.
430
+ else if (value.type === "thinking.delta" && previous?.type === "thinking.delta") record.events[record.events.length - 1] = { ...value, text: previous.text + value.text };
431
+ else if (value.type === "tool.progress" && previous?.type === "tool.progress" && previous.callId === value.callId) record.events[record.events.length - 1] = value;
428
432
  else record.events.push(value);
429
433
  if (record.events.length > 256) record.events.splice(0, record.events.length - 256);
430
434
  if (["done", "error", "receipt"].includes(value.type)) record.terminal = true;
431
435
  studioEvents.set(turnId, record); for (const listener of [...record.listeners]) listener(value);
432
436
  };
433
- const unsubscribe = auth.onAuthChange?.(({ kind }) => {
434
- if (kind !== "signed_out" && kind !== "replaced") return;
437
+ // A sign-out, another account, or a provider key or server address that changed: the registry and every runner on it go.
438
+ const resetRegistry = () => {
435
439
  for (const session of sessions.values()) session.controller?.abort();
436
440
  for (const session of studioSessions.values()) session.controller?.abort();
437
441
  for (const runner of [...workflowRunners.values(), ...studioRunners.values()]) void runner.close?.();
@@ -439,7 +443,9 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
439
443
  workflowModels = models;
440
444
  workflowModelsPromise = models ? Promise.resolve(models) : null;
441
445
  sessions.clear(); studioSessions.clear(); studioEvents.clear(); studioOwnerTokens.clear();
442
- });
446
+ };
447
+ const unsubscribe = auth.onAuthChange?.(({ kind }) => { if (kind === "signed_out" || kind === "replaced") resetRegistry(); });
448
+ const providerRoutes = createProviderRoutes({ auth, getModels: ensureWorkflowModels, onChange: resetRegistry, json, readBody });
443
449
 
444
450
  const studioIdentity = host => Object.fromEntries(["workspaceId", "documentEpoch", "sceneId", "sceneEpoch"].map(key => [key, host[key]]));
445
451
  const writeStudioStream = (res, record, after = 0) => {
@@ -473,27 +479,12 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
473
479
  if (path === "/agent/stop") {
474
480
  const session = studioSessions.get(value.sessionId);
475
481
  if (!session || session.owner !== parseCookies(req).studio_owner || !session.turns.has(value.turnId)) throw new StudioProtocolError("AUTH_REQUIRED", "Studio stop is not owned by this session.");
476
- const jobId = value.jobId ?? session.activeJobId;
477
- // Explicit ids are checked against every motion job this session admitted.
478
- // Retired ids remain in the set so a repeat Stop is idempotent, while an id
479
- // admitted by another session is stale even when this session is idle.
480
- if (value.jobId && !session.motionJobIds.has(value.jobId)) throw new StudioProtocolError("STALE_TARGET", "Stop does not own that motion job.");
481
- // An owned retired id is still stale while another motion job is active:
482
- // this Stop cannot acknowledge the current turn's different job.
483
- if (value.jobId && session.activeJobId && value.jobId !== session.activeJobId) throw new StudioProtocolError("STALE_TARGET", "Stop does not target the active motion job.");
484
- // Only this turn's held editor job can settle the tool quietly. A retired
485
- // job from an earlier turn must never silence an unrelated turn's Stop.
486
- const acknowledged = Boolean(session.activeJobId) && session.activeJobTurnId === value.turnId;
487
- // The editor's outcome, not a sidecar abort, establishes application.
488
- let outcome = null;
489
- if (jobId) outcome = await session.controlJob('job.cancel', jobId);
490
- if (jobId && session.activeJobId === jobId) { session.activeJobId = null; session.activeJobTurnId = null; }
491
- // Let the acknowledged job settle its held tool with that outcome before
492
- // aborting the model lane. Aborting the shared signal would interrupt the
493
- // tool first and lose the editor's proof. Without a held job, abort both.
494
- if (!acknowledged) session.controller?.abort();
495
- await session.modelSession?.abort?.("studio stop", acknowledged ? { quiet: true } : undefined);
496
- json(res, 200, { ok: true, status: jobId ? "stopped" : "detached", ...(outcome ? { outcome: { status: outcome.status ?? null, code: outcome.code ?? null, mutated: outcome.mutated ?? null } } : {}) }); return true;
482
+ // Studio turns hold no editor jobs since 3.0 (motion.compile answers
483
+ // synchronously), so a Stop names no job: it aborts the turn and the model lane.
484
+ if (value.jobId) throw new StudioProtocolError("STALE_TARGET", "Stop does not own that motion job.");
485
+ session.controller?.abort();
486
+ await session.modelSession?.abort?.("studio stop");
487
+ json(res, 200, { ok: true, status: "detached" }); return true;
497
488
  }
498
489
  if (!studioRuntime && !hub && hubDeps.hubPortBusy) throw new StudioProtocolError("CAPABILITY_MISSING", `The Studio live hub could not start: port ${hubDeps.hubPortBusy} is in use by another CozyClay or its dev server. Free that port and relaunch.`);
499
490
  if (!studioRuntime && (!hub?.command || !hub?.workspaceId)) throw new StudioProtocolError("CAPABILITY_MISSING", "Studio execution is not installed.");
@@ -516,7 +507,7 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
516
507
  studioOwner(req, value.sessionId, true);
517
508
  let persisted = null;
518
509
  try { persisted = sessionStore.read(value.sessionId); } catch { /* an unreadable history starts the session fresh */ }
519
- session = { owner: studioOwnerTokens.get(value.sessionId), history: persisted?.history ?? [], persistedItems: persisted?.history?.length ?? 0, meta: persisted?.meta ?? null, turns: new Map(), controller: null, activeJobId: null, activeJobTurnId: null, motionJobIds: new Set(Array.isArray(persisted?.meta?.motionJobIds) ? persisted.meta.motionJobIds.filter(id => typeof id === "string") : []), host: null, updatedAt: clock() };
510
+ session = { owner: studioOwnerTokens.get(value.sessionId), history: persisted?.history ?? [], persistedItems: persisted?.history?.length ?? 0, meta: persisted?.meta ?? null, turns: new Map(), controller: null, host: null, updatedAt: clock() };
520
511
  studioSessions.set(value.sessionId, session);
521
512
  }
522
513
  session.updatedAt = clock();
@@ -530,7 +521,7 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
530
521
  let replySegment = "", lastReply = "";
531
522
  const send = event => {
532
523
  if (event.type === "text.delta") { replySegment += event.text ?? ""; lastReply = replySegment; }
533
- else if (!["execution_telemetry", "execution_tool_started"].includes(event.type)) {
524
+ else if (!["execution_telemetry", "execution_tool_started", "thinking.delta", "tool.compose", "tool.progress"].includes(event.type)) {
534
525
  if (event.type === "tool.start") replySegment = "";
535
526
  turnFrames.push(event);
536
527
  }
@@ -562,49 +553,11 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
562
553
  return refreshed;
563
554
  },
564
555
  };
565
- // One motion generation per user message, whichever path starts it:
566
- // generate_motion below and a run_action job action share this gate.
567
- const generation = { used: false, failures: 0 };
568
- // The editor's command index (authoritative context) declares each action's
569
- // generation and hub timeout; no sidecar list of actions exists.
570
- const controlJob = async (action, jobId) => {
571
- await admission.refresh();
572
- return hub.command('run_action', { name: 'run_action', args: { action, args: { jobId, ...(action === 'job.await' ? { timeoutMs: 300_000 } : {}) } },
573
- commandId: randomUUID(), host: admission.host, expectedRevision: admission.revision }, value.context.host.workspaceHandle, { timeoutMs: 300_000 });
574
- };
575
- session.controlJob = controlJob;
576
- const onJob = async started => {
577
- const jobId = started.jobId;
578
- session.motionJobIds.add(jobId);
579
- persistenceMeta.motionJobIds = [...session.motionJobIds];
580
- session.activeJobId = jobId; session.activeJobTurnId = value.turnId;
581
- const cancel = () => { void controlJob('job.cancel', jobId).catch(error => send({ type: 'error', code: error.code ?? 'CANCEL_FAILED', message: error.message })); };
582
- controller.signal.addEventListener('abort', cancel, { once: true });
583
- if (controller.signal.aborted) cancel();
584
- send({ type: 'job.state', jobId, commandId: started.commandId, state: 'generating', phase: 'generating' });
585
- try {
586
- const outcome = await controlJob('job.await', jobId);
587
- send({ type: 'job.state', jobId, commandId: outcome.commandId, state: outcome.ok ? 'installed' : outcome.code === 'CANCELLED' ? 'cancelled' : 'failed', phase: 'complete' });
588
- return outcome;
589
- } finally {
590
- controller.signal.removeEventListener('abort', cancel);
591
- if (session.activeJobId === jobId) { session.activeJobId = null; session.activeJobTurnId = null; }
592
- }
593
- };
594
- const tools = createStudioTools({ liveHub: hub, workspaceHandle: value.context.host.workspaceHandle, previsMode: value.context.scene?.previsMode, session: { signal: controller.signal, admission, generation, onJob, actionIndex: current?.actionIndex ?? [], baseline: value.context }, resolveImage: async (id, correlation) => hub.command("resolve_studio_image", { imageId: id, ...correlation }, value.context.host.workspaceHandle) });
595
- const motion = async args => {
596
- // Private artifact IDs belonged to the retired sidecar runtime. Reuse
597
- // retained editor takes through motion.loadVersion, not another installer.
598
- if (args.source.kind !== 'generate') throw new StudioProtocolError('CAPABILITY_MISSING', 'Private motion artifacts are no longer retained. Use motion.loadVersion for an editor take.');
599
- await tools.internal.invoke('inspect_studio', { scope: 'motion', ids: [args.characterId] });
600
- const receipt = await tools.internal.invoke('generate_motion', args);
601
- if (receipt.ok && receipt.status === 'completed') send({ type: 'receipt', receipt });
602
- return receipt;
603
- };
556
+ const tools = createStudioTools({ liveHub: hub, workspaceHandle: value.context.host.workspaceHandle, previsMode: value.context.scene?.previsMode, session: { signal: controller.signal, admission, actionIndex: current?.actionIndex ?? [], baseline: value.context }, resolveImage: async (id, correlation) => hub.command("resolve_studio_image", { imageId: id, ...correlation }, value.context.host.workspaceHandle) });
604
557
  const modelTools = tools.map(tool => ({
605
558
  ...tool,
606
559
  handler: async args => {
607
- const result = await (tool.name === "generate_motion" ? motion(args) : tool.handler(args));
560
+ const result = await tool.handler(args);
608
561
  if (!result || !Array.isArray(result.visualRefs) || !result.visualRefs.length) return result;
609
562
  const ref = result.visualRefs.find(item => item?.imageId || item?.id);
610
563
  if (!ref) return result;
@@ -616,9 +569,11 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
616
569
  const script = createScriptTool({ tools: modelTools, signal: controller.signal, emit: send });
617
570
  const scriptTool = {
618
571
  ...script,
619
- // A script that fails after it edited the scene rolls the turn back, so
620
- // the model retries from the pre-turn document, never from half a script.
572
+ // A script that fails after it edited the scene rolls back its own edits, so
573
+ // the model retries from the document the script started from, never from
574
+ // half a script; what the turn did before the script stays.
621
575
  handler: async (args, options) => {
576
+ await turnTransaction.mark();
622
577
  const result = await script.handler(args, options);
623
578
  if (result.ok || !result.calls.some(call => call.ok && Number.isSafeInteger(call.revision?.after) && call.revision.after !== call.revision.before)) return result;
624
579
  const rollback = await turnTransaction.rollback();
@@ -675,12 +630,12 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
675
630
  else telemetry.finished(turnOutcome, turnFailureCode || "tool_failed");
676
631
  // Marked before the done frame leaves, so a client that asks for the
677
632
  // review the moment its stream ends finds it pending, not absent.
678
- // The author's own switch (value.supervisor === false) skips this turn's review.
679
- if (supervisor && process.env.COZYCLAY_SUPERVISOR !== "off" && value.supervisor !== false && !controller.signal.aborted && shouldSupervise(turnFrames)) record.supervisor = "pending";
633
+ // Off by default: only a turn sent with value.supervisor === true is reviewed.
634
+ if (supervisor && process.env.COZYCLAY_SUPERVISOR !== "off" && value.supervisor === true && !controller.signal.aborted && shouldSupervise(turnFrames)) record.supervisor = "pending";
680
635
  }
681
636
  send(frame);
682
637
  };
683
- const persistenceMeta = { sceneName: value.context?.scene?.name ?? value.context?.sceneName ?? null, firstText: value.text, motionJobIds: [...session.motionJobIds] };
638
+ const persistenceMeta = { sceneName: value.context?.scene?.name ?? value.context?.sceneName ?? null, firstText: value.text };
684
639
  const modelKey = value.model || (fauxProvider ? `${fauxProvider.provider?.id || fauxProvider.provider || "faux"}/scripted` : "gpt-6-astra");
685
640
  let runner = studioRunners.get(value.sessionId);
686
641
  if (!runner) {
@@ -789,35 +744,7 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
789
744
  await session.modelSession.steer(value.text, images);
790
745
  json(res, 200, { ok: true, queued: true }); return true;
791
746
  }
792
- if (path === "/agent/providers" && req.method === "GET") {
793
- const { providerStatus } = await import("./providers.mjs");
794
- try { json(res, 200, { providers: providerStatus({ auth }) }); }
795
- catch { json(res, 500, { error: "provider credentials unavailable" }); }
796
- return true;
797
- }
798
- if (/^\/agent\/providers\/[^/]+$/.test(path) && ["PUT", "DELETE"].includes(req.method)) {
799
- const { PROVIDERS } = await import("./providers.mjs");
800
- const keys = await import("./provider-keys.mjs");
801
- const { createCredentialStore } = await import("./credential-store.mjs");
802
- const id = path.slice("/agent/providers/".length);
803
- if (!PROVIDERS.some((provider) => provider.id === id)) { json(res, 404, { error: "provider not found" }); return true; }
804
- const credentials = createCredentialStore({ auth, keys });
805
- let key;
806
- if (req.method === "PUT") {
807
- if (id === "openai-codex") { json(res, 400, { error: "Use ChatGPT sign-in for OpenAI Codex." }); return true; }
808
- try {
809
- const value = await readBody(req);
810
- if (typeof value?.key !== "string" || !value.key.trim()) throw new Error("Invalid key.");
811
- key = value.key.trim();
812
- } catch { json(res, 400, { error: "a non-empty API key is required" }); return true; }
813
- }
814
- try {
815
- if (req.method === "PUT") await credentials.modify(id, async () => ({ type: "api_key", key }));
816
- else await credentials.delete(id);
817
- json(res, 200, { ok: true });
818
- } catch { json(res, 500, { error: "provider credentials could not be saved" }); }
819
- return true;
820
- }
747
+ if (await providerRoutes(req, res, path)) return true;
821
748
  if (path === "/agent/sessions" && req.method === "GET") {
822
749
  const surface = new URL(req.url, "http://127.0.0.1").searchParams.get("surface") || undefined;
823
750
  try { json(res, 200, { sessions: sessionStore.list({ surface }) }); }
@@ -1,4 +1,5 @@
1
1
  import { summariseCanvasResult } from "./agent-tools.mjs";
2
+ import { createReasoningFrames } from "./turn-frames.mjs";
2
3
  import { sanitizeUpstreamDetail } from "./agent-routes.mjs";
3
4
 
4
5
  const DEFAULT_MODEL = "gpt-6-astra";
@@ -363,7 +364,7 @@ export function createAgentRunner({ models: suppliedModels, sessionStore, tools
363
364
 
364
365
  const ensureHarness = async (input) => {
365
366
  const { pi, models: registry } = await ensureModels();
366
- const activePrompt = input.systemPrompt || (input.surface === "studio" ? (await import("./studio-prompt.mjs")).STUDIO_SYSTEM_PROMPT : systemPrompt);
367
+ const activePrompt = input.systemPrompt || (input.surface === "studio" ? await (await import("./studio-motion-reference.mjs")).studioSystemPrompt() : systemPrompt);
367
368
  const { resolveModel, resolveEffort } = await import("./providers.mjs");
368
369
  const requested = input.model || DEFAULT_MODEL;
369
370
  const slash = requested.indexOf("/");
@@ -449,27 +450,25 @@ export function createAgentRunner({ models: suppliedModels, sessionStore, tools
449
450
  queue.push(frame);
450
451
  };
451
452
  const subscribe = (type, listener) => eventUnsubscribers.push(state.harness.events.on(type, listener));
453
+ // The reasoning summary and the tool call being written, for the panel (turn-frames.mjs).
454
+ const reasoning = createReasoningFrames(pushFrame, clock);
452
455
  subscribe("message_update", (event) => {
456
+ reasoning.onMessageEvent(event.event);
453
457
  if (event.event?.type === "text_delta") pushFrame({ type: "text.delta", text: event.event.delta });
454
458
  if (event.event?.type === "error") pushFrame(errorFrame(event.event.error, event.event.reason === "aborted"));
455
459
  });
456
- subscribe("tool_start", (event) => {
460
+ const startTool = (event) => {
457
461
  if (toolStarted.has(event.toolCallId)) return;
458
462
  toolStarted.add(event.toolCallId);
459
463
  toolStartedAt.set(event.toolCallId, clock());
460
464
  const activeTools = state.lastInput?.tools || tools;
461
465
  const tool = (Array.isArray(activeTools) ? activeTools : []).find((candidate) => candidate.name === event.toolName);
462
466
  pushFrame({ type: "tool.start", callId: event.toolCallId, name: event.toolName, label: tool?.label || event.toolName.replaceAll("_", " "), args: summariseCanvasResult(event.args) });
463
- });
467
+ };
468
+ subscribe("tool_start", startTool);
464
469
  subscribe("tool_end", (event) => {
465
470
  if (toolCompleted.has(event.toolCallId)) return;
466
- if (!toolStarted.has(event.toolCallId)) {
467
- toolStarted.add(event.toolCallId);
468
- toolStartedAt.set(event.toolCallId, clock());
469
- const activeTools = state.lastInput?.tools || tools;
470
- const tool = (Array.isArray(activeTools) ? activeTools : []).find((candidate) => candidate.name === event.toolName);
471
- pushFrame({ type: "tool.start", callId: event.toolCallId, name: event.toolName, label: tool?.label || event.toolName.replaceAll("_", " "), args: summariseCanvasResult(event.args) });
472
- }
471
+ startTool(event);
473
472
  toolCompleted.add(event.toolCallId);
474
473
  const started = toolStartedAt.get(event.toolCallId) ?? clock();
475
474
  const elapsedMs = Math.round(clock() - started);