cozyclay 2.1.1 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (585) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +43 -116
  4. package/bin/agent/agent-runner.mjs +14 -15
  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/session-store.mjs +2 -2
  14. package/bin/agent/studio-context-budget.mjs +93 -0
  15. package/bin/agent/studio-motion-reference.mjs +23 -0
  16. package/bin/agent/studio-prompt.mjs +1 -1
  17. package/bin/agent/studio-rebase-text.mjs +32 -0
  18. package/bin/agent/studio-rebase.mjs +26 -39
  19. package/bin/agent/studio-tools.mjs +9 -28
  20. package/bin/agent/tool-arguments.mjs +49 -0
  21. package/bin/agent/turn-frames.mjs +66 -0
  22. package/bin/codex-auth.mjs +9 -2
  23. package/bin/cozyclay.mjs +10 -116
  24. package/bin/live/cli.mjs +3 -3
  25. package/bin/mcp-runtime.mjs +2 -12
  26. package/bin/package-signature.mjs +0 -4
  27. package/dist/app/index.html +7 -6
  28. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  29. package/dist/assets/analytics-BWCqdgWA.js +1 -0
  30. package/dist/assets/app-DfTCxaKk.js +5087 -0
  31. package/dist/assets/app-f-fkV5uP.css +1 -0
  32. package/dist/assets/compile-worker-BxKXh07s.js +5 -0
  33. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  34. package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-DNO8kv_N.js} +1 -1
  35. package/dist/assets/{landing-ChUvB_zL.js → landing-yfZaJkWc.js} +1 -1
  36. package/dist/assets/scene-asset-sync-U3kS2rvK.js +20 -0
  37. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  38. package/dist/assets/workflow-D78eiKTa.js +179 -0
  39. package/dist/cozyclay-package.json +1 -1
  40. package/dist/index.html +3 -3
  41. package/dist/sw.js +3 -6
  42. package/dist/workflow/index.html +5 -4
  43. package/mcp/LIVE-PROTOCOL.md +7 -47
  44. package/mcp/README.md +16 -26
  45. package/mcp/live-hub.mjs +6 -10
  46. package/mcp/package.json +1 -3
  47. package/mcp/tool-handlers.mjs +4 -168
  48. package/package.json +3 -22
  49. package/src/App.jsx +151 -2625
  50. package/src/agent-progress.js +80 -0
  51. package/src/agent-turn-mark.js +30 -0
  52. package/src/analytics.js +1 -19
  53. package/src/app-context.js +12 -2
  54. package/src/app-stage.jsx +2 -34
  55. package/src/asset-pane.jsx +6 -5
  56. package/src/camera-block.js +21 -0
  57. package/src/camera-direction.js +120 -0
  58. package/src/character-kind.js +3 -9
  59. package/src/command-bus.js +10 -8
  60. package/src/commands/cast.js +5 -34
  61. package/src/commands/elements/character.js +1 -1
  62. package/src/commands/elements/motion.js +1 -6
  63. package/src/commands/elements/object.js +6 -2
  64. package/src/commands/elements/shot.js +4 -1
  65. package/src/commands/elements.js +38 -14
  66. package/src/commands/index.js +3 -1
  67. package/src/commands/kits.js +37 -0
  68. package/src/commands/motion-timing.js +57 -0
  69. package/src/commands/motion.js +9 -152
  70. package/src/commands/motion3-compile.js +65 -0
  71. package/src/commands/motion3.js +214 -0
  72. package/src/commands/objects.js +6 -4
  73. package/src/commands/shot.js +55 -13
  74. package/src/commands/stage.js +14 -1
  75. package/src/commands/view.js +4 -1
  76. package/src/document-mirrors.js +42 -0
  77. package/src/document-store.js +4 -0
  78. package/src/domains/cast.js +19 -194
  79. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  80. package/src/domains/motion/take-source.js +53 -0
  81. package/src/domains/motion.js +60 -1838
  82. package/src/domains/scenes.js +12 -55
  83. package/src/domains/shots.js +2 -2
  84. package/src/domains/stage.js +4 -2
  85. package/src/draft-ghost.js +47 -0
  86. package/src/draft-ghost.jsx +129 -0
  87. package/src/dualview.jsx +4 -3
  88. package/src/effects/EffectsLayer.jsx +112 -0
  89. package/src/effects/effect-meshes.js +232 -0
  90. package/src/effects/effect-record.js +103 -0
  91. package/src/effects/emitter.js +59 -0
  92. package/src/effects/simulate.js +261 -0
  93. package/src/effects/state.js +59 -0
  94. package/src/execution-telemetry.js +2 -3
  95. package/src/first-success-guide.jsx +5 -5
  96. package/src/ghost-set.js +257 -0
  97. package/src/held-object-edit.jsx +113 -0
  98. package/src/held-object-pose.js +175 -0
  99. package/src/hierarchy-model.js +1 -1
  100. package/src/hierarchy-panel.jsx +1 -1
  101. package/src/imported-mesh.jsx +103 -0
  102. package/src/kits/held.js +84 -0
  103. package/src/kits/index.js +70 -0
  104. package/src/kits/smoking.js +57 -0
  105. package/src/light-blockers.js +93 -0
  106. package/src/motion/beat-board-model.js +84 -0
  107. package/src/motion/beat-board.css +124 -0
  108. package/src/motion/beat-board.jsx +60 -0
  109. package/src/motion/center-of-mass.js +66 -0
  110. package/src/motion/fix-collisions.js +850 -0
  111. package/src/motion/physics-review.js +118 -0
  112. package/src/{ardy → motion}/physics-support.js +1 -76
  113. package/src/motion/rig-snapshot.js +22 -0
  114. package/src/motion/take-segments.jsx +140 -0
  115. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  116. package/src/{ardy → motion}/timeline.css +1 -2
  117. package/src/{ardy → motion}/timeline.jsx +42 -413
  118. package/src/motion/waypoints.js +76 -0
  119. package/src/motion3/beats.js +83 -0
  120. package/src/motion3/body.js +158 -0
  121. package/src/motion3/bones.js +47 -0
  122. package/src/motion3/carried.js +47 -0
  123. package/src/motion3/compile-job.js +79 -0
  124. package/src/motion3/compile-worker.js +10 -0
  125. package/src/motion3/compiler/anchors.js +113 -0
  126. package/src/motion3/compiler/arm-orient.js +128 -0
  127. package/src/motion3/compiler/channels.js +125 -0
  128. package/src/motion3/compiler/compile.js +205 -0
  129. package/src/motion3/compiler/gait-rhythm.js +97 -0
  130. package/src/motion3/compiler/grip.js +171 -0
  131. package/src/motion3/compiler/head-steady.js +48 -0
  132. package/src/motion3/compiler/hold.js +134 -0
  133. package/src/motion3/compiler/idle.js +113 -0
  134. package/src/motion3/compiler/limbs.js +197 -0
  135. package/src/motion3/compiler/math.js +72 -0
  136. package/src/motion3/compiler/pose.js +177 -0
  137. package/src/motion3/compiler/quat.js +49 -0
  138. package/src/motion3/compiler/skeleton.js +74 -0
  139. package/src/motion3/compiler/validate-common.js +32 -0
  140. package/src/motion3/compiler/validate-goals.js +118 -0
  141. package/src/motion3/compiler/validate-objects.js +144 -0
  142. package/src/motion3/compiler/validate-parts.js +118 -0
  143. package/src/motion3/compiler/validate.js +122 -0
  144. package/src/motion3/compiler/vocab.js +74 -0
  145. package/src/motion3/compiler/walk-pelvis.js +275 -0
  146. package/src/motion3/compiler/walk.js +207 -0
  147. package/src/motion3/compiler/wrist-clamp.js +32 -0
  148. package/src/motion3/delta.js +47 -0
  149. package/src/motion3/describe.js +177 -0
  150. package/src/motion3/docs.js +27 -0
  151. package/src/motion3/edit-carry.js +21 -0
  152. package/src/motion3/fix/ease.js +16 -0
  153. package/src/motion3/fix/ground.js +37 -0
  154. package/src/motion3/fix/index.js +52 -0
  155. package/src/motion3/fix/push.js +71 -0
  156. package/src/motion3/fix/reach.js +46 -0
  157. package/src/motion3/fix/retime.js +79 -0
  158. package/src/motion3/fix/self.js +211 -0
  159. package/src/motion3/fix/slide.js +70 -0
  160. package/src/motion3/fix/turn.js +51 -0
  161. package/src/motion3/follow.js +39 -0
  162. package/src/motion3/free-motion.js +204 -0
  163. package/src/motion3/grip-choice.js +130 -0
  164. package/src/motion3/hold-plan.js +157 -0
  165. package/src/motion3/json-patch.js +77 -0
  166. package/src/motion3/key-aim.js +105 -0
  167. package/src/motion3/key-edit.js +82 -0
  168. package/src/motion3/lenient.js +127 -0
  169. package/src/motion3/object-keys.js +236 -0
  170. package/src/motion3/object-pose.js +182 -0
  171. package/src/motion3/object-tracks.js +12 -0
  172. package/src/motion3/people.js +82 -0
  173. package/src/motion3/pipeline.js +102 -0
  174. package/src/motion3/playback.js +51 -0
  175. package/src/motion3/probes.js +91 -0
  176. package/src/motion3/qa/body-checks.js +34 -0
  177. package/src/motion3/qa/check-names.js +14 -0
  178. package/src/motion3/qa/checks.js +125 -0
  179. package/src/motion3/qa/contact-checks.js +51 -0
  180. package/src/motion3/qa/explain.js +80 -0
  181. package/src/motion3/qa/fmt.js +5 -0
  182. package/src/motion3/qa/foot-checks.js +72 -0
  183. package/src/motion3/qa/geometry.js +77 -0
  184. package/src/motion3/qa/goals.js +191 -0
  185. package/src/motion3/qa/ground.js +60 -0
  186. package/src/motion3/qa/hints-contact.js +195 -0
  187. package/src/motion3/qa/hints-goal.js +160 -0
  188. package/src/motion3/qa/hints-knee.js +24 -0
  189. package/src/motion3/qa/hints-motion.js +209 -0
  190. package/src/motion3/qa/hints-object.js +91 -0
  191. package/src/motion3/qa/index.js +44 -0
  192. package/src/motion3/qa/key-checks.js +82 -0
  193. package/src/motion3/qa/near.js +96 -0
  194. package/src/motion3/qa/object-checks.js +135 -0
  195. package/src/motion3/qa/patch.js +161 -0
  196. package/src/motion3/qa/people-checks.js +37 -0
  197. package/src/motion3/qa/severity.js +38 -0
  198. package/src/motion3/qa/spec-checks.js +65 -0
  199. package/src/motion3/qa/style.js +228 -0
  200. package/src/motion3/qa/wrist-checks.js +69 -0
  201. package/src/motion3/qa/wrist-obs.js +60 -0
  202. package/src/motion3/refs.js +129 -0
  203. package/src/motion3/repair-tune.js +121 -0
  204. package/src/motion3/repair.js +83 -0
  205. package/src/motion3/report.js +67 -0
  206. package/src/motion3/rigs/index.js +14 -0
  207. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  208. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  209. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  210. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  211. package/src/motion3/scene.js +60 -0
  212. package/src/motion3/schema/brief.js +89 -0
  213. package/src/motion3/schema/fields.js +44 -0
  214. package/src/motion3/schema/spec-examples.js +46 -0
  215. package/src/motion3/schema/topics.js +113 -0
  216. package/src/motion3/schema/types.js +149 -0
  217. package/src/motion3/search.js +117 -0
  218. package/src/motion3/second-hand.js +112 -0
  219. package/src/motion3/settle.js +136 -0
  220. package/src/motion3/skills/basic.js +83 -0
  221. package/src/motion3/skills/book.js +76 -0
  222. package/src/motion3/skills/car.js +145 -0
  223. package/src/motion3/skills/flip.js +200 -0
  224. package/src/motion3/skills/frames-auto.js +29 -0
  225. package/src/motion3/skills/impact.js +109 -0
  226. package/src/motion3/skills/index.js +85 -0
  227. package/src/motion3/skills/info.js +80 -0
  228. package/src/motion3/skills/smoke-kit.js +109 -0
  229. package/src/motion3/skills/smoke.js +160 -0
  230. package/src/motion3/spec-store.js +59 -0
  231. package/src/motion3/take-edit.js +155 -0
  232. package/src/motion3/take.js +63 -0
  233. package/src/motion3/timeline.js +127 -0
  234. package/src/motion3/views.js +121 -0
  235. package/src/motion3/walk-plan.js +119 -0
  236. package/src/object-catalog.jsx +7 -1
  237. package/src/object-colors.js +98 -0
  238. package/src/object-gizmo-parts.js +115 -0
  239. package/src/object-gizmo.jsx +37 -122
  240. package/src/object-glow.js +139 -0
  241. package/src/object-hidden.js +33 -0
  242. package/src/object-look.js +196 -0
  243. package/src/object-size.js +10 -0
  244. package/src/object-travel.js +71 -6
  245. package/src/panels/CameraPanel.jsx +7 -7
  246. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  247. package/src/panels/LightPanel.jsx +25 -4
  248. package/src/panels/ObjectLookPanel.jsx +88 -0
  249. package/src/panels/PosePanel.jsx +2 -17
  250. package/src/panels/RigControlPanel.jsx +5 -53
  251. package/src/panels/SubjectBox.jsx +4 -1
  252. package/src/panels/details.css +14 -0
  253. package/src/posestudio.jsx +4 -27
  254. package/src/project-browser.jsx +3 -4
  255. package/src/project-resources.js +18 -31
  256. package/src/project.js +13 -104
  257. package/src/props.jsx +25 -280
  258. package/src/qa-label.js +25 -0
  259. package/src/rail-dot-drag.js +23 -0
  260. package/src/render-passes.js +10 -0
  261. package/src/resource-status.jsx +1 -0
  262. package/src/result-modal.jsx +4 -19
  263. package/src/room.jsx +20 -12
  264. package/src/scene-assets.js +8 -6
  265. package/src/scene-light-place.js +72 -0
  266. package/src/scene-lights.jsx +148 -0
  267. package/src/scene-objects.js +31 -139
  268. package/src/scenes.js +15 -105
  269. package/src/semantic-edit.js +2 -5
  270. package/src/set-pieces.jsx +183 -0
  271. package/src/settings-menu.jsx +3 -11
  272. package/src/shell/BottomDock.jsx +9 -51
  273. package/src/shell/DetailsSlot.jsx +16 -200
  274. package/src/shell/GhostsMenuGroup.jsx +74 -0
  275. package/src/shell/LibrarySlot.jsx +1 -3
  276. package/src/shell/MenuBar.jsx +3 -3
  277. package/src/shell/PreferencesDialog.jsx +15 -264
  278. package/src/shell/PreferencesSlot.jsx +2 -8
  279. package/src/shell/TopBar.jsx +5 -84
  280. package/src/shell/ViewportToolbar.jsx +10 -70
  281. package/src/shell/agent-glass.css +19 -6
  282. package/src/shell/details-selection.js +42 -0
  283. package/src/shell/ghosts-menu.css +22 -0
  284. package/src/shell/glass-regions.css +29 -3
  285. package/src/shell/preferences.css +6 -1
  286. package/src/shot-authoring.js +4 -7
  287. package/src/soft-shadows.js +37 -0
  288. package/src/stage-attach-frames.js +12 -12
  289. package/src/stage-canvas-rigs.jsx +3 -3
  290. package/src/stage-capture.jsx +42 -8
  291. package/src/stage-characters.jsx +22 -216
  292. package/src/stage-editor-gizmos.jsx +6 -6
  293. package/src/stage-environment.js +93 -0
  294. package/src/stage-hdr-builtins.js +107 -0
  295. package/src/stage-hdr-image.js +116 -0
  296. package/src/stage-hdr-library.js +70 -0
  297. package/src/stage-hdr-map.js +94 -0
  298. package/src/stage-hdr.js +130 -0
  299. package/src/stage-lighting.js +149 -0
  300. package/src/stage-occlusion.js +210 -0
  301. package/src/stage-sky.jsx +159 -0
  302. package/src/stage-startup-defaults.js +5 -9
  303. package/src/studio-actions.js +16 -14
  304. package/src/studio-agent-commands.js +19 -16
  305. package/src/studio-agent-motion.js +9 -9
  306. package/src/studio-agent-protocol.js +47 -77
  307. package/src/studio-app-binding.js +27 -47
  308. package/src/studio-asset-list.js +17 -0
  309. package/src/studio-contact-sheet.js +37 -5
  310. package/src/studio-elements.js +27 -7
  311. package/src/studio-entity-bounds.js +18 -0
  312. package/src/studio-error-codes.js +15 -0
  313. package/src/studio-export-framing.js +152 -0
  314. package/src/studio-lighting-check.js +103 -0
  315. package/src/studio-motion-visual.js +150 -0
  316. package/src/studio-schema-errors.js +80 -0
  317. package/src/studio-support-gap.js +74 -0
  318. package/src/styles.css +1 -19
  319. package/src/timeline-defaults.js +5 -0
  320. package/src/timeline-extent.js +8 -20
  321. package/src/tutorial-walk.js +17 -0
  322. package/src/ui-scrub.js +1 -1
  323. package/src/use-shot-camera-tracks.js +12 -0
  324. package/src/view-ghosts.js +97 -0
  325. package/src/view-ghosts.jsx +49 -0
  326. package/src/workflow/AgentPanel.jsx +47 -166
  327. package/src/workflow/AgentProgressCard.jsx +109 -0
  328. package/src/workflow/AgentReplyText.jsx +37 -0
  329. package/src/workflow/AgentTurnSteps.jsx +84 -0
  330. package/src/workflow/LoginPanel.jsx +140 -0
  331. package/src/workflow/WorkflowBuilder.jsx +7 -23
  332. package/src/workflow/agent-client.js +54 -51
  333. package/src/workflow/agent-panel.css +71 -1
  334. package/src/workflow/agent-progress-card.css +167 -0
  335. package/src/workflow/agent-progress-view.js +47 -0
  336. package/src/workflow/agent-turn-steps.css +113 -0
  337. package/src/workflow/agent-turn.js +191 -0
  338. package/src/workflow/login-client.js +34 -0
  339. package/src/workflow/motion-input.js +18 -31
  340. package/src/workflow/node-schema.js +1 -1
  341. package/src/workflow/reply-markdown.js +69 -0
  342. package/src/workflow/scene-asset-sync.js +8 -8
  343. package/tools/agent-session-stats.mjs +61 -0
  344. package/tools/dev-full.mjs +5 -56
  345. package/tools/eslint-baseline.json +12 -13
  346. package/tools/knip-baseline.json +0 -179
  347. package/tools/loc-baseline.json +31 -81
  348. package/tools/motion3/extract-body.mjs +103 -0
  349. package/tools/motion3/extract-rig.mjs +85 -0
  350. package/tools/process-supervisor.mjs +0 -88
  351. package/tools/run-tests.mjs +9 -125
  352. package/bin/agent/motion-runtime.mjs +0 -76
  353. package/dist/ardy/cskel27-rest.json +0 -557
  354. package/dist/assets/analytics-C9pMpW7s.js +0 -1
  355. package/dist/assets/app-B_2XB9rw.css +0 -1
  356. package/dist/assets/app-DAuvKPP8.js +0 -4920
  357. package/dist/assets/config-Bx7pYsD9.js +0 -1
  358. package/dist/assets/config-DeoFTYzb.css +0 -1
  359. package/dist/assets/demo-CvKuBgy5.js +0 -1
  360. package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
  361. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  362. package/dist/assets/ticket-BPk2Ni95.js +0 -1
  363. package/dist/assets/workflow-jJRBR94Z.js +0 -179
  364. package/dist/demo/walk-then-stop.npz +0 -0
  365. package/mcp/ardy-prompts.mjs +0 -218
  366. package/src/ardy/auto-fix-panel.css +0 -108
  367. package/src/ardy/auto-physics.js +0 -1194
  368. package/src/ardy/client.js +0 -153
  369. package/src/ardy/convert.js +0 -313
  370. package/src/ardy/cskel27-neutral.js +0 -39
  371. package/src/ardy/cskel27.js +0 -77
  372. package/src/ardy/export.js +0 -165
  373. package/src/ardy/fix-collisions.js +0 -2257
  374. package/src/ardy/foot-lock.js +0 -230
  375. package/src/ardy/motion-calibration.js +0 -118
  376. package/src/ardy/motion-edit.js +0 -213
  377. package/src/ardy/motion-url.js +0 -55
  378. package/src/ardy/mp4-duration.js +0 -102
  379. package/src/ardy/npz.js +0 -595
  380. package/src/ardy/physics-panel.css +0 -21
  381. package/src/ardy/physics-panel.jsx +0 -89
  382. package/src/ardy/physics-refine.js +0 -111
  383. package/src/ardy/physics-review.js +0 -567
  384. package/src/ardy/physics-temporal.js +0 -54
  385. package/src/ardy/platform-fit-panel.css +0 -64
  386. package/src/ardy/platform-fit-panel.jsx +0 -41
  387. package/src/ardy/platform-fit.js +0 -326
  388. package/src/ardy/playback.js +0 -711
  389. package/src/ardy/pose-pin.js +0 -97
  390. package/src/ardy/prompt-clips.js +0 -30
  391. package/src/ardy/retime.js +0 -211
  392. package/src/ardy/root-drop.js +0 -369
  393. package/src/ardy/timeline-resize.js +0 -13
  394. package/src/ardy/to-cskel27.js +0 -225
  395. package/src/ardy/trim.js +0 -33
  396. package/src/ardy/waypoints.js +0 -369
  397. package/src/ardy-bridge-contract.js +0 -281
  398. package/src/domains/motion/motion-video-capture.js +0 -664
  399. package/src/i2v-motion-client.js +0 -77
  400. package/src/i2v-motion-studio.jsx +0 -180
  401. package/src/line-edit.js +0 -1348
  402. package/src/motion/generation.js +0 -105
  403. package/src/motion-readiness-ui.jsx +0 -67
  404. package/src/motion-readiness.js +0 -40
  405. package/src/motion-resources.js +0 -177
  406. package/src/motion-store.js +0 -65
  407. package/src/motion-trail.js +0 -601
  408. package/src/multimodel-ingest.js +0 -394
  409. package/src/panels/PromptBlocksPanel.jsx +0 -444
  410. package/src/panels/VideoCapturePanel.jsx +0 -181
  411. package/src/pose-extract/browser.js +0 -42
  412. package/src/pose-extract/detector.js +0 -100
  413. package/src/pose-extract/fit.js +0 -227
  414. package/src/pose-extract/image-frame.js +0 -51
  415. package/src/pose-extract/index.js +0 -58
  416. package/src/pose-extract/keyframes.js +0 -74
  417. package/src/pose-extract/landmarks.js +0 -162
  418. package/src/pose-extract/mirror.js +0 -153
  419. package/src/pose-extract/take.js +0 -131
  420. package/src/pose-extract/video-frames.js +0 -91
  421. package/src/take-recipe.js +0 -165
  422. package/src/trail-key-conflicts.js +0 -39
  423. package/src/trail-pick.js +0 -59
  424. package/src/use-i2v-capture-modal.js +0 -15
  425. package/tools/ardy/BRIDGE.md +0 -37
  426. package/tools/ardy/GVHMR-NPZ.md +0 -68
  427. package/tools/ardy/README.md +0 -124
  428. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  429. package/tools/ardy/artifacts.mjs +0 -34
  430. package/tools/ardy/bridge.mjs +0 -1732
  431. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  432. package/tools/ardy/cclay_constrained_generate.py +0 -561
  433. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  434. package/tools/ardy/cclay_motion_edit.py +0 -510
  435. package/tools/ardy/cclay_sequence_generate.py +0 -705
  436. package/tools/ardy/cclay_worker.py +0 -304
  437. package/tools/ardy/dump-npz.py +0 -205
  438. package/tools/ardy/extract-rest.mjs +0 -299
  439. package/tools/ardy/extract.mjs +0 -507
  440. package/tools/ardy/footage.mjs +0 -467
  441. package/tools/ardy/gvhmr-floor.mjs +0 -102
  442. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  443. package/tools/ardy/gvhmr_fastpath.py +0 -168
  444. package/tools/ardy/gvhmr_trajectory.py +0 -240
  445. package/tools/ardy/merge-text-encoder.py +0 -160
  446. package/tools/ardy/mocap-metrics.mjs +0 -200
  447. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  448. package/tools/ardy/motion-stabilize.mjs +0 -348
  449. package/tools/ardy/npz.mjs +0 -414
  450. package/tools/ardy/pose-to-npz.mjs +0 -106
  451. package/tools/ardy/prompt-limits.mjs +0 -5
  452. package/tools/ardy/run-edit-on-box.sh +0 -73
  453. package/tools/ardy/run-local.mjs +0 -233
  454. package/tools/ardy/run-on-box.sh +0 -593
  455. package/tools/ardy/run-sequence-on-box.sh +0 -177
  456. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  457. package/tools/ardy/runners/index.mjs +0 -18
  458. package/tools/ardy/runners/local.mjs +0 -455
  459. package/tools/ardy/runners/proc.mjs +0 -125
  460. package/tools/ardy/runners/remote.mjs +0 -252
  461. package/tools/ardy/setup-local.mjs +0 -189
  462. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  463. package/tools/ardy/setup-text-encoder.py +0 -306
  464. package/tools/ardy/smpl-cskel27.mjs +0 -178
  465. package/tools/ardy/visual-qa.mjs +0 -196
  466. package/tools/ardy/vq-car.mjs +0 -52
  467. package/tools/bench/EXP3.md +0 -61
  468. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  469. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  470. package/tools/bench/cclay_bench_runner.py +0 -42
  471. package/tools/bench/cube-contact.mjs +0 -162
  472. package/tools/bench/exp3.mjs +0 -152
  473. package/tools/bench/extract-bench-lib.mjs +0 -216
  474. package/tools/bench/extract-bench.mjs +0 -302
  475. package/tools/bench/fal-generate.mjs +0 -88
  476. package/tools/bench/fit/README.md +0 -189
  477. package/tools/bench/fit/camera.mjs +0 -59
  478. package/tools/bench/fit/contact.mjs +0 -419
  479. package/tools/bench/fit/footlock.mjs +0 -234
  480. package/tools/bench/fit/motion.mjs +0 -85
  481. package/tools/bench/fit/pin.mjs +0 -27
  482. package/tools/bench/fit/remote.mjs +0 -105
  483. package/tools/bench/fit-bench.mjs +0 -126
  484. package/tools/bench/fit-sanity.mjs +0 -45
  485. package/tools/bench/metrics.mjs +0 -313
  486. package/tools/bench/obs/depth.mjs +0 -258
  487. package/tools/bench/obs/extrinsics.mjs +0 -175
  488. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  489. package/tools/bench/obs/ground.mjs +0 -294
  490. package/tools/bench/obs/heading.mjs +0 -151
  491. package/tools/bench/obs/ladder.mjs +0 -555
  492. package/tools/bench/obs/mannequin-betas.json +0 -124
  493. package/tools/bench/obs/remote.mjs +0 -83
  494. package/tools/bench/obs/rest_joints.py +0 -52
  495. package/tools/bench/obs/ybot-targets.mjs +0 -49
  496. package/tools/bench/obs-bench.mjs +0 -520
  497. package/tools/bench/score.mjs +0 -343
  498. package/tools/bench/summarize.mjs +0 -86
  499. package/tools/demo-worker/README.md +0 -137
  500. package/tools/demo-worker/api-client.mjs +0 -233
  501. package/tools/demo-worker/generate.mjs +0 -301
  502. package/tools/demo-worker/index.mjs +0 -466
  503. package/tools/demo-worker/sign-core.mjs +0 -112
  504. package/tools/demo-worker/sign.mjs +0 -161
  505. package/tools/gt-render/browser.mjs +0 -182
  506. package/tools/gt-render/camera-math.mjs +0 -276
  507. package/tools/gt-render/page.mjs +0 -267
  508. package/tools/gt-render/render.mjs +0 -514
  509. package/tools/gt-render/scene-box.mjs +0 -31
  510. package/tools/gt-render/take-transform.mjs +0 -87
  511. package/tools/kimodo/compare-seam.mjs +0 -108
  512. package/tools/kimodo/constraints.mjs +0 -244
  513. package/tools/kimodo/edit.mjs +0 -259
  514. package/tools/kimodo/effector-constraints.mjs +0 -200
  515. package/tools/kimodo/generate.mjs +0 -646
  516. package/tools/kimodo/local-output.mjs +0 -265
  517. package/tools/kimodo/measure-pose.mjs +0 -65
  518. package/tools/kimodo/measure-preserve.mjs +0 -899
  519. package/tools/kimodo/measure-waypoints.mjs +0 -55
  520. package/tools/kimodo/mlx-generate.py +0 -111
  521. package/tools/kimodo/pose-constraints.mjs +0 -248
  522. package/tools/kimodo/preserve-mask.mjs +0 -543
  523. package/tools/kimodo/read-npz.mjs +0 -189
  524. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  525. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  526. package/tools/kimodo/runner.mjs +0 -284
  527. package/tools/kimodo/seam-batch.mjs +0 -77
  528. package/tools/kimodo/setup-local.mjs +0 -30
  529. package/tools/kimodo/setup-on-box.sh +0 -339
  530. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  531. package/tools/morphgs/assets/gen_truth.py +0 -39
  532. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  533. package/tools/morphgs/assets/playback-check.mjs +0 -30
  534. package/tools/morphgs/demo-gate.sh +0 -26
  535. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  536. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  537. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  538. package/tools/morphgs/setup-on-cluster.sh +0 -126
  539. package/tools/projflow/driver.py +0 -1069
  540. package/tools/projflow/generate.mjs +0 -805
  541. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  542. package/tools/projflow/line-edit-job.mjs +0 -547
  543. package/tools/projflow/replay.mjs +0 -452
  544. package/tools/projflow/runner.mjs +0 -269
  545. package/tools/projflow/service.mjs +0 -742
  546. package/tools/track/DESIGN.md +0 -513
  547. package/tools/track/backfill-provenance.mjs +0 -116
  548. package/tools/track/budget.mjs +0 -8
  549. package/tools/track/check-rig.mjs +0 -209
  550. package/tools/track/diagnostics.schema.json +0 -60
  551. package/tools/track/export-rig.mjs +0 -256
  552. package/tools/track/fallback.mjs +0 -7
  553. package/tools/track/fk-parity-fixture.mjs +0 -161
  554. package/tools/track/gate.mjs +0 -349
  555. package/tools/track/masks.mjs +0 -318
  556. package/tools/track/metrics.mjs +0 -201
  557. package/tools/track/publish-obs.mjs +0 -108
  558. package/tools/track/py/check_env.py +0 -61
  559. package/tools/track/py/eval_lr.py +0 -229
  560. package/tools/track/py/lr_viterbi.py +0 -294
  561. package/tools/track/py/masks.py +0 -405
  562. package/tools/track/py/objective.py +0 -382
  563. package/tools/track/py/rig.py +0 -508
  564. package/tools/track/py/scene.py +0 -326
  565. package/tools/track/py/test_joint_indices.py +0 -123
  566. package/tools/track/py/test_lr_viterbi.py +0 -195
  567. package/tools/track/py/test_masks.py +0 -229
  568. package/tools/track/py/test_rig.py +0 -288
  569. package/tools/track/py/test_scene.py +0 -354
  570. package/tools/track/py/test_track.py +0 -328
  571. package/tools/track/py/track.py +0 -411
  572. package/tools/track/remote.mjs +0 -140
  573. package/tools/track/rig-dump.mjs +0 -227
  574. package/tools/track/run-box-tests.mjs +0 -14
  575. package/tools/track/run-box.mjs +0 -168
  576. package/tools/track/setup-box.sh +0 -59
  577. package/tools/track/study-2d.mjs +0 -728
  578. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  579. /package/src/{ardy → motion}/ground.js +0 -0
  580. /package/src/{ardy → motion}/ik-drag.js +0 -0
  581. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  582. /package/src/{ardy → motion}/ik.js +0 -0
  583. /package/src/{ardy → motion}/physics-surface.js +0 -0
  584. /package/src/{ardy → motion}/playback-clock.js +0 -0
  585. /package/src/{ardy → motion}/range-pin.js +0 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,94 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 3.0.1 - 2026-10-19
