cozyclay 2.1.0 → 3.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.
Files changed (583) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +40 -113
  4. package/bin/agent/agent-runner.mjs +9 -10
  5. package/bin/agent/cliproxy-provider.mjs +135 -0
  6. package/bin/agent/credential-store.mjs +30 -7
  7. package/bin/agent/pi-tools.mjs +4 -1
  8. package/bin/agent/provider-keys.mjs +32 -0
  9. package/bin/agent/provider-login.mjs +110 -0
  10. package/bin/agent/provider-routes.mjs +99 -0
  11. package/bin/agent/providers.mjs +19 -97
  12. package/bin/agent/script-tool.mjs +17 -4
  13. package/bin/agent/studio-motion-reference.mjs +23 -0
  14. package/bin/agent/studio-prompt.mjs +1 -1
  15. package/bin/agent/studio-rebase-text.mjs +32 -0
  16. package/bin/agent/studio-rebase.mjs +26 -39
  17. package/bin/agent/studio-tools.mjs +9 -28
  18. package/bin/agent/tool-arguments.mjs +49 -0
  19. package/bin/agent/turn-frames.mjs +66 -0
  20. package/bin/codex-auth.mjs +9 -2
  21. package/bin/cozyclay.mjs +10 -116
  22. package/bin/live/cli.mjs +22 -6
  23. package/bin/mcp-runtime.mjs +2 -12
  24. package/bin/package-signature.mjs +0 -4
  25. package/dist/app/index.html +7 -6
  26. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  27. package/dist/assets/analytics-C9sUnndd.js +1 -0
  28. package/dist/assets/app-CCh--1Hc.js +5087 -0
  29. package/dist/assets/app-f-fkV5uP.css +1 -0
  30. package/dist/assets/compile-worker-CkxiD6lH.js +5 -0
  31. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  32. package/dist/assets/{first-shot-handoff-BlT33CGf.js → first-shot-handoff-CC4xe4HM.js} +1 -1
  33. package/dist/assets/{landing-BSesrCpL.js → landing-DjHD8u7k.js} +1 -1
  34. package/dist/assets/scene-asset-sync-Hx_FzdOH.js +20 -0
  35. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  36. package/dist/assets/workflow-C4yKH1p4.js +179 -0
  37. package/dist/cozyclay-package.json +1 -1
  38. package/dist/index.html +3 -3
  39. package/dist/llms.txt +14 -0
  40. package/dist/sw.js +3 -6
  41. package/dist/workflow/index.html +5 -4
  42. package/mcp/LIVE-PROTOCOL.md +7 -47
  43. package/mcp/README.md +16 -26
  44. package/mcp/live-hub.mjs +6 -10
  45. package/mcp/package.json +1 -3
  46. package/mcp/tool-handlers.mjs +4 -168
  47. package/package.json +3 -22
  48. package/src/App.jsx +174 -2639
  49. package/src/agent-progress.js +79 -0
  50. package/src/agent-turn-mark.js +30 -0
  51. package/src/analytics.js +1 -19
  52. package/src/app-context.js +5 -0
  53. package/src/app-stage.jsx +2 -34
  54. package/src/asset-pane.jsx +6 -5
  55. package/src/camera-block.js +21 -0
  56. package/src/camera-direction.js +120 -0
  57. package/src/character-kind.js +3 -9
  58. package/src/command-bus.js +8 -6
  59. package/src/commands/cast.js +5 -34
  60. package/src/commands/elements/character.js +1 -1
  61. package/src/commands/elements/motion.js +1 -6
  62. package/src/commands/elements/object.js +6 -2
  63. package/src/commands/elements/shot.js +4 -1
  64. package/src/commands/elements.js +38 -14
  65. package/src/commands/index.js +3 -1
  66. package/src/commands/kits.js +37 -0
  67. package/src/commands/motion-timing.js +57 -0
  68. package/src/commands/motion.js +9 -152
  69. package/src/commands/motion3-compile.js +65 -0
  70. package/src/commands/motion3.js +207 -0
  71. package/src/commands/objects.js +6 -4
  72. package/src/commands/shot.js +49 -11
  73. package/src/commands/stage.js +14 -1
  74. package/src/commands/view.js +4 -1
  75. package/src/document-mirrors.js +42 -0
  76. package/src/document-store.js +4 -0
  77. package/src/domains/cast.js +19 -194
  78. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  79. package/src/domains/motion/take-source.js +51 -0
  80. package/src/domains/motion.js +60 -1838
  81. package/src/domains/scenes.js +12 -55
  82. package/src/domains/shots.js +2 -2
  83. package/src/domains/stage.js +4 -2
  84. package/src/draft-ghost.js +46 -0
  85. package/src/draft-ghost.jsx +129 -0
  86. package/src/dualview.jsx +4 -3
  87. package/src/effects/EffectsLayer.jsx +112 -0
  88. package/src/effects/effect-meshes.js +232 -0
  89. package/src/effects/effect-record.js +103 -0
  90. package/src/effects/emitter.js +59 -0
  91. package/src/effects/simulate.js +261 -0
  92. package/src/effects/state.js +59 -0
  93. package/src/execution-telemetry.js +2 -3
  94. package/src/first-success-guide.jsx +5 -5
  95. package/src/ghost-set.js +257 -0
  96. package/src/held-object-edit.jsx +113 -0
  97. package/src/held-object-pose.js +175 -0
  98. package/src/hierarchy-model.js +1 -1
  99. package/src/hierarchy-panel.jsx +1 -1
  100. package/src/imported-mesh.jsx +103 -0
  101. package/src/kits/held.js +84 -0
  102. package/src/kits/index.js +70 -0
  103. package/src/kits/smoking.js +57 -0
  104. package/src/light-blockers.js +93 -0
  105. package/src/motion/beat-board-model.js +84 -0
  106. package/src/motion/beat-board.css +124 -0
  107. package/src/motion/beat-board.jsx +59 -0
  108. package/src/motion/center-of-mass.js +66 -0
  109. package/src/motion/fix-collisions.js +850 -0
  110. package/src/motion/physics-review.js +118 -0
  111. package/src/{ardy → motion}/physics-support.js +1 -76
  112. package/src/motion/rig-snapshot.js +22 -0
  113. package/src/motion/take-segments.jsx +140 -0
  114. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  115. package/src/{ardy → motion}/timeline.css +1 -2
  116. package/src/{ardy → motion}/timeline.jsx +48 -419
  117. package/src/motion/waypoints.js +76 -0
  118. package/src/motion3/beats.js +83 -0
  119. package/src/motion3/body.js +158 -0
  120. package/src/motion3/bones.js +47 -0
  121. package/src/motion3/carried.js +47 -0
  122. package/src/motion3/compile-job.js +75 -0
  123. package/src/motion3/compile-worker.js +10 -0
  124. package/src/motion3/compiler/anchors.js +105 -0
  125. package/src/motion3/compiler/arm-orient.js +128 -0
  126. package/src/motion3/compiler/channels.js +125 -0
  127. package/src/motion3/compiler/compile.js +205 -0
  128. package/src/motion3/compiler/gait-rhythm.js +97 -0
  129. package/src/motion3/compiler/grip.js +171 -0
  130. package/src/motion3/compiler/head-steady.js +48 -0
  131. package/src/motion3/compiler/hold.js +134 -0
  132. package/src/motion3/compiler/idle.js +113 -0
  133. package/src/motion3/compiler/limbs.js +170 -0
  134. package/src/motion3/compiler/math.js +72 -0
  135. package/src/motion3/compiler/pose.js +162 -0
  136. package/src/motion3/compiler/quat.js +49 -0
  137. package/src/motion3/compiler/skeleton.js +74 -0
  138. package/src/motion3/compiler/validate-common.js +32 -0
  139. package/src/motion3/compiler/validate-goals.js +118 -0
  140. package/src/motion3/compiler/validate-objects.js +143 -0
  141. package/src/motion3/compiler/validate-parts.js +116 -0
  142. package/src/motion3/compiler/validate.js +122 -0
  143. package/src/motion3/compiler/vocab.js +73 -0
  144. package/src/motion3/compiler/walk-pelvis.js +275 -0
  145. package/src/motion3/compiler/walk.js +207 -0
  146. package/src/motion3/compiler/wrist-clamp.js +32 -0
  147. package/src/motion3/delta.js +47 -0
  148. package/src/motion3/describe.js +174 -0
  149. package/src/motion3/docs.js +27 -0
  150. package/src/motion3/edit-carry.js +21 -0
  151. package/src/motion3/fix/ease.js +16 -0
  152. package/src/motion3/fix/ground.js +37 -0
  153. package/src/motion3/fix/index.js +52 -0
  154. package/src/motion3/fix/push.js +71 -0
  155. package/src/motion3/fix/reach.js +46 -0
  156. package/src/motion3/fix/retime.js +79 -0
  157. package/src/motion3/fix/self.js +211 -0
  158. package/src/motion3/fix/slide.js +70 -0
  159. package/src/motion3/fix/turn.js +51 -0
  160. package/src/motion3/follow.js +39 -0
  161. package/src/motion3/free-motion.js +173 -0
  162. package/src/motion3/grip-choice.js +130 -0
  163. package/src/motion3/hold-plan.js +157 -0
  164. package/src/motion3/json-patch.js +77 -0
  165. package/src/motion3/key-aim.js +105 -0
  166. package/src/motion3/key-edit.js +82 -0
  167. package/src/motion3/lenient.js +127 -0
  168. package/src/motion3/object-keys.js +236 -0
  169. package/src/motion3/object-pose.js +182 -0
  170. package/src/motion3/object-tracks.js +12 -0
  171. package/src/motion3/pipeline.js +102 -0
  172. package/src/motion3/playback.js +51 -0
  173. package/src/motion3/probes.js +91 -0
  174. package/src/motion3/qa/body-checks.js +34 -0
  175. package/src/motion3/qa/check-names.js +14 -0
  176. package/src/motion3/qa/checks.js +121 -0
  177. package/src/motion3/qa/contact-checks.js +51 -0
  178. package/src/motion3/qa/explain.js +80 -0
  179. package/src/motion3/qa/fmt.js +5 -0
  180. package/src/motion3/qa/foot-checks.js +72 -0
  181. package/src/motion3/qa/geometry.js +77 -0
  182. package/src/motion3/qa/goals.js +191 -0
  183. package/src/motion3/qa/ground.js +60 -0
  184. package/src/motion3/qa/hints-contact.js +179 -0
  185. package/src/motion3/qa/hints-goal.js +160 -0
  186. package/src/motion3/qa/hints-knee.js +24 -0
  187. package/src/motion3/qa/hints-motion.js +209 -0
  188. package/src/motion3/qa/hints-object.js +91 -0
  189. package/src/motion3/qa/index.js +42 -0
  190. package/src/motion3/qa/key-checks.js +82 -0
  191. package/src/motion3/qa/near.js +96 -0
  192. package/src/motion3/qa/object-checks.js +135 -0
  193. package/src/motion3/qa/patch.js +161 -0
  194. package/src/motion3/qa/severity.js +38 -0
  195. package/src/motion3/qa/spec-checks.js +65 -0
  196. package/src/motion3/qa/style.js +218 -0
  197. package/src/motion3/qa/wrist-checks.js +69 -0
  198. package/src/motion3/qa/wrist-obs.js +60 -0
  199. package/src/motion3/refs.js +120 -0
  200. package/src/motion3/repair-tune.js +121 -0
  201. package/src/motion3/repair.js +83 -0
  202. package/src/motion3/report.js +67 -0
  203. package/src/motion3/rigs/index.js +14 -0
  204. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  205. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  206. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  207. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  208. package/src/motion3/scene.js +60 -0
  209. package/src/motion3/schema/brief.js +89 -0
  210. package/src/motion3/schema/fields.js +44 -0
  211. package/src/motion3/schema/spec-examples.js +46 -0
  212. package/src/motion3/schema/topics.js +111 -0
  213. package/src/motion3/schema/types.js +147 -0
  214. package/src/motion3/search.js +117 -0
  215. package/src/motion3/second-hand.js +112 -0
  216. package/src/motion3/skills/basic.js +82 -0
  217. package/src/motion3/skills/book.js +76 -0
  218. package/src/motion3/skills/car.js +145 -0
  219. package/src/motion3/skills/frames-auto.js +29 -0
  220. package/src/motion3/skills/index.js +83 -0
  221. package/src/motion3/skills/info.js +65 -0
  222. package/src/motion3/skills/smoke-kit.js +109 -0
  223. package/src/motion3/skills/smoke.js +160 -0
  224. package/src/motion3/spec-store.js +59 -0
  225. package/src/motion3/take-edit.js +155 -0
  226. package/src/motion3/take.js +63 -0
  227. package/src/motion3/timeline.js +127 -0
  228. package/src/motion3/views.js +121 -0
  229. package/src/motion3/walk-plan.js +119 -0
  230. package/src/object-catalog.jsx +7 -1
  231. package/src/object-colors.js +98 -0
  232. package/src/object-gizmo-parts.js +115 -0
  233. package/src/object-gizmo.jsx +37 -122
  234. package/src/object-glow.js +139 -0
  235. package/src/object-hidden.js +33 -0
  236. package/src/object-look.js +196 -0
  237. package/src/object-size.js +10 -0
  238. package/src/object-travel.js +71 -6
  239. package/src/panels/CameraPanel.jsx +7 -7
  240. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  241. package/src/panels/LightPanel.jsx +25 -4
  242. package/src/panels/ObjectLookPanel.jsx +88 -0
  243. package/src/panels/PoseImagePanel.jsx +1 -1
  244. package/src/panels/PosePanel.jsx +2 -17
  245. package/src/panels/RigControlPanel.jsx +5 -53
  246. package/src/panels/SubjectBox.jsx +4 -1
  247. package/src/panels/details.css +14 -0
  248. package/src/posestudio.jsx +4 -27
  249. package/src/project-browser.jsx +3 -4
  250. package/src/project-resources.js +18 -31
  251. package/src/project.js +13 -104
  252. package/src/props.jsx +25 -280
  253. package/src/rail-dot-drag.js +23 -0
  254. package/src/render-passes.js +10 -0
  255. package/src/resource-status.jsx +1 -0
  256. package/src/result-modal.jsx +4 -19
  257. package/src/room.jsx +20 -12
  258. package/src/scene-assets.js +8 -6
  259. package/src/scene-light-place.js +72 -0
  260. package/src/scene-lights.jsx +148 -0
  261. package/src/scene-objects.js +31 -139
  262. package/src/scenes.js +15 -105
  263. package/src/semantic-edit.js +2 -5
  264. package/src/set-pieces.jsx +183 -0
  265. package/src/settings-menu.jsx +3 -11
  266. package/src/shell/BottomDock.jsx +11 -53
  267. package/src/shell/CompositionGuideButton.jsx +20 -0
  268. package/src/shell/DetailsSlot.jsx +16 -200
  269. package/src/shell/GhostsMenuGroup.jsx +74 -0
  270. package/src/shell/LibrarySlot.jsx +1 -3
  271. package/src/shell/MenuBar.jsx +3 -3
  272. package/src/shell/PreferencesDialog.jsx +15 -264
  273. package/src/shell/PreferencesSlot.jsx +2 -8
  274. package/src/shell/ShotPreviewHeader.jsx +17 -0
  275. package/src/shell/TopBar.jsx +5 -83
  276. package/src/shell/ViewportToolbar.jsx +23 -93
  277. package/src/shell/agent-glass.css +19 -6
  278. package/src/shell/details-selection.js +42 -0
  279. package/src/shell/ghosts-menu.css +22 -0
  280. package/src/shell/glass-regions.css +29 -3
  281. package/src/shell/preferences.css +6 -1
  282. package/src/shell/viewport.css +12 -0
  283. package/src/shot-authoring.js +4 -7
  284. package/src/soft-shadows.js +37 -0
  285. package/src/stage-attach-frames.js +12 -12
  286. package/src/stage-canvas-rigs.jsx +3 -3
  287. package/src/stage-capture.jsx +42 -8
  288. package/src/stage-characters.jsx +22 -216
  289. package/src/stage-editor-gizmos.jsx +6 -6
  290. package/src/stage-environment.js +93 -0
  291. package/src/stage-hdr-builtins.js +107 -0
  292. package/src/stage-hdr-image.js +116 -0
  293. package/src/stage-hdr-library.js +70 -0
  294. package/src/stage-hdr-map.js +94 -0
  295. package/src/stage-hdr.js +130 -0
  296. package/src/stage-lighting.js +149 -0
  297. package/src/stage-occlusion.js +210 -0
  298. package/src/stage-sky.jsx +159 -0
  299. package/src/stage-startup-defaults.js +5 -9
  300. package/src/studio-actions.js +12 -14
  301. package/src/studio-agent-commands.js +46 -16
  302. package/src/studio-agent-motion.js +9 -9
  303. package/src/studio-agent-protocol.js +46 -73
  304. package/src/studio-app-binding.js +29 -45
  305. package/src/studio-asset-list.js +17 -0
  306. package/src/studio-contact-sheet.js +37 -5
  307. package/src/studio-elements.js +27 -7
  308. package/src/studio-entity-bounds.js +18 -0
  309. package/src/studio-error-codes.js +8 -0
  310. package/src/studio-export-framing.js +152 -0
  311. package/src/studio-lighting-check.js +103 -0
  312. package/src/studio-motion-visual.js +150 -0
  313. package/src/studio-schema-errors.js +80 -0
  314. package/src/studio-support-gap.js +74 -0
  315. package/src/styles.css +1 -19
  316. package/src/timeline-defaults.js +5 -0
  317. package/src/timeline-extent.js +8 -20
  318. package/src/tutorial-walk.js +17 -0
  319. package/src/ui-scrub.js +1 -1
  320. package/src/use-shot-camera-tracks.js +12 -0
  321. package/src/view-ghosts.js +97 -0
  322. package/src/view-ghosts.jsx +49 -0
  323. package/src/workflow/AgentPanel.jsx +47 -166
  324. package/src/workflow/AgentProgressCard.jsx +107 -0
  325. package/src/workflow/AgentReplyText.jsx +37 -0
  326. package/src/workflow/AgentTurnSteps.jsx +84 -0
  327. package/src/workflow/LoginPanel.jsx +140 -0
  328. package/src/workflow/WorkflowBuilder.jsx +7 -23
  329. package/src/workflow/agent-client.js +54 -51
  330. package/src/workflow/agent-panel.css +71 -1
  331. package/src/workflow/agent-progress-card.css +167 -0
  332. package/src/workflow/agent-progress-view.js +47 -0
  333. package/src/workflow/agent-turn-steps.css +113 -0
  334. package/src/workflow/agent-turn.js +191 -0
  335. package/src/workflow/login-client.js +34 -0
  336. package/src/workflow/motion-input.js +18 -31
  337. package/src/workflow/node-schema.js +1 -1
  338. package/src/workflow/reply-markdown.js +69 -0
  339. package/src/workflow/scene-asset-sync.js +8 -8
  340. package/tools/agent-session-stats.mjs +61 -0
  341. package/tools/dev-full.mjs +5 -56
  342. package/tools/eslint-baseline.json +12 -13
  343. package/tools/headless-studio.mjs +85 -0
  344. package/tools/knip-baseline.json +0 -179
  345. package/tools/loc-baseline.json +33 -83
  346. package/tools/motion3/extract-body.mjs +103 -0
  347. package/tools/motion3/extract-rig.mjs +85 -0
  348. package/tools/process-supervisor.mjs +0 -88
  349. package/tools/run-tests.mjs +9 -124
  350. package/bin/agent/motion-runtime.mjs +0 -76
  351. package/dist/ardy/cskel27-rest.json +0 -557
  352. package/dist/assets/analytics-B41FmPYI.js +0 -1
  353. package/dist/assets/app-Do-Og5Pu.css +0 -1
  354. package/dist/assets/app-DwP-tl2x.js +0 -4920
  355. package/dist/assets/config-Bx7pYsD9.js +0 -1
  356. package/dist/assets/config-DeoFTYzb.css +0 -1
  357. package/dist/assets/demo-Dt0qZfS7.js +0 -1
  358. package/dist/assets/scene-asset-sync-BCWDJB1d.js +0 -19
  359. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  360. package/dist/assets/ticket-jqMhqL88.js +0 -1
  361. package/dist/assets/workflow-Br-K7Srs.js +0 -179
  362. package/dist/demo/walk-then-stop.npz +0 -0
  363. package/mcp/ardy-prompts.mjs +0 -218
  364. package/src/ardy/auto-fix-panel.css +0 -108
  365. package/src/ardy/auto-physics.js +0 -1194
  366. package/src/ardy/client.js +0 -153
  367. package/src/ardy/convert.js +0 -313
  368. package/src/ardy/cskel27-neutral.js +0 -39
  369. package/src/ardy/cskel27.js +0 -77
  370. package/src/ardy/export.js +0 -165
  371. package/src/ardy/fix-collisions.js +0 -2257
  372. package/src/ardy/foot-lock.js +0 -230
  373. package/src/ardy/motion-calibration.js +0 -118
  374. package/src/ardy/motion-edit.js +0 -213
  375. package/src/ardy/motion-url.js +0 -55
  376. package/src/ardy/mp4-duration.js +0 -102
  377. package/src/ardy/npz.js +0 -595
  378. package/src/ardy/physics-panel.css +0 -21
  379. package/src/ardy/physics-panel.jsx +0 -89
  380. package/src/ardy/physics-refine.js +0 -111
  381. package/src/ardy/physics-review.js +0 -567
  382. package/src/ardy/physics-temporal.js +0 -54
  383. package/src/ardy/platform-fit-panel.css +0 -64
  384. package/src/ardy/platform-fit-panel.jsx +0 -41
  385. package/src/ardy/platform-fit.js +0 -326
  386. package/src/ardy/playback.js +0 -711
  387. package/src/ardy/pose-pin.js +0 -97
  388. package/src/ardy/prompt-clips.js +0 -30
  389. package/src/ardy/retime.js +0 -211
  390. package/src/ardy/root-drop.js +0 -369
  391. package/src/ardy/timeline-resize.js +0 -13
  392. package/src/ardy/to-cskel27.js +0 -225
  393. package/src/ardy/trim.js +0 -33
  394. package/src/ardy/waypoints.js +0 -369
  395. package/src/ardy-bridge-contract.js +0 -281
  396. package/src/domains/motion/motion-video-capture.js +0 -664
  397. package/src/i2v-motion-client.js +0 -77
  398. package/src/i2v-motion-studio.jsx +0 -180
  399. package/src/line-edit.js +0 -1348
  400. package/src/motion/generation.js +0 -105
  401. package/src/motion-readiness-ui.jsx +0 -67
  402. package/src/motion-readiness.js +0 -40
  403. package/src/motion-resources.js +0 -177
  404. package/src/motion-store.js +0 -65
  405. package/src/motion-trail.js +0 -601
  406. package/src/multimodel-ingest.js +0 -394
  407. package/src/panels/PromptBlocksPanel.jsx +0 -444
  408. package/src/panels/VideoCapturePanel.jsx +0 -181
  409. package/src/pose-extract/browser.js +0 -42
  410. package/src/pose-extract/detector.js +0 -100
  411. package/src/pose-extract/fit.js +0 -227
  412. package/src/pose-extract/image-frame.js +0 -51
  413. package/src/pose-extract/index.js +0 -58
  414. package/src/pose-extract/keyframes.js +0 -74
  415. package/src/pose-extract/landmarks.js +0 -162
  416. package/src/pose-extract/mirror.js +0 -153
  417. package/src/pose-extract/take.js +0 -131
  418. package/src/pose-extract/video-frames.js +0 -91
  419. package/src/take-recipe.js +0 -165
  420. package/src/trail-key-conflicts.js +0 -39
  421. package/src/trail-pick.js +0 -59
  422. package/src/use-i2v-capture-modal.js +0 -15
  423. package/tools/ardy/BRIDGE.md +0 -37
  424. package/tools/ardy/GVHMR-NPZ.md +0 -68
  425. package/tools/ardy/README.md +0 -124
  426. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  427. package/tools/ardy/artifacts.mjs +0 -34
  428. package/tools/ardy/bridge.mjs +0 -1732
  429. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  430. package/tools/ardy/cclay_constrained_generate.py +0 -561
  431. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  432. package/tools/ardy/cclay_motion_edit.py +0 -510
  433. package/tools/ardy/cclay_sequence_generate.py +0 -705
  434. package/tools/ardy/cclay_worker.py +0 -304
  435. package/tools/ardy/dump-npz.py +0 -205
  436. package/tools/ardy/extract-rest.mjs +0 -299
  437. package/tools/ardy/extract.mjs +0 -507
  438. package/tools/ardy/footage.mjs +0 -467
  439. package/tools/ardy/gvhmr-floor.mjs +0 -102
  440. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  441. package/tools/ardy/gvhmr_fastpath.py +0 -168
  442. package/tools/ardy/gvhmr_trajectory.py +0 -240
  443. package/tools/ardy/merge-text-encoder.py +0 -160
  444. package/tools/ardy/mocap-metrics.mjs +0 -200
  445. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  446. package/tools/ardy/motion-stabilize.mjs +0 -348
  447. package/tools/ardy/npz.mjs +0 -414
  448. package/tools/ardy/pose-to-npz.mjs +0 -106
  449. package/tools/ardy/prompt-limits.mjs +0 -5
  450. package/tools/ardy/run-edit-on-box.sh +0 -73
  451. package/tools/ardy/run-local.mjs +0 -233
  452. package/tools/ardy/run-on-box.sh +0 -593
  453. package/tools/ardy/run-sequence-on-box.sh +0 -177
  454. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  455. package/tools/ardy/runners/index.mjs +0 -18
  456. package/tools/ardy/runners/local.mjs +0 -455
  457. package/tools/ardy/runners/proc.mjs +0 -125
  458. package/tools/ardy/runners/remote.mjs +0 -252
  459. package/tools/ardy/setup-local.mjs +0 -189
  460. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  461. package/tools/ardy/setup-text-encoder.py +0 -306
  462. package/tools/ardy/smpl-cskel27.mjs +0 -178
  463. package/tools/ardy/visual-qa.mjs +0 -196
  464. package/tools/ardy/vq-car.mjs +0 -52
  465. package/tools/bench/EXP3.md +0 -61
  466. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  467. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  468. package/tools/bench/cclay_bench_runner.py +0 -42
  469. package/tools/bench/cube-contact.mjs +0 -162
  470. package/tools/bench/exp3.mjs +0 -152
  471. package/tools/bench/extract-bench-lib.mjs +0 -216
  472. package/tools/bench/extract-bench.mjs +0 -302
  473. package/tools/bench/fal-generate.mjs +0 -88
  474. package/tools/bench/fit/README.md +0 -189
  475. package/tools/bench/fit/camera.mjs +0 -59
  476. package/tools/bench/fit/contact.mjs +0 -419
  477. package/tools/bench/fit/footlock.mjs +0 -234
  478. package/tools/bench/fit/motion.mjs +0 -85
  479. package/tools/bench/fit/pin.mjs +0 -27
  480. package/tools/bench/fit/remote.mjs +0 -105
  481. package/tools/bench/fit-bench.mjs +0 -126
  482. package/tools/bench/fit-sanity.mjs +0 -45
  483. package/tools/bench/metrics.mjs +0 -313
  484. package/tools/bench/obs/depth.mjs +0 -258
  485. package/tools/bench/obs/extrinsics.mjs +0 -175
  486. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  487. package/tools/bench/obs/ground.mjs +0 -294
  488. package/tools/bench/obs/heading.mjs +0 -151
  489. package/tools/bench/obs/ladder.mjs +0 -555
  490. package/tools/bench/obs/mannequin-betas.json +0 -124
  491. package/tools/bench/obs/remote.mjs +0 -83
  492. package/tools/bench/obs/rest_joints.py +0 -52
  493. package/tools/bench/obs/ybot-targets.mjs +0 -49
  494. package/tools/bench/obs-bench.mjs +0 -520
  495. package/tools/bench/score.mjs +0 -343
  496. package/tools/bench/summarize.mjs +0 -86
  497. package/tools/demo-worker/README.md +0 -137
  498. package/tools/demo-worker/api-client.mjs +0 -233
  499. package/tools/demo-worker/generate.mjs +0 -301
  500. package/tools/demo-worker/index.mjs +0 -466
  501. package/tools/demo-worker/sign-core.mjs +0 -112
  502. package/tools/demo-worker/sign.mjs +0 -161
  503. package/tools/gt-render/browser.mjs +0 -159
  504. package/tools/gt-render/camera-math.mjs +0 -276
  505. package/tools/gt-render/page.mjs +0 -267
  506. package/tools/gt-render/render.mjs +0 -514
  507. package/tools/gt-render/scene-box.mjs +0 -31
  508. package/tools/gt-render/take-transform.mjs +0 -87
  509. package/tools/kimodo/compare-seam.mjs +0 -108
  510. package/tools/kimodo/constraints.mjs +0 -244
  511. package/tools/kimodo/edit.mjs +0 -259
  512. package/tools/kimodo/effector-constraints.mjs +0 -200
  513. package/tools/kimodo/generate.mjs +0 -646
  514. package/tools/kimodo/local-output.mjs +0 -265
  515. package/tools/kimodo/measure-pose.mjs +0 -65
  516. package/tools/kimodo/measure-preserve.mjs +0 -899
  517. package/tools/kimodo/measure-waypoints.mjs +0 -55
  518. package/tools/kimodo/mlx-generate.py +0 -111
  519. package/tools/kimodo/pose-constraints.mjs +0 -248
  520. package/tools/kimodo/preserve-mask.mjs +0 -543
  521. package/tools/kimodo/read-npz.mjs +0 -189
  522. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  523. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  524. package/tools/kimodo/runner.mjs +0 -284
  525. package/tools/kimodo/seam-batch.mjs +0 -77
  526. package/tools/kimodo/setup-local.mjs +0 -30
  527. package/tools/kimodo/setup-on-box.sh +0 -339
  528. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  529. package/tools/morphgs/assets/gen_truth.py +0 -39
  530. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  531. package/tools/morphgs/assets/playback-check.mjs +0 -30
  532. package/tools/morphgs/demo-gate.sh +0 -26
  533. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  534. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  535. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  536. package/tools/morphgs/setup-on-cluster.sh +0 -126
  537. package/tools/projflow/driver.py +0 -1069
  538. package/tools/projflow/generate.mjs +0 -805
  539. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  540. package/tools/projflow/line-edit-job.mjs +0 -547
  541. package/tools/projflow/replay.mjs +0 -452
  542. package/tools/projflow/runner.mjs +0 -269
  543. package/tools/projflow/service.mjs +0 -742
  544. package/tools/track/DESIGN.md +0 -513
  545. package/tools/track/backfill-provenance.mjs +0 -116
  546. package/tools/track/budget.mjs +0 -8
  547. package/tools/track/check-rig.mjs +0 -209
  548. package/tools/track/diagnostics.schema.json +0 -60
  549. package/tools/track/export-rig.mjs +0 -256
  550. package/tools/track/fallback.mjs +0 -7
  551. package/tools/track/fk-parity-fixture.mjs +0 -161
  552. package/tools/track/gate.mjs +0 -349
  553. package/tools/track/masks.mjs +0 -318
  554. package/tools/track/metrics.mjs +0 -201
  555. package/tools/track/publish-obs.mjs +0 -108
  556. package/tools/track/py/check_env.py +0 -61
  557. package/tools/track/py/eval_lr.py +0 -229
  558. package/tools/track/py/lr_viterbi.py +0 -294
  559. package/tools/track/py/masks.py +0 -405
  560. package/tools/track/py/objective.py +0 -382
  561. package/tools/track/py/rig.py +0 -508
  562. package/tools/track/py/scene.py +0 -326
  563. package/tools/track/py/test_joint_indices.py +0 -123
  564. package/tools/track/py/test_lr_viterbi.py +0 -195
  565. package/tools/track/py/test_masks.py +0 -229
  566. package/tools/track/py/test_rig.py +0 -288
  567. package/tools/track/py/test_scene.py +0 -354
  568. package/tools/track/py/test_track.py +0 -328
  569. package/tools/track/py/track.py +0 -411
  570. package/tools/track/remote.mjs +0 -140
  571. package/tools/track/rig-dump.mjs +0 -227
  572. package/tools/track/run-box-tests.mjs +0 -14
  573. package/tools/track/run-box.mjs +0 -168
  574. package/tools/track/setup-box.sh +0 -59
  575. package/tools/track/study-2d.mjs +0 -728
  576. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  577. /package/src/{ardy → motion}/ground.js +0 -0
  578. /package/src/{ardy → motion}/ik-drag.js +0 -0
  579. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  580. /package/src/{ardy → motion}/ik.js +0 -0
  581. /package/src/{ardy → motion}/physics-surface.js +0 -0
  582. /package/src/{ardy → motion}/playback-clock.js +0 -0
  583. /package/src/{ardy → motion}/range-pin.js +0 -0
