synabun 2.1.0 → 2.2.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 (317) hide show
  1. package/CHANGELOG.md +121 -0
  2. package/README.md +1 -1
  3. package/docs/whatsapp.md +11 -11
  4. package/lib/client-config-repair.js +13 -9
  5. package/lib/external-tools.js +6 -5
  6. package/lib/update-snapshot.js +77 -3
  7. package/lib/virustotal.mjs +12 -1
  8. package/mcp-server/dist/index.js +1 -1
  9. package/mcp-server/dist/index.js.map +1 -1
  10. package/mcp-server/dist/services/budget-judgments.d.ts +1 -1
  11. package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -1
  12. package/mcp-server/dist/services/budget-judgments.js +5 -3
  13. package/mcp-server/dist/services/budget-judgments.js.map +1 -1
  14. package/mcp-server/dist/services/identity.d.ts +2 -2
  15. package/mcp-server/dist/services/identity.js +2 -2
  16. package/mcp-server/dist/services/memory-sessions.d.ts +1 -1
  17. package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -1
  18. package/mcp-server/dist/services/memory-sessions.js +28 -2
  19. package/mcp-server/dist/services/memory-sessions.js.map +1 -1
  20. package/mcp-server/dist/services/neural-interface.d.ts +2 -1
  21. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  22. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  23. package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
  24. package/mcp-server/dist/services/server-instructions.js +2 -1
  25. package/mcp-server/dist/services/server-instructions.js.map +1 -1
  26. package/mcp-server/dist/services/tool-catalog.js +1 -1
  27. package/mcp-server/dist/services/tool-catalog.js.map +1 -1
  28. package/mcp-server/dist/services/tool-list.d.ts +1 -1
  29. package/mcp-server/dist/services/tool-list.d.ts.map +1 -1
  30. package/mcp-server/dist/services/tool-runtime.d.ts +2 -2
  31. package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -1
  32. package/mcp-server/dist/services/tool-runtime.js +1 -1
  33. package/mcp-server/dist/services/tool-runtime.js.map +1 -1
  34. package/mcp-server/dist/services/typesafe-config.js +1 -1
  35. package/mcp-server/dist/services/typesafe-config.js.map +1 -1
  36. package/mcp-server/dist/tools/agents.d.ts +8 -8
  37. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  38. package/mcp-server/dist/tools/agents.js +10 -9
  39. package/mcp-server/dist/tools/agents.js.map +1 -1
  40. package/mcp-server/dist/tools/choice.d.ts +6 -0
  41. package/mcp-server/dist/tools/choice.d.ts.map +1 -1
  42. package/mcp-server/dist/tools/choice.js +18 -13
  43. package/mcp-server/dist/tools/choice.js.map +1 -1
  44. package/mcp-server/dist/tools/git-tools.js +2 -2
  45. package/mcp-server/dist/tools/git-tools.js.map +1 -1
  46. package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
  47. package/mcp-server/dist/tools/morelogin.js +9 -6
  48. package/mcp-server/dist/tools/morelogin.js.map +1 -1
  49. package/mcp-server/dist/tools/skill-tools.d.ts +8 -8
  50. package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -1
  51. package/mcp-server/dist/tools/skill-tools.js +11 -8
  52. package/mcp-server/dist/tools/skill-tools.js.map +1 -1
  53. package/mcp-server/package-lock.json +2 -2
  54. package/mcp-server/package.json +1 -1
  55. package/mcp-server/src/index.ts +1 -1
  56. package/mcp-server/src/services/budget-judgments.ts +5 -3
  57. package/mcp-server/src/services/identity.ts +2 -2
  58. package/mcp-server/src/services/memory-sessions.ts +24 -3
  59. package/mcp-server/src/services/neural-interface.ts +2 -1
  60. package/mcp-server/src/services/server-instructions.ts +2 -1
  61. package/mcp-server/src/services/tool-catalog.ts +1 -1
  62. package/mcp-server/src/services/tool-list.ts +1 -1
  63. package/mcp-server/src/services/tool-runtime.ts +2 -2
  64. package/mcp-server/src/services/typesafe-config.ts +1 -1
  65. package/mcp-server/src/tools/agents.ts +10 -9
  66. package/mcp-server/src/tools/choice.ts +17 -12
  67. package/mcp-server/src/tools/git-tools.ts +2 -2
  68. package/mcp-server/src/tools/morelogin.ts +6 -4
  69. package/mcp-server/src/tools/skill-tools.ts +11 -8
  70. package/neural-interface/i18n/en.json +501 -39
  71. package/neural-interface/i18n/pt-BR.json +501 -39
  72. package/neural-interface/lib/agent-sandbox-elevation.js +6 -0
  73. package/neural-interface/lib/antigravity/acp-client-host.js +554 -0
  74. package/neural-interface/lib/antigravity/acp-connection.js +299 -0
  75. package/neural-interface/lib/antigravity/bypass.js +100 -0
  76. package/neural-interface/lib/antigravity/catalog.js +273 -0
  77. package/neural-interface/lib/antigravity/home.js +260 -0
  78. package/neural-interface/lib/antigravity/migrate-gemini.js +139 -0
  79. package/neural-interface/lib/antigravity/panel-bridge.js +1058 -0
  80. package/neural-interface/lib/antigravity/policy-hook.mjs +78 -0
  81. package/neural-interface/lib/antigravity/policy.js +74 -0
  82. package/neural-interface/lib/antigravity/tool-call.js +178 -0
  83. package/neural-interface/lib/antigravity/transcripts.js +252 -0
  84. package/neural-interface/lib/antigravity/usage.js +43 -0
  85. package/neural-interface/lib/assistant-api.js +1 -0
  86. package/neural-interface/lib/assistant-brains/antigravity.js +423 -0
  87. package/neural-interface/lib/assistant-budget-planner.js +7 -3
  88. package/neural-interface/lib/assistant-budget-split.js +42 -15
  89. package/neural-interface/lib/assistant-catalog.js +76 -9
  90. package/neural-interface/lib/assistant-config.js +6 -3
  91. package/neural-interface/lib/assistant-dispatch.js +207 -49
  92. package/neural-interface/lib/assistant-envelope.js +153 -1
  93. package/neural-interface/lib/assistant-evidence.js +80 -0
  94. package/neural-interface/lib/assistant-hidden-models.js +7 -5
  95. package/neural-interface/lib/assistant-persona.js +16 -11
  96. package/neural-interface/lib/assistant-plan-permissions.js +15 -0
  97. package/neural-interface/lib/assistant-router.js +237 -35
  98. package/neural-interface/lib/assistant-runtime.js +158 -12
  99. package/neural-interface/lib/assistant-task-prompt.js +10 -8
  100. package/neural-interface/lib/assistant-tools.js +2 -2
  101. package/neural-interface/lib/assistant-usage.js +29 -0
  102. package/neural-interface/lib/backup-service.js +6 -1
  103. package/neural-interface/lib/browser-guard-cdp.js +5 -4
  104. package/neural-interface/lib/browser-network-guard.js +274 -82
  105. package/neural-interface/lib/browser-tool-policy.js +12 -7
  106. package/neural-interface/lib/claude-agent-bridge.js +7 -0
  107. package/neural-interface/lib/claude-history-cache.js +210 -0
  108. package/neural-interface/lib/claude-model-catalog.js +5 -1
  109. package/neural-interface/lib/cli-install-api.js +1 -1
  110. package/neural-interface/lib/cli-install.js +160 -31
  111. package/neural-interface/lib/cli-resolver.js +9 -2
  112. package/neural-interface/lib/cli-update-planner.js +25 -15
  113. package/neural-interface/lib/codex-rollout-reader.js +52 -0
  114. package/neural-interface/lib/codex-worker-app-server.js +25 -6
  115. package/neural-interface/lib/editor-git-api.js +3 -20
  116. package/neural-interface/lib/exec-loop-command.js +9 -2
  117. package/neural-interface/lib/file-explorer-api.js +74 -40
  118. package/neural-interface/lib/file-explorer-watch.js +9 -3
  119. package/neural-interface/lib/git-bash/install.js +52 -6
  120. package/neural-interface/lib/git-bash/manager.js +59 -17
  121. package/neural-interface/lib/git-bash/pins.js +12 -0
  122. package/neural-interface/lib/git-read.js +2 -2
  123. package/neural-interface/lib/git-status-runner.js +390 -0
  124. package/neural-interface/lib/git-status-watchdog.js +40 -0
  125. package/neural-interface/lib/greeting/budgets.js +1 -0
  126. package/neural-interface/lib/greeting/engine.js +2 -2
  127. package/neural-interface/lib/greeting/sources.js +10 -12
  128. package/neural-interface/lib/greeting/writer.js +1 -1
  129. package/neural-interface/lib/mcp-client-config.js +37 -6
  130. package/neural-interface/lib/mcp-external-servers.js +13 -4
  131. package/neural-interface/lib/mcp-installer.js +2 -1
  132. package/neural-interface/lib/memory-session-capture.js +17 -3
  133. package/neural-interface/lib/morelogin.js +147 -25
  134. package/neural-interface/lib/native-loop-providers.js +420 -21
  135. package/neural-interface/lib/native-loop-runtime.js +23 -2
  136. package/neural-interface/lib/opencode-native-boundary.js +11 -2
  137. package/neural-interface/lib/owner-authority.js +1 -1
  138. package/neural-interface/lib/path-confine.js +12 -9
  139. package/neural-interface/lib/peer-identity.js +32 -2
  140. package/neural-interface/lib/pty-host/core.js +28 -1
  141. package/neural-interface/lib/pty-host/lifetime.js +12 -0
  142. package/neural-interface/lib/pty-host/manager.js +12 -1
  143. package/neural-interface/lib/remote-policy.js +1 -1
  144. package/neural-interface/lib/request-gate.js +3 -0
  145. package/neural-interface/lib/rulesets/api.js +6 -5
  146. package/neural-interface/lib/rulesets/fs-safe.js +1 -1
  147. package/neural-interface/lib/rulesets/installer.js +89 -10
  148. package/neural-interface/lib/rulesets/render.js +1 -1
  149. package/neural-interface/lib/session-cache-builder.js +64 -1
  150. package/neural-interface/lib/skills/catalog.js +5 -1
  151. package/neural-interface/lib/skills/creation-brief.js +1 -1
  152. package/neural-interface/lib/skills/creation-sources.js +19 -1
  153. package/neural-interface/lib/skills/deploy.js +9 -4
  154. package/neural-interface/lib/skills/draft-confinement.js +3 -1
  155. package/neural-interface/lib/skills/eval-confinement.js +3 -1
  156. package/neural-interface/lib/skills/eval-trace.js +16 -0
  157. package/neural-interface/lib/skills/export.js +5 -2
  158. package/neural-interface/lib/skills/known-bridge-digests.json +58 -4
  159. package/neural-interface/lib/skills/ownership.js +1 -1
  160. package/neural-interface/lib/skills/roots.js +10 -3
  161. package/neural-interface/lib/skills/runtime-checks.js +6 -3
  162. package/neural-interface/lib/skills/service.js +15 -7
  163. package/neural-interface/lib/skills/telemetry.js +6 -0
  164. package/neural-interface/lib/synabun-version.js +1 -1
  165. package/neural-interface/lib/system-activity.js +1 -1
  166. package/neural-interface/lib/whatsapp/brain.js +7 -3
  167. package/neural-interface/lib/whatsapp/cards.js +22 -9
  168. package/neural-interface/lib/whatsapp/config.js +1 -1
  169. package/neural-interface/lib/worker-confinement.js +77 -1
  170. package/neural-interface/lib/worker-question.js +74 -0
  171. package/neural-interface/lib/worker-resume.js +13 -4
  172. package/neural-interface/lib/worker-sandbox.js +46 -16
  173. package/neural-interface/package-lock.json +18 -2
  174. package/neural-interface/package.json +2 -1
  175. package/neural-interface/public/onboarding.html +1 -1
  176. package/neural-interface/public/shared/agent-runtime-options.js +89 -11
  177. package/neural-interface/public/shared/agy/agy-bypass.js +47 -0
  178. package/neural-interface/public/shared/agy/agy-client.js +266 -0
  179. package/neural-interface/public/shared/agy/agy-commands.js +120 -0
  180. package/neural-interface/public/shared/agy/agy-copy.js +326 -0
  181. package/neural-interface/public/shared/agy/agy-diff.js +161 -0
  182. package/neural-interface/public/shared/agy/agy-events.js +469 -0
  183. package/neural-interface/public/shared/agy/agy-greeting.js +26 -0
  184. package/neural-interface/public/shared/agy/agy-options.js +232 -0
  185. package/neural-interface/public/shared/agy/agy-panel.js +2543 -0
  186. package/neural-interface/public/shared/agy/agy-render.js +392 -0
  187. package/neural-interface/public/shared/agy/agy-setup.js +149 -0
  188. package/neural-interface/public/shared/agy/agy-styles.js +604 -0
  189. package/neural-interface/public/shared/agy/agy-tabs-logic.js +181 -0
  190. package/neural-interface/public/shared/api.js +4 -4
  191. package/neural-interface/public/shared/assistant/asst-agents-dock.js +3 -4
  192. package/neural-interface/public/shared/assistant/asst-api.js +1 -1
  193. package/neural-interface/public/shared/assistant/asst-brain-picker.js +56 -9
  194. package/neural-interface/public/shared/assistant/asst-budget.js +5 -4
  195. package/neural-interface/public/shared/assistant/asst-control.js +48 -0
  196. package/neural-interface/public/shared/assistant/asst-hosts.js +16 -7
  197. package/neural-interface/public/shared/assistant/asst-limit-dock.js +6 -1
  198. package/neural-interface/public/shared/assistant/asst-limit-wait.js +38 -5
  199. package/neural-interface/public/shared/assistant/asst-panel.js +45 -8
  200. package/neural-interface/public/shared/assistant/asst-render.js +51 -12
  201. package/neural-interface/public/shared/assistant/asst-route.js +237 -57
  202. package/neural-interface/public/shared/assistant/asst-routes-editor.js +9 -8
  203. package/neural-interface/public/shared/assistant/asst-scenes.js +54 -1
  204. package/neural-interface/public/shared/assistant/asst-sidepanel.js +51 -7
  205. package/neural-interface/public/shared/assistant/asst-slash.js +8 -2
  206. package/neural-interface/public/shared/assistant/asst-state.js +76 -9
  207. package/neural-interface/public/shared/assistant/asst-styles.js +34 -0
  208. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +34 -7
  209. package/neural-interface/public/shared/assistant/asst-usage.js +2 -1
  210. package/neural-interface/public/shared/cdx/cdx-output.js +26 -3
  211. package/neural-interface/public/shared/cdx/cdx-panel.js +14 -2
  212. package/neural-interface/public/shared/cdx/cdx-preview.js +12 -0
  213. package/neural-interface/public/shared/cdx/cdx-protocol.js +11 -10
  214. package/neural-interface/public/shared/cdx/cdx-render.js +10 -9
  215. package/neural-interface/public/shared/cdx/cdx-requests.js +11 -1
  216. package/neural-interface/public/shared/cdx/cdx-styles.js +4 -0
  217. package/neural-interface/public/shared/cdx/cdx-tabs.js +75 -5
  218. package/neural-interface/public/shared/cli-install-controller.js +218 -0
  219. package/neural-interface/public/shared/cli-install-view.js +7 -4
  220. package/neural-interface/public/shared/cli-status.js +9 -8
  221. package/neural-interface/public/shared/constants.js +3 -2
  222. package/neural-interface/public/shared/context-menu-model.js +1 -0
  223. package/neural-interface/public/shared/cp/cp-restore.js +162 -0
  224. package/neural-interface/public/shared/cp/cp-tool-cards.js +10 -3
  225. package/neural-interface/public/shared/desktop-bridge.js +17 -0
  226. package/neural-interface/public/shared/desktop-titlebar-view.js +12 -0
  227. package/neural-interface/public/shared/desktop-titlebar.js +13 -4
  228. package/neural-interface/public/shared/desktop-update-view.js +1 -0
  229. package/neural-interface/public/shared/explorer/fx-api.js +4 -4
  230. package/neural-interface/public/shared/explorer/fx-links.js +3 -3
  231. package/neural-interface/public/shared/explorer/fx-logic.js +1 -1
  232. package/neural-interface/public/shared/explorer/fx-main.js +48 -16
  233. package/neural-interface/public/shared/explorer/fx-tree.js +1 -0
  234. package/neural-interface/public/shared/greeting/greeting-card.js +2 -2
  235. package/neural-interface/public/shared/greeting/greeting-view.js +1 -0
  236. package/neural-interface/public/shared/html-shell.js +21 -4
  237. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +27 -7
  238. package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +13 -4
  239. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
  240. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +17 -0
  241. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +46 -3
  242. package/neural-interface/public/shared/provider-icons.js +4 -2
  243. package/neural-interface/public/shared/settings/settings-rules.js +2 -2
  244. package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +1 -1
  245. package/neural-interface/public/shared/settings/settings-skill-folders.js +2 -2
  246. package/neural-interface/public/shared/showcase-privacy.js +6 -1
  247. package/neural-interface/public/shared/sidepanel-session-status.js +3 -3
  248. package/neural-interface/public/shared/skills/ss-creation.js +6 -3
  249. package/neural-interface/public/shared/skills/ss-dialog.js +5 -2
  250. package/neural-interface/public/shared/skills/ss-lint.js +9 -1
  251. package/neural-interface/public/shared/skills/ss-profiles.js +44 -6
  252. package/neural-interface/public/shared/skills/ss-review-view.js +2 -2
  253. package/neural-interface/public/shared/skills/studio/sx-actions.js +1 -1
  254. package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +1 -1
  255. package/neural-interface/public/shared/skills/studio/sx-create-ai.js +2 -1
  256. package/neural-interface/public/shared/skills/studio/sx-dom.js +2 -1
  257. package/neural-interface/public/shared/skills/studio/sx-fixes-view.js +359 -0
  258. package/neural-interface/public/shared/skills/studio/sx-fixes.js +327 -0
  259. package/neural-interface/public/shared/skills/studio/sx-inspector.js +1 -1
  260. package/neural-interface/public/shared/skills/studio/sx-install-flow.js +9 -6
  261. package/neural-interface/public/shared/skills/studio/sx-install.js +20 -3
  262. package/neural-interface/public/shared/skills/studio/sx-library-view.js +144 -35
  263. package/neural-interface/public/shared/skills/studio/sx-library.js +6 -2
  264. package/neural-interface/public/shared/skills/studio/sx-main.js +11 -2
  265. package/neural-interface/public/shared/skills/studio/sx-review-view.js +1 -1
  266. package/neural-interface/public/shared/skills/studio/sx-subject.js +7 -6
  267. package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +6 -5
  268. package/neural-interface/public/shared/state.js +1 -1
  269. package/neural-interface/public/shared/streaming-markdown.js +58 -0
  270. package/neural-interface/public/shared/styles.css +354 -34
  271. package/neural-interface/public/shared/text-cache.js +29 -0
  272. package/neural-interface/public/shared/tool-list-picker.js +2 -2
  273. package/neural-interface/public/shared/tool-list-view.js +2 -2
  274. package/neural-interface/public/shared/topright-overflow-logic.js +54 -0
  275. package/neural-interface/public/shared/ui-antigravity-panel.js +19 -0
  276. package/neural-interface/public/shared/ui-assistant.js +2 -0
  277. package/neural-interface/public/shared/ui-automation-studio.js +20 -12
  278. package/neural-interface/public/shared/ui-claude-panel.js +299 -77
  279. package/neural-interface/public/shared/ui-cli-banner.js +23 -167
  280. package/neural-interface/public/shared/ui-file-explorer.js +48 -6
  281. package/neural-interface/public/shared/ui-file-links.js +2 -2
  282. package/neural-interface/public/shared/ui-games.js +2 -2
  283. package/neural-interface/public/shared/ui-judgments.js +1 -1
  284. package/neural-interface/public/shared/ui-keybinds.js +8 -2
  285. package/neural-interface/public/shared/ui-link.js +3 -3
  286. package/neural-interface/public/shared/ui-menubar.js +2 -2
  287. package/neural-interface/public/shared/ui-native-loop-router.js +4 -0
  288. package/neural-interface/public/shared/ui-navbar.js +45 -0
  289. package/neural-interface/public/shared/ui-notifications.js +1 -0
  290. package/neural-interface/public/shared/ui-resume.js +9 -7
  291. package/neural-interface/public/shared/ui-schedules-studio.js +41 -10
  292. package/neural-interface/public/shared/ui-scroll-follow.js +16 -5
  293. package/neural-interface/public/shared/ui-sessions.js +22 -12
  294. package/neural-interface/public/shared/ui-settings.js +90 -65
  295. package/neural-interface/public/shared/ui-sidepanel-session-entry.js +9 -2
  296. package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -3
  297. package/neural-interface/public/shared/ui-sidepanel-tray.js +48 -7
  298. package/neural-interface/public/shared/ui-sidepanel-windows.js +3 -3
  299. package/neural-interface/public/shared/ui-terminal.js +146 -10
  300. package/neural-interface/public/shared/ui-tool-updates.js +3 -3
  301. package/neural-interface/public/shared/ui-tooltip.js +71 -46
  302. package/neural-interface/public/shared/ui-topright-overflow.js +233 -0
  303. package/neural-interface/public/shared/ui-whiteboard.js +1 -1
  304. package/neural-interface/public/shared/ui-worker-end.js +1 -1
  305. package/neural-interface/public/shared/whatsapp/wa-view.js +2 -2
  306. package/neural-interface/public/shared/worker-questions.js +34 -0
  307. package/neural-interface/public/sidepanel-session.html +1 -1
  308. package/neural-interface/server.js +644 -426
  309. package/neural-interface/templates/rulesets/README.md +1 -1
  310. package/neural-interface/templates/rulesets/legacy-hashes.json +5 -5
  311. package/neural-interface/templates/rulesets/manifest.json +1 -1
  312. package/npm-shrinkwrap.json +18 -2
  313. package/package.json +2 -1
  314. package/skills/skill-forge/SKILL.md +4 -4
  315. package/skills/synabun/SKILL.md +1 -1
  316. package/skills/synabun/modules/schedule.md +3 -3
  317. /package/neural-interface/templates/rulesets/{gemini.md → antigravity.md} +0 -0
