cozyclay 3.1.0 → 3.1.2

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 (154) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/bin/agent/agent-routes.mjs +12 -11
  3. package/bin/agent/main-agent.mjs +21 -7
  4. package/bin/agent/main-prompt.mjs +7 -6
  5. package/bin/agent/plan-tool.mjs +114 -0
  6. package/bin/agent/script-tool.mjs +4 -1
  7. package/bin/agent/sculpt-sketch.mjs +190 -0
  8. package/bin/agent/session-store.mjs +2 -1
  9. package/bin/agent/sketch-code.mjs +151 -0
  10. package/bin/agent/sketch-sandbox.mjs +102 -0
  11. package/bin/agent/studio-prompt.mjs +1 -1
  12. package/bin/agent/studio-rebase.mjs +5 -4
  13. package/bin/agent/studio-task-routes.mjs +1 -1
  14. package/bin/agent/studio-tasks.mjs +116 -25
  15. package/bin/agent/studio-tools.mjs +3 -2
  16. package/bin/agent/turn-frames.mjs +4 -0
  17. package/bin/cozyclay.mjs +4 -2
  18. package/bin/live-endpoint.mjs +2 -0
  19. package/dist/app/index.html +6 -6
  20. package/dist/assets/{TaskBoard-DdDXMgZm.js → TaskBoard-GR8xZm5c.js} +1 -1
  21. package/dist/assets/{app-BlwOpest.css → app-Bhe0TpGz.css} +1 -1
  22. package/dist/assets/app-C4QTcDcb.js +5144 -0
  23. package/dist/assets/compile-worker-oy_P9HHh.js +6 -0
  24. package/dist/assets/motion3-compile-dgGhrvPK.js +1 -0
  25. package/dist/assets/scene-asset-sync-BEjjhNEo.css +1 -0
  26. package/dist/assets/scene-asset-sync-qoxcdrhj.js +13 -0
  27. package/dist/assets/task-board-BqReHzOi.js +15 -0
  28. package/dist/assets/workflow-C87p6E1E.js +179 -0
  29. package/dist/cozyclay-package.json +1 -1
  30. package/dist/index.html +1 -1
  31. package/dist/sitemap.xml +1 -1
  32. package/dist/workflow/index.html +4 -4
  33. package/mcp/README.md +1 -1
  34. package/mcp/live-hub.mjs +5 -3
  35. package/mcp/tool-handlers.mjs +7 -7
  36. package/package.json +3 -1
  37. package/src/App.jsx +44 -66
  38. package/src/bus-task-scopes.js +59 -11
  39. package/src/bus-task-targets.js +35 -9
  40. package/src/bus-tasks.js +23 -10
  41. package/src/command-bus.js +7 -4
  42. package/src/commands/ai.js +1 -1
  43. package/src/commands/elements.js +4 -3
  44. package/src/commands/export.js +1 -1
  45. package/src/commands/motion3.js +27 -7
  46. package/src/commands/objects.js +1 -1
  47. package/src/export-workspace.js +244 -0
  48. package/src/keyframe-pack.js +5 -1
  49. package/src/live-control.js +14 -7
  50. package/src/material-look.js +189 -0
  51. package/src/material-presets.js +78 -0
  52. package/src/material-textures.js +157 -0
  53. package/src/motion3/compile-job.js +15 -3
  54. package/src/motion3/compiler/channels.js +74 -24
  55. package/src/motion3/compiler/compile.js +2 -1
  56. package/src/motion3/compiler/idle.js +2 -0
  57. package/src/motion3/compiler/overlap.js +180 -0
  58. package/src/motion3/compiler/pose.js +6 -4
  59. package/src/motion3/compiler/validate-common.js +1 -0
  60. package/src/motion3/compiler/validate-parts.js +5 -1
  61. package/src/motion3/compiler/validate.js +30 -10
  62. package/src/motion3/correct.js +236 -0
  63. package/src/motion3/docs.js +8 -6
  64. package/src/motion3/face-lean.js +31 -0
  65. package/src/motion3/fix/index.js +5 -1
  66. package/src/motion3/fix/reach-body.js +172 -0
  67. package/src/motion3/hint-check.js +44 -0
  68. package/src/motion3/json-patch.js +120 -12
  69. package/src/motion3/lenient.js +46 -4
  70. package/src/motion3/object-keys.js +17 -5
  71. package/src/motion3/pipeline.js +46 -9
  72. package/src/motion3/qa/arm-checks.js +96 -0
  73. package/src/motion3/qa/asked.js +113 -0
  74. package/src/motion3/qa/goals.js +37 -8
  75. package/src/motion3/qa/hints-goal.js +6 -5
  76. package/src/motion3/qa/index.js +5 -2
  77. package/src/motion3/qa/key-checks.js +4 -3
  78. package/src/motion3/qa/motion-life.js +243 -0
  79. package/src/motion3/qa/spec-checks.js +15 -9
  80. package/src/motion3/qa/verdict.js +50 -0
  81. package/src/motion3/refs.js +38 -14
  82. package/src/motion3/repair-watch.js +132 -0
  83. package/src/motion3/repair.js +52 -19
  84. package/src/motion3/report.js +50 -9
  85. package/src/motion3/schema/topics.js +6 -4
  86. package/src/motion3/schema/types.js +1 -1
  87. package/src/motion3/skills/basic.js +15 -8
  88. package/src/motion3/skills/index.js +52 -18
  89. package/src/motion3/skills/info.js +1 -1
  90. package/src/motion3/skills/stance.js +6 -0
  91. package/src/motion3/skills/walk-fit.js +170 -0
  92. package/src/object-look.js +9 -5
  93. package/src/panels/PoseImagePanel.jsx +3 -2
  94. package/src/panels/SculptPanel.jsx +2 -2
  95. package/src/primitive-look.js +38 -0
  96. package/src/props.jsx +20 -28
  97. package/src/render-passes.js +7 -1
  98. package/src/result-modal.jsx +3 -3
  99. package/src/scene-objects.js +2 -2
  100. package/src/sculpt-colours.js +65 -0
  101. package/src/sculpt-compare.js +13 -50
  102. package/src/sculpt-csg.js +112 -0
  103. package/src/sculpt-curve.js +123 -0
  104. package/src/sculpt-cutters.js +43 -0
  105. package/src/sculpt-detail.js +207 -0
  106. package/src/sculpt-geometry-loft-convex.js +100 -0
  107. package/src/sculpt-geometry-shapes.js +250 -0
  108. package/src/sculpt-geometry.js +33 -15
  109. package/src/sculpt-loft-rings.js +225 -0
  110. package/src/sculpt-noise.js +65 -0
  111. package/src/sculpt-part-fields.js +27 -0
  112. package/src/sculpt-recipe-read.js +65 -0
  113. package/src/sculpt-recipe.js +33 -75
  114. package/src/sculpt-run.js +1 -1
  115. package/src/sculpt-scatter-items.js +183 -0
  116. package/src/sculpt-scatter-shape.js +57 -0
  117. package/src/sculpt-scatter.js +124 -0
  118. package/src/sculpt-shapes-curved.js +211 -0
  119. package/src/sculpt-shapes-loft-convex.js +218 -0
  120. package/src/sculpt-shapes.js +138 -0
  121. package/src/sculpt-sketch-state.js +76 -0
  122. package/src/sculpt-sketch.jsx +124 -0
  123. package/src/sculpt-surface-fields.js +65 -0
  124. package/src/shell/ExportFolderPreference.jsx +103 -0
  125. package/src/shell/PreferencesDialog.jsx +3 -166
  126. package/src/shell/StatusBar.jsx +1 -1
  127. package/src/shell/preference-controls.jsx +114 -0
  128. package/src/shell/preference-pages.jsx +72 -0
  129. package/src/shell/preferences.css +46 -0
  130. package/src/shot-prompt.js +29 -9
  131. package/src/studio-actions.js +2 -2
  132. package/src/studio-agent-motion.js +19 -9
  133. package/src/studio-agent-protocol.js +1 -1
  134. package/src/studio-app-binding.js +3 -2
  135. package/src/studio-elements.js +5 -1
  136. package/src/workflow/AgentPanel.jsx +6 -6
  137. package/src/workflow/AgentPlanCard.jsx +41 -0
  138. package/src/workflow/CozySceneNode.jsx +1 -1
  139. package/src/workflow/WorkflowBuilder.jsx +2 -2
  140. package/src/workflow/agent-client.js +14 -11
  141. package/src/workflow/agent-plan.css +93 -0
  142. package/src/workflow/agent-plan.js +59 -0
  143. package/src/workflow/keyframe-pack-request.js +3 -10
  144. package/src/workflow/task-board.css +1 -1
  145. package/src/workflow/task-board.js +7 -5
  146. package/tools/loc-baseline.json +1 -2
  147. package/tools/run-tests.mjs +8 -8
  148. package/dist/assets/app-B2BpfCl2.js +0 -5088
  149. package/dist/assets/compile-worker-D0pKioa0.js +0 -5
  150. package/dist/assets/motion3-compile-B887n77J.js +0 -1
  151. package/dist/assets/scene-asset-sync-COGOlF5T.css +0 -1
  152. package/dist/assets/scene-asset-sync-CQzEdsqu.js +0 -13
  153. package/dist/assets/task-board-BKaM7d-9.js +0 -14
  154. package/dist/assets/workflow-B8Dhzexj.js +0 -179