@@ -1,13 +1,6 @@
1
- import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
2
- import { FiAlertTriangle, FiArrowUp, FiCheck, FiChevronRight, FiClock, FiDownload, FiFilm, FiImage, FiMoreHorizontal, FiPaperclip, FiPlus, FiRotateCw, FiX } from "react-icons/fi";
3
- import {
4
- ATTACHMENT_FAILED_NOTICE,
5
- ATTACHMENT_LIMIT_NOTICE,
6
- ATTACHMENT_TYPE_NOTICE,
7
- attachmentFilesFromTransfer,
8
- attachmentFromFile,
9
- transferCarriesFiles,
10
- } from "./attachment-image.js";
1
+ import { Fragment, useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
2
+ import { FiAlertTriangle, FiArrowUp, FiCheck, FiChevronRight, FiClock, FiDownload, FiImage, FiMoreHorizontal, FiPaperclip, FiPlus, FiRotateCw, FiX } from "react-icons/fi";
3
+ import { ATTACHMENT_FAILED_NOTICE, ATTACHMENT_LIMIT_NOTICE, ATTACHMENT_TYPE_NOTICE, attachmentFilesFromTransfer, attachmentFromFile, transferCarriesFiles } from "./attachment-image.js";
11
4
  import { isImeComposing } from "../ime.js";
12
5
  import { useStudioShell } from "../shell/studio-shell-context.js";
13
6
  import { ko } from "../locale.js";
@@ -31,10 +24,8 @@ import {
31
24
  clampPanelWidth,
32
25
  createAgentTransport,
33
26
  panelPresentation,
34
- providerEnvLabel,
35
27
  requestHostImageAction,
36
- resolveToolLabel,
37
- formatElapsed,
28
+ formatTurnClock,
38
29
  formatResetIn,
39
30
  readStoredPanelWidth,
40
31
  readStoredSupervisor,
@@ -47,6 +38,11 @@ import {
47
38
  supervisorFollowUp,
48
39
  } from "./agent-client.js";
49
40
  import "./agent-panel.css";
41
+ import { AgentProgressCard } from "./AgentProgressCard.jsx";
42
+ import { LoginPanel } from "./LoginPanel.jsx";
43
+ import { TurnSteps } from "./AgentTurnSteps.jsx";
44
+ import { ReplyText } from "./AgentReplyText.jsx";
45
+ import { stepLabel, turnLayout } from "./agent-turn.js";
50
46
 
51
47
  function relativeTime(value, now = Date.now()) {
52
48
  const at = Date.parse(value || "");
@@ -65,32 +61,6 @@ function StatusDot({ tone, title }) {
65
61
  return <span className={`agent-status-dot ${tone}`} title={title} aria-hidden="true" />;
66
62
  }
67
63
 
68
- function ToolCallCard({ call, presentation, onRetry }) {
69
- const tone = call.status === "running" ? "busy" : ["failed", "cancelled"].includes(call.status) ? "alert" : "ok";
70
- // The badge marks a call that edits the host surface, so it is drawn exactly
71
- // for the families that surface names.
72
- const hostTool = Object.hasOwn(presentation.toolLabels, call.name);
73
- const detail = call.error ? `error: ${call.error}` : JSON.stringify(call.result ?? call.args ?? {}, null, 2);
74
- return <div className={`agent-card agent-tool-card${call.status === "failed" ? " failed" : ""}`} data-tool-status={call.status} data-tool-name={call.name}>
75
- <details>
76
- <summary>
77
- <StatusDot tone={tone} title={call.status} />
78
- <span className="agent-tool-label">{resolveToolLabel(call, presentation.toolLabels)}</span>{hostTool && <span className="agent-tool-badge">{presentation.toolBadge}</span>}
79
- <span className="agent-tool-elapsed">{call.status === "running" ? "running…" : call.status === "cancelled" ? (call.result?.status === "not_applied" ? "not applied" : "result unknown") : formatElapsed(call.elapsedMs)}</span>
80
- <FiChevronRight size={12} aria-hidden="true" />
81
- </summary>
82
- <pre className="agent-tool-detail">{detail}</pre>
83
- </details>
84
- {call.failure && <div className="agent-error" role="alert">
85
- <span>{call.failure.message || ERROR_COPY[call.failure.code] || "The turn failed."}</span>
86
- <div className="agent-error-actions">
87
- <button type="button" className="agent-ghost-button agent-error-retry" onClick={onRetry}>Retry</button>
88
- <button type="button" className="agent-ghost-button agent-error-details" onClick={(event) => { const card = event.currentTarget.closest(".agent-tool-card"); const details = card?.querySelector("details"); if (details) details.open = !details.open; }}>Details</button>
89
- </div>
90
- </div>}
91
- </div>;
92
- }
93
-
94
64
  function ImageResultCard({ image, onUse, onUndo, onRegenerate, onOpen }) {
95
65
  const applying = image.apply?.status === "applying";
96
66
  const failed = image.apply?.status === "failed";
@@ -250,69 +220,15 @@ const RECOVERY_COPY = {
250
220
  new_intent: "Tell the agent what to do with the changed target.",
251
221
  reconcile: "The result is unknown; reconcile before changing this target.",
252
222
  sign_in: "Sign in again to continue.",
223
+ fix_key: "Log in again under Connect a model, then retry.",
253
224
  };
254
225
 
255
- function FailureCard({ failure, onRetry }) {
226
+ function FailureCard({ failure, onRetry, onFixKey }) {
256
227
  return <div className="agent-card agent-failure-card" data-failure-code={failure.code} role="alert">
257
228
  <div className="agent-failure-head"><FiAlertTriangle size={12} aria-hidden="true" /><span className="agent-failure-code">{failure.code}</span></div>
258
229
  <p className="agent-failure-message">{failure.message || "The command did not complete."}</p>
259
230
  <p className="agent-failure-recovery">{RECOVERY_COPY[failure.recovery?.action] || RECOVERY_COPY.none}{failure.preserved?.authoredState === "unchanged" ? " Nothing in the scene changed." : failure.preserved?.authoredState === "unknown" ? " Whether the scene changed is unknown." : ""}</p>
260
- {failure.recovery?.retryAllowed && <div className="agent-error-actions"><button type="button" className="agent-ghost-button agent-error-retry" onClick={onRetry}>Retry</button></div>}
261
- </div>;
262
- }
263
-
264
- // One provider's credential row. The key lives in this component and nowhere
265
- // else: it is typed into a password field, handed to the transport once and
266
- // dropped the moment the sidecar accepts it, so no store, no transcript and no
267
- // screenshot can ever carry it. The sidecar answers with the SOURCE of a key
268
- // (env / file), never with a key, which is what the row reports.
269
- function ProviderKeyRow({ provider, onSave, onRemove }) {
270
- const [draft, setDraft] = useState("");
271
- const [busy, setBusy] = useState(false);
272
- const [error, setError] = useState("");
273
- const fromEnv = provider.authSource === "env";
274
- const fromFile = provider.authSource === "file";
275
- const envLabel = `set by ${providerEnvLabel(provider.id)}`;
276
- const inputId = `agent-key-${provider.id}`;
277
- const run = async (action) => {
278
- setBusy(true);
279
- setError("");
280
- try {
281
- await action();
282
- // The key is gone from the page the instant it is stored.
283
- setDraft("");
284
- } catch (failure) {
285
- // The sidecar's own refusal, which never quotes key material.
286
- setError(failure?.message || "The sidecar refused that key.");
287
- } finally {
288
- setBusy(false);
289
- }
290
- };
291
- return <div className="agent-key-row" data-provider={provider.id} data-provider-source={provider.authSource || "none"} data-provider-signed-in={provider.signedIn ? "true" : "false"}>
292
- <div className="agent-key-head">
293
- <StatusDot tone={provider.signedIn ? "ok" : ""} title={provider.signedIn ? "key configured" : "no key"} />
294
- <label className="agent-key-label" htmlFor={inputId}>{provider.label}</label>
295
- {/* The head states WHERE the key lives in two words; the variable's own
296
- name belongs on the input it replaces, where there is room for it. */}
297
- <span className="agent-key-source">{fromEnv ? "environment" : fromFile ? "saved on this machine" : "no key"}</span>
298
- </div>
299
- <div className="agent-key-controls">
300
- <input
301
- id={inputId}
302
- className="agent-key-input"
303
- type="password"
304
- autoComplete="off"
305
- spellCheck="false"
306
- aria-label={`${provider.label} API key`}
307
- placeholder={fromEnv ? envLabel : provider.signedIn ? "Replace key" : "Paste API key"}
308
- disabled={fromEnv || busy}
309
- value={draft}
310
- onChange={(event) => { setDraft(event.target.value); setError(""); }}
311
- />
312
- <button type="button" className="agent-key-save" disabled={fromEnv || busy || !draft.trim()} onClick={() => run(() => onSave(provider.id, draft))}>Save</button>
313
- {fromFile && <button type="button" className="agent-ghost-button agent-key-remove" disabled={busy} onClick={() => run(() => onRemove(provider.id))}>Remove</button>}
314
- </div>
315
- {error && <p className="agent-key-error" role="alert">{error}</p>}
231
+ {failure.recovery?.retryAllowed && <div className="agent-error-actions">{failure.recovery.action === "fix_key" && onFixKey && <button type="button" className="agent-ghost-button agent-error-keys" onClick={onFixKey}>Log in again</button>}<button type="button" className="agent-ghost-button agent-error-retry" onClick={onRetry}>Retry</button></div>}
316
232
  </div>;
317
233
  }
318
234
 
@@ -336,7 +252,6 @@ export default function AgentPanel({
336
252
  surface = "workflow",
337
253
  buildContext = null,
338
254
  onImageAction = null,
339
- onI2vAction = null,
340
255
  onReceipt = null,
341
256
  }) {
342
257
  // What this host shows around the shared conversation: labels, chips, the
@@ -405,10 +320,8 @@ export default function AgentPanel({
405
320
  const [lightbox, setLightbox] = useState(null);
406
321
  const [overlay, setOverlay] = useState(() => !embedded && (globalThis.innerWidth || 1440) < AGENT_PANEL_OVERLAY_BREAKPOINT);
407
322
  const [historyOpen, setHistoryOpen] = useState(false);
408
- // Provider credentials, read from the sidecar only while the section is open.
323
+ // Connect a model (pi's login list, LoginPanel.jsx) reads its own list from the sidecar while it is open.
409
324
  const [keysOpen, setKeysOpen] = useState(false);
410
- const [providers, setProviders] = useState([]);
411
- const [providersState, setProvidersState] = useState("loading");
412
325
  const [historySessions, setHistorySessions] = useState([]);
413
326
  const [restoreNotice, setRestoreNotice] = useState("");
414
327
  // Why the last steer did not reach the running turn. It reads in the same
@@ -427,8 +340,6 @@ export default function AgentPanel({
427
340
  buildContextRef.current = buildContext;
428
341
  const imageActionRef = useRef(onImageAction);
429
342
  imageActionRef.current = onImageAction;
430
- const i2vActionRef = useRef(onI2vAction);
431
- i2vActionRef.current = onI2vAction;
432
343
  const receiptRef = useRef(onReceipt);
433
344
  receiptRef.current = onReceipt;
434
345
 
@@ -479,10 +390,15 @@ export default function AgentPanel({
479
390
  // is live and holding the turn's outcome for a few seconds after it ends.
480
391
  const [activityNow, setActivityNow] = useState(() => Date.now());
481
392
  const activity = model
482
- ? describeActivity(chat, { now: activityNow, toolLabel: (call) => resolveToolLabel(call, presentation.toolLabels) })
393
+ ? describeActivity(chat, { now: activityNow, toolLabel: (call) => stepLabel(call, presentation.toolLabels) })
483
394
  : modelsState === "failed"
484
395
  ? { phase: "no-model", kind: "terminal", tone: "alert", text: "No model available — the agent service did not answer with one", ticking: false }
485
396
  : { phase: "models", kind: "live", tone: "busy", text: "Loading models…", ticking: false };
397
+ // The transcript grouped per turn, and the motion progress card (above the latest turn's steps, so the live
398
+ // step stays at the bottom where the transcript scrolls) limited to that turn's drafts.
399
+ const layout = useMemo(() => turnLayout(items, { streaming }), [items, streaming]);
400
+ const turnSince = streaming ? chat.turnStartedAt : chat.lastTurn ? chat.lastTurn.endedAt - chat.lastTurn.durationMs : null;
401
+ const progressCard = <AgentProgressCard items={items} since={turnSince} live={streaming} />;
486
402
  useEffect(() => {
487
403
  if (!activity.ticking) return;
488
404
  const timer = setInterval(() => setActivityNow(Date.now()), 500);
@@ -510,21 +426,6 @@ export default function AgentPanel({
510
426
  return null;
511
427
  }
512
428
  }, [sessionState, transport]);
513
- // The sidecar owns the credentials; this reads which providers have one and
514
- // where it came from. The list is read when the keys section opens and again
515
- // after every write — a key that just landed (or left) changes which models
516
- // the composer may offer, so the model list is re-read with it.
517
- const readProviders = useCallback(async () => {
518
- try {
519
- const list = await transport.providers?.();
520
- if (!Array.isArray(list)) throw new Error("no provider list");
521
- setProviders(list);
522
- setProvidersState("ready");
523
- } catch {
524
- setProviders([]);
525
- setProvidersState("failed");
526
- }
527
- }, [transport]);
528
429
  // Everything an authentication change moves, read back once: WHO the session
529
430
  // is (readiness is `signedIn || providersConfigured > 0`), which providers
530
431
  // hold a credential, and WHICH models this session may therefore send to. A
@@ -535,13 +436,11 @@ export default function AgentPanel({
535
436
  // into a composer without reloading the page, and what takes it away again
536
437
  // when the last way in goes. No timer is involved.
537
438
  const refreshProviderState = useCallback(async () => {
538
- const session = readAccount();
539
- await readProviders();
540
- await session;
439
+ await readAccount();
541
440
  try {
542
441
  applyModelList(await transport.models());
543
442
  } catch { /* the composer keeps the list it was last advertised */ }
544
- }, [applyModelList, readAccount, readProviders, transport]);
443
+ }, [applyModelList, readAccount, transport]);
545
444
 
546
445
  useEffect(() => {
547
446
  let cancelled = false;
@@ -798,20 +697,13 @@ export default function AgentPanel({
798
697
  // refresh above, because a key changes the session, not just a row.
799
698
  const toggleProviderKeys = useCallback(() => {
800
699
  setMenuOpen(false);
801
- const next = !keysOpen;
802
- setKeysOpen(next);
803
- if (!next) return;
804
- setProvidersState("loading");
805
- readProviders();
806
- }, [keysOpen, readProviders]);
807
- const saveProviderKey = useCallback(async (id, key) => {
808
- await transport.setProviderKey(id, key);
809
- await refreshProviderState();
810
- }, [refreshProviderState, transport]);
811
- const removeProviderKey = useCallback(async (id) => {
812
- await transport.removeProviderKey(id);
813
- await refreshProviderState();
814
- }, [refreshProviderState, transport]);
700
+ setKeysOpen(!keysOpen);
701
+ }, [keysOpen]);
702
+ // Opens (never toggles) Connect a model, pi's login list: the signed-out card and a refused key both lead here.
703
+ const openProviderKeys = useCallback(() => {
704
+ setMenuOpen(false);
705
+ setKeysOpen(true);
706
+ }, []);
815
707
 
816
708
  // Clearing the transcript also retires the session, so what the author sees
817
709
  // and what the model remembers cannot diverge.
@@ -927,8 +819,8 @@ export default function AgentPanel({
927
819
  {resetLabel && <span className="agent-account-reset">resets in {resetLabel}</span>}
928
820
  </div>}
929
821
  <button type="button" role="menuitem" onClick={clearContext}>Clear context</button>
930
- <button type="button" role="menuitem" className="agent-menu-keys" aria-expanded={keysOpen} onClick={toggleProviderKeys}>Provider keys…</button>
931
- <button type="button" role="menuitem" onClick={signOut}>Sign out</button>
822
+ <button type="button" role="menuitem" className="agent-menu-keys" aria-expanded={keysOpen} onClick={toggleProviderKeys}>Connect a model…</button>
823
+ {account?.signedIn && <button type="button" role="menuitem" onClick={signOut}>Sign out</button>}
932
824
  </div>}
933
825
  </span>
934
826
  {!embedded && <button type="button" className="agent-icon-button agent-collapse" onClick={() => setCollapsed(true)} aria-label="Collapse agent panel" title="Collapse agent panel (Cmd/Ctrl+B)"><FiChevronRight size={13} /></button>}
@@ -939,28 +831,21 @@ export default function AgentPanel({
939
831
 
940
832
  {/* An inline section, not a modal: the panel stays the one place the
941
833
  conversation and the credentials it runs on are managed. */}
942
- {keysOpen && <section className="agent-keys" aria-label="Provider keys" data-agent-keys="true">
943
- <div className="agent-keys-head">
944
- <h3 className="agent-keys-title">Provider keys</h3>
945
- <button type="button" className="agent-icon-button agent-keys-close" aria-label="Close provider keys" onClick={() => setKeysOpen(false)}><FiX size={11} aria-hidden="true" /></button>
946
- </div>
947
- <p className="agent-keys-hint">Keys are stored by the local CozyClay service and are never shown again.</p>
948
- {providersState === "loading" && <p className="agent-keys-note">Loading providers…</p>}
949
- {providersState === "failed" && <p className="agent-keys-note alert" role="alert">The provider list is unavailable.</p>}
950
- {providers.filter((entry) => entry.id !== "openai-codex").map((entry) => <ProviderKeyRow key={entry.id} provider={entry} onSave={saveProviderKey} onRemove={removeProviderKey} />)}
951
- </section>}
834
+ {keysOpen && <LoginPanel onClose={() => setKeysOpen(false)} onChatGPT={signIn} onChanged={refreshProviderState} />}
952
835
 
953
836
  <div className="agent-transcript" ref={transcriptRef} aria-live="polite" aria-label="Conversation" data-agent-transcript="true">
954
837
  {authState === "signed-out" && <div className="agent-state-card" data-agent-card="signed-out">
955
- <h3>Sign in with ChatGPT</h3>
956
- <p>The agent runs against your ChatGPT plan. Sign-in happens in your browser — no token is ever stored in this page.</p>
838
+ <h3>Connect a model</h3>
839
+ <p>Sign in with ChatGPT, or log in to another provider with a subscription, an API key, or your own proxy server. Sign-in happens in your browser — no token is ever stored in this page.</p>
957
840
  <button type="button" className="agent-primary-button agent-signin" onClick={signIn}>Sign in with ChatGPT</button>
841
+ <button type="button" className="agent-ghost-button agent-use-key" onClick={openProviderKeys}>Log in with another provider</button>
958
842
  </div>}
959
843
 
960
844
  {authState === "signing-in" && <div className="agent-state-card" data-agent-card="signing-in">
961
845
  <span className="agent-spinner" aria-hidden="true" />
962
846
  <h3>Waiting for your browser…</h3>
963
- <p>Finish the ChatGPT sign-in in the tab that just opened. This panel picks up the session when you come back.</p>
847
+ <p>Finish the ChatGPT sign-in in the browser page that just opened. This panel picks up the session when you come back.</p>
848
+ {account?.authorizeUrl && <a className="agent-ghost-button agent-signin-link" href={account.authorizeUrl} target="_blank" rel="noopener noreferrer">Open the sign-in page again</a>}
964
849
  </div>}
965
850
 
966
851
  {authState === "no-entitlement" && <div className="agent-state-card" data-agent-card="no-entitlement">
@@ -977,22 +862,28 @@ export default function AgentPanel({
977
862
  </div>
978
863
  </div>}
979
864
 
980
- {items.map((item) => {
865
+ {/* One step list per turn (AgentTurnSteps.jsx); the motion progress card sits just above the latest one. */}
866
+ {layout.map((entry) => {
867
+ if (entry.type === "steps") return <Fragment key={entry.id}>
868
+ {entry.last && progressCard}
869
+ <div className="agent-row"><TurnSteps block={entry} now={activityNow} toolLabels={presentation.toolLabels} formatClock={formatTurnClock} onRetry={() => runTurn(chat.lastPrompt)} /></div>
870
+ </Fragment>;
871
+ const { item } = entry;
981
872
  if (item.kind === "user") return <div className="agent-row user" key={item.id}>
982
873
  {item.attachments?.length > 0 && <div className="agent-bubble-attachments">
983
874
  {item.attachments.map((attachment, index) => <img key={`${item.id}-${index}`} src={attachment.dataUrl} alt={attachment.name || "Attached image"} onClick={() => setLightbox({ dataUrl: attachment.dataUrl, prompt: attachment.name })} />)}
984
875
  </div>}
985
876
  <div className="agent-bubble">{item.text}</div>
986
877
  </div>;
987
- if (item.kind === "assistant") return <div className="agent-row assistant" key={item.id}><div className="agent-assistant-text">{item.text}{streaming && <span className="agent-caret">▌</span>}</div></div>;
988
- if (item.kind === "tool") return <div className="agent-row" key={item.id}><ToolCallCard call={item} presentation={presentation} onRetry={() => runTurn(chat.lastPrompt)} /></div>;
878
+ if (item.kind === "assistant") return <div className="agent-row assistant" key={item.id}><div className="agent-assistant-text"><ReplyText text={item.text} tail={streaming && <span className="agent-caret">▌</span>} /></div></div>;
989
879
  if (item.kind === "job") return <div className="agent-row" key={item.id}><JobCard job={item} onStop={stopTurn} onAccept={(job) => store.acceptJob(job.jobId)} /></div>;
990
880
  if (item.kind === "receipt") return <div className="agent-row" key={item.id}><ReceiptCard item={item} /></div>;
991
- if (item.kind === "failure") return <div className="agent-row" key={item.id}><FailureCard failure={item.failure} onRetry={() => runTurn(chat.lastPrompt)} /></div>;
881
+ if (item.kind === "failure") return <div className="agent-row" key={item.id}><FailureCard failure={item.failure} onRetry={() => runTurn(chat.lastPrompt)} onFixKey={openProviderKeys} /></div>;
992
882
  if (item.kind === "supervisor") return <div className="agent-row" key={item.id}><SupervisorCard note={item} onSend={(text) => { store.setDraft(text); runTurn(text); }} /></div>;
993
883
  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>;
994
884
  })}
995
885
 
886
+ {!layout.some((entry) => entry.type === "steps" && entry.last) && progressCard}
996
887
  {rateLimit && <PausedCard resetAt={rateLimit.resetAt} onRetry={() => { store.clearRateLimit(); runTurn(chat.lastPrompt); }} onSwitchModel={switchModel} />}
997
888
  </div>
998
889
 
@@ -1001,6 +892,7 @@ export default function AgentPanel({
1001
892
  {authenticated && <div className="agent-activity" data-agent-activity={activity.phase} data-agent-activity-kind={activity.kind} role="status" aria-live="polite">
1002
893
  <span className={`agent-activity-indicator ${activity.tone}`} aria-hidden="true" />
1003
894
  <span className="agent-activity-text">{activity.text}</span>
895
+ {activity.detail && <span className="agent-activity-detail">{activity.detail}</span>}
1004
896
  </div>}
1005
897
 
1006
898
  {authenticated && <div
@@ -1062,17 +954,6 @@ export default function AgentPanel({
1062
954
  </button>
1063
955
  )}
1064
956
  <span className="agent-composer-spacer" aria-hidden="true" />
1065
- {surface === "studio" && i2vActionRef.current && <button
1066
- type="button"
1067
- className="agent-attach-chip agent-i2v-action"
1068
- data-testid="i2v-agent-action"
1069
- aria-label="Generate motion"
1070
- title="Generate motion from this message"
1071
- disabled={composerDisabled || !draft.trim() || streaming}
1072
- onClick={() => { const instruction = draft.trim(); store.setDraft(""); i2vActionRef.current?.(instruction); }}
1073
- >
1074
- <FiFilm size={13} aria-hidden="true" />
1075
- </button>}
1076
957
  {/* While a turn runs, Stop is still the way out of it; on a surface
1077
958
  that can be steered, Send becomes the way INTO it. */}
1078
959
  {streaming
@@ -0,0 +1,107 @@
1
+ // The live progress card in the agent panel (agent-progress.js). While the agent works on motion it shows what
2
+ // the agent actually produced so far: how many drafts it compiled and their QA trend, the latest draft's goals
3
+ // (met or not, value against need) and heaviest issues, whether that draft is installed, and the images the
4
+ // agent asked to see (click to enlarge, over the whole window). Nothing here is invented progress. A new turn
5
+ // starts it over.
6
+ import { useEffect, useRef, useState, useSyncExternalStore } from "react";
7
+ import { createPortal } from "react-dom";
8
+ import { FiCheck, FiX } from "react-icons/fi";
9
+ import { clearProgress, draftsOf, latestDraft, progressSheets, progressVersion, subscribeProgress } from "../agent-progress.js";
10
+ import { ko } from "../locale.js";
11
+ import { goalRows, issueRows, trendPoints, turnCount } from "./agent-progress-view.js";
12
+ import "./agent-progress-card.css";
13
+
14
+ const W = 220, H = 40;
15
+
16
+ function Trend({ totals }) {
17
+ const { points, first, last } = trendPoints(totals, { width: W, height: H });
18
+ if (!points.length) return null;
19
+ const [lx, ly] = points[points.length - 1];
20
+ const label = ko(`QA issues by draft: ${totals.join(", ")}`, `초안별 QA 문제 수: ${totals.join(", ")}`);
21
+ return <div className="agent-progress-trend">
22
+ <svg viewBox={`0 0 ${W} ${H}`} role="img" aria-label={label} preserveAspectRatio="none">
23
+ <line className="base" x1={4} y1={H - 4} x2={W - 4} y2={H - 4} />
24
+ {points.length > 1 && <polyline className="line" points={points.map(p => p.join(",")).join(" ")} />}
25
+ {points.slice(0, -1).map(([x, y], i) => <circle key={i} className="dot" cx={x} cy={y} r={1.8} />)}
26
+ <circle className={`end ${last === 0 ? "ok" : "issue"}`} cx={lx} cy={ly} r={3.4} />
27
+ </svg>
28
+ <span className="agent-progress-trend-text">{totals.length > 1 ? `${first} → ${last}` : `${last}`}</span>
29
+ </div>;
30
+ }
31
+
32
+ // The panel's own lightbox stays inside the panel; a contact sheet needs the window.
33
+ function SheetViewer({ sheet, onClose }) {
34
+ useEffect(() => {
35
+ const onKey = event => { if (event.key === "Escape") onClose(); };
36
+ window.addEventListener("keydown", onKey);
37
+ return () => window.removeEventListener("keydown", onKey);
38
+ }, [onClose]);
39
+ return createPortal(<button type="button" className="agent-progress-viewer" aria-label={ko("Close image", "이미지 닫기")} onClick={onClose}>
40
+ <img src={sheet.dataUrl} alt={sheet.caption || ko("Image the agent checked", "에이전트가 확인한 이미지")} />
41
+ {sheet.caption && <span className="agent-progress-viewer-caption">{sheet.caption}</span>}
42
+ </button>, document.body);
43
+ }
44
+
45
+ /** `since`: when the latest turn started (null: no turn yet). Drafts and images from before it are not this turn's
46
+ * work, such as the take the camera tutorial compiles at startup. `live`: that turn is still running. */
47
+ export function AgentProgressCard({ items, since = null, live = false }) {
48
+ useSyncExternalStore(subscribeProgress, progressVersion);
49
+ const [open, setOpen] = useState(null);
50
+ // The goal and issue lists fold to one line each: the panel is short and the live steps sit below the card.
51
+ const [shown, setShown] = useState({});
52
+ const toggle = key => setShown(now => ({ ...now, [key]: !now[key] }));
53
+ // a new turn (not a steer of the running one) starts the card over
54
+ const turns = turnCount(items), seen = useRef(turns);
55
+ useEffect(() => {
56
+ if (turns > seen.current) clearProgress();
57
+ seen.current = turns;
58
+ }, [turns]);
59
+ const ours = record => since !== null && record.at >= since;
60
+ const newest = latestDraft(), latest = newest && ours(newest) ? newest : null, sheets = progressSheets().filter(ours);
61
+ const viewing = open && sheets.find(sheet => sheet.seq === open);
62
+ if (!latest && !sheets.length) return null;
63
+ const drafts = latest ? draftsOf(latest.characterId).filter(ours) : [];
64
+ const goals = goalRows(latest?.qa.goals), issues = latest && !latest.qa.ok ? issueRows(latest.qa.groups) : [];
65
+ return <div className="agent-row">
66
+ <div className="agent-card agent-progress-card" data-agent-card="progress" data-drafts={drafts.length} data-installed={latest?.installed ? "true" : "false"} role="status" aria-live="polite">
67
+ {latest && <div className="agent-progress-head">
68
+ <span className="agent-progress-title">{ko("Draft", "초안")} {latest.n} · QA {latest.qa.total}</span>
69
+ <span className={`agent-progress-chip ${latest.installed ? "installed" : "draft"}`}>{latest.installed ? ko("Installed", "설치됨") : live ? ko("Not installed yet", "아직 설치 전") : ko("Not installed", "설치 안 됨")}</span>
70
+ </div>}
71
+ {latest?.label && <p className="agent-progress-label">{latest.label}</p>}
72
+ {drafts.length > 0 && <section className="agent-progress-section">
73
+ <h4>{ko(`QA issues across ${drafts.length} draft${drafts.length === 1 ? "" : "s"}`, `초안 ${drafts.length}개의 QA 문제 수`)}</h4>
74
+ <Trend totals={drafts.map(d => d.qa.total)} />
75
+ </section>}
76
+ {goals.length > 0 && <section className="agent-progress-section">
77
+ <button type="button" className="agent-progress-fold" aria-expanded={!!shown.goals} onClick={() => toggle("goals")}>
78
+ <h4>{ko("Goals", "목표")}</h4><span>{ko(`${goals.filter(g => g.ok).length} of ${goals.length} met`, `${goals.length}개 중 ${goals.filter(g => g.ok).length}개 충족`)}</span>
79
+ </button>
80
+ {shown.goals && <ul className="agent-progress-goals">
81
+ {goals.map((g, i) => <li key={i} data-goal-ok={g.ok ? "true" : "false"}>
82
+ <span className={`agent-progress-mark ${g.ok ? "ok" : "miss"}`} aria-label={g.ok ? ko("met", "충족") : ko("missed", "미충족")}>{g.ok ? <FiCheck size={11} /> : <FiX size={11} />}</span>
83
+ <span className="agent-progress-goal-name">{g.name}</span>
84
+ <span className="agent-progress-goal-value">{g.value}{g.need && <span className="need"> / {g.need}</span>}</span>
85
+ </li>)}
86
+ </ul>}
87
+ </section>}
88
+ {issues.length > 0 && <section className="agent-progress-section">
89
+ <button type="button" className="agent-progress-fold" aria-expanded={!!shown.issues} onClick={() => toggle("issues")}>
90
+ <h4>{ko("Largest issues", "가장 큰 문제")}</h4><span>{issues[0].kind} {issues[0].subject}</span>
91
+ </button>
92
+ {shown.issues && <ul className="agent-progress-issues">
93
+ {issues.map((row, i) => <li key={i}><span className="kind">{row.kind}</span> {row.subject} <span className="meta">×{row.count}{row.frames && ` · ${row.frames}`}</span></li>)}
94
+ </ul>}
95
+ </section>}
96
+ {sheets.length > 0 && <section className="agent-progress-section">
97
+ <h4>{ko("What the agent looked at", "에이전트가 본 화면")}</h4>
98
+ <div className="agent-progress-sheets">
99
+ {sheets.map(sheet => <button type="button" key={sheet.seq} className="agent-progress-sheet" title={sheet.caption} onClick={() => setOpen(sheet.seq)}>
100
+ <img src={sheet.dataUrl} alt={sheet.caption || ko("Image the agent checked", "에이전트가 확인한 이미지")} />
101
+ </button>)}
102
+ </div>
103
+ </section>}
104
+ </div>
105
+ {viewing && <SheetViewer sheet={viewing} onClose={() => setOpen(null)} />}
106
+ </div>;
107
+ }
@@ -0,0 +1,37 @@
1
+ // The agent's reply as formatted text (reply-markdown.js): paragraphs, headings, lists, bold, code and tables,
2
+ // never the raw marks. `tail` (the streaming caret) sits at the end of the last line.
3
+ import { Fragment, useMemo } from "react";
4
+ import { replyBlocks } from "./reply-markdown.js";
5
+
6
+ function Spans({ spans }) {
7
+ return spans.map((span, index) => {
8
+ if (span.code) return <code key={index}>{span.text}</code>;
9
+ if (span.bold) return <strong key={index}>{span.text}</strong>;
10
+ return <Fragment key={index}>{span.text}</Fragment>;
11
+ });
12
+ }
13
+
14
+ function Block({ block, tail }) {
15
+ if (block.type === "h") return <p className="agent-reply-heading"><Spans spans={block.spans} />{tail}</p>;
16
+ if (block.type === "code") return <pre><code>{block.text}</code>{tail}</pre>;
17
+ if (block.type === "ul" || block.type === "ol") {
18
+ const List = block.type;
19
+ return <List start={block.start}>
20
+ {block.items.map((spans, index) => <li key={index}><Spans spans={spans} />{index === block.items.length - 1 && tail}</li>)}
21
+ </List>;
22
+ }
23
+ if (block.type === "table") return <>
24
+ <div className="agent-reply-table"><table>
25
+ <thead><tr>{block.head.map((spans, index) => <th key={index}><Spans spans={spans} /></th>)}</tr></thead>
26
+ <tbody>{block.rows.map((row, rowIndex) => <tr key={rowIndex}>{row.map((spans, index) => <td key={index}><Spans spans={spans} /></td>)}</tr>)}</tbody>
27
+ </table></div>
28
+ {tail}
29
+ </>;
30
+ return <p><Spans spans={block.spans} />{tail}</p>;
31
+ }
32
+
33
+ export function ReplyText({ text, tail = null }) {
34
+ const blocks = useMemo(() => replyBlocks(text), [text]);
35
+ if (!blocks.length) return tail || null;
36
+ return blocks.map((block, index) => <Block key={index} block={block} tail={index === blocks.length - 1 ? tail : null} />);
37
+ }
@@ -0,0 +1,84 @@
1
+ // One turn's work in the agent panel (agent-turn.js turnLayout): a header that says how the turn stands (working,
2
+ // done, stopped, failed), how many steps and how long, and one compact row per step: the tool call with a plain
3
+ // label, what it was about and its one-line result; the reasoning the model streamed between calls; the call it is
4
+ // writing now. Open while the turn runs, folded to its header when the turn ends (a failed or stopped turn stays
5
+ // open, and a stopped one says what it left in the scene).
6
+ import { useState } from "react";
7
+ import { FiCheck, FiChevronRight, FiSquare, FiX } from "react-icons/fi";
8
+ import { ko } from "../locale.js";
9
+ import { scriptNow, stepLabel, stepResult, stepsHeader, stoppedNote, thinkingLine } from "./agent-turn.js";
10
+ import "./agent-turn-steps.css";
11
+
12
+ /** Step time: "0.6 s", "12 s", "1 m 05 s". */
13
+ function stepClock(ms) {
14
+ if (!Number.isFinite(ms) || ms < 0) return "";
15
+ const s = ms / 1000;
16
+ if (s < 10) return `${s.toFixed(1)} s`;
17
+ if (s < 60) return `${Math.round(s)} s`;
18
+ return `${Math.floor(s / 60)} m ${String(Math.round(s % 60)).padStart(2, "0")} s`;
19
+ }
20
+
21
+ function StepIcon({ state }) {
22
+ if (state === "running") return <span className="agent-step-spin" aria-label={ko("running", "진행 중")} />;
23
+ if (state === "failed") return <FiX className="agent-step-icon alert" size={11} aria-label={ko("failed", "실패")} />;
24
+ if (state === "cancelled") return <FiSquare className="agent-step-icon warn" size={9} aria-label={ko("stopped", "중지됨")} />;
25
+ if (state === "note") return <span className="agent-step-dot" aria-hidden="true" />;
26
+ return <FiCheck className="agent-step-icon ok" size={11} aria-label={ko("done", "완료")} />;
27
+ }
28
+
29
+ function Step({ step, live, isLast, now, toolLabels }) {
30
+ const [open, setOpen] = useState(false);
31
+ if (step.kind === "narration") return <li className="agent-step narration" data-step-kind="narration"><StepIcon state="note" /><span className="agent-step-text">{step.text}</span></li>;
32
+ if (step.kind === "thinking") {
33
+ const running = live && isLast;
34
+ const line = thinkingLine(step.text);
35
+ // from its first words until the next step began; a thought that arrived at once shows no time
36
+ const took = (running ? now : step.endedAt ?? step.updatedAt) - step.startedAt;
37
+ return <li className="agent-step thinking" data-step-kind="thinking">
38
+ <button type="button" className="agent-step-row" aria-expanded={open} onClick={() => setOpen(value => !value)}>
39
+ <StepIcon state={running ? "running" : "note"} />
40
+ <span className="agent-step-label">{ko("Thinking", "생각")}</span>
41
+ <span className="agent-step-subject">{open ? "" : line}</span>
42
+ <span className="agent-step-time">{took >= 1000 ? stepClock(took) : ""}</span>
43
+ </button>
44
+ {open && <p className="agent-step-thought">{step.text}</p>}
45
+ </li>;
46
+ }
47
+ const running = step.kind === "compose" || step.status === "running";
48
+ const label = step.kind === "compose" ? ko(`Preparing ${stepLabel(step, toolLabels)}`, `${stepLabel(step, toolLabels)} 준비 중`) : stepLabel(step, toolLabels);
49
+ const result = running ? (step.progress ? `${scriptNow(step)}${step.progress.summary ? ` — ${step.progress.summary}` : ""}` : "") : stepResult(step);
50
+ const time = running ? now - (step.startedAt ?? now) : step.elapsedMs;
51
+ return <li className={`agent-step ${step.kind}${step.status === "failed" ? " failed" : ""}`} data-step-kind={step.kind} data-tool-name={step.name} data-tool-status={step.kind === "compose" ? "composing" : step.status} data-action={step.action}>
52
+ <div className="agent-step-row">
53
+ <StepIcon state={running ? "running" : step.status} />
54
+ <span className="agent-step-label">{label}</span>
55
+ {step.subject && <span className="agent-step-subject">{step.subject}</span>}
56
+ <span className="agent-step-time">{stepClock(time)}</span>
57
+ </div>
58
+ {result && <p className="agent-step-result">{result}</p>}
59
+ </li>;
60
+ }
61
+
62
+ export function TurnSteps({ block, now, toolLabels, formatClock, onRetry }) {
63
+ const [chosen, setChosen] = useState(null);
64
+ const failed = block.end?.status === "failed" || block.steps.some(step => step.failure);
65
+ const stopped = block.end?.status === "stopped";
66
+ const open = chosen ?? (block.live || failed || stopped);
67
+ const header = stepsHeader(block, now, formatClock);
68
+ const failure = block.steps.find(step => step.failure)?.failure;
69
+ return <div className="agent-card agent-steps" data-agent-card="steps" data-steps-state={block.live ? "live" : block.end?.status ?? "done"} data-open={open ? "true" : "false"}>
70
+ <button type="button" className="agent-steps-head" aria-expanded={open} onClick={() => setChosen(!open)}>
71
+ <span className={`agent-status-dot ${header.tone}`} aria-hidden="true" />
72
+ <span className="agent-steps-title">{header.text}</span>
73
+ <FiChevronRight className="agent-steps-chevron" size={12} aria-hidden="true" />
74
+ </button>
75
+ {open && <ol className="agent-steps-list">
76
+ {block.steps.map((step, index) => <Step key={step.id} step={step} live={block.live} isLast={index === block.steps.length - 1} now={now} toolLabels={toolLabels} />)}
77
+ </ol>}
78
+ {stopped && <p className="agent-steps-note">{stoppedNote(block)}</p>}
79
+ {failure && <div className="agent-error" role="alert">
80
+ <span>{failure.message || ko("The turn failed.", "턴이 실패했어요.")}</span>
81
+ <div className="agent-error-actions"><button type="button" className="agent-ghost-button agent-error-retry" onClick={onRetry}>{ko("Retry", "다시 시도")}</button></div>
82
+ </div>}
83
+ </div>;
84
+ }