cozyclay 3.1.6 → 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.
- package/CHANGELOG.md +58 -0
- package/bin/agent/agent-runner.mjs +9 -4
- package/bin/agent/openai-compatible-provider.mjs +24 -1
- package/dist/app/index.html +5 -5
- package/dist/assets/TaskBoard-BUjjuvm5.js +1 -0
- package/dist/assets/{app-CEP7jzXB.css → app-Dige7q3X.css} +1 -1
- package/dist/assets/app-DxoI941o.js +5144 -0
- package/dist/assets/compile-worker-DRWRa9pr.js +6 -0
- package/dist/assets/{motion3-compile-Du7UXrWn.js → motion3-compile-B4SX4TGx.js} +1 -1
- package/dist/assets/scene-asset-sync-LeI3eSnO.js +13 -0
- package/dist/assets/task-board-5PKn7ZaA.js +15 -0
- package/dist/assets/{workflow-DJwOUzqJ.js → workflow-Dg9jCKOq.js} +3 -3
- package/dist/cozyclay-package.json +1 -1
- package/dist/index.html +1 -1
- package/dist/workflow/index.html +3 -3
- package/package.json +2 -1
- package/src/App.jsx +5 -5
- package/src/commands/index.js +2 -1
- package/src/commands/physics.js +21 -0
- package/src/domains/motion.js +7 -9
- package/src/motion/center-of-mass.js +5 -23
- package/src/motion/contact-project.js +143 -0
- package/src/motion/fix-collisions.js +1 -1
- package/src/motion/ik-drag.js +93 -1
- package/src/motion/timeline.css +12 -0
- package/src/motion/timeline.jsx +2 -2
- package/src/motion3/compile-job.js +3 -2
- package/src/motion3/compiler/compile.js +3 -3
- package/src/motion3/compiler/spring.js +95 -0
- package/src/motion3/compiler/validate-objects.js +18 -2
- package/src/motion3/compiler/validate-parts.js +1 -0
- package/src/motion3/compiler/vocab.js +1 -1
- package/src/motion3/fix/index.js +8 -4
- package/src/motion3/fix/person.js +110 -0
- package/src/motion3/free-motion.js +49 -11
- package/src/motion3/object-pose.js +44 -2
- package/src/motion3/people.js +16 -1
- package/src/motion3/physics-frames.js +72 -0
- package/src/motion3/physics-trail.js +45 -0
- package/src/motion3/physics-view.js +79 -0
- package/src/motion3/pipeline.js +5 -1
- package/src/motion3/qa/ballistic.js +67 -0
- package/src/motion3/qa/com.js +140 -0
- package/src/motion3/qa/ground.js +10 -3
- package/src/motion3/qa/hints-contact.js +9 -7
- package/src/motion3/qa/hints-motion.js +38 -1
- package/src/motion3/qa/key-checks.js +5 -4
- package/src/motion3/qa/patch.js +28 -3
- package/src/motion3/qa/people-checks.js +1 -1
- package/src/motion3/qa/spring-pivot.js +23 -0
- package/src/motion3/qa/style.js +13 -30
- package/src/motion3/schema/fields.js +4 -2
- package/src/motion3/schema/topics.js +15 -11
- package/src/motion3/schema/types.js +13 -9
- package/src/physics-overlay-settings.js +41 -0
- package/src/physics-overlay.js +152 -0
- package/src/physics-overlay.jsx +54 -0
- package/src/physics-toggle.jsx +20 -0
- package/src/posestudio.jsx +1 -1
- package/tools/run-tests.mjs +9 -16
- package/dist/assets/TaskBoard-DwXhsJXz.js +0 -1
- package/dist/assets/app-BLS1Zaru.js +0 -5144
- package/dist/assets/compile-worker-or7djI2v.js +0 -6
- package/dist/assets/scene-asset-sync-t45pWCyq.js +0 -13
- package/dist/assets/task-board-CkLZGPkv.js +0 -15
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,64 @@
|
|
|
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
|
+
|
|
5
63
|
## 3.1.6 - 2026-10-25
|
|
6
64
|
|
|
7
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.
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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) =>
|
|
108
|
+
return base ? Promise.all((await listEndpoint(base, credential.key, signal)).map((id) => serverModel(id, base))) : [];
|
|
86
109
|
},
|
|
87
110
|
});
|
|
88
111
|
}
|
package/dist/app/index.html
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
42
|
-
<link rel="modulepreload" crossorigin href="/assets/motion3-compile-
|
|
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-
|
|
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};
|