cozyclay 1.9.0 → 2.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 (286) hide show
  1. package/CHANGELOG.md +171 -0
  2. package/bin/agent/agent-routes.mjs +74 -70
  3. package/bin/agent/agent-runner.mjs +10 -3
  4. package/bin/agent/motion-runtime.mjs +32 -260
  5. package/bin/agent/providers.mjs +46 -3
  6. package/bin/agent/session-store.mjs +41 -5
  7. package/bin/agent/studio-prompt.mjs +1 -1
  8. package/bin/agent/studio-tools.mjs +48 -11
  9. package/bin/cozyclay.mjs +26 -8
  10. package/bin/live/cli.mjs +22 -3
  11. package/bin/mcp-runtime.mjs +35 -20
  12. package/bin/telemetry-state.mjs +5 -2
  13. package/bin/update-check.mjs +15 -0
  14. package/dist/app/index.html +6 -6
  15. package/dist/assets/analytics-MVZOEW8O.js +1 -0
  16. package/dist/assets/app-C1sdVbg4.css +1 -0
  17. package/dist/assets/app-CA55tcI3.js +4876 -0
  18. package/dist/assets/{demo-B-eg1CT8.js → demo-ByeYp-dY.js} +1 -1
  19. package/dist/assets/{first-shot-handoff-CGgnvNu8.js → first-shot-handoff-DgT28ZiV.js} +1 -1
  20. package/dist/assets/{landing-IbRp3FnQ.js → landing-BaXkpFip.js} +1 -1
  21. package/dist/assets/shot-prompt-CbqWcWYi.css +1 -0
  22. package/dist/assets/shot-prompt-Yrt99wZx.js +17 -0
  23. package/dist/assets/{ticket-BgIzOBOf.js → ticket-BcmsnHn5.js} +1 -1
  24. package/dist/assets/workflow-DRdwoDBT.css +1 -0
  25. package/dist/assets/workflow-dNPGY9xz.js +179 -0
  26. package/dist/cozyclay-package.json +1 -1
  27. package/dist/fonts/IBM-Plex-Sans-OFL.txt +92 -0
  28. package/dist/fonts/JetBrains-Mono-OFL.txt +93 -0
  29. package/dist/fonts/README.md +13 -9
  30. package/dist/fonts/ibm-plex-sans-400-latin.woff2 +0 -0
  31. package/dist/fonts/ibm-plex-sans-500-latin.woff2 +0 -0
  32. package/dist/fonts/ibm-plex-sans-600-latin.woff2 +0 -0
  33. package/dist/fonts/jetbrains-mono-400-latin.woff2 +0 -0
  34. package/dist/fonts/jetbrains-mono-500-latin.woff2 +0 -0
  35. package/dist/index.html +7 -7
  36. package/dist/privacy/index.html +13 -8
  37. package/dist/sitemap.xml +6 -6
  38. package/dist/workflow/index.html +5 -5
  39. package/mcp/LIVE-PROTOCOL.md +1 -1
  40. package/mcp/live-hub.mjs +35 -93
  41. package/mcp/mesh-file.mjs +48 -0
  42. package/mcp/server.mjs +6 -77
  43. package/mcp/tool-handlers.mjs +224 -377
  44. package/package.json +2 -1
  45. package/src/App.jsx +1273 -9075
  46. package/src/analytics.js +393 -9
  47. package/src/app-context.js +211 -0
  48. package/src/app-stage.jsx +25 -24
  49. package/src/ardy/auto-fix-panel.css +108 -0
  50. package/src/ardy/collision-blockers.js +9 -3
  51. package/src/ardy/fix-collisions.js +10 -66
  52. package/src/ardy/ground.js +4 -4
  53. package/src/ardy/ik-drag.js +101 -0
  54. package/src/ardy/ik-key-json.js +43 -0
  55. package/src/ardy/ik.js +122 -76
  56. package/src/ardy/physics-panel.css +2 -0
  57. package/src/ardy/physics-panel.jsx +17 -5
  58. package/src/ardy/platform-fit-panel.css +64 -0
  59. package/src/ardy/platform-fit-panel.jsx +40 -0
  60. package/src/ardy/platform-fit.js +326 -0
  61. package/src/ardy/playback.js +166 -20
  62. package/src/ardy/range-pin.js +299 -0
  63. package/src/ardy/timeline-coordinates.js +26 -0
  64. package/src/ardy/timeline.css +1199 -0
  65. package/src/ardy/timeline.jsx +149 -41
  66. package/src/ardy/waypoints.js +2 -2
  67. package/src/asset-pane.css +462 -0
  68. package/src/asset-pane.jsx +461 -232
  69. package/src/command-bus.js +349 -0
  70. package/src/commands/ai.js +31 -0
  71. package/src/commands/cast.js +140 -0
  72. package/src/commands/elements/character.js +13 -0
  73. package/src/commands/elements/motion.js +14 -0
  74. package/src/commands/elements/object.js +22 -0
  75. package/src/commands/elements/scene.js +16 -0
  76. package/src/commands/elements/shot.js +9 -0
  77. package/src/commands/elements/stage.js +11 -0
  78. package/src/commands/elements.js +155 -0
  79. package/src/commands/export.js +20 -0
  80. package/src/commands/index.js +40 -0
  81. package/src/commands/motion.js +231 -0
  82. package/src/commands/objects.js +174 -0
  83. package/src/commands/project.js +53 -0
  84. package/src/commands/scene.js +81 -0
  85. package/src/commands/shared.js +19 -0
  86. package/src/commands/shot.js +103 -0
  87. package/src/commands/stage.js +17 -0
  88. package/src/commands/view.js +42 -0
  89. package/src/document-store.js +144 -0
  90. package/src/domains/cast.js +1007 -0
  91. package/src/domains/motion.js +3219 -0
  92. package/src/domains/objects.js +932 -0
  93. package/src/domains/scenes.js +817 -0
  94. package/src/domains/shots.js +404 -0
  95. package/src/domains/stage.js +93 -0
  96. package/src/dualview.jsx +113 -57
  97. package/src/facing-marks.js +3 -0
  98. package/src/first-success-guide.jsx +14 -14
  99. package/src/grid-view.js +18 -5
  100. package/src/hierarchy-model.js +24 -16
  101. package/src/hierarchy-panel.css +386 -0
  102. package/src/hierarchy-panel.jsx +150 -21
  103. package/src/{fal-motion-client.js → i2v-motion-client.js} +13 -13
  104. package/src/i2v-motion-studio.jsx +180 -0
  105. package/src/ik-camera.js +3 -0
  106. package/src/main.jsx +6 -0
  107. package/src/motion/generation.js +105 -0
  108. package/src/motion-readiness-ui.jsx +4 -4
  109. package/src/motion-readiness.js +11 -0
  110. package/src/motion-trail.js +366 -9
  111. package/src/object-gizmo.jsx +7 -1
  112. package/src/otio.js +2 -1
  113. package/src/panels/CameraPanel.jsx +60 -0
  114. package/src/panels/CharacterTransformPanel.jsx +48 -0
  115. package/src/panels/EnvironmentPanel.jsx +40 -0
  116. package/src/panels/Foldout.jsx +32 -0
  117. package/src/panels/LightPanel.jsx +24 -0
  118. package/src/panels/ObjectTransformPanel.jsx +554 -0
  119. package/src/panels/PosePanel.jsx +97 -0
  120. package/src/panels/ProjectPanel.jsx +33 -0
  121. package/src/panels/PromptBlocksPanel.jsx +442 -0
  122. package/src/panels/PropsPanel.jsx +70 -0
  123. package/src/panels/ReferenceImageField.jsx +91 -0
  124. package/src/panels/RigControlPanel.jsx +78 -0
  125. package/src/panels/RigPanel.jsx +37 -0
  126. package/src/panels/SubjectBox.jsx +47 -0
  127. package/src/panels/SubjectsPanel.jsx +39 -0
  128. package/src/panels/VideoCapturePanel.jsx +180 -0
  129. package/src/panels/details.css +1171 -0
  130. package/src/panels/motion.css +197 -0
  131. package/src/panels/pose.css +39 -0
  132. package/src/planview.jsx +60 -31
  133. package/src/posestudio.jsx +59 -4
  134. package/src/project-browser.css +1073 -0
  135. package/src/project-browser.jsx +284 -123
  136. package/src/range-pin-object-transform.js +19 -0
  137. package/src/range-pin-panel.css +518 -0
  138. package/src/range-pin-panel.jsx +344 -0
  139. package/src/result-modal.jsx +5 -5
  140. package/src/room.jsx +79 -51
  141. package/src/scene-objects.js +37 -1
  142. package/src/scenes.js +5 -0
  143. package/src/semantic-edit.js +2 -0
  144. package/src/settings-menu.jsx +39 -105
  145. package/src/shell/BottomDock.jsx +450 -0
  146. package/src/shell/DetailsSlot.jsx +513 -0
  147. package/src/shell/LibrarySlot.jsx +86 -0
  148. package/src/shell/MenuBar.jsx +601 -0
  149. package/src/shell/OutlinerSlot.jsx +50 -0
  150. package/src/shell/PreferencesDialog.jsx +538 -0
  151. package/src/shell/PreferencesSlot.jsx +40 -0
  152. package/src/shell/StatusBar.jsx +125 -0
  153. package/src/shell/StudioShell.jsx +92 -0
  154. package/src/shell/TopBar.jsx +129 -0
  155. package/src/shell/ViewportToolbar.jsx +656 -0
  156. package/src/shell/agent-glass.css +296 -0
  157. package/src/shell/dock.css +93 -0
  158. package/src/shell/glass-regions.css +985 -0
  159. package/src/shell/glass.css +687 -0
  160. package/src/shell/log-store.js +52 -0
  161. package/src/shell/mode.css +9 -0
  162. package/src/shell/preferences.css +651 -0
  163. package/src/shell/shell.css +282 -0
  164. package/src/shell/studio-shell-context.js +19 -0
  165. package/src/shell/topbar.css +435 -0
  166. package/src/shell/viewport.css +451 -0
  167. package/src/store/authored-intent.js +22 -0
  168. package/src/store/runtime-adapters.js +92 -0
  169. package/src/store/scene-stage.js +21 -0
  170. package/src/store/use-document-store.js +14 -0
  171. package/src/studio-actions.js +210 -0
  172. package/src/studio-agent-commands.js +41 -271
  173. package/src/studio-agent-context.js +39 -10
  174. package/src/studio-agent-motion.js +122 -373
  175. package/src/studio-agent-protocol.js +67 -31
  176. package/src/studio-app-binding.js +379 -0
  177. package/src/studio-contact-sheet.js +73 -0
  178. package/src/studio-elements.js +58 -51
  179. package/src/styles/themes.css +200 -0
  180. package/src/styles/tokens.css +69 -0
  181. package/src/styles.css +460 -1742
  182. package/src/theme.js +44 -0
  183. package/src/timeline-extent.js +16 -0
  184. package/src/trail-key-conflicts.js +39 -0
  185. package/src/trail-pick.js +59 -0
  186. package/src/ui.jsx +8 -8
  187. package/src/use-case-question.jsx +66 -0
  188. package/src/workflow/AgentPanel.jsx +82 -69
  189. package/src/workflow/agent-client.js +12 -2
  190. package/src/workflow/agent-panel.css +34 -32
  191. package/src/workflow/cozy-scene-node.css +2 -0
  192. package/src/workflow/workflow.css +2 -0
  193. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  194. package/tools/ardy/bridge.mjs +164 -146
  195. package/tools/ardy/visual-qa.mjs +5 -5
  196. package/tools/bench/EXP3.md +61 -0
  197. package/tools/bench/cclay_bench_extract_incam.py +125 -0
  198. package/tools/bench/cclay_bench_extract_obs.py +204 -0
  199. package/tools/bench/cclay_bench_runner.py +42 -0
  200. package/tools/bench/cube-contact.mjs +162 -0
  201. package/tools/bench/exp3.mjs +152 -0
  202. package/tools/bench/extract-bench-lib.mjs +216 -0
  203. package/tools/bench/extract-bench.mjs +302 -0
  204. package/tools/bench/fal-generate.mjs +88 -0
  205. package/tools/bench/fit/README.md +189 -0
  206. package/tools/bench/fit/camera.mjs +59 -0
  207. package/tools/bench/fit/contact.mjs +419 -0
  208. package/tools/bench/fit/footlock.mjs +234 -0
  209. package/tools/bench/fit/motion.mjs +85 -0
  210. package/tools/bench/fit/pin.mjs +27 -0
  211. package/tools/bench/fit/remote.mjs +105 -0
  212. package/tools/bench/fit-bench.mjs +126 -0
  213. package/tools/bench/fit-sanity.mjs +45 -0
  214. package/tools/bench/metrics.mjs +313 -0
  215. package/tools/bench/obs/depth.mjs +258 -0
  216. package/tools/bench/obs/extrinsics.mjs +175 -0
  217. package/tools/bench/obs/fit_mannequin_betas.py +340 -0
  218. package/tools/bench/obs/ground.mjs +294 -0
  219. package/tools/bench/obs/heading.mjs +151 -0
  220. package/tools/bench/obs/ladder.mjs +555 -0
  221. package/tools/bench/obs/mannequin-betas.json +124 -0
  222. package/tools/bench/obs/remote.mjs +83 -0
  223. package/tools/bench/obs/rest_joints.py +52 -0
  224. package/tools/bench/obs/ybot-targets.mjs +49 -0
  225. package/tools/bench/obs-bench.mjs +520 -0
  226. package/tools/bench/score.mjs +343 -0
  227. package/tools/bench/summarize.mjs +86 -0
  228. package/tools/dev/pages/privacy.html +13 -8
  229. package/tools/gt-render/browser.mjs +159 -0
  230. package/tools/gt-render/camera-math.mjs +276 -0
  231. package/tools/gt-render/page.mjs +267 -0
  232. package/tools/gt-render/render.mjs +514 -0
  233. package/tools/gt-render/scene-box.mjs +31 -0
  234. package/tools/gt-render/take-transform.mjs +87 -0
  235. package/tools/morphgs/assets/gen_truth.py +39 -0
  236. package/tools/morphgs/assets/mesh_ori_rig.txt +27 -0
  237. package/tools/morphgs/assets/playback-check.mjs +30 -0
  238. package/tools/morphgs/demo-gate.sh +26 -0
  239. package/tools/morphgs/fbx2morphgs.mjs +68 -0
  240. package/tools/morphgs/morphgs-to-cskel27.mjs +105 -0
  241. package/tools/morphgs/patches/preprocess_src-none-mode.patch +76 -0
  242. package/tools/morphgs/setup-on-cluster.sh +126 -0
  243. package/tools/qa/css-rule-usage.mjs +283 -0
  244. package/tools/qa/studio-control-count.mjs +70 -32
  245. package/tools/run-tests.mjs +134 -12
  246. package/tools/track/DESIGN.md +513 -0
  247. package/tools/track/backfill-provenance.mjs +116 -0
  248. package/tools/track/budget.mjs +8 -0
  249. package/tools/track/check-rig.mjs +209 -0
  250. package/tools/track/diagnostics.schema.json +60 -0
  251. package/tools/track/export-rig.mjs +256 -0
  252. package/tools/track/fallback.mjs +7 -0
  253. package/tools/track/fk-parity-fixture.mjs +161 -0
  254. package/tools/track/gate.mjs +349 -0
  255. package/tools/track/masks.mjs +318 -0
  256. package/tools/track/metrics.mjs +201 -0
  257. package/tools/track/publish-obs.mjs +108 -0
  258. package/tools/track/py/check_env.py +61 -0
  259. package/tools/track/py/eval_lr.py +229 -0
  260. package/tools/track/py/lr_viterbi.py +294 -0
  261. package/tools/track/py/masks.py +405 -0
  262. package/tools/track/py/objective.py +382 -0
  263. package/tools/track/py/rig.py +508 -0
  264. package/tools/track/py/scene.py +326 -0
  265. package/tools/track/py/test_joint_indices.py +123 -0
  266. package/tools/track/py/test_lr_viterbi.py +195 -0
  267. package/tools/track/py/test_masks.py +229 -0
  268. package/tools/track/py/test_rig.py +288 -0
  269. package/tools/track/py/test_scene.py +354 -0
  270. package/tools/track/py/test_track.py +328 -0
  271. package/tools/track/py/track.py +411 -0
  272. package/tools/track/remote.mjs +140 -0
  273. package/tools/track/rig-dump.mjs +227 -0
  274. package/tools/track/run-box-tests.mjs +14 -0
  275. package/tools/track/run-box.mjs +168 -0
  276. package/tools/track/setup-box.sh +59 -0
  277. package/tools/track/study-2d.mjs +728 -0
  278. package/dist/assets/analytics-B1hnH66c.js +0 -1
  279. package/dist/assets/app-BWusbqgO.js +0 -4861
  280. package/dist/assets/app-qKDo4PBX.css +0 -1
  281. package/dist/assets/shot-prompt-CHUtw6af.js +0 -17
  282. package/dist/assets/shot-prompt-C_g2BHVc.css +0 -1
  283. package/dist/assets/workflow-CVwzMhz2.js +0 -179
  284. package/dist/assets/workflow-DsudxKHF.css +0 -1
  285. package/src/fal-motion-studio.jsx +0 -180
  286. package/src/scene-history.js +0 -129