@@ -0,0 +1,59 @@
1
+ // A turn's step checklist as the agent panel keeps it (AgentPlanCard.jsx draws it): the sidecar's plan tool
2
+ // (bin/agent/plan-tool.mjs) sends the whole list in every plan.update frame, and the chat store (agent-client.js) keeps
3
+ // one plan card per turn, right under the turn's prompt. The card never claims more than happened: when the turn ends,
4
+ // the step still under way counts as done only if the turn succeeded, and is left unfinished when it failed or stopped.
5
+ // Pure data, like agent-turn.js.
6
+
7
+ const STATUSES = new Set(["pending", "active", "done"]);
8
+ const MAX_STEPS = 12, TEXT_MAX = 80;
9
+
10
+ /** A frame's steps as the card keeps them, or null when they are not a plan. */
11
+ function planSteps(steps) {
12
+ if (!Array.isArray(steps) || !steps.length || steps.length > MAX_STEPS) return null;
13
+ const kept = steps.map(step => ({ text: String(step?.text ?? "").replace(/\s+/g, " ").trim().slice(0, TEXT_MAX), status: step?.status }));
14
+ return kept.every(step => step.text && STATUSES.has(step.status)) ? kept : null;
15
+ }
16
+
17
+ /** Whether a frame belongs to the plan: its update, or the plan tool's own call (the card shows it, never a step row). */
18
+ export const isPlanFrame = event => event?.type === "plan.update" || (event?.name === "plan" && ["tool.compose", "tool.start", "tool.progress"].includes(event?.type));
19
+
20
+ /** items after a plan frame: the turn's one plan card, under its prompt, takes the frame's list whole. */
21
+ export function withPlanFrame(items, event, newId) {
22
+ const steps = event?.type === "plan.update" ? planSteps(event.steps) : null;
23
+ if (!steps) return items;
24
+ const prompt = items.findLastIndex(item => item.kind === "user" && !item.steered);
25
+ const at = items.findIndex((item, i) => i > prompt && item.kind === "plan");
26
+ if (at !== -1) return items.map((item, i) => i === at ? { ...item, steps } : item);
27
+ return [...items.slice(0, prompt + 1), { kind: "plan", id: newId(), steps }, ...items.slice(prompt + 1)];
28
+ }
29
+
30
+ /** A plan whose turn ended ("done", "failed" or "stopped"): the step under way is done only when the turn succeeded. */
31
+ export function settledPlan(plan, status) {
32
+ const outcome = status === "done" || status === "stopped" ? status : "failed";
33
+ return { ...plan, outcome, steps: plan.steps.map(step => step.status === "active" ? { ...step, status: outcome === "done" ? "done" : "unfinished" } : step) };
34
+ }
35
+
36
+ /** items at the end of a turn: its plan card settles (settledPlan); earlier turns' cards already have. */
37
+ export const withPlanEnd = (items, status) => items.map(item => item.kind === "plan" && !item.outcome ? settledPlan(item, status) : item);
38
+
39
+ /**
40
+ * A restored transcript's plans (session-store.mjs lists one per change): each turn keeps its last list, under its
41
+ * prompt; the plan tool's own calls go; and the card settles by how the turn ended: failed when a failure follows, done
42
+ * when the turn ends on the reply, else stopped (the history ends mid-turn: the panel reloaded while it ran).
43
+ */
44
+ export function restoredPlans(items) {
45
+ const turns = [];
46
+ for (const item of items) {
47
+ if (item.kind === "tool" && item.name === "plan") continue;
48
+ if (item.kind === "user" || !turns.length) turns.push([]);
49
+ turns.at(-1).push(item);
50
+ }
51
+ return turns.flatMap(turn => {
52
+ const rest = turn.filter(item => item.kind !== "plan"), plan = turn.findLast(item => item.kind === "plan");
53
+ const steps = plan ? planSteps(plan.steps) : null;
54
+ if (!steps) return rest;
55
+ const status = rest.some(item => item.kind === "failure") ? "failed" : rest.at(-1)?.kind === "assistant" ? "done" : "stopped";
56
+ const prompt = rest[0]?.kind === "user" ? 1 : 0;
57
+ return [...rest.slice(0, prompt), settledPlan({ ...plan, steps }, status), ...rest.slice(prompt)];
58
+ });
59
+ }
@@ -8,6 +8,7 @@
8
8
  * window and the listener pair.