6
+
7
+ Fixes from an audit of recent Studio agent sessions.
8
+
9
+ Motion
10
+
11
+ - Whole-body pitch and roll take full turns; new skill `flip` (back, front,
12
+ cartwheel).
13
+ - New skill `hitReaction`: a body hit by an object or a person is thrown,
14
+ lands and comes to rest.
15
+ - Other characters' installed takes are bodies for QA and anchor targets
16
+ (`{kind: "person", person, bone}`, refs `person:<id>.<Bone>`).
17
+ - A dropped object tumbles and rests on a stable face.
18
+ - The `fist` and `grip` thumbs wrap the fingers instead of standing up.
19
+
20
+ Studio and agent
21
+
22
+ - QA reads as problems (`qa.problems`), not frame-level issues, on the draft
23
+ card, the timeline beats and the receipt summary.
24
+ - The agent's requests carry less old bulk (older images and receipts are
25
+ shortened at call time), replies follow the user's language, and the plan
26
+ image comes only when staging or the scene asks for it.
27
+ - The agent can place a character within ±240 m, rename a shot and set a
28
+ shot's camera mode (`shot.setCameraMode`). `object.attach` no longer
29
+ crashes, and an editor fault answers `INTERNAL_ERROR`.
30
+ - Playback no longer stutters in large scenes: panels stop serializing the
31
+ scene on every render.
32
+
33
+ ## 3.0.0 - 2026-10-19
34
+
35
+ Motion is computed, not generated. The Studio agent writes a motion spec (key
36
+ poses, skills, walks, contacts and goals) and a deterministic compiler in the
37
+ browser bakes it frame by frame and measures it with QA. The agent compiles,
38
+ reads the numbers and fixes the spec in seconds, with no GPU host.
39
+
40
+ Breaking
41
+
42
+ - The motion-generation backend (ARDY with Kimodo) is removed, and with it
43
+ text-to-motion generation, AI video, video motion capture, line edit,
44
+ AutoPhysics, platform fit and the collision fixer. `npm run dev` starts no
45
+ motion bridge and `CCLAY_KIMODO_HOST` is no longer read.
46
+ - Motion is stored as a new take, `cozyclay.take.v3`: per-bone rotations on
47
+ the X Bot and Y Bot rigs, the hips' path, object tracks and the spec that
48
+ made it. A 2.x project still opens, but its generated (npz) motion is
49
+ dropped when the project is read; compile new motion for its characters.
50
+
51
+ Motion compiler
52
+
53
+ - `motion.compile`, `motion.qa`, `motion.sample`, `motion.describe` and
54
+ `motion.keyObject`. Specs combine keys, generators and skills (`standIdle`,
55
+ `walkTo`, `sitInSeat`, `exitCar`, `reachGrip`, `smokeCigarette`), with
56
+ two-bone IK limbs, finger presets and hand anchors on the world, the body,
57
+ bones, moving objects or the mouth.
58
+ - QA measures collisions with scene objects and the body's own skin, foot
59
+ slides, floating, reach, balance, elbow, knee and wrist limits and pops, and
60
+ names each issue's cause with a hint. Hints become patches that
61
+ `repair` rounds apply; `goals` (near, above, moved, still, speed, ordering)
62
+ state what the motion must do.
63
+ - Held props: grab and release, carried groups, keyed and falling objects,
64
+ prop kits and aim keys; the arm follows what the hand holds.
65
+ - The walk rolls heel to toe, sets its hips from the legs, keeps an uneven
66
+ natural rhythm, and scales its knees, ankles, arm swing and trunk with its
67
+ pace. A held pose breathes and shifts its weight.
68
+ - Compiles run in a Web Worker, off the editor's main thread. The Sequencer
69
+ cuts, retimes and trims an installed take.
70
+
71
+ Look
72
+
73
+ - Practical point and spot lights, glow, lighting looks
74
+ (`stage.applyLighting`), sky light with ambient occlusion and HDR
75
+ environment maps.
76
+ - Deterministic effects: smoke, exhale, steam, fire, sparks, dust, rain, snow
77
+ and a motion trail.
78
+ - Characters default to white clay; the outline ink is at half strength.
79
+
80
+ Agent and Studio
81
+
82
+ - The agent's motion drafts show while it works: as beats on the timeline,
83
+ as a ghost on the character and in the panel, which also shows the running
84
+ turn's reasoning and steps. Replies render their Markdown.
85
+ - Contact sheets pick beat frames, add views that show depth, and draw
86
+ trails and onion-skin ghosts; the viewport has ghosts too.
87
+ - The agent cuts for a new view and lays a camera move as one rail.
88
+ - Connect a model runs the agent runtime's own provider login; keys are
89
+ checked before they are kept. Supervisor review is off by default.
90
+ - Objects scale down to 1 cm, inspect reports each object's world box,
91
+ verify reports support gaps, and a follow dolly goes up to 40 m/s.
92
+
5
93
  ## 2.1.1 - 2026-10-13
