cozyclay 3.1.5 → 3.2.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 (67) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/bin/agent/agent-runner.mjs +9 -4
  3. package/bin/agent/openai-compatible-provider.mjs +24 -1
  4. package/dist/app/index.html +5 -5
  5. package/dist/assets/TaskBoard-BUjjuvm5.js +1 -0
  6. package/dist/assets/{app-CEP7jzXB.css → app-Dige7q3X.css} +1 -1
  7. package/dist/assets/app-DxoI941o.js +5144 -0
  8. package/dist/assets/compile-worker-DRWRa9pr.js +6 -0
  9. package/dist/assets/{motion3-compile-Du7UXrWn.js → motion3-compile-B4SX4TGx.js} +1 -1
  10. package/dist/assets/scene-asset-sync-LeI3eSnO.js +13 -0
  11. package/dist/assets/task-board-5PKn7ZaA.js +15 -0
  12. package/dist/assets/{workflow-DJwOUzqJ.js → workflow-Dg9jCKOq.js} +3 -3
  13. package/dist/cozyclay-package.json +1 -1
  14. package/dist/index.html +1 -1
  15. package/dist/workflow/index.html +3 -3
  16. package/mcp/project-camera.mjs +43 -0
  17. package/package.json +3 -1
  18. package/src/App.jsx +5 -5
  19. package/src/commands/index.js +2 -1
  20. package/src/commands/physics.js +21 -0
  21. package/src/domains/motion.js +7 -9
  22. package/src/motion/center-of-mass.js +5 -23
  23. package/src/motion/contact-project.js +143 -0
  24. package/src/motion/fix-collisions.js +1 -1
  25. package/src/motion/ik-drag.js +93 -1
  26. package/src/motion/timeline.css +12 -0
  27. package/src/motion/timeline.jsx +2 -2
  28. package/src/motion3/compile-job.js +3 -2
  29. package/src/motion3/compiler/compile.js +3 -3
  30. package/src/motion3/compiler/spring.js +95 -0
  31. package/src/motion3/compiler/validate-objects.js +18 -2
  32. package/src/motion3/compiler/validate-parts.js +1 -0
  33. package/src/motion3/compiler/vocab.js +1 -1
  34. package/src/motion3/fix/index.js +8 -4
  35. package/src/motion3/fix/person.js +110 -0
  36. package/src/motion3/free-motion.js +49 -11
  37. package/src/motion3/object-pose.js +44 -2
  38. package/src/motion3/people.js +16 -1
  39. package/src/motion3/physics-frames.js +72 -0
  40. package/src/motion3/physics-trail.js +45 -0
  41. package/src/motion3/physics-view.js +79 -0
  42. package/src/motion3/pipeline.js +5 -1
  43. package/src/motion3/qa/ballistic.js +67 -0
  44. package/src/motion3/qa/com.js +140 -0
  45. package/src/motion3/qa/ground.js +10 -3
  46. package/src/motion3/qa/hints-contact.js +9 -7
  47. package/src/motion3/qa/hints-motion.js +38 -1
  48. package/src/motion3/qa/key-checks.js +5 -4
  49. package/src/motion3/qa/patch.js +28 -3
  50. package/src/motion3/qa/people-checks.js +1 -1
  51. package/src/motion3/qa/spring-pivot.js +23 -0
  52. package/src/motion3/qa/style.js +13 -30
  53. package/src/motion3/schema/fields.js +4 -2
  54. package/src/motion3/schema/topics.js +15 -11
  55. package/src/motion3/schema/types.js +13 -9
  56. package/src/physics-overlay-settings.js +41 -0
  57. package/src/physics-overlay.js +152 -0
  58. package/src/physics-overlay.jsx +54 -0
  59. package/src/physics-toggle.jsx +20 -0
  60. package/src/posestudio.jsx +1 -1
  61. package/tools/run-tests.mjs +9 -16
  62. package/tools/verify-package-tarball.mjs +29 -1
  63. package/dist/assets/TaskBoard-DwXhsJXz.js +0 -1
  64. package/dist/assets/app-CTKcyPM-.js +0 -5144
  65. package/dist/assets/compile-worker-or7djI2v.js +0 -6
  66. package/dist/assets/scene-asset-sync-t45pWCyq.js +0 -13
  67. package/dist/assets/task-board-CkLZGPkv.js +0 -15
