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
@@ -2,7 +2,7 @@
2
2
  import { spawn } from "node:child_process";
3
3
  import { mkdtempSync, readdirSync, rmSync } from "node:fs";
4
4
  import { createRequire } from "node:module";
5
- import { tmpdir } from "node:os";
5
+ import { availableParallelism, tmpdir } from "node:os";
6
6
  import { join, relative } from "node:path";
7
7
 
8
8
  // Whatever a suite drives through the agent sidecar lands in a scratch
@@ -14,7 +14,10 @@ if (!process.env.COZYCLAY_AGENT_SESSIONS_DIR) {
14
14
  }
15
15
 
16
16
  const NODE_FILES = [
17
+ "test/verify-morphgs-exporter.mjs",
17
18
  "test/verify-studio-elements.mjs",
19
+ "test/bus/verify-view-mode-pose.mjs",
20
+ "test/verify-studio-actions.mjs",
18
21
  "test/ardy/verify-base-free.mjs",
19
22
  "test/ardy/verify-browser-motion.mjs",
20
23
  "test/ardy/verify-collision-blockers.mjs",
@@ -26,11 +29,34 @@ const NODE_FILES = [
26
29
  "test/ardy/verify-motion-calibration.mjs",
27
30
  "test/ardy/verify-palette-segmentation.mjs",
28
31
  "test/verify-motion-stabilize.mjs",
29
- "test/verify-fal-motion-prompt.mjs",
32
+ "test/verify-i2v-motion-prompt.mjs",
30
33
  "test/verify-facing-marks.mjs",
31
34
  "test/ardy/verify-gvhmr-floor.mjs",
32
35
  "test/verify-gvhmr-detector-flag.mjs",
33
36
  "test/verify-gvhmr-only.mjs",
37
+ "test/verify-extract-bench.mjs",
38
+ "test/verify-fit-bench.mjs",
39
+ "test/verify-obs-extrinsics.mjs",
40
+ "test/verify-obs-depth.mjs",
41
+ "test/verify-obs-ground.mjs",
42
+ "test/verify-obs-betas.mjs",
43
+ "test/verify-obs-remote.mjs",
44
+ "test/verify-track-run-box.mjs",
45
+ "test/verify-track-gate.mjs",
46
+ "test/verify-track-backfill.mjs",
47
+ "test/verify-track-remote.mjs",
48
+ "test/verify-track-metrics.mjs",
49
+ "test/verify-track-study-2d.mjs",
50
+ "test/verify-track-masks.mjs",
51
+ "test/verify-obs-ladder.mjs",
52
+ "test/verify-obs-isolation.mjs",
53
+ "test/verify-obs-bench-t1.mjs",
54
+ "test/verify-track-rig-dump.mjs",
55
+ "test/verify-scene-continuity.mjs",
56
+ "test/verify-heading-unwrap.mjs",
57
+ "test/verify-scene-ray.mjs",
58
+ "test/verify-footlock.mjs",
59
+ "test/verify-cube-contact.mjs",
34
60
  "test/ardy/verify-key-runs.mjs",
35
61
  "test/ardy/verify-playback-skinning.mjs",
36
62
  "test/ardy/verify-playback-clock.mjs",
@@ -42,8 +68,10 @@ const NODE_FILES = [
42
68
  "test/ardy/verify-root-drop.mjs",
43
69
  "test/ardy/verify-surface-rise.mjs",
44
70
  "test/ardy/verify-secure-artifacts.mjs",
71
+ "test/ardy/verify-motion-cors.mjs",
45
72
  "test/ardy/verify-timeline-coordinates.mjs",
46
73
  "test/ardy/verify-timeline-resize.mjs",
74
+ "test/verify-tokens.mjs",
47
75
  "test/demo/verify-demo-pages.mjs",
48
76
  "test/demo/verify-motion-api.mjs",
49
77
  "test/demo/verify-motion-url-allowlist.mjs",
@@ -57,10 +85,19 @@ const NODE_FILES = [
57
85
  "test/ik/verify-ground.mjs",
58
86
  "test/ik/verify-auto-physics.mjs",
59
87
  "test/ik/verify-physics-review.mjs",
88
+ "test/ik/verify-platform-fit.mjs",
60
89
  "test/ik/verify-physics-support.mjs",
61
90
  "test/ik/verify-physics-surface.mjs",
62
91
  "test/ik/verify-ik.mjs",
92
+ "test/ik/verify-knee-side.mjs",
93
+ "test/ik/translation-step.mjs",
94
+ "test/ik/verify-ik-drag-delta.mjs",
95
+ "test/ik/verify-ik-blend-range.mjs",
96
+ "test/ik/verify-range-pin.mjs",
97
+ "test/ik/verify-range-pin-ui.mjs",
98
+ "test/ik/verify-range-pin-object-transform.mjs",
63
99
  "test/verify-ik-camera-performance.mjs",
100
+ "test/verify-ik-entry-camera.mjs",
64
101
  "test/process/verify-bridge-launch.mjs",
65
102
  "test/process/verify-lifecycle.mjs",
66
103
  "test/process/verify-mcp-package-isolation.mjs",
@@ -70,6 +107,7 @@ const NODE_FILES = [
70
107
  "test/verify-agent-panel.mjs",
71
108
  "test/verify-agent-host.mjs",
72
109
  "test/verify-agent-routes.mjs",
110
+ "test/verify-agent-live-runtime.mjs",
73
111
  "test/verify-agent-runner-errors.mjs",
74
112
  "test/verify-agent-steer.mjs",
75
113
  "test/verify-pi-tools.mjs",
@@ -123,6 +161,13 @@ const NODE_FILES = [
123
161
  "test/verify-kimodo-runner.mjs",
124
162
  "test/verify-kimodo-setup.mjs",
125
163
  "test/verify-motion-trail.mjs",
164
+ "test/verify-motion-trail-limb.mjs",
165
+ "test/verify-trail-key-conflicts.mjs",
166
+ "test/verify-motion-trail-orientation.mjs",
167
+ "test/verify-trail-pick.mjs",
168
+ "test/verify-trail-rendered-geometry.mjs",
169
+ "test/verify-motion-trail-overreach.mjs",
170
+ "test/verify-motion-trail-persistence.mjs",
126
171
  "test/verify-kimodo-waypoints.mjs",
127
172
  "test/verify-keyframe-pack.mjs",
128
173
  "test/verify-keyframe-pack-request.mjs",
@@ -139,9 +184,12 @@ const NODE_FILES = [
139
184
  "test/verify-matte.mjs",
140
185
  "test/verify-model-presets.mjs",
141
186
  "test/verify-mp4-duration.mjs",
187
+ "test/verify-gt-render-camera.mjs",
188
+ "test/verify-bench-metrics.mjs",
142
189
  "test/verify-mcp-invariants.mjs",
143
190
  "test/verify-motion-edit.mjs",
144
191
  "test/verify-motion-readiness.mjs",
192
+ "test/verify-demo-seed.mjs",
145
193
  "test/verify-motion-readiness-ui.mjs",
146
194
  "test/verify-multimodel-ingest.mjs",
147
195
  "test/verify-offscreen-export.mjs",
@@ -176,6 +224,7 @@ const NODE_FILES = [
176
224
  "test/verify-studio-agent-motion.mjs",
177
225
  "test/verify-studio-agent-binding.mjs",
178
226
  "test/verify-studio-undo-hygiene.mjs",
227
+ "test/verify-studio-contact-sheet.mjs",
179
228
  "test/verify-reference-slots.mjs",
180
229
  "test/verify-scenes.mjs",
181
230
  "test/verify-cozy-scene-node.mjs",
@@ -247,6 +296,7 @@ const BROWSER_FILES = [
247
296
  "test/verify-static-shot-export-browser.mjs",
248
297
  "test/qa-agent-view-toggle-browser.mjs",
249
298
  "test/qa-studio-agent-browser.mjs",
299
+ "test/qa-agent-scenarios-browser.mjs",
250
300
  "test/qa-camera-tutorial-browser.mjs",
251
301
  "test/qa-first-shot-handoff-browser.mjs",
252
302
  "test/qa-tutorial-analytics-browser.mjs",
@@ -263,12 +313,13 @@ const BROWSER_FILES = [
263
313
  "test/qa-execution-outcomes-browser.mjs",
264
314
  "test/qa-view-menu-browser.mjs",
265
315
  "test/qa-agent-activity-browser.mjs",
316
+ "test/qa-platform-fit-browser.mjs",
266
317
  ];
267
318
 
268
319
  // The inventory sweep only picks up `verify*.mjs`; a browser suite named for
269
320
  // the QA runner it needs is listed here so it still shows up in the manifest
270
321
  // (as an EXCLUDE with its reason) instead of going unmentioned.
271
- const EXTRA_INVENTORY = ["test/qa-studio-agent-browser.mjs", "test/qa-execution-outcomes-browser.mjs", "test/qa-motion-readiness-browser.mjs", "test/qa-export-recovery-browser.mjs", "test/qa-agent-view-toggle-browser.mjs", "test/qa-camera-tutorial-browser.mjs", "test/qa-first-shot-handoff-browser.mjs", "test/qa-tutorial-analytics-browser.mjs", "test/qa-camera-pointer-lock-browser.mjs", "test/qa-first-edit-browser.mjs", "test/qa-ia-tail-browser.mjs", "test/qa-keyframe-pack-browser.mjs", "test/qa-preview-browser.mjs", "test/qa-project-resources-browser.mjs", "test/qa-reference-slots-browser.mjs", "test/qa-scene-playback-browser.mjs", "test/qa-scene-switcher-browser.mjs", "test/qa-send-to-ai-browser.mjs", "test/qa-view-menu-browser.mjs", "test/qa-agent-activity-browser.mjs", "test/qa-live-reconnect-browser.mjs"];
322
+ const EXTRA_INVENTORY = ["test/ik/translation-step.mjs", "test/qa-studio-agent-browser.mjs", "test/qa-agent-scenarios-browser.mjs", "test/qa-execution-outcomes-browser.mjs", "test/qa-motion-readiness-browser.mjs", "test/qa-export-recovery-browser.mjs", "test/qa-agent-view-toggle-browser.mjs", "test/qa-camera-tutorial-browser.mjs", "test/qa-first-shot-handoff-browser.mjs", "test/qa-tutorial-analytics-browser.mjs", "test/qa-camera-pointer-lock-browser.mjs", "test/qa-first-edit-browser.mjs", "test/qa-ia-tail-browser.mjs", "test/qa-keyframe-pack-browser.mjs", "test/qa-preview-browser.mjs", "test/qa-project-resources-browser.mjs", "test/qa-reference-slots-browser.mjs", "test/qa-scene-playback-browser.mjs", "test/qa-scene-switcher-browser.mjs", "test/qa-send-to-ai-browser.mjs", "test/qa-view-menu-browser.mjs", "test/qa-agent-activity-browser.mjs", "test/qa-live-reconnect-browser.mjs", "test/qa-platform-fit-browser.mjs"];
272
323
 
273
324
  function verificationFiles(directory) {
274
325
  return readdirSync(directory, { withFileTypes: true })
@@ -280,9 +331,13 @@ function verificationFiles(directory) {
280
331
  .sort();
281
332
  }
282
333
 
334
+ // Every suite reserves its own ports and writes under a temp dir, so files
335
+ // are independent of each other; running them serially cost CI five minutes
336
+ // (#413). `--jobs 1` restores the old interleaved live output for debugging.
283
337
  function parseArguments(arguments_) {
284
338
  let listOnly = false;
285
339
  let scope = "all";
340
+ let jobs = Number(process.env.COZYCLAY_TEST_JOBS) || availableParallelism();
286
341
  for (let index = 0; index < arguments_.length; index += 1) {
287
342
  const argument = arguments_[index];
288
343
  if (argument === "--list") {
@@ -294,23 +349,81 @@ function parseArguments(arguments_) {
294
349
  index += 1;
295
350
  continue;
296
351
  }
352
+ if (argument === "--jobs") {
353
+ jobs = Number(arguments_[index + 1]);
354
+ index += 1;
355
+ continue;
356
+ }
297
357
  throw new Error(`unknown argument: ${argument}`);
298
358
  }
299
359
  if (scope !== "all" && scope !== "ardy") throw new Error(`unknown test scope: ${scope}`);
300
- return { listOnly, scope };
360
+ if (!Number.isInteger(jobs) || jobs < 1) throw new Error(`--jobs must be a positive integer, got ${jobs}`);
361
+ return { listOnly, scope, jobs };
301
362
  }
302
363
 
303
- function run(file) {
364
+ // One file per child. With more than one job the output is buffered per file
365
+ // and replayed as a block when the file finishes, so PASS/FAIL lines of
366
+ // different suites never interleave.
367
+ function run(file, { buffered }) {
304
368
  return new Promise((resolve, reject) => {
305
- const child = spawn(process.execPath, [file], { stdio: "inherit" });
369
+ const chunks = [];
370
+ const child = spawn(process.execPath, [file], { stdio: buffered ? ["ignore", "pipe", "pipe"] : "inherit" });
371
+ if (buffered) {
372
+ child.stdout.on("data", (chunk) => chunks.push(chunk));
373
+ child.stderr.on("data", (chunk) => chunks.push(chunk));
374
+ }
306
375
  child.once("error", reject);
307
376
  child.once("exit", (code, signal) => {
377
+ if (buffered) process.stdout.write(`\nRUNNING ${file}\n${Buffer.concat(chunks)}`);
308
378
  if (code === 0) resolve();
309
379
  else reject(new Error(`${file} failed${signal ? ` with ${signal}` : ` with exit code ${code}`}`));
310
380
  });
311
381
  });
312
382
  }
313
383
 
384
+ // These suites reserve a port by binding :0, release it, and hand the number
385
+ // to a child that binds it again (or an adjacent one). Any concurrent suite
386
+ // can take it in between, so they run one at a time after the parallel wave
387
+ // instead of by luck. Everything else picks its port inside the process that
388
+ // keeps it.
389
+ const SERIAL_ONLY = new Set([
390
+ "test/process/verify-bridge-launch.mjs",
391
+ "mcp/verify-http-origin.mjs",
392
+ "mcp/verify-live-controller.mjs",
393
+ "mcp/verify-live-motion-job.mjs",
394
+ "mcp/verify-live-p0.mjs",
395
+ "mcp/verify-live-port.mjs",
396
+ "mcp/verify-live-routing.mjs",
397
+ ]);
398
+
399
+ // Longest files first so the tail of the run is not one slow suite on its own.
400
+ const SLOW_FIRST = [
401
+ "test/verify-studio-agent-motion.mjs",
402
+ "test/verify-studio-agent-binding.mjs",
403
+ "test/ardy/verify-gvhmr-floor.mjs",
404
+ "test/verify-part-colours.mjs",
405
+ "test/verify-agent-runner-errors.mjs",
406
+ ];
407
+
408
+ async function runAll(files, jobs) {
409
+ const parallel = files.filter((file) => !SERIAL_ONLY.has(file));
410
+ const queue = [...SLOW_FIRST.filter((file) => parallel.includes(file)), ...parallel.filter((file) => !SLOW_FIRST.includes(file))];
411
+ let failure = null;
412
+ const worker = async () => {
413
+ while (queue.length > 0 && !failure) {
414
+ const file = queue.shift();
415
+ try {
416
+ await run(file, { buffered: jobs > 1 });
417
+ } catch (error) {
418
+ failure ??= error;
419
+ }
420
+ }
421
+ };
422
+ await Promise.all(Array.from({ length: Math.min(jobs, queue.length) }, worker));
423
+ if (failure) throw failure;
424
+ for (const file of files.filter((file) => SERIAL_ONLY.has(file))) await run(file, { buffered: true });
425
+ }
426
+
314
427
  // node:sqlite's DatabaseSync only ships unflagged from Node 22.13.0 (it lived
315
428
  // behind --experimental-sqlite before that). package.json requires >=22.19,
316
429
  // but older 22.x installs are still common enough that the manifest should
@@ -346,6 +459,8 @@ function hasMcpRuntimeDeps() {
346
459
  }
347
460
 
348
461
  const mcpDepsInstalled = hasMcpRuntimeDeps();
462
+ // Starts the repository hub through mcp/live-hub.mjs, so it needs that tree too.
463
+ const MCP_DEPENDENT_NODE_FILES = new Set(["test/verify-agent-live-runtime.mjs"]);
349
464
 
350
465
  const categories = new Map([
351
466
  ...NODE_FILES.map((file) => [file, { kind: "node", reason: "runs directly under Node" }]),
@@ -367,7 +482,7 @@ const categories = new Map([
367
482
  ]);
368
483
  if (!mcpDepsInstalled) {
369
484
  for (const [file, category] of categories) {
370
- if (!file.startsWith("mcp/") || category.kind !== "node") continue;
485
+ if (!(file.startsWith("mcp/") || MCP_DEPENDENT_NODE_FILES.has(file)) || category.kind !== "node") continue;
371
486
  categories.set(file, {
372
487
  kind: "mcp-deps",
373
488
  reason: "requires the MCP server dependencies; run `npm --prefix mcp ci` (or `cd mcp && npm install`) first",
@@ -375,8 +490,11 @@ if (!mcpDepsInstalled) {
375
490
  }
376
491
  }
377
492
 
378
- const { listOnly, scope } = parseArguments(process.argv.slice(2));
493
+ const { listOnly, scope, jobs } = parseArguments(process.argv.slice(2));
379
494
  const inventory = [...verificationFiles("test"), ...verificationFiles("mcp"), ...EXTRA_INVENTORY].sort();
495
+ for (const file of inventory) {
496
+ if (/^test\/bus\/verify-.*\.mjs$/.test(file)) categories.set(file, { kind: "node", reason: "command bus contract verification" });
497
+ }
380
498
  const unclassified = inventory.filter((file) => !categories.has(file));
381
499
  const stale = [...categories.keys()].filter((file) => !inventory.includes(file));
382
500
  if (unclassified.length > 0 || stale.length > 0) {
@@ -388,16 +506,20 @@ if (unclassified.length > 0 || stale.length > 0) {
388
506
 
389
507
  const scoped = inventory.filter((file) => scope === "all" || file.startsWith("test/ardy/") || file.startsWith("test/ik/"));
390
508
  const runnable = scoped.filter((file) => categories.get(file).kind === "node");
391
- console.log(`TEST MANIFEST scope=${scope} runnable=${runnable.length} total=${scoped.length}`);
509
+ console.log(`TEST MANIFEST scope=${scope} runnable=${runnable.length} total=${scoped.length} jobs=${jobs}`);
392
510
  for (const file of scoped) {
393
511
  const { kind, reason } = categories.get(file);
394
512
  console.log(`${kind === "node" ? "RUN" : "EXCLUDE"} ${kind} ${file} - ${reason}`);
395
513
  }
396
514
 
397
515
  if (!listOnly) {
398
- for (const file of runnable) {
399
- console.log(`\nRUNNING ${file}`);
400
- await run(file);
516
+ if (jobs === 1) {
517
+ for (const file of runnable) {
518
+ console.log(`\nRUNNING ${file}`);
519
+ await run(file, { buffered: false });
520
+ }
521
+ } else {
522
+ await runAll(runnable, jobs);
401
523
  }
402
524
  console.log(`\nPASS ${runnable.length} Node verification files`);
403
525
  }