cozyclay 2.0.2 → 2.1.1

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.
Files changed (262) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +17 -2
  3. package/THIRD_PARTY_NOTICES.md +8 -0
  4. package/bin/agent/agent-routes.mjs +211 -36
  5. package/bin/agent/agent-runner.mjs +98 -4
  6. package/bin/agent/codex-client.mjs +2 -1
  7. package/bin/agent/fal-contract.mjs +0 -17
  8. package/bin/agent/h3-preservation.mjs +6 -8
  9. package/bin/agent/provider-keys.mjs +1 -1
  10. package/bin/agent/providers.mjs +124 -3
  11. package/bin/agent/script-tool.mjs +74 -0
  12. package/bin/agent/session-store.mjs +5 -5
  13. package/bin/agent/studio-prompt.mjs +2 -1
  14. package/bin/agent/studio-rebase.mjs +314 -0
  15. package/bin/agent/studio-tools.mjs +21 -14
  16. package/bin/agent/supervisor.mjs +311 -0
  17. package/bin/agent/video-adapters.mjs +0 -7
  18. package/bin/codex-auth.mjs +4 -4
  19. package/bin/live/cli.mjs +23 -8
  20. package/bin/live/client.mjs +1 -1
  21. package/bin/live/studio.mjs +5 -2
  22. package/bin/mcp-runtime.mjs +2 -2
  23. package/bin/telemetry-state.mjs +3 -3
  24. package/dist/app/index.html +6 -6
  25. package/dist/assets/analytics-C9pMpW7s.js +1 -0
  26. package/dist/assets/app-B_2XB9rw.css +1 -0
  27. package/dist/assets/app-DAuvKPP8.js +4920 -0
  28. package/dist/assets/{demo-ByeYp-dY.js → demo-CvKuBgy5.js} +1 -1
  29. package/dist/assets/{first-shot-handoff-DgT28ZiV.js → first-shot-handoff-msLI71g1.js} +1 -1
  30. package/dist/assets/{landing-BaXkpFip.js → landing-ChUvB_zL.js} +1 -1
  31. package/dist/assets/scene-asset-sync-DKkpjKBV.js +19 -0
  32. package/dist/assets/{shot-prompt-CbqWcWYi.css → scene-asset-sync-DjJ_cJ2R.css} +1 -1
  33. package/dist/assets/{ticket-BcmsnHn5.js → ticket-BPk2Ni95.js} +1 -1
  34. package/dist/assets/workflow-jJRBR94Z.js +179 -0
  35. package/dist/cozyclay-package.json +1 -1
  36. package/dist/index.html +328 -222
  37. package/dist/llms.txt +14 -0
  38. package/dist/sitemap.xml +1 -1
  39. package/dist/workflow/index.html +4 -4
  40. package/mcp/LIVE-PROTOCOL.md +23 -2
  41. package/mcp/README.md +37 -2
  42. package/mcp/ardy-prompts.mjs +1 -1
  43. package/mcp/image-file.mjs +132 -0
  44. package/mcp/live-hub.mjs +2 -2
  45. package/mcp/package.json +1 -0
  46. package/mcp/runtime/package-lock.json +7 -0
  47. package/mcp/runtime/package.json +1 -0
  48. package/mcp/server.mjs +0 -6
  49. package/mcp/tool-handlers.mjs +289 -87
  50. package/package.json +11 -2
  51. package/src/App.jsx +813 -278
  52. package/src/agent-image-client.js +94 -0
  53. package/src/analytics.js +47 -3
  54. package/src/app-stage.jsx +129 -2745
  55. package/src/ardy/auto-physics.js +6 -16
  56. package/src/ardy/collision-blockers.js +3 -7
  57. package/src/ardy/fix-collisions.js +5 -35
  58. package/src/ardy/ik-drag.js +86 -5
  59. package/src/ardy/ik-key-json.js +3 -1
  60. package/src/ardy/ik.js +15 -13
  61. package/src/ardy/motion-calibration.js +1 -1
  62. package/src/ardy/npz.js +0 -8
  63. package/src/ardy/physics-panel.jsx +3 -2
  64. package/src/ardy/physics-review.js +3 -2
  65. package/src/ardy/physics-support.js +1 -1
  66. package/src/ardy/physics-surface.js +1 -1
  67. package/src/ardy/platform-fit-panel.jsx +2 -1
  68. package/src/ardy/playback.js +1 -5
  69. package/src/ardy/range-pin.js +2 -2
  70. package/src/ardy/root-drop.js +0 -1
  71. package/src/ardy/timeline.css +184 -6
  72. package/src/ardy/timeline.jsx +121 -36
  73. package/src/ardy/to-cskel27.js +0 -2
  74. package/src/ardy/waypoints.js +0 -3
  75. package/src/ardy-bridge-contract.js +281 -0
  76. package/src/asset-pane.jsx +19 -9
  77. package/src/auto-color.js +1 -1
  78. package/src/burn-in.js +1 -1
  79. package/src/camera-block.js +1 -1
  80. package/src/camera-follow.js +75 -32
  81. package/src/camera-move.js +0 -6
  82. package/src/camera-tutorial.jsx +6 -7
  83. package/src/character-kind.js +79 -0
  84. package/src/command-bus.js +76 -17
  85. package/src/commands/cast.js +31 -7
  86. package/src/commands/elements/object.js +1 -1
  87. package/src/commands/elements/shot.js +1 -1
  88. package/src/commands/elements.js +1 -1
  89. package/src/commands/export.js +3 -3
  90. package/src/commands/motion.js +2 -2
  91. package/src/commands/objects.js +105 -10
  92. package/src/commands/project.js +15 -2
  93. package/src/commands/sculpt-images.js +53 -0
  94. package/src/commands/shot.js +88 -10
  95. package/src/commands/view.js +10 -4
  96. package/src/coplanar-depth.js +216 -0
  97. package/src/cuts.js +7 -6
  98. package/src/domains/cast.js +60 -43
  99. package/src/domains/motion/motion-intent-codec.js +50 -0
  100. package/src/domains/motion/motion-range-pin-rules.js +23 -0
  101. package/src/domains/motion/motion-video-capture.js +664 -0
  102. package/src/domains/motion.js +253 -742
  103. package/src/domains/objects.js +106 -5
  104. package/src/domains/scenes.js +79 -28
  105. package/src/domains/shots.js +33 -7
  106. package/src/error-boundary.jsx +1 -1
  107. package/src/execution-telemetry.js +5 -5
  108. package/src/facing-marks.js +14 -1
  109. package/src/first-success-guide.jsx +35 -4
  110. package/src/gizmo-claim.js +3 -1
  111. package/src/grid-floor.jsx +1 -4
  112. package/src/hierarchy-model.js +48 -35
  113. package/src/hierarchy-panel.css +1 -0
  114. package/src/hierarchy-panel.jsx +54 -102
  115. package/src/history.js +0 -2
  116. package/src/i2v-motion-client.js +1 -4
  117. package/src/ime.js +6 -0
  118. package/src/keyframe-pack.js +15 -6
  119. package/src/line-edit.js +4 -4
  120. package/src/live-control.js +6 -6
  121. package/src/matte-editor.js +2 -8
  122. package/src/matte.js +0 -19
  123. package/src/mesh-graph-clone.js +16 -2
  124. package/src/mesh-sniff.js +0 -1
  125. package/src/monotone-cubic.js +40 -0
  126. package/src/motion-store.js +4 -4
  127. package/src/motion-trail.js +5 -11
  128. package/src/multimodel-ingest.js +1 -3
  129. package/src/object-catalog.jsx +7 -5
  130. package/src/object-gizmo.jsx +45 -31
  131. package/src/object-path.js +310 -67
  132. package/src/object-travel.js +224 -0
  133. package/src/otio.js +8 -3
  134. package/src/panels/CharacterTransformPanel.jsx +24 -6
  135. package/src/panels/ObjectTransformPanel.jsx +4 -3
  136. package/src/panels/PoseImagePanel.jsx +205 -0
  137. package/src/panels/PromptBlocksPanel.jsx +3 -1
  138. package/src/panels/ProxyFigurePanel.jsx +56 -0
  139. package/src/panels/ReferenceImageField.jsx +1 -1
  140. package/src/panels/RigControlPanel.jsx +24 -3
  141. package/src/panels/RigPanel.jsx +2 -1
  142. package/src/panels/SculptPanel.jsx +405 -0
  143. package/src/panels/SubjectBox.jsx +1 -1
  144. package/src/panels/SubjectsPanel.jsx +0 -2
  145. package/src/panels/VideoCapturePanel.jsx +2 -1
  146. package/src/panels/pose-image.css +42 -0
  147. package/src/panels/pose.css +3 -0
  148. package/src/panels/sculpt.css +162 -0
  149. package/src/plan-diagram.js +184 -0
  150. package/src/planview.jsx +127 -80
  151. package/src/playground.js +3 -5
  152. package/src/pose-fade.js +77 -0
  153. package/src/pose-gizmo.css +23 -0
  154. package/src/pose-gizmo.js +96 -0
  155. package/src/posestudio.jsx +134 -83
  156. package/src/project-browser.css +53 -0
  157. package/src/project-browser.jsx +49 -4
  158. package/src/project.js +55 -24
  159. package/src/props.jsx +177 -36
  160. package/src/proxy-figure.jsx +69 -0
  161. package/src/range-pin-panel.jsx +0 -1
  162. package/src/receipt-findings.js +20 -0
  163. package/src/render-passes.js +0 -1
  164. package/src/resource-status.jsx +1 -1
  165. package/src/root-path.js +48 -0
  166. package/src/route-owner.js +52 -0
  167. package/src/scene-asset-cache.js +1 -2
  168. package/src/scene-assets.js +12 -4
  169. package/src/scene-fbx.js +1 -1
  170. package/src/scene-mesh-cache.js +0 -1
  171. package/src/scene-objects.js +319 -19
  172. package/src/scenes.js +58 -9
  173. package/src/sculpt-blueprint.js +65 -0
  174. package/src/sculpt-compare.js +384 -0
  175. package/src/sculpt-geometry.js +141 -0
  176. package/src/sculpt-mode.js +168 -0
  177. package/src/sculpt-recipe.js +385 -0
  178. package/src/sculpt-run.js +351 -0
  179. package/src/sculpt-stage.jsx +221 -0
  180. package/src/settings-menu.jsx +1 -1
  181. package/src/shell/BoardSlot.jsx +341 -0
  182. package/src/shell/BottomDock.jsx +60 -16
  183. package/src/shell/CompositionGuideButton.jsx +20 -0
  184. package/src/shell/DetailsSlot.jsx +26 -6
  185. package/src/shell/MenuBar.jsx +78 -6
  186. package/src/shell/OutlinerSlot.jsx +3 -1
  187. package/src/shell/ShotPreviewHeader.jsx +17 -0
  188. package/src/shell/TopBar.jsx +18 -3
  189. package/src/shell/ViewportToolbar.jsx +36 -31
  190. package/src/shell/dock.css +239 -0
  191. package/src/shell/glass-regions.css +3 -2
  192. package/src/shell/log-store.js +4 -4
  193. package/src/shell/mode.css +20 -0
  194. package/src/shell/studio-shell-context.js +4 -1
  195. package/src/shell/viewport.css +12 -0
  196. package/src/shot-authoring.js +69 -6
  197. package/src/stable-items.js +1 -1
  198. package/src/stage-attach-frames.js +169 -0
  199. package/src/stage-canvas-rigs.jsx +402 -0
  200. package/src/stage-capture.jsx +216 -0
  201. package/src/stage-characters.jsx +302 -0
  202. package/src/stage-editor-gizmos.jsx +1229 -0
  203. package/src/stage-labels-ko.js +204 -0
  204. package/src/stage-startup-defaults.js +182 -0
  205. package/src/starter-scenes.js +1 -1
  206. package/src/storyboard.js +24 -5
  207. package/src/studio-actions.js +42 -9
  208. package/src/studio-agent-commands.js +213 -23
  209. package/src/studio-agent-context.js +11 -9
  210. package/src/studio-agent-motion.js +2 -1
  211. package/src/studio-agent-protocol.js +60 -24
  212. package/src/studio-app-binding.js +56 -23
  213. package/src/studio-contact-sheet.js +3 -3
  214. package/src/studio-elements.js +11 -2
  215. package/src/studio-geometry-facts.js +114 -0
  216. package/src/styles.css +92 -9
  217. package/src/ui-scrub.js +4 -4
  218. package/src/ui.jsx +6 -4
  219. package/src/use-asset-shelf.js +60 -0
  220. package/src/use-camera-tutorial.js +50 -0
  221. package/src/use-embed-bridge.js +12 -0
  222. package/src/use-first-success-prompt.js +7 -0
  223. package/src/use-i2v-capture-modal.js +15 -0
  224. package/src/use-sculpt-mode.js +95 -0
  225. package/src/workflow/AgentPanel.jsx +106 -18
  226. package/src/workflow/WorkflowBuilder.jsx +1 -1
  227. package/src/workflow/agent-client.js +113 -45
  228. package/src/workflow/agent-panel.css +35 -0
  229. package/src/workflow/attachment-image.js +1 -1
  230. package/src/workflow/canvas-commands.js +3 -3
  231. package/src/workflow/cozy-scene-node.js +3 -3
  232. package/src/workflow/local-workflow.js +2 -2
  233. package/src/workflow/motion-input.js +3 -3
  234. package/src/workflow/node-schema.js +1 -1
  235. package/src/workflow/scene-asset-sync.js +2 -9
  236. package/src/workflow/shot-prompt-node.js +1 -1
  237. package/src/workflow/video-contract.js +1 -1
  238. package/src/workflow/workflow-resources.js +0 -8
  239. package/src/workflow-send.js +87 -0
  240. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  241. package/tools/check-eslint.mjs +57 -0
  242. package/tools/check-knip.mjs +60 -0
  243. package/tools/check-loc.mjs +54 -0
  244. package/tools/demo-worker/api-client.mjs +7 -13
  245. package/tools/demo-worker/generate.mjs +2 -4
  246. package/tools/eslint-baseline.json +23 -0
  247. package/tools/gt-render/browser.mjs +23 -0
  248. package/tools/headless-studio.mjs +85 -0
  249. package/tools/kimodo/effector-constraints.mjs +4 -4
  250. package/tools/kimodo/generate.mjs +1 -1
  251. package/tools/knip-baseline.json +331 -0
  252. package/tools/loc-baseline.json +126 -0
  253. package/tools/process-supervisor.mjs +1 -1
  254. package/tools/projflow/replay.mjs +4 -4
  255. package/tools/projflow/runner.mjs +3 -3
  256. package/tools/qa/studio-control-count.mjs +2 -0
  257. package/tools/run-tests.mjs +95 -7
  258. package/dist/assets/analytics-MVZOEW8O.js +0 -1
  259. package/dist/assets/app-BxBLC6GT.js +0 -4876
  260. package/dist/assets/app-CJdxvnrl.css +0 -1
  261. package/dist/assets/shot-prompt-hN0j_O-p.js +0 -17
  262. package/dist/assets/workflow-B5XZwVfe.js +0 -179