9
9
  */
10
10
  import { exportFailureCode } from "../analytics.js";
11
+ import { saveExport } from "../export-workspace.js";
11
12
 
12
13
  export const KEYFRAME_PACK_REQUEST = "cozyclay:export-keyframe-pack";
13
14
  export const KEYFRAME_PACK_RESULT = "cozyclay:export-keyframe-pack-result";
@@ -64,15 +65,7 @@ export function requestKeyframePack(iframeWindow, {
64
65
  });
65
66
  }
66
67
 
67
- /** Save a pack the browser already holds in memory. */
68
+ /** Save a pack the browser already holds in memory; resolves when handed off. */
68
69
  export function downloadPack({ name, bytes }) {
69
- const url = URL.createObjectURL(new Blob([bytes], { type: "application/zip" }));
70
- const anchor = document.createElement("a");
71
- anchor.href = url;
72
- anchor.download = name || "cozyclay-shot.zip";
73
- anchor.click();
74
- // Revoking synchronously can race the download in some browsers; one task
75
- // later is enough for the click to have been handed off.
76
- setTimeout(() => URL.revokeObjectURL(url), 0);
77
- return { name: anchor.download, url };
70
+ return saveExport(new Blob([bytes], { type: "application/zip" }), name || "cozyclay-shot.zip");
78
71
  }