package/CHANGELOG.md CHANGED
@@ -2,6 +2,82 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 3.2.0 - 2026-10-26
6
+
7
+ Motion gets physics you can see and trust: props swing and settle after the hand stops, balance and jumps are judged by real mass and gravity and the Studio draws why, and characters no longer pass through each other when you drag or compile.
8
+
9
+ Studio
10
+
11
+ - **Physics overlay.** Tick **Physics** in the motion controls, or call
12
+ `motion.physicsOverlay`, to draw the centre of mass (green inside the feet,
13
+ red outside, grey in the air), the support polygon under the feet, each
14
+ jump's actual hips path next to its gravity arc, and a short trail behind
15
+ swinging props. It shows in scrub, playback and video export, and red
16
+ frames are exactly the ones QA calls BALANCE. (#950)
17
+ - Dragging a hand or foot IK handle into another character now stops at that
18
+ character's body instead of passing through, even on a fast drag across a
19
+ thin forearm. Hold **Alt** while dragging to pass through on purpose; a
20
+ one-time notice says so. The other character never moves. (#949)
21
+
22
+ Motion3
23
+
24
+ - **Prop spring.** `objects.<id>.spring = { length, stiffness, damping }`
25
+ makes a carried or free prop (a bag, a lantern, a club) swing a beat behind
26
+ the hand and settle. `length` is the string or rod length; stiffness
27
+ defaults to gravity over length and damping to 0.5, so `spring: { length: 0.5 }`
28
+ already swings naturally. Put the prop's base at the hand with `at`/`along`,
29
+ because the swing pivots at the base; QA notes a base that sits far from the
30
+ hand. The swing is computed once at compile time, stops when a dropped prop
31
+ lands, and never pushes the prop into the floor. (#945)
32
+ - **BALANCE** now uses a mass-weighted centre of mass against the support
33
+ polygon of the grounded feet, instead of a four-point average and a box. Its
34
+ hint gives the distance and direction (for example 0.10 m ahead of the feet),
35
+ and a lean that changes direction stays one issue. A hint that moves the
36
+ root now moves every key that holds the pose, so the body does not slide
37
+ between them. (#944, #937)
38
+ - **FLOAT** issues come with a fix: the root keyed onto the gravity arc between
39
+ takeoff and landing, returning to the original motion at both ends. When the
40
+ correction would be too large, the hint gives the apex height or the air time
41
+ that would fit instead. (#943)
42
+ - **`fix.person`** (on by default) moves a character's arms out of another
43
+ character's body at compile time, keeps contact you allowed in
44
+ `checks.allow`, and gives the same take after saving and reopening. (#948)
45
+
46
+ Agent
47
+
48
+ - Kimi K3 models (`kimi-k3*` on the Moonshot hosts) work on the
49
+ OpenAI-compatible provider with their reasoning efforts and full output
50
+ limit. A turn that spends every output token before answering now says so
51
+ and suggests a lower reasoning effort, instead of failing with no output.
52
+ (#951, #947)
53
+
54
+ Package and tests
55
+
56
+ - `jpeg-js`, which the MCP image tools import, is a root dev dependency, so a
57
+ development checkout runs the MCP tests without a separate install. The test
58
+ runner now skips the MCP suites with a clear message when `mcp/node_modules`
59
+ is missing. (#946, #936)
60
+ - The Studio camera QA enters look-through from the dock's Shot Camera card,
61
+ where the Studio shows it. (#952, #925)
62
+
63
+ ## 3.1.6 - 2026-10-25
64
+
65
+ The npm package ships the MCP module the live tools need, so the Studio's live tools and `cozyclay mcp` work on a fresh install.
66
+
67
+ Studio
68
+
69
+ - A 3.1.5 global install shows the **Update** chip in the top bar for the
70
+ first time, since this is the first release after the chip shipped.
71
+ **Update now**, or the command it offers to copy, installs 3.1.6; then
72
+ restart `cclay`.
73
+
74
+ MCP
75
+
76
+ - The npm package now ships `mcp/project-camera.mjs`, so the Studio's live
77
+ tools and `cozyclay mcp` start on an npm install instead of failing with
78
+ "Cannot find module" (broken since 3.1.2). The package check now fails a
79
+ release whose `bin/` or `mcp/` code imports a file the package leaves out.
80
+
5
81
  ## 3.1.5 - 2026-10-25
6
82
 
7
83
  The Studio tells you when a new release is out and installs it in one click, Claude models work in the agent panel again, IK edits stay on their own character, turns and walks look more natural, and anonymous analytics show where the Studio gets in the way.
@@ -414,7 +414,8 @@ export function createAgentRunner({ models: suppliedModels, sessionStore, tools
414
414
  initialState: { systemPrompt: activePrompt, model: selected.model, thinkingLevel: thinkingLevel ?? "off", tools: await adapters(input), messages: restored?.history ?? [] },
415
415
  streamFn: createRetryingStream({ ai: pi.ai, models: registry, refreshAuth, onAttempt: (message) => { countAttempt(message); emitQuota(state.active?.queue, state.lastInput); } }),
416
416
  transformContext: studioView,
417
- onPayload: (payload, model) => (model?.api === "anthropic-messages" ? withAnthropicCacheBreakpoints(payload, model) : undefined),
417
+ // keeps the output limit this request really sent, for a turn that runs out of it (finish)
418
+ onPayload: (payload, model) => { if (state.active) state.active.maxTokens = payload?.max_tokens ?? payload?.max_completion_tokens ?? payload?.max_output_tokens; return model?.api === "anthropic-messages" ? withAnthropicCacheBreakpoints(payload, model) : undefined; },
418
419
  onResponse: (response) => { if (state.active && response?.headers) state.active.lastResponseHeaders = response.headers; },
419
420
  sessionId,
420
421
  // pi-agent-core's Agent attributes requests to pi (OpenRouter, NVIDIA NIM and OpenCode headers); these are
@@ -480,7 +481,11 @@ export function createAgentRunner({ models: suppliedModels, sessionStore, tools
480
481
  // The run's outcome is its last assistant message: the Agent ends a
481
482
  // failed or cancelled request there (stopReason error/aborted).
482
483
  const last = event.messages.findLast((message) => message.role === "assistant");
483
- const status = last?.stopReason === "aborted" ? "aborted" : last?.stopReason === "error" ? "error" : "completed";
484
+ // #947: a request that hit its output limit before any answer or tool call (reasoning alone can use it up)
485
+ // is a failure, not an empty success; with some text or a tool call it completes as before.
486
+ const outOfTokens = last?.stopReason === "length" && !last.content?.some((part) => part?.type === "toolCall" || (part?.type === "text" && part.text))
487
+ ? `The model used all ${state.active?.maxTokens ?? state.currentModel?.maxTokens} output tokens before answering. Lower the reasoning effort or raise the output limit.` : null;
488
+ const status = last?.stopReason === "aborted" ? "aborted" : last?.stopReason === "error" || outOfTokens ? "error" : "completed";
484
489
  // #379 / 16q: an acknowledged Studio Stop whose held tool already settled
485
490
  // with a real structured outcome (tool.done above) calls `abort(reason,
486
491
  // { quiet: true })` purely to stop the model from being prompted again —
@@ -493,10 +498,10 @@ export function createAgentRunner({ models: suppliedModels, sessionStore, tools
493
498
  const rawMessage = last?.errorMessage || (status === "aborted" ? "The turn was aborted." : "The model or live editor could not complete this turn.");
494
499
  const truncated = /ended before a terminal response event/i.test(rawMessage);
495
500
  const classified = status === "aborted" ? { code: "aborted", status: undefined } : classifyError({ message: rawMessage });
496
- const code = status === "aborted" ? "aborted" : truncated ? "truncated" : classified.code;
501
+ const code = status === "aborted" ? "aborted" : truncated || outOfTokens ? "truncated" : classified.code;
497
502
  const detail = sanitizeUpstreamDetail(JSON.stringify({ message: rawMessage }));
498
503
  const fallback = code === "aborted" ? "The turn was aborted." : code === "truncated" ? "The model stream ended before a terminal response event." : "The model or live editor could not complete this turn.";
499
- queue.push({ type: "error", code, message: detail || fallback, ...(classified.status ? { status: classified.status } : {}) });
504
+ queue.push({ type: "error", code, message: outOfTokens || detail || fallback, ...(classified.status ? { status: classified.status } : {}) });
500
505
  }
501
506
  queue.push({ type: "done", usage: { ...state.active.usage }, ...(state.lastInput?.reportCost ? { cost: state.active.cost ?? 0 } : {}) });
502
507
  queue.close();
@@ -33,6 +33,29 @@ function endpointModel(id, base) {
33
33
  };
34
34
  }
35
35
 
36
+ /** Moonshot's own API (#947): a host check on the address, nothing else. */
37
+ export const isMoonshotBase = (base) => { try { return /^api\.moonshot\.(ai|cn)$/.test(new URL(base).hostname); } catch { return false; } };
38
+ // The documented default output limit for Kimi K3 models; the catalog's 1048576 is the context window.
39
+ const KIMI_K3_MAX_TOKENS = 131072;
40
+
41
+ /** A Kimi K3 model on Moonshot's API (#947), or null. It always reasons (low/high/max, never off) and needs its
42
+ * reasoning_content sent back, so it takes pi-ai's catalog kimi-k3 as its template, with a larger output limit than
43
+ * the generic 8192 that its reasoning alone can use up. Null when the catalog has no template: the generic shape. */
44
+ export async function moonshotModel(id, base, moonshot = isMoonshotBase(base)) {
45
+ if (!moonshot || !/^kimi-k3/.test(id)) return null;
46
+ try {
47
+ const cn = new URL(base).hostname.endsWith(".cn");
48
+ const catalog = cn
49
+ ? (await import("@earendil-works/pi-ai/providers/moonshotai-cn.models")).MOONSHOTAI_CN_MODELS
50
+ : (await import("@earendil-works/pi-ai/providers/moonshotai.models")).MOONSHOTAI_MODELS;
51
+ const template = catalog?.["kimi-k3"];
52
+ if (!template?.compat) return null;
53
+ return { ...structuredClone(template), id, name: id, provider: OPENAI_COMPATIBLE_ID, baseUrl: v1(base), maxTokens: KIMI_K3_MAX_TOKENS };
54
+ } catch { return null; }
55
+ }
56
+
57
+ const serverModel = async (id, base) => (await moonshotModel(id, base)) ?? endpointModel(id, base);
58
+
36
59
  /** The server's model ids, or an error that says what went wrong in words the login and the picker can show. */
37
60
  async function listEndpoint(base, key, signal) {
38
61
  let data;
@@ -82,7 +105,7 @@ export async function openaiCompatibleProvider() {
82
105
  api: { "openai-completions": completionsApi },
83
106
  fetchModels: async ({ credential, signal }) => {
84
107
  const base = credential?.env?.[BASE_ENV];
85
- return base ? (await listEndpoint(base, credential.key, signal)).map((id) => endpointModel(id, base)) : [];
108
+ return base ? Promise.all((await listEndpoint(base, credential.key, signal)).map((id) => serverModel(id, base))) : [];
86
109
  },
87
110
  });
88
111
  }
@@ -34,14 +34,14 @@
34
34
  <title>CozyClay Studio — 3D staging and previs in your browser</title>
35
35
 
36
36
  <script defer src="https://static.cloudflareinsights.com/beacon.min.js" data-cf-beacon='{"token": "6a4384d1375d4ffc8a30a6346a8ad1a0"}'></script>
37
- <script type="module" crossorigin src="/assets/app-CTKcyPM-.js"></script>
37
+ <script type="module" crossorigin src="/assets/app-DxoI941o.js"></script>
38
38
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
39
- <link rel="modulepreload" crossorigin href="/assets/task-board-CkLZGPkv.js">
39
+ <link rel="modulepreload" crossorigin href="/assets/task-board-5PKn7ZaA.js">
40
40
  <link rel="modulepreload" crossorigin href="/assets/fflate.module-KbGj5-H6.js">
41
- <link rel="modulepreload" crossorigin href="/assets/scene-asset-sync-t45pWCyq.js">
42
- <link rel="modulepreload" crossorigin href="/assets/motion3-compile-Du7UXrWn.js">
41
+ <link rel="modulepreload" crossorigin href="/assets/scene-asset-sync-LeI3eSnO.js">
42
+ <link rel="modulepreload" crossorigin href="/assets/motion3-compile-B4SX4TGx.js">
43
43
  <link rel="stylesheet" crossorigin href="/assets/scene-asset-sync-BEjjhNEo.css">
44
- <link rel="stylesheet" crossorigin href="/assets/app-CEP7jzXB.css">
44
+ <link rel="stylesheet" crossorigin href="/assets/app-Dige7q3X.css">
45
45
  </head>
46
46
  <body>
47
47
  <div id="root">
@@ -0,0 +1 @@
1
+ import{r as e}from"./rolldown-runtime-hePW80VL.js";import{Za as t,c as n,d as r,f as i,l as a,m as o,o as s,ua as c,v as l}from"./task-board-5PKn7ZaA.js";import{o as u,r as d,t as f}from"./motion3-compile-B4SX4TGx.js";var p=e(t(),1),m=2e3,h=(e,t)=>new Promise(n=>{let r=setTimeout(n,e);t.addEventListener(`abort`,()=>{clearTimeout(r),n()},{once:!0})});function g(e,t){(0,p.useEffect)(()=>{let n=new Map;for(let[r,i]of Object.entries(e))for(let e of i)n.set(e,t[r]??r);let r=()=>{for(let e of document.querySelectorAll(`[data-node-id]`)){let t=n.get(e.getAttribute(`data-node-id`));t?(e.setAttribute(`data-task-scope`,t),e.setAttribute(`title`,c(`In task: ${t}`,`작업 중: ${t}`,`任务中:${t}`))):e.hasAttribute(`data-task-scope`)&&(e.removeAttribute(`data-task-scope`),e.removeAttribute(`title`))}};r();let i=new MutationObserver(r);return i.observe(document.body,{childList:!0,subtree:!0}),()=>{i.disconnect(),n.clear(),r()}},[e,t])}function _({sessionId:e,pulse:t,labelOf:c,onMainNote:_}){let v=(0,p.useMemo)(()=>s(),[]),y=u(),[b,x]=(0,p.useReducer)((e,t)=>t.seed?o(e,t.seed):t.reset?n():r(e,t.event),void 0,n),[S,C]=(0,p.useState)({}),[w,T]=(0,p.useState)(f),E=(0,p.useRef)(0),D=(0,p.useRef)(_);(0,p.useEffect)(()=>{E.current=b.after,D.current=_},[b.after,_]),(0,p.useEffect)(()=>{x({reset:!0}),E.current=0},[e]),(0,p.useEffect)(()=>d(T),[]),(0,p.useEffect)(()=>y.subscribe(e=>{typeof e?.type!=`string`||!e.type.startsWith(`task.`)||(C(t=>i(t,e)),(e.type===`task.userEdit`||e.type===`task.undone`)&&x({event:e}))}),[y]),(0,p.useEffect)(()=>{if(!e)return;let t=new AbortController;return(async()=>{for(;!t.signal.aborted;){try{let{tasks:n}=await v.board(e);x({seed:n}),await v.events(e,E.current,e=>{if(e.type===`main.turn`&&e.turnId){let n=``;v.frames(e.turnId,e=>{e.type===`text.delta`&&(n+=e.text??``),e.type===`tool.start`&&(n=``)},t.signal).then(()=>D.current?.(n),()=>{})}x({event:e})},t.signal)}catch{}await h(m,t.signal)}})(),()=>t.abort()},[v,e,t]);let O=b.order.filter(e=>b.tasks[e]?.status===`running`&&b.tasks[e]?.turnId),k=O.map(e=>`${e}:${b.tasks[e].turnId}`).join();return(0,p.useEffect)(()=>{let e=O.map(e=>{let t=new AbortController,{turnId:n}=b.tasks[e];return v.frames(n,t=>{let n=a(t,c);n&&x({event:{type:`task.progress`,taskId:e,status:`running`,progress:n}})},t.signal).catch(()=>{}),t});return()=>e.forEach(e=>e.abort())},[v,k,c]),g(S,(0,p.useMemo)(()=>Object.fromEntries(b.order.map(e=>[e,b.tasks[e].title])),[b])),(0,p.useEffect)(()=>{l.set({client:v,labelOf:c,undo:async e=>{let t=await y.run(`task.undo`,{taskId:e});if(t?.ok===!1)throw Error(t.message??t.code);x({event:{type:`task.undone`,taskId:e}})}})},[y,v,c]),(0,p.useEffect)(()=>{l.set({board:b,activity:w})},[b,w]),(0,p.useEffect)(()=>()=>l.set({board:n(),open:null}),[e]),null}export{_ as default};