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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,176 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
CozyClay 2.0 is the v2 editor: a floating-glass shell with light and dark
|
|
6
|
+
themes, one four-mode Studio (Stage, Pose, Camera, Motion on keys 1-4), and
|
|
7
|
+
the v2 Outliner, Sequencer, start screen, Details panels and Preferences.
|
|
8
|
+
Under it, every document edit, whether it comes from the UI, the Agent panel,
|
|
9
|
+
MCP or the CLI, now runs through one command bus on a document store with one
|
|
10
|
+
undo history, so anything the editor can do is callable by an agent without
|
|
11
|
+
separate wiring. The Studio Agent panel in the npm package works again
|
|
12
|
+
(#576). **No MCP tool or CLI command was removed**: legacy MCP tools are
|
|
13
|
+
aliases of bus commands and answer the editor's receipts.
|
|
14
|
+
|
|
15
|
+
Studio
|
|
16
|
+
|
|
17
|
+
- Floating-glass shell: the viewport fills the window and the top bar, both
|
|
18
|
+
columns, the dock and the status strip float over it. Light and dark
|
|
19
|
+
themes, and the stage follows the theme live. The dock runs Animation |
|
|
20
|
+
Assets tabs sized to the Sequencer's lanes, with the shot preview docked at
|
|
21
|
+
its right end; the standing status row is gone and live export status
|
|
22
|
+
floats in the viewport. (#570, #572, #519)
|
|
23
|
+
- One four-mode switch, Stage, Pose, Camera and Motion, on keys 1-4. Pose
|
|
24
|
+
mode is the single way into IK (W/E/R pick ik, trail and pin there); the
|
|
25
|
+
timeline IK toggle and the Rig Control IK buttons are gone, and leaving
|
|
26
|
+
Pose ends IK. (#521)
|
|
27
|
+
- v2 Outliner: 36 px header with the item count, search, typed rows, amber
|
|
28
|
+
selection; right-click Create replaces the toolbar's + Add object. (#525)
|
|
29
|
+
- v2 Sequencer: header, readout and actions, a 190 px track tree, clip
|
|
30
|
+
fills, amber playhead, keys and pin bands, scrollable camera, rail and
|
|
31
|
+
crane rows. (#528)
|
|
32
|
+
- v2 start screen: project nav, template categories, recent rows, the
|
|
33
|
+
browser project store and a project preview; File › New opens the same
|
|
34
|
+
screen. (#531, #551)
|
|
35
|
+
- v2 Details panels and primitives: pose tools with an auto-fix preview,
|
|
36
|
+
motion details with takes, the viewport toolbar and overlays, the top bar,
|
|
37
|
+
menus and status bar, the content browser and log, and the Preferences
|
|
38
|
+
dialog. (#522, #523, #524, #526, #527, #529, #530, #532)
|
|
39
|
+
- Generate actions have one home: the top-bar Generate Motion button runs
|
|
40
|
+
every prompt block when any block has text. Copy follows v2: "ARDY" leaves
|
|
41
|
+
the user-facing text, pose toasts say "Pose mode", path endpoints read
|
|
42
|
+
PATH START / PATH END, the first-run guide says Outliner. (#550, #551)
|
|
43
|
+
- Top-View props recede so the camera, cast and rail read first. (#566)
|
|
44
|
+
- When WebGL is unavailable the stage says so instead of staying blank. (#518)
|
|
45
|
+
- 194 dead CSS rules are gone and the v2 information architecture is
|
|
46
|
+
recorded in `docs/studio-ui-ia.md`; Details, Outliner and top-bar props
|
|
47
|
+
that were never read are dropped. (#533, #552, #556, #558, #560, #562)
|
|
48
|
+
- Korean UI: 56 reversed `ko()` argument pairs in the motion card are fixed
|
|
49
|
+
(#505, by @romanr), as are the VideoCapturePanel quality-gate messages
|
|
50
|
+
(#565).
|
|
51
|
+
|
|
52
|
+
IK and motion
|
|
53
|
+
|
|
54
|
+
- IK and motion trails on the split architecture: delta IK drag baking over
|
|
55
|
+
the raw clip, per-key blend persistence, a remembered IK camera entry,
|
|
56
|
+
trail picking against the rendered rig, limb, head and root trail edits
|
|
57
|
+
with conflict warnings. (#511)
|
|
58
|
+
- Range pin with an exact hinge solve and body reach compensation, carried
|
|
59
|
+
through persistence, undo/redo, timeline bands and preview markers. (#515)
|
|
60
|
+
- Fit to platforms with Fit / Remove controls and a stable knee bend side,
|
|
61
|
+
verified over 784 cases with no reverse bends. (#517)
|
|
62
|
+
- One motion generation pipeline on the job contract: `motion.generate`
|
|
63
|
+
serves the UI, the Agent and MCP alike, with concurrent-edit parity (a
|
|
64
|
+
stale job publishes nothing, an unrelated object edit still installs),
|
|
65
|
+
Stop and cancel, reconnect, and receipt undo. The agent-only motion
|
|
66
|
+
pipeline is deleted. (#444, #452)
|
|
67
|
+
- Image-to-video motion is named `i2v` in code and "AI motion" in the UI;
|
|
68
|
+
"fal" names only the provider adapter. Command ids, analytics values and
|
|
69
|
+
persisted values are unchanged. (#509)
|
|
70
|
+
|
|
71
|
+
Command bus and document store
|
|
72
|
+
|
|
73
|
+
- Every document mutation runs through one command bus: wire transactions,
|
|
74
|
+
a scoped job fence, receipts, refusals and exposure. Action registrations,
|
|
75
|
+
the agent binding and the domain writers moved out of App.jsx. (#435,
|
|
76
|
+
#437, #459, #460, #477)
|
|
77
|
+
- A document store owns stage, objects, shots, cast, motion and IK, and
|
|
78
|
+
project and scenes, each routed through the bus, with collection element
|
|
79
|
+
kinds and cross-domain undo order. (#449, #447, #445, #441, #442, #448,
|
|
80
|
+
#461, #480)
|
|
81
|
+
- One store-owned undo history; the per-domain history owners are gone.
|
|
82
|
+
(#494) Duplicate agent paths are removed: `inspect_studio`, `run_action`
|
|
83
|
+
and `verify_result` are the three tool families, `operate_studio` is an
|
|
84
|
+
alias, and selection, playhead and view changes add no undo entries.
|
|
85
|
+
(#495) The writer baseline is zero and parity is complete: all 62
|
|
86
|
+
document-mutating UI handlers reach `run`, enforced by ratchet tests and
|
|
87
|
+
a generated parity matrix. (#436, #496)
|
|
88
|
+
|
|
89
|
+
Agent
|
|
90
|
+
|
|
91
|
+
- The Studio agent pane reaches the whole Studio through the action
|
|
92
|
+
registry the UI dispatches through, and every bus command is discoverable
|
|
93
|
+
and callable through agent `run_action`, MCP `studio_commands` /
|
|
94
|
+
`studio_run` and `cclay live commands` / `cclay live run` with no
|
|
95
|
+
agent-specific wiring. (#423, #438)
|
|
96
|
+
- Motion failures are visible and routable, `verify_result` reports
|
|
97
|
+
honestly, and a failed attempt no longer uses up the message's one
|
|
98
|
+
generation. (#426)
|
|
99
|
+
- Minimal Studio Agent panel: an icon-only head, the account under the ⋯
|
|
100
|
+
menu, one-line notices, an empty state with suggestion chips; it sits
|
|
101
|
+
under Details in the right column instead of replacing it. (#574, #570)
|
|
102
|
+
- In the npm package the Studio Agent panel loads its live tools from the
|
|
103
|
+
staged MCP runtime that `cclay mcp` installs once per version, so the live
|
|
104
|
+
hub starts and the pane works; `mcp/mesh-file.mjs` ships with the package
|
|
105
|
+
and `cclay mcp` starts again. A runtime that cannot be installed is
|
|
106
|
+
reported on the launcher's terminal. (#576)
|
|
107
|
+
|
|
108
|
+
MCP
|
|
109
|
+
|
|
110
|
+
- Legacy tools are thin aliases of bus commands and return the editor's JSON
|
|
111
|
+
receipts: revision before and after, retained undo and refusal codes.
|
|
112
|
+
`apply_batch` maps to `objects.batch` and keeps `atomic`, `stopOnError`
|
|
113
|
+
and one undo entry; replacing scene ids needs a Studio-issued confirmation
|
|
114
|
+
token. (#446)
|
|
115
|
+
- `initialize` waits the SDK's default 60 s. (#502)
|
|
116
|
+
|
|
117
|
+
Analytics
|
|
118
|
+
|
|
119
|
+
- Builder telemetry: pose edits, i2v motion A→B, `$exception`, a device
|
|
120
|
+
profile (GPU and frame rate), the install source from
|
|
121
|
+
`npx cozyclay --via`, use case and update status. (#466)
|
|
122
|
+
|
|
123
|
+
Tools
|
|
124
|
+
|
|
125
|
+
- A mocap research bench under `tools/bench/` and `tools/gt-render/`:
|
|
126
|
+
ground-truth part-colour renders with camera truths (#428), a GVHMR
|
|
127
|
+
extraction bench (#430), a scorer for pose, trajectory, endpoints, overlap
|
|
128
|
+
and contact (#431), 3D fit stages F0-F5 (#432), a cube-contact scenario
|
|
129
|
+
(#454), the observation-driven GVHMR tuning ladder G0-G5 (#462, #488,
|
|
130
|
+
#492) and the known-character tracker T1 (#500).
|
|
131
|
+
|
|
132
|
+
Housekeeping
|
|
133
|
+
|
|
134
|
+
- The camera-rail browser test waits for the delete toast, which exposed and
|
|
135
|
+
fixed a stale-toast dismissal bug (#470, #483); the lifecycle test waits
|
|
136
|
+
for the grandchild to disappear (#479).
|
|
137
|
+
- The owner's v2 editor design contract is adopted in the docs (#512).
|
|
138
|
+
- Repository traffic is archived to the `metrics` branch twice a week.
|
|
139
|
+
|
|
140
|
+
## 1.10.0
|
|
141
|
+
|
|
142
|
+
Props and characters can now be hidden without being deleted, the Studio top
|
|
143
|
+
bar no longer collapses at 1100px when a live workspace handle is shown, and
|
|
144
|
+
the MorphGS mocap experiment gets its conversion scripts, a playback
|
|
145
|
+
regression test and a cluster setup script under `tools/morphgs/`.
|
|
146
|
+
|
|
147
|
+
Studio
|
|
148
|
+
|
|
149
|
+
- Hide or show a prop or a character from the hierarchy eye and the row
|
|
150
|
+
context menu. The row, selection and inspector stay; move handles appear
|
|
151
|
+
only while the object is on stage. A hidden parent or a hidden character
|
|
152
|
+
takes its children and carried props off the stage, out of collision and
|
|
153
|
+
off the ground while their own eye stays on. Hidden state survives
|
|
154
|
+
save/reload and Ctrl+Z. (#416, by @bjoernfal)
|
|
155
|
+
- MCP `update_object` and the Studio agent's object `update` op accept
|
|
156
|
+
`hidden`; `describe_scene` marks hidden props. The OTIO cut-list export
|
|
157
|
+
leaves hidden props out of the blocking metadata. (#416)
|
|
158
|
+
- The Inspector Parent select writes through the history store, so a
|
|
159
|
+
reparent no longer disappears on the next object edit. (#416)
|
|
160
|
+
- The `Live workspace <uuid>` handle in the top bar stays on one line and
|
|
161
|
+
truncates with an ellipsis instead of wrapping over Settings and Export at
|
|
162
|
+
1100px; the responsive browser QA now asserts that top-bar chrome does
|
|
163
|
+
not overlap. (#321)
|
|
164
|
+
|
|
165
|
+
Tools
|
|
166
|
+
|
|
167
|
+
- `tools/morphgs/`: `fbx2morphgs.mjs` (x-bot FBX → MorphGS mesh + compressed
|
|
168
|
+
rig, drops the zero-length duplicate bones FBXLoader creates and appends the
|
|
169
|
+
required `fixed_joint` line), `morphgs-to-cskel27.mjs` (rot_params +
|
|
170
|
+
pred_joints → playback NPZ), `setup-on-cluster.sh`, `demo-gate.sh` and the
|
|
171
|
+
`--mode none` preprocess patch, with `test/verify-morphgs-exporter.mjs`
|
|
172
|
+
checking the exporter against MorphGS's own Animation.step. (#415)
|
|
173
|
+
|
|
3
174
|
## 1.9.0
|
|
4
175
|
|
|
5
176
|
The Agent panel moves onto the open-source pi harness with six model
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
3
|
-
import { basename } from "node:path";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
4
5
|
import * as defaultAuth from "../codex-auth.mjs";
|
|
5
6
|
import { publishLiveEndpoint, removeLiveEndpoint } from "../live-endpoint.mjs";
|
|
6
7
|
import { createCodexClient } from "./codex-client.mjs";
|
|
@@ -230,10 +231,22 @@ function defaultClient(auth, requestContext) {
|
|
|
230
231
|
const liveHubOwner = () => process.env.COZYCLAY_LIVE_OWNER
|
|
231
232
|
|| (basename(process.argv[1] ?? "") === "dev-full.mjs" ? "dev-full" : "cozyclay");
|
|
232
233
|
|
|
234
|
+
/** The two live-tool modules. A source checkout resolves them from the
|
|
235
|
+
* repository, where mcp/ has its own node_modules. The published package
|
|
236
|
+
* ships mcp/ without zod, three or ws, so the launcher passes an `mcpRuntime`
|
|
237
|
+
* whose ensure() installs the staged runtime (bin/mcp-runtime.mjs) once per
|
|
238
|
+
* version and the modules are imported from that root instead (#576). */
|
|
239
|
+
async function liveToolsModules(mcpRuntime) {
|
|
240
|
+
if (!mcpRuntime) return Promise.all([import("../../mcp/tool-handlers.mjs"), import("../../mcp/live-hub.mjs")]);
|
|
241
|
+
const root = await mcpRuntime.ensure();
|
|
242
|
+
const staged = (file) => pathToFileURL(join(root, "mcp", file)).href;
|
|
243
|
+
return Promise.all([import(staged("tool-handlers.mjs")), import(staged("live-hub.mjs"))]);
|
|
244
|
+
}
|
|
245
|
+
|
|
233
246
|
/** Start the optional registry/live dependencies without making signed-out
|
|
234
247
|
* startup depend on an MCP dependency install. Failures remain visible on use. */
|
|
235
|
-
function liveToolsRuntime() {
|
|
236
|
-
return
|
|
248
|
+
function liveToolsRuntime(mcpRuntime) {
|
|
249
|
+
return liveToolsModules(mcpRuntime).then(async ([registry, { startLiveHub }]) => {
|
|
237
250
|
const owner = liveHubOwner();
|
|
238
251
|
const token = randomBytes(32).toString("hex");
|
|
239
252
|
const liveHub = await startLiveHub(Number(process.env.COZYCLAY_LIVE_PORT ?? 5184), { token, owner });
|
|
@@ -258,13 +271,19 @@ function liveToolsRuntime() {
|
|
|
258
271
|
liveHub.server?.once("close", () => removeLiveEndpoint(liveHub.port));
|
|
259
272
|
}
|
|
260
273
|
return { liveHub, handlers };
|
|
261
|
-
}).catch((error) =>
|
|
274
|
+
}).catch((error) => {
|
|
275
|
+
// The pane answers CAPABILITY_MISSING on use; the reason belongs on the
|
|
276
|
+
// launcher's terminal rather than nowhere.
|
|
277
|
+
console.error(`cozyclay: Studio live tools unavailable: ${error?.message ?? error}`);
|
|
278
|
+
for (const line of error?.lines ?? []) console.error(`cozyclay: ${line}`);
|
|
279
|
+
return { error };
|
|
280
|
+
});
|
|
262
281
|
}
|
|
263
282
|
|
|
264
|
-
export function createAgentHandler({ auth = defaultAuth, codex, models, codexBaseUrl, cliproxyBaseUrl, env, fauxProvider, handlers, liveHub,
|
|
283
|
+
export function createAgentHandler({ auth = defaultAuth, codex, models, codexBaseUrl, cliproxyBaseUrl, env, fauxProvider, handlers, liveHub, mcpRuntime, port, studioRuntime, clock = Date.now, setIntervalImpl = setInterval, clearIntervalImpl = clearInterval, sessionStore: injectedSessionStore } = {}) {
|
|
265
284
|
const requestContext = new AsyncLocalStorage();
|
|
266
285
|
codex ||= defaultClient(auth, requestContext);
|
|
267
|
-
const runtime = handlers !== undefined || liveHub !== undefined ? Promise.resolve({ handlers: handlers ?? [], liveHub }) : liveToolsRuntime();
|
|
286
|
+
const runtime = handlers !== undefined || liveHub !== undefined ? Promise.resolve({ handlers: handlers ?? [], liveHub }) : liveToolsRuntime(mcpRuntime);
|
|
268
287
|
const renderGuidance = async (environment) => {
|
|
269
288
|
try {
|
|
270
289
|
const { handlers: tools, liveHub: hub } = await runtime;
|
|
@@ -306,7 +325,6 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
|
|
|
306
325
|
const sessionStore = injectedSessionStore ?? createSessionStore();
|
|
307
326
|
const studioSessions = new Map();
|
|
308
327
|
const studioEvents = new Map();
|
|
309
|
-
let ownedStudioRuntime = studioRuntime || null;
|
|
310
328
|
const studioOwnerTokens = new Map();
|
|
311
329
|
const parseCookies = req => Object.fromEntries(String(req.headers.cookie || "").split(";").map(part => part.trim().split("=")).filter(([key, value]) => key && value).map(([key, value]) => [key, decodeURIComponent(value)]));
|
|
312
330
|
const pruneStudioSessions = () => {
|
|
@@ -323,13 +341,6 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
|
|
|
323
341
|
if (!owner || supplied !== owner) throw Object.assign(new Error("Studio session owner mismatch."), { code: "AUTH_REQUIRED" });
|
|
324
342
|
return owner;
|
|
325
343
|
};
|
|
326
|
-
const studioRuntimeFor = async hub => {
|
|
327
|
-
if (ownedStudioRuntime) return ownedStudioRuntime;
|
|
328
|
-
const { createStudioMotionRuntime } = await import("./motion-runtime.mjs");
|
|
329
|
-
if (!hub) return null;
|
|
330
|
-
ownedStudioRuntime = createStudioMotionRuntime({ liveHub: hub, getBridgeOrigin, clock });
|
|
331
|
-
return ownedStudioRuntime;
|
|
332
|
-
};
|
|
333
344
|
const emitStudioEvent = (turnId, event) => {
|
|
334
345
|
const record = studioEvents.get(turnId) || { next: 0, events: [], listeners: new Set(), terminal: false };
|
|
335
346
|
// Execution telemetry is advisory and is validated key-by-key in the
|
|
@@ -353,7 +364,6 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
|
|
|
353
364
|
if (kind !== "signed_out" && kind !== "replaced") return;
|
|
354
365
|
for (const session of sessions.values()) session.controller?.abort();
|
|
355
366
|
for (const session of studioSessions.values()) session.controller?.abort();
|
|
356
|
-
void ownedStudioRuntime?.dispose?.(); ownedStudioRuntime = studioRuntime || null;
|
|
357
367
|
for (const runner of [...workflowRunners.values(), ...studioRunners.values()]) void runner.close?.();
|
|
358
368
|
workflowRunners.clear(); studioRunners.clear();
|
|
359
369
|
workflowModels = models;
|
|
@@ -401,32 +411,16 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
|
|
|
401
411
|
// An owned retired id is still stale while another motion job is active:
|
|
402
412
|
// this Stop cannot acknowledge the current turn's different job.
|
|
403
413
|
if (value.jobId && session.activeJobId && value.jobId !== session.activeJobId) throw new StudioProtocolError("STALE_TARGET", "Stop does not target the active motion job.");
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
// still in flight (or pending an explicit accept), and activeJobTurnId ties
|
|
407
|
-
// it to the turn that admitted it. A retired job from an earlier turn must
|
|
408
|
-
// never make a later, unrelated turn's Stop go quiet.
|
|
414
|
+
// Only this turn's held editor job can settle the tool quietly. A retired
|
|
415
|
+
// job from an earlier turn must never silence an unrelated turn's Stop.
|
|
409
416
|
const acknowledged = Boolean(session.activeJobId) && session.activeJobTurnId === value.turnId;
|
|
410
|
-
// The
|
|
411
|
-
// Forwarding its outcome keeps the panel from turning "I could not find
|
|
412
|
-
// out" into "nothing was applied"; a discarded outcome reads as proof.
|
|
417
|
+
// The editor's outcome, not a sidecar abort, establishes application.
|
|
413
418
|
let outcome = null;
|
|
414
|
-
if (jobId
|
|
419
|
+
if (jobId) outcome = await session.controlJob('job.cancel', jobId);
|
|
415
420
|
if (jobId && session.activeJobId === jobId) { session.activeJobId = null; session.activeJobTurnId = null; }
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
//
|
|
419
|
-
// internal step — aborting it here interrupts the in-flight generate_motion
|
|
420
|
-
// tool call's OWN completion machinery before it can settle, well before
|
|
421
|
-
// pi's own graceful `lane.abort()` (below) ever runs. A job the runtime
|
|
422
|
-
// actually acknowledged already resolved the held tool call with its real
|
|
423
|
-
// outcome via `runtime.stop()` above; the only abort this stop still needs
|
|
424
|
-
// is the graceful one that stops the model from being prompted again, so
|
|
425
|
-
// `session.controller` must NOT be touched on that path (no other tool is
|
|
426
|
-
// concurrently in flight while generate_motion holds the turn). A stop
|
|
427
|
-
// for a job this turn never acknowledged (none active, or a retired one
|
|
428
|
-
// from an earlier turn) never resolved anything for THIS turn and stays a
|
|
429
|
-
// plain (loud) abort on both signals.
|
|
421
|
+
// Let the acknowledged job settle its held tool with that outcome before
|
|
422
|
+
// aborting the model lane. Aborting the shared signal would interrupt the
|
|
423
|
+
// tool first and lose the editor's proof. Without a held job, abort both.
|
|
430
424
|
if (!acknowledged) session.controller?.abort();
|
|
431
425
|
await session.modelSession?.abort?.("studio stop", acknowledged ? { quiet: true } : undefined);
|
|
432
426
|
json(res, 200, { ok: true, status: jobId ? "stopped" : "detached", ...(outcome ? { outcome: { status: outcome.status ?? null, code: outcome.code ?? null, mutated: outcome.mutated ?? null } } : {}) }); return true;
|
|
@@ -440,7 +434,7 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
|
|
|
440
434
|
studioOwner(req, value.sessionId, true);
|
|
441
435
|
let persisted = null;
|
|
442
436
|
try { persisted = sessionStore.read(value.sessionId); } catch { persisted = null; }
|
|
443
|
-
session = { owner: studioOwnerTokens.get(value.sessionId), history: persisted?.history ?? [], persistedItems: persisted?.history?.length ?? 0, meta: persisted?.meta ?? null, turns: new Map(), controller: null, activeJobId: null, activeJobTurnId: null, motionJobIds: new Set(Array.isArray(persisted?.meta?.motionJobIds) ? persisted.meta.motionJobIds.filter(id => typeof id === "string") : []),
|
|
437
|
+
session = { owner: studioOwnerTokens.get(value.sessionId), history: persisted?.history ?? [], persistedItems: persisted?.history?.length ?? 0, meta: persisted?.meta ?? null, turns: new Map(), controller: null, activeJobId: null, activeJobTurnId: null, motionJobIds: new Set(Array.isArray(persisted?.meta?.motionJobIds) ? persisted.meta.motionJobIds.filter(id => typeof id === "string") : []), host: null, updatedAt: clock() };
|
|
444
438
|
studioSessions.set(value.sessionId, session);
|
|
445
439
|
}
|
|
446
440
|
session.updatedAt = clock();
|
|
@@ -468,33 +462,52 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
|
|
|
468
462
|
const admission = {
|
|
469
463
|
commandId: () => randomUUID(), host: studioIdentity(value.context.host), revision: value.context.revision.scene,
|
|
470
464
|
refresh: async () => {
|
|
471
|
-
|
|
465
|
+
// Read at the admitted host: a scene action may have opened another
|
|
466
|
+
// scene of this workspace during the turn (studio-tools adopts it).
|
|
467
|
+
const host = { ...value.context.host, ...admission.host };
|
|
468
|
+
const refreshed = studioRuntime?.readContext ? await studioRuntime.readContext(host) : await authoritativeStudioContext({ ...value, context: { ...value.context, host } }, hub);
|
|
472
469
|
admission.revision = refreshed.revision.scene;
|
|
473
470
|
},
|
|
474
471
|
};
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
472
|
+
// One motion generation per user message, whichever path starts it:
|
|
473
|
+
// generate_motion below and a run_action job action share this gate.
|
|
474
|
+
const generation = { used: false, failures: 0 };
|
|
475
|
+
// The editor's command index (authoritative context) declares each action's
|
|
476
|
+
// generation and hub timeout; no sidecar list of actions exists.
|
|
477
|
+
const controlJob = async (action, jobId) => {
|
|
478
|
+
await admission.refresh();
|
|
479
|
+
return hub.command('run_action', { name: 'run_action', args: { action, args: { jobId, ...(action === 'job.await' ? { timeoutMs: 300_000 } : {}) } },
|
|
480
|
+
commandId: randomUUID(), host: admission.host, expectedRevision: admission.revision }, value.context.host.workspaceHandle, { timeoutMs: 300_000 });
|
|
481
|
+
};
|
|
482
|
+
session.controlJob = controlJob;
|
|
483
|
+
const onJob = async started => {
|
|
484
|
+
const jobId = started.jobId;
|
|
485
|
+
session.motionJobIds.add(jobId);
|
|
486
|
+
persistenceMeta.motionJobIds = [...session.motionJobIds];
|
|
487
|
+
session.activeJobId = jobId; session.activeJobTurnId = value.turnId;
|
|
488
|
+
const cancel = () => { void controlJob('job.cancel', jobId).catch(error => send({ type: 'error', code: error.code ?? 'CANCEL_FAILED', message: error.message })); };
|
|
489
|
+
controller.signal.addEventListener('abort', cancel, { once: true });
|
|
490
|
+
if (controller.signal.aborted) cancel();
|
|
491
|
+
send({ type: 'job.state', jobId, commandId: started.commandId, state: 'generating', phase: 'generating' });
|
|
492
|
+
try {
|
|
493
|
+
const outcome = await controlJob('job.await', jobId);
|
|
494
|
+
send({ type: 'job.state', jobId, commandId: outcome.commandId, state: outcome.ok ? 'installed' : outcome.code === 'CANCELLED' ? 'cancelled' : 'failed', phase: 'complete' });
|
|
495
|
+
return outcome;
|
|
496
|
+
} finally {
|
|
497
|
+
controller.signal.removeEventListener('abort', cancel);
|
|
498
|
+
if (session.activeJobId === jobId) { session.activeJobId = null; session.activeJobTurnId = null; }
|
|
496
499
|
}
|
|
497
500
|
};
|
|
501
|
+
const tools = createStudioTools({ liveHub: hub, workspaceHandle: value.context.host.workspaceHandle, session: { signal: controller.signal, admission, generation, onJob, actionIndex: current?.actionIndex ?? [] }, resolveImage: async (id, correlation) => hub.command("resolve_studio_image", { imageId: id, ...correlation }, value.context.host.workspaceHandle) });
|
|
502
|
+
const motion = async args => {
|
|
503
|
+
// Private artifact IDs belonged to the retired sidecar runtime. Reuse
|
|
504
|
+
// retained editor takes through motion.loadVersion, not another installer.
|
|
505
|
+
if (args.source.kind !== 'generate') throw new StudioProtocolError('CAPABILITY_MISSING', 'Private motion artifacts are no longer retained. Use motion.loadVersion for an editor take.');
|
|
506
|
+
await tools.internal.invoke('inspect_studio', { scope: 'motion', ids: [args.characterId] });
|
|
507
|
+
const receipt = await tools.internal.invoke('generate_motion', args);
|
|
508
|
+
if (receipt.ok && receipt.status === 'completed') send({ type: 'receipt', receipt });
|
|
509
|
+
return receipt;
|
|
510
|
+
};
|
|
498
511
|
const modelTools = tools.map(tool => ({
|
|
499
512
|
...tool,
|
|
500
513
|
handler: async args => {
|
|
@@ -699,14 +712,6 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
|
|
|
699
712
|
}
|
|
700
713
|
return true;
|
|
701
714
|
}
|
|
702
|
-
if (path.startsWith("/agent/jobs/") && path.endsWith("/accept") && req.method === "POST") {
|
|
703
|
-
if (!await hasAnyCredential()) { json(res, 401, { error: { code: "AUTH_REQUIRED", message: "Sign in or configure a provider key." } }); return true; }
|
|
704
|
-
let value; try { value = await readBody(req); } catch { json(res, 400, { error: "invalid request" }); return true; }
|
|
705
|
-
const jobId = decodeURIComponent(path.slice("/agent/jobs/".length, -"/accept".length)); const session = studioSessions.get(value?.sessionId);
|
|
706
|
-
if (!session || session.owner !== parseCookies(req).studio_owner || value.surface !== "studio" || value.explicitUnverifiedAcceptance !== true || !session.turns.has(value.turnId) || session.activeJobId !== jobId) { json(res, 403, { error: { code: "AUTH_REQUIRED", message: "Only the owning Studio UI may accept this candidate." } }); return true; }
|
|
707
|
-
try { const receipt = await ownedStudioRuntime.accept(jobId); const record = studioEvents.get(value.turnId); if (record) { emitStudioEvent(value.turnId, { type: "receipt", receipt }); emitStudioEvent(value.turnId, { type: "done" }); record.terminal = true; } session.activeJobId = null; session.activeJobTurnId = null; json(res, 200, { receipt }); } catch (error) { json(res, 409, { error: { code: error.code || "VERIFICATION_FAILED", message: error.message } }); }
|
|
708
|
-
return true;
|
|
709
|
-
}
|
|
710
715
|
if (req.method !== "POST" || !["/agent/turn", "/agent/stop"].includes(path)) {
|
|
711
716
|
json(res, 404, { error: "not found" }); return true;
|
|
712
717
|
}
|
|
@@ -841,7 +846,6 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
|
|
|
841
846
|
for (const runner of workflowRunners.values()) await runner.close?.();
|
|
842
847
|
for (const runner of studioRunners.values()) await runner.close?.();
|
|
843
848
|
workflowRunners.clear(); studioRunners.clear();
|
|
844
|
-
if (ownedStudioRuntime?.dispose) await ownedStudioRuntime.dispose();
|
|
845
849
|
studioSessions.clear(); studioEvents.clear(); studioOwnerTokens.clear();
|
|
846
850
|
sessions.clear();
|
|
847
851
|
const { liveHub: hub } = await runtime;
|
|
@@ -228,11 +228,18 @@ export function createAgentRunner({ models: suppliedModels, sessionStore, tools
|
|
|
228
228
|
const messages = entries.filter((entry) => entry.type === "message").map((entry) => entry.message);
|
|
229
229
|
if (messages.length <= state.persisted) return;
|
|
230
230
|
const aborted = state.active?.abortRequested === true;
|
|
231
|
-
const
|
|
231
|
+
const newlyGenerated = messages.slice(state.persisted);
|
|
232
|
+
const failedAssistant = newlyGenerated.find((message) => message?.role === "assistant" && (aborted || message.stopReason === "error"));
|
|
233
|
+
const pending = newlyGenerated.filter((message) => !(aborted && message?.role === "assistant") && !(message?.role === "assistant" && message?.stopReason === "error"));
|
|
232
234
|
state.persisted = messages.length;
|
|
233
|
-
if (!pending.length) return;
|
|
234
235
|
const input = state.lastInput || {};
|
|
235
|
-
await sessionStore.append(sessionId, pending, { surface, ...(input.meta || {}) });
|
|
236
|
+
if (pending.length) await sessionStore.append(sessionId, pending, { surface, ...(input.meta || {}) });
|
|
237
|
+
if (failedAssistant && sessionStore.appendTurnError) {
|
|
238
|
+
const rawMessage = failedAssistant.errorMessage || "The model or live editor could not complete this turn.";
|
|
239
|
+
const message = aborted ? "The turn was aborted." : sanitizeUpstreamDetail(JSON.stringify({ message: rawMessage })) || rawMessage;
|
|
240
|
+
const code = aborted ? "aborted" : classifyProviderError(rawMessage);
|
|
241
|
+
await sessionStore.appendTurnError(sessionId, { code, message, at: new Date().toISOString() }, { surface, ...(input.meta || {}) });
|
|
242
|
+
}
|
|
236
243
|
};
|
|
237
244
|
|
|
238
245
|
// pi calls AgentTool.execute(toolCallId, params, signal, onUpdate) — the
|