cozyclay 2.1.1 → 3.0.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 (585) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +43 -116
  4. package/bin/agent/agent-runner.mjs +14 -15
  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/session-store.mjs +2 -2
  14. package/bin/agent/studio-context-budget.mjs +93 -0
  15. package/bin/agent/studio-motion-reference.mjs +23 -0
  16. package/bin/agent/studio-prompt.mjs +1 -1
  17. package/bin/agent/studio-rebase-text.mjs +32 -0
  18. package/bin/agent/studio-rebase.mjs +26 -39
  19. package/bin/agent/studio-tools.mjs +9 -28
  20. package/bin/agent/tool-arguments.mjs +49 -0
  21. package/bin/agent/turn-frames.mjs +66 -0
  22. package/bin/codex-auth.mjs +9 -2
  23. package/bin/cozyclay.mjs +10 -116
  24. package/bin/live/cli.mjs +3 -3
  25. package/bin/mcp-runtime.mjs +2 -12
  26. package/bin/package-signature.mjs +0 -4
  27. package/dist/app/index.html +7 -6
  28. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  29. package/dist/assets/analytics-BWCqdgWA.js +1 -0
  30. package/dist/assets/app-DfTCxaKk.js +5087 -0
  31. package/dist/assets/app-f-fkV5uP.css +1 -0
  32. package/dist/assets/compile-worker-BxKXh07s.js +5 -0
  33. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  34. package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-DNO8kv_N.js} +1 -1
  35. package/dist/assets/{landing-ChUvB_zL.js → landing-yfZaJkWc.js} +1 -1
  36. package/dist/assets/scene-asset-sync-U3kS2rvK.js +20 -0
  37. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  38. package/dist/assets/workflow-D78eiKTa.js +179 -0
  39. package/dist/cozyclay-package.json +1 -1
  40. package/dist/index.html +3 -3
  41. package/dist/sw.js +3 -6
  42. package/dist/workflow/index.html +5 -4
  43. package/mcp/LIVE-PROTOCOL.md +7 -47
  44. package/mcp/README.md +16 -26
  45. package/mcp/live-hub.mjs +6 -10
  46. package/mcp/package.json +1 -3
  47. package/mcp/tool-handlers.mjs +4 -168
  48. package/package.json +3 -22
  49. package/src/App.jsx +151 -2625
  50. package/src/agent-progress.js +80 -0
  51. package/src/agent-turn-mark.js +30 -0
  52. package/src/analytics.js +1 -19
  53. package/src/app-context.js +12 -2
  54. package/src/app-stage.jsx +2 -34
  55. package/src/asset-pane.jsx +6 -5
  56. package/src/camera-block.js +21 -0
  57. package/src/camera-direction.js +120 -0
  58. package/src/character-kind.js +3 -9
  59. package/src/command-bus.js +10 -8
  60. package/src/commands/cast.js +5 -34
  61. package/src/commands/elements/character.js +1 -1
  62. package/src/commands/elements/motion.js +1 -6
  63. package/src/commands/elements/object.js +6 -2
  64. package/src/commands/elements/shot.js +4 -1
  65. package/src/commands/elements.js +38 -14
  66. package/src/commands/index.js +3 -1
  67. package/src/commands/kits.js +37 -0
  68. package/src/commands/motion-timing.js +57 -0
  69. package/src/commands/motion.js +9 -152
  70. package/src/commands/motion3-compile.js +65 -0
  71. package/src/commands/motion3.js +214 -0
  72. package/src/commands/objects.js +6 -4
  73. package/src/commands/shot.js +55 -13
  74. package/src/commands/stage.js +14 -1
  75. package/src/commands/view.js +4 -1
  76. package/src/document-mirrors.js +42 -0
  77. package/src/document-store.js +4 -0
  78. package/src/domains/cast.js +19 -194
  79. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  80. package/src/domains/motion/take-source.js +53 -0
  81. package/src/domains/motion.js +60 -1838
  82. package/src/domains/scenes.js +12 -55
  83. package/src/domains/shots.js +2 -2
  84. package/src/domains/stage.js +4 -2
  85. package/src/draft-ghost.js +47 -0
  86. package/src/draft-ghost.jsx +129 -0
  87. package/src/dualview.jsx +4 -3
  88. package/src/effects/EffectsLayer.jsx +112 -0
  89. package/src/effects/effect-meshes.js +232 -0
  90. package/src/effects/effect-record.js +103 -0
  91. package/src/effects/emitter.js +59 -0
  92. package/src/effects/simulate.js +261 -0
  93. package/src/effects/state.js +59 -0
  94. package/src/execution-telemetry.js +2 -3
  95. package/src/first-success-guide.jsx +5 -5
  96. package/src/ghost-set.js +257 -0
  97. package/src/held-object-edit.jsx +113 -0
  98. package/src/held-object-pose.js +175 -0
  99. package/src/hierarchy-model.js +1 -1
  100. package/src/hierarchy-panel.jsx +1 -1
  101. package/src/imported-mesh.jsx +103 -0
  102. package/src/kits/held.js +84 -0
  103. package/src/kits/index.js +70 -0
  104. package/src/kits/smoking.js +57 -0
  105. package/src/light-blockers.js +93 -0
  106. package/src/motion/beat-board-model.js +84 -0
  107. package/src/motion/beat-board.css +124 -0
  108. package/src/motion/beat-board.jsx +60 -0
  109. package/src/motion/center-of-mass.js +66 -0
  110. package/src/motion/fix-collisions.js +850 -0
  111. package/src/motion/physics-review.js +118 -0
  112. package/src/{ardy → motion}/physics-support.js +1 -76
  113. package/src/motion/rig-snapshot.js +22 -0
  114. package/src/motion/take-segments.jsx +140 -0
  115. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  116. package/src/{ardy → motion}/timeline.css +1 -2
  117. package/src/{ardy → motion}/timeline.jsx +42 -413
  118. package/src/motion/waypoints.js +76 -0
  119. package/src/motion3/beats.js +83 -0
  120. package/src/motion3/body.js +158 -0
  121. package/src/motion3/bones.js +47 -0
  122. package/src/motion3/carried.js +47 -0
  123. package/src/motion3/compile-job.js +79 -0
  124. package/src/motion3/compile-worker.js +10 -0
  125. package/src/motion3/compiler/anchors.js +113 -0
  126. package/src/motion3/compiler/arm-orient.js +128 -0
  127. package/src/motion3/compiler/channels.js +125 -0
  128. package/src/motion3/compiler/compile.js +205 -0
  129. package/src/motion3/compiler/gait-rhythm.js +97 -0
  130. package/src/motion3/compiler/grip.js +171 -0
  131. package/src/motion3/compiler/head-steady.js +48 -0
  132. package/src/motion3/compiler/hold.js +134 -0
  133. package/src/motion3/compiler/idle.js +113 -0
  134. package/src/motion3/compiler/limbs.js +197 -0
  135. package/src/motion3/compiler/math.js +72 -0
  136. package/src/motion3/compiler/pose.js +177 -0
  137. package/src/motion3/compiler/quat.js +49 -0
  138. package/src/motion3/compiler/skeleton.js +74 -0
  139. package/src/motion3/compiler/validate-common.js +32 -0
  140. package/src/motion3/compiler/validate-goals.js +118 -0
  141. package/src/motion3/compiler/validate-objects.js +144 -0
  142. package/src/motion3/compiler/validate-parts.js +118 -0
  143. package/src/motion3/compiler/validate.js +122 -0
  144. package/src/motion3/compiler/vocab.js +74 -0
  145. package/src/motion3/compiler/walk-pelvis.js +275 -0
  146. package/src/motion3/compiler/walk.js +207 -0
  147. package/src/motion3/compiler/wrist-clamp.js +32 -0
  148. package/src/motion3/delta.js +47 -0
  149. package/src/motion3/describe.js +177 -0
  150. package/src/motion3/docs.js +27 -0
  151. package/src/motion3/edit-carry.js +21 -0
  152. package/src/motion3/fix/ease.js +16 -0
  153. package/src/motion3/fix/ground.js +37 -0
  154. package/src/motion3/fix/index.js +52 -0
  155. package/src/motion3/fix/push.js +71 -0
  156. package/src/motion3/fix/reach.js +46 -0
  157. package/src/motion3/fix/retime.js +79 -0
  158. package/src/motion3/fix/self.js +211 -0
  159. package/src/motion3/fix/slide.js +70 -0
  160. package/src/motion3/fix/turn.js +51 -0
  161. package/src/motion3/follow.js +39 -0
  162. package/src/motion3/free-motion.js +204 -0
  163. package/src/motion3/grip-choice.js +130 -0
  164. package/src/motion3/hold-plan.js +157 -0
  165. package/src/motion3/json-patch.js +77 -0
  166. package/src/motion3/key-aim.js +105 -0
  167. package/src/motion3/key-edit.js +82 -0
  168. package/src/motion3/lenient.js +127 -0
  169. package/src/motion3/object-keys.js +236 -0
  170. package/src/motion3/object-pose.js +182 -0
  171. package/src/motion3/object-tracks.js +12 -0
  172. package/src/motion3/people.js +82 -0
  173. package/src/motion3/pipeline.js +102 -0
  174. package/src/motion3/playback.js +51 -0
  175. package/src/motion3/probes.js +91 -0
  176. package/src/motion3/qa/body-checks.js +34 -0
  177. package/src/motion3/qa/check-names.js +14 -0
  178. package/src/motion3/qa/checks.js +125 -0
  179. package/src/motion3/qa/contact-checks.js +51 -0
  180. package/src/motion3/qa/explain.js +80 -0
  181. package/src/motion3/qa/fmt.js +5 -0
  182. package/src/motion3/qa/foot-checks.js +72 -0
  183. package/src/motion3/qa/geometry.js +77 -0
  184. package/src/motion3/qa/goals.js +191 -0
  185. package/src/motion3/qa/ground.js +60 -0
  186. package/src/motion3/qa/hints-contact.js +195 -0
  187. package/src/motion3/qa/hints-goal.js +160 -0
  188. package/src/motion3/qa/hints-knee.js +24 -0
  189. package/src/motion3/qa/hints-motion.js +209 -0
  190. package/src/motion3/qa/hints-object.js +91 -0
  191. package/src/motion3/qa/index.js +44 -0
  192. package/src/motion3/qa/key-checks.js +82 -0
  193. package/src/motion3/qa/near.js +96 -0
  194. package/src/motion3/qa/object-checks.js +135 -0
  195. package/src/motion3/qa/patch.js +161 -0
  196. package/src/motion3/qa/people-checks.js +37 -0
  197. package/src/motion3/qa/severity.js +38 -0
  198. package/src/motion3/qa/spec-checks.js +65 -0
  199. package/src/motion3/qa/style.js +228 -0
  200. package/src/motion3/qa/wrist-checks.js +69 -0
  201. package/src/motion3/qa/wrist-obs.js +60 -0
  202. package/src/motion3/refs.js +129 -0
  203. package/src/motion3/repair-tune.js +121 -0
  204. package/src/motion3/repair.js +83 -0
  205. package/src/motion3/report.js +67 -0
  206. package/src/motion3/rigs/index.js +14 -0
  207. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  208. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  209. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  210. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  211. package/src/motion3/scene.js +60 -0
  212. package/src/motion3/schema/brief.js +89 -0
  213. package/src/motion3/schema/fields.js +44 -0
  214. package/src/motion3/schema/spec-examples.js +46 -0
  215. package/src/motion3/schema/topics.js +113 -0
  216. package/src/motion3/schema/types.js +149 -0
  217. package/src/motion3/search.js +117 -0
  218. package/src/motion3/second-hand.js +112 -0
  219. package/src/motion3/settle.js +136 -0
  220. package/src/motion3/skills/basic.js +83 -0
  221. package/src/motion3/skills/book.js +76 -0
  222. package/src/motion3/skills/car.js +145 -0
  223. package/src/motion3/skills/flip.js +200 -0
  224. package/src/motion3/skills/frames-auto.js +29 -0
  225. package/src/motion3/skills/impact.js +109 -0
  226. package/src/motion3/skills/index.js +85 -0
  227. package/src/motion3/skills/info.js +80 -0
  228. package/src/motion3/skills/smoke-kit.js +109 -0
  229. package/src/motion3/skills/smoke.js +160 -0
  230. package/src/motion3/spec-store.js +59 -0
  231. package/src/motion3/take-edit.js +155 -0
  232. package/src/motion3/take.js +63 -0
  233. package/src/motion3/timeline.js +127 -0
  234. package/src/motion3/views.js +121 -0
  235. package/src/motion3/walk-plan.js +119 -0
  236. package/src/object-catalog.jsx +7 -1
  237. package/src/object-colors.js +98 -0
  238. package/src/object-gizmo-parts.js +115 -0
  239. package/src/object-gizmo.jsx +37 -122
  240. package/src/object-glow.js +139 -0
  241. package/src/object-hidden.js +33 -0
  242. package/src/object-look.js +196 -0
  243. package/src/object-size.js +10 -0
  244. package/src/object-travel.js +71 -6
  245. package/src/panels/CameraPanel.jsx +7 -7
  246. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  247. package/src/panels/LightPanel.jsx +25 -4
  248. package/src/panels/ObjectLookPanel.jsx +88 -0
  249. package/src/panels/PosePanel.jsx +2 -17
  250. package/src/panels/RigControlPanel.jsx +5 -53
  251. package/src/panels/SubjectBox.jsx +4 -1
  252. package/src/panels/details.css +14 -0
  253. package/src/posestudio.jsx +4 -27
  254. package/src/project-browser.jsx +3 -4
  255. package/src/project-resources.js +18 -31
  256. package/src/project.js +13 -104
  257. package/src/props.jsx +25 -280
  258. package/src/qa-label.js +25 -0
  259. package/src/rail-dot-drag.js +23 -0
  260. package/src/render-passes.js +10 -0
  261. package/src/resource-status.jsx +1 -0
  262. package/src/result-modal.jsx +4 -19
  263. package/src/room.jsx +20 -12
  264. package/src/scene-assets.js +8 -6
  265. package/src/scene-light-place.js +72 -0
  266. package/src/scene-lights.jsx +148 -0
  267. package/src/scene-objects.js +31 -139
  268. package/src/scenes.js +15 -105
  269. package/src/semantic-edit.js +2 -5
  270. package/src/set-pieces.jsx +183 -0
  271. package/src/settings-menu.jsx +3 -11
  272. package/src/shell/BottomDock.jsx +9 -51
  273. package/src/shell/DetailsSlot.jsx +16 -200
  274. package/src/shell/GhostsMenuGroup.jsx +74 -0
  275. package/src/shell/LibrarySlot.jsx +1 -3
  276. package/src/shell/MenuBar.jsx +3 -3
  277. package/src/shell/PreferencesDialog.jsx +15 -264
  278. package/src/shell/PreferencesSlot.jsx +2 -8
  279. package/src/shell/TopBar.jsx +5 -84
  280. package/src/shell/ViewportToolbar.jsx +10 -70
  281. package/src/shell/agent-glass.css +19 -6
  282. package/src/shell/details-selection.js +42 -0
  283. package/src/shell/ghosts-menu.css +22 -0
  284. package/src/shell/glass-regions.css +29 -3
  285. package/src/shell/preferences.css +6 -1
  286. package/src/shot-authoring.js +4 -7
  287. package/src/soft-shadows.js +37 -0
  288. package/src/stage-attach-frames.js +12 -12
  289. package/src/stage-canvas-rigs.jsx +3 -3
  290. package/src/stage-capture.jsx +42 -8
  291. package/src/stage-characters.jsx +22 -216
  292. package/src/stage-editor-gizmos.jsx +6 -6
  293. package/src/stage-environment.js +93 -0
  294. package/src/stage-hdr-builtins.js +107 -0
  295. package/src/stage-hdr-image.js +116 -0
  296. package/src/stage-hdr-library.js +70 -0
  297. package/src/stage-hdr-map.js +94 -0
  298. package/src/stage-hdr.js +130 -0
  299. package/src/stage-lighting.js +149 -0
  300. package/src/stage-occlusion.js +210 -0
  301. package/src/stage-sky.jsx +159 -0
  302. package/src/stage-startup-defaults.js +5 -9
  303. package/src/studio-actions.js +16 -14
  304. package/src/studio-agent-commands.js +19 -16
  305. package/src/studio-agent-motion.js +9 -9
  306. package/src/studio-agent-protocol.js +47 -77
  307. package/src/studio-app-binding.js +27 -47
  308. package/src/studio-asset-list.js +17 -0
  309. package/src/studio-contact-sheet.js +37 -5
  310. package/src/studio-elements.js +27 -7
  311. package/src/studio-entity-bounds.js +18 -0
  312. package/src/studio-error-codes.js +15 -0
  313. package/src/studio-export-framing.js +152 -0
  314. package/src/studio-lighting-check.js +103 -0
  315. package/src/studio-motion-visual.js +150 -0
  316. package/src/studio-schema-errors.js +80 -0
  317. package/src/studio-support-gap.js +74 -0
  318. package/src/styles.css +1 -19
  319. package/src/timeline-defaults.js +5 -0
  320. package/src/timeline-extent.js +8 -20
  321. package/src/tutorial-walk.js +17 -0
  322. package/src/ui-scrub.js +1 -1
  323. package/src/use-shot-camera-tracks.js +12 -0
  324. package/src/view-ghosts.js +97 -0
  325. package/src/view-ghosts.jsx +49 -0
  326. package/src/workflow/AgentPanel.jsx +47 -166
  327. package/src/workflow/AgentProgressCard.jsx +109 -0
  328. package/src/workflow/AgentReplyText.jsx +37 -0
  329. package/src/workflow/AgentTurnSteps.jsx +84 -0
  330. package/src/workflow/LoginPanel.jsx +140 -0
  331. package/src/workflow/WorkflowBuilder.jsx +7 -23
  332. package/src/workflow/agent-client.js +54 -51
  333. package/src/workflow/agent-panel.css +71 -1
  334. package/src/workflow/agent-progress-card.css +167 -0
  335. package/src/workflow/agent-progress-view.js +47 -0
  336. package/src/workflow/agent-turn-steps.css +113 -0
  337. package/src/workflow/agent-turn.js +191 -0
  338. package/src/workflow/login-client.js +34 -0
  339. package/src/workflow/motion-input.js +18 -31
  340. package/src/workflow/node-schema.js +1 -1
  341. package/src/workflow/reply-markdown.js +69 -0
  342. package/src/workflow/scene-asset-sync.js +8 -8
  343. package/tools/agent-session-stats.mjs +61 -0
  344. package/tools/dev-full.mjs +5 -56
  345. package/tools/eslint-baseline.json +12 -13
  346. package/tools/knip-baseline.json +0 -179
  347. package/tools/loc-baseline.json +31 -81
  348. package/tools/motion3/extract-body.mjs +103 -0
  349. package/tools/motion3/extract-rig.mjs +85 -0
  350. package/tools/process-supervisor.mjs +0 -88
  351. package/tools/run-tests.mjs +9 -125
  352. package/bin/agent/motion-runtime.mjs +0 -76
  353. package/dist/ardy/cskel27-rest.json +0 -557
  354. package/dist/assets/analytics-C9pMpW7s.js +0 -1
  355. package/dist/assets/app-B_2XB9rw.css +0 -1
  356. package/dist/assets/app-DAuvKPP8.js +0 -4920
  357. package/dist/assets/config-Bx7pYsD9.js +0 -1
  358. package/dist/assets/config-DeoFTYzb.css +0 -1
  359. package/dist/assets/demo-CvKuBgy5.js +0 -1
  360. package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
  361. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  362. package/dist/assets/ticket-BPk2Ni95.js +0 -1
  363. package/dist/assets/workflow-jJRBR94Z.js +0 -179
  364. package/dist/demo/walk-then-stop.npz +0 -0
  365. package/mcp/ardy-prompts.mjs +0 -218
  366. package/src/ardy/auto-fix-panel.css +0 -108
  367. package/src/ardy/auto-physics.js +0 -1194
  368. package/src/ardy/client.js +0 -153
  369. package/src/ardy/convert.js +0 -313
  370. package/src/ardy/cskel27-neutral.js +0 -39
  371. package/src/ardy/cskel27.js +0 -77
  372. package/src/ardy/export.js +0 -165
  373. package/src/ardy/fix-collisions.js +0 -2257
  374. package/src/ardy/foot-lock.js +0 -230
  375. package/src/ardy/motion-calibration.js +0 -118
  376. package/src/ardy/motion-edit.js +0 -213
  377. package/src/ardy/motion-url.js +0 -55
  378. package/src/ardy/mp4-duration.js +0 -102
  379. package/src/ardy/npz.js +0 -595
  380. package/src/ardy/physics-panel.css +0 -21
  381. package/src/ardy/physics-panel.jsx +0 -89
  382. package/src/ardy/physics-refine.js +0 -111
  383. package/src/ardy/physics-review.js +0 -567
  384. package/src/ardy/physics-temporal.js +0 -54
  385. package/src/ardy/platform-fit-panel.css +0 -64
  386. package/src/ardy/platform-fit-panel.jsx +0 -41
  387. package/src/ardy/platform-fit.js +0 -326
  388. package/src/ardy/playback.js +0 -711
  389. package/src/ardy/pose-pin.js +0 -97
  390. package/src/ardy/prompt-clips.js +0 -30
  391. package/src/ardy/retime.js +0 -211
  392. package/src/ardy/root-drop.js +0 -369
  393. package/src/ardy/timeline-resize.js +0 -13
  394. package/src/ardy/to-cskel27.js +0 -225
  395. package/src/ardy/trim.js +0 -33
  396. package/src/ardy/waypoints.js +0 -369
  397. package/src/ardy-bridge-contract.js +0 -281
  398. package/src/domains/motion/motion-video-capture.js +0 -664
  399. package/src/i2v-motion-client.js +0 -77
  400. package/src/i2v-motion-studio.jsx +0 -180
  401. package/src/line-edit.js +0 -1348
  402. package/src/motion/generation.js +0 -105
  403. package/src/motion-readiness-ui.jsx +0 -67
  404. package/src/motion-readiness.js +0 -40
  405. package/src/motion-resources.js +0 -177
  406. package/src/motion-store.js +0 -65
  407. package/src/motion-trail.js +0 -601
  408. package/src/multimodel-ingest.js +0 -394
  409. package/src/panels/PromptBlocksPanel.jsx +0 -444
  410. package/src/panels/VideoCapturePanel.jsx +0 -181
  411. package/src/pose-extract/browser.js +0 -42
  412. package/src/pose-extract/detector.js +0 -100
  413. package/src/pose-extract/fit.js +0 -227
  414. package/src/pose-extract/image-frame.js +0 -51
  415. package/src/pose-extract/index.js +0 -58
  416. package/src/pose-extract/keyframes.js +0 -74
  417. package/src/pose-extract/landmarks.js +0 -162
  418. package/src/pose-extract/mirror.js +0 -153
  419. package/src/pose-extract/take.js +0 -131
  420. package/src/pose-extract/video-frames.js +0 -91
  421. package/src/take-recipe.js +0 -165
  422. package/src/trail-key-conflicts.js +0 -39
  423. package/src/trail-pick.js +0 -59
  424. package/src/use-i2v-capture-modal.js +0 -15
  425. package/tools/ardy/BRIDGE.md +0 -37
  426. package/tools/ardy/GVHMR-NPZ.md +0 -68
  427. package/tools/ardy/README.md +0 -124
  428. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  429. package/tools/ardy/artifacts.mjs +0 -34
  430. package/tools/ardy/bridge.mjs +0 -1732
  431. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  432. package/tools/ardy/cclay_constrained_generate.py +0 -561
  433. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  434. package/tools/ardy/cclay_motion_edit.py +0 -510
  435. package/tools/ardy/cclay_sequence_generate.py +0 -705
  436. package/tools/ardy/cclay_worker.py +0 -304
  437. package/tools/ardy/dump-npz.py +0 -205
  438. package/tools/ardy/extract-rest.mjs +0 -299
  439. package/tools/ardy/extract.mjs +0 -507
  440. package/tools/ardy/footage.mjs +0 -467
  441. package/tools/ardy/gvhmr-floor.mjs +0 -102
  442. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  443. package/tools/ardy/gvhmr_fastpath.py +0 -168
  444. package/tools/ardy/gvhmr_trajectory.py +0 -240
  445. package/tools/ardy/merge-text-encoder.py +0 -160
  446. package/tools/ardy/mocap-metrics.mjs +0 -200
  447. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  448. package/tools/ardy/motion-stabilize.mjs +0 -348
  449. package/tools/ardy/npz.mjs +0 -414
  450. package/tools/ardy/pose-to-npz.mjs +0 -106
  451. package/tools/ardy/prompt-limits.mjs +0 -5
  452. package/tools/ardy/run-edit-on-box.sh +0 -73
  453. package/tools/ardy/run-local.mjs +0 -233
  454. package/tools/ardy/run-on-box.sh +0 -593
  455. package/tools/ardy/run-sequence-on-box.sh +0 -177
  456. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  457. package/tools/ardy/runners/index.mjs +0 -18
  458. package/tools/ardy/runners/local.mjs +0 -455
  459. package/tools/ardy/runners/proc.mjs +0 -125
  460. package/tools/ardy/runners/remote.mjs +0 -252
  461. package/tools/ardy/setup-local.mjs +0 -189
  462. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  463. package/tools/ardy/setup-text-encoder.py +0 -306
  464. package/tools/ardy/smpl-cskel27.mjs +0 -178
  465. package/tools/ardy/visual-qa.mjs +0 -196
  466. package/tools/ardy/vq-car.mjs +0 -52
  467. package/tools/bench/EXP3.md +0 -61
  468. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  469. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  470. package/tools/bench/cclay_bench_runner.py +0 -42
  471. package/tools/bench/cube-contact.mjs +0 -162
  472. package/tools/bench/exp3.mjs +0 -152
  473. package/tools/bench/extract-bench-lib.mjs +0 -216
  474. package/tools/bench/extract-bench.mjs +0 -302
  475. package/tools/bench/fal-generate.mjs +0 -88
  476. package/tools/bench/fit/README.md +0 -189
  477. package/tools/bench/fit/camera.mjs +0 -59
  478. package/tools/bench/fit/contact.mjs +0 -419
  479. package/tools/bench/fit/footlock.mjs +0 -234
  480. package/tools/bench/fit/motion.mjs +0 -85
  481. package/tools/bench/fit/pin.mjs +0 -27
  482. package/tools/bench/fit/remote.mjs +0 -105
  483. package/tools/bench/fit-bench.mjs +0 -126
  484. package/tools/bench/fit-sanity.mjs +0 -45
  485. package/tools/bench/metrics.mjs +0 -313
  486. package/tools/bench/obs/depth.mjs +0 -258
  487. package/tools/bench/obs/extrinsics.mjs +0 -175
  488. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  489. package/tools/bench/obs/ground.mjs +0 -294
  490. package/tools/bench/obs/heading.mjs +0 -151
  491. package/tools/bench/obs/ladder.mjs +0 -555
  492. package/tools/bench/obs/mannequin-betas.json +0 -124
  493. package/tools/bench/obs/remote.mjs +0 -83
  494. package/tools/bench/obs/rest_joints.py +0 -52
  495. package/tools/bench/obs/ybot-targets.mjs +0 -49
  496. package/tools/bench/obs-bench.mjs +0 -520
  497. package/tools/bench/score.mjs +0 -343
  498. package/tools/bench/summarize.mjs +0 -86
  499. package/tools/demo-worker/README.md +0 -137
  500. package/tools/demo-worker/api-client.mjs +0 -233
  501. package/tools/demo-worker/generate.mjs +0 -301
  502. package/tools/demo-worker/index.mjs +0 -466
  503. package/tools/demo-worker/sign-core.mjs +0 -112
  504. package/tools/demo-worker/sign.mjs +0 -161
  505. package/tools/gt-render/browser.mjs +0 -182
  506. package/tools/gt-render/camera-math.mjs +0 -276
  507. package/tools/gt-render/page.mjs +0 -267
  508. package/tools/gt-render/render.mjs +0 -514
  509. package/tools/gt-render/scene-box.mjs +0 -31
  510. package/tools/gt-render/take-transform.mjs +0 -87
  511. package/tools/kimodo/compare-seam.mjs +0 -108
  512. package/tools/kimodo/constraints.mjs +0 -244
  513. package/tools/kimodo/edit.mjs +0 -259
  514. package/tools/kimodo/effector-constraints.mjs +0 -200
  515. package/tools/kimodo/generate.mjs +0 -646
  516. package/tools/kimodo/local-output.mjs +0 -265
  517. package/tools/kimodo/measure-pose.mjs +0 -65
  518. package/tools/kimodo/measure-preserve.mjs +0 -899
  519. package/tools/kimodo/measure-waypoints.mjs +0 -55
  520. package/tools/kimodo/mlx-generate.py +0 -111
  521. package/tools/kimodo/pose-constraints.mjs +0 -248
  522. package/tools/kimodo/preserve-mask.mjs +0 -543
  523. package/tools/kimodo/read-npz.mjs +0 -189
  524. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  525. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  526. package/tools/kimodo/runner.mjs +0 -284
  527. package/tools/kimodo/seam-batch.mjs +0 -77
  528. package/tools/kimodo/setup-local.mjs +0 -30
  529. package/tools/kimodo/setup-on-box.sh +0 -339
  530. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  531. package/tools/morphgs/assets/gen_truth.py +0 -39
  532. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  533. package/tools/morphgs/assets/playback-check.mjs +0 -30
  534. package/tools/morphgs/demo-gate.sh +0 -26
  535. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  536. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  537. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  538. package/tools/morphgs/setup-on-cluster.sh +0 -126
  539. package/tools/projflow/driver.py +0 -1069
  540. package/tools/projflow/generate.mjs +0 -805
  541. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  542. package/tools/projflow/line-edit-job.mjs +0 -547
  543. package/tools/projflow/replay.mjs +0 -452
  544. package/tools/projflow/runner.mjs +0 -269
  545. package/tools/projflow/service.mjs +0 -742
  546. package/tools/track/DESIGN.md +0 -513
  547. package/tools/track/backfill-provenance.mjs +0 -116
  548. package/tools/track/budget.mjs +0 -8
  549. package/tools/track/check-rig.mjs +0 -209
  550. package/tools/track/diagnostics.schema.json +0 -60
  551. package/tools/track/export-rig.mjs +0 -256
  552. package/tools/track/fallback.mjs +0 -7
  553. package/tools/track/fk-parity-fixture.mjs +0 -161
  554. package/tools/track/gate.mjs +0 -349
  555. package/tools/track/masks.mjs +0 -318
  556. package/tools/track/metrics.mjs +0 -201
  557. package/tools/track/publish-obs.mjs +0 -108
  558. package/tools/track/py/check_env.py +0 -61
  559. package/tools/track/py/eval_lr.py +0 -229
  560. package/tools/track/py/lr_viterbi.py +0 -294
  561. package/tools/track/py/masks.py +0 -405
  562. package/tools/track/py/objective.py +0 -382
  563. package/tools/track/py/rig.py +0 -508
  564. package/tools/track/py/scene.py +0 -326
  565. package/tools/track/py/test_joint_indices.py +0 -123
  566. package/tools/track/py/test_lr_viterbi.py +0 -195
  567. package/tools/track/py/test_masks.py +0 -229
  568. package/tools/track/py/test_rig.py +0 -288
  569. package/tools/track/py/test_scene.py +0 -354
  570. package/tools/track/py/test_track.py +0 -328
  571. package/tools/track/py/track.py +0 -411
  572. package/tools/track/remote.mjs +0 -140
  573. package/tools/track/rig-dump.mjs +0 -227
  574. package/tools/track/run-box-tests.mjs +0 -14
  575. package/tools/track/run-box.mjs +0 -168
  576. package/tools/track/setup-box.sh +0 -59
  577. package/tools/track/study-2d.mjs +0 -728
  578. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  579. /package/src/{ardy → motion}/ground.js +0 -0
  580. /package/src/{ardy → motion}/ik-drag.js +0 -0
  581. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  582. /package/src/{ardy → motion}/ik.js +0 -0
  583. /package/src/{ardy → motion}/physics-surface.js +0 -0
  584. /package/src/{ardy → motion}/playback-clock.js +0 -0
  585. /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,109 @@
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 { draftLabel } from "../draft-ghost.js";
12
+ import { qaProblems } from "../qa-label.js";
13
+ import { goalRows, issueRows, trendPoints, turnCount } from "./agent-progress-view.js";
14
+ import "./agent-progress-card.css";
15
+
16
+ const W = 220, H = 40;
17
+
18
+ function Trend({ totals }) {
19
+ const { points, first, last } = trendPoints(totals, { width: W, height: H });
20
+ if (!points.length) return null;
21
+ const [lx, ly] = points[points.length - 1];
22
+ const label = ko(`Problems by draft: ${totals.join(", ")}`, `초안별 문제 수: ${totals.join(", ")}`);
23
+ return <div className="agent-progress-trend">
24
+ <svg viewBox={`0 0 ${W} ${H}`} role="img" aria-label={label} preserveAspectRatio="none">
25
+ <line className="base" x1={4} y1={H - 4} x2={W - 4} y2={H - 4} />
26
+ {points.length > 1 && <polyline className="line" points={points.map(p => p.join(",")).join(" ")} />}
27
+ {points.slice(0, -1).map(([x, y], i) => <circle key={i} className="dot" cx={x} cy={y} r={1.8} />)}
28
+ <circle className={`end ${last === 0 ? "ok" : "issue"}`} cx={lx} cy={ly} r={3.4} />
29
+ </svg>
30
+ <span className="agent-progress-trend-text">{totals.length > 1 ? `${first} → ${last}` : `${last}`}</span>
31
+ </div>;
32
+ }
33
+
34
+ // The panel's own lightbox stays inside the panel; a contact sheet needs the window.
35
+ function SheetViewer({ sheet, onClose }) {
36
+ useEffect(() => {
37
+ const onKey = event => { if (event.key === "Escape") onClose(); };
38
+ window.addEventListener("keydown", onKey);
39
+ return () => window.removeEventListener("keydown", onKey);
40
+ }, [onClose]);
41
+ return createPortal(<button type="button" className="agent-progress-viewer" aria-label={ko("Close image", "이미지 닫기")} onClick={onClose}>
42
+ <img src={sheet.dataUrl} alt={sheet.caption || ko("Image the agent checked", "에이전트가 확인한 이미지")} />
43
+ {sheet.caption && <span className="agent-progress-viewer-caption">{sheet.caption}</span>}
44
+ </button>, document.body);
45
+ }
46
+
47
+ /** `since`: when the latest turn started (null: no turn yet). Drafts and images from before it are not this turn's
48
+ * work, such as the take the camera tutorial compiles at startup. `live`: that turn is still running. */
49
+ export function AgentProgressCard({ items, since = null, live = false }) {
50
+ useSyncExternalStore(subscribeProgress, progressVersion);
51
+ const [open, setOpen] = useState(null);
52
+ // The goal and issue lists fold to one line each: the panel is short and the live steps sit below the card.
53
+ const [shown, setShown] = useState({});
54
+ const toggle = key => setShown(now => ({ ...now, [key]: !now[key] }));
55
+ // a new turn (not a steer of the running one) starts the card over
56
+ const turns = turnCount(items), seen = useRef(turns);
57
+ useEffect(() => {
58
+ if (turns > seen.current) clearProgress();
59
+ seen.current = turns;
60
+ }, [turns]);
61
+ const ours = record => since !== null && record.at >= since;
62
+ const newest = latestDraft(), latest = newest && ours(newest) ? newest : null, sheets = progressSheets().filter(ours);
63
+ const viewing = open && sheets.find(sheet => sheet.seq === open);
64
+ if (!latest && !sheets.length) return null;
65
+ const drafts = latest ? draftsOf(latest.characterId).filter(ours) : [];
66
+ const goals = goalRows(latest?.qa.goals), issues = latest && !latest.qa.ok ? issueRows(latest.qa.groups) : [];
67
+ return <div className="agent-row">
68
+ <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">
69
+ {latest && <div className="agent-progress-head">
70
+ <span className="agent-progress-title" title={ko(`${latest.qa.total} frame-level issues`, `프레임 단위 문제 ${latest.qa.total}건`)}>{draftLabel(latest)}</span>
71
+ <span className={`agent-progress-chip ${latest.installed ? "installed" : "draft"}`}>{latest.installed ? ko("Installed", "설치됨") : live ? ko("Not installed yet", "아직 설치 전") : ko("Not installed", "설치 안 됨")}</span>
72
+ </div>}
73
+ {latest?.label && <p className="agent-progress-label">{latest.label}</p>}
74
+ {drafts.length > 0 && <section className="agent-progress-section">
75
+ <h4>{ko(`Problems across ${drafts.length} draft${drafts.length === 1 ? "" : "s"}`, `초안 ${drafts.length}개의 문제 수`)}</h4>
76
+ <Trend totals={drafts.map(d => qaProblems(d.qa))} />
77
+ </section>}
78
+ {goals.length > 0 && <section className="agent-progress-section">
79
+ <button type="button" className="agent-progress-fold" aria-expanded={!!shown.goals} onClick={() => toggle("goals")}>
80
+ <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>
81
+ </button>
82
+ {shown.goals && <ul className="agent-progress-goals">
83
+ {goals.map((g, i) => <li key={i} data-goal-ok={g.ok ? "true" : "false"}>
84
+ <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>
85
+ <span className="agent-progress-goal-name">{g.name}</span>
86
+ <span className="agent-progress-goal-value">{g.value}{g.need && <span className="need"> / {g.need}</span>}</span>
87
+ </li>)}
88
+ </ul>}
89
+ </section>}
90
+ {issues.length > 0 && <section className="agent-progress-section">
91
+ <button type="button" className="agent-progress-fold" aria-expanded={!!shown.issues} onClick={() => toggle("issues")}>
92
+ <h4>{ko("Largest issues", "가장 큰 문제")}</h4><span>{issues[0].kind} {issues[0].subject}</span>
93
+ </button>
94
+ {shown.issues && <ul className="agent-progress-issues">
95
+ {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>)}
96
+ </ul>}
97
+ </section>}
98
+ {sheets.length > 0 && <section className="agent-progress-section">
99
+ <h4>{ko("What the agent looked at", "에이전트가 본 화면")}</h4>
100
+ <div className="agent-progress-sheets">
101
+ {sheets.map(sheet => <button type="button" key={sheet.seq} className="agent-progress-sheet" title={sheet.caption} onClick={() => setOpen(sheet.seq)}>
102
+ <img src={sheet.dataUrl} alt={sheet.caption || ko("Image the agent checked", "에이전트가 확인한 이미지")} />
103
+ </button>)}
104
+ </div>
105
+ </section>}
106
+ </div>
107
+ {viewing && <SheetViewer sheet={viewing} onClose={() => setOpen(null)} />}
108
+ </div>;
109
+ }
@@ -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
+ }