cozyclay 1.10.0 → 2.0.2
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 +193 -0
- package/bin/agent/agent-routes.mjs +82 -72
- 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/codex-auth.mjs +2 -1
- package/bin/cozyclay.mjs +38 -11
- package/bin/live/cli.mjs +22 -3
- package/bin/mcp-runtime.mjs +45 -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-BxBLC6GT.js +4876 -0
- package/dist/assets/app-CJdxvnrl.css +1 -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-hN0j_O-p.js +17 -0
- package/dist/assets/{ticket-BgIzOBOf.js → ticket-BcmsnHn5.js} +1 -1
- package/dist/assets/workflow-B5XZwVfe.js +179 -0
- package/dist/assets/workflow-DRdwoDBT.css +1 -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 +2 -2
- package/dist/workflow/index.html +5 -5
- 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 +219 -376
- package/package.json +2 -1
- package/src/App.jsx +1275 -9086
- 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/fix-collisions.js +10 -66
- package/src/ardy/ik-drag.js +101 -0
- package/src/ardy/ik-key-json.js +43 -0
- package/src/ardy/ik.js +97 -67
- 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 +1214 -0
- package/src/ardy/timeline.jsx +152 -41
- package/src/ardy/waypoints.js +3 -3
- package/src/asset-pane.css +462 -0
- package/src/asset-pane.jsx +461 -232
- package/src/camera-tutorial.jsx +38 -18
- 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 +823 -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-panel.css +386 -0
- package/src/hierarchy-panel.jsx +88 -19
- 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/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 +288 -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/scenes.js +9 -8
- 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 +38 -268
- package/src/studio-agent-context.js +38 -9
- package/src/studio-agent-motion.js +122 -378
- package/src/studio-agent-protocol.js +66 -30
- 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 +456 -1748
- package/src/theme.js +44 -0
- package/src/timeline-extent.js +16 -0
- package/src/timeline-fps.js +13 -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 +86 -71
- 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/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/qa/css-rule-usage.mjs +283 -0
- package/tools/qa/studio-control-count.mjs +70 -32
- package/tools/run-tests.mjs +60 -3
- 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-Cu1cexZR.css +0 -1
- package/dist/assets/app-Dxdh8kds.js +0 -4861
- package/dist/assets/shot-prompt-C_g2BHVc.css +0 -1
- package/dist/assets/shot-prompt-HVJRP4Gr.js +0 -17
- package/dist/assets/workflow-CJd-PDtV.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/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,198 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.0.2
|
|
4
|
+
|
|
5
|
+
The first-run audit release: `npx cozyclay` is exercised end to end as a new
|
|
6
|
+
user, and the things that broke on that path are fixed. 2.0.1 (the camera
|
|
7
|
+
tutorial refresh and light-mode default, #586) was tagged but never reached
|
|
8
|
+
npm; this release carries it.
|
|
9
|
+
|
|
10
|
+
Agent and package
|
|
11
|
+
|
|
12
|
+
- The Studio Agent panel in the npm package answered every turn with
|
|
13
|
+
"502 — agent unavailable": the sidecar's Studio tools imported `three`
|
|
14
|
+
(a devDependency the package does not ship) through `src/scenes.js`. The
|
|
15
|
+
timeline clock moved to its own module and a test now walks every import
|
|
16
|
+
reachable from `bin/` and fails on any chain to `three`. (#591)
|
|
17
|
+
- A CLIProxy key in the environment no longer leaves the panel on the ChatGPT
|
|
18
|
+
sign-in wall; readiness counts every key-authenticated provider. (#591)
|
|
19
|
+
- `CCLAY_KIMODO_HOST=… npx cozyclay` died at start with
|
|
20
|
+
`Cannot find package 'three'`: the motion bridge now runs from the staged
|
|
21
|
+
MCP runtime (which carries `three`, `tools/` and `dist/models`), and the
|
|
22
|
+
package isolation test keeps it that way. (#591)
|
|
23
|
+
- A live-hub port already taken by another CozyClay is reported on the
|
|
24
|
+
launcher's terminal and named in the pane's error instead of
|
|
25
|
+
"Studio execution is not installed"; agent handler failures are logged
|
|
26
|
+
before the 502; a session id with no record no longer shows
|
|
27
|
+
"Previous conversation could not be restored". (#591)
|
|
28
|
+
|
|
29
|
+
Studio
|
|
30
|
+
|
|
31
|
+
- Create on the start screen opens the selected sample scene under the typed
|
|
32
|
+
name instead of an empty stage named after it; starting over with unsaved
|
|
33
|
+
work asks first. (#591)
|
|
34
|
+
- Reloading a project created from the start screen brought the previous
|
|
35
|
+
session's walking take back onto the character, with "Unsaved changes":
|
|
36
|
+
the hosted-demo seed re-fired on every mount while the bridge probe was
|
|
37
|
+
down. It now skips a saved project (a `?scene=` launch still seeds), and a
|
|
38
|
+
reload opens on the clean checkpoint. (#591)
|
|
39
|
+
- With a take installed, the gizmo and the Transform panel's Position and
|
|
40
|
+
Rotation move the character again; the take keeps animating relative to
|
|
41
|
+
the new placement. (#591)
|
|
42
|
+
- The shot card's hover actions sit above the card, so clicking a short
|
|
43
|
+
shot's name selects it instead of hitting Delete. Draw rail is disabled
|
|
44
|
+
with a reason when no shot is selected. (#591)
|
|
45
|
+
|
|
46
|
+
Tests
|
|
47
|
+
|
|
48
|
+
- The start-screen and project-menu browser suites run against the v2
|
|
49
|
+
shell again, gain the sample-card Create regression, and join the CI
|
|
50
|
+
browser matrix as observation suites; new regressions cover the package
|
|
51
|
+
import graph, CLIProxy readiness, the stage transform and the reload
|
|
52
|
+
session. (#591)
|
|
53
|
+
|
|
54
|
+
## 2.0.1
|
|
55
|
+
|
|
56
|
+
- The camera tutorial follows the v2 shell and new installs default to the
|
|
57
|
+
light theme. (#586)
|
|
58
|
+
|
|
59
|
+
## 2.0.0
|
|
60
|
+
|
|
61
|
+
CozyClay 2.0 is the v2 editor: a floating-glass shell with light and dark
|
|
62
|
+
themes, one four-mode Studio (Stage, Pose, Camera, Motion on keys 1-4), and
|
|
63
|
+
the v2 Outliner, Sequencer, start screen, Details panels and Preferences.
|
|
64
|
+
Under it, every document edit, whether it comes from the UI, the Agent panel,
|
|
65
|
+
MCP or the CLI, now runs through one command bus on a document store with one
|
|
66
|
+
undo history, so anything the editor can do is callable by an agent without
|
|
67
|
+
separate wiring. The Studio Agent panel in the npm package works again
|
|
68
|
+
(#576). **No MCP tool or CLI command was removed**: legacy MCP tools are
|
|
69
|
+
aliases of bus commands and answer the editor's receipts.
|
|
70
|
+
|
|
71
|
+
Studio
|
|
72
|
+
|
|
73
|
+
- Floating-glass shell: the viewport fills the window and the top bar, both
|
|
74
|
+
columns, the dock and the status strip float over it. Light and dark
|
|
75
|
+
themes, and the stage follows the theme live. The dock runs Animation |
|
|
76
|
+
Assets tabs sized to the Sequencer's lanes, with the shot preview docked at
|
|
77
|
+
its right end; the standing status row is gone and live export status
|
|
78
|
+
floats in the viewport. (#570, #572, #519)
|
|
79
|
+
- One four-mode switch, Stage, Pose, Camera and Motion, on keys 1-4. Pose
|
|
80
|
+
mode is the single way into IK (W/E/R pick ik, trail and pin there); the
|
|
81
|
+
timeline IK toggle and the Rig Control IK buttons are gone, and leaving
|
|
82
|
+
Pose ends IK. (#521)
|
|
83
|
+
- v2 Outliner: 36 px header with the item count, search, typed rows, amber
|
|
84
|
+
selection; right-click Create replaces the toolbar's + Add object. (#525)
|
|
85
|
+
- v2 Sequencer: header, readout and actions, a 190 px track tree, clip
|
|
86
|
+
fills, amber playhead, keys and pin bands, scrollable camera, rail and
|
|
87
|
+
crane rows. (#528)
|
|
88
|
+
- v2 start screen: project nav, template categories, recent rows, the
|
|
89
|
+
browser project store and a project preview; File › New opens the same
|
|
90
|
+
screen. (#531, #551)
|
|
91
|
+
- v2 Details panels and primitives: pose tools with an auto-fix preview,
|
|
92
|
+
motion details with takes, the viewport toolbar and overlays, the top bar,
|
|
93
|
+
menus and status bar, the content browser and log, and the Preferences
|
|
94
|
+
dialog. (#522, #523, #524, #526, #527, #529, #530, #532)
|
|
95
|
+
- Generate actions have one home: the top-bar Generate Motion button runs
|
|
96
|
+
every prompt block when any block has text. Copy follows v2: "ARDY" leaves
|
|
97
|
+
the user-facing text, pose toasts say "Pose mode", path endpoints read
|
|
98
|
+
PATH START / PATH END, the first-run guide says Outliner. (#550, #551)
|
|
99
|
+
- Top-View props recede so the camera, cast and rail read first. (#566)
|
|
100
|
+
- When WebGL is unavailable the stage says so instead of staying blank. (#518)
|
|
101
|
+
- 194 dead CSS rules are gone and the v2 information architecture is
|
|
102
|
+
recorded in `docs/studio-ui-ia.md`; Details, Outliner and top-bar props
|
|
103
|
+
that were never read are dropped. (#533, #552, #556, #558, #560, #562)
|
|
104
|
+
- Korean UI: 56 reversed `ko()` argument pairs in the motion card are fixed
|
|
105
|
+
(#505, by @romanr), as are the VideoCapturePanel quality-gate messages
|
|
106
|
+
(#565).
|
|
107
|
+
|
|
108
|
+
IK and motion
|
|
109
|
+
|
|
110
|
+
- IK and motion trails on the split architecture: delta IK drag baking over
|
|
111
|
+
the raw clip, per-key blend persistence, a remembered IK camera entry,
|
|
112
|
+
trail picking against the rendered rig, limb, head and root trail edits
|
|
113
|
+
with conflict warnings. (#511)
|
|
114
|
+
- Range pin with an exact hinge solve and body reach compensation, carried
|
|
115
|
+
through persistence, undo/redo, timeline bands and preview markers. (#515)
|
|
116
|
+
- Fit to platforms with Fit / Remove controls and a stable knee bend side,
|
|
117
|
+
verified over 784 cases with no reverse bends. (#517)
|
|
118
|
+
- One motion generation pipeline on the job contract: `motion.generate`
|
|
119
|
+
serves the UI, the Agent and MCP alike, with concurrent-edit parity (a
|
|
120
|
+
stale job publishes nothing, an unrelated object edit still installs),
|
|
121
|
+
Stop and cancel, reconnect, and receipt undo. The agent-only motion
|
|
122
|
+
pipeline is deleted. (#444, #452)
|
|
123
|
+
- Image-to-video motion is named `i2v` in code and "AI motion" in the UI;
|
|
124
|
+
"fal" names only the provider adapter. Command ids, analytics values and
|
|
125
|
+
persisted values are unchanged. (#509)
|
|
126
|
+
|
|
127
|
+
Command bus and document store
|
|
128
|
+
|
|
129
|
+
- Every document mutation runs through one command bus: wire transactions,
|
|
130
|
+
a scoped job fence, receipts, refusals and exposure. Action registrations,
|
|
131
|
+
the agent binding and the domain writers moved out of App.jsx. (#435,
|
|
132
|
+
#437, #459, #460, #477)
|
|
133
|
+
- A document store owns stage, objects, shots, cast, motion and IK, and
|
|
134
|
+
project and scenes, each routed through the bus, with collection element
|
|
135
|
+
kinds and cross-domain undo order. (#449, #447, #445, #441, #442, #448,
|
|
136
|
+
#461, #480)
|
|
137
|
+
- One store-owned undo history; the per-domain history owners are gone.
|
|
138
|
+
(#494) Duplicate agent paths are removed: `inspect_studio`, `run_action`
|
|
139
|
+
and `verify_result` are the three tool families, `operate_studio` is an
|
|
140
|
+
alias, and selection, playhead and view changes add no undo entries.
|
|
141
|
+
(#495) The writer baseline is zero and parity is complete: all 62
|
|
142
|
+
document-mutating UI handlers reach `run`, enforced by ratchet tests and
|
|
143
|
+
a generated parity matrix. (#436, #496)
|
|
144
|
+
|
|
145
|
+
Agent
|
|
146
|
+
|
|
147
|
+
- The Studio agent pane reaches the whole Studio through the action
|
|
148
|
+
registry the UI dispatches through, and every bus command is discoverable
|
|
149
|
+
and callable through agent `run_action`, MCP `studio_commands` /
|
|
150
|
+
`studio_run` and `cclay live commands` / `cclay live run` with no
|
|
151
|
+
agent-specific wiring. (#423, #438)
|
|
152
|
+
- Motion failures are visible and routable, `verify_result` reports
|
|
153
|
+
honestly, and a failed attempt no longer uses up the message's one
|
|
154
|
+
generation. (#426)
|
|
155
|
+
- Minimal Studio Agent panel: an icon-only head, the account under the ⋯
|
|
156
|
+
menu, one-line notices, an empty state with suggestion chips; it sits
|
|
157
|
+
under Details in the right column instead of replacing it. (#574, #570)
|
|
158
|
+
- In the npm package the Studio Agent panel loads its live tools from the
|
|
159
|
+
staged MCP runtime that `cclay mcp` installs once per version, so the live
|
|
160
|
+
hub starts and the pane works; `mcp/mesh-file.mjs` ships with the package
|
|
161
|
+
and `cclay mcp` starts again. A runtime that cannot be installed is
|
|
162
|
+
reported on the launcher's terminal. (#576)
|
|
163
|
+
|
|
164
|
+
MCP
|
|
165
|
+
|
|
166
|
+
- Legacy tools are thin aliases of bus commands and return the editor's JSON
|
|
167
|
+
receipts: revision before and after, retained undo and refusal codes.
|
|
168
|
+
`apply_batch` maps to `objects.batch` and keeps `atomic`, `stopOnError`
|
|
169
|
+
and one undo entry; replacing scene ids needs a Studio-issued confirmation
|
|
170
|
+
token. (#446)
|
|
171
|
+
- `initialize` waits the SDK's default 60 s. (#502)
|
|
172
|
+
|
|
173
|
+
Analytics
|
|
174
|
+
|
|
175
|
+
- Builder telemetry: pose edits, i2v motion A→B, `$exception`, a device
|
|
176
|
+
profile (GPU and frame rate), the install source from
|
|
177
|
+
`npx cozyclay --via`, use case and update status. (#466)
|
|
178
|
+
|
|
179
|
+
Tools
|
|
180
|
+
|
|
181
|
+
- A mocap research bench under `tools/bench/` and `tools/gt-render/`:
|
|
182
|
+
ground-truth part-colour renders with camera truths (#428), a GVHMR
|
|
183
|
+
extraction bench (#430), a scorer for pose, trajectory, endpoints, overlap
|
|
184
|
+
and contact (#431), 3D fit stages F0-F5 (#432), a cube-contact scenario
|
|
185
|
+
(#454), the observation-driven GVHMR tuning ladder G0-G5 (#462, #488,
|
|
186
|
+
#492) and the known-character tracker T1 (#500).
|
|
187
|
+
|
|
188
|
+
Housekeeping
|
|
189
|
+
|
|
190
|
+
- The camera-rail browser test waits for the delete toast, which exposed and
|
|
191
|
+
fixed a stale-toast dismissal bug (#470, #483); the lifecycle test waits
|
|
192
|
+
for the grandchild to disappear (#479).
|
|
193
|
+
- The owner's v2 editor design contract is adopted in the docs (#512).
|
|
194
|
+
- Repository traffic is archived to the `metrics` branch twice a week.
|
|
195
|
+
|
|
3
196
|
## 1.10.0
|
|
4
197
|
|
|
5
198
|
Props and characters can now be hidden without being deleted, the Studio top
|
|
@@ -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,13 +231,30 @@ 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
|
-
const
|
|
252
|
+
const livePort = Number(process.env.COZYCLAY_LIVE_PORT ?? 5184);
|
|
253
|
+
const liveHub = await startLiveHub(livePort, { token, owner });
|
|
254
|
+
// EADDRINUSE comes back as null: another CozyClay (or its dev server) owns
|
|
255
|
+
// the port, and the Studio page is built to connect to that port, so the
|
|
256
|
+
// pane would answer every turn with a misleading capability error.
|
|
257
|
+
if (!liveHub) console.error(`cozyclay: live hub port ${livePort} is already in use (another CozyClay or its dev server?); the Agent pane cannot drive this Studio until that port is free.`);
|
|
240
258
|
registry.setLiveHub(liveHub);
|
|
241
259
|
const handlers = registry.createToolHandlers().map((tool) => ({
|
|
242
260
|
...tool,
|
|
@@ -257,14 +275,20 @@ function liveToolsRuntime() {
|
|
|
257
275
|
publishLiveEndpoint({ port: liveHub.port, token, owner });
|
|
258
276
|
liveHub.server?.once("close", () => removeLiveEndpoint(liveHub.port));
|
|
259
277
|
}
|
|
260
|
-
return { liveHub, handlers };
|
|
261
|
-
}).catch((error) =>
|
|
278
|
+
return { liveHub, handlers, hubPortBusy: !liveHub ? livePort : null };
|
|
279
|
+
}).catch((error) => {
|
|
280
|
+
// The pane answers CAPABILITY_MISSING on use; the reason belongs on the
|
|
281
|
+
// launcher's terminal rather than nowhere.
|
|
282
|
+
console.error(`cozyclay: Studio live tools unavailable: ${error?.message ?? error}`);
|
|
283
|
+
for (const line of error?.lines ?? []) console.error(`cozyclay: ${line}`);
|
|
284
|
+
return { error };
|
|
285
|
+
});
|
|
262
286
|
}
|
|
263
287
|
|
|
264
|
-
export function createAgentHandler({ auth = defaultAuth, codex, models, codexBaseUrl, cliproxyBaseUrl, env, fauxProvider, handlers, liveHub,
|
|
288
|
+
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
289
|
const requestContext = new AsyncLocalStorage();
|
|
266
290
|
codex ||= defaultClient(auth, requestContext);
|
|
267
|
-
const runtime = handlers !== undefined || liveHub !== undefined ? Promise.resolve({ handlers: handlers ?? [], liveHub }) : liveToolsRuntime();
|
|
291
|
+
const runtime = handlers !== undefined || liveHub !== undefined ? Promise.resolve({ handlers: handlers ?? [], liveHub }) : liveToolsRuntime(mcpRuntime);
|
|
268
292
|
const renderGuidance = async (environment) => {
|
|
269
293
|
try {
|
|
270
294
|
const { handlers: tools, liveHub: hub } = await runtime;
|
|
@@ -306,7 +330,6 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
|
|
|
306
330
|
const sessionStore = injectedSessionStore ?? createSessionStore();
|
|
307
331
|
const studioSessions = new Map();
|
|
308
332
|
const studioEvents = new Map();
|
|
309
|
-
let ownedStudioRuntime = studioRuntime || null;
|
|
310
333
|
const studioOwnerTokens = new Map();
|
|
311
334
|
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
335
|
const pruneStudioSessions = () => {
|
|
@@ -323,13 +346,6 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
|
|
|
323
346
|
if (!owner || supplied !== owner) throw Object.assign(new Error("Studio session owner mismatch."), { code: "AUTH_REQUIRED" });
|
|
324
347
|
return owner;
|
|
325
348
|
};
|
|
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
349
|
const emitStudioEvent = (turnId, event) => {
|
|
334
350
|
const record = studioEvents.get(turnId) || { next: 0, events: [], listeners: new Set(), terminal: false };
|
|
335
351
|
// Execution telemetry is advisory and is validated key-by-key in the
|
|
@@ -353,7 +369,6 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
|
|
|
353
369
|
if (kind !== "signed_out" && kind !== "replaced") return;
|
|
354
370
|
for (const session of sessions.values()) session.controller?.abort();
|
|
355
371
|
for (const session of studioSessions.values()) session.controller?.abort();
|
|
356
|
-
void ownedStudioRuntime?.dispose?.(); ownedStudioRuntime = studioRuntime || null;
|
|
357
372
|
for (const runner of [...workflowRunners.values(), ...studioRunners.values()]) void runner.close?.();
|
|
358
373
|
workflowRunners.clear(); studioRunners.clear();
|
|
359
374
|
workflowModels = models;
|
|
@@ -401,36 +416,21 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
|
|
|
401
416
|
// An owned retired id is still stale while another motion job is active:
|
|
402
417
|
// this Stop cannot acknowledge the current turn's different job.
|
|
403
418
|
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.
|
|
419
|
+
// Only this turn's held editor job can settle the tool quietly. A retired
|
|
420
|
+
// job from an earlier turn must never silence an unrelated turn's Stop.
|
|
409
421
|
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.
|
|
422
|
+
// The editor's outcome, not a sidecar abort, establishes application.
|
|
413
423
|
let outcome = null;
|
|
414
|
-
if (jobId
|
|
424
|
+
if (jobId) outcome = await session.controlJob('job.cancel', jobId);
|
|
415
425
|
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.
|
|
426
|
+
// Let the acknowledged job settle its held tool with that outcome before
|
|
427
|
+
// aborting the model lane. Aborting the shared signal would interrupt the
|
|
428
|
+
// tool first and lose the editor's proof. Without a held job, abort both.
|
|
430
429
|
if (!acknowledged) session.controller?.abort();
|
|
431
430
|
await session.modelSession?.abort?.("studio stop", acknowledged ? { quiet: true } : undefined);
|
|
432
431
|
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;
|
|
433
432
|
}
|
|
433
|
+
if (!studioRuntime && !hub && hubDeps.hubPortBusy) throw new StudioProtocolError("CAPABILITY_MISSING", `The Studio live hub could not start: port ${hubDeps.hubPortBusy} is in use by another CozyClay or its dev server. Free that port and relaunch.`);
|
|
434
434
|
if (!studioRuntime && (!hub?.command || !hub?.workspaceId)) throw new StudioProtocolError("CAPABILITY_MISSING", "Studio execution is not installed.");
|
|
435
435
|
if (!value.context.host.workspaceHandle) throw new StudioProtocolError("LIVE_HUB_UNAVAILABLE", "A connected editor handle is required.");
|
|
436
436
|
let session = studioSessions.get(value.sessionId);
|
|
@@ -440,7 +440,7 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
|
|
|
440
440
|
studioOwner(req, value.sessionId, true);
|
|
441
441
|
let persisted = null;
|
|
442
442
|
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") : []),
|
|
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") : []), host: null, updatedAt: clock() };
|
|
444
444
|
studioSessions.set(value.sessionId, session);
|
|
445
445
|
}
|
|
446
446
|
session.updatedAt = clock();
|
|
@@ -468,33 +468,52 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
|
|
|
468
468
|
const admission = {
|
|
469
469
|
commandId: () => randomUUID(), host: studioIdentity(value.context.host), revision: value.context.revision.scene,
|
|
470
470
|
refresh: async () => {
|
|
471
|
-
|
|
471
|
+
// Read at the admitted host: a scene action may have opened another
|
|
472
|
+
// scene of this workspace during the turn (studio-tools adopts it).
|
|
473
|
+
const host = { ...value.context.host, ...admission.host };
|
|
474
|
+
const refreshed = studioRuntime?.readContext ? await studioRuntime.readContext(host) : await authoritativeStudioContext({ ...value, context: { ...value.context, host } }, hub);
|
|
472
475
|
admission.revision = refreshed.revision.scene;
|
|
473
476
|
},
|
|
474
477
|
};
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
478
|
+
// One motion generation per user message, whichever path starts it:
|
|
479
|
+
// generate_motion below and a run_action job action share this gate.
|
|
480
|
+
const generation = { used: false, failures: 0 };
|
|
481
|
+
// The editor's command index (authoritative context) declares each action's
|
|
482
|
+
// generation and hub timeout; no sidecar list of actions exists.
|
|
483
|
+
const controlJob = async (action, jobId) => {
|
|
484
|
+
await admission.refresh();
|
|
485
|
+
return hub.command('run_action', { name: 'run_action', args: { action, args: { jobId, ...(action === 'job.await' ? { timeoutMs: 300_000 } : {}) } },
|
|
486
|
+
commandId: randomUUID(), host: admission.host, expectedRevision: admission.revision }, value.context.host.workspaceHandle, { timeoutMs: 300_000 });
|
|
487
|
+
};
|
|
488
|
+
session.controlJob = controlJob;
|
|
489
|
+
const onJob = async started => {
|
|
490
|
+
const jobId = started.jobId;
|
|
491
|
+
session.motionJobIds.add(jobId);
|
|
492
|
+
persistenceMeta.motionJobIds = [...session.motionJobIds];
|
|
493
|
+
session.activeJobId = jobId; session.activeJobTurnId = value.turnId;
|
|
494
|
+
const cancel = () => { void controlJob('job.cancel', jobId).catch(error => send({ type: 'error', code: error.code ?? 'CANCEL_FAILED', message: error.message })); };
|
|
495
|
+
controller.signal.addEventListener('abort', cancel, { once: true });
|
|
496
|
+
if (controller.signal.aborted) cancel();
|
|
497
|
+
send({ type: 'job.state', jobId, commandId: started.commandId, state: 'generating', phase: 'generating' });
|
|
498
|
+
try {
|
|
499
|
+
const outcome = await controlJob('job.await', jobId);
|
|
500
|
+
send({ type: 'job.state', jobId, commandId: outcome.commandId, state: outcome.ok ? 'installed' : outcome.code === 'CANCELLED' ? 'cancelled' : 'failed', phase: 'complete' });
|
|
501
|
+
return outcome;
|
|
502
|
+
} finally {
|
|
503
|
+
controller.signal.removeEventListener('abort', cancel);
|
|
504
|
+
if (session.activeJobId === jobId) { session.activeJobId = null; session.activeJobTurnId = null; }
|
|
496
505
|
}
|
|
497
506
|
};
|
|
507
|
+
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) });
|
|
508
|
+
const motion = async args => {
|
|
509
|
+
// Private artifact IDs belonged to the retired sidecar runtime. Reuse
|
|
510
|
+
// retained editor takes through motion.loadVersion, not another installer.
|
|
511
|
+
if (args.source.kind !== 'generate') throw new StudioProtocolError('CAPABILITY_MISSING', 'Private motion artifacts are no longer retained. Use motion.loadVersion for an editor take.');
|
|
512
|
+
await tools.internal.invoke('inspect_studio', { scope: 'motion', ids: [args.characterId] });
|
|
513
|
+
const receipt = await tools.internal.invoke('generate_motion', args);
|
|
514
|
+
if (receipt.ok && receipt.status === 'completed') send({ type: 'receipt', receipt });
|
|
515
|
+
return receipt;
|
|
516
|
+
};
|
|
498
517
|
const modelTools = tools.map(tool => ({
|
|
499
518
|
...tool,
|
|
500
519
|
handler: async args => {
|
|
@@ -699,14 +718,6 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
|
|
|
699
718
|
}
|
|
700
719
|
return true;
|
|
701
720
|
}
|
|
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
721
|
if (req.method !== "POST" || !["/agent/turn", "/agent/stop"].includes(path)) {
|
|
711
722
|
json(res, 404, { error: "not found" }); return true;
|
|
712
723
|
}
|
|
@@ -841,7 +852,6 @@ export function createAgentHandler({ auth = defaultAuth, codex, models, codexBas
|
|
|
841
852
|
for (const runner of workflowRunners.values()) await runner.close?.();
|
|
842
853
|
for (const runner of studioRunners.values()) await runner.close?.();
|
|
843
854
|
workflowRunners.clear(); studioRunners.clear();
|
|
844
|
-
if (ownedStudioRuntime?.dispose) await ownedStudioRuntime.dispose();
|
|
845
855
|
studioSessions.clear(); studioEvents.clear(); studioOwnerTokens.clear();
|
|
846
856
|
sessions.clear();
|
|
847
857
|
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
|