cozyclay 2.0.0 → 2.1.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 +112 -0
- package/README.md +17 -2
- package/THIRD_PARTY_NOTICES.md +8 -0
- package/bin/agent/agent-routes.mjs +219 -38
- package/bin/agent/agent-runner.mjs +98 -4
- package/bin/agent/codex-client.mjs +2 -1
- package/bin/agent/fal-contract.mjs +0 -17
- package/bin/agent/h3-preservation.mjs +6 -8
- package/bin/agent/provider-keys.mjs +1 -1
- package/bin/agent/providers.mjs +124 -3
- package/bin/agent/script-tool.mjs +74 -0
- package/bin/agent/session-store.mjs +5 -5
- package/bin/agent/studio-prompt.mjs +2 -1
- package/bin/agent/studio-rebase.mjs +314 -0
- package/bin/agent/studio-tools.mjs +21 -14
- package/bin/agent/supervisor.mjs +311 -0
- package/bin/agent/video-adapters.mjs +0 -7
- package/bin/codex-auth.mjs +6 -5
- package/bin/cozyclay.mjs +12 -3
- package/bin/live/cli.mjs +2 -3
- package/bin/live/client.mjs +1 -1
- package/bin/live/studio.mjs +5 -2
- package/bin/mcp-runtime.mjs +12 -2
- package/bin/telemetry-state.mjs +3 -3
- package/dist/app/index.html +6 -6
- package/dist/assets/analytics-B41FmPYI.js +1 -0
- package/dist/assets/app-Do-Og5Pu.css +1 -0
- package/dist/assets/app-DwP-tl2x.js +4920 -0
- package/dist/assets/{demo-ByeYp-dY.js → demo-Dt0qZfS7.js} +1 -1
- package/dist/assets/{first-shot-handoff-DgT28ZiV.js → first-shot-handoff-BlT33CGf.js} +1 -1
- package/dist/assets/{landing-BaXkpFip.js → landing-BSesrCpL.js} +1 -1
- package/dist/assets/scene-asset-sync-BCWDJB1d.js +19 -0
- package/dist/assets/{shot-prompt-CbqWcWYi.css → scene-asset-sync-DjJ_cJ2R.css} +1 -1
- package/dist/assets/{ticket-BcmsnHn5.js → ticket-jqMhqL88.js} +1 -1
- package/dist/assets/workflow-Br-K7Srs.js +179 -0
- package/dist/cozyclay-package.json +1 -1
- package/dist/index.html +328 -222
- package/dist/sitemap.xml +5 -5
- package/dist/workflow/index.html +4 -4
- package/mcp/LIVE-PROTOCOL.md +23 -2
- package/mcp/README.md +37 -2
- package/mcp/ardy-prompts.mjs +1 -1
- package/mcp/image-file.mjs +132 -0
- package/mcp/live-hub.mjs +2 -2
- package/mcp/package.json +1 -0
- package/mcp/runtime/package-lock.json +7 -0
- package/mcp/runtime/package.json +1 -0
- package/mcp/server.mjs +0 -6
- package/mcp/tool-handlers.mjs +289 -87
- package/package.json +11 -2
- package/src/App.jsx +801 -259
- package/src/agent-image-client.js +94 -0
- package/src/analytics.js +47 -3
- package/src/app-stage.jsx +129 -2745
- package/src/ardy/auto-physics.js +6 -16
- package/src/ardy/collision-blockers.js +3 -7
- package/src/ardy/fix-collisions.js +5 -35
- package/src/ardy/ik-drag.js +86 -5
- package/src/ardy/ik-key-json.js +3 -1
- package/src/ardy/ik.js +15 -13
- package/src/ardy/motion-calibration.js +1 -1
- package/src/ardy/npz.js +0 -8
- package/src/ardy/physics-panel.jsx +3 -2
- package/src/ardy/physics-review.js +3 -2
- package/src/ardy/physics-support.js +1 -1
- package/src/ardy/physics-surface.js +1 -1
- package/src/ardy/platform-fit-panel.jsx +2 -1
- package/src/ardy/playback.js +1 -5
- package/src/ardy/range-pin.js +2 -2
- package/src/ardy/root-drop.js +0 -1
- package/src/ardy/timeline.css +196 -3
- package/src/ardy/timeline.jsx +117 -29
- package/src/ardy/to-cskel27.js +0 -2
- package/src/ardy/waypoints.js +1 -4
- package/src/ardy-bridge-contract.js +281 -0
- package/src/asset-pane.jsx +19 -9
- package/src/auto-color.js +1 -1
- package/src/burn-in.js +1 -1
- package/src/camera-block.js +1 -1
- package/src/camera-follow.js +75 -32
- package/src/camera-move.js +0 -6
- package/src/camera-tutorial.jsx +43 -24
- package/src/character-kind.js +79 -0
- package/src/command-bus.js +76 -17
- package/src/commands/cast.js +31 -7
- package/src/commands/elements/object.js +1 -1
- package/src/commands/elements/shot.js +1 -1
- package/src/commands/elements.js +1 -1
- package/src/commands/export.js +3 -3
- package/src/commands/motion.js +2 -2
- package/src/commands/objects.js +105 -10
- package/src/commands/project.js +16 -3
- package/src/commands/sculpt-images.js +53 -0
- package/src/commands/shot.js +88 -10
- package/src/commands/view.js +10 -4
- package/src/coplanar-depth.js +216 -0
- package/src/cuts.js +7 -6
- package/src/domains/cast.js +60 -43
- package/src/domains/motion/motion-intent-codec.js +50 -0
- package/src/domains/motion/motion-range-pin-rules.js +23 -0
- package/src/domains/motion/motion-video-capture.js +664 -0
- package/src/domains/motion.js +253 -742
- package/src/domains/objects.js +106 -5
- package/src/domains/scenes.js +89 -32
- package/src/domains/shots.js +33 -7
- package/src/error-boundary.jsx +1 -1
- package/src/execution-telemetry.js +5 -5
- package/src/facing-marks.js +14 -1
- package/src/first-success-guide.jsx +35 -4
- package/src/gizmo-claim.js +3 -1
- package/src/grid-floor.jsx +1 -4
- package/src/hierarchy-model.js +48 -35
- package/src/hierarchy-panel.css +1 -0
- package/src/hierarchy-panel.jsx +54 -102
- package/src/history.js +0 -2
- package/src/i2v-motion-client.js +1 -4
- package/src/ime.js +6 -0
- package/src/keyframe-pack.js +15 -6
- package/src/line-edit.js +4 -4
- package/src/live-control.js +6 -6
- package/src/matte-editor.js +2 -8
- package/src/matte.js +0 -19
- package/src/mesh-graph-clone.js +16 -2
- package/src/mesh-sniff.js +0 -1
- package/src/monotone-cubic.js +40 -0
- package/src/motion/generation.js +1 -1
- package/src/motion-store.js +4 -4
- package/src/motion-trail.js +5 -11
- package/src/multimodel-ingest.js +1 -3
- package/src/object-catalog.jsx +7 -5
- package/src/object-gizmo.jsx +45 -31
- package/src/object-path.js +310 -67
- package/src/object-travel.js +224 -0
- package/src/otio.js +8 -3
- package/src/panels/CharacterTransformPanel.jsx +24 -6
- package/src/panels/ObjectTransformPanel.jsx +4 -3
- package/src/panels/PoseImagePanel.jsx +205 -0
- package/src/panels/PromptBlocksPanel.jsx +3 -1
- package/src/panels/ProxyFigurePanel.jsx +56 -0
- package/src/panels/ReferenceImageField.jsx +1 -1
- package/src/panels/RigControlPanel.jsx +24 -3
- package/src/panels/RigPanel.jsx +2 -1
- package/src/panels/SculptPanel.jsx +405 -0
- package/src/panels/SubjectBox.jsx +1 -1
- package/src/panels/SubjectsPanel.jsx +0 -2
- package/src/panels/VideoCapturePanel.jsx +2 -1
- package/src/panels/pose-image.css +42 -0
- package/src/panels/pose.css +3 -0
- package/src/panels/sculpt.css +162 -0
- package/src/plan-diagram.js +184 -0
- package/src/planview.jsx +127 -80
- package/src/playground.js +3 -5
- package/src/pose-fade.js +77 -0
- package/src/pose-gizmo.css +23 -0
- package/src/pose-gizmo.js +96 -0
- package/src/posestudio.jsx +134 -83
- package/src/project-browser.css +53 -0
- package/src/project-browser.jsx +52 -3
- package/src/project.js +55 -24
- package/src/props.jsx +177 -36
- package/src/proxy-figure.jsx +69 -0
- package/src/range-pin-panel.jsx +0 -1
- package/src/receipt-findings.js +20 -0
- package/src/render-passes.js +0 -1
- package/src/resource-status.jsx +1 -1
- package/src/root-path.js +48 -0
- package/src/route-owner.js +52 -0
- package/src/scene-asset-cache.js +1 -2
- package/src/scene-assets.js +12 -4
- package/src/scene-fbx.js +1 -1
- package/src/scene-mesh-cache.js +0 -1
- package/src/scene-objects.js +319 -19
- package/src/scenes.js +62 -17
- package/src/sculpt-blueprint.js +65 -0
- package/src/sculpt-compare.js +384 -0
- package/src/sculpt-geometry.js +141 -0
- package/src/sculpt-mode.js +168 -0
- package/src/sculpt-recipe.js +385 -0
- package/src/sculpt-run.js +351 -0
- package/src/sculpt-stage.jsx +221 -0
- package/src/settings-menu.jsx +1 -1
- package/src/shell/BoardSlot.jsx +341 -0
- package/src/shell/BottomDock.jsx +58 -14
- package/src/shell/DetailsSlot.jsx +26 -6
- package/src/shell/MenuBar.jsx +78 -6
- package/src/shell/OutlinerSlot.jsx +3 -1
- package/src/shell/TopBar.jsx +17 -3
- package/src/shell/ViewportToolbar.jsx +18 -3
- package/src/shell/dock.css +239 -0
- package/src/shell/glass-regions.css +3 -2
- package/src/shell/log-store.js +4 -4
- package/src/shell/mode.css +20 -0
- package/src/shell/studio-shell-context.js +4 -1
- package/src/shot-authoring.js +69 -6
- package/src/stable-items.js +1 -1
- package/src/stage-attach-frames.js +169 -0
- package/src/stage-canvas-rigs.jsx +402 -0
- package/src/stage-capture.jsx +216 -0
- package/src/stage-characters.jsx +302 -0
- package/src/stage-editor-gizmos.jsx +1229 -0
- package/src/stage-labels-ko.js +204 -0
- package/src/stage-startup-defaults.js +182 -0
- package/src/starter-scenes.js +1 -1
- package/src/storyboard.js +24 -5
- package/src/studio-actions.js +42 -9
- package/src/studio-agent-commands.js +186 -23
- package/src/studio-agent-context.js +11 -9
- package/src/studio-agent-motion.js +2 -1
- package/src/studio-agent-protocol.js +56 -23
- package/src/studio-app-binding.js +52 -23
- package/src/studio-contact-sheet.js +3 -3
- package/src/studio-elements.js +11 -2
- package/src/studio-geometry-facts.js +114 -0
- package/src/styles.css +111 -10
- package/src/theme.js +1 -1
- package/src/timeline-fps.js +13 -0
- package/src/ui-scrub.js +4 -4
- package/src/ui.jsx +6 -4
- package/src/use-asset-shelf.js +60 -0
- package/src/use-camera-tutorial.js +50 -0
- package/src/use-embed-bridge.js +12 -0
- package/src/use-first-success-prompt.js +7 -0
- package/src/use-i2v-capture-modal.js +15 -0
- package/src/use-sculpt-mode.js +95 -0
- package/src/workflow/AgentPanel.jsx +110 -20
- package/src/workflow/WorkflowBuilder.jsx +1 -1
- package/src/workflow/agent-client.js +113 -45
- package/src/workflow/agent-panel.css +35 -0
- package/src/workflow/attachment-image.js +1 -1
- package/src/workflow/canvas-commands.js +3 -3
- package/src/workflow/cozy-scene-node.js +3 -3
- package/src/workflow/local-workflow.js +2 -2
- package/src/workflow/motion-input.js +3 -3
- package/src/workflow/node-schema.js +1 -1
- package/src/workflow/scene-asset-sync.js +2 -9
- package/src/workflow/shot-prompt-node.js +1 -1
- package/src/workflow/video-contract.js +1 -1
- package/src/workflow/workflow-resources.js +0 -8
- package/src/workflow-send.js +87 -0
- package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
- package/tools/check-eslint.mjs +57 -0
- package/tools/check-knip.mjs +60 -0
- package/tools/check-loc.mjs +54 -0
- package/tools/demo-worker/api-client.mjs +7 -13
- package/tools/demo-worker/generate.mjs +2 -4
- package/tools/eslint-baseline.json +23 -0
- package/tools/kimodo/effector-constraints.mjs +4 -4
- package/tools/kimodo/generate.mjs +1 -1
- package/tools/knip-baseline.json +331 -0
- package/tools/loc-baseline.json +126 -0
- package/tools/process-supervisor.mjs +1 -1
- package/tools/projflow/replay.mjs +4 -4
- package/tools/projflow/runner.mjs +3 -3
- package/tools/qa/studio-control-count.mjs +2 -0
- package/tools/run-tests.mjs +96 -7
- package/dist/assets/analytics-MVZOEW8O.js +0 -1
- package/dist/assets/app-C1sdVbg4.css +0 -1
- package/dist/assets/app-CA55tcI3.js +0 -4876
- package/dist/assets/shot-prompt-Yrt99wZx.js +0 -17
- package/dist/assets/workflow-dNPGY9xz.js +0 -179
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
// Supervisor v2 (#728): one asynchronous review of a finished Studio turn by
|
|
2
|
+
// the turn's own model at high reasoning effort. It never blocks the turn and
|
|
3
|
+
// never talks back to the agent: the sidecar starts it after the turn's stream
|
|
4
|
+
// has ended and appends exactly one `supervisor` event to the turn record,
|
|
5
|
+
// which the panel shows as a note card.
|
|
6
|
+
|
|
7
|
+
export const SUPERVISOR_RUBRIC = "You are the previs supervisor reviewing one agent turn in CozyClay Studio. Judge ONLY from the evidence given (receipts, refusal codes, geometry facts, pictures). Evaluate exactly these seven items: 1 request coverage - every deliverable the user literally asked for exists (blocker when missing); 2 impossible items - anything refused with TARGET_NOT_READY / CAPABILITY_MISSING / CONFIRMATION_REQUIRED is 'refused', not 'missing'; it is a blocker only when the reply hid the refusal from the user; 3 spatial sanity - floating or sunken objects, unintended interpenetration, subjects occluded by set pieces (concern); 4 framing - requested size vs derivedSize, clipped subjects, subject out of frame (concern); 5 continuity across shots - 180-degree line (axisConsistent, cameraSide), eyeline, OTS over the correct shoulder, screen direction (concern); 6 undo integrity - the turn is one history entry / receipts are undoable (blocker when broken); 7 report honesty - the reply claims nothing the receipts do not show and claims no verification that did not run (blocker). Capsule figures (characterKind proxy) cannot pose, sit or animate: a refused pose on one is 'refused'. Reply with JSON only: { \"items\": [{ \"text\": string, \"status\": \"done\"|\"partial\"|\"refused\"|\"missing\", \"evidence\": string }], \"issues\": [{ \"severity\": \"blocker\"|\"concern\"|\"note\", \"kind\": \"coverage\"|\"disclosure\"|\"spatial\"|\"framing\"|\"continuity\"|\"undo\"|\"honesty\", \"text\": string, \"evidence\": string }], \"summary\": string }. Write text fields in the user's language. Be concrete and short; cite ids, metres and shot names. An issue MAY carry \"fix\": { \"action\": \"<action id from the ACTIONS list>\", \"args\": { ... } } - ONE run_action call the next turn executes verbatim, with real ids and values taken from the receipts and geometry and args shaped by ACTION SCHEMAS. Examples: {\"action\":\"character.setPose\",\"args\":{\"characterId\":\"char-b\",\"posture\":\"sit\"}}, {\"action\":\"shot.frame\",\"args\":{\"shotId\":\"shot-1\",\"subjectIds\":[\"char-a\",\"char-b\"],\"framing\":{\"intent\":{\"size\":\"wide shot\",\"view\":\"front\",\"level\":\"eye\",\"side\":\"left\"}}}}, {\"action\":\"object.update\",\"args\":{\"id\":\"table-1\",\"position\":{\"x\":0,\"y\":0,\"z\":1.2}}}. Use only actions present in ACTIONS; never invent ids; never propose a pose or motion fix for a capsule figure; omit fix when no safe single action exists.";
|
|
8
|
+
|
|
9
|
+
const REFUSAL_CODES = Object.freeze(["TARGET_NOT_READY", "CAPABILITY_MISSING", "CONFIRMATION_REQUIRED"]);
|
|
10
|
+
const SUPERVISOR_LIMITS = Object.freeze({ delta: 8, warnings: 12, reply: 4000, images: 5, createdShots: 3, imageWidth: 1280, args: 600, timeoutMs: 120_000, schemaBytes: 6000 });
|
|
11
|
+
/** The actions whose input schemas the reviewer gets, so a fix it writes has
|
|
12
|
+
* the right shape (#743). Only those present in the turn's action index are
|
|
13
|
+
* asked for. */
|
|
14
|
+
const SUPERVISOR_FIX_ACTIONS = Object.freeze(["character.setPose", "character.move", "character.update", "shot.frame", "shot.set", "shot.setRange", "object.update", "object.remove", "objects.arrange", "stage.set", "edit.undo", "view.update"]);
|
|
15
|
+
const ITEM_STATUSES = ["done", "partial", "refused", "missing"];
|
|
16
|
+
const SEVERITIES = ["blocker", "concern", "note"];
|
|
17
|
+
const KINDS = ["coverage", "disclosure", "spatial", "framing", "continuity", "undo", "honesty"];
|
|
18
|
+
|
|
19
|
+
const isObject = value => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
20
|
+
const clip = (text, max) => text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
21
|
+
|
|
22
|
+
/** Pictures never ride in the digest's text: every dataUrl, at any depth, is dropped. */
|
|
23
|
+
function withoutDataUrls(value) {
|
|
24
|
+
if (Array.isArray(value)) return value.map(withoutDataUrls);
|
|
25
|
+
if (!isObject(value)) return value;
|
|
26
|
+
return Object.fromEntries(Object.entries(value).filter(([key]) => key !== "dataUrl").map(([key, entry]) => [key, withoutDataUrls(entry)]));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** One receipt, verbatim but bounded: no image bytes, at most 8 delta rows and 12 warnings. */
|
|
30
|
+
function boundReceipt(receipt) {
|
|
31
|
+
const bounded = withoutDataUrls(receipt);
|
|
32
|
+
if (!isObject(bounded)) return bounded;
|
|
33
|
+
if (Array.isArray(bounded.delta) && bounded.delta.length > SUPERVISOR_LIMITS.delta) {
|
|
34
|
+
bounded.deltaOmitted = bounded.delta.length - SUPERVISOR_LIMITS.delta;
|
|
35
|
+
bounded.delta = bounded.delta.slice(0, SUPERVISOR_LIMITS.delta);
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(bounded.warnings) && bounded.warnings.length > SUPERVISOR_LIMITS.warnings) {
|
|
38
|
+
bounded.warningsOmitted = bounded.warnings.length - SUPERVISOR_LIMITS.warnings;
|
|
39
|
+
bounded.warnings = bounded.warnings.slice(0, SUPERVISOR_LIMITS.warnings);
|
|
40
|
+
}
|
|
41
|
+
return bounded;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const refusalOf = (code, message, tool) => REFUSAL_CODES.includes(code) ? [{ code, message: clip(String(message ?? ""), 400), ...(tool ? { tool } : {}) }] : [];
|
|
45
|
+
|
|
46
|
+
/** What a turn's frames say about it: whether it authored anything, the
|
|
47
|
+
* refusals it met, its tool calls and its receipts (read-only results such as
|
|
48
|
+
* inspect_studio are calls, not receipts). */
|
|
49
|
+
function analyseTurnFrames(frames = []) {
|
|
50
|
+
const names = new Map();
|
|
51
|
+
const calls = [], receipts = [], refusals = [];
|
|
52
|
+
let authored = false;
|
|
53
|
+
for (const frame of Array.isArray(frames) ? frames : []) {
|
|
54
|
+
if (frame?.type === "tool.start") {
|
|
55
|
+
names.set(frame.callId, frame.name);
|
|
56
|
+
calls.push({ callId: frame.callId, name: frame.name, args: clip(JSON.stringify(frame.args ?? {}), SUPERVISOR_LIMITS.args) });
|
|
57
|
+
} else if (frame?.type === "tool.done") {
|
|
58
|
+
const name = names.get(frame.callId);
|
|
59
|
+
const call = calls.find(entry => entry.callId === frame.callId);
|
|
60
|
+
if (call) call.ok = frame.ok === true;
|
|
61
|
+
if (frame.ok !== true) {
|
|
62
|
+
const error = String(frame.error ?? "");
|
|
63
|
+
const match = /^([A-Z][A-Z_]+):\s*([\s\S]*)$/.exec(error);
|
|
64
|
+
if (call) call.error = clip(error, 400);
|
|
65
|
+
if (match) refusals.push(...refusalOf(match[1], match[2], name));
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const result = frame.result;
|
|
69
|
+
if (!isObject(result)) continue;
|
|
70
|
+
if (result.authored === true) authored = true;
|
|
71
|
+
if (result.ok === false) refusals.push(...refusalOf(result.code ?? result.error?.code, result.message ?? result.error?.message, name));
|
|
72
|
+
if (name !== "inspect_studio" && (typeof result.receiptId === "string" || typeof result.authored === "boolean" || isObject(result.checks))) receipts.push({ tool: name ?? null, receipt: result });
|
|
73
|
+
} else if (frame?.type === "receipt" && isObject(frame.receipt)) {
|
|
74
|
+
if (frame.receipt.authored === true) authored = true;
|
|
75
|
+
if (frame.receipt.ok === false) refusals.push(...refusalOf(frame.receipt.code, frame.receipt.message, null));
|
|
76
|
+
receipts.push({ tool: "turn", receipt: frame.receipt });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { authored, refusals, calls: calls.map(({ callId: _callId, ...call }) => call), receipts };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Pure read-only turns and questions are not reviewed: only a turn that
|
|
83
|
+
* authored something or met a refusal is. */
|
|
84
|
+
export const shouldSupervise = frames => {
|
|
85
|
+
const { authored, refusals } = analyseTurnFrames(frames);
|
|
86
|
+
return authored || refusals.length > 0;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
function imagePart(image) {
|
|
90
|
+
const match = /^data:([^;,]+);base64,(.*)$/.exec(image?.dataUrl ?? "");
|
|
91
|
+
return match ? { type: "image", data: match[2], mimeType: match[1] } : null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The ACTION SCHEMAS block: whole schemas in the order given while they fit
|
|
95
|
+
* the byte budget; the ids that did not fit are named, never half-quoted. */
|
|
96
|
+
function boundSchemas(schemas) {
|
|
97
|
+
const kept = {}, omitted = [];
|
|
98
|
+
let size = 2;
|
|
99
|
+
for (const entry of Array.isArray(schemas) ? schemas : []) {
|
|
100
|
+
if (typeof entry?.id !== "string" || !isObject(entry.input)) continue;
|
|
101
|
+
const cost = JSON.stringify({ [entry.id]: entry.input }).length;
|
|
102
|
+
if (size + cost > SUPERVISOR_LIMITS.schemaBytes) { omitted.push(entry.id); continue; }
|
|
103
|
+
kept[entry.id] = entry.input;
|
|
104
|
+
size += cost;
|
|
105
|
+
}
|
|
106
|
+
return omitted.length ? { ...kept, omitted } : kept;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The digest the reviewer reads: a compact JSON-ish text block plus at most
|
|
110
|
+
* five pi ImageContent parts, in the order the text lists them. `actions` is
|
|
111
|
+
* `{ index: [{ id, label }], schemas: [{ id, input }] }`: what a fix may name
|
|
112
|
+
* and the input shape of the pre-selected ones (#743). */
|
|
113
|
+
export function buildSupervisorInput({ request = "", frames = [], reply = "", geometry = null, refusals, images = [], notes = [], actions = null } = {}) {
|
|
114
|
+
const turn = analyseTurnFrames(frames);
|
|
115
|
+
const pictures = (Array.isArray(images) ? images : []).map(image => ({ image, part: imagePart(image) })).filter(entry => entry.part).slice(0, SUPERVISOR_LIMITS.images);
|
|
116
|
+
const section = (name, value) => `${name}: ${JSON.stringify(value)}`;
|
|
117
|
+
const text = [
|
|
118
|
+
"<turn-review>",
|
|
119
|
+
section("request", String(request ?? "")),
|
|
120
|
+
section("toolCalls", turn.calls),
|
|
121
|
+
section("receipts", turn.receipts.map(entry => ({ tool: entry.tool, ...boundReceipt(entry.receipt) }))),
|
|
122
|
+
section("refusals", Array.isArray(refusals) ? refusals : turn.refusals),
|
|
123
|
+
section("reply", clip(String(reply ?? ""), SUPERVISOR_LIMITS.reply)),
|
|
124
|
+
section("geometry", geometry ?? { note: "no geometry facts were measured" }),
|
|
125
|
+
section("images", pictures.map((entry, index) => `${index + 1} ${entry.image.label ?? "image"}${entry.image.width ? ` (${entry.image.width}x${entry.image.height})` : ""}`)),
|
|
126
|
+
...(notes.length ? [section("evidenceNotes", notes)] : []),
|
|
127
|
+
section("ACTIONS", (Array.isArray(actions?.index) ? actions.index : []).filter(row => typeof row?.id === "string").map(row => typeof row.label === "string" && row.label ? `${row.id} (${row.label})` : row.id)),
|
|
128
|
+
section("ACTION SCHEMAS", boundSchemas(actions?.schemas)),
|
|
129
|
+
"</turn-review>",
|
|
130
|
+
].join("\n");
|
|
131
|
+
return { text, images: pictures.map(entry => entry.part) };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The first balanced {...} block of a reply, string-aware. */
|
|
135
|
+
function firstJsonBlock(text) {
|
|
136
|
+
const start = text.indexOf("{");
|
|
137
|
+
if (start === -1) return null;
|
|
138
|
+
let depth = 0, inString = false, escaped = false;
|
|
139
|
+
for (let index = start; index < text.length; index += 1) {
|
|
140
|
+
const char = text[index];
|
|
141
|
+
if (inString) {
|
|
142
|
+
if (escaped) escaped = false;
|
|
143
|
+
else if (char === "\\") escaped = true;
|
|
144
|
+
else if (char === "\"") inString = false;
|
|
145
|
+
} else if (char === "\"") inString = true;
|
|
146
|
+
else if (char === "{") depth += 1;
|
|
147
|
+
else if (char === "}" && --depth === 0) return text.slice(start, index + 1);
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** The reviewer's verdict, validated, or null for anything that is not one. */
|
|
153
|
+
export function parseSupervisorVerdict(text) {
|
|
154
|
+
if (typeof text !== "string") return null;
|
|
155
|
+
const block = firstJsonBlock(text);
|
|
156
|
+
if (!block) return null;
|
|
157
|
+
let value;
|
|
158
|
+
try { value = JSON.parse(block); } catch { return null; }
|
|
159
|
+
if (!isObject(value) || typeof value.summary !== "string" || !Array.isArray(value.items) || !Array.isArray(value.issues)) return null;
|
|
160
|
+
const items = value.items.map(item => isObject(item) && typeof item.text === "string" && ITEM_STATUSES.includes(item.status) && typeof item.evidence === "string"
|
|
161
|
+
? { text: item.text, status: item.status, evidence: item.evidence } : null);
|
|
162
|
+
// A fix rides along unvalidated here; validateFixes keeps or drops it.
|
|
163
|
+
const issues = value.issues.map(issue => isObject(issue) && SEVERITIES.includes(issue.severity) && KINDS.includes(issue.kind) && typeof issue.text === "string" && typeof issue.evidence === "string"
|
|
164
|
+
? { severity: issue.severity, kind: issue.kind, text: issue.text, evidence: issue.evidence, ...(issue.fix !== undefined && issue.fix !== null ? { fix: issue.fix } : {}) } : null);
|
|
165
|
+
if (items.includes(null) || issues.includes(null)) return null;
|
|
166
|
+
return { items, issues, summary: value.summary };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The fixes a note may hand the next turn (#743). A fix survives only when its
|
|
171
|
+
* action is in the turn's action index and its args are an object carrying
|
|
172
|
+
* every key the action's schema requires (when the schema was read). Every
|
|
173
|
+
* other fix is removed from its issue and reported with the reason.
|
|
174
|
+
*/
|
|
175
|
+
export function validateFixes(issues, { index = [], schemas = [] } = {}) {
|
|
176
|
+
const known = new Set((Array.isArray(index) ? index : []).map(row => typeof row === "string" ? row : row?.id).filter(id => typeof id === "string"));
|
|
177
|
+
const inputs = new Map((Array.isArray(schemas) ? schemas : []).filter(entry => typeof entry?.id === "string" && isObject(entry.input)).map(entry => [entry.id, entry.input]));
|
|
178
|
+
const dropped = [];
|
|
179
|
+
const kept = (Array.isArray(issues) ? issues : []).map((issue, issueIndex) => {
|
|
180
|
+
if (!isObject(issue) || issue.fix === undefined) return issue;
|
|
181
|
+
const { fix, ...rest } = issue;
|
|
182
|
+
const action = typeof fix?.action === "string" ? fix.action : null;
|
|
183
|
+
const required = Array.isArray(inputs.get(action)?.required) ? inputs.get(action).required : [];
|
|
184
|
+
const missing = isObject(fix?.args) ? required.filter(key => !Object.hasOwn(fix.args, key)) : [];
|
|
185
|
+
const reason = !isObject(fix) || !action ? "malformed fix"
|
|
186
|
+
: !known.has(action) ? "unknown action"
|
|
187
|
+
: !isObject(fix.args) ? "args is not an object"
|
|
188
|
+
: missing.length ? `missing required key ${missing.map(key => `"${key}"`).join(", ")}` : null;
|
|
189
|
+
if (!reason) return { ...rest, fix: { action, args: fix.args } };
|
|
190
|
+
dropped.push({ issueIndex, action, reason });
|
|
191
|
+
return rest;
|
|
192
|
+
});
|
|
193
|
+
return { issues: kept, dropped };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const verdictCounts = issues => Object.fromEntries(SEVERITIES.map(severity => [severity, issues.filter(issue => issue.severity === severity).length]));
|
|
197
|
+
|
|
198
|
+
const redact = message => clip(String(message ?? "unknown failure").replace(/data:[^\s"']+/gi, "[image]").replace(/\b(?:sk-or-|sk-ant-|sk-|Bearer\s+|eyJ)[\w.\-+/=]+/gi, "[redacted]").replace(/\s+/g, " ").trim(), 200);
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* What the reviewer gets to see beyond the frames, read from the editor after
|
|
202
|
+
* the turn: geometry facts for the shots the turn touched (a framing
|
|
203
|
+
* verify_result the sidecar issues itself), the current shot frame, the top
|
|
204
|
+
* view and one frame per shot the turn created. Every piece is optional; what
|
|
205
|
+
* could not be read becomes a note instead of a failure.
|
|
206
|
+
*/
|
|
207
|
+
async function gatherSupervisorEvidence({ hub, workspaceHandle, admission, baseline, receipts, actionIndex = [] }) {
|
|
208
|
+
const notes = [], images = [];
|
|
209
|
+
const command = (name, args, options) => options ? hub.command(name, args, workspaceHandle, options) : hub.command(name, args, workspaceHandle);
|
|
210
|
+
// What a fix may name: the turn's own action index, and the input schemas
|
|
211
|
+
// of the pre-selected actions it declares (#743).
|
|
212
|
+
const index = (Array.isArray(actionIndex) ? actionIndex : []).filter(row => typeof row?.id === "string").map(row => ({ id: row.id, ...(typeof row.label === "string" ? { label: row.label } : {}) }));
|
|
213
|
+
const wanted = SUPERVISOR_FIX_ACTIONS.filter(id => index.some(row => row.id === id));
|
|
214
|
+
let schemas = [];
|
|
215
|
+
if (wanted.length) {
|
|
216
|
+
try {
|
|
217
|
+
const result = await command("inspect_studio", { scope: "actions", ids: wanted });
|
|
218
|
+
schemas = (Array.isArray(result?.actions) ? result.actions : []).filter(row => wanted.includes(row?.id) && isObject(row.input)).map(row => ({ id: row.id, input: row.input }));
|
|
219
|
+
} catch (error) { notes.push(`action schemas unavailable: ${redact(error?.message)}`); }
|
|
220
|
+
}
|
|
221
|
+
const envelope = (name, args) => ({ name, args, commandId: admission.commandId(), host: admission.host, expectedRevision: admission.revision });
|
|
222
|
+
let context = null;
|
|
223
|
+
try { context = await admission.refresh(); } catch (error) { notes.push(`scene context unavailable: ${redact(error?.message)}`); }
|
|
224
|
+
const shots = Array.isArray(context?.shots) ? context.shots : [];
|
|
225
|
+
const shotIds = new Set(shots.map(shot => shot.id));
|
|
226
|
+
const before = new Set((Array.isArray(baseline?.shots) ? baseline.shots : []).map(shot => shot.id));
|
|
227
|
+
const touched = [...new Set(receipts.flatMap(entry => Array.isArray(entry.receipt?.affectedIds) ? entry.receipt.affectedIds : []))].filter(id => shotIds.has(id));
|
|
228
|
+
let geometry;
|
|
229
|
+
const targets = touched.length ? touched : context?.shot?.id ? [context.shot.id] : shots[0] ? [shots[0].id] : [];
|
|
230
|
+
if (!targets.length) geometry = { note: "no shot exists; geometry skipped" };
|
|
231
|
+
else {
|
|
232
|
+
try {
|
|
233
|
+
const result = await command("verify_result", envelope("verify_result", { targets, checks: ["framing"] }));
|
|
234
|
+
geometry = isObject(result?.geometry) ? { shotIds: targets, ...result.geometry }
|
|
235
|
+
: { note: result?.ok === false ? `verify_result refused: ${result.code ?? result.error?.code ?? "error"}` : "the editor returned no geometry facts" };
|
|
236
|
+
} catch (error) { geometry = { note: `geometry unavailable: ${error?.code ? `${error.code}: ` : ""}${redact(error?.message)}` }; }
|
|
237
|
+
}
|
|
238
|
+
// The editor renders at the size it is asked for, so an oversized capture is
|
|
239
|
+
// re-requested at 1280 px wide rather than resized here.
|
|
240
|
+
const capture = async (name, label) => {
|
|
241
|
+
const named = typeof label === "string" ? label : name;
|
|
242
|
+
try {
|
|
243
|
+
let shot = await command(name, {});
|
|
244
|
+
if (shot?.width > SUPERVISOR_LIMITS.imageWidth && shot?.height > 0) {
|
|
245
|
+
const width = SUPERVISOR_LIMITS.imageWidth, height = Math.round(shot.height * width / shot.width);
|
|
246
|
+
const smaller = await command(name, { output: { width, height } });
|
|
247
|
+
if (smaller?.dataUrl?.startsWith("data:image/")) shot = smaller;
|
|
248
|
+
else notes.push(`${named} kept at ${shot.width} px wide: the editor did not render a smaller copy`);
|
|
249
|
+
}
|
|
250
|
+
if (!shot?.dataUrl?.startsWith("data:image/")) { notes.push(`${named} unavailable: the editor returned no image`); return; }
|
|
251
|
+
images.push({ label: typeof label === "function" ? label(shot) : label, dataUrl: shot.dataUrl, width: shot.width, height: shot.height });
|
|
252
|
+
} catch (error) { notes.push(`${named} unavailable: ${redact(error?.message)}`); }
|
|
253
|
+
};
|
|
254
|
+
await capture("capture_framing_png", shot => `current shot frame (shot ${shot.shotId ?? "none"}, frame ${shot.frame ?? "?"})`);
|
|
255
|
+
await capture("capture_plan_png", "top view (plan) with subject labels and the shot camera wedge");
|
|
256
|
+
const created = touched.filter(id => !before.has(id)).map(id => shots.find(shot => shot.id === id)).filter(shot => Number.isSafeInteger(shot?.range?.startFrame)).slice(0, SUPERVISOR_LIMITS.createdShots);
|
|
257
|
+
const original = context?.view?.frame;
|
|
258
|
+
if (created.length && Number.isSafeInteger(original)) {
|
|
259
|
+
const operate = async frame => {
|
|
260
|
+
const receipt = await command("operate_studio", envelope("operate_studio", { frame }));
|
|
261
|
+
if (Number.isSafeInteger(receipt?.revision?.after)) admission.revision = receipt.revision.after;
|
|
262
|
+
if (receipt?.ok === false) throw Object.assign(new Error(receipt.message ?? "operate_studio refused"), { code: receipt.code });
|
|
263
|
+
};
|
|
264
|
+
try {
|
|
265
|
+
for (const shot of created) {
|
|
266
|
+
if (images.length >= SUPERVISOR_LIMITS.images) break;
|
|
267
|
+
try { await operate(shot.range.startFrame); }
|
|
268
|
+
catch (error) { notes.push(`shot ${shot.id} frame unavailable: ${redact(error?.message)}`); continue; }
|
|
269
|
+
await capture("capture_framing_png", `created shot ${shot.name ?? shot.id} (${shot.id}) at its start frame ${shot.range.startFrame}`);
|
|
270
|
+
}
|
|
271
|
+
} finally {
|
|
272
|
+
try { await operate(original); } catch (error) { notes.push(`playhead not restored to frame ${original}: ${redact(error?.message)}`); }
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return { geometry, images: images.slice(0, SUPERVISOR_LIMITS.images), notes, actions: { index, schemas } };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Review one finished turn. Never throws: any failure becomes a frame with
|
|
280
|
+
* verdict "unavailable" and the reason.
|
|
281
|
+
*/
|
|
282
|
+
export async function runSupervisor({ registry, modelKey, effort = "high", turnId, request, frames, reply, hub, workspaceHandle, admission, baseline, actions = [], now = () => Date.now(), timeoutMs = SUPERVISOR_LIMITS.timeoutMs }) {
|
|
283
|
+
const startedAt = now();
|
|
284
|
+
let thinking = null;
|
|
285
|
+
const frame = (fields) => ({ type: "supervisor", turnId, model: modelKey, effort: thinking, elapsedMs: Math.max(0, Math.round(now() - startedAt)), ...fields });
|
|
286
|
+
const unavailable = reason => frame({ verdict: "unavailable", summary: "", items: [], issues: [], fixes: [], droppedFixes: [], counts: { ...verdictCounts([]), fixes: 0 }, reason: redact(reason) });
|
|
287
|
+
try {
|
|
288
|
+
const { resolveModel, resolveEffort } = await import("./providers.mjs");
|
|
289
|
+
const slash = modelKey.indexOf("/");
|
|
290
|
+
const provider = slash === -1 ? "openai-codex" : modelKey.slice(0, slash);
|
|
291
|
+
const model = registry.getModel(provider, slash === -1 ? modelKey : modelKey.slice(slash + 1)) ?? (await resolveModel(modelKey, { models: registry })).model;
|
|
292
|
+
const level = await resolveEffort(model, effort);
|
|
293
|
+
thinking = level;
|
|
294
|
+
const turn = analyseTurnFrames(frames);
|
|
295
|
+
const evidence = await gatherSupervisorEvidence({ hub, workspaceHandle, admission, baseline, receipts: turn.receipts, actionIndex: actions });
|
|
296
|
+
const input = buildSupervisorInput({ request, frames, reply, geometry: evidence.geometry, refusals: turn.refusals, images: evidence.images, notes: evidence.notes, actions: evidence.actions });
|
|
297
|
+
const message = await registry.completeSimple(model, {
|
|
298
|
+
systemPrompt: SUPERVISOR_RUBRIC,
|
|
299
|
+
messages: [{ role: "user", content: [{ type: "text", text: input.text }, ...input.images], timestamp: Date.now() }],
|
|
300
|
+
}, { ...(level && level !== "off" ? { reasoning: level } : {}), signal: AbortSignal.timeout(timeoutMs) });
|
|
301
|
+
if (message?.stopReason === "error" || message?.stopReason === "aborted") return unavailable(message.errorMessage || `the reviewer request ${message.stopReason === "aborted" ? "timed out" : "failed"}`);
|
|
302
|
+
const text = (Array.isArray(message?.content) ? message.content : []).filter(part => part?.type === "text").map(part => part.text).join("");
|
|
303
|
+
const verdict = parseSupervisorVerdict(text);
|
|
304
|
+
if (!verdict) return unavailable("the reviewer did not answer with a valid verdict");
|
|
305
|
+
const { issues, dropped } = validateFixes(verdict.issues, evidence.actions);
|
|
306
|
+
const fixes = issues.flatMap((issue, issueIndex) => issue.fix ? [{ issueIndex, severity: issue.severity, action: issue.fix.action, args: issue.fix.args }] : []);
|
|
307
|
+
return frame({ verdict: "reviewed", summary: verdict.summary, items: verdict.items, issues, fixes, droppedFixes: dropped, counts: { ...verdictCounts(issues), fixes: fixes.length } });
|
|
308
|
+
} catch (error) {
|
|
309
|
+
return unavailable(error?.message);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
@@ -154,13 +154,6 @@ function preferredVideoOutputNodes(workflow) {
|
|
|
154
154
|
const isH3Node = (node) => /minimax.?h3|h3.*video/i.test(String(node?.class_type || ""));
|
|
155
155
|
const isLoadImageNode = (node) => /loadimage/i.test(String(node?.class_type || ""));
|
|
156
156
|
const isCompositeNode = (node) => /imagecomposite|compositeimage|imagemattecomposite/i.test(String(node?.class_type || ""));
|
|
157
|
-
const isMaskNode = (node) => {
|
|
158
|
-
if (isCompositeNode(node)) return false;
|
|
159
|
-
// A generic/static mask can hide a broken tracker and would not preserve a
|
|
160
|
-
// moving performer across frames. Only accept a temporal SAM3/tracked-mask
|
|
161
|
-
// node on the compositor path.
|
|
162
|
-
return /sam3[_-]?(videotrack|tracktomask)|tracktomask|videomask/i.test(String(node?.class_type || ""));
|
|
163
|
-
};
|
|
164
157
|
|
|
165
158
|
function h3GuardError(message, reason) {
|
|
166
159
|
return Object.assign(new Error(message), {
|
package/bin/codex-auth.mjs
CHANGED
|
@@ -6,11 +6,12 @@ import { join } from "node:path";
|
|
|
6
6
|
import { openBrowser } from "./open-browser.mjs";
|
|
7
7
|
import { writeSecureJson } from "./agent/secure-file.mjs";
|
|
8
8
|
import { readKeys } from "./agent/provider-keys.mjs";
|
|
9
|
+
import { PROVIDERS } from "./agent/providers.mjs";
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
const AUTHORIZE_ENDPOINT = "https://auth.openai.com/oauth/authorize";
|
|
12
|
+
const TOKEN_ENDPOINT = "https://auth.openai.com/oauth/token";
|
|
13
|
+
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
14
|
+
const SCOPE = "openid profile email offline_access";
|
|
14
15
|
const DEFAULT_FILE = join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "cozyclay", "codex-auth.json");
|
|
15
16
|
const tokenFile = process.env.COZYCLAY_CODEX_AUTH_FILE || DEFAULT_FILE;
|
|
16
17
|
let tokens = null;
|
|
@@ -51,7 +52,7 @@ const decodeJwt = (value) => { try { return JSON.parse(Buffer.from(value.split("
|
|
|
51
52
|
const claims = () => decodeJwt(readTokens()?.id_token || "");
|
|
52
53
|
function providersConfigured() {
|
|
53
54
|
const configured = new Set();
|
|
54
|
-
for (const
|
|
55
|
+
for (const provider of PROVIDERS) for (const name of provider.env) if (process.env[name]?.trim()) configured.add(provider.id);
|
|
55
56
|
for (const [id, value] of Object.entries(readKeys())) if (typeof value === "string" && value.trim()) configured.add(id);
|
|
56
57
|
return configured.size;
|
|
57
58
|
}
|
package/bin/cozyclay.mjs
CHANGED
|
@@ -407,10 +407,13 @@ process.on("SIGTERM", () => shutdown(143));
|
|
|
407
407
|
|
|
408
408
|
if (opts.motion && kimodoHost && existsSync(BRIDGE)) {
|
|
409
409
|
try {
|
|
410
|
+
// The package has no three (a devDependency); the bridge runs from the
|
|
411
|
+
// staged MCP runtime, which carries three, tools/ and src/.
|
|
412
|
+
const bridgeRoot = SOURCE_CHECKOUT ? PKG_ROOT : await ensureMcpRuntime();
|
|
410
413
|
({ child: bridge, port: bridgePort } = await startBridge({
|
|
411
414
|
command: process.execPath,
|
|
412
|
-
args: [
|
|
413
|
-
cwd:
|
|
415
|
+
args: [join(bridgeRoot, "tools", "ardy", "bridge.mjs")],
|
|
416
|
+
cwd: bridgeRoot,
|
|
414
417
|
env: process.env,
|
|
415
418
|
mainPort: opts.port,
|
|
416
419
|
onSpawn: (child) => {
|
|
@@ -431,6 +434,7 @@ if (opts.motion && kimodoHost && existsSync(BRIDGE)) {
|
|
|
431
434
|
}));
|
|
432
435
|
} catch (err) {
|
|
433
436
|
console.error(`cozyclay: motion generation sidecar failed: ${err.message}`);
|
|
437
|
+
for (const line of err.lines ?? []) console.error(`cozyclay: ${line}`);
|
|
434
438
|
console.error("cozyclay: studio did not start; set COZYCLAY_BRIDGE_PORT to an available port or use --no-motion.");
|
|
435
439
|
if (bridge) await terminateOwned(bridge);
|
|
436
440
|
process.exit(1);
|
|
@@ -455,7 +459,12 @@ server = createServer((req, res) => {
|
|
|
455
459
|
return;
|
|
456
460
|
}
|
|
457
461
|
if (url.pathname.startsWith("/agent/")) {
|
|
458
|
-
void agentHandler(req, res, url.pathname).then((handled) => { if (!handled && !res.writableEnded) { res.writeHead(404); res.end(); } }).catch(() => {
|
|
462
|
+
void agentHandler(req, res, url.pathname).then((handled) => { if (!handled && !res.writableEnded) { res.writeHead(404); res.end(); } }).catch((error) => {
|
|
463
|
+
// The pane only sees "502 — agent unavailable"; the reason belongs on
|
|
464
|
+
// the launcher's terminal rather than nowhere.
|
|
465
|
+
console.error(`cozyclay: agent request ${url.pathname} failed: ${error?.message ?? error}`);
|
|
466
|
+
if (!res.headersSent) { res.writeHead(502); res.end(JSON.stringify({ error: "agent unavailable" })); }
|
|
467
|
+
});
|
|
459
468
|
return;
|
|
460
469
|
}
|
|
461
470
|
if (url.pathname === "/__cozyclay/telemetry") {
|
package/bin/live/cli.mjs
CHANGED
|
@@ -247,7 +247,6 @@ function patchArguments(flags, verb) {
|
|
|
247
247
|
const target = colon < 0 ? { kind: raw } : { kind: raw.slice(0, colon), id: raw.slice(colon + 1) };
|
|
248
248
|
required(flags, "--set", verb);
|
|
249
249
|
const set = jsonFlag(flags, "--set");
|
|
250
|
-
if (set === null || typeof set !== "object" || Array.isArray(set)) throw new UsageError(`${verb} expects --set '{"path": value}'`);
|
|
251
250
|
return { ops: [{ target, set }] };
|
|
252
251
|
}
|
|
253
252
|
|
|
@@ -320,7 +319,7 @@ async function runVerb({ client, verb, spec, name, flags }) {
|
|
|
320
319
|
}
|
|
321
320
|
};
|
|
322
321
|
const admitted = async (commandName, args, { context, options = forwarded } = {}) => {
|
|
323
|
-
const envelope = admissionEnvelope(commandName, args, context ??
|
|
322
|
+
const envelope = admissionEnvelope(commandName, args, context ?? await command("inspect_studio", { scope: "selection" }));
|
|
324
323
|
try {
|
|
325
324
|
const value = await command(commandName, envelope, options);
|
|
326
325
|
if (value?.ok === false) throw receiptFailure(value);
|
|
@@ -446,7 +445,7 @@ async function runVerb({ client, verb, spec, name, flags }) {
|
|
|
446
445
|
// at and the deadline it needs, which --timeout overrides.
|
|
447
446
|
const inspected = await command("inspect_studio", { scope: "actions", ids: [name] });
|
|
448
447
|
const deadline = timeoutMs ?? inspected?.actions?.find((row) => row.id === name)?.timeoutMs;
|
|
449
|
-
return admitted("run_action", { action: name, args }, { context: inspected
|
|
448
|
+
return admitted("run_action", { action: name, args }, { context: inspected, options: deadline === undefined ? {} : { timeoutMs: deadline } });
|
|
450
449
|
}
|
|
451
450
|
|
|
452
451
|
if (verb === "cmd") return raw(() => command(name, flags.has("--args") ? jsonFlag(flags, "--args") : {}));
|
package/bin/live/client.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { randomUUID } from "node:crypto";
|
|
|
11
11
|
|
|
12
12
|
import { readLiveEndpoint } from "../live-endpoint.mjs";
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
const DEFAULT_LIVE_PORT = 5184;
|
|
15
15
|
/** The hub caps one command at 300 s; give up a little past that, so a hub
|
|
16
16
|
* that answers nothing at all can never hang a terminal forever. */
|
|
17
17
|
const REQUEST_CEILING_MS = 330_000;
|
package/bin/live/studio.mjs
CHANGED
|
@@ -48,8 +48,11 @@ export const describeEditors = (editors) => editors.map((editor) => ({
|
|
|
48
48
|
* carries no per-entity tokens, because the scene revision already bumps on
|
|
49
49
|
* every authored change and a retired token only refuses a legitimate retry.
|
|
50
50
|
*/
|
|
51
|
-
export function admissionEnvelope(name, args,
|
|
52
|
-
|
|
51
|
+
export function admissionEnvelope(name, args, inspected) {
|
|
52
|
+
// inspect_studio answers the host and revision at its top level; an older
|
|
53
|
+
// editor embedded a whole context. Admission needs only those two.
|
|
54
|
+
const context = inspected?.context ?? inspected;
|
|
55
|
+
if (!context?.host || !context.revision) {
|
|
53
56
|
throw new LiveCliError("EDITOR_ERROR", "The editor did not return a Studio context to admit this command against.");
|
|
54
57
|
}
|
|
55
58
|
const host = Object.fromEntries(IDENTITY.map((key) => [key, context.host[key]]));
|
package/bin/mcp-runtime.mjs
CHANGED
|
@@ -18,8 +18,9 @@ const MCP_RUNTIME = process.env.COZYCLAY_MCP_RUNTIME_DIR || join(
|
|
|
18
18
|
function probeMcpRuntime(runtime = MCP_RUNTIME) {
|
|
19
19
|
const server = join(runtime, "mcp", "server.mjs");
|
|
20
20
|
if (!existsSync(server)) throw new Error("MCP server is missing");
|
|
21
|
+
if (!existsSync(join(runtime, "tools", "ardy", "bridge.mjs"))) throw new Error("motion bridge is missing");
|
|
21
22
|
const runtimeRequire = createRequire(join(runtime, "package.json"));
|
|
22
|
-
for (const dependency of ["@modelcontextprotocol/sdk/server/mcp.js", "three", "ws", "zod"]) runtimeRequire.resolve(dependency);
|
|
23
|
+
for (const dependency of ["@modelcontextprotocol/sdk/server/mcp.js", "jpeg-js", "three", "ws", "zod"]) runtimeRequire.resolve(dependency);
|
|
23
24
|
return server;
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -46,6 +47,15 @@ function installMcpRuntime() {
|
|
|
46
47
|
// The server publishes its live endpoint file from this module.
|
|
47
48
|
copyFileSync(join(PKG_ROOT, "bin", "live-endpoint.mjs"), join(staging, "bin", "live-endpoint.mjs"));
|
|
48
49
|
cpSync(join(PKG_ROOT, "src"), join(staging, "src"), { recursive: true });
|
|
50
|
+
// The motion bridge imports three, which only this runtime resolves, so the
|
|
51
|
+
// package launches it from here; its out/ scratch dirs are never copied.
|
|
52
|
+
for (const dir of ["ardy", "kimodo", "projflow"]) {
|
|
53
|
+
cpSync(join(PKG_ROOT, "tools", dir), join(staging, "tools", dir), { recursive: true, filter: (path) => basename(path) !== "out" });
|
|
54
|
+
}
|
|
55
|
+
copyFileSync(join(PKG_ROOT, "tools", "process-supervisor.mjs"), join(staging, "tools", "process-supervisor.mjs"));
|
|
56
|
+
// gvhmr-floor.mjs loads its FBX rigs from ../../{public,dist}/models.
|
|
57
|
+
const models = join(PKG_ROOT, "dist", "models");
|
|
58
|
+
if (existsSync(models)) cpSync(models, join(staging, "dist", "models"), { recursive: true });
|
|
49
59
|
|
|
50
60
|
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
51
61
|
const child = spawn(npm, ["ci", "--no-audit", "--no-fund"], {
|
|
@@ -73,7 +83,7 @@ function installMcpRuntime() {
|
|
|
73
83
|
} catch {
|
|
74
84
|
try {
|
|
75
85
|
renameSync(staging, MCP_RUNTIME);
|
|
76
|
-
} catch
|
|
86
|
+
} catch {
|
|
77
87
|
// Another first run can publish its complete staging
|
|
78
88
|
// directory first. Accept that winner only after the
|
|
79
89
|
// same full probe; never execute a partial cache.
|
package/bin/telemetry-state.mjs
CHANGED
|
@@ -3,8 +3,8 @@ import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
4
|
|
|
5
5
|
export const TELEMETRY_NOTICE_VERSION = 1;
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const POSTHOG_PROJECT_TOKEN = "phc_CpizzZ8VhSorSS8yEeQhdpUcvB2erp5xkCbnFD8HTJ5m";
|
|
7
|
+
const POSTHOG_API_HOST = "https://t.cozyclay.org";
|
|
8
8
|
// First-launch answers plus the `npx cozyclay --via <source>` values the
|
|
9
9
|
// landing page's copy commands carry (#466). Mirrors analytics HEARD_FROM_SOURCES.
|
|
10
10
|
export const FIRST_LAUNCH_SOURCES = Object.freeze(["x", "hn", "reddit", "github", "friend", "other", "site", "playground"]);
|
|
@@ -111,7 +111,7 @@ export function takeRuntimeTelemetryConfig(
|
|
|
111
111
|
) {
|
|
112
112
|
const existing = readTelemetryState(stateFile);
|
|
113
113
|
const telemetryEnabled = officialPackage && effectiveTelemetryEnabled(existing, env);
|
|
114
|
-
const internalQa = telemetryEnabled && existing.internalQa
|
|
114
|
+
const internalQa = telemetryEnabled && existing.internalQa;
|
|
115
115
|
let installationId = existing.installationId;
|
|
116
116
|
const firstLaunch = telemetryEnabled && !existing.firstLaunchedAt;
|
|
117
117
|
const patch = {};
|
package/dist/app/index.html
CHANGED
|
@@ -34,13 +34,13 @@
|
|
|
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-DwP-tl2x.js"></script>
|
|
38
38
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
|
|
39
|
-
<link rel="modulepreload" crossorigin href="/assets/analytics-
|
|
40
|
-
<link rel="modulepreload" crossorigin href="/assets/first-shot-handoff-
|
|
41
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
42
|
-
<link rel="stylesheet" crossorigin href="/assets/
|
|
43
|
-
<link rel="stylesheet" crossorigin href="/assets/app-
|
|
39
|
+
<link rel="modulepreload" crossorigin href="/assets/analytics-B41FmPYI.js">
|
|
40
|
+
<link rel="modulepreload" crossorigin href="/assets/first-shot-handoff-BlT33CGf.js">
|
|
41
|
+
<link rel="modulepreload" crossorigin href="/assets/scene-asset-sync-BCWDJB1d.js">
|
|
42
|
+
<link rel="stylesheet" crossorigin href="/assets/scene-asset-sync-DjJ_cJ2R.css">
|
|
43
|
+
<link rel="stylesheet" crossorigin href="/assets/app-Do-Og5Pu.css">
|
|
44
44
|
</head>
|
|
45
45
|
<body>
|
|
46
46
|
<div id="root">
|