@@ -31,7 +31,7 @@
31
31
 
32
32
  .task-dot { flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--text-3, var(--agent-text-dim)); }
33
33
  [data-status="running"] > .task-dot, [data-status="starting"] > .task-dot { background: var(--select, var(--agent-accent)); animation: task-dot-pulse 1.2s ease-in-out infinite; }
34
- [data-status="waiting"] > .task-dot, [data-status="queued"] > .task-dot { background: none; box-shadow: inset 0 0 0 1.5px var(--text-3, var(--agent-text-dim)); }
34
+ [data-status="waiting"] > .task-dot, [data-status="queued"] > .task-dot, [data-status="review"] > .task-dot { background: none; box-shadow: inset 0 0 0 1.5px var(--text-3, var(--agent-text-dim)); }
35
35
  [data-status="done"] > .task-dot { background: var(--ok, var(--agent-ok)); }
36
36
  [data-status="failed"] > .task-dot, [data-status="paused"] > .task-dot { background: var(--danger, var(--agent-alert)); }
37
37
  @keyframes task-dot-pulse { 50% { opacity: 0.35; } }
@@ -2,7 +2,7 @@
2
2
  // toggle) or the server's COZYCLAY_STUDIO_TASKS flag: the chat then talks to the main agent (POST /agent/main), which