@@ -1,3 +1,4 @@
1
+ @layer legacy {
1
2
  /* Agent panel (#126).
2
3
  *
3
4
  * workflow.css is shipped minified on one line, so the panel keeps its own
@@ -215,14 +216,14 @@
215
216
  height: 58px;
216
217
  padding: 0 var(--agent-space-4);
217
218
  border-bottom: 1px solid var(--agent-border);
218
- background: #171921;
219
+ background: var(--agent-chrome, #171921);
219
220
  }
220
221
 
221
222
  .agent-title {
222
223
  margin: 0;
223
224
  font-size: var(--agent-font-xl);
224
225
  font-weight: 700;
225
- color: #f5f7ff;
226
+ color: var(--agent-text-strong, #f5f7ff);
226
227
  }
227
228
 
228
229
  .agent-header-spacer { flex: 1; }
@@ -234,8 +235,8 @@
234
235
  gap: var(--agent-space-1);
235
236
  border: 1px solid var(--agent-border-strong);
236
237
  border-radius: var(--agent-radius-md);
237
- background: #20232d;
238
- color: #c8ccdb;
238
+ background: var(--agent-panel, #20232d);
239
+ color: var(--agent-text-soft, #c8ccdb);
239
240
  padding: var(--agent-space-2) var(--agent-space-3);
240
241
  font: inherit;
241
242
  font-size: var(--agent-font-md);
@@ -246,9 +247,9 @@
246
247
 
247
248
  .agent-icon-button:hover,
248
249
  .agent-ghost-button:hover {
249
- border-color: #606b9a;
250
- background: #2b3040;
251
- color: #f5f7ff;
250
+ border-color: var(--agent-border-strong, #606b9a);
251
+ background: var(--agent-hover, #2b3040);
252
+ color: var(--agent-text-strong, #f5f7ff);
252
253
  }
253
254
 
254
255
  .agent-ghost-button[disabled],
@@ -278,7 +279,7 @@
278
279
  border: 0;
279
280
  border-radius: var(--agent-radius-sm);
280
281
  background: transparent;
281
- color: #c8ccdb;
282
+ color: var(--agent-text-soft, #c8ccdb);
282
283
  padding: var(--agent-space-2) var(--agent-space-3);
283
284
  font: inherit;
284
285
  font-size: var(--agent-font-md);
@@ -286,7 +287,7 @@
286
287
  cursor: pointer;
287
288
  }
288
289
 
289
- .agent-menu button:hover { background: #2b3040; color: #f5f7ff; }
290
+ .agent-menu button:hover { background: var(--agent-hover, #2b3040); color: var(--agent-text-strong, #f5f7ff); }
290
291
 
291
292
  /* --- account strip ----------------------------------------------------- */
292
293
 
@@ -313,7 +314,7 @@
313
314
  padding: 2px var(--agent-space-2);
314
315
  border: 1px solid var(--agent-accent-edge);
315
316
  border-radius: var(--agent-radius-pill);
316
- color: #cfd5ff;
317
+ color: var(--agent-text-strong, #cfd5ff);
317
318
  font-size: var(--agent-font-xs);
318
319
  letter-spacing: 0.06em;
319
320
  text-transform: uppercase;
@@ -462,7 +463,7 @@
462
463
  .agent-bubble {
463
464
  max-width: 86%;
464
465
  padding: var(--agent-space-3) var(--agent-space-4);
465
- border: 1px solid #343b58;
466
+ border: 1px solid var(--agent-border-strong, #343b58);
466
467
  border-radius: var(--agent-radius-lg);
467
468
  background: var(--agent-bubble);
468
469
  color: var(--agent-text);
@@ -473,7 +474,7 @@
473
474
  }
474
475
 
475
476
  .agent-assistant-text {
476
- color: #dfe3f2;
477
+ color: var(--agent-text-soft, #dfe3f2);
477
478
  font-size: var(--agent-font-lg);
478
479
  line-height: 1.6;
479
480
  white-space: pre-wrap;
@@ -495,7 +496,7 @@
495
496
  .agent-card {
496
497
  border: 1px solid var(--agent-border-strong);
497
498
  border-radius: var(--agent-radius-lg);
498
- background: #20232d;
499
+ background: var(--agent-panel, #20232d);
499
500
  overflow: hidden;
500
501
  }
501
502
 
@@ -504,7 +505,7 @@
504
505
  align-items: center;
505
506
  gap: var(--agent-space-3);
506
507
  padding: var(--agent-space-3) var(--agent-space-4);
507
- color: #ccd1e2;
508
+ color: var(--agent-text-soft, #ccd1e2);
508
509
  font-size: var(--agent-font-md);
509
510
  cursor: pointer;
510
511
  list-style: none;
@@ -518,9 +519,9 @@
518
519
  .agent-tool-detail {
519
520
  margin: 0;
520
521
  padding: var(--agent-space-3) var(--agent-space-4);
521
- border-top: 1px solid #2c3140;
522
+ border-top: 1px solid var(--agent-border, #2c3140);
522
523
  background: var(--agent-bg-sunken);
523
- color: #98a0b8;
524
+ color: var(--agent-text-muted, #98a0b8);
524
525
  font: var(--agent-font-sm) ui-monospace, monospace;
525
526
  white-space: pre-wrap;
526
527
  word-break: break-all;
@@ -556,7 +557,7 @@
556
557
  flex-wrap: wrap;
557
558
  gap: var(--agent-space-2);
558
559
  padding: var(--agent-space-3);
559
- border-top: 1px solid #2c3140;
560
+ border-top: 1px solid var(--agent-border, #2c3140);
560
561
  }
561
562
 
562
563
  .agent-image-actions button,
@@ -566,8 +567,8 @@
566
567
  gap: var(--agent-space-1);
567
568
  border: 1px solid var(--agent-border-strong);
568
569
  border-radius: var(--agent-radius-sm);
569
- background: #292d39;
570
- color: #ccd1e2;
570
+ background: var(--agent-hover, #292d39);
571
+ color: var(--agent-text-soft, #ccd1e2);
571
572
  padding: var(--agent-space-2) var(--agent-space-3);
572
573
  font: inherit;
573
574
  font-size: var(--agent-font-sm);
@@ -578,18 +579,18 @@
578
579
  .agent-image-actions button.primary {
579
580
  border-color: var(--agent-accent-edge);
580
581
  background: var(--agent-accent-deep);
581
- color: #e6e9ff;
582
+ color: var(--agent-text-strong, #e6e9ff);
582
583
  }
583
584
 
584
585
  .agent-image-actions button:hover,
585
- .agent-image-actions a:hover { border-color: #606b9a; }
586
+ .agent-image-actions a:hover { border-color: var(--agent-border-strong, #606b9a); }
586
587
 
587
588
  .agent-placed {
588
589
  display: flex;
589
590
  align-items: center;
590
591
  gap: var(--agent-space-2);
591
592
  padding: var(--agent-space-3);
592
- border-top: 1px solid #2c3140;
593
+ border-top: 1px solid var(--agent-border, #2c3140);
593
594
  color: var(--agent-ok);
594
595
  font-size: var(--agent-font-md);
595
596
  }
@@ -598,8 +599,8 @@
598
599
  margin-left: auto;
599
600
  border: 1px solid var(--agent-border-strong);
600
601
  border-radius: var(--agent-radius-sm);
601
- background: #292d39;
602
- color: #ccd1e2;
602
+ background: var(--agent-hover, #292d39);
603
+ color: var(--agent-text-soft, #ccd1e2);
603
604
  padding: var(--agent-space-1) var(--agent-space-3);
604
605
  font: inherit;
605
606
  font-size: var(--agent-font-sm);
@@ -644,7 +645,7 @@
644
645
  display: flex;
645
646
  align-items: center;
646
647
  gap: var(--agent-space-2);
647
- color: #ccd1e2;
648
+ color: var(--agent-text-soft, #ccd1e2);
648
649
  }
649
650
 
650
651
  .agent-job-label { font-weight: 600; }
@@ -778,7 +779,7 @@
778
779
  margin: 0;
779
780
  font-size: var(--agent-font-xl);
780
781
  font-weight: 700;
781
- color: #f5f7ff;
782
+ color: var(--agent-text-strong, #f5f7ff);
782
783
  }
783
784
 
784
785
  .agent-state-card p {
@@ -793,7 +794,7 @@
793
794
  border: 1px solid var(--agent-accent-edge);
794
795
  border-radius: var(--agent-radius-md);
795
796
  background: var(--agent-accent-deep);
796
- color: #e6e9ff;
797
+ color: var(--agent-text-strong, #e6e9ff);
797
798
  padding: var(--agent-space-3) var(--agent-space-5);
798
799
  font: inherit;
799
800
  font-size: var(--agent-font-lg);
@@ -856,7 +857,7 @@
856
857
  .agent-chip {
857
858
  border: 1px solid var(--agent-border-strong);
858
859
  border-radius: var(--agent-radius-pill);
859
- background: #20232d;
860
+ background: var(--agent-panel, #20232d);
860
861
  color: #bcc2d4;
861
862
  padding: var(--agent-space-2) var(--agent-space-4);
862
863
  font: inherit;
@@ -865,7 +866,7 @@
865
866
  cursor: pointer;
866
867
  }
867
868
 
868
- .agent-chip:hover { border-color: #606b9a; background: #2b3040; color: #f5f7ff; }
869
+ .agent-chip:hover { border-color: var(--agent-border-strong, #606b9a); background: var(--agent-hover, #2b3040); color: var(--agent-text-strong, #f5f7ff); }
869
870
 
870
871
  /* --- activity line ------------------------------------------------------ */
871
872
 
@@ -1048,7 +1049,7 @@
1048
1049
  gap: var(--agent-space-2);
1049
1050
  border: 1px solid var(--agent-border-strong);
1050
1051
  border-radius: var(--agent-radius-pill);
1051
- background: #20232d;
1052
+ background: var(--agent-panel, #20232d);
1052
1053
  color: #b6bcd0;
1053
1054
  padding: var(--agent-space-1) var(--agent-space-3);
1054
1055
  font: inherit;
@@ -1059,7 +1060,7 @@
1059
1060
  .agent-attach-chip[aria-pressed="true"] {
1060
1061
  border-color: var(--agent-accent-edge);
1061
1062
  background: var(--agent-accent-deep);
1062
- color: #e6e9ff;
1063
+ color: var(--agent-text-strong, #e6e9ff);
1063
1064
  }
1064
1065
 
1065
1066
  .agent-attach-thumb {
@@ -1105,7 +1106,7 @@
1105
1106
  border: 1px solid var(--agent-accent-edge);
1106
1107
  border-radius: var(--agent-radius-md);
1107
1108
  background: var(--agent-accent-deep);
1108
- color: #e6e9ff;
1109
+ color: var(--agent-text-strong, #e6e9ff);
1109
1110
  padding: var(--agent-space-2) var(--agent-space-5);
1110
1111
  font: inherit;
1111
1112
  font-size: var(--agent-font-md);
@@ -1169,3 +1170,4 @@
1169
1170
 
1170
1171
  .agent-resize { display: none; }
1171
1172
  }
1173
+ }
@@ -1,3 +1,4 @@
1
+ @layer legacy {
1
2
  .cozy-scene-node {
2
3
  position: relative;
3
4
  width: 318px;
@@ -61,3 +62,4 @@
61
62
  .cozy-scene-reactflow-handle-motion { top: 115px !important; }
62
63
  .cozy-scene-reactflow-handle-render { top: 108px !important; }
63
64
  .cozy-scene-reactflow-handle-scene { top: 140px !important; }
65
+ }
@@ -1,3 +1,4 @@
1
+ @layer legacy {
1
2
  :root{font-family:Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;color:#edf0fb;background:#101116;font-synthesis:none}*{box-sizing:border-box}body{margin:0;min-width:900px;background:#101116}#workflow-root{height:100vh}.workflow-app{height:100vh;display:flex;flex-direction:column;background:#101116;color:#edf0fb}.workflow-topbar{height:58px;display:flex;align-items:center;justify-content:space-between;padding:0 22px;border-bottom:1px solid #292d38;background:#171921}.workflow-brand{display:flex;align-items:center;gap:9px;font-size:14px;color:#afb4c6}.workflow-brand strong{font-size:14px;color:#f5f7ff}.workflow-brand-mark{display:grid;place-items:center;width:23px;height:23px;border-radius:7px;background:linear-gradient(135deg,#ef759d,#6c7cff);font-size:12px;font-weight:800;color:white}.workflow-divider{color:#555b70;margin-left:3px}.workflow-top-actions{display:flex;align-items:center;gap:9px}.workflow-top-actions button,.workflow-sidebar-bottom button,.workflow-sidebar-bottom a{border:1px solid #353a4a;border-radius:6px;background:#20232d;color:#c8ccdb;padding:7px 12px;font:inherit;font-size:12px;cursor:pointer;text-decoration:none}.workflow-top-actions button:hover,.workflow-sidebar-bottom button:hover,.workflow-sidebar-bottom a:hover{border-color:#606b9a;background:#2b3040}.workflow-status{display:flex;align-items:center;gap:7px;margin-right:8px;color:#7f879e;font-size:11px}.workflow-status i{width:6px;height:6px;border-radius:50%;background:#4dcda4;box-shadow:0 0 8px #4dcda4}.workflow-main{display:flex;flex:1;min-height:0}.workflow-sidebar{width:232px;display:flex;flex-direction:column;flex:none;padding:22px 14px;border-right:1px solid #292d38;background:#171921}.workflow-sidebar-title{font-size:12px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#f3f5fd}.workflow-sidebar-copy{margin:8px 6px 18px 0;color:#737b92;line-height:1.5;font-size:11px}.workflow-node-menu{display:flex;flex-direction:column;gap:5px}.workflow-add-node{display:flex;align-items:center;gap:10px;width:100%;padding:9px 10px;border:1px solid transparent;border-radius:6px;background:transparent;color:#bcc2d4;font:inherit;font-size:12px;text-align:left;cursor:pointer}.workflow-add-node:hover{border-color:#373d50;background:#222631;color:#f5f7ff}.workflow-add-node span:nth-child(2){flex:1}.workflow-sidebar-bottom{display:flex;flex-direction:column;gap:8px;margin-top:auto}.workflow-sidebar-bottom button,.workflow-sidebar-bottom a{text-align:center;font-size:11px}.workflow-canvas{position:relative;flex:1;min-width:0}.workflow-canvas .react-flow{background:#11141c}.workflow-canvas .react-flow__controls{bottom:22px;left:20px;box-shadow:0 5px 18px #0005}.workflow-canvas .react-flow__controls-button{width:28px;height:28px;border-bottom:1px solid #323744;background:#20242e;fill:#aeb5c9;color:#aeb5c9}.workflow-canvas .react-flow__minimap{right:20px;bottom:20px;border:1px solid #353a49;border-radius:7px;overflow:hidden}.workflow-canvas-panel{margin:14px}.workflow-canvas-panel button{display:flex;align-items:center;gap:6px;padding:8px 11px;border:1px solid #454d70;border-radius:6px;background:#27304a;color:#e7eaff;font:inherit;font-size:11px;cursor:pointer}.workflow-node{position:relative;width:230px;overflow:visible;border:1px solid #3c4152;border-radius:9px;background:#20232d;box-shadow:0 10px 30px #0004;color:#e5e8f5}.workflow-node:hover{border-color:#6877bd}.workflow-node-header{display:flex;align-items:center;gap:8px;padding:10px 11px;border-bottom:1px solid #343946;background:#252935;border-radius:9px 9px 0 0;font-size:12px}.workflow-node-icon{display:grid;place-items:center;width:23px;height:23px;border-radius:6px;background:#303648;color:#aeb8ff}.workflow-node-image .workflow-node-icon{color:#67d8b6}.workflow-node-video .workflow-node-icon{color:#eca56e}.workflow-node-upload .workflow-node-icon{color:#c4a8ef}.workflow-node-concat .workflow-node-icon{color:#e3c969}.workflow-node-scene .workflow-node-icon{color:#ef82a7}.workflow-node-id{margin-left:auto;color:#6c7389;font:10px ui-monospace,monospace}.workflow-node-body{display:flex;flex-direction:column;gap:7px;padding:11px}.workflow-node label{color:#8d95ab;font-size:10px}.workflow-node input,.workflow-node textarea,.workflow-node select{width:100%;border:1px solid #3a3f4e;border-radius:5px;background:#171a21;color:#e5e8f5;padding:7px 8px;outline:0;font:inherit;font-size:11px;resize:vertical}.workflow-node input:focus,.workflow-node textarea:focus,.workflow-node select:focus{border-color:#707dce}.workflow-textarea{height:65px}.workflow-node-foot{display:flex;align-items:center;justify-content:space-between;margin-top:3px;color:#737b92;font-size:10px}.workflow-mini-button,.workflow-run-button{display:flex;align-items:center;gap:4px;border:1px solid #4c5aab;border-radius:5px;background:#303b71;color:#e6e9ff;padding:5px 8px;font:inherit;font-size:10px;cursor:pointer}.workflow-dropzone{display:flex;align-items:center;justify-content:center;gap:7px;height:74px;border:1px dashed #49516a;border-radius:6px;color:#78819c;font-size:10px;text-align:center}.workflow-upload{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;height:90px;border:1px dashed #525b79;border-radius:6px;color:#a89ce4;cursor:pointer}.workflow-upload input{position:absolute;inset:0;opacity:0;cursor:pointer}.workflow-schema-fields{display:flex;flex-direction:column;gap:6px;padding-top:4px;border-top:1px solid #323746}.workflow-schema-title{color:#8d95ab;font-size:9px;text-transform:uppercase;letter-spacing:.08em}.workflow-schema-fields label{display:flex;flex-direction:column;gap:4px}.workflow-schema-check{display:flex!important;flex-direction:row!important;align-items:center;gap:6px!important}.workflow-schema-check input{width:auto}.workflow-node-cost{margin-left:4px;color:#78d9b0}.workflow-hint{margin:0;color:#666e83;font-size:9px;line-height:1.35}.workflow-scene-preview{position:relative;display:flex;align-items:center;justify-content:center;height:80px;overflow:hidden;border:1px solid #3d4357;border-radius:6px;background:#171a23;color:#7b849e;font-size:10px}.workflow-scene-grid{position:absolute;inset:0;opacity:.32;background:linear-gradient(135deg,transparent 46%,#687090 47%,transparent 49%),linear-gradient(45deg,transparent 46%,#687090 47%,transparent 49%);background-size:22px 22px}.workflow-scene-preview span{position:relative}.workflow-scene-controls{display:flex;gap:4px}.workflow-scene-controls button{flex:1;border:1px solid #3b4050;border-radius:4px;background:#292d39;color:#9da5ba;padding:5px 2px;font:inherit;font-size:9px;cursor:pointer}.workflow-handle{width:9px;height:9px;border:2px solid #20232d;background:#8994ff}.workflow-handle.target{background:#4c566f}.workflow-node-image .workflow-handle{background:#44c2a4}.workflow-node-video .workflow-handle{background:#d9955b}.workflow-node-scene .workflow-handle{background:#ef759d}.workflow-node-upload .workflow-handle{background:#a88cdb}.workflow-video-preview{display:block;width:100%;max-height:150px;border:1px solid #3d4357;border-radius:6px;background:#11141c}.workflow-provider-hint{margin-top:-3px;color:#737b92;font-size:9px}
2
3
  .workflow-node-search{width:100%;margin:0 0 8px;padding:8px 9px;border:1px solid #353a4a;border-radius:6px;background:#11141b;color:#e5e8f5;font:inherit;font-size:11px;outline:0}.workflow-node-search:focus{border-color:#707dce}.workflow-model-results{display:flex;flex-direction:column;gap:2px;margin:0 0 7px;padding:4px;border:1px solid #303545;border-radius:6px;background:#12151d}.workflow-model-results .workflow-hint{padding:6px}
3
4
  .workflow-node-motion-input .workflow-node-icon{color:#79b5ed}.workflow-motion-upload{height:70px}.workflow-node-motion-input .workflow-handle{background:#79b5ed}
@@ -9,3 +10,4 @@
9
10
  .workflow-node-shot-prompt .workflow-node-icon{color:#8fd4b0}.workflow-node-shot-prompt .workflow-handle{background:#8fd4b0}.workflow-shot-prompt-text{height:92px;font:11px ui-monospace,monospace;color:#cfe6da}.workflow-shot-prompt-copy{display:flex;align-items:center;justify-content:center;gap:5px;border:1px solid #4c8f7c;border-radius:5px;background:#25443c;color:#a9e6c8;padding:5px 8px;font:inherit;font-size:10px;cursor:pointer}.workflow-shot-prompt-copy:hover{border-color:#6fc0a2;background:#2e564b}
10
11
 
11
12
  .workflow-warning{margin:0;padding:6px 7px;border:1px solid #8d6b3d;border-radius:5px;background:#2b2419;color:#e8c78c;font-size:9px;line-height:1.35}
13
+ }
@@ -37,9 +37,9 @@
37
37
  import { createServer } from "node:http";
38
38
  import { spawn } from "node:child_process";
39
39
  import { randomBytes } from "node:crypto";
40
- import { createReadStream, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
40
+ import { createReadStream, existsSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
41
41
  import { basename, dirname, join, resolve, sep } from "node:path";
42
- import { fileURLToPath } from "node:url";
42
+ import { fileURLToPath, pathToFileURL } from "node:url";
43
43
  import { decodeMotionNpz } from "../../src/ardy/npz.js";
44
44
  import { motionArraysToNpzMembers, replaceMotionSegment, writeNpz } from "./npz.mjs";
45
45
  import { globalChildren, killGroup, runStreaming, track } from "./runners/proc.mjs";
@@ -781,6 +781,16 @@ function registerMotion(runId, absPath) {
781
781
  }
782
782
  }
783
783
 
784
+ // The Studio agent runtime pins the artifact URL to this bridge's own origin
785
+ // (bin/agent/motion-runtime.mjs), so the editor page on the dev/launcher port
786
+ // fetches it cross-origin. Only loopback pages are granted read access.
787
+ const LOOPBACK_ORIGIN = /^http:\/\/(127\.0\.0\.1|localhost|\[::1\]):\d{1,5}$/;
788
+
789
+ /** CORS headers for a generated-motion download requested from `origin`. */
790
+ export function motionCorsHeaders(origin) {
791
+ return LOOPBACK_ORIGIN.test(origin ?? "") ? { "Access-Control-Allow-Origin": origin } : {};
792
+ }
793
+
784
794
  // Serves /ardy/motions/<run-id>; returns the HTTP status to log.
785
795
  function serveMotion(req, res, pathname) {
786
796
  const match = /^\/ardy\/motions\/([^/]+)$/.exec(pathname);
@@ -813,6 +823,8 @@ function serveMotion(req, res, pathname) {
813
823
  "Content-Length": size,
814
824
  "Content-Disposition": `attachment; filename="${basename(absPath)}"`,
815
825
  "Cache-Control": "no-store",
826
+ Vary: "Origin",
827
+ ...motionCorsHeaders(req.headers.origin),
816
828
  });
817
829
  createReadStream(absPath)
818
830
  .on("error", (err) => {
@@ -1559,156 +1571,162 @@ if (process.env.COZYCLAY_BRIDGE_PORT !== undefined) {
1559
1571
  return DEFAULT_PORT;
1560
1572
  }
1561
1573
 
1562
- const port = resolvePort(process.argv.slice(2));
1563
- try {
1564
- runner = createRunner();
1565
- } catch (err) {
1566
- die(err.message);
1567
- }
1568
- // Beside the selected backend, never instead of it. A missing ProjFlow
1569
- // configuration is NOT fatal — the bridge's other run modes do not need it —
1570
- // so the failure is reported once at startup and the capability stays off.
1571
- try {
1572
- projflowRunner = createProjflowRunner();
1573
- } catch (err) {
1574
- console.error(`[bridge] line editing is unavailable: ${err.message}`);
1575
- }
1574
+ // Importing the module (tests) must not bind a port or pick a runner.
1575
+ function startBridge() {
1576
+ const port = resolvePort(process.argv.slice(2));
1577
+ try {
1578
+ runner = createRunner();
1579
+ } catch (err) {
1580
+ die(err.message);
1581
+ }
1582
+ // Beside the selected backend, never instead of it. A missing ProjFlow
1583
+ // configuration is NOT fatal — the bridge's other run modes do not need it —
1584
+ // so the failure is reported once at startup and the capability stays off.
1585
+ try {
1586
+ projflowRunner = createProjflowRunner();
1587
+ } catch (err) {
1588
+ console.error(`[bridge] line editing is unavailable: ${err.message}`);
1589
+ }
1576
1590
 
1577
- const server = createServer((req, res) => {
1578
- const started = Date.now();
1579
- const pathname = (req.url || "/").split("?")[0];
1580
- const log = (status) => {
1581
- console.log(`[bridge] ${req.method} ${pathname} -> ${status} (${Date.now() - started} ms)`);
1582
- };
1591
+ const server = createServer((req, res) => {
1592
+ const started = Date.now();
1593
+ const pathname = (req.url || "/").split("?")[0];
1594
+ const log = (status) => {
1595
+ console.log(`[bridge] ${req.method} ${pathname} -> ${status} (${Date.now() - started} ms)`);
1596
+ };
1583
1597
 
1584
- if (req.method === "OPTIONS") {
1585
- // 204 with NO CORS headers on purpose: a cross-origin browser
1586
- // preflight must fail, and the same-origin Vite proxy never
1587
- // preflights the bridge (it forwards server-side).
1588
- res.writeHead(204);
1589
- res.end();
1590
- log(204);
1591
- return;
1592
- }
1593
- if (pathname === "/ardy/health" && req.method === "GET") {
1594
- // `capabilities` is how the app learns that an OPTIONAL run mode is wired
1595
- // on this box. The draw-a-line affordance is gated on capabilities.lineEdit
1596
- // (App.jsx accepts the object or an array spelling), so a bridge whose
1597
- // ProjFlow env is missing keeps the feature dark instead of letting the
1598
- // artist draw a stroke that would 503. Probed, never assumed.
1599
- Promise.all([getHealth(), getLineEditCapability()])
1600
- .then(([value, lineEdit]) => {
1601
- sendJson(res, 200, {
1602
- ...value,
1603
- extractionBackend: EXTRACT_BACKEND_SUPPORTED ? EXTRACT_BACKEND : "unsupported",
1604
- capabilities: { lineEdit, extractionBackend: EXTRACT_BACKEND_SUPPORTED ? EXTRACT_BACKEND : "unsupported" },
1598
+ if (req.method === "OPTIONS") {
1599
+ // 204 with NO CORS headers on purpose: a cross-origin browser
1600
+ // preflight must fail, and the same-origin Vite proxy never
1601
+ // preflights the bridge (it forwards server-side).
1602
+ res.writeHead(204);
1603
+ res.end();
1604
+ log(204);
1605
+ return;
1606
+ }
1607
+ if (pathname === "/ardy/health" && req.method === "GET") {
1608
+ // `capabilities` is how the app learns that an OPTIONAL run mode is wired
1609
+ // on this box. The draw-a-line affordance is gated on capabilities.lineEdit
1610
+ // (App.jsx accepts the object or an array spelling), so a bridge whose
1611
+ // ProjFlow env is missing keeps the feature dark instead of letting the
1612
+ // artist draw a stroke that would 503. Probed, never assumed.
1613
+ Promise.all([getHealth(), getLineEditCapability()])
1614
+ .then(([value, lineEdit]) => {
1615
+ sendJson(res, 200, {
1616
+ ...value,
1617
+ extractionBackend: EXTRACT_BACKEND_SUPPORTED ? EXTRACT_BACKEND : "unsupported",
1618
+ capabilities: { lineEdit, extractionBackend: EXTRACT_BACKEND_SUPPORTED ? EXTRACT_BACKEND : "unsupported" },
1619
+ });
1620
+ log(200);
1621
+ })
1622
+ .catch((err) => {
1623
+ // The selected Kimodo route is configured even when its probe fails;
1624
+ // expose only the safe backend bucket and boolean configuration state.
1625
+ sendJson(res, 503, {
1626
+ ok: false,
1627
+ backend: "local_kimodo",
1628
+ host_configured: Boolean(process.env.CCLAY_KIMODO_HOST?.trim()),
1629
+ reason: err.message,
1630
+ });
1631
+ log(503);
1605
1632
  });
1606
- log(200);
1607
- })
1608
- .catch((err) => {
1609
- // The selected Kimodo route is configured even when its probe fails;
1610
- // expose only the safe backend bucket and boolean configuration state.
1611
- sendJson(res, 503, {
1612
- ok: false,
1613
- backend: "local_kimodo",
1614
- host_configured: Boolean(process.env.CCLAY_KIMODO_HOST?.trim()),
1615
- reason: err.message,
1633
+ return;
1634
+ }
1635
+ if (pathname === "/ardy/bases" && req.method === "GET") {
1636
+ getBases()
1637
+ .then((value) => {
1638
+ sendJson(res, 200, value);
1639
+ log(200);
1640
+ })
1641
+ .catch((err) => {
1642
+ sendJson(res, 503, { ok: false, reason: err.message });
1643
+ log(503);
1616
1644
  });
1617
- log(503);
1618
- });
1619
- return;
1620
- }
1621
- if (pathname === "/ardy/bases" && req.method === "GET") {
1622
- getBases()
1623
- .then((value) => {
1624
- sendJson(res, 200, value);
1625
- log(200);
1626
- })
1627
- .catch((err) => {
1628
- sendJson(res, 503, { ok: false, reason: err.message });
1629
- log(503);
1630
- });
1631
- return;
1632
- }
1633
- if (pathname === "/ardy/generate" && req.method === "POST") {
1634
- handleGenerate(req, res).catch((err) => respondInternalError(res, err, `${req.method} ${pathname}`));
1635
- return;
1636
- }
1637
- if (/^\/ardy\/motions\//.test(pathname) && req.method === "GET") {
1638
- log(serveMotion(req, res, pathname));
1639
- return;
1640
- }
1641
- if (pathname === "/ardy/footage" && req.method === "POST") {
1642
- handleFootage(req, res, (request) => readBody(request, MAX_BODY_BYTES)).catch((err) =>
1643
- respondInternalError(res, err, `${req.method} ${pathname}`),
1644
- );
1645
- return;
1646
- }
1647
- if (/^\/ardy\/footage\//.test(pathname) && req.method === "GET") {
1648
- log(serveFootage(req, res, pathname));
1649
- return;
1650
- }
1651
- if (pathname === "/ardy/extract" && req.method === "POST") {
1652
- handleExtract(req, res, {
1653
- readBody: (request) => readBody(request, MAX_BODY_BYTES),
1654
- footagePath,
1655
- registerMotion,
1656
- artifactRoot: OUT_DIR,
1657
- }).catch((err) => respondInternalError(res, err, `${req.method} ${pathname}`));
1658
- return;
1659
- }
1660
- if (
1661
- pathname === "/ardy/health" ||
1662
- pathname === "/ardy/bases" ||
1663
- pathname === "/ardy/generate" ||
1664
- pathname === "/ardy/footage" ||
1665
- pathname === "/ardy/extract" ||
1666
- /^\/ardy\/motions\//.test(pathname) ||
1667
- /^\/ardy\/footage\//.test(pathname)
1668
- ) {
1669
- sendJson(res, 405, { ok: false, reason: `method ${req.method} not allowed on ${pathname}` });
1670
- log(405);
1671
- return;
1672
- }
1673
- sendJson(res, 404, { ok: false, reason: `not found: ${req.method} ${pathname}` });
1674
- log(404);
1675
- });
1676
-
1677
- server.on("clientError", (err, socket) => {
1678
- if (socket.writable) {
1679
- socket.end("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
1680
- }
1681
- console.error(`[bridge] client error: ${err.message}`);
1682
- });
1645
+ return;
1646
+ }
1647
+ if (pathname === "/ardy/generate" && req.method === "POST") {
1648
+ handleGenerate(req, res).catch((err) => respondInternalError(res, err, `${req.method} ${pathname}`));
1649
+ return;
1650
+ }
1651
+ if (/^\/ardy\/motions\//.test(pathname) && req.method === "GET") {
1652
+ log(serveMotion(req, res, pathname));
1653
+ return;
1654
+ }
1655
+ if (pathname === "/ardy/footage" && req.method === "POST") {
1656
+ handleFootage(req, res, (request) => readBody(request, MAX_BODY_BYTES)).catch((err) =>
1657
+ respondInternalError(res, err, `${req.method} ${pathname}`),
1658
+ );
1659
+ return;
1660
+ }
1661
+ if (/^\/ardy\/footage\//.test(pathname) && req.method === "GET") {
1662
+ log(serveFootage(req, res, pathname));
1663
+ return;
1664
+ }
1665
+ if (pathname === "/ardy/extract" && req.method === "POST") {
1666
+ handleExtract(req, res, {
1667
+ readBody: (request) => readBody(request, MAX_BODY_BYTES),
1668
+ footagePath,
1669
+ registerMotion,
1670
+ artifactRoot: OUT_DIR,
1671
+ }).catch((err) => respondInternalError(res, err, `${req.method} ${pathname}`));
1672
+ return;
1673
+ }
1674
+ if (
1675
+ pathname === "/ardy/health" ||
1676
+ pathname === "/ardy/bases" ||
1677
+ pathname === "/ardy/generate" ||
1678
+ pathname === "/ardy/footage" ||
1679
+ pathname === "/ardy/extract" ||
1680
+ /^\/ardy\/motions\//.test(pathname) ||
1681
+ /^\/ardy\/footage\//.test(pathname)
1682
+ ) {
1683
+ sendJson(res, 405, { ok: false, reason: `method ${req.method} not allowed on ${pathname}` });
1684
+ log(405);
1685
+ return;
1686
+ }
1687
+ sendJson(res, 404, { ok: false, reason: `not found: ${req.method} ${pathname}` });
1688
+ log(404);
1689
+ });
1683
1690
 
1684
- server.listen(port, BIND_HOST, () => {
1685
- const address = server.address();
1686
- const boundPort = typeof address === "object" && address ? address.port : port;
1687
- process.send?.({ type: "cozyclay-bridge-ready", port: boundPort });
1688
- console.log(`[bridge] motion dev bridge listening on http://${BIND_HOST}:${boundPort}`);
1689
- console.log("[bridge] dev-only sidecar: the static dist/ build does not need it; stop with Ctrl-C");
1690
- console.log(`[bridge] ${runner.mode} backend: ${runner.describe()}`);
1691
- });
1691
+ server.on("clientError", (err, socket) => {
1692
+ if (socket.writable) {
1693
+ socket.end("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
1694
+ }
1695
+ console.error(`[bridge] client error: ${err.message}`);
1696
+ });
1692
1697
 
1693
- server.on("error", (err) => {
1694
- const override = "set COZYCLAY_BRIDGE_PORT to a free port or pass --port <n>";
1695
- console.error(
1696
- `[bridge] cannot listen on ${BIND_HOST}:${port}: ${err.message}` +
1697
- (err?.code === "EADDRINUSE" ? `; ${override}` : ""),
1698
- );
1699
- if (process.send && process.connected) {
1700
- process.send({ type: "cozyclay-bridge-listen-error", port, code: err?.code }, () => process.exit(1));
1701
- } else {
1702
- process.exit(1);
1703
- }
1704
- });
1698
+ server.listen(port, BIND_HOST, () => {
1699
+ const address = server.address();
1700
+ const boundPort = typeof address === "object" && address ? address.port : port;
1701
+ process.send?.({ type: "cozyclay-bridge-ready", port: boundPort });
1702
+ console.log(`[bridge] motion dev bridge listening on http://${BIND_HOST}:${boundPort}`);
1703
+ console.log("[bridge] dev-only sidecar: the static dist/ build does not need it; stop with Ctrl-C");
1704
+ console.log(`[bridge] ${runner.mode} backend: ${runner.describe()}`);
1705
+ });
1705
1706
 
1706
- // Ctrl-C / SIGTERM: take the in-flight process groups down with us; ssh dies,
1707
- // sshd closes the session, and the remote generation is not orphaned.
1708
- for (const signal of ["SIGINT", "SIGTERM"]) {
1709
- process.on(signal, () => {
1710
- console.error(`[bridge] received ${signal}; killing ${globalChildren.size} in-flight child process group(s)`);
1711
- for (const child of globalChildren) killGroup(child);
1712
- process.exit(signal === "SIGINT" ? 130 : 143);
1707
+ server.on("error", (err) => {
1708
+ const override = "set COZYCLAY_BRIDGE_PORT to a free port or pass --port <n>";
1709
+ console.error(
1710
+ `[bridge] cannot listen on ${BIND_HOST}:${port}: ${err.message}` +
1711
+ (err?.code === "EADDRINUSE" ? `; ${override}` : ""),
1712
+ );
1713
+ if (process.send && process.connected) {
1714
+ process.send({ type: "cozyclay-bridge-listen-error", port, code: err?.code }, () => process.exit(1));
1715
+ } else {
1716
+ process.exit(1);
1717
+ }
1713
1718
  });
1719
+
1720
+ // Ctrl-C / SIGTERM: take the in-flight process groups down with us; ssh dies,
1721
+ // sshd closes the session, and the remote generation is not orphaned.
1722
+ for (const signal of ["SIGINT", "SIGTERM"]) {
1723
+ process.on(signal, () => {
1724
+ console.error(`[bridge] received ${signal}; killing ${globalChildren.size} in-flight child process group(s)`);
1725
+ for (const child of globalChildren) killGroup(child);
1726
+ process.exit(signal === "SIGINT" ? 130 : 143);
1727
+ });
1728
+ }
1714
1729
  }
1730
+
1731
+ const isEntry = Boolean(process.argv[1]) && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href;
1732
+ if (isEntry) startBridge();
@@ -78,7 +78,7 @@ await sleep(6000);
78
78
 
79
79
  // Prompt Blocks replaced the old single "Generate motion" field. Select the
80
80
  // first character, open the Prompt Blocks foldout, then add a block before
81
- // looking for its prompt input and batch Generate action.
81
+ // looking for its prompt input and the top-bar Generate Motion action.
82
82
  await evaluate(tab, `(() => {
83
83
  const character = [...document.querySelectorAll('.hierarchy-row')].find((row) => /Character 1|Subject 1|인물 1/.test(row.textContent));
84
84
  character?.click();
@@ -92,8 +92,8 @@ await sleep(400);
92
92
  const state0 = await evaluate(tab, `(() => {
93
93
  const seed = document.querySelector('input[placeholder="empty = random"]');
94
94
  const promptEl = document.querySelector('input[placeholder*="motion block"], input[placeholder*="모션 블록"]');
95
- const gen = document.querySelector('button.prompt-block-generate');
96
- return { seedValue: seed?.value ?? null, hasGenerate: !!gen, generateDisabled: !!gen?.disabled, hasPrompt: !!promptEl };
95
+ const gen = document.querySelector('[data-testid=topbar-generate]');
96
+ return { seedValue: seed?.value ?? null, hasGenerate: !!gen, generateDisabled: gen?.getAttribute('aria-disabled') === 'true', hasPrompt: !!promptEl };
97
97
  })()`);
98
98
  console.log("initial:", JSON.stringify(state0));
99
99
 
@@ -119,8 +119,8 @@ for (const key of ["a", " ", "p", "e", "r", "s", "o", "n", " ", "w", "a", "l", "
119
119
  await sleep(300);
120
120
  await shot(tab, "01-before-generate");
121
121
  const clicked = await evaluate(tab, `(() => {
122
- const gen = document.querySelector('button.prompt-block-generate');
123
- if (!gen || gen.disabled) return false;
122
+ const gen = document.querySelector('[data-testid=topbar-generate]');
123
+ if (!gen || gen.getAttribute('aria-disabled') === 'true') return false;
124
124
  gen.click();
125
125
  return true;
126
126
  })()`);