cozyclay 1.9.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +171 -0
- package/bin/agent/agent-routes.mjs +74 -70
- package/bin/agent/agent-runner.mjs +10 -3
- package/bin/agent/motion-runtime.mjs +32 -260
- package/bin/agent/providers.mjs +46 -3
- package/bin/agent/session-store.mjs +41 -5
- package/bin/agent/studio-prompt.mjs +1 -1
- package/bin/agent/studio-tools.mjs +48 -11
- package/bin/cozyclay.mjs +26 -8
- package/bin/live/cli.mjs +22 -3
- package/bin/mcp-runtime.mjs +35 -20
- package/bin/telemetry-state.mjs +5 -2
- package/bin/update-check.mjs +15 -0
- package/dist/app/index.html +6 -6
- package/dist/assets/analytics-MVZOEW8O.js +1 -0
- package/dist/assets/app-C1sdVbg4.css +1 -0
- package/dist/assets/app-CA55tcI3.js +4876 -0
- package/dist/assets/{demo-B-eg1CT8.js → demo-ByeYp-dY.js} +1 -1
- package/dist/assets/{first-shot-handoff-CGgnvNu8.js → first-shot-handoff-DgT28ZiV.js} +1 -1
- package/dist/assets/{landing-IbRp3FnQ.js → landing-BaXkpFip.js} +1 -1
- package/dist/assets/shot-prompt-CbqWcWYi.css +1 -0
- package/dist/assets/shot-prompt-Yrt99wZx.js +17 -0
- package/dist/assets/{ticket-BgIzOBOf.js → ticket-BcmsnHn5.js} +1 -1
- package/dist/assets/workflow-DRdwoDBT.css +1 -0
- package/dist/assets/workflow-dNPGY9xz.js +179 -0
- package/dist/cozyclay-package.json +1 -1
- package/dist/fonts/IBM-Plex-Sans-OFL.txt +92 -0
- package/dist/fonts/JetBrains-Mono-OFL.txt +93 -0
- package/dist/fonts/README.md +13 -9
- package/dist/fonts/ibm-plex-sans-400-latin.woff2 +0 -0
- package/dist/fonts/ibm-plex-sans-500-latin.woff2 +0 -0
- package/dist/fonts/ibm-plex-sans-600-latin.woff2 +0 -0
- package/dist/fonts/jetbrains-mono-400-latin.woff2 +0 -0
- package/dist/fonts/jetbrains-mono-500-latin.woff2 +0 -0
- package/dist/index.html +7 -7
- package/dist/privacy/index.html +13 -8
- package/dist/sitemap.xml +6 -6
- package/dist/workflow/index.html +5 -5
- package/mcp/LIVE-PROTOCOL.md +1 -1
- package/mcp/live-hub.mjs +35 -93
- package/mcp/mesh-file.mjs +48 -0
- package/mcp/server.mjs +6 -77
- package/mcp/tool-handlers.mjs +224 -377
- package/package.json +2 -1
- package/src/App.jsx +1273 -9075
- package/src/analytics.js +393 -9
- package/src/app-context.js +211 -0
- package/src/app-stage.jsx +25 -24
- package/src/ardy/auto-fix-panel.css +108 -0
- package/src/ardy/collision-blockers.js +9 -3
- package/src/ardy/fix-collisions.js +10 -66
- package/src/ardy/ground.js +4 -4
- package/src/ardy/ik-drag.js +101 -0
- package/src/ardy/ik-key-json.js +43 -0
- package/src/ardy/ik.js +122 -76
- package/src/ardy/physics-panel.css +2 -0
- package/src/ardy/physics-panel.jsx +17 -5
- package/src/ardy/platform-fit-panel.css +64 -0
- package/src/ardy/platform-fit-panel.jsx +40 -0
- package/src/ardy/platform-fit.js +326 -0
- package/src/ardy/playback.js +166 -20
- package/src/ardy/range-pin.js +299 -0
- package/src/ardy/timeline-coordinates.js +26 -0
- package/src/ardy/timeline.css +1199 -0
- package/src/ardy/timeline.jsx +149 -41
- package/src/ardy/waypoints.js +2 -2
- package/src/asset-pane.css +462 -0
- package/src/asset-pane.jsx +461 -232
- package/src/command-bus.js +349 -0
- package/src/commands/ai.js +31 -0
- package/src/commands/cast.js +140 -0
- package/src/commands/elements/character.js +13 -0
- package/src/commands/elements/motion.js +14 -0
- package/src/commands/elements/object.js +22 -0
- package/src/commands/elements/scene.js +16 -0
- package/src/commands/elements/shot.js +9 -0
- package/src/commands/elements/stage.js +11 -0
- package/src/commands/elements.js +155 -0
- package/src/commands/export.js +20 -0
- package/src/commands/index.js +40 -0
- package/src/commands/motion.js +231 -0
- package/src/commands/objects.js +174 -0
- package/src/commands/project.js +53 -0
- package/src/commands/scene.js +81 -0
- package/src/commands/shared.js +19 -0
- package/src/commands/shot.js +103 -0
- package/src/commands/stage.js +17 -0
- package/src/commands/view.js +42 -0
- package/src/document-store.js +144 -0
- package/src/domains/cast.js +1007 -0
- package/src/domains/motion.js +3219 -0
- package/src/domains/objects.js +932 -0
- package/src/domains/scenes.js +817 -0
- package/src/domains/shots.js +404 -0
- package/src/domains/stage.js +93 -0
- package/src/dualview.jsx +113 -57
- package/src/facing-marks.js +3 -0
- package/src/first-success-guide.jsx +14 -14
- package/src/grid-view.js +18 -5
- package/src/hierarchy-model.js +24 -16
- package/src/hierarchy-panel.css +386 -0
- package/src/hierarchy-panel.jsx +150 -21
- package/src/{fal-motion-client.js → i2v-motion-client.js} +13 -13
- package/src/i2v-motion-studio.jsx +180 -0
- package/src/ik-camera.js +3 -0
- package/src/main.jsx +6 -0
- package/src/motion/generation.js +105 -0
- package/src/motion-readiness-ui.jsx +4 -4
- package/src/motion-readiness.js +11 -0
- package/src/motion-trail.js +366 -9
- package/src/object-gizmo.jsx +7 -1
- package/src/otio.js +2 -1
- package/src/panels/CameraPanel.jsx +60 -0
- package/src/panels/CharacterTransformPanel.jsx +48 -0
- package/src/panels/EnvironmentPanel.jsx +40 -0
- package/src/panels/Foldout.jsx +32 -0
- package/src/panels/LightPanel.jsx +24 -0
- package/src/panels/ObjectTransformPanel.jsx +554 -0
- package/src/panels/PosePanel.jsx +97 -0
- package/src/panels/ProjectPanel.jsx +33 -0
- package/src/panels/PromptBlocksPanel.jsx +442 -0
- package/src/panels/PropsPanel.jsx +70 -0
- package/src/panels/ReferenceImageField.jsx +91 -0
- package/src/panels/RigControlPanel.jsx +78 -0
- package/src/panels/RigPanel.jsx +37 -0
- package/src/panels/SubjectBox.jsx +47 -0
- package/src/panels/SubjectsPanel.jsx +39 -0
- package/src/panels/VideoCapturePanel.jsx +180 -0
- package/src/panels/details.css +1171 -0
- package/src/panels/motion.css +197 -0
- package/src/panels/pose.css +39 -0
- package/src/planview.jsx +60 -31
- package/src/posestudio.jsx +59 -4
- package/src/project-browser.css +1073 -0
- package/src/project-browser.jsx +284 -123
- package/src/range-pin-object-transform.js +19 -0
- package/src/range-pin-panel.css +518 -0
- package/src/range-pin-panel.jsx +344 -0
- package/src/result-modal.jsx +5 -5
- package/src/room.jsx +79 -51
- package/src/scene-objects.js +37 -1
- package/src/scenes.js +5 -0
- package/src/semantic-edit.js +2 -0
- package/src/settings-menu.jsx +39 -105
- package/src/shell/BottomDock.jsx +450 -0
- package/src/shell/DetailsSlot.jsx +513 -0
- package/src/shell/LibrarySlot.jsx +86 -0
- package/src/shell/MenuBar.jsx +601 -0
- package/src/shell/OutlinerSlot.jsx +50 -0
- package/src/shell/PreferencesDialog.jsx +538 -0
- package/src/shell/PreferencesSlot.jsx +40 -0
- package/src/shell/StatusBar.jsx +125 -0
- package/src/shell/StudioShell.jsx +92 -0
- package/src/shell/TopBar.jsx +129 -0
- package/src/shell/ViewportToolbar.jsx +656 -0
- package/src/shell/agent-glass.css +296 -0
- package/src/shell/dock.css +93 -0
- package/src/shell/glass-regions.css +985 -0
- package/src/shell/glass.css +687 -0
- package/src/shell/log-store.js +52 -0
- package/src/shell/mode.css +9 -0
- package/src/shell/preferences.css +651 -0
- package/src/shell/shell.css +282 -0
- package/src/shell/studio-shell-context.js +19 -0
- package/src/shell/topbar.css +435 -0
- package/src/shell/viewport.css +451 -0
- package/src/store/authored-intent.js +22 -0
- package/src/store/runtime-adapters.js +92 -0
- package/src/store/scene-stage.js +21 -0
- package/src/store/use-document-store.js +14 -0
- package/src/studio-actions.js +210 -0
- package/src/studio-agent-commands.js +41 -271
- package/src/studio-agent-context.js +39 -10
- package/src/studio-agent-motion.js +122 -373
- package/src/studio-agent-protocol.js +67 -31
- package/src/studio-app-binding.js +379 -0
- package/src/studio-contact-sheet.js +73 -0
- package/src/studio-elements.js +58 -51
- package/src/styles/themes.css +200 -0
- package/src/styles/tokens.css +69 -0
- package/src/styles.css +460 -1742
- package/src/theme.js +44 -0
- package/src/timeline-extent.js +16 -0
- package/src/trail-key-conflicts.js +39 -0
- package/src/trail-pick.js +59 -0
- package/src/ui.jsx +8 -8
- package/src/use-case-question.jsx +66 -0
- package/src/workflow/AgentPanel.jsx +82 -69
- package/src/workflow/agent-client.js +12 -2
- package/src/workflow/agent-panel.css +34 -32
- package/src/workflow/cozy-scene-node.css +2 -0
- package/src/workflow/workflow.css +2 -0
- package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
- package/tools/ardy/bridge.mjs +164 -146
- package/tools/ardy/visual-qa.mjs +5 -5
- package/tools/bench/EXP3.md +61 -0
- package/tools/bench/cclay_bench_extract_incam.py +125 -0
- package/tools/bench/cclay_bench_extract_obs.py +204 -0
- package/tools/bench/cclay_bench_runner.py +42 -0
- package/tools/bench/cube-contact.mjs +162 -0
- package/tools/bench/exp3.mjs +152 -0
- package/tools/bench/extract-bench-lib.mjs +216 -0
- package/tools/bench/extract-bench.mjs +302 -0
- package/tools/bench/fal-generate.mjs +88 -0
- package/tools/bench/fit/README.md +189 -0
- package/tools/bench/fit/camera.mjs +59 -0
- package/tools/bench/fit/contact.mjs +419 -0
- package/tools/bench/fit/footlock.mjs +234 -0
- package/tools/bench/fit/motion.mjs +85 -0
- package/tools/bench/fit/pin.mjs +27 -0
- package/tools/bench/fit/remote.mjs +105 -0
- package/tools/bench/fit-bench.mjs +126 -0
- package/tools/bench/fit-sanity.mjs +45 -0
- package/tools/bench/metrics.mjs +313 -0
- package/tools/bench/obs/depth.mjs +258 -0
- package/tools/bench/obs/extrinsics.mjs +175 -0
- package/tools/bench/obs/fit_mannequin_betas.py +340 -0
- package/tools/bench/obs/ground.mjs +294 -0
- package/tools/bench/obs/heading.mjs +151 -0
- package/tools/bench/obs/ladder.mjs +555 -0
- package/tools/bench/obs/mannequin-betas.json +124 -0
- package/tools/bench/obs/remote.mjs +83 -0
- package/tools/bench/obs/rest_joints.py +52 -0
- package/tools/bench/obs/ybot-targets.mjs +49 -0
- package/tools/bench/obs-bench.mjs +520 -0
- package/tools/bench/score.mjs +343 -0
- package/tools/bench/summarize.mjs +86 -0
- package/tools/dev/pages/privacy.html +13 -8
- package/tools/gt-render/browser.mjs +159 -0
- package/tools/gt-render/camera-math.mjs +276 -0
- package/tools/gt-render/page.mjs +267 -0
- package/tools/gt-render/render.mjs +514 -0
- package/tools/gt-render/scene-box.mjs +31 -0
- package/tools/gt-render/take-transform.mjs +87 -0
- package/tools/morphgs/assets/gen_truth.py +39 -0
- package/tools/morphgs/assets/mesh_ori_rig.txt +27 -0
- package/tools/morphgs/assets/playback-check.mjs +30 -0
- package/tools/morphgs/demo-gate.sh +26 -0
- package/tools/morphgs/fbx2morphgs.mjs +68 -0
- package/tools/morphgs/morphgs-to-cskel27.mjs +105 -0
- package/tools/morphgs/patches/preprocess_src-none-mode.patch +76 -0
- package/tools/morphgs/setup-on-cluster.sh +126 -0
- package/tools/qa/css-rule-usage.mjs +283 -0
- package/tools/qa/studio-control-count.mjs +70 -32
- package/tools/run-tests.mjs +134 -12
- package/tools/track/DESIGN.md +513 -0
- package/tools/track/backfill-provenance.mjs +116 -0
- package/tools/track/budget.mjs +8 -0
- package/tools/track/check-rig.mjs +209 -0
- package/tools/track/diagnostics.schema.json +60 -0
- package/tools/track/export-rig.mjs +256 -0
- package/tools/track/fallback.mjs +7 -0
- package/tools/track/fk-parity-fixture.mjs +161 -0
- package/tools/track/gate.mjs +349 -0
- package/tools/track/masks.mjs +318 -0
- package/tools/track/metrics.mjs +201 -0
- package/tools/track/publish-obs.mjs +108 -0
- package/tools/track/py/check_env.py +61 -0
- package/tools/track/py/eval_lr.py +229 -0
- package/tools/track/py/lr_viterbi.py +294 -0
- package/tools/track/py/masks.py +405 -0
- package/tools/track/py/objective.py +382 -0
- package/tools/track/py/rig.py +508 -0
- package/tools/track/py/scene.py +326 -0
- package/tools/track/py/test_joint_indices.py +123 -0
- package/tools/track/py/test_lr_viterbi.py +195 -0
- package/tools/track/py/test_masks.py +229 -0
- package/tools/track/py/test_rig.py +288 -0
- package/tools/track/py/test_scene.py +354 -0
- package/tools/track/py/test_track.py +328 -0
- package/tools/track/py/track.py +411 -0
- package/tools/track/remote.mjs +140 -0
- package/tools/track/rig-dump.mjs +227 -0
- package/tools/track/run-box-tests.mjs +14 -0
- package/tools/track/run-box.mjs +168 -0
- package/tools/track/setup-box.sh +59 -0
- package/tools/track/study-2d.mjs +728 -0
- package/dist/assets/analytics-B1hnH66c.js +0 -1
- package/dist/assets/app-BWusbqgO.js +0 -4861
- package/dist/assets/app-qKDo4PBX.css +0 -1
- package/dist/assets/shot-prompt-CHUtw6af.js +0 -17
- package/dist/assets/shot-prompt-C_g2BHVc.css +0 -1
- package/dist/assets/workflow-CVwzMhz2.js +0 -179
- package/dist/assets/workflow-DsudxKHF.css +0 -1
- package/src/fal-motion-studio.jsx +0 -180
- package/src/scene-history.js +0 -129
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
// Shared by the browser and sidecar. No Node, React, renderer or provider imports.
|
|
2
|
-
import { STUDIO_ELEMENTS } from "./studio-elements.js";
|
|
2
|
+
import { STUDIO_ELEMENTS, isSettableElement } from "./studio-elements.js";
|
|
3
3
|
|
|
4
4
|
export const STUDIO_PROTOCOL_VERSION = "studio-agent-v1";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export const
|
|
5
|
+
// Sized for the compact index of up to 400 entities (~100 bytes each) beside
|
|
6
|
+
// 24 detailed rows; a small scene stays far below it.
|
|
7
|
+
export const STUDIO_CONTEXT_MAX_BYTES = 64 * 1024;
|
|
8
|
+
export const STUDIO_CONTEXT_LIMITS = Object.freeze({ entities: 24, entityIndex: 400, shots: 8, assets: 48, recentReceipts: 3, jobs: 8 });
|
|
9
|
+
export const STUDIO_TOOL_FAMILIES = Object.freeze(["inspect_studio", "run_action", "verify_result"]);
|
|
10
|
+
export const STUDIO_TOOL_ALIASES = Object.freeze(["operate_studio", "arrange_objects", "arrange_characters", "patch_elements", "frame_shot", "generate_motion", "undo_edit"]);
|
|
8
11
|
export const STUDIO_TOOL_LABELS = Object.freeze({
|
|
9
12
|
inspect_studio: "Read the scene",
|
|
10
13
|
operate_studio: "Selection and view",
|
|
@@ -15,7 +18,9 @@ export const STUDIO_TOOL_LABELS = Object.freeze({
|
|
|
15
18
|
generate_motion: "Generate motion",
|
|
16
19
|
verify_result: "Verify the result",
|
|
17
20
|
undo_edit: "Undo an edit",
|
|
21
|
+
run_action: "Run an editor action",
|
|
18
22
|
});
|
|
23
|
+
export const STUDIO_TOOLS = Object.freeze(Object.keys(STUDIO_TOOL_LABELS));
|
|
19
24
|
/** One patch target kind per authored commit domain: character→cast,
|
|
20
25
|
* object→objects, shot→shot, stage→stage. */
|
|
21
26
|
export const STUDIO_PATCH_KINDS = Object.freeze(["character", "object", "shot", "stage"]);
|
|
@@ -23,16 +28,16 @@ export const STUDIO_PATCH_DOMAINS = Object.freeze({ character: "cast", object: "
|
|
|
23
28
|
export const STUDIO_ERROR_CODES = Object.freeze([
|
|
24
29
|
"INVALID_ARGUMENT", "INVALID_CONTEXT", "INVALID_IDENTITY", "INVALID_TURN_ID", "INVALID_SESSION_ID", "INVALID_RECEIPT", "INVALID_RANGE", "INVALID_REQUEST",
|
|
25
30
|
"UNKNOWN_TOOL", "UNKNOWN_VARIANT", "DUPLICATE_NAME", "CONTEXT_LIMIT", "CONTEXT_TOO_LARGE", "AMBIGUOUS_TARGET", "AMBIGUOUS_BASIS", "TARGET_NOT_READY", "TARGET_BUSY",
|
|
26
|
-
"STALE_TARGET", "STALE_SCENE", "STALE_ENVIRONMENT", "STALE_CURSOR", "CAPABILITY_MISSING", "LIVE_HUB_UNAVAILABLE", "AUTH_REQUIRED", "RATE_LIMITED", "BACKEND_UNAVAILABLE",
|
|
27
|
-
"VERIFICATION_FAILED", "REPAIR_REGRESSED", "CANCELLED", "UNCERTAIN_APPLY", "UNDO_CONFLICT",
|
|
31
|
+
"STALE_TARGET", "STALE_SCENE", "STALE_ENVIRONMENT", "STALE_CURSOR", "CAPABILITY_MISSING", "LIVE_HUB_UNAVAILABLE", "AUTH_REQUIRED", "GENERATION_LIMIT", "RATE_LIMITED", "BACKEND_UNAVAILABLE",
|
|
32
|
+
"VERIFICATION_FAILED", "REPAIR_REGRESSED", "CANCELLED", "UNCERTAIN_APPLY", "UNDO_CONFLICT", "CONFIRMATION_REQUIRED", "TIMEOUT", "UNDO_EXPIRED",
|
|
28
33
|
]);
|
|
29
34
|
export const STUDIO_VARIANTS = freezeStudioData({
|
|
30
|
-
selectionKinds: ["scene", "object", "character", "rig", "camera"], modes: ["scene", "camera", "motion"], shotModes: ["keys", "follow", "rail"],
|
|
35
|
+
selectionKinds: ["scene", "object", "character", "rig", "camera"], modes: ["scene", "pose", "camera", "motion"], shotModes: ["keys", "follow", "rail"],
|
|
31
36
|
framingSizes: ["extreme close-up", "close-up", "medium close-up", "medium shot", "medium-wide shot", "wide shot", "extreme wide shot"],
|
|
32
37
|
framingViews: ["front", "front three-quarter", "profile", "rear three-quarter", "back"], framingLevels: ["ground", "low", "hip", "eye", "high", "overhead"],
|
|
33
38
|
framingSides: ["left", "right"], positionSides: ["left", "right", "front", "behind"], positionBases: ["world", "subject", "shot_camera"], collisionPolicies: ["report", "avoid"],
|
|
34
39
|
objectOps: ["create", "update", "remove", "group", "ungroup"], characterOps: ["create", "update", "remove"],
|
|
35
|
-
|
|
40
|
+
inspectionScopes: ["selection", "scene", "entities", "shot", "motion", "catalogue", "actions", "document"], receiptStatuses: ["applied", "partial", "noop", "transient", "installed", "undone"],
|
|
36
41
|
opStatuses: ["applied", "partial", "noop"],
|
|
37
42
|
jobStates: ["queued", "generating", "preparing", "verifying", "repairing", "committing", "reconciling", "installed", "review_required", "failed", "cancelled", "stale_target", "stale_environment"],
|
|
38
43
|
});
|
|
@@ -59,6 +64,9 @@ const nullable = schema => ({ oneOf: [schema, { type: "null" }] });
|
|
|
59
64
|
const array = (items, maxItems, minItems = 0, uniqueItems = false) => ({ type: "array", items, minItems, maxItems, ...(uniqueItems ? { uniqueItems } : {}) });
|
|
60
65
|
const object = (required, optional = {}) => ({ type: "object", properties: { ...required, ...optional }, required: Object.keys(required), additionalProperties: false });
|
|
61
66
|
const union = (...oneOf) => ({ oneOf });
|
|
67
|
+
// Arguments whose schema lives elsewhere (a registered Studio action declares
|
|
68
|
+
// its own input and validates it where it runs): any JSON object, detached.
|
|
69
|
+
const openObject = { type: "object", properties: {}, required: [], additionalProperties: true };
|
|
62
70
|
const id = { ...text(), pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$" };
|
|
63
71
|
const uuidSchema = { ...text(36), pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" };
|
|
64
72
|
export const isUuid = value => typeof value === "string" && new RegExp(uuidSchema.pattern).test(value);
|
|
@@ -87,7 +95,7 @@ const framing = union(
|
|
|
87
95
|
);
|
|
88
96
|
const objectOp = union(
|
|
89
97
|
object({ op: literal("create"), source: object({ kind: id }), position }, { name, facing, scale: positiveVec3 }),
|
|
90
|
-
object({ op: literal("update"), id }, { position, facing, rotationDeg: vec3, scale: positiveVec3, color: text(32), name }),
|
|
98
|
+
object({ op: literal("update"), id }, { position, facing, rotationDeg: vec3, scale: positiveVec3, color: text(32), name, hidden: bool }),
|
|
91
99
|
object({ op: literal("remove"), id }),
|
|
92
100
|
object({ op: literal("group"), parentId: id, childIds: ids() }),
|
|
93
101
|
object({ op: literal("ungroup"), childIds: ids() }),
|
|
@@ -103,7 +111,7 @@ const source = union(generateSource, object({ kind: literal("reuse"), artifactId
|
|
|
103
111
|
/* ----------------------------------------------- element patches ----
|
|
104
112
|
* The accepted fields of `patch_elements` are DERIVED from the element
|
|
105
113
|
* declaration table (src/studio-elements.js): one property per element whose
|
|
106
|
-
*
|
|
114
|
+
* document field is settable, keyed by the path inside its kind, typed by the
|
|
107
115
|
* declared type and bounded by the declared min/max/enum. Structured values
|
|
108
116
|
* (schedules, routes, pictures) declare their shape here, because the table
|
|
109
117
|
* records what an element IS, not how JSON carries it. */
|
|
@@ -143,7 +151,7 @@ export function patchValueSchema(element) {
|
|
|
143
151
|
* derived from the same declaration table as `patchValueSchema` so the two
|
|
144
152
|
* can never drift apart. */
|
|
145
153
|
export function buildPatchDescriptors(elements) {
|
|
146
|
-
return elements.filter(
|
|
154
|
+
return elements.filter(isSettableElement).map(({ path, type, min, max, enum: enumValues, gizmo, note }) => ({
|
|
147
155
|
path, type,
|
|
148
156
|
...(gizmo?.min !== undefined ? { min: { ...gizmo.min } } : min !== undefined ? { min: min && typeof min === "object" ? { ...min } : min } : {}),
|
|
149
157
|
...(gizmo?.max !== undefined ? { max: { ...gizmo.max } } : max !== undefined ? { max: max && typeof max === "object" ? { ...max } : max } : {}),
|
|
@@ -158,7 +166,7 @@ export function buildPatchSchema(elements) {
|
|
|
158
166
|
for (const kind of STUDIO_PATCH_KINDS) {
|
|
159
167
|
const properties = {};
|
|
160
168
|
for (const element of elements) {
|
|
161
|
-
if (element
|
|
169
|
+
if (!isSettableElement(element) || !element.path.startsWith(`${kind}.`)) continue;
|
|
162
170
|
const schema = patchValueSchema(element);
|
|
163
171
|
if (schema) properties[element.path.slice(kind.length + 1)] = schema;
|
|
164
172
|
}
|
|
@@ -176,7 +184,7 @@ const patchOp = union(
|
|
|
176
184
|
object({ target: object({ kind: literal("stage") }), set: STUDIO_PATCH_SET_SCHEMAS.stage }),
|
|
177
185
|
);
|
|
178
186
|
const toolSchemas = {
|
|
179
|
-
inspect_studio: object({ scope: choices(STUDIO_VARIANTS.
|
|
187
|
+
inspect_studio: object({ scope: choices(STUDIO_VARIANTS.inspectionScopes) }, { ids: ids(32), select: ids(32), query: name, cursor: text(512), limit: { ...integer(1, 32), default: 12 } }),
|
|
180
188
|
operate_studio: object({}, { selection, shotId: id, frame: integer(), playing: bool, mode: choices(STUDIO_VARIANTS.modes), view: object({}, { lookThrough: bool, grid: bool, autoColor: bool }) }),
|
|
181
189
|
arrange_objects: object({ ops: array(objectOp, 100, 1) }, { collisionPolicy: { ...choices(STUDIO_VARIANTS.collisionPolicies), default: "report" } }),
|
|
182
190
|
arrange_characters: object({ ops: array(characterOp, 8, 1) }),
|
|
@@ -185,23 +193,33 @@ const toolSchemas = {
|
|
|
185
193
|
generate_motion: object({ characterId: id, source }, { repair: { ...choices(["bounded", "none"]), default: "bounded" } }),
|
|
186
194
|
verify_result: object({ checks: array(choices(["placement", "framing", "motion"]), 3, 1, true) }, { receiptId: id, targets: ids(), range: union(literal("whole_clip"), range), visual: { ...choices(["none", "frame", "contact_sheet"]), default: "none" } }),
|
|
187
195
|
undo_edit: object({ receiptId: id }),
|
|
196
|
+
run_action: object({ action: id }, { args: openObject, confirmationToken: id }),
|
|
188
197
|
};
|
|
189
198
|
export const STUDIO_TOOL_SCHEMAS = freezeStudioData(toolSchemas);
|
|
190
|
-
export const STUDIO_CATALOGUE = freezeStudioData(
|
|
199
|
+
export const STUDIO_CATALOGUE = freezeStudioData(STUDIO_TOOLS.map(name => ({ name, slice: 1, parameters: toolSchemas[name] })));
|
|
191
200
|
|
|
192
201
|
// Bounded observations, never a document/pose/asset transport. Every nested
|
|
193
202
|
// object is closed. Null distinguishes unavailable data from measured zero.
|
|
194
203
|
const bounds = object({ min: vec3, max: vec3 });
|
|
195
204
|
const entity = object({ id, kind: choices(["object", "character", "rig"]), token: id }, {
|
|
196
205
|
name, detailsOmitted: bool, position: vec3, yawDeg: number(), rotationDeg: vec3, scale: union(positive, positiveVec3), bounds: nullable(bounds),
|
|
197
|
-
libraryKind: id, renderer: id, parentId: nullable(id), attachment: nullable(object({ characterId: id, bone: nullable(id) })), pathPointCount: integer(0, 64),
|
|
206
|
+
libraryKind: id, renderer: id, color: nullable(text(32)), tint: nullable(text(32)), modelId: nullable(text(120)), assetId: id, parentId: nullable(id), attachment: nullable(object({ characterId: id, bone: nullable(id) })), pathPointCount: integer(0, 64),
|
|
198
207
|
motion: object({ takeId: nullable(id), frames: integer(), ikKeyCount: integer(), promptBlockCount: integer() }, { poseId: nullable(id), keyIds: ids(8, 0) }),
|
|
199
208
|
capabilities: object({ rigReady: bool, ik: bool, measuredFeet: bool }),
|
|
200
209
|
});
|
|
210
|
+
// One compact row per entity, so the model sees the whole scene even when
|
|
211
|
+
// only 24 rows carry full detail.
|
|
212
|
+
const indexRow = object({ id, kind: choices(["object", "character", "rig"]) }, { name, position: vec3 });
|
|
213
|
+
// One compact row per registered Studio command: what run_action can call.
|
|
214
|
+
// Its schema is on request (inspect_studio scope "actions" with ids); the
|
|
215
|
+
// declared generation and hub timeout ride along for the sidecar's gate.
|
|
216
|
+
const actionIndexRow = object({ id }, { label: name, generation: literal("motion"), timeoutMs: integer(1, 300_000) });
|
|
201
217
|
const shotSummary = object({ id, name, range, keyCount: integer() }, { subjectIds: ids(24, 0) });
|
|
202
218
|
const currentShot = object({ id, name, range, mode: choices(STUDIO_VARIANTS.shotModes) }, { subjectIds: ids(24, 0) });
|
|
203
219
|
const camera = object({ position: vec3, lookAt: vec3, focalMm: positive, sensorId: id, slate: name });
|
|
204
|
-
|
|
220
|
+
// What the agent can place: a catalogue kind, or an asset imported into the scene.
|
|
221
|
+
const assetTypes = choices(["primitive", "set-piece", "image", "mesh"]);
|
|
222
|
+
const assetSummary = union(object({ kind: id, name, type: assetTypes }), object({ id, name, type: assetTypes }));
|
|
205
223
|
const jobSummary = object({ id, characterId: id, state: choices(STUDIO_VARIANTS.jobStates) }, { targetIds: ids(24, 0), progress: nullable(number(0, 1)), phase: name });
|
|
206
224
|
const contextSchema = object({
|
|
207
225
|
schema: literal("studio-context-v1"), host, revision,
|
|
@@ -209,10 +227,10 @@ const contextSchema = object({
|
|
|
209
227
|
scene: object({ name, aspect: text(40), floorY: number(), frameCount: integer(), objectCount: integer(), characterCount: integer() }),
|
|
210
228
|
selection, activeCharacterId: nullable(id), view, shot: nullable(currentShot), camera: nullable(camera),
|
|
211
229
|
entities: array(entity, 24), entityPage: object({ returned: integer(0, 24), total: integer(), truncated: bool, nextCursor: nullable(text(512)) }),
|
|
212
|
-
shots: array(shotSummary, 8), shotsTruncated: bool, assets: array(assetSummary,
|
|
230
|
+
shots: array(shotSummary, 8), shotsTruncated: bool, assets: array(assetSummary, STUDIO_CONTEXT_LIMITS.assets),
|
|
213
231
|
recentReceipts: array(object({ id, summary: name, canUndoDirect: bool }), 3), jobs: array(jobSummary, 8),
|
|
214
|
-
capabilities: object({ profile: literal("studio-slice-1"), tools: array(choices(
|
|
215
|
-
});
|
|
232
|
+
capabilities: object({ profile: literal("studio-slice-1"), tools: array(choices(STUDIO_TOOLS), STUDIO_TOOLS.length, 0, true) }, { rigReady: bool, cameraReady: bool, bridgeReady: bool }),
|
|
233
|
+
}, { entityIndex: array(indexRow, STUDIO_CONTEXT_LIMITS.entityIndex), actionIndex: array(actionIndexRow, 512) });
|
|
216
234
|
const guardSchema = object({ ...identityFields, targetId: id, token: id });
|
|
217
235
|
const efforts = ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"];
|
|
218
236
|
// Pictures the author pasted or dropped into the composer (#367). Inline bytes
|
|
@@ -248,13 +266,19 @@ const batchDetails = { counts: object({ created: integer(), updated: integer(),
|
|
|
248
266
|
// which lost a field to a domain normalizer, and exactly which paths were lost.
|
|
249
267
|
const opResults = array(object({ index: integer(), status: choices(STUDIO_VARIANTS.opStatuses) }, { droppedPaths: array(text(120), 32, 1) }), 32, 1);
|
|
250
268
|
const authoredReceipt = { ...receiptBase, authored: literal(true), undo };
|
|
269
|
+
// A run_action receipt names the registered action and what it did.
|
|
270
|
+
const actionFields = { action: id, summary: text(240) };
|
|
251
271
|
const receiptVariants = {
|
|
252
|
-
|
|
272
|
+
completed: object({ ...receiptBase, status: literal("completed"), kind: choices(["job", "document", "transaction", "mutation"]) }, { ...actionFields, ...batchDetails, output: openObject, jobId: id, txId: id, nextHost: identity }),
|
|
273
|
+
started: object({ ...receiptBase, status: literal("started"), kind: literal("job"), jobId: id, authored: literal(false), undo: literal(null) }, { ...actionFields }),
|
|
274
|
+
applied: object({ ...authoredReceipt, status: literal("applied") }, { mutated: literal(true), ops: opResults, ...batchDetails, ...actionFields }),
|
|
253
275
|
partial: object({ ...authoredReceipt, status: literal("partial"), ops: opResults }, { mutated: literal(true), ...batchDetails }),
|
|
254
|
-
noop: object({ ...receiptBase, status: literal("noop"), authored: literal(false), mutated: literal(false), undo: literal(null) }, { ops: opResults }),
|
|
255
|
-
transient: object({ ...receiptBase, status: literal("transient"), authored: literal(false), view: revisions, undo: literal(null) }, { mutated: bool }),
|
|
276
|
+
noop: object({ ...receiptBase, status: literal("noop"), authored: literal(false), mutated: literal(false), undo: literal(null) }, { ops: opResults, ...actionFields }),
|
|
277
|
+
transient: object({ ...receiptBase, status: literal("transient"), authored: literal(false), view: revisions, undo: literal(null) }, { mutated: bool, ...actionFields }),
|
|
256
278
|
installed: object({ ...authoredReceipt, status: literal("installed"), jobId: id, artifactId: id, installed, verification }, {
|
|
257
|
-
|
|
279
|
+
// Who accepted an unverified take: the user's explicit button, or the
|
|
280
|
+
// runtime's advisory install policy. Exactly these two may admit one.
|
|
281
|
+
mutated: literal(true), explicitUnverifiedAcceptance: bool, acceptance: literal("advisory-policy"),
|
|
258
282
|
repairs: object({ autoPhysicsInvocations: integer(0, 1), fixCollisionsInvocations: integer(0, 1), remaining: integer(0, 2) }), ...batchDetails,
|
|
259
283
|
}),
|
|
260
284
|
undone: object({ ...authoredReceipt, status: literal("undone"), undoneReceiptId: id, restoredTargets: array(guardSchema, 100, 1) }, { mutated: literal(true), ...batchDetails }),
|
|
@@ -263,7 +287,7 @@ const receiptSchema = union(...Object.values(receiptVariants));
|
|
|
263
287
|
const failureSchema = object({ ok: literal(false), commandId: id, host: identity, code: choices(STUDIO_ERROR_CODES), phase: choices(["admission", "execution", "prepare", "verify", "repair", "commit", "reconcile", "undo"]),
|
|
264
288
|
affectedIds: ids(100, 0), expectedTargets: array(guardSchema, 24), currentTargets: array(guardSchema, 24), mutated: union(bool, literal("unknown")),
|
|
265
289
|
preserved: object({ authoredState: choices(["unchanged", "changed", "unknown"]) }), recovery: object({ action: choices(["none", "inspect", "retry", "new_intent", "reconcile", "sign_in"]) }, { retryAllowed: bool }),
|
|
266
|
-
}, { message:
|
|
290
|
+
}, { message: text(500), jobId: id, txId: id, candidates: array(object({ id, kind: choices(["object", "character", "rig"]), position: nullable(vec3) }), 5) });
|
|
267
291
|
|
|
268
292
|
// These are JSON Schema data, not validators with hidden browser dependencies.
|
|
269
293
|
// x-studio-range is the sole relational schema annotation: end > start.
|
|
@@ -298,8 +322,10 @@ export function validateStudioSchema(schema, value, code = "INVALID_ARGUMENT", p
|
|
|
298
322
|
if (schema.type === "null") { if (value !== null) fail(code, "Expected null.", path); return null; }
|
|
299
323
|
if (schema.type === "object") {
|
|
300
324
|
if (!record(value)) fail(code, "Expected object.", path);
|
|
301
|
-
|
|
325
|
+
const open = schema.additionalProperties === true;
|
|
326
|
+
for (const key of Object.keys(value)) if (!Object.hasOwn(schema.properties, key) && !open) fail(code, "Unexpected field.", path);
|
|
302
327
|
const result = {};
|
|
328
|
+
if (open) for (const key of Object.keys(value)) if (!Object.hasOwn(schema.properties, key)) result[key] = structuredClone(value[key]);
|
|
303
329
|
for (const [key, child] of Object.entries(schema.properties)) {
|
|
304
330
|
if (Object.hasOwn(value, key)) result[key] = validateStudioSchema(child, value[key], code, `${path}.${key}`);
|
|
305
331
|
else if (schema.required.includes(key)) fail(code, "Required field missing.", `${path}.${key}`);
|
|
@@ -344,7 +370,7 @@ export function validateStudioContext(value) {
|
|
|
344
370
|
// Budget check before inspecting strings/arrays; still reject nonfinite values
|
|
345
371
|
// structurally below (JSON itself would otherwise turn them into null).
|
|
346
372
|
const bytes = utf8ByteLength(JSON.stringify(value) ?? "");
|
|
347
|
-
if (bytes > STUDIO_CONTEXT_MAX_BYTES) fail("CONTEXT_TOO_LARGE",
|
|
373
|
+
if (bytes > STUDIO_CONTEXT_MAX_BYTES) fail("CONTEXT_TOO_LARGE", `Studio context exceeds ${STUDIO_CONTEXT_MAX_BYTES / 1024} KiB.`, "$", { bytes, maxBytes: STUDIO_CONTEXT_MAX_BYTES });
|
|
348
374
|
const c = validateStudioSchema(contextSchema, value, "INVALID_CONTEXT");
|
|
349
375
|
const unique = new Set(c.entities.map(e => e.id));
|
|
350
376
|
if (unique.size !== c.entities.length) fail("INVALID_CONTEXT", "Duplicate entity ID.");
|
|
@@ -359,6 +385,11 @@ export function validateStudioContext(value) {
|
|
|
359
385
|
}
|
|
360
386
|
const p = c.entityPage;
|
|
361
387
|
if (p.returned !== c.entities.length || p.total < p.returned || p.truncated !== (p.total > p.returned) || (p.truncated ? p.nextCursor === null : p.nextCursor !== null)) fail("INVALID_CONTEXT", "Entity pagination is inconsistent.");
|
|
388
|
+
if (c.entityIndex) {
|
|
389
|
+
const indexed = new Set(c.entityIndex.map(e => e.id));
|
|
390
|
+
if (indexed.size !== c.entityIndex.length) fail("INVALID_CONTEXT", "Duplicate entity index ID.");
|
|
391
|
+
if (c.entityIndex.length > p.total || c.entities.some(e => !indexed.has(e.id))) fail("INVALID_CONTEXT", "Entity index must cover every detailed entity and no more than the total.");
|
|
392
|
+
}
|
|
362
393
|
if (c.view.frame >= Math.max(1, c.scene.frameCount)) fail("INVALID_CONTEXT", "Playhead is outside the scene.");
|
|
363
394
|
if (new Set(c.shots.map(s => s.id)).size !== c.shots.length) fail("INVALID_CONTEXT", "Duplicate shot ID.");
|
|
364
395
|
for (const shot of [...c.shots, ...(c.shot ? [c.shot] : [])]) if (shot.range.endFrameExclusive > c.scene.frameCount) fail("INVALID_CONTEXT", "Shot is outside the scene.");
|
|
@@ -422,9 +453,9 @@ function validatePatchPaths(args) {
|
|
|
422
453
|
if (kinds.size > 1) fail("INVALID_ARGUMENT", "One domain per patch: split character, object, shot and stage edits into separate commands.");
|
|
423
454
|
}
|
|
424
455
|
export function validateStudioCommand(command) {
|
|
425
|
-
if (!record(command) || !
|
|
456
|
+
if (!record(command) || !STUDIO_TOOLS.includes(command.name)) fail("UNKNOWN_TOOL", "Unsupported Studio tool.");
|
|
426
457
|
if (command.name === "patch_elements") validatePatchPaths(command.args);
|
|
427
|
-
const { args } = validateStudioSchema(object({ name: choices(
|
|
458
|
+
const { args } = validateStudioSchema(object({ name: choices(STUDIO_TOOLS), args: toolSchemas[command.name] }), command);
|
|
428
459
|
if (command.name === "inspect_studio" && args.ids && args.query !== undefined) fail("INVALID_ARGUMENT", "IDs and query are exclusive.");
|
|
429
460
|
if (command.name === "operate_studio" && (!Object.keys(args).length || (args.view && !Object.keys(args.view).length))) fail("INVALID_ARGUMENT", "Transient operation must specify an action.");
|
|
430
461
|
if (args.ops) {
|
|
@@ -485,7 +516,12 @@ export function validateReceipt(value) {
|
|
|
485
516
|
if (r.status === "partial" && !dropped) fail("INVALID_RECEIPT", "Partial status requires at least one dropped path.");
|
|
486
517
|
if (r.status === "noop" && r.ops.some(op => op.status === "applied")) fail("INVALID_RECEIPT", "A noop cannot report an applied operation.");
|
|
487
518
|
}
|
|
488
|
-
if (r.status === "
|
|
519
|
+
if (r.status === "started" || r.status === "completed") {
|
|
520
|
+
if (r.revision.after < r.revision.before && !r.nextHost) fail("INVALID_RECEIPT", "Completion revisions must not move backwards in one document.");
|
|
521
|
+
if (r.status === "started" && (r.authored || r.undo || r.revision.before !== r.revision.after)) fail("INVALID_RECEIPT", "Started jobs cannot claim an authored commit.");
|
|
522
|
+
if (r.kind === "mutation" && (!r.authored || !r.undo || r.revision.after <= r.revision.before || !r.delta.length)) fail("INVALID_RECEIPT", "A completed composite mutation needs retained undo and advancing revision evidence.");
|
|
523
|
+
if (r.undo && (!r.authored || !r.affectedIds.length)) fail("INVALID_RECEIPT", "An undo entry requires an authored result and affected targets.");
|
|
524
|
+
} else if (r.status === "noop" || r.status === "transient") {
|
|
489
525
|
if (r.authored || r.undo !== null || r.revision.before !== r.revision.after) fail("INVALID_RECEIPT", "Non-authored operations cannot create history or advance authored revision.");
|
|
490
526
|
if (r.status === "noop" && (r.mutated !== false || r.delta.length)) fail("INVALID_RECEIPT", "Noop must prove no mutation.");
|
|
491
527
|
if (r.status === "transient" && (!r.view || r.view.after < r.view.before || !r.delta.length)) fail("INVALID_RECEIPT", "Transient receipt requires view revisions and actual readback.");
|
|
@@ -502,7 +538,7 @@ export function validateReceipt(value) {
|
|
|
502
538
|
for (const block of blocks) { if (block.startFrame !== previous || block.endFrameExclusive <= previous || block.endFrameExclusive - previous > 120) fail("INVALID_RECEIPT", "Installed schedule is not contiguous or bounded."); previous = block.endFrameExclusive; }
|
|
503
539
|
if (previous !== frameCount || r.installed.durationSeconds !== frameCount / 24 || r.verification.range.startFrame !== 0 || r.verification.range.endFrameExclusive !== frameCount || r.verification.evaluatedFrames > frameCount) fail("INVALID_RECEIPT", "Installed schedule and verification coverage disagree.");
|
|
504
540
|
if (r.verification.status === "verified" && r.verification.evaluatedFrames !== frameCount) fail("INVALID_RECEIPT", "Verified motion requires whole-clip coverage.");
|
|
505
|
-
if (r.verification.status === "unverified" && r.explicitUnverifiedAcceptance !== true) fail("INVALID_RECEIPT", "Unverified installation requires explicit user acceptance.");
|
|
506
|
-
} else if (r.installed || r.verification || r.jobId || r.artifactId || r.repairs || r.explicitUnverifiedAcceptance !== undefined) fail("INVALID_RECEIPT", "Installation evidence is exclusive to installed receipts.");
|
|
541
|
+
if (r.verification.status === "unverified" && r.explicitUnverifiedAcceptance !== true && r.acceptance !== "advisory-policy") fail("INVALID_RECEIPT", "Unverified installation requires explicit user or advisory-policy acceptance.");
|
|
542
|
+
} else if (r.installed || r.verification || (r.jobId && !["started", "completed"].includes(r.status)) || r.artifactId || r.repairs || r.explicitUnverifiedAcceptance !== undefined) fail("INVALID_RECEIPT", "Installation evidence is exclusive to installed receipts.");
|
|
507
543
|
return freezeStudioData(r);
|
|
508
544
|
}
|