3
3
  // answers at once and dispatches tasks; this board shows them. Pure parts here (the reducer, labels, the switch's
4
4
  // storage); the network side is createTaskBoardClient; the view is TaskBoard.jsx.
5
- // server events (GET /agent/main/<sessionId>/events): task.queued|waiting|started|resumed|paused|done|failed|cancelled
5
+ // server events (GET /agent/main/<sessionId>/events): task.queued|waiting|started|resumed|paused|review|done|failed|cancelled
6
6
  // carry the task's view { taskId, title, status, scope, reason?, turnId?, usage, summary? }; task.sceneChanged
7
7
  // { taskId, keys, midCommand }; main.turn { turnId } (the main agent reporting on its own)
8
8
  // editor events (the command bus): task.userEdit { taskId, keys } (the user changed something in its scope)
@@ -25,6 +25,7 @@ export const TASK_STATUS_COPY = {
25
25
  starting: ko("Starting", "시작 중"),
26
26
  running: ko("Running", "작업 중"),
27
27
  paused: ko("Paused: Studio lost", "일시정지: Studio 연결 끊김"),
28
+ review: ko("Being checked", "확인 중"),
28
29
  done: ko("Done", "완료"),
29
30
  failed: ko("Failed", "실패"),
30
31
  cancelled: ko("Cancelled", "취소됨"),
@@ -92,15 +93,16 @@ export function boardGroups(board) {
92
93
  const groups = { failed: [], running: [], waiting: [], done: [] };
93
94
  for (const id of board.order) {
94
95
  const task = board.tasks[id];
95
- const group = task.status === "failed" || task.status === "paused" ? "failed" : task.status === "running" || task.status === "starting" ? "running" : ACTIVE_STATUSES.has(task.status) ? "waiting" : "done";
96
+ // review: the run ended but the coordinator still checks it, so it waits (and offers no cancel: not in ACTIVE_STATUSES).
97
+ const group = task.status === "failed" || task.status === "paused" ? "failed" : task.status === "running" || task.status === "starting" ? "running" : ACTIVE_STATUSES.has(task.status) || task.status === "review" ? "waiting" : "done";
96
98
  groups[group].push(task);
97
99
  }
98
100
  return groups;
99
101
  }
100
102
 
101
- /** The task ids a turn's dispatch_task steps started, in order (a duplicate points at the task it repeats). */
102
- export const dispatchedTaskIds = steps => (steps ?? []).filter(step => step.kind === "tool" && step.name === "dispatch_task")
103
- .map(step => step.result?.taskId ?? step.result?.duplicateOf).filter(Boolean);
103
+ /** The task ids a turn's dispatch_task and dispatch_plan steps started, in order (a duplicate points at the task it repeats). */
104
+ export const dispatchedTaskIds = steps => (steps ?? []).filter(step => step.kind === "tool" && ["dispatch_task", "dispatch_plan"].includes(step.name))
105
+ .flatMap(step => (step.name === "dispatch_plan" ? step.result?.tasks ?? [] : [step.result])).map(result => result?.taskId ?? result?.duplicateOf).filter(Boolean);
104
106
 
105
107
  /** What the task view shares between the headless board (TaskBoard.jsx) and the rows in the chat (TaskInline.jsx). */
106
108
  function createTaskViewStore() {
@@ -15,7 +15,7 @@
15
15
  "mcp/verify-live-p0.mjs": 264,
16
16
  "mcp/verify-live.mjs": 416,
17
17
  "src/analytics.js": 1038,
18
- "src/App.jsx": 4476,
18
+ "src/App.jsx": 4448,
19
19
  "src/asset-pane.jsx": 643,
20
20
  "src/camera-follow.js": 505,
21
21
  "src/camera-move.js": 289,
@@ -58,7 +58,6 @@
58
58
  "src/shell/BottomDock.jsx": 416,
59
59
  "src/shell/DetailsSlot.jsx": 334,
60
60
  "src/shell/MenuBar.jsx": 632,
61
- "src/shell/PreferencesDialog.jsx": 269,
62
61
  "src/shell/ViewportToolbar.jsx": 567,
63
62
  "src/shot-authoring.js": 345,
64
63
  "src/stage-canvas-rigs.jsx": 350,
@@ -14,7 +14,7 @@ if (!process.env.COZYCLAY_AGENT_SESSIONS_DIR) {
14
14
  }
15
15
 
16
16
  const NODE_FILES = [
17
- "test/motion3/verify-motion3-core.mjs", "test/motion3/verify-playback-frame-store.mjs", "test/verify-playback-render-isolation.mjs", "test/motion3/verify-motion3-idle.mjs", "test/motion3/verify-motion3-flip.mjs", "test/motion3/verify-motion3-timing.mjs", "test/motion3/verify-motion3-self.mjs", "test/motion3/verify-motion3-commands.mjs", "test/motion3/verify-motion3-exitcar.mjs", "test/motion3/verify-motion3-agentloop.mjs", "test/motion3/verify-motion3-search.mjs", "test/motion3/verify-motion3-skills.mjs", "test/motion3/verify-motion3-carried.mjs", "test/motion3/verify-motion3-people.mjs", "test/motion3/verify-motion3-hit.mjs", "test/motion3/verify-motion3-socket.mjs", "test/motion3/verify-motion3-shapes.mjs", "test/motion3/verify-motion3-grasp-fields.mjs", "test/motion3/verify-motion3-finger-contact.mjs", "test/motion3/verify-motion3-grasp-qa.mjs", "test/motion3/verify-motion3-load.mjs", "test/motion3/verify-motion3-throw.mjs", "test/motion3/verify-motion3-catch.mjs", "test/motion3/verify-motion3-grasp-hints.mjs", "test/motion3/verify-motion3-reach-human.mjs", "test/motion3/verify-motion3-stance-grasp.mjs", "test/motion3/verify-motion3-grasp-friction.mjs", "test/motion3/verify-motion3-grasp-measure.mjs", "test/motion3/verify-motion3-contracts.mjs", "test/motion3/verify-motion3-describe.mjs", "test/motion3/verify-motion3-timeline.mjs", "test/motion3/verify-motion3-probes.mjs", "test/motion3/verify-motion3-grab.mjs", "test/motion3/verify-motion3-grab-blend.mjs", "test/motion3/verify-motion3-patch.mjs", "test/motion3/verify-motion3-repair.mjs", "test/motion3/verify-motion3-specstore.mjs", "test/motion3/verify-motion3-visual.mjs", "test/motion3/verify-motion3-goals.mjs", "test/motion3/verify-motion3-prompt.mjs", "test/motion3/verify-motion3-cup.mjs", "test/verify-proxy-figure.mjs", "test/verify-text-object.mjs",
17
+ "test/motion3/verify-motion3-core.mjs", "test/motion3/verify-playback-frame-store.mjs", "test/verify-playback-render-isolation.mjs", "test/motion3/verify-motion3-idle.mjs", "test/motion3/verify-motion3-life.mjs", "test/motion3/verify-motion3-overlap.mjs", "test/motion3/verify-motion3-flip.mjs", "test/motion3/verify-motion3-timing.mjs", "test/motion3/verify-motion3-self.mjs", "test/motion3/verify-motion3-commands.mjs", "test/motion3/verify-motion3-exitcar.mjs", "test/motion3/verify-motion3-agentloop.mjs", "test/motion3/verify-motion3-search.mjs", "test/motion3/verify-motion3-skills.mjs", "test/motion3/verify-motion3-carried.mjs", "test/motion3/verify-motion3-people.mjs", "test/motion3/verify-motion3-hit.mjs", "test/motion3/verify-motion3-socket.mjs", "test/motion3/verify-motion3-shapes.mjs", "test/motion3/verify-motion3-grasp-fields.mjs", "test/motion3/verify-motion3-finger-contact.mjs", "test/motion3/verify-motion3-grasp-qa.mjs", "test/motion3/verify-motion3-load.mjs", "test/motion3/verify-motion3-throw.mjs", "test/motion3/verify-motion3-catch.mjs", "test/motion3/verify-motion3-grasp-hints.mjs", "test/motion3/verify-motion3-reach-human.mjs", "test/motion3/verify-motion3-stance-grasp.mjs", "test/motion3/verify-motion3-grasp-friction.mjs", "test/motion3/verify-motion3-grasp-measure.mjs", "test/motion3/verify-motion3-contracts.mjs", "test/motion3/verify-motion3-describe.mjs", "test/motion3/verify-motion3-timeline.mjs", "test/motion3/verify-motion3-probes.mjs", "test/motion3/verify-motion3-grab.mjs", "test/motion3/verify-motion3-grab-blend.mjs", "test/motion3/verify-motion3-patch.mjs", "test/motion3/verify-motion3-patch-keys.mjs", "test/motion3/verify-motion3-stuck.mjs", "test/motion3/verify-motion3-repair.mjs", "test/motion3/verify-motion3-specstore.mjs", "test/motion3/verify-motion3-visual.mjs", "test/motion3/verify-motion3-goals.mjs", "test/motion3/verify-motion3-prompt.mjs", "test/motion3/verify-motion3-cup.mjs", "test/motion3/verify-motion3-arm.mjs", "test/verify-proxy-figure.mjs", "test/verify-text-object.mjs",
18
18
  "test/verify-proxy-figure-bounds.mjs",
19
19
  "test/verify-character-capabilities.mjs",
20
20
  "test/verify-root-path.mjs",
@@ -49,7 +49,7 @@ const NODE_FILES = [
49
49
  "test/process/verify-package-telemetry.mjs",
50
50
  "test/process/verify-agent-deps.mjs",
51
51
  "test/verify-agent-image-references.mjs",
52
- "test/verify-agent-panel.mjs", "test/verify-agent-turn-view.mjs", "test/verify-agent-reply-text.mjs",
52
+ "test/verify-agent-panel.mjs", "test/verify-agent-turn-view.mjs", "test/verify-agent-plan.mjs", "test/verify-agent-reply-text.mjs", "test/verify-agent-sculpt-sketch.mjs",
53
53
  "test/verify-agent-host.mjs",
54
54
  "test/verify-agent-routes.mjs",
55
55
  "test/verify-studio-owner-restart.mjs",
@@ -81,14 +81,14 @@ const NODE_FILES = [
81
81
  "test/verify-auto-color.mjs",
82
82
  "test/verify-coplanar-depth.mjs",
83
83
  "test/verify-empty-object.mjs",
84
- "test/verify-sculpt-recipe.mjs",
84
+ "test/verify-sculpt-recipe.mjs", "test/verify-sculpt-shapes.mjs", "test/verify-sculpt-loft-convex.mjs", "test/verify-sculpt-detail.mjs",
85
85
  "test/verify-sculpt-object.mjs",
86
86
  "test/verify-sculpt-compare.mjs",
87
87
  "test/verify-sculpt-run.mjs",
88
88
  "test/verify-sculpt-compare-command.mjs",
89
89
  "test/verify-sculpt-panel.mjs",
90
90
  "test/verify-sculpt-blueprint.mjs",
91
- "test/verify-sculpt-mode.mjs",
91
+ "test/verify-sculpt-mode.mjs", "test/verify-sculpt-csg.mjs", "test/verify-material-presets.mjs",
92
92
  "test/verify-route-owner.mjs",
93
93
  "test/verify-part-colours.mjs",
94
94
  "test/verify-asset-shelf.mjs",
@@ -124,7 +124,7 @@ const NODE_FILES = [
124
124
  "test/verify-mcp-invariants.mjs",
125
125
  "test/verify-offscreen-export.mjs",
126
126
  "test/verify-export-capture.mjs",
127
- "test/verify-export-recovery.mjs",
127
+ "test/verify-export-recovery.mjs", "test/verify-export-workspace.mjs",
128
128
  "test/verify-record-mp4-source.mjs",
129
129
  "test/verify-timeline-extent.mjs",
130
130
  "test/verify-otio.mjs",
@@ -196,7 +196,7 @@ const NODE_FILES = [
196
196
  "mcp/verify-protocol-version.mjs",
197
197
  "mcp/verify-tool-annotations.mjs",
198
198
  "mcp/verify-import-mesh.mjs",
199
- "mcp/verify-sculpt-object.mjs",
199
+ "mcp/verify-sculpt-object.mjs", "mcp/verify-project-camera.mjs",
200
200
  "test/verify-object-path.mjs",
201
201
  "test/verify-object-travel.mjs",
202
202
  "test/verify-object-travel-lean.mjs",
@@ -216,7 +216,7 @@ const BROWSER_FILES = [
216
216
  "test/qa-storyboard-export-browser.mjs",
217
217
  "test/qa-proxy-figure-browser.mjs",
218
218
  "test/qa-rail-lean-browser.mjs",
219
- "test/qa-export-recovery-browser.mjs",
219
+ "test/qa-export-recovery-browser.mjs", "test/qa-export-workspace-browser.mjs",
220
220
  "test/verify-camera-mode-browser.mjs",
221
221
  "test/verify-camera-rail-browser.mjs",
222
222
  "test/verify-cutout-browser.mjs",
@@ -255,7 +255,7 @@ const BROWSER_FILES = [
255
255
  // The inventory sweep only picks up `verify*.mjs`; a browser suite named for
256
256
  // the QA runner it needs is listed here so it still shows up in the manifest
257
257
  // (as an EXCLUDE with its reason) instead of going unmentioned.
258
- const EXTRA_INVENTORY = ["test/ik/translation-step.mjs", "test/qa-scenes-v5-migration-browser.mjs", "test/qa-agent-scenarios-browser.mjs", "test/qa-execution-outcomes-browser.mjs", "test/qa-export-recovery-browser.mjs", "test/qa-agent-view-toggle-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-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-live-reconnect-browser.mjs", "test/qa-clipboard-paste-browser.mjs", "test/qa-rail-lean-browser.mjs", "test/qa-rail-reach-browser.mjs", "test/qa-rail-ring-browser.mjs", "test/qa-speed-graph-browser.mjs"];
258
+ const EXTRA_INVENTORY = ["test/ik/translation-step.mjs", "test/qa-scenes-v5-migration-browser.mjs", "test/qa-agent-scenarios-browser.mjs", "test/qa-execution-outcomes-browser.mjs", "test/qa-export-recovery-browser.mjs", "test/qa-agent-view-toggle-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-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-live-reconnect-browser.mjs", "test/qa-clipboard-paste-browser.mjs", "test/qa-rail-lean-browser.mjs", "test/qa-rail-reach-browser.mjs", "test/qa-rail-ring-browser.mjs", "test/qa-speed-graph-browser.mjs", "test/qa-export-workspace-browser.mjs"];
259
259
 
260
260
  EXTRA_INVENTORY.push("test/qa-proxy-figure-browser.mjs");
261
261
  EXTRA_INVENTORY.push("test/qa-proxy-entry-browser.mjs");