@@ -8,6 +8,9 @@ import {
8
8
  attachmentFromFile,
9
9
  transferCarriesFiles,
10
10
  } from "./attachment-image.js";
11
+ import { isImeComposing } from "../ime.js";
12
+ import { useStudioShell } from "../shell/studio-shell-context.js";
13
+ import { ko } from "../locale.js";
11
14
  import {
12
15
  AGENT_PANEL_OVERLAY_BREAKPOINT,
13
16
  AGENT_PANEL_RAIL_WIDTH,
@@ -34,9 +37,14 @@ import {
34
37
  formatElapsed,
35
38
  formatResetIn,
36
39
  readStoredPanelWidth,
40
+ readStoredSupervisor,
37
41
  storeModel,
38
42
  storePanelWidth,
43
+ storeSupervisor,
39
44
  STUDIO_SESSION_STORAGE_KEY,
45
+ selectedModelRoles,
46
+ shortModelId,
47
+ supervisorFollowUp,
40
48
  } from "./agent-client.js";
41
49
  import "./agent-panel.css";
42
50
 
@@ -122,6 +130,18 @@ function PausedCard({ resetAt, onRetry, onSwitchModel }) {
122
130
  </div>;
123
131
  }
124
132
 
133
+ // The lineup the sidecar derived for the selected model (#717), read-only:
134
+ // what a turn may lean on beside it. Vision is named only when it is not the
135
+ // selected model itself.
136
+ function ModelRolesLine({ model, roles }) {
137
+ if (!roles) return null;
138
+ const parts = [[ko("helper", "보조"), roles.helper], [ko("advisor", "자문"), roles.advisor]];
139
+ if (roles.vision !== model) parts.push([ko("vision", "비전"), roles.vision]);
140
+ return <p className="agent-model-roles" data-agent-model-roles="true">
141
+ {parts.map(([label, key]) => `${label} · ${shortModelId(key)}`).join(" · ")}
142
+ </p>;
143
+ }
144
+
125
145
  // A generation is a server-owned job. The card shows the state the runtime
126
146
  // reported, the phase it named and only a progress value it actually sent.
127
147
  function JobCard({ job, onStop, onAccept }) {
@@ -182,6 +202,47 @@ function ReceiptCard({ item }) {
182
202
  </div>;
183
203
  }
184
204
 
205
+ const SUPERVISOR_SEVERITY_COPY = { blocker: ko("blocker", "차단"), concern: ko("concern", "우려"), note: ko("note", "참고") };
206
+ const SUPERVISOR_STATUS_COPY = { done: ko("done", "완료"), partial: ko("partial", "일부"), refused: ko("refused", "거부됨"), missing: ko("missing", "누락") };
207
+
208
+ // The supervisor's note on a finished Studio turn (#728): muted, collapsed to
209
+ // its summary and counts, and never acted on by itself. Its one action puts
210
+ // the blockers and concerns into the composer as the author's next turn.
211
+ function SupervisorCard({ note, onSend, defaultOpen = false }) {
212
+ const [open, setOpen] = useState(defaultOpen);
213
+ if (note.verdict !== "reviewed") return <div className="agent-card agent-supervisor-card" data-supervisor-verdict="unavailable">
214
+ <p className="agent-supervisor-summary">{ko("Supervisor review unavailable", "감독 검토를 할 수 없었어요")}{note.reason ? ` — ${note.reason}` : ""}</p>
215
+ </div>;
216
+ const followUp = supervisorFollowUp(note);
217
+ // Validated run_action drafts (#743), shown under the issue each one fixes.
218
+ const fixes = Array.isArray(note.fixes) ? note.fixes : [];
219
+ return <div className="agent-card agent-supervisor-card" data-supervisor-verdict="reviewed" data-open={open ? "true" : "false"}>
220
+ <div className="agent-supervisor-head">
221
+ <span className="agent-job-label">{ko("Supervisor", "감독")}</span>
222
+ {["blocker", "concern", "note"].filter((severity) => note.counts?.[severity] > 0).map((severity) => <span key={severity} className={`agent-job-badge agent-supervisor-badge ${severity}`} data-severity={severity}>{note.counts[severity]} {SUPERVISOR_SEVERITY_COPY[severity]}</span>)}
223
+ <button type="button" className="agent-ghost-button agent-supervisor-toggle" aria-expanded={open} onClick={() => setOpen((value) => !value)}>{open ? ko("Less", "접기") : ko("Details", "자세히")}</button>
224
+ </div>
225
+ <p className="agent-supervisor-summary">{note.summary}</p>
226
+ {open && note.items?.length > 0 && <ul className="agent-receipt-notes agent-supervisor-items">
227
+ {note.items.map((item, index) => <li key={index} data-status={item.status}>{SUPERVISOR_STATUS_COPY[item.status] ?? item.status} · {item.text}{item.evidence ? ` — ${item.evidence}` : ""}</li>)}
228
+ </ul>}
229
+ {open && note.issues?.length > 0 && <ul className="agent-receipt-notes agent-supervisor-issues">
230
+ {note.issues.map((issue, index) => {
231
+ const fix = fixes.find((entry) => entry.issueIndex === index);
232
+ return <li key={index} data-severity={issue.severity}>{SUPERVISOR_SEVERITY_COPY[issue.severity] ?? issue.severity} · {issue.text}{issue.evidence ? ` — ${issue.evidence}` : ""}
233
+ {fix && <code className="agent-supervisor-fix" data-fix-action={fix.action}>{`run_action ${JSON.stringify({ action: fix.action, args: fix.args })}`}</code>}
234
+ </li>;
235
+ })}
236
+ </ul>}
237
+ {followUp && <div className="agent-job-actions">
238
+ <button type="button" className="agent-ghost-button agent-supervisor-send" onClick={() => onSend(followUp)}
239
+ title={note.autofix?.eligible ? ko("Autofix is on: the blocker fixes would be applied as written", "자동 수정 켜짐: 차단 수정안을 적힌 그대로 적용합니다") : undefined}>
240
+ {fixes.length ? ko("Apply fixes as next prompt", "수정안 다음 턴으로 보내기") : ko("Send as next prompt", "다음 턴으로 보내기")}
241
+ </button>
242
+ </div>}
243
+ </div>;
244
+ }
245
+
185
246
  const RECOVERY_COPY = {
186
247
  none: "No recovery is available for this command.",
187
248
  inspect: "Ask the agent to inspect the target before trying again.",
@@ -286,6 +347,11 @@ export default function AgentPanel({
286
347
  const transport = useMemo(() => injectedTransport || createAgentTransport({ surface }), [injectedTransport, surface]);
287
348
  const mockState = transport.mock ? transport.state : null;
288
349
 
350
+ // Inside the studio the title names the project's previs mode (#650).
351
+ const previsMode = useStudioShell()?.previsMode;
352
+ const titleMode = surface === "studio" && previsMode
353
+ ? (previsMode === "storyboard" ? ko("Storyboard", "스토리보드") : ko("Animation", "애니메이션"))
354
+ : null;
289
355
  const [collapsed, setCollapsed] = useState(defaultCollapsed);
290
356
  // Only the dock owns a width; an embedded host sizes the panel itself and
291
357
  // never reads the dock's stored key.
@@ -302,6 +368,8 @@ export default function AgentPanel({
302
368
  const [modelProviders, setModelProviders] = useState([]);
303
369
  const [model, setModel] = useState("");
304
370
  const [modelsState, setModelsState] = useState("loading");
371
+ // The roles derived per main model (#717); shown, never sent or stored.
372
+ const [modelRoles, setModelRoles] = useState(null);
305
373
  // null = the model's backend default; picking a model resets it.
306
374
  const [effort, setEffort] = useState(null);
307
375
  const efforts = useMemo(() => effortOptions(models.find((entry) => entry.id === model)), [models, model]);
@@ -309,6 +377,10 @@ export default function AgentPanel({
309
377
  // remembered so a reload does not quietly move the next turn to another
310
378
  // provider's model.
311
379
  const chooseModel = useCallback((id) => { setModel(id); setEffort(null); storeModel(id); }, []);
380
+ // The supervisor switch (#728): a preference like the model pick. Off skips
381
+ // the post-turn review for every turn sent from this panel.
382
+ const [supervisorOn, setSupervisorOn] = useState(() => readStoredSupervisor());
383
+ const toggleSupervisor = useCallback(() => setSupervisorOn((current) => { storeSupervisor(!current); return !current; }), []);
312
384
  // One place takes `{ providers, models }` from the sidecar: the grouped list
313
385
  // the dropdown draws, the flat key-addressed list every lookup uses, and the
314
386
  // model to open with. Returns false when nothing was advertised.
@@ -318,6 +390,7 @@ export default function AgentPanel({
318
390
  const providers = Array.isArray(advertised?.providers) ? advertised.providers : [];
319
391
  setModelProviders(providers);
320
392
  setModels(list);
393
+ setModelRoles(advertised?.roles?.byMain ?? null);
321
394
  // A saved or removed key arrives through this same path, so a selection the
322
395
  // panel opened with is dropped here the moment its provider cannot run it.
323
396
  setModel((current) => nextSelectedModel(providers, list, current));
@@ -345,6 +418,7 @@ export default function AgentPanel({
345
418
  const [restoreReady, setRestoreReady] = useState(surface !== "studio");
346
419
 
347
420
  const composerRef = useRef(null);
421
+ const focusPendingRef = useRef(false);
348
422
  const transcriptRef = useRef(null);
349
423
 
350
424
  // The host's context builder and image-action handler are read through refs
@@ -401,7 +475,6 @@ export default function AgentPanel({
401
475
  try { globalThis.localStorage?.setItem(STUDIO_SESSION_STORAGE_KEY, chat.sessionId); } catch { /* storage may be unavailable */ }
402
476
  }, [chat.sessionId, restoreReady, surface]);
403
477
 
404
- // --- activity line -----------------------------------------------------
405
478
  // One line that always states what the panel is doing, ticking while a turn
406
479
  // is live and holding the turn's outcome for a few seconds after it ends.
407
480
  const [activityNow, setActivityNow] = useState(() => Date.now());
@@ -416,7 +489,6 @@ export default function AgentPanel({
416
489
  return () => clearInterval(timer);
417
490
  }, [activity.ticking]);
418
491
 
419
- // --- session bootstrap -------------------------------------------------
420
492
  // Two ways in (#379): the ChatGPT sign-in, or a provider key the sidecar
421
493
  // already holds. `providersConfigured` counts the second, so a session that
422
494
  // never signed in to ChatGPT still gets a composer instead of a sign-in wall
@@ -520,7 +592,6 @@ export default function AgentPanel({
520
592
  store.send("Give me a wide two-shot of this scene", { attachFrame: false, model });
521
593
  }, [authState, items.length, mockState, model, store]);
522
594
 
523
- // --- layout ------------------------------------------------------------
524
595
  useEffect(() => {
525
596
  if (embedded) return;
526
597
  const onResize = () => setOverlay((globalThis.innerWidth || 1440) < AGENT_PANEL_OVERLAY_BREAKPOINT);
@@ -557,7 +628,10 @@ export default function AgentPanel({
557
628
  // Focus moves to the composer whenever the panel opens. The composer only
558
629
  // mounts once the session resolves, so authState is a dependency too:
559
630
  // otherwise this fires against a composer that does not exist yet. A hidden
560
- // embedded panel never pulls focus out of the host's own surface.
631
+ // embedded panel never pulls focus out of the host's own surface. An
632
+ // embedded panel never collapses itself (its host hides it), so shown means
633
+ // open there: without that the Studio composer opened unfocused and the
634
+ // next Cmd+V landed on the stage.
561
635
  useEffect(() => {
562
636
  if (hidden) {
563
637
  // Chrome blurs a hidden focused field only on its next lifecycle step;
@@ -565,8 +639,12 @@ export default function AgentPanel({
565
639
  if (composerRef.current && composerRef.current === document.activeElement) composerRef.current.blur();
566
640
  return;
567
641
  }
568
- if (!collapsed) composerRef.current?.focus();
569
- }, [authState, collapsed, hidden]);
642
+ if (!(embedded || !collapsed)) return;
643
+ composerRef.current?.focus();
644
+ // A composer still waiting on the model list is disabled and refuses
645
+ // focus; the open is remembered so it lands once the composer enables.
646
+ focusPendingRef.current = document.activeElement !== composerRef.current;
647
+ }, [authState, collapsed, embedded, hidden]);
570
648
 
571
649
  useEffect(() => {
572
650
  const node = transcriptRef.current;
@@ -597,12 +675,13 @@ export default function AgentPanel({
597
675
  });
598
676
  }, []);
599
677
 
600
- // --- pasted and dropped pictures ---------------------------------------
601
678
  // A screenshot is the fastest thing an author can show the agent, so the
602
679
  // composer takes one from the clipboard or the desktop. Only a transfer that
603
680
  // actually carries a picture is intercepted: a text paste still lands in the
604
681
  // caret, which is the whole reason the studio's document handler steps aside
605
- // for a textarea in the first place.
682
+ // for a textarea in the first place. The paste is taken on the composer
683
+ // block, not the textarea, so the Send and model buttons a click just left
684
+ // focus on do not hand the picture to the stage.
606
685
  const attachFiles = useCallback(async (files) => {
607
686
  const prepared = [];
608
687
  let failed = 0;
@@ -612,7 +691,6 @@ export default function AgentPanel({
612
691
  const rejected = prepared.length ? store.addAttachments(prepared).rejected : 0;
613
692
  setAttachNotice(rejected ? ATTACHMENT_LIMIT_NOTICE : failed ? ATTACHMENT_FAILED_NOTICE : null);
614
693
  }, [store]);
615
- /** Whether this transfer belongs to the composer. */
616
694
  const takeTransfer = useCallback((transfer) => {
617
695
  const { images, unsupported } = attachmentFilesFromTransfer(transfer);
618
696
  if (!images.length) {
@@ -643,12 +721,11 @@ export default function AgentPanel({
643
721
  if (takeTransfer(event.dataTransfer)) event.preventDefault();
644
722
  }, [takeTransfer]);
645
723
 
646
- // --- turn --------------------------------------------------------------
647
724
  const runTurn = useCallback((text) => {
648
725
  setAttachNotice(null);
649
726
  setSteerNotice("");
650
- return store.send(text, { attachFrame, model, effort: effort ?? undefined });
651
- }, [attachFrame, effort, model, store]);
727
+ return store.send(text, { attachFrame, model, effort: effort ?? undefined, supervisor: supervisorOn });
728
+ }, [attachFrame, effort, model, store, supervisorOn]);
652
729
  const stopTurn = useCallback(() => store.stop(), [store]);
653
730
  // Steering the turn that is already running: the composer's text joins THAT
654
731
  // turn instead of starting a second one. A refusal (the turn ended while the
@@ -717,7 +794,6 @@ export default function AgentPanel({
717
794
  }
718
795
  }, [store, transport]);
719
796
 
720
- // --- provider keys -----------------------------------------------------
721
797
  // The section is read when it opens; every write goes back through the
722
798
  // refresh above, because a key changes the session, not just a row.
723
799
  const toggleProviderKeys = useCallback(() => {
@@ -761,7 +837,7 @@ export default function AgentPanel({
761
837
  stopTurn();
762
838
  return;
763
839
  }
764
- if (event.key === "Enter" && !event.shiftKey) {
840
+ if (event.key === "Enter" && !isImeComposing(event) && !event.shiftKey) {
765
841
  event.preventDefault();
766
842
  // Enter does what the button under it says: Send, or Steer while a turn
767
843
  // on a steerable surface is still running.
@@ -784,6 +860,13 @@ export default function AgentPanel({
784
860
  // can send to: the composer stays shut instead of submitting a turn the
785
861
  // provider would refuse.
786
862
  const composerDisabled = panelState === "rate-limited" || !model || !modelIsSelectable(modelProviders, model);
863
+ useEffect(() => {
864
+ if (hidden) focusPendingRef.current = false;
865
+ if (hidden || composerDisabled || !focusPendingRef.current) return;
866
+ focusPendingRef.current = false;
867
+ // Only claim focus nobody else has taken in the meantime.
868
+ if (!document.activeElement || document.activeElement === document.body) composerRef.current?.focus();
869
+ }, [composerDisabled, hidden]);
787
870
 
788
871
  if (collapsed && !embedded) {
789
872
  return <aside className="agent-panel collapsed" data-agent-state={panelState} data-agent-collapsed="true" aria-label="Agent panel, collapsed">
@@ -821,7 +904,7 @@ export default function AgentPanel({
821
904
 
822
905
  <header className="agent-header">
823
906
  <StatusDot tone={statusTone} title={panelState} />
824
- <h2 className="agent-title">Agent</h2>
907
+ <h2 className="agent-title" data-previs-mode={titleMode ? previsMode : undefined}>{titleMode ? `Agent · ${titleMode}` : "Agent"}</h2>
825
908
  <span className="agent-header-spacer" />
826
909
  <button type="button" className="agent-icon-button agent-new" aria-label="New conversation" title="New conversation" onClick={newSession}><FiPlus size={14} aria-hidden="true" /></button>
827
910
  {presentation.history && <span className="agent-history-wrap">
@@ -906,6 +989,7 @@ export default function AgentPanel({
906
989
  if (item.kind === "job") return <div className="agent-row" key={item.id}><JobCard job={item} onStop={stopTurn} onAccept={(job) => store.acceptJob(job.jobId)} /></div>;
907
990
  if (item.kind === "receipt") return <div className="agent-row" key={item.id}><ReceiptCard item={item} /></div>;
908
991
  if (item.kind === "failure") return <div className="agent-row" key={item.id}><FailureCard failure={item.failure} onRetry={() => runTurn(chat.lastPrompt)} /></div>;
992
+ if (item.kind === "supervisor") return <div className="agent-row" key={item.id}><SupervisorCard note={item} onSend={(text) => { store.setDraft(text); runTurn(text); }} /></div>;
909
993
  return <div className="agent-row" key={item.id}><ImageResultCard image={item} onUse={(image) => store.applyImage(image.id)} onUndo={(image) => store.undoImage(image.id)} onRegenerate={() => runTurn(chat.lastPrompt)} onOpen={setLightbox} /></div>;
910
994
  })}
911
995
 
@@ -925,6 +1009,7 @@ export default function AgentPanel({
925
1009
  onDragOver={composerDisabled ? undefined : onComposerDragOver}
926
1010
  onDragLeave={composerDisabled ? undefined : onComposerDragLeave}
927
1011
  onDrop={composerDisabled ? undefined : onComposerDrop}
1012
+ onPaste={composerDisabled ? undefined : onComposerPaste}
928
1013
  >
929
1014
  {pendingAttachments.length > 0 && <ul className="agent-attachments" aria-label="Attached images">
930
1015
  {pendingAttachments.map((attachment) => <li key={attachment.id} className="agent-attachment">
@@ -933,8 +1018,6 @@ export default function AgentPanel({
933
1018
  </li>)}
934
1019
  </ul>}
935
1020
  {attachNotice && <p className="agent-attachment-notice" role="status">{attachNotice}</p>}
936
- {/* One field: the message, and under it on the same surface the
937
- frame toggle, the model and effort picks, and Send. */}
938
1021
  <div className="agent-field">
939
1022
  <textarea
940
1023
  ref={composerRef}
@@ -945,7 +1028,6 @@ export default function AgentPanel({
945
1028
  disabled={composerDisabled}
946
1029
  onChange={(event) => store.setDraft(event.target.value)}
947
1030
  onKeyDown={onComposerKeyDown}
948
- onPaste={onComposerPaste}
949
1031
  onDragOver={onComposerDragOver}
950
1032
  onDrop={onComposerDrop}
951
1033
  />
@@ -974,6 +1056,11 @@ export default function AgentPanel({
974
1056
  {efforts.map((value, index) => <option key={value} value={value} title={index === 0 ? "Default for this model" : undefined}>{value}</option>)}
975
1057
  </select>
976
1058
  )}
1059
+ {surface === "studio" && (
1060
+ <button type="button" className={`agent-supervisor-toggle${supervisorOn ? " is-on" : ""}`} role="switch" aria-checked={supervisorOn} aria-label={ko("Supervisor review", "감독 검토")} title={ko(supervisorOn ? "Supervisor review is on: a note arrives after each editing turn" : "Supervisor review is off", supervisorOn ? "감독 검토 켜짐: 편집 턴이 끝나면 메모가 옵니다" : "감독 검토 꺼짐")} onClick={toggleSupervisor}>
1061
+ {ko("Supervisor", "감독")} {supervisorOn ? "ON" : "OFF"}
1062
+ </button>
1063
+ )}
977
1064
  <span className="agent-composer-spacer" aria-hidden="true" />
978
1065
  {surface === "studio" && i2vActionRef.current && <button
979
1066
  type="button"
@@ -995,6 +1082,7 @@ export default function AgentPanel({
995
1082
  </>
996
1083
  : <button type="button" className="agent-send" aria-label="Send" title="Send (Enter)" disabled={composerDisabled || !draft.trim()} onClick={() => runTurn(draft)}><FiArrowUp size={14} aria-hidden="true" /></button>}
997
1084
  </div>
1085
+ <ModelRolesLine model={model} roles={selectedModelRoles(modelRoles, model)} />
998
1086
  </div>
999
1087
  </div>}
1000
1088
  {/* The hint exists to warn about image spend; a surface that cannot
@@ -14,7 +14,7 @@ import { Toaster, toast } from "react-hot-toast";
14
14
  import { loadWorkflowGraph, normalizeWorkflowGraph, storeWorkflowGraph, WORKFLOW_STORAGE_KEY } from "../project.js";
15
15
  import AgentPanel from "./AgentPanel.jsx";
16
16
  import CozySceneNode from "./CozySceneNode.jsx";
17
- import { applyCozyScenePatch, normalizeCozySceneData, sceneConnectionAllowed, toCozySceneRunRequest } from "./cozy-scene-node.js";
17
+ import { applyCozyScenePatch, normalizeCozySceneData, sceneConnectionAllowed } from "./cozy-scene-node.js";
18
18
  import { activeSceneCharacters, characterHandleId, characterIdFromHandle, normalizeMotionInputData, motionInputOutput } from "./motion-input.js";
19
19
  import { DEFAULT_NODE_SCHEMAS, defaultFormValues, schemaCategoryForType, schemaModelEntries, schemaProperties } from "./node-schema.js";
20
20
  import { executeLocalWorkflowGraph } from "./local-workflow.js";