@@ -6,10 +6,15 @@
6
6
  // mascot dozes inside a gold ring that fills toward the reset, a live
7
7
  // hh:mm:ss countdown and the local reset time beside it. At zero it wakes
8
8
  // ("Usage limit restored") and offers Retry now. With no reset time known it
9
- // waits with "Reset time unknown" and offers the retry at once. The clock is
10
- // the shared ticker at 1 fps, only while it counts and only while the card is
11
- // on the page; under prefers-reduced-motion the mascot is a still and nothing
12
- // eases. Its sheet is its own (injectLimitWaitStyles), like the rack's.
9
+ // waits with "Reset time unknown" and offers the retry at once. While it
10
+ // blocks, a host that passes onSwitchProvider also offers "Continue with
11
+ // another provider" (the host owns the picker and what a pick does); a host
12
+ // that passes onClose always offers Close, which only dismisses the card —
13
+ // the limit itself is untouched — and Esc does the same while focus is
14
+ // inside. The clock is the shared ticker at 1 fps, only while it counts and
15
+ // only while the card is on the page; under prefers-reduced-motion the mascot
16
+ // is a still and nothing eases. Its sheet is its own (injectLimitWaitStyles),
17
+ // like the rack's.
13
18
 
14
19
  import * as mascotLib from '../synabun-mascot.js';