6
94
 
7
95
  - Include the sampled framing fix, embedded composition-guide controls, and
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();
@@ -631,13 +586,13 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
631
586
  if (!captured?.dataUrl?.startsWith("data:image/")) throw new StudioProtocolError("TARGET_NOT_READY", "The current frame has no image bytes.");
632
587
  const match = /^data:([^;]+);base64,(.*)$/.exec(captured.dataUrl);
633
588
  frameObservation = { data: match[2], mimeType: match[1], revision: captured.revision, receiptId: captured.receiptId };
634
- } else if (value.context.scene?.characterCount >= 2 || (value.context.shots?.length ?? 0) >= 1 || /샷|컷|카메라|커버리지|OTS|투샷|프레임|shot|camera|coverage|two-shot|framing/i.test(value.text || "")) {
635
- // A staging or coverage request also sees the top-view plan; without it the turn just runs.
589
+ } else if ((await import("./studio-context-budget.mjs")).wantsPlan({ text: value.text, characterCount: value.context.scene?.characterCount ?? 0, shots: value.context.shots?.length ?? 0, planSeen: session.planSceneId === value.context.host.sceneId })) {
590
+ // A staging or coverage request sees the top-view plan, and so does a staged scene's first turn here (once per scene).
636
591
  try {
637
592
  const captured = await hub.command("capture_plan_png", {}, value.context.host.workspaceHandle);
638
593
  const match = /^data:([^;]+);base64,(.*)$/.exec(captured?.dataUrl ?? "");
639
594
  if (!match) throw new Error("The plan capture has no image bytes.");
640
- frameObservation = { kind: "plan", data: match[2], mimeType: match[1], revision: captured.revision ?? value.context.revision?.scene ?? null, receiptId: captured.receiptId };
595
+ frameObservation = { kind: "plan", data: match[2], mimeType: match[1], revision: captured.revision ?? value.context.revision?.scene ?? null, receiptId: captured.receiptId }; session.planSceneId = value.context.host.sceneId;
641
596
  } catch (error) {
642
597
  console.warn(`[agent] plan observation skipped: ${error.message}`);
643
598
  }
@@ -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,5 +1,7 @@
1
1
  import { summariseCanvasResult } from "./agent-tools.mjs";
2
+ import { createReasoningFrames } from "./turn-frames.mjs";
2
3
  import { sanitizeUpstreamDetail } from "./agent-routes.mjs";
4
+ import { budgetStudioRequest } from "./studio-context-budget.mjs";
3
5
 
4
6
  const DEFAULT_MODEL = "gpt-6-astra";
5
7
 
@@ -228,7 +230,7 @@ function studioObservationMessage(observation) {
228
230
  return {
229
231
  role: "user",
230
232
  content: [
231
- { type: "text", text: `Studio ${observation?.kind === "plan" ? "plan observation (top view)" : "frame observation"} revision ${JSON.stringify(observation?.revision ?? null)} receipt ${observation?.receiptId ?? "unavailable"}` },
233
+ { type: "text", text: `Studio ${observation?.kind === "plan" ? "plan observation (top view)" : "frame observation"} at scene revision ${JSON.stringify(observation?.revision ?? null)}${observation?.receiptId ? `, receipt ${observation.receiptId}` : ""}` },
232
234
  { type: "image", data: observation.data, mimeType: observation.mimeType },
233
235
  ],
234
236
  };
@@ -363,7 +365,7 @@ export function createAgentRunner({ models: suppliedModels, sessionStore, tools
363
365
 
364
366
  const ensureHarness = async (input) => {
365
367
  const { pi, models: registry } = await ensureModels();
366
- const activePrompt = input.systemPrompt || (input.surface === "studio" ? (await import("./studio-prompt.mjs")).STUDIO_SYSTEM_PROMPT : systemPrompt);
368
+ const activePrompt = input.systemPrompt || (input.surface === "studio" ? await (await import("./studio-motion-reference.mjs")).studioSystemPrompt() : systemPrompt);
367
369
  const { resolveModel, resolveEffort } = await import("./providers.mjs");
368
370
  const requested = input.model || DEFAULT_MODEL;
369
371
  const slash = requested.indexOf("/");
@@ -396,7 +398,7 @@ export function createAgentRunner({ models: suppliedModels, sessionStore, tools
396
398
  usage.requests += 1;
397
399
  for (const key of ["input", "output", "cacheRead", "cacheWrite"]) if (Number.isFinite(message?.usage?.[key])) usage[key] += message.usage[key];
398
400
  });
399
- state.harness.hooks.on("transform_context", (event) => state.lastInput?.surface === "studio" ? studioContextDiet(event) : undefined);
401
+ state.harness.hooks.on("transform_context", (event) => state.lastInput?.surface === "studio" ? budgetStudioRequest(studioContextDiet(event)) : undefined);
400
402
  state.harness.hooks.on("before_payload", ({ model, payload }) => {
401
403
  if (model?.api !== "anthropic-messages") return undefined;
402
404
  const next = withAnthropicCacheBreakpoints(payload, model);
@@ -418,9 +420,8 @@ export function createAgentRunner({ models: suppliedModels, sessionStore, tools
418
420
  for (const message of restored.history) await state.lane.appendMessage(message, state.context);
419
421
  state.persisted = restored.history.length;
420
422
  }
421
- // Studio only: older turns' full contexts are stale and large, so each request
422
- // carries the newest one whole and a stub for the rest. The stored lane and
423
- // the persisted history keep every context.
423
+ // Studio only, on the provider view (studioContextDiet, then studio-context-budget.mjs): the stored
424
+ // lane and the persisted history keep every context, result, script and image whole.
424
425
  state.unsubscribers.push(state.harness.events.on("turn_end", persist));
425
426
  state.unsubscribers.push(state.harness.events.on("run_end", persist));
426
427
  }
@@ -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);