15
20
  import { isReducedMotion, subscribe } from '../synabun-ticker.js';
@@ -37,10 +42,12 @@ function localTime(ms, now) {
37
42
  }
38
43
 
39
44
  /**
40
- * createLimitWait({ t, resetsAt, since, provider, onRetry, retryLabel, announce(text) })
45
+ * createLimitWait({ t, resetsAt, since, provider, onRetry, retryLabel, onSwitchProvider, onClose, announce(text) })
41
46
  * → { el, get state(), update({ resetsAt, since }), retried(), stale(), destroy() }
42
47
  * `resetsAt`: epoch ms / s or ISO, or null (unknown). `since`: when the wait began (the ring's start).
43
48
  * `provider`: the name in the meta line ("Codex"). `onRetry()`: Retry now; null: no button.
49
+ * `onSwitchProvider(anchorEl)`: Continue with another provider, while blocking; null: no button.
50
+ * `onClose()`: Close (and Esc, while focus is inside); null: no button.
44
51
  */
45
52
  export function createLimitWait(opts = {}) {
46
53
  injectLimitWaitStyles();
@@ -84,6 +91,16 @@ export function createLimitWait(opts = {}) {
84
91
  const actions = $('.asst-limit-wait-actions');
85
92
  const live = $('.asst-limit-wait-live');
86
93
 
94
+ // Continue with another provider: only while actively blocking (waiting / unknown). Opens the
95
+ // host's own provider picker (never a native select); the host decides what happens on a pick.
96
+ let switchBtn = null;
97
+ if (typeof opts.onSwitchProvider === 'function') {
98
+ switchBtn = el('button', 'asst-btn asst-btn-secondary asst-limit-wait-switch', t('assistant.limitWait.switchProvider', 'Continue with another provider'));
99
+ switchBtn.type = 'button';
100
+ switchBtn.addEventListener('click', () => { if (!destroyed) opts.onSwitchProvider(switchBtn); });
101
+ actions.appendChild(switchBtn);
102
+ }
103
+
87
104
  let retryBtn = null;
88
105
  if (typeof opts.onRetry === 'function') {
89
106
  retryBtn = el('button', 'asst-btn asst-btn-primary asst-limit-wait-retry');
@@ -96,6 +113,21 @@ export function createLimitWait(opts = {}) {
96
113
  actions.appendChild(retryBtn);
97
114
  }
98
115
 
116
+ // Dismisses the card only; the limit itself is untouched (the host decides how it resurfaces).
117
+ let closeBtn = null;
118
+ if (typeof opts.onClose === 'function') {
119
+ closeBtn = el('button', 'asst-btn asst-btn-secondary asst-limit-wait-close', t('common.close', 'Close'));
120
+ closeBtn.type = 'button';
121
+ closeBtn.addEventListener('click', () => { if (!destroyed) opts.onClose(); });
122
+ actions.appendChild(closeBtn);
123
+ // Not a dialog (role="group"): Esc closes it only while focus is inside.
124
+ card.addEventListener('keydown', (e) => {
125
+ if (e.key !== 'Escape' || destroyed) return;
126
+ e.stopPropagation();
127
+ opts.onClose();
128
+ });
129
+ }
130
+
99
131
  function setPose(next) {
100
132
  if (pose === next || destroyed) return;
101
133
  pose = next;
@@ -146,6 +178,7 @@ export function createLimitWait(opts = {}) {
146
178
  : (opts.retryLabel || t('assistant.limitWait.retry', 'Retry now'));
147
179
  retryBtn.textContent = label;
148
180
  }
181
+ if (switchBtn) switchBtn.hidden = !(next === 'waiting' || next === 'unknown');
149
182
  if (next === 'waiting') { setPose('sleep'); setRing(limitCountdown({ resetsAt, since }, now).progress || 0); }
150
183
  else if (next === 'unknown') { setPose('wait'); setRing(0.18, true); }
151
184
  else if (next === 'restored') { setPose(was === 'waiting' ? 'success' : 'idle'); setRing(1); }
@@ -35,7 +35,6 @@ import { t } from '../i18n.js';
35
35
  import '../account-i18n.js';
36
36
  import { forgetGreeting, mountGreeting } from '../greeting/greeting-card.js';
37
37
  import { notify, NOTIF_TYPE } from '../ui-notifications.js';
38
- import { getProviderMeta } from '../provider-icons.js';
39
38
  import { isGuest, hasPermission } from '../ui-sync.js';
40
39
  import { getWhiteboardElementById } from '../ui-whiteboard.js';
41
40
  import { focusNativeLoopRun } from '../ui-native-loop-router.js';
@@ -51,7 +50,7 @@ import { buildPromptText, createComposer } from './asst-composer.js';
51
50
  import { buildDispatchSpec, parseSlashCommand, slashHelpMarkdown } from './asst-slash.js';
52
51
  import { resolveStopTarget } from './asst-stop.js';
53
52
  import { plural } from './asst-tool-kinds.js';
54
- import { cachedProjects, createBrainPicker, testDensityLabel } from './asst-brain-picker.js';
53
+ import { cachedProjects, createBrainPicker, openModelMenu, testDensityLabel } from './asst-brain-picker.js';
55
54
  import { createRovingToolbar } from './asst-roving.js';
56
55
  import { openAccountsManager } from './asst-accounts.js';
57
56
  import { createAgentsDock } from './asst-agents-dock.js';
@@ -62,8 +61,8 @@ import { createRouteModeControl, openModelsManager, openRoutesEditor, refreshMod
62
61
  import { budgetChipText, budgetSpendText, budgetTone, closeBudgetEditor, createBudgetController, mountBudgetChip, openBudgetEditor } from './asst-budget.js';
63
62
  import { computerRemoteView, createComputerPanel, createComputerToggle, normalizeComputerRemote, openFrameLightbox, closeFrameLightbox, requestDesktopStop } from './asst-computer.js';
64
63
  import {
65
- applyLimit, applyLimitPacket, applyUsagePacket, brainLabel, createRenameWriter, createTabState, DEFAULT_ROUTE_MODE, deserializeTabState, fmtCost, fmtRunLine, limitExpiresIn, limitNoticeText, modelShortName,
66
- brainModePatch, normalizeBrain, normalizeComputerMode, permissionModeLabel, normalizeRouteMode, normalizeTestDensity, projectLabel, providerShortLabel, readStoredBrain, runFromPayload,
64
+ applyLimit, applyLimitPacket, applyUsagePacket, BRAIN_PROVIDERS, brainLabel, createRenameWriter, createTabState, DEFAULT_ROUTE_MODE, deserializeTabState, fmtCost, fmtRunLine, limitExpiresIn, limitNoticeText, modelShortName,
65
+ brainModePatch, isBrainProvider, normalizeBrain, normalizeComputerMode, permissionModeLabel, normalizeRouteMode, normalizeTestDensity, projectLabel, providerMeta, providerShortLabel, readStoredBrain, runFromPayload,
67
66
  serializeTabState, sessionStatus, tabStorageKey, userSessionTitle,
68
67
  } from './asst-state.js';
69
68
  import {
@@ -244,8 +243,9 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
244
243
  if (host.toolbarSlot) host.toolbarSlot.appendChild(barEl);
245
244
 
246
245
  // ── Helpers ──
247
- const providerIcon = (p) => getProviderMeta(p).icon;
248
- const providerColor = (p) => getProviderMeta(p).color;
246
+ // A worker-only provider provider-icons.js does not know yet keeps its own name (asst-state.js providerMeta).
247
+ const providerIcon = (p) => providerMeta(p).icon;
248
+ const providerColor = (p) => providerMeta(p).color;
249
249
  const visible = () => { try { return host.isVisible ? !!host.isVisible() : true; } catch { return true; } };
250
250
 
251
251
  // ── Just-in-time tips (../tour/tour-tips.js): once each, ever; never in a replay or while the tour runs ──
@@ -537,6 +537,7 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
537
537
  onFollowup: (text) => { submit(clickPayload(text)); },
538
538
  onFollowupOther: () => composer.focus(),
539
539
  onLimitRetry: (text) => { submit(clickPayload(text)); },
540
+ onSwitchProvider: (anchor, meta) => continueOnAnotherProvider(anchor, meta),
540
541
  limitInfo: () => st.limit,
541
542
  brainProvider: () => st.brain.provider,
542
543
  heroOrigin: () => { try { return host.takeHeroOrigin?.() || null; } catch { return null; } },
@@ -1480,6 +1481,7 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
1480
1481
  compact();
1481
1482
  return true;
1482
1483
  case 'new':
1484
+ if (parsed.error === 'not_brain') { toast(notBrainText(parsed.provider)); return false; }
1483
1485
  newSession(parsed.provider || null);
1484
1486
  return true;
1485
1487
  case 'resume':
@@ -1507,6 +1509,7 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
1507
1509
  if (parsed.error) { toast(tt('assistant.slash.needsText', 'Usage: /remember <text>')); return false; }
1508
1510
  return queueOrSend({ ...payload, display: payload.text, text: tt('assistant.prompts.remember', 'Store this in SynaBun memory with the remember tool (pick a fitting category, project, tags and importance): {text}', { text: parsed.text }) });
1509
1511
  case 'model': {
1512
+ if (parsed.error === 'not_brain') { toast(notBrainText(parsed.provider)); return false; }
1510
1513
  if (!parsed.model && !parsed.provider) { picker.openField('model'); return true; }
1511
1514
  const next = { ...st.brain };
1512
1515
  let requiresSwitch = false;
@@ -1555,10 +1558,15 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
1555
1558
  }
1556
1559
  }
1557
1560
 
1561
+ /** /model or /new naming a provider that runs dispatched work only (none today: all four can be the brain). */
1562
+ function notBrainText(provider) {
1563
+ return tt('assistant.slash.notBrain', '{provider} runs dispatched work only, never the Assistant itself. Send it a task with /dispatch {id} <task>.', { provider: providerShortLabel(provider), id: provider });
1564
+ }
1565
+
1558
1566
  function dispatchErrorText(parsed) {
1559
1567
  switch (parsed.error) {
1560
- case 'missing_provider': return tt('assistant.slash.dispatchProvider', 'Usage: /dispatch <claude|codex|opencode> [flags] <task>');
1561
- case 'invalid_provider': return tt('assistant.slash.dispatchInvalidProvider', 'Unknown provider "{provider}". Use claude, codex or opencode.', { provider: parsed.provider });
1568
+ case 'missing_provider': return tt('assistant.slash.dispatchProvider', 'Usage: /dispatch <claude|codex|opencode|antigravity> [flags] <task>');
1569
+ case 'invalid_provider': return tt('assistant.slash.dispatchInvalidProvider', 'Unknown provider "{provider}". Use claude, codex, opencode or antigravity.', { provider: parsed.provider });
1562
1570
  case 'unknown_flag': return tt('assistant.slash.dispatchUnknownFlag', 'Unknown flag --{flag}', { flag: parsed.flag });
1563
1571
  case 'missing_flag_value': return tt('assistant.slash.dispatchFlagValue', 'Flag --{flag} needs a value', { flag: parsed.flag });
1564
1572
  case 'missing_task': return tt('assistant.slash.dispatchTask', 'Add the task text after the flags');
@@ -1607,6 +1615,8 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
1607
1615
  }
1608
1616
 
1609
1617
  function newSession(provider) {
1618
+ // Only a brain provider names a new session's brain (Claude Code, Codex, OpenCode or Antigravity).
1619
+ if (provider && !isBrainProvider(provider)) provider = null;
1610
1620
  const b = provider ? normalizeBrain({ ...st.brain, provider, model: '', effort: null, agent: null, accountId: null }) : st.brain;
1611
1621
  // This one is still empty: it is the new session (the server keeps one draft, not a pile of them).
1612
1622
  // /new <provider> switches its brain as the picker does; what only this page showed (/help) is cleared.
@@ -1663,6 +1673,33 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
1663
1673
  });
1664
1674
  }
1665
1675
 
1676
+ // "Continue with another provider" on the Assistant's own limit-wait card: the same model menu
1677
+ // the toolbar's model field opens (never a native select), minus the limited provider, then the
1678
+ // existing switch (applyBrain → switch_brain) and, where the blocked prompt is known, Retry now's
1679
+ // own resend (submit) — in that order, so the server sees the switch before the retried prompt.
1680
+ function continueOnAnotherProvider(anchor, { excludeProvider = '', retryText = '' } = {}) {
1681
+ if (!anchor) return;
1682
+ const providers = BRAIN_PROVIDERS.filter((p) => p !== excludeProvider);
1683
+ if (!providers.length) return;
1684
+ openModelMenu(anchor, {
1685
+ brain: st.brain,
1686
+ t,
1687
+ providers,
1688
+ title: tt('assistant.limitWait.switchProvider', 'Continue with another provider'),
1689
+ warnSwitch: true,
1690
+ allowCustom: false, // a custom id here would resolve to the (still limited) current provider
1691
+ onToast: toast,
1692
+ onManageModels: () => openModels(),
1693
+ onSelect: async (target) => {
1694
+ if (!target?.provider) return;
1695
+ await picker.switchProvider(target.provider, target.model || '');
1696
+ if (destroyed) return;
1697
+ renderer.clearLimitWait();
1698
+ if (retryText) submit(clickPayload(retryText));
1699
+ },
1700
+ }).catch(() => {});
1701
+ }
1702
+
1666
1703
  // ── Runs ──
1667
1704
  async function focusRun(runId) {
1668
1705
  if (!runId) return;
@@ -50,13 +50,14 @@ import { closeMenu, isMenuOpen } from './asst-menu.js';
50
50
  import { computerToolName, describeComputerAction, frameUrls, markerPercent, openFrameLightbox, parseComputerResult, swapImage } from './asst-computer.js';
51
51
  import * as mascotLib from '../synabun-mascot.js';
52
52
  import { isReducedMotion, subscribe } from '../synabun-ticker.js';
53
- import { KIND_SPECS, callOutcome, commandOf, editDiff, editPaths, fmtDuration, groupSubject, headEllipsis, kindOf, maskEcho, mcpServerOf, middleEllipsis, planState, plural, prettyServer, receiptSentence, redactInput, resultDetail, sayCall, shouldFoldNarration, stationAggregate, stationKey, targetKey, toolWords, verbText, wrapRuns } from './asst-tool-kinds.js';
53
+ import { KIND_SPECS, callOutcome, canonicalToolInput, commandOf, editDiff, editPaths, fmtDuration, groupSubject, headEllipsis, kindOf, maskEcho, mcpServerOf, middleEllipsis, planState, plural, prettyServer, receiptSentence, redactInput, resultDetail, sayCall, shouldFoldNarration, stationAggregate, stationKey, targetKey, toolWords, verbText, wrapRuns } from './asst-tool-kinds.js';
54
54
  import { injectRackStyles } from './asst-rack-styles.js';
55
55
  import { injectSceneStyles } from './asst-scene-styles.js';
56
56
  import { activityScene, compactActivity, isLivePhase, runChip, sceneOfClass, sceneSvg, stagePhase } from './asst-scenes.js';
57
57
  import { STAGE_TIMING, createWorkerStage } from './asst-run-stage.js';
58
58
  import { formatRunTokens } from './asst-usage.js';
59
59
  import { sanitizeHtmlString, sanitizeInto } from './asst-sanitize.js';
60
+ import { renderStreamingMarkdown } from '../streaming-markdown.js';
60
61
  import { automationView } from './asst-automation.js';
61
62
  import { guessFollowups, splitFollowups } from './asst-followups.js';
62
63
  import { createFollowupsCard } from './asst-followups-card.js';
@@ -502,7 +503,9 @@ const SUGGESTION_KINDS = { computer: 'computer', web: 'web', fix: 'edit', skill:
502
503
  * onFollowup(text, { set, indices }) (a follow-up was clicked: send `text` as the user's message),
503
504
  * onFollowupOther() ("Something else…": the composer), limitInfo() → the session's limit notice
504
505
  * ({ status, resetsAt } | null), brainProvider() → the brain's provider id, onLimitRetry(text)
505
- * (Retry now on the Assistant's own limit card: send `text`) }
506
+ * (Retry now on the Assistant's own limit card: send `text`), onSwitchProvider(anchorEl, { excludeProvider, retryText })
507
+ * (Continue with another provider on the Assistant's own limit card: the host opens its own
508
+ * provider picker anchored on anchorEl and decides what a pick does) }
506
509
  */
507
510
  export function createRenderer(transcriptEl, hooks = {}) {
508
511
  loadMarked();
@@ -522,6 +525,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
522
525
  // there, above the "Working…" row, until it is answered; rows appended meanwhile go above it.
523
526
  const askTail = createAskTail($msgs, { anchor: () => (working?.parentNode === $msgs ? working : null) });
524
527
  let followTail = true;
528
+ let scrollFrame = 0;
525
529
  let emptyEl = null;
526
530
  let hero = null; // the empty state's character: { rig, el, box, parked, leaving, held, dock, egg, target }
527
531
  const mcpWarned = new Set();
@@ -637,7 +641,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
637
641
 
638
642
  function scrollEnd(force = false) {
639
643
  if (!force && !followTail) return;
640
- requestAnimationFrame(() => { $msgs.scrollTop = $msgs.scrollHeight; });
644
+ if (scrollFrame) return;
645
+ scrollFrame = requestAnimationFrame(() => { scrollFrame = 0; $msgs.scrollTop = $msgs.scrollHeight; });
641
646
  }
642
647
 
643
648
  function root() { return { container: $msgs, stream: null, key: null, msgId: null, lastAt: 0 }; }
@@ -948,7 +953,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
948
953
  */
949
954
  function paintMessage(row, body, text, { streaming = false, scope = rootScope } = {}) {
950
955
  const split = splitFollowups(text, { streaming });
951
- renderMarkdownInto(body, split.body);
956
+ renderStreamingMarkdown(body, split.body, renderMarkdownInto, { streaming });
952
957
  if (!streaming && split.block && scope === rootScope) attachFollowups(row, split.block);
953
958
  return split;
954
959
  }
@@ -963,6 +968,10 @@ export function createRenderer(transcriptEl, hooks = {}) {
963
968
  function attachFollowups(row, set) {
964
969
  if (!row || !set?.items?.length) return null;
965
970
  if (row._followups?.el.isConnected) return row._followups;
971
+ // One turn, one card: a set offered earlier in this turn (another text segment between tool calls, a
972
+ // streamed row the final message replaced) is superseded by this one. `offers` holds only this turn's:
973
+ // the user's prompt and a new turn settle and drop the earlier ones (settleOffers), which stay as history.
974
+ supersedeFollowups(row);
966
975
  const card = createFollowupsCard(set, {
967
976
  t,
968
977
  // A card drawn from the journal comes in quietly; a live one with a nod.
@@ -972,6 +981,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
972
981
  onFocusComposer: () => hooks.onFollowupOther?.(),
973
982
  });
974
983
  row._followups = card;
984
+ card._row = row;
975
985
  (row.querySelector('.asst-msg-content') || row).appendChild(card.el);
976
986
  offers.add(card);
977
987
  registerAsk(card);
@@ -980,6 +990,19 @@ export function createRenderer(transcriptEl, hooks = {}) {
980
990
  return card;
981
991
  }
982
992
 
993
+ /** Take out this turn's open follow-ups cards that belong to a row other than `row`. */
994
+ function supersedeFollowups(row) {
995
+ for (const offer of [...offers]) {
996
+ if (!offer._row || offer._row === row) continue;
997
+ offers.delete(offer);
998
+ askCards.delete(offer);
999
+ try { offer.presenter?.destroy?.(); } catch { /* gone */ }
1000
+ if (offer._row._followups === offer) offer._row._followups = null;
1001
+ offer.el?.remove();
1002
+ }
1003
+ askTail.sync();
1004
+ }
1005
+
983
1006
  /**
984
1007
  * A finished turn whose last message still ends in a list of options or an "I can X, or Y" offer,
985
1008
  * with no block: best-effort items read from the prose. Once per message.
@@ -987,6 +1010,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
987
1010
  function guessOffer() {
988
1011
  const row = lastTextRow;
989
1012
  if (!row?.isConnected || row._followups || row._guessed || row.classList.contains('asst-msg-error')) return;
1013
+ // The turn already offered a set from its block: a guess from the prose would be a second card.
1014
+ for (const offer of offers) if (offer._row && offer.el?.isConnected) return;
990
1015
  row._guessed = true;
991
1016
  const set = guessFollowups(row._text || '');
992
1017
  if (set) attachFollowups(row, set);
@@ -1023,9 +1048,17 @@ export function createRenderer(transcriptEl, hooks = {}) {
1023
1048
  if (!read && !(brainError && reported)) return null;
1024
1049
  const resetsAt = reported ? info.resetsAt : read?.resetsAt;
1025
1050
  const retryText = t('assistant.limitWait.continue', 'The usage limit has reset. Retry what was blocked and continue where you left off.');
1026
- let provider = '';
1027
- try { provider = providerShortLabel(hooks.brainProvider?.() || ''); } catch { /* unknown */ }
1028
- return limitDock.set('brain', { resetsAt, since: nowMs(), provider, onRetry: () => { limitDock.set('brain', null); hooks.onLimitRetry?.(retryText); } });
1051
+ let providerId = '';
1052
+ try { providerId = hooks.brainProvider?.() || ''; } catch { /* unknown */ }
1053
+ const provider = providerId ? providerShortLabel(providerId) : '';
1054
+ return limitDock.set('brain', {
1055
+ resetsAt, since: nowMs(), provider,
1056
+ onRetry: () => { limitDock.set('brain', null); hooks.onLimitRetry?.(retryText); },
1057
+ onSwitchProvider: typeof hooks.onSwitchProvider === 'function'
1058
+ ? (anchor) => hooks.onSwitchProvider(anchor, { excludeProvider: providerId, retryText })
1059
+ : null,
1060
+ onClose: () => limitDock.set('brain', null),
1061
+ });
1029
1062
  }
1030
1063
 
1031
1064
  /** The session's limit report changed (assistant:limit): this turn's waiting card counts to its reset time. */
@@ -1368,7 +1401,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
1368
1401
  const kind = kindOf(block.name, block.input);
1369
1402
  const step = {
1370
1403
  type: 'call', id: block.id || `call-${++stepSeq}`, seq: ++stepSeq, name: block.name || 'tool', kind, key: stationKey(kind, block.name),
1371
- input: block.input || {}, state: 'running', result: '', detail: '', code: null, startedAt: nowMs(), endedAt: 0, progress: 0,
1404
+ input: canonicalToolInput(block.input), state: 'running', result: '', detail: '', code: null, startedAt: nowMs(), endedAt: 0, progress: 0,
1372
1405
  timed: isTimed(), ghost, stale: false, rack, station: null, card: null, parent: parentOf(rack.scope), bodyBuilt: false,
1373
1406
  ...(extra || {}),
1374
1407
  };
@@ -1383,7 +1416,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
1383
1416
  /** The final block of a streamed call, or the same block replayed: the step learns its input, nothing is added. */
1384
1417
  function updateCall(step, block) {
1385
1418
  if (block.name) step.name = block.name;
1386
- if (block.input && typeof block.input === 'object' && Object.keys(block.input).length) step.input = block.input;
1419
+ if (block.input && typeof block.input === 'object' && Object.keys(block.input).length) step.input = canonicalToolInput(block.input);
1387
1420
  step.ghost = false;
1388
1421
  if (step.card?._computer) { step.card._computer.input = step.input; step.card._computer.description = describeComputerAction(step.input, step.name); }
1389
1422
  refreshStep(step);
@@ -3833,8 +3866,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
3833
3866
  }
3834
3867
  case 'result': {
3835
3868
  flushStream(scope);
3836
- // Codex/OpenCode also send an `error` packet; the SDK reports only here.
3837
- const errorText = !parentId && ev.brain !== 'codex' && ev.brain !== 'opencode' ? resultErrorText(ev, t) : '';
3869
+ // Codex/OpenCode/Antigravity also send an `error` packet; the SDK reports only here.
3870
+ const errorText = !parentId && ev.brain !== 'codex' && ev.brain !== 'opencode' && ev.brain !== 'antigravity' ? resultErrorText(ev, t) : '';
3838
3871
  if (errorText && !muted) turn.errored = true;
3839
3872
  // The stage on screen waits for the turn's verdict (onResult → finishTurn) before it folds.
3840
3873
  if (!parentId && !muted) grace = newestStage();
@@ -4043,6 +4076,10 @@ export function createRenderer(transcriptEl, hooks = {}) {
4043
4076
  }
4044
4077
 
4045
4078
  function clear() {
4079
+ if (scrollFrame) { cancelAnimationFrame(scrollFrame); scrollFrame = 0; }
4080
+ clearTimeout(recutTimer);
4081
+ recutTimer = 0;
4082
+ toRecut.clear();
4046
4083
  for (const scope of scopes.values()) { if (scope.stream?.mdTimer) clearTimeout(scope.stream.mdTimer); if (scope.stream?.thinkTimer) clearTimeout(scope.stream.thinkTimer); }
4047
4084
  if (rootScope.stream?.mdTimer) clearTimeout(rootScope.stream.mdTimer);
4048
4085
  if (rootScope.stream?.thinkTimer) clearTimeout(rootScope.stream.thinkTimer);
@@ -4085,7 +4122,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
4085
4122
  return {
4086
4123
  el: $msgs,
4087
4124
  clear,
4088
- destroy: () => { clear(); limitDock.destroy(); },
4125
+ destroy: () => { clear(); stageResizes?.disconnect(); limitDock.destroy(); },
4089
4126
  appendUser,
4090
4127
  appendAssistantMarkdown,
4091
4128
  appendStatus,
@@ -4126,6 +4163,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
4126
4163
  setWaiting,
4127
4164
  /** The session's usage-limit report changed: this turn's waiting card counts to its reset time. */
4128
4165
  noteLimit,
4166
+ /** Dismiss the Assistant's own limit-wait card (a provider switch already decided what follows). */
4167
+ clearLimitWait: () => limitDock.set('brain', null),
4129
4168
  finishTurn,
4130
4169
  renderTranscript,
4131
4170
  showWorking,