synabun 2.0.0 → 2.0.7

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 (507) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/NOTICE +5 -0
  3. package/README.md +2 -1
  4. package/THIRD-PARTY-LICENSES.md +59 -0
  5. package/benchmarks/fixtures/browser/README.md +1 -1
  6. package/benchmarks/fixtures/desktop/README.md +1 -1
  7. package/docs/whatsapp.md +153 -18
  8. package/hooks/claude-code/pre-compact.mjs +11 -0
  9. package/hooks/claude-code/prompt-submit.mjs +60 -184
  10. package/hooks/claude-code/session-start.mjs +73 -16
  11. package/hooks/claude-code/shared.mjs +26 -3
  12. package/hooks/claude-code/stop.mjs +5 -5
  13. package/lib/child-path.js +303 -0
  14. package/lib/claude-hooks.js +2 -1
  15. package/lib/data-home-migration.js +2 -0
  16. package/lib/desktop-app.js +265 -0
  17. package/lib/external-tools.js +7 -3
  18. package/lib/log-redact.js +102 -0
  19. package/lib/packaged-runtime.js +138 -11
  20. package/lib/start-launcher.js +88 -1
  21. package/mcp-server/dist/http.d.ts +3 -1
  22. package/mcp-server/dist/http.d.ts.map +1 -1
  23. package/mcp-server/dist/http.js +15 -4
  24. package/mcp-server/dist/http.js.map +1 -1
  25. package/mcp-server/dist/index.d.ts.map +1 -1
  26. package/mcp-server/dist/index.js +9 -6
  27. package/mcp-server/dist/index.js.map +1 -1
  28. package/mcp-server/dist/services/budget-judgments.d.ts +108 -0
  29. package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -0
  30. package/mcp-server/dist/services/budget-judgments.js +153 -0
  31. package/mcp-server/dist/services/budget-judgments.js.map +1 -0
  32. package/mcp-server/dist/services/codex-config-heal.d.ts +5 -1
  33. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  34. package/mcp-server/dist/services/codex-config-heal.js +41 -4
  35. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  36. package/mcp-server/dist/services/identity.d.ts +11 -0
  37. package/mcp-server/dist/services/identity.d.ts.map +1 -1
  38. package/mcp-server/dist/services/identity.js +24 -0
  39. package/mcp-server/dist/services/identity.js.map +1 -1
  40. package/mcp-server/dist/services/neural-interface.d.ts +13 -0
  41. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  42. package/mcp-server/dist/services/neural-interface.js +34 -2
  43. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  44. package/mcp-server/dist/services/typesafe-config.d.ts +1 -1
  45. package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -1
  46. package/mcp-server/dist/services/typesafe-config.js +4 -1
  47. package/mcp-server/dist/services/typesafe-config.js.map +1 -1
  48. package/mcp-server/dist/tools/agents.d.ts +10 -7
  49. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  50. package/mcp-server/dist/tools/agents.js +34 -13
  51. package/mcp-server/dist/tools/agents.js.map +1 -1
  52. package/mcp-server/dist/tools/whatsapp-send-file.d.ts +17 -0
  53. package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -0
  54. package/mcp-server/dist/tools/whatsapp-send-file.js +44 -0
  55. package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -0
  56. package/mcp-server/dist/tui.js +2 -1
  57. package/mcp-server/dist/tui.js.map +1 -1
  58. package/mcp-server/dist/version.d.ts +5 -0
  59. package/mcp-server/dist/version.d.ts.map +1 -0
  60. package/mcp-server/dist/version.js +21 -0
  61. package/mcp-server/dist/version.js.map +1 -0
  62. package/mcp-server/package-lock.json +2 -2
  63. package/mcp-server/package.json +1 -3
  64. package/mcp-server/run.mjs +15 -9
  65. package/mcp-server/src/http.ts +18 -3
  66. package/mcp-server/src/index.ts +9 -6
  67. package/mcp-server/src/services/budget-judgments.ts +183 -0
  68. package/mcp-server/src/services/codex-config-heal.ts +35 -4
  69. package/mcp-server/src/services/identity.ts +27 -0
  70. package/mcp-server/src/services/neural-interface.ts +34 -2
  71. package/mcp-server/src/services/typesafe-config.ts +4 -1
  72. package/mcp-server/src/tools/agents.ts +33 -14
  73. package/mcp-server/src/tools/whatsapp-send-file.ts +51 -0
  74. package/mcp-server/src/tui.ts +2 -1
  75. package/mcp-server/src/version.ts +22 -0
  76. package/neural-interface/i18n/en.json +2126 -209
  77. package/neural-interface/i18n/pt-BR.json +2123 -207
  78. package/neural-interface/lib/account-usage.js +163 -0
  79. package/neural-interface/lib/assistant-api.js +316 -57
  80. package/neural-interface/lib/assistant-brains/claude.js +19 -2
  81. package/neural-interface/lib/assistant-brains/codex.js +3 -0
  82. package/neural-interface/lib/assistant-brains/opencode.js +17 -4
  83. package/neural-interface/lib/assistant-budget-planner.js +390 -0
  84. package/neural-interface/lib/assistant-budget-split.js +408 -0
  85. package/neural-interface/lib/assistant-budget.js +94 -95
  86. package/neural-interface/lib/assistant-catalog.js +179 -27
  87. package/neural-interface/lib/assistant-config.js +91 -14
  88. package/neural-interface/lib/assistant-dispatch.js +494 -138
  89. package/neural-interface/lib/assistant-hidden-models.js +393 -66
  90. package/neural-interface/lib/assistant-jev.js +26 -3
  91. package/neural-interface/lib/assistant-memory.js +24 -10
  92. package/neural-interface/lib/assistant-persona.js +73 -12
  93. package/neural-interface/lib/assistant-plan-permissions.js +8 -1
  94. package/neural-interface/lib/assistant-pricing.js +21 -6
  95. package/neural-interface/lib/assistant-route-accounts.js +14 -0
  96. package/neural-interface/lib/assistant-route-gate.js +19 -3
  97. package/neural-interface/lib/assistant-router.js +373 -42
  98. package/neural-interface/lib/assistant-runtime.js +488 -59
  99. package/neural-interface/lib/assistant-task-prompt.js +13 -3
  100. package/neural-interface/lib/assistant-test-density.js +142 -0
  101. package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
  102. package/neural-interface/lib/assistant-user-context.js +908 -0
  103. package/neural-interface/lib/assistant-user-knowledge.js +328 -0
  104. package/neural-interface/lib/assistant-user-learn.js +404 -0
  105. package/neural-interface/lib/assistant-user-threads.js +226 -0
  106. package/neural-interface/lib/assistant-user-writer.js +183 -0
  107. package/neural-interface/lib/augmented-path.js +317 -53
  108. package/neural-interface/lib/browser-background-tab.js +134 -0
  109. package/neural-interface/lib/browser-screenshot.js +678 -61
  110. package/neural-interface/lib/claude-accounts.js +342 -31
  111. package/neural-interface/lib/claude-agent-bridge.js +123 -9
  112. package/neural-interface/lib/claude-executable.js +6 -1
  113. package/neural-interface/lib/claude-model-catalog.js +33 -5
  114. package/neural-interface/lib/claude-panel-auth.js +221 -0
  115. package/neural-interface/lib/claude-profile-api-key.js +62 -0
  116. package/neural-interface/lib/claude-temporary.js +46 -7
  117. package/neural-interface/lib/cli-install-api.js +137 -0
  118. package/neural-interface/lib/cli-install.js +797 -0
  119. package/neural-interface/lib/cli-path.js +130 -0
  120. package/neural-interface/lib/cli-resolver.js +744 -0
  121. package/neural-interface/lib/cli-update-planner.js +143 -7
  122. package/neural-interface/lib/cloudflared/install.js +533 -0
  123. package/neural-interface/lib/cloudflared/manager.js +545 -0
  124. package/neural-interface/lib/cloudflared/pins.js +115 -0
  125. package/neural-interface/lib/cloudflared/pins.json +63 -0
  126. package/neural-interface/lib/cloudflared/tunnel.js +265 -0
  127. package/neural-interface/lib/codex-account-auth.js +178 -0
  128. package/neural-interface/lib/codex-accounts.js +489 -0
  129. package/neural-interface/lib/codex-capabilities.js +2 -1
  130. package/neural-interface/lib/codex-compat-rpc.js +8 -2
  131. package/neural-interface/lib/codex-mcp-approvals.js +5 -1
  132. package/neural-interface/lib/codex-panel-auth.js +62 -0
  133. package/neural-interface/lib/codex-session-settings.js +1 -1
  134. package/neural-interface/lib/codex-settings-schema.js +120 -0
  135. package/neural-interface/lib/desktop/config.js +3 -0
  136. package/neural-interface/lib/editor-completion-endpoint.js +264 -0
  137. package/neural-interface/lib/editor-completion-opencode.js +436 -0
  138. package/neural-interface/lib/editor-completion.js +714 -0
  139. package/neural-interface/lib/editor-config.js +149 -0
  140. package/neural-interface/lib/editor-files-api.js +268 -0
  141. package/neural-interface/lib/editor-git-api.js +134 -0
  142. package/neural-interface/lib/file-explorer-api.js +1039 -0
  143. package/neural-interface/lib/file-explorer-system.js +355 -0
  144. package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
  145. package/neural-interface/lib/file-explorer-watch.js +239 -0
  146. package/neural-interface/lib/git-bash/bzip2.js +339 -0
  147. package/neural-interface/lib/git-bash/claude-settings.js +73 -0
  148. package/neural-interface/lib/git-bash/extract-worker.js +34 -0
  149. package/neural-interface/lib/git-bash/install.js +529 -0
  150. package/neural-interface/lib/git-bash/licenses/COPYING +360 -0
  151. package/neural-interface/lib/git-bash/licenses/LGPL-2.1 +511 -0
  152. package/neural-interface/lib/git-bash/manager.js +268 -0
  153. package/neural-interface/lib/git-bash/pins.js +206 -0
  154. package/neural-interface/lib/git-bash/pins.json +34 -0
  155. package/neural-interface/lib/git-bash/resolver.js +194 -0
  156. package/neural-interface/lib/git-diff-api.js +103 -0
  157. package/neural-interface/lib/git-read.js +234 -0
  158. package/neural-interface/lib/greeting/api.js +89 -0
  159. package/neural-interface/lib/greeting/budgets.js +90 -0
  160. package/neural-interface/lib/greeting/engine.js +461 -0
  161. package/neural-interface/lib/greeting/facts.js +320 -0
  162. package/neural-interface/lib/greeting/sources.js +252 -0
  163. package/neural-interface/lib/greeting/writer.js +139 -0
  164. package/neural-interface/lib/http-guards.js +189 -13
  165. package/neural-interface/lib/install-details.js +145 -0
  166. package/neural-interface/lib/machine-names.js +88 -0
  167. package/neural-interface/lib/mcp-installer.js +14 -2
  168. package/neural-interface/lib/memory-api.js +31 -7
  169. package/neural-interface/lib/native-loop-providers.js +26 -8
  170. package/neural-interface/lib/native-loop-runtime.js +9 -4
  171. package/neural-interface/lib/os-path.js +319 -0
  172. package/neural-interface/lib/plugin-installer.js +4 -2
  173. package/neural-interface/lib/project-storage.js +5 -7
  174. package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
  175. package/neural-interface/lib/provider-panel-terminal-auth.js +95 -0
  176. package/neural-interface/lib/pty-host/core.js +2 -1
  177. package/neural-interface/lib/pty-host/manager.js +27 -2
  178. package/neural-interface/lib/remote-policy.js +20 -11
  179. package/neural-interface/lib/request-gate.js +589 -0
  180. package/neural-interface/lib/session-title-generator.js +4 -3
  181. package/neural-interface/lib/system-about.js +340 -0
  182. package/neural-interface/lib/system-activity.js +66 -0
  183. package/neural-interface/lib/terminal-env.js +11 -3
  184. package/neural-interface/lib/terminal-path.js +55 -0
  185. package/neural-interface/lib/ui-blob-api.js +108 -0
  186. package/neural-interface/lib/ui-blob-store.js +68 -0
  187. package/neural-interface/lib/virtual-socket.js +12 -1
  188. package/neural-interface/lib/whatsapp/baileys-adapter.js +165 -45
  189. package/neural-interface/lib/whatsapp/bridge.js +78 -34
  190. package/neural-interface/lib/whatsapp/commands.js +13 -14
  191. package/neural-interface/lib/whatsapp/config.js +134 -20
  192. package/neural-interface/lib/whatsapp/fake-baileys.js +99 -14
  193. package/neural-interface/lib/whatsapp/host-core.js +168 -35
  194. package/neural-interface/lib/whatsapp/inbound.js +5 -3
  195. package/neural-interface/lib/whatsapp/local-file.js +524 -0
  196. package/neural-interface/lib/whatsapp/manager.js +92 -32
  197. package/neural-interface/lib/whatsapp/paths.js +11 -1
  198. package/neural-interface/lib/whatsapp/protocol.js +65 -5
  199. package/neural-interface/lib/whatsapp/redact.js +63 -0
  200. package/neural-interface/lib/whatsapp/service.js +343 -91
  201. package/neural-interface/lib/whatsapp/transcription.js +463 -0
  202. package/neural-interface/package-lock.json +701 -2
  203. package/neural-interface/package.json +13 -2
  204. package/neural-interface/public/index.html +2 -0
  205. package/neural-interface/public/index2d.html +2 -0
  206. package/neural-interface/public/install-app.html +9 -6
  207. package/neural-interface/public/offline.html +19 -3
  208. package/neural-interface/public/onboarding.html +107 -2
  209. package/neural-interface/public/shared/account-i18n.js +7 -0
  210. package/neural-interface/public/shared/account-menu.js +539 -0
  211. package/neural-interface/public/shared/account-usage.js +19 -0
  212. package/neural-interface/public/shared/account-view.js +335 -0
  213. package/neural-interface/public/shared/agent-runtime-options.js +50 -14
  214. package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
  215. package/neural-interface/public/shared/assistant/asst-api.js +32 -2
  216. package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
  217. package/neural-interface/public/shared/assistant/asst-brain-picker.js +430 -81
  218. package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
  219. package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
  220. package/neural-interface/public/shared/assistant/asst-mascot.js +140 -61
  221. package/neural-interface/public/shared/assistant/asst-menu.js +27 -11
  222. package/neural-interface/public/shared/assistant/asst-panel.js +198 -42
  223. package/neural-interface/public/shared/assistant/asst-render.js +197 -23
  224. package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
  225. package/neural-interface/public/shared/assistant/asst-route.js +95 -343
  226. package/neural-interface/public/shared/assistant/asst-routes-editor.js +534 -0
  227. package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
  228. package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
  229. package/neural-interface/public/shared/assistant/asst-slash.js +1 -1
  230. package/neural-interface/public/shared/assistant/asst-state.js +186 -2
  231. package/neural-interface/public/shared/assistant/asst-styles.js +247 -11
  232. package/neural-interface/public/shared/blob-store.js +111 -22
  233. package/neural-interface/public/shared/cdx/cdx-commands.js +4 -1
  234. package/neural-interface/public/shared/cdx/cdx-features.js +22 -14
  235. package/neural-interface/public/shared/cdx/cdx-icons.js +2 -0
  236. package/neural-interface/public/shared/cdx/cdx-panel.js +80 -118
  237. package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
  238. package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
  239. package/neural-interface/public/shared/cdx/cdx-styles.js +164 -107
  240. package/neural-interface/public/shared/cdx/cdx-tabs.js +348 -668
  241. package/neural-interface/public/shared/cdx/cdx-ws.js +2 -0
  242. package/neural-interface/public/shared/claude-account-view.js +3 -0
  243. package/neural-interface/public/shared/cli-install-view.js +392 -0
  244. package/neural-interface/public/shared/cli-status.js +282 -22
  245. package/neural-interface/public/shared/constants.js +6 -1
  246. package/neural-interface/public/shared/cp/cp-events.js +2 -1
  247. package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
  248. package/neural-interface/public/shared/cp/cp-styles.js +61 -0
  249. package/neural-interface/public/shared/desktop-bridge.js +39 -0
  250. package/neural-interface/public/shared/desktop-titlebar-page.js +59 -0
  251. package/neural-interface/public/shared/desktop-titlebar-view.js +122 -0
  252. package/neural-interface/public/shared/desktop-titlebar.js +153 -0
  253. package/neural-interface/public/shared/desktop-update-view.js +462 -0
  254. package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
  255. package/neural-interface/public/shared/editor/editor-ai.js +619 -0
  256. package/neural-interface/public/shared/editor/editor-commands.js +212 -0
  257. package/neural-interface/public/shared/editor/editor-compare.js +103 -0
  258. package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
  259. package/neural-interface/public/shared/editor/editor-doc.js +257 -0
  260. package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
  261. package/neural-interface/public/shared/editor/editor-git.js +95 -0
  262. package/neural-interface/public/shared/editor/editor-languages.js +156 -0
  263. package/neural-interface/public/shared/editor/editor-lint.js +229 -0
  264. package/neural-interface/public/shared/editor/editor-loader.js +51 -0
  265. package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
  266. package/neural-interface/public/shared/editor/editor-menu.js +233 -0
  267. package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
  268. package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
  269. package/neural-interface/public/shared/editor/editor-palette.js +125 -0
  270. package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
  271. package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
  272. package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
  273. package/neural-interface/public/shared/editor/editor-safety.js +500 -0
  274. package/neural-interface/public/shared/editor/editor-search.js +352 -0
  275. package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
  276. package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
  277. package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
  278. package/neural-interface/public/shared/editor/editor-theme.js +244 -0
  279. package/neural-interface/public/shared/editor/editor-view.js +332 -0
  280. package/neural-interface/public/shared/explorer/fx-api.js +216 -0
  281. package/neural-interface/public/shared/explorer/fx-links.js +204 -0
  282. package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
  283. package/neural-interface/public/shared/explorer/fx-main.js +865 -0
  284. package/neural-interface/public/shared/explorer/fx-names.js +87 -0
  285. package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
  286. package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
  287. package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
  288. package/neural-interface/public/shared/file-links.js +339 -0
  289. package/neural-interface/public/shared/git-bash-view.js +145 -0
  290. package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
  291. package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
  292. package/neural-interface/public/shared/html-shell.js +161 -165
  293. package/neural-interface/public/shared/i18n.js +19 -1
  294. package/neural-interface/public/shared/install-details-view.js +86 -0
  295. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
  296. package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
  297. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +131 -17
  298. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
  299. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +22 -0
  300. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +158 -0
  301. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
  302. package/neural-interface/public/shared/pwa-install-page.js +32 -2
  303. package/neural-interface/public/shared/pwa-install.css +3 -3
  304. package/neural-interface/public/shared/pwa-install.js +100 -1
  305. package/neural-interface/public/shared/settings/settings-about-pane.js +271 -0
  306. package/neural-interface/public/shared/settings/settings-about.js +254 -0
  307. package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
  308. package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
  309. package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
  310. package/neural-interface/public/shared/settings/settings-ia.js +33 -4
  311. package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
  312. package/neural-interface/public/shared/showcase-privacy.js +489 -0
  313. package/neural-interface/public/shared/start-bridge.js +238 -19
  314. package/neural-interface/public/shared/storage.js +30 -3
  315. package/neural-interface/public/shared/styles.css +1710 -770
  316. package/neural-interface/public/shared/synabun-mascot.js +68 -58
  317. package/neural-interface/public/shared/tunnel-view.js +167 -0
  318. package/neural-interface/public/shared/ui-claude-panel.js +369 -229
  319. package/neural-interface/public/shared/ui-cli-banner.js +355 -0
  320. package/neural-interface/public/shared/ui-detail.js +15 -0
  321. package/neural-interface/public/shared/ui-explorer.js +22 -0
  322. package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
  323. package/neural-interface/public/shared/ui-file-links.js +103 -0
  324. package/neural-interface/public/shared/ui-git-bash.js +124 -0
  325. package/neural-interface/public/shared/ui-help.js +26 -0
  326. package/neural-interface/public/shared/ui-invite.js +126 -116
  327. package/neural-interface/public/shared/ui-judgments.js +10 -1
  328. package/neural-interface/public/shared/ui-keybinds.js +4 -0
  329. package/neural-interface/public/shared/ui-loading.js +187 -19
  330. package/neural-interface/public/shared/ui-menubar.js +25 -33
  331. package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
  332. package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
  333. package/neural-interface/public/shared/ui-navbar.js +48 -22
  334. package/neural-interface/public/shared/ui-provider-auth.js +104 -0
  335. package/neural-interface/public/shared/ui-provider-terminal-auth.js +223 -0
  336. package/neural-interface/public/shared/ui-sessions.js +9 -3
  337. package/neural-interface/public/shared/ui-settings.js +509 -490
  338. package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
  339. package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
  340. package/neural-interface/public/shared/ui-sync.js +22 -0
  341. package/neural-interface/public/shared/ui-terminal.js +40 -5
  342. package/neural-interface/public/shared/ui-tool-updates.js +5 -0
  343. package/neural-interface/public/shared/ui-tunnel.js +130 -0
  344. package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
  345. package/neural-interface/public/shared/ui-update.js +594 -5
  346. package/neural-interface/public/shared/ui-whatsapp.js +21 -6
  347. package/neural-interface/public/shared/ui-whiteboard.js +12 -0
  348. package/neural-interface/public/shared/ui-worker-end.js +245 -0
  349. package/neural-interface/public/shared/ui-workspaces.js +41 -27
  350. package/neural-interface/public/shared/whatsapp/wa-view.js +51 -8
  351. package/neural-interface/public/shared/worker-end.js +253 -0
  352. package/neural-interface/public/sw.js +2 -2
  353. package/neural-interface/public/variant/2d/main.js +10 -1
  354. package/neural-interface/public/variant/3d/main.js +10 -1
  355. package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
  356. package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
  357. package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
  358. package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
  359. package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
  360. package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
  361. package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
  362. package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
  363. package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
  364. package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
  365. package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
  366. package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
  367. package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
  368. package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
  369. package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
  370. package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
  371. package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
  372. package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
  373. package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
  374. package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
  375. package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
  376. package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
  377. package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
  378. package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
  379. package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
  380. package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
  381. package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
  382. package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
  383. package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
  384. package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
  385. package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
  386. package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
  387. package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
  388. package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
  389. package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
  390. package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
  391. package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
  392. package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
  393. package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
  394. package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
  395. package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
  396. package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
  397. package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
  398. package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
  399. package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
  400. package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
  401. package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
  402. package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
  403. package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
  404. package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
  405. package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
  406. package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
  407. package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
  408. package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
  409. package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
  410. package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
  411. package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
  412. package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
  413. package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
  414. package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
  415. package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
  416. package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
  417. package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
  418. package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
  419. package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
  420. package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
  421. package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
  422. package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
  423. package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
  424. package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
  425. package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
  426. package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
  427. package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
  428. package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
  429. package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
  430. package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
  431. package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
  432. package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
  433. package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
  434. package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
  435. package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
  436. package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
  437. package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
  438. package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
  439. package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
  440. package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
  441. package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
  442. package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
  443. package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
  444. package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
  445. package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
  446. package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
  447. package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
  448. package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
  449. package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
  450. package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
  451. package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
  452. package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
  453. package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
  454. package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
  455. package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
  456. package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
  457. package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
  458. package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
  459. package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
  460. package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
  461. package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
  462. package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
  463. package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
  464. package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
  465. package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
  466. package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
  467. package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
  468. package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
  469. package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
  470. package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
  471. package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
  472. package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
  473. package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
  474. package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
  475. package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
  476. package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
  477. package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
  478. package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
  479. package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
  480. package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
  481. package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
  482. package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
  483. package/neural-interface/scripts/build-vendor.mjs +78 -2
  484. package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
  485. package/neural-interface/server.js +2085 -1629
  486. package/neural-interface/templates/rulesets/README.md +1 -1
  487. package/package.json +5 -8
  488. package/setup.js +53 -15
  489. package/benchmarks/browser-live-results.json +0 -79
  490. package/benchmarks/browser-v2-results.json +0 -258
  491. package/neural-interface/lib/codex-capabilities.d.ts +0 -41
  492. package/neural-interface/public/category-logos/claude.svg +0 -1
  493. package/neural-interface/public/category-logos/gemini-color.svg +0 -1
  494. package/neural-interface/public/category-logos/openai.svg +0 -1
  495. package/neural-interface/public/category-logos/synabun.png +0 -0
  496. package/neural-interface/public/claude.svg +0 -1
  497. package/neural-interface/public/gallery.svg +0 -6
  498. package/neural-interface/public/leonardoai.svg +0 -1
  499. package/neural-interface/public/mcp.svg +0 -4
  500. package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
  501. package/neural-interface/public/pin-svgrepo-com.svg +0 -25
  502. package/neural-interface/public/sessionicon.svg +0 -8
  503. package/neural-interface/public/toolbar-album.svg +0 -8
  504. package/neural-interface/public/toolbar-question.svg +0 -6
  505. package/neural-interface/public/toolbar-sessions.svg +0 -6
  506. package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
  507. package/skills/synabun/synabunicon.png +0 -0
@@ -1,3 +1,8 @@
1
+ import { providerAuthSnapshot } from './ui-provider-auth.js';
2
+ import { mountProviderTerminalAuth, providerApiKeyRequest, startProviderTerminalLogin } from './ui-provider-terminal-auth.js';
3
+ import { accountName, accountSwitchPlan, accountText, tabAccountId, serverAccountId } from './account-view.js';
4
+ import { mountAccountMenu } from './account-menu.js';
5
+ import './account-i18n.js';
1
6
  import { hostedSidepanelId, sidepanelHost } from './ui-sidepanel-runtime.js';
2
7
  // ═══════════════════════════════════════════
3
8
  // SynaBun — Claude Code Panel (Main Area)
@@ -7,14 +12,18 @@ import { hostedSidepanelId, sidepanelHost } from './ui-sidepanel-runtime.js';
7
12
  import { storage } from './storage.js';
8
13
  import { fetchBlobNamespace, putBlob, deleteBlob, flushBlobs } from './blob-store.js';
9
14
  import { state, emit, on } from './state.js';
15
+ import { forgetGreeting, mountGreeting } from './greeting/greeting-card.js';
10
16
  import { fetchClaudeSessions, fetchBrowserSessions, searchSessions } from './api.js';
11
17
  import { createFrameRenderer } from './utils.js';
12
18
  import { notify, NOTIF_TYPE } from './ui-notifications.js';
13
19
  import { registerSidepanel, setSidepanelVisible, syncSidepanelLayout, focusSidepanel, openAnotherSidepanelSession, isHostedSessionFocused } from './ui-sidepanel-windows.js';
14
- import { subscribeCliStatus, recheckCliStatus, getCliDocUrl, getCliInstallCommand, getCliLabel } from './cli-status.js';
20
+ import { mountCliBanner } from './ui-cli-banner.js';
15
21
  import { nativeLoopWindowId } from './ui-native-window-id.js';
16
22
  import { createStickyScrollController } from './ui-scroll-follow.js';
17
23
  import { requestGeneratedSessionTitle } from './session-title.js';
24
+ import { wireFileLinks } from './ui-file-links.js';
25
+ import { WORKER_END_CLASS, backToAssistant, buildWorkerEndCard, endWorkerRunOnClose, forgetWorkerEnd, trackWorkerEnd, updateWorkerEndBusy } from './ui-worker-end.js';
26
+ import { isDispatchedRun, isEndOfRunAbort } from './worker-end.js';
18
27
  // ── cp/ feature modules (SDK-engine CLI parity: diffs, agents, bash, perms, statusline) ──
19
28
  import { setCpCtx } from './cp/cp-ctx.js';
20
29
  import { buildDiffCard, buildDiffPreviewEl, finalizeDiffCard } from './cp/cp-diff.js';
@@ -24,7 +33,7 @@ import { buildAgentCard, ensureAgentScope, noteAgentActivity, updateAgentTodoBad
24
33
  import { renderPermissionCard, renderPlanApprovalCard, renderElicitationCard, decorateAskCard, askAnnotations, markAskAnswered, PERMISSION_MODES, MODE_LABELS } from './cp/cp-permissions.js';
25
34
  import { normalizeSession, changedStartSettings, sessionFlags, sessionRows, fastModeSupported, fastModeReason, mergeSessionModels, parseMcpServerLines, formatMcpServerLines } from './cp/cp-session-model.js';
26
35
  import { renderSessionSettingsCard } from './cp/cp-session-settings.js';
27
- import { describeOrigin, reconcileTasks, adoptLiveTasks, describeHistorySystemRow, workEndedByRestart } from './cp/cp-tasks-model.js';
36
+ import { describeOrigin, reconcileTasks, adoptLiveTasks, describeHistorySystemRow, workEndedByRestart, isTaskRunning } from './cp/cp-tasks-model.js';
28
37
  import { renderTasksCard } from './cp/cp-tasks.js';
29
38
  import { sessionListLabel, collectTags, tagSession as tagClaudeSession, sessionActionsAvailable, sessionOpsSupported, labelledSessionIds, mergeSessionSearch, writeSessionTitle, forkSession as forkClaudeSession, deleteSession as deleteClaudeSession, fetchSubagentMessages, forkTitle, entryBefore, promptText, removeFrom, sessionRowParts, fillSelectOptions } from './cp/cp-sessions.js';
30
39
  import { contextCard, usageCard, mcpRows, rewindPreviewText, rewindResultText } from './cp/cp-usage-model.js';
@@ -162,9 +171,8 @@ function activeTab() { return _tabs[_activeTabIdx] || null; }
162
171
  let _browserEmbed = null; // { sessionId, ws, canvas, ctx, urlBar, container }
163
172
  let _browserEmbedVisible = false;
164
173
 
165
- // ── CLI-installed status (claude binary detection) ──
166
- let _cliInstalled = null; // null = unknown / missing, version string = installed
167
- let _cliUnsub = null;
174
+ // ── CLI status (claude binary detection): the banner controller, ui-cli-banner.js ──
175
+ let _cliBanner = null;
168
176
 
169
177
  // Marked.js (lazy loaded)
170
178
  let _marked = null;
@@ -304,7 +312,7 @@ const SLASH_COMMANDS = [
304
312
  { name: 'plugin', desc: 'List active plugins and skills', kind: 'info' },
305
313
  { name: 'status', desc: 'Show session status summary', kind: 'info' },
306
314
  { name: 'cost', desc: 'Toggle the cost widget', kind: 'action' },
307
- { name: 'login', desc: 'Open Claude account login', kind: 'action' },
315
+ { name: 'login', desc: 'Sign this tab\'s Claude account in (opens the account menu)', kind: 'action' },
308
316
  { name: 'logout', desc: 'Open Claude account sign-out', kind: 'action' },
309
317
  { name: 'plan', desc: 'Toggle plan mode', kind: 'action' },
310
318
  { name: 'btw', desc: 'Add context while Claude is processing (Shift+Enter)', kind: 'info' },
@@ -318,7 +326,7 @@ const SLASH_COMMANDS = [
318
326
  { name: 'reload-plugins', desc: 'Reload plugins without restarting the session', kind: 'action', needs: 'reload_plugins' },
319
327
  { name: 'tasks', desc: 'Background work of this tab: running tasks (with stop) and scheduled wakeups', kind: 'info' },
320
328
  { name: 'usage', desc: 'This session\'s cost and the plan\'s usage limits', kind: 'info', needs: 'session_requests' },
321
- { name: 'account', desc: 'Choose the Claude account this tab runs under', kind: 'action', needs: 'accounts' },
329
+ { name: 'account', desc: 'Choose and manage the Claude account of this tab (the header account menu)', kind: 'action', needs: 'accounts' },
322
330
  { name: 'effective-settings', desc: 'The Claude Code settings in effect here, and the file each comes from', kind: 'info', needs: 'settings_view' },
323
331
  ];
324
332
 
@@ -540,72 +548,7 @@ function injectStyles() {
540
548
  .cp-messages:has(.msg) .cp-empty,
541
549
  .cp-messages:has(.msg-status) .cp-empty { display: none; }
542
550
 
543
- /* ── CLI-not-installed banner (sticky top of messages container) ── */
544
- .cp-cli-banner {
545
- flex-shrink: 0;
546
- display: grid;
547
- grid-template-columns: auto 1fr auto;
548
- gap: 10px;
549
- align-items: center;
550
- margin: 6px 8px 0;
551
- padding: 10px 12px;
552
- border: 1px solid rgba(255,180,80,0.45);
553
- border-radius: 8px;
554
- background: rgba(255,180,80,0.08);
555
- color: rgba(255,235,200,0.92);
556
- font-size: 11px;
557
- line-height: 1.4;
558
- pointer-events: auto;
559
- position: relative;
560
- z-index: 5;
561
- }
562
- .cp-cli-banner-icon {
563
- width: 22px; height: 22px;
564
- display: flex; align-items: center; justify-content: center;
565
- border-radius: 50%;
566
- background: rgba(255,180,80,0.25);
567
- color: rgba(255,210,140,1);
568
- font-weight: 700;
569
- }
570
- .cp-cli-banner-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
571
- .cp-cli-banner-title {
572
- font-size: 12px; font-weight: 600;
573
- color: rgba(255,210,140,0.95);
574
- }
575
- .cp-cli-banner-body {
576
- font-size: 11px;
577
- color: rgba(255,255,255,0.7);
578
- word-break: break-word;
579
- }
580
- .cp-cli-banner-body code {
581
- padding: 1px 5px;
582
- background: rgba(0,0,0,0.35);
583
- border-radius: 3px;
584
- font-size: 10.5px;
585
- color: rgba(255,255,255,0.85);
586
- }
587
- .cp-cli-banner-actions { display: flex; gap: 6px; }
588
- .cp-cli-banner-link, .cp-cli-banner-recheck {
589
- display: inline-flex; align-items: center; gap: 4px;
590
- padding: 4px 9px;
591
- font-size: 11px;
592
- font-weight: 500;
593
- border-radius: 4px;
594
- border: 1px solid rgba(255,255,255,0.2);
595
- background: rgba(255,255,255,0.05);
596
- color: rgba(255,255,255,0.9);
597
- text-decoration: none;
598
- cursor: pointer;
599
- user-select: none;
600
- font-family: inherit;
601
- }
602
- .cp-cli-banner-link:hover, .cp-cli-banner-recheck:hover {
603
- background: rgba(255,255,255,0.12);
604
- border-color: rgba(255,255,255,0.4);
605
- }
606
- .cp-cli-banner-recheck:disabled { opacity: 0.5; cursor: default; }
607
- .claude-panel.cp-cli-blocked .cp-send { opacity: 0.4 !important; cursor: not-allowed !important; }
608
- .claude-panel.cp-cli-blocked .cp-input { opacity: 0.7; }
551
+ /* ── CLI banner (ui-cli-banner.js): its rules are in cp/cp-styles.js ── */
609
552
 
610
553
  /* ── Browser embed (live screencast inside panel) ── */
611
554
  .cp-browser-embed {
@@ -2928,6 +2871,7 @@ function newTabState(sessionId = null, label = 'New chat', id = null) {
2928
2871
  automationRunId: null,
2929
2872
  automationActive: false,
2930
2873
  automationOwnerId: null,
2874
+ releasedRunId: null, // the ended run this tab showed before the user continued it by hand
2931
2875
  };
2932
2876
  }
2933
2877
 
@@ -2970,6 +2914,7 @@ function connectTab(tab) {
2970
2914
  ws.addEventListener('open', () => {
2971
2915
  if (tab.ws !== ws) return;
2972
2916
  clearTimeout(tab.reconnectTimer);
2917
+ _ensureClaudeTerminalAuth(tab);
2973
2918
  connectionOpened(tab); // no statement of the tab's mode was made on this socket yet
2974
2919
  // Reclaim this tab's server session if it outlived the old socket — a turn in
2975
2920
  // flight, a permission card, background agents or a scheduled wakeup. The
@@ -3041,6 +2986,7 @@ function switchTab(idx) {
3041
2986
  tab.messagesEl._exitToken = (tab.messagesEl._exitToken || 0) + 1; // cancel pending exits
3042
2987
  _attachMessagesEl(tab);
3043
2988
  tab.messagesEl.style.display = '';
2989
+ _ensureClaudeTerminalAuth(tab);
3044
2990
  tab.scrollController?.setActive(true);
3045
2991
  tab.messagesEl.classList.remove('cp-tab-exit');
3046
2992
  tab.messagesEl.classList.add('cp-tab-enter');
@@ -3122,6 +3068,7 @@ function switchTab(idx) {
3122
3068
  _setCompactingUI(!!tab.compacting);
3123
3069
  populateViewDropdown(_panel?.querySelector('#cp-view'), tab);
3124
3070
  populateModeDropdown(_panel?.querySelector('#cp-mode'), tab);
3071
+ _renderAccountMenu();
3125
3072
  _paintTemporary(tab);
3126
3073
  saveTabs();
3127
3074
  }
@@ -3130,17 +3077,22 @@ function closeTab(idx, { detachAutomation = false } = {}) {
3130
3077
  if (idx < 0 || idx >= _tabs.length) return;
3131
3078
  const tab = _tabs[idx];
3132
3079
  if (tab.automationRunId && !detachAutomation) {
3080
+ const runId = tab.automationRunId;
3133
3081
  window.dispatchEvent(new CustomEvent('native-loop:dismissed', {
3134
- detail: { runId: tab.automationRunId },
3082
+ detail: { runId },
3135
3083
  }));
3136
- fetch('/api/loop/stop', {
3084
+ // A worker that finished its task is completed; any other run is stopped.
3085
+ endWorkerRunOnClose(runId, () => fetch('/api/loop/stop', {
3137
3086
  method: 'POST',
3138
3087
  headers: { 'Content-Type': 'application/json' },
3139
- body: JSON.stringify({ runId: tab.automationRunId }),
3140
- }).catch(() => {});
3088
+ body: JSON.stringify({ runId }),
3089
+ }).catch(() => {}), { busy: hasLiveBackgroundWork(tab) });
3141
3090
  }
3091
+ if (tab.automationRunId) forgetWorkerEnd(tab.automationRunId);
3142
3092
  flushSessionSnapshotSave(tab);
3143
3093
  recordHookEvent(tab, 'SessionEnd', tab.sessionId || '(new)');
3094
+ tab.authView?.dispose();
3095
+ tab.terminalAuthView?.dispose();
3144
3096
  tab.closed = true;
3145
3097
  tab.titleRequestController?.abort();
3146
3098
  tab.titleRequestController = null;
@@ -3264,7 +3216,7 @@ function saveTabs() {
3264
3216
  try {
3265
3217
  // A temporary tab is never written: not the tab, its label, its queue or its session id.
3266
3218
  const kept = persistableTabs(_tabs, _activeTabIdx);
3267
- const localTabs = kept.tabs.map(t => ({ id: t.id, sessionId: t.sessionId, label: t.label, titleState: t.titleState === 'generating' ? 'default' : (t.titleState || 'default'), sessionCost: t.sessionCost || 0, running: t.running || false, project: t.project || '', model: t.model || '', effort: t.effort || 'off', planMode: t.planMode || false, ...savedMode(t), toolPolicy: t.toolPolicy || 'full', accountId: t.accountId || '', session: normalizeSession(t.session), transcriptAt: t.transcriptAt || null, planFilePath: t.planFilePath || '', conversation: t.conversation || '', queue: t.queue || [], queuePaused: t.queuePaused || false, automationRunId: t.automationRunId || null, automationActive: !!t.automationActive, automationOwnerId: t.automationOwnerId || null }));
3219
+ const localTabs = kept.tabs.map(t => ({ id: t.id, sessionId: t.sessionId, label: t.label, titleState: t.titleState === 'generating' ? 'default' : (t.titleState || 'default'), sessionCost: t.sessionCost || 0, running: t.running || false, project: t.project || '', model: t.model || '', effort: t.effort || 'off', planMode: t.planMode || false, ...savedMode(t), toolPolicy: t.toolPolicy || 'full', accountId: t.accountId || '', session: normalizeSession(t.session), transcriptAt: t.transcriptAt || null, planFilePath: t.planFilePath || '', conversation: t.conversation || '', queue: t.queue || [], queuePaused: t.queuePaused || false, automationRunId: t.automationRunId || null, automationActive: !!t.automationActive, automationOwnerId: t.automationOwnerId || null, releasedRunId: t.releasedRunId || null }));
3268
3220
  const localRunIds = new Set(localTabs.map((tab) => tab.automationRunId).filter(Boolean));
3269
3221
  let foreignAutomationTabs = [];
3270
3222
  try {
@@ -3406,6 +3358,7 @@ function restoreTabs() {
3406
3358
  if (t && saved.automationRunId) t.automationRunId = saved.automationRunId;
3407
3359
  if (t && saved.automationRunId) t.automationActive = !!saved.automationActive;
3408
3360
  if (t && saved.automationRunId) t.automationOwnerId = saved.automationOwnerId || null;
3361
+ if (t && saved.releasedRunId && !saved.automationRunId) t.releasedRunId = saved.releasedRunId;
3409
3362
  }
3410
3363
  if (_tabs.length) {
3411
3364
  switchTab(Math.min(data.activeIdx || 0, _tabs.length - 1));
@@ -4470,7 +4423,7 @@ function writeSessionSnapshot(tab) {
4470
4423
  const sid = tab?.sessionId || null;
4471
4424
  const $msgs = tab?.messagesEl || null;
4472
4425
  if (!sid || !$msgs) return;
4473
- const html = $msgs.innerHTML || '';
4426
+ const html = providerAuthSnapshot($msgs);
4474
4427
  if (!html.trim()) return;
4475
4428
  // Skip snapshot if the transcript only contains the empty-state placeholder
4476
4429
  if ($msgs.children.length === 1 && $msgs.firstElementChild?.classList?.contains('cp-empty')) return;
@@ -5125,7 +5078,7 @@ function _contextMenuData(tab, { full = false } = {}) {
5125
5078
  folder: tab.init?.cwd || tab.project || '',
5126
5079
  // The mode as the project bar words it (it knows Bypass and "from settings").
5127
5080
  mode: ($mode && !$mode.hidden && $mode.querySelector('.cp-dd-label')?.textContent) || tab.permissionMode || '',
5128
- account: [tab.accountInfo?.email, tab.accountInfo?.organization].filter(Boolean).join(' · '),
5081
+ account: [tab.accountInfo?.authMethod === 'apiKey' ? 'API key' : tab.accountInfo?.email, tab.accountInfo?.organization].filter(Boolean).join(' · '),
5129
5082
  flags: sessionFlags(tab.session, { toolPolicy: tab.toolPolicy, fastModeState: tab.fastModeState, account: tab.accountId }),
5130
5083
  limit: tab.rateLimit?.text || '',
5131
5084
  backgroundTasks: (tab.backgroundWork || []).length,
@@ -5314,6 +5267,7 @@ function _processTabMsg(tab, msg) {
5314
5267
  if (tab === activeTab()) {
5315
5268
  renderStatusline(tab);
5316
5269
  populateModeDropdown(_panel?.querySelector('#cp-mode'), tab);
5270
+ _renderAccountMenu();
5317
5271
  }
5318
5272
  break;
5319
5273
  }
@@ -5537,6 +5491,8 @@ function _processTabMsg(tab, msg) {
5537
5491
  break;
5538
5492
  }
5539
5493
  appendError(tab, msg.message);
5494
+ // The tab's account is gone, or belongs to another conversation: the accounts card is where to choose.
5495
+ if (msg.code === 'account_unavailable' || msg.code === 'account_change_refused') _showAccounts(tab);
5540
5496
  notify('panel', NOTIF_TYPE.ERROR, tab.label || 'Claude Code', { tabId: tab.id });
5541
5497
  if (typeof msg.message === 'string' && /Claude CLI not found|ENOENT/i.test(msg.message)) {
5542
5498
  flagClaudeCliInstallFailure(msg.message);
@@ -7445,7 +7401,57 @@ function handleControlRequest(tab, msg) {
7445
7401
  // What a query tells the bridge about this tab's session: the permission mode
7446
7402
  // (or that the user's own settings should pick it), the tool policy, and what
7447
7403
  // this panel can render. Keys a server that predates them ignores.
7404
+ // ── Greeting (shared/greeting/, lib/greeting/) ──
7405
+ // What SynaBun says when a person comes back and opens a new chat: one or two sentences in the
7406
+ // empty state, written by SynaBun ahead of time (this session's model never greets). Only for a
7407
+ // tab that holds no conversation yet: never a resumed session, a temporary chat or a tab showing
7408
+ // an automation run (hence the short wait: those are marked right after the tab is made). The
7409
+ // server decides whether this opening is a return worth greeting; an older server answers nothing.
7410
+ // Asked from _paintTemporary(), which runs wherever the conversation area is rebuilt. Nothing of it
7411
+ // is kept on the tab object (what a tab carries over, and what is saved of it, is listed elsewhere).
7412
+ const _greetingOf = new WeakMap(); // tab → { seq, host, greeting: { id, session } | null }
7413
+ function _greetingState(tab) {
7414
+ let held = _greetingOf.get(tab);
7415
+ if (!held) { held = { seq: 0, host: null, greeting: null }; _greetingOf.set(tab, held); }
7416
+ return held;
7417
+ }
7418
+ function _offerGreetingSoon(tab) {
7419
+ if (!tab) return;
7420
+ setTimeout(() => { try { _offerGreeting(tab); } catch { /* a tab always opens */ } }, 300);
7421
+ }
7422
+ function _offerGreeting(tab) {
7423
+ if (!_tabs.includes(tab) || tab.sessionId || tab.temporary || tab.automationRunId || tab.automationActive || tab.running) return;
7424
+ const host = tab.messagesEl?.querySelector(':scope > .cp-empty');
7425
+ if (!host || host.dataset.greetingAsked === '1') return;
7426
+ host.dataset.greetingAsked = '1';
7427
+ const held = _greetingState(tab);
7428
+ // The conversation area was rebuilt: a new conversation in the same tab is a new opening.
7429
+ if (held.host && held.host !== host) { held.seq += 1; held.greeting = null; }
7430
+ held.host = host;
7431
+ const session = `${tab.id}.${held.seq}`;
7432
+ const cwd = tab.project || ddGetValue(_panel?.querySelector('#cp-project')) || '';
7433
+ mountGreeting(host, {
7434
+ surface: 'claude-panel', session, cwd,
7435
+ onOpenMemory: (id) => { if (id) emit('memory:open', { id }); },
7436
+ isCurrent: () => !tab.sessionId && !tab.temporary && !tab.automationRunId && held.host === host && tab.messagesEl?.contains(host),
7437
+ }).then((greeting) => {
7438
+ if (held.host === host) held.greeting = greeting ? { id: greeting.id, session } : null;
7439
+ }).catch(() => {});
7440
+ }
7441
+
7442
+ // The greeting a chat opened with goes back once, with its first prompt: the bridge tells the model
7443
+ // what was said (capability 'greeting'). A temporary chat names nothing, so it takes none.
7444
+ function _echoGreeting(tab, msg) {
7445
+ const held = msg.type === 'query' ? _greetingOf.get(tab) : null;
7446
+ if (!held?.greeting?.id) return;
7447
+ if (hasCapability(tab, 'greeting') && !tab.temporary) msg.greetingId = held.greeting.id;
7448
+ forgetGreeting('claude-panel', held.greeting.session);
7449
+ held.greeting = null;
7450
+ }
7451
+
7448
7452
  function _applySessionOptions(tab, msg) {
7453
+ // The greeting this chat opened with goes back once, with its first prompt.
7454
+ _echoGreeting(tab, msg);
7449
7455
  // The tab's own mode (statedMode() in cp/cp-permission-model.js: Bypass only
7450
7456
  // as the user's recorded pick). A tab that never picked one leaves it to the
7451
7457
  // user's Claude Code settings (permissions.defaultMode).
@@ -7471,6 +7477,8 @@ function _applySessionOptions(tab, msg) {
7471
7477
  function _paintTemporary(tab) {
7472
7478
  if (!tab) return;
7473
7479
  paintTemporary(tab, { capable: hasCapability(tab, TEMPORARY_CAPABILITY), onToggle: (on) => _chooseTemporary(tab, on) });
7480
+ // The conversation area was (re)built: a new chat may open with SynaBun's greeting.
7481
+ _offerGreetingSoon(tab);
7474
7482
  }
7475
7483
 
7476
7484
  // The user's choice on a new tab. A process kept ready for the other kind of
@@ -7506,6 +7514,7 @@ function _afterTemporary(tab, sid, label) {
7506
7514
  for (const waiting of tab._sessionRequests?.values() || []) { clearTimeout(waiting.timer); try { waiting.reject(new Error('The temporary chat has ended.')); } catch {} }
7507
7515
  // An open history search lists its prompts.
7508
7516
  if (tab === activeTab()) _panel?.querySelector('.cp-rsearch')?.remove();
7517
+ tab.terminalAuthView?.dispose();
7509
7518
  tabAfterTemporary(tab, newTabState(sid, label || 'New chat', tab.id));
7510
7519
  }
7511
7520
 
@@ -8556,47 +8565,149 @@ function _showUsage(tab) {
8556
8565
  }).catch(err => appendInfoCard(tab, { title: 'Usage', kind: 'info', body: err.message }));
8557
8566
  }
8558
8567
 
8559
- // /account: the Claude accounts set up in SynaBun, and which one this tab uses.
8568
+ // ── Claude accounts ──
8569
+ // Every account and its state come from the server (lib/claude-accounts.js
8570
+ // describe(): its mode, whether it is signed in, its identity: an email or a
8571
+ // masked key, never a key); account-view.js decides the words. One list for
8572
+ // every tab, and one place to choose and manage accounts: the account menu in
8573
+ // the header (account-menu.js, the same component as the Codex panel's).
8574
+ // /account, /login and the sign-in screen's "Other accounts…" open it.
8575
+ // `claude:accounts-changed` (any window, any surface) reloads the list.
8560
8576
  // A conversation belongs to the account it started under (its transcript lives
8561
- // in that account's config directory), so the account is chosen before the
8562
- // first message of a tab.
8563
- function _showAccounts(tab, wanted = '') {
8564
- if (!hasCapability(tab, 'accounts')) { appendStatus(tab, 'Choosing an account needs a newer SynaBun server: restart it to enable this.'); return; }
8565
- fetch('/api/claude/accounts').then(r => r.json()).then((d) => {
8566
- const accounts = Array.isArray(d?.accounts) ? d.accounts : [];
8567
- const pick = (account) => {
8568
- const id = account.isDefault ? '' : account.id;
8569
- if (id === (tab.accountId || '')) { appendStatus(tab, `This tab already uses ${account.label}.`); return; }
8570
- if (tab.sessionId) { appendStatus(tab, 'This conversation belongs to the account it started under. Start a new chat (or a new tab) to use another account.'); return; }
8571
- if (!account.loggedIn) { appendStatus(tab, `${account.label} is not signed in yet. Sign it in from Settings first.`); return; }
8572
- tab.accountId = id;
8573
- saveTabs();
8574
- if (tab === activeTab()) renderStatusline(tab);
8575
- appendStatus(tab, `This tab runs under ${account.label}${account.email ? ` (${account.email})` : ''}.`);
8576
- };
8577
- if (wanted) {
8577
+ // in that account's config directory): a tab with one opens another account in
8578
+ // a new chat, and the tab's own account changes only before its first message.
8579
+ let _claudeAccountRows = null;
8580
+ let _claudeAccountsLoad = null;
8581
+ let _claudeAccountsError = '';
8582
+ let _accountMenu = null;
8583
+ function _loadClaudeAccounts({ force = false, refresh = false } = {}) {
8584
+ if (!force && !refresh && _claudeAccountRows) return Promise.resolve(_claudeAccountRows);
8585
+ if (_claudeAccountsLoad && !refresh) return _claudeAccountsLoad;
8586
+ const load = fetch(`/api/claude/accounts${refresh ? '?refresh=true' : ''}`, { cache: 'no-store' })
8587
+ .then(r => r.ok ? r.json() : Promise.reject(new Error(accountText('readFailed'))))
8588
+ .then((d) => {
8589
+ _claudeAccountRows = Array.isArray(d?.accounts) ? d.accounts : [];
8590
+ _claudeAccountsError = '';
8591
+ _claudeAccountsChanged();
8592
+ return _claudeAccountRows;
8593
+ })
8594
+ .catch((error) => { _claudeAccountsError = accountText('readFailed'); _claudeAccountsChanged(); throw error; })
8595
+ .finally(() => { if (_claudeAccountsLoad === load) _claudeAccountsLoad = null; });
8596
+ _claudeAccountsLoad = load;
8597
+ return load;
8598
+ }
8599
+ function _claudeAccountRow(id) {
8600
+ return (_claudeAccountRows || []).find(row => row.id === serverAccountId(id)) || null;
8601
+ }
8602
+ function _claudeAccountsChanged() {
8603
+ _renderAccountMenu();
8604
+ }
8605
+
8606
+ // The header account menu: the active tab's account and what it runs on, every account, and their
8607
+ // management (add, sign in with Claude, an API key, the "Uses" switch, sign out, rename, remove).
8608
+ function _mountAccountMenu() {
8609
+ if (_accountMenu || !_panel) return _accountMenu;
8610
+ const actions = _panel.querySelector('.cp-header-actions');
8611
+ if (!actions) return null;
8612
+ const path = id => `/api/claude/accounts/${encodeURIComponent(id)}`;
8613
+ _accountMenu = mountAccountMenu({
8614
+ provider: 'claude', container: actions, before: actions.firstChild, layer: () => _panel,
8615
+ read: () => {
8616
+ const tab = activeTab();
8617
+ return {
8618
+ rows: _claudeAccountRows || [], currentId: tab?.accountId || '', hasConversation: !!tab?.sessionId,
8619
+ available: !!tab && hasCapability(tab, 'accounts') && !tab.automationActive,
8620
+ busy: !!(tab?.running || tab?.automationActive), loading: !_claudeAccountRows, error: _claudeAccountsError,
8621
+ };
8622
+ },
8623
+ actions: {
8624
+ load: ({ refresh }) => _loadClaudeAccounts({ force: true, refresh }),
8625
+ pick: (id) => { const tab = activeTab(); if (tab) _pickAccount(tab, id); },
8626
+ add: async () => (await _claudeAuthRequest('/api/claude/accounts', { method: 'POST', body: '{}' })).account,
8627
+ signIn: (id) => startProviderTerminalLogin('claude', id, `Claude sign-in · ${_claudeAccountRow(id)?.label || id}`),
8628
+ saveKey: (id, key) => providerApiKeyRequest('claude', id, key),
8629
+ setMode: (id, mode) => _claudeAuthRequest(path(id), { method: 'PATCH', body: JSON.stringify({ mode }) }),
8630
+ signOut: (id, credential) => _claudeAuthRequest(`${path(id)}/auth/logout`, { method: 'POST', body: JSON.stringify({ credential }) }),
8631
+ rename: (id, label) => _claudeAuthRequest(path(id), { method: 'PATCH', body: JSON.stringify({ label }) }),
8632
+ remove: (id) => _claudeAuthRequest(path(id), { method: 'DELETE' }),
8633
+ },
8634
+ });
8635
+ return _accountMenu;
8636
+ }
8637
+ function _renderAccountMenu() {
8638
+ if (!_accountMenu) return;
8639
+ const tab = activeTab();
8640
+ if (tab && hasCapability(tab, 'accounts') && !_claudeAccountRows && !_claudeAccountsLoad) _loadClaudeAccounts().catch(() => {});
8641
+ _accountMenu.render();
8642
+ }
8643
+
8644
+ // Pick an account for a tab: now when it has no conversation yet, else in a new chat (a new tab).
8645
+ function _pickAccount(tab, id) {
8646
+ const rows = _claudeAccountRows || [];
8647
+ const plan = accountSwitchPlan({ rows, currentId: tab.accountId || '', targetId: id, hasConversation: !!tab.sessionId });
8648
+ const target = _claudeAccountRow(id);
8649
+ if (plan.effect === 'missing' || plan.effect === 'same') { appendStatus(tab, plan.message); return; }
8650
+ if (plan.effect === 'next-chat') {
8651
+ if (_tabs.length >= MAX_TABS) { appendStatus(tab, `${plan.message} ${accountText('tooManyTabs')}`); return; }
8652
+ const fresh = createTab(null, 'New chat');
8653
+ fresh.accountId = tabAccountId(id);
8654
+ _ensureClaudeTerminalAuth(fresh);
8655
+ saveTabs();
8656
+ if (fresh === activeTab()) _renderAccountMenu();
8657
+ appendStatus(fresh, accountText('newChatUnder', { label: target ? accountName(target) : id }));
8658
+ return;
8659
+ }
8660
+ tab.accountId = tabAccountId(id);
8661
+ _ensureClaudeTerminalAuth(tab);
8662
+ saveTabs();
8663
+ if (tab === activeTab()) { renderStatusline(tab); _renderAccountMenu(); }
8664
+ appendStatus(tab, plan.message);
8665
+ }
8666
+
8667
+ // /account (and /login, and the sign-in screen's "Other accounts…"): the header account menu.
8668
+ // `/account <name>` picks that account at once; `manage` opens one account's management.
8669
+ function _showAccounts(tab, wanted = '', { manage = '' } = {}) {
8670
+ if (!hasCapability(tab, 'accounts')) { appendStatus(tab, accountText('needsServer')); return; }
8671
+ if (wanted) {
8672
+ _loadClaudeAccounts({ force: true }).then((accounts) => {
8578
8673
  const w = wanted.trim().toLowerCase();
8579
- const match = accounts.find(a => a.id.toLowerCase() === w || String(a.label || '').toLowerCase() === w || String(a.email || '').toLowerCase() === w);
8674
+ const match = accounts.find(a => a.id.toLowerCase() === w || String(a.label || '').toLowerCase() === w || String(a.subscription?.email || '').toLowerCase() === w);
8580
8675
  if (!match) { appendStatus(tab, `No account "${wanted}". Use /account to list them.`); return; }
8581
- pick(match);
8582
- return;
8583
- }
8584
- const current = tab.accountId || 'default';
8585
- const html = accounts.map(a => `<div class="cp-task-row" data-account="${esc(a.id)}"><div class="cp-task-head">
8586
- <span class="cp-task-title">${esc(a.label)}${a.id === current ? ' ✓' : ''}</span>
8587
- <span class="cp-chip ${a.loggedIn ? 'cp-chip-ok' : 'cp-chip-warn'}">${a.loggedIn ? 'signed in' : 'not signed in'}</span>
8588
- ${a.id !== current ? '<button type="button" class="cp-engine-retry" data-use>Use for this tab</button>' : ''}</div>
8589
- <div class="cp-task-stats">${esc([a.email, a.organization].filter(Boolean).join(' · ') || 'no identity yet')}</div></div>`).join('')
8590
- + '<div class="cp-info-row muted">Accounts are added and signed in from Settings. The default account is what Claude Code uses in a terminal.</div>';
8591
- const card = appendInfoCard(tab, { title: 'Claude accounts', kind: 'info', html });
8592
- card?.querySelectorAll('[data-use]').forEach((btn) => {
8593
- btn.addEventListener('click', () => {
8594
- const id = btn.closest('[data-account]')?.dataset.account;
8595
- const account = accounts.find(a => a.id === id);
8596
- if (account) pick(account);
8597
- });
8598
- });
8599
- }).catch(() => appendStatus(tab, 'Could not read the accounts.'));
8676
+ _pickAccount(tab, match.id);
8677
+ }).catch(() => appendStatus(tab, accountText('readFailed')));
8678
+ return;
8679
+ }
8680
+ if (tab !== activeTab()) return;
8681
+ _mountAccountMenu()?.open(manage ? { manage } : {});
8682
+ }
8683
+
8684
+ async function _claudeAuthRequest(url, options = {}) {
8685
+ const response = await fetch(url, { ...options, cache: 'no-store', headers: { 'Content-Type': 'application/json' } });
8686
+ const data = await response.json().catch(() => ({}));
8687
+ if (!response.ok || data.error) throw new Error(data.error || 'Claude authentication request failed.');
8688
+ return data;
8689
+ }
8690
+
8691
+ function _ensureClaudeTerminalAuth(tab) {
8692
+ const accountId = tab.accountId || 'default';
8693
+ if (tab.closed || !tab.messagesEl) return;
8694
+ if (tab.terminalAuthView && tab.terminalAuthAccount === accountId) { void tab.terminalAuthView.refresh(); return; }
8695
+ tab.terminalAuthView?.dispose();
8696
+ tab.terminalAuthAccount = accountId;
8697
+ const named = accountId !== 'default' ? _claudeAccountRow(accountId) : null;
8698
+ tab.terminalAuthView = mountProviderTerminalAuth({
8699
+ host: tab.messagesEl, provider: 'claude', accountId, logo: CLAUDE_ICON,
8700
+ ...(named ? { message: `Sign in to Claude to use ${named.label}.` } : {}),
8701
+ // Another account (or a new one) is one click away from the sign-in screen: the header account menu.
8702
+ moreAction: { label: accountText('otherAccounts'), run: () => _showAccounts(tab) },
8703
+ isCurrent: () => !tab.closed && (tab.accountId || 'default') === accountId,
8704
+ readAccount: async () => {
8705
+ const account = (await _claudeAuthRequest(`/api/claude/accounts/${encodeURIComponent(accountId)}/auth?refresh=true`)).account;
8706
+ if (!tab.closed && (tab.accountId || 'default') === accountId) tab.accountInfo = account;
8707
+ return account;
8708
+ },
8709
+ onSignedIn: () => { _loadClaudeAccounts({ force: true }).catch(() => {}); },
8710
+ });
8600
8711
  }
8601
8712
 
8602
8713
  // /mcp: every server of the session with its state, and what can be done about it.
@@ -9115,8 +9226,15 @@ function runSlashCommand(tab, raw) {
9115
9226
  return true;
9116
9227
  }
9117
9228
  case 'cost': { clearInput(); emit('cost:toggle'); return true; }
9118
- case 'login': { clearInput(); window.open('https://claude.ai/login', '_blank'); return true; }
9119
- case 'logout': { clearInput(); window.open('https://claude.ai/logout', '_blank'); return true; }
9229
+ case 'login': { clearInput(); _showAccounts(tab, '', { manage: serverAccountId(tab.accountId) }); return true; }
9230
+ case 'logout': {
9231
+ clearInput();
9232
+ if (tab.running || tab.automationActive) { appendStatus(tab, 'Finish or stop the current turn before signing out.'); return true; }
9233
+ void _claudeAuthRequest(`/api/claude/accounts/${encodeURIComponent(tab.accountId || 'default')}/auth/logout`, { method: 'POST', body: '{}' })
9234
+ .then(() => { tab.accountInfo = null; tab.terminalAuthView?.update({ loggedIn: false }); })
9235
+ .catch(() => appendStatus(tab, 'Could not sign out. Try again.'));
9236
+ return true;
9237
+ }
9120
9238
  case 'plan': {
9121
9239
  clearInput();
9122
9240
  // The one path for plan mode: on is the user's pick of Plan, off goes back
@@ -9138,8 +9256,9 @@ function runSlashCommand(tab, raw) {
9138
9256
  function send({ shift = false } = {}) {
9139
9257
  const tab = activeTab();
9140
9258
  if (!tab) return;
9141
- if ((_cliInstalled === null || _cliInstallFailureForced) && !tab.automationActive) {
9142
- notify({ type: NOTIF_TYPE.WARN, title: 'Claude CLI not installed', body: 'Install the Claude Code CLI before sending a message.' });
9259
+ if (_cliBlocked() && !tab.automationActive) {
9260
+ // The banner above the transcript says why, and offers the way out.
9261
+ _cliBanner?.nudge();
9143
9262
  return;
9144
9263
  }
9145
9264
  const $input = _panel?.querySelector('#cp-input');
@@ -9228,6 +9347,9 @@ function send({ shift = false } = {}) {
9228
9347
  // A temporary chat that is over, or one the running server would save: nothing is sent.
9229
9348
  { const refusal = temporaryRefusal(tab, { capable: hasCapability(tab, TEMPORARY_CAPABILITY) }); if (refusal) { appendStatus(tab, refusal); return; } }
9230
9349
 
9350
+ // A message of the user's own in the tab of a run that ended: the tab is theirs from here on.
9351
+ releaseAutomationForManualUse(tab);
9352
+
9231
9353
  // ── !bash shortcut — prefix a Bash-tool hint so Claude executes the command ──
9232
9354
  if (text.startsWith('!') && text.length > 1) {
9233
9355
  const cmd = text.slice(1).trim();
@@ -10030,116 +10152,50 @@ function syncReservedWidth(knownWidth) {
10030
10152
  }
10031
10153
 
10032
10154
  // ── CLI installation status (banner + send-block) ──
10033
- let _cliInstallFailureForced = false;
10034
- let _cliFailureReason = null;
10035
-
10036
- function _ensureCliBannerEl() {
10037
- if (!_panel) return null;
10038
- const container = _panel.querySelector('#cp-messages-container');
10039
- if (!container || !container.parentNode) return null;
10040
- let banner = container.parentNode.querySelector(':scope > .cp-cli-banner');
10041
- if (banner) { _renderCliBannerContent(banner); return banner; }
10042
- const url = getCliDocUrl('claude-code');
10043
- banner = document.createElement('div');
10044
- banner.className = 'cp-cli-banner';
10045
- banner.innerHTML = `
10046
- <div class="cp-cli-banner-icon">!</div>
10047
- <div class="cp-cli-banner-text">
10048
- <div class="cp-cli-banner-title"></div>
10049
- <div class="cp-cli-banner-body"></div>
10050
- </div>
10051
- <div class="cp-cli-banner-actions">
10052
- <a class="cp-cli-banner-link" href="${esc(url || '#')}" target="_blank" rel="noopener noreferrer">Install guide</a>
10053
- <button class="cp-cli-banner-recheck" type="button">Re-check</button>
10054
- </div>
10055
- `;
10056
- container.parentNode.insertBefore(banner, container);
10057
- const btn = banner.querySelector('.cp-cli-banner-recheck');
10058
- btn?.addEventListener('click', async () => {
10059
- btn.disabled = true;
10060
- btn.textContent = 'Checking…';
10061
- try { await recheckCliStatus('claude-code'); }
10062
- finally {
10063
- if (btn.isConnected) {
10064
- btn.disabled = false;
10065
- btn.textContent = 'Re-check';
10066
- }
10067
- }
10068
- });
10069
- _renderCliBannerContent(banner);
10070
- return banner;
10071
- }
10072
-
10073
- // Distinguish "never installed" from "installed but would not launch" —
10074
- // telling the user to install something already present sends them chasing a
10075
- // reinstall they do not need.
10076
- function _renderCliBannerContent(banner) {
10077
- const titleEl = banner.querySelector('.cp-cli-banner-title');
10078
- const bodyEl = banner.querySelector('.cp-cli-banner-body');
10079
- if (!titleEl || !bodyEl) return;
10080
- const label = getCliLabel('claude-code');
10081
- bodyEl.replaceChildren();
10082
-
10083
- if (_cliInstalled === null) {
10084
- titleEl.textContent = `${label} CLI not installed`;
10085
- bodyEl.append('Run ');
10086
- const code = document.createElement('code');
10087
- code.textContent = getCliInstallCommand('claude-code');
10088
- bodyEl.append(code, ' or follow the install guide.');
10089
- return;
10090
- }
10091
-
10092
- titleEl.textContent = `${label} CLI failed to start`;
10093
- bodyEl.append(`${label} v${_cliInstalled} is installed, but it could not be launched. `);
10094
- if (_cliFailureReason) {
10095
- const code = document.createElement('code');
10096
- code.textContent = _cliFailureReason;
10097
- bodyEl.append(code);
10098
- } else {
10099
- bodyEl.append('Check the CLI path in Settings, then re-check.');
10155
+ // One controller (ui-cli-banner.js) draws the banner above the transcript while
10156
+ // Claude Code is not usable: not installed (with Install, which runs Anthropic's
10157
+ // own installer on the server), being installed, failed to install, or found but
10158
+ // failed to start (with the error it gave). It also holds the class that keeps
10159
+ // sending off. What it says is cli-install-view.js (`cliInstall.*` strings).
10160
+ function _cliBannerController() {
10161
+ if (!_cliBanner) {
10162
+ _cliBanner = mountCliBanner({
10163
+ toolKey: 'claude-code',
10164
+ prefix: 'cp',
10165
+ panel: () => _panel,
10166
+ anchor: () => _panel?.querySelector('#cp-messages-container'),
10167
+ onBlockedChange: (blocked) => {
10168
+ const $send = _panel?.querySelector('#cp-send');
10169
+ if (!$send) return;
10170
+ if (blocked) { $send.disabled = true; return; }
10171
+ const tab = activeTab();
10172
+ const $input = _panel?.querySelector('#cp-input');
10173
+ if (tab?.running) $send.disabled = false;
10174
+ else $send.disabled = !$input?.value.trim() && !tab?.attachedImages?.length && !tab?.attachedFiles?.length;
10175
+ },
10176
+ });
10100
10177
  }
10178
+ return _cliBanner;
10101
10179
  }
10102
10180
 
10103
- function _removeCliBannerEl() {
10104
- if (!_panel) return;
10105
- const banner = _panel.querySelector(':scope > .cp-cli-banner');
10106
- banner?.remove();
10181
+ /** Sending is held back: Claude Code is missing, broken, failed to install, or being installed. */
10182
+ function _cliBlocked() {
10183
+ return !!_cliBanner?.blocked();
10107
10184
  }
10108
10185
 
10109
10186
  function _refreshCliBanners() {
10110
10187
  if (!_panel) return;
10111
- const missing = _cliInstalled === null || _cliInstallFailureForced;
10112
- _panel.classList.toggle('cp-cli-blocked', missing);
10113
- if (missing) _ensureCliBannerEl();
10114
- else _removeCliBannerEl();
10115
- const $send = _panel.querySelector('#cp-send');
10116
- if ($send && missing) $send.disabled = true;
10188
+ _cliBannerController().refresh();
10117
10189
  }
10118
10190
 
10191
+ // A session could not start Claude Code: "failed to start" with its words at
10192
+ // once, then the server is asked again and its answer decides what stays.
10119
10193
  export function flagClaudeCliInstallFailure(reason = null) {
10120
- _cliInstallFailureForced = true;
10121
- _cliFailureReason = reason ? String(reason).slice(0, 300) : null;
10122
- _refreshCliBanners();
10123
- // Clear from the RETURNED value, not the subscriber callback: the callback
10124
- // only fires when the version string changes, and a launch failure does not
10125
- // change it, so the banner would otherwise latch permanently.
10126
- recheckCliStatus('claude-code')
10127
- .then((info) => {
10128
- if (!info?.installed) return;
10129
- _cliInstallFailureForced = false;
10130
- _cliFailureReason = null;
10131
- _refreshCliBanners();
10132
- })
10133
- .catch(() => {});
10194
+ _cliBannerController().flagFailure(reason);
10134
10195
  }
10135
10196
 
10136
10197
  function _ensureCliSubscription() {
10137
- if (_cliUnsub) return;
10138
- _cliUnsub = subscribeCliStatus('claude-code', (info) => {
10139
- _cliInstalled = info?.installed || null;
10140
- if (_cliInstalled) { _cliInstallFailureForced = false; _cliFailureReason = null; }
10141
- _refreshCliBanners();
10142
- });
10198
+ _cliBannerController();
10143
10199
  }
10144
10200
 
10145
10201
  // ── Delegated transcript actions ──
@@ -10162,15 +10218,7 @@ function _onMessagesClick(e) {
10162
10218
  });
10163
10219
  return;
10164
10220
  }
10165
- const link = e.target.closest('a.file-link');
10166
- if (link) {
10167
- e.preventDefault();
10168
- const path = (link.textContent || '').replace(/:\d+$/, '');
10169
- navigator.clipboard.writeText(path);
10170
- link.style.opacity = '0.5';
10171
- setTimeout(() => { link.style.opacity = ''; }, 300);
10172
- return;
10173
- }
10221
+ // File links (a.file-link, and the links of a message's Markdown) are taken by _fileLinkOptions below.
10174
10222
  const hdr = e.target.closest('.tool-hdr');
10175
10223
  if (hdr && !e.target.closest('button, a, input, select, textarea')) {
10176
10224
  const card = hdr.closest('.tool-card');
@@ -10183,6 +10231,25 @@ function _onMessagesClick(e) {
10183
10231
  }
10184
10232
  }
10185
10233
 
10234
+ // ── File links ──
10235
+ // A link to a file the code editor can open opens it there (ui-file-links.js, shared with the other panels).
10236
+ // The same delegation as above: it covers streaming, finished and snapshot-restored messages of every tab.
10237
+ const _fileLinkOptions = {
10238
+ // A relative path is a path in the folder of the tab the message belongs to.
10239
+ cwd: (link) => {
10240
+ const tab = _tabs.find((t) => t.messagesEl?.contains(link)) || activeTab();
10241
+ return tab?.init?.cwd || tab?.project || '';
10242
+ },
10243
+ textLinks: 'a.file-link',
10244
+ // A path the editor does not open keeps what a click on it did: it is copied.
10245
+ copyPath: (link) => {
10246
+ const path = (link.textContent || '').replace(/:\d+$/, '');
10247
+ link.style.opacity = '0.5';
10248
+ setTimeout(() => { link.style.opacity = ''; }, 300);
10249
+ return navigator.clipboard.writeText(path);
10250
+ },
10251
+ };
10252
+
10186
10253
  // ── Public API ──
10187
10254
  async function _ensureClaudePanelReady() {
10188
10255
  if (!_panel) {
@@ -10197,6 +10264,7 @@ async function _ensureClaudePanelReady() {
10197
10264
  });
10198
10265
  wireEvents();
10199
10266
  _panel.querySelector('#cp-messages-container')?.addEventListener('click', _onMessagesClick);
10267
+ wireFileLinks(_panel.querySelector('#cp-messages-container'), _fileLinkOptions);
10200
10268
  await loadConfig(); // must complete before restoreTabs — checks server boot ID
10201
10269
  loadSkills();
10202
10270
  restoreTabs();
@@ -10248,6 +10316,18 @@ export async function attachClaudeAutomation(run, { focus = !!run?.focus, isCanc
10248
10316
  if (!run?.runId || !run?.providerSessionId) return { ok: false, reason: 'identity_pending' };
10249
10317
  await _ensureClaudePanelReady();
10250
10318
  if (isCancelled()) return { ok: false, reason: 'dismissed' };
10319
+ // The user continued this run's tab by hand after the run ended: it stays theirs. The dock's
10320
+ // Focus raises it; the run is let go again, and nothing in the tab changes.
10321
+ const own = _tabs.find((entry) => !entry.automationRunId && entry.releasedRunId === run.runId && entry.sessionId === run.providerSessionId);
10322
+ if (own && !(run.status === 'starting' || run.status === 'running')) {
10323
+ window.dispatchEvent(new CustomEvent('native-loop:dismissed', { detail: { runId: run.runId } }));
10324
+ if (focus) {
10325
+ if (!_visible) await toggleClaudePanel();
10326
+ const ownIdx = _tabs.indexOf(own);
10327
+ if (ownIdx >= 0) switchTab(ownIdx);
10328
+ }
10329
+ return { ok: true, tabId: own.id, released: true };
10330
+ }
10251
10331
  let tab = _tabs.find((entry) => entry.automationRunId === run.runId)
10252
10332
  || _tabs.find((entry) => entry.sessionId === run.providerSessionId);
10253
10333
  if (!tab) {
@@ -10270,6 +10350,7 @@ export async function attachClaudeAutomation(run, { focus = !!run?.focus, isCanc
10270
10350
  tab.automationRunId = run.runId;
10271
10351
  tab.automationActive = running;
10272
10352
  tab.automationOwnerId = nativeLoopWindowId;
10353
+ tab.releasedRunId = null;
10273
10354
  tab.sdkMode = true;
10274
10355
  tab.sessionId = run.providerSessionId;
10275
10356
  tab.label = run.title || tab.label || 'Automation';
@@ -10288,10 +10369,12 @@ export async function attachClaudeAutomation(run, { focus = !!run?.focus, isCanc
10288
10369
  return { ok: false, reason: 'dismissed' };
10289
10370
  }
10290
10371
  }
10291
- if (run.status === 'failed' && run.error && tab._automationLifecycleError !== run.error) {
10372
+ // (A dispatched worker's failure, with its reason, is on its end card.)
10373
+ if (run.status === 'failed' && run.error && !isDispatchedRun(run) && tab._automationLifecycleError !== run.error) {
10292
10374
  tab._automationLifecycleError = run.error;
10293
10375
  appendError(tab, run.error);
10294
10376
  }
10377
+ trackWorkerEnd(run, { apply: (view) => showWorkerEnd(tab, view), busy: hasLiveBackgroundWork(tab) });
10295
10378
  // The mode control is hidden while the run is active and back when it ended.
10296
10379
  if (tab === activeTab()) populateModeDropdown(_panel?.querySelector('#cp-mode'), tab);
10297
10380
  const idx = _tabs.indexOf(tab);
@@ -10319,10 +10402,60 @@ export function handleClaudeAutomationEvent(runId, payload) {
10319
10402
  return true;
10320
10403
  }
10321
10404
  if (event.type === 'synabun.error') {
10405
+ // Every run's end disposes its SDK query, and the SDK says so in these words: not a failure.
10406
+ if (isEndOfRunAbort(event.message)) return true;
10322
10407
  appendError(tab, event.message || 'Claude Code automation failed');
10323
10408
  return true;
10324
10409
  }
10325
10410
  handleTabMsg(tab, { type: 'event', event, fromRun: true });
10411
+ // Background agents still running keep a warm worker's "Task complete" away.
10412
+ if (event.type === 'system' || event.type === 'result') updateWorkerEndBusy(runId, hasLiveBackgroundWork(tab));
10413
+ return true;
10414
+ }
10415
+
10416
+ function hasLiveBackgroundWork(tab) {
10417
+ for (const task of tab?.tasks?.values?.() || []) {
10418
+ if (task && !task.ambient && isTaskRunning(task)) return true;
10419
+ }
10420
+ return false;
10421
+ }
10422
+
10423
+ // The end of a worker the Assistant dispatched (ui-worker-end.js): one card,
10424
+ // the last row of its transcript. Its button closes this tab the way the X
10425
+ // does (a warm worker that finished its task is completed, one still at work
10426
+ // is stopped; its record stays for the Assistant) and shows the Assistant
10427
+ // session that dispatched it.
10428
+ function showWorkerEnd(tab, view) {
10429
+ tab._workerEnd = view || null;
10430
+ const $msgs = tab.messagesEl;
10431
+ if (!$msgs || tab.closed) return;
10432
+ $msgs.querySelectorAll(`.${WORKER_END_CLASS}`).forEach((el) => el.remove());
10433
+ if (!view) return;
10434
+ $msgs.appendChild(buildWorkerEndCard(view, {
10435
+ classes: { card: 'cp-worker-end', title: 'post-plan-header', actions: 'post-plan-actions', button: 'post-plan-action pp-primary' },
10436
+ onBack: () => {
10437
+ const idx = _tabs.indexOf(tab);
10438
+ if (idx >= 0) closeTab(idx);
10439
+ backToAssistant(view.assistantSessionId);
10440
+ },
10441
+ }));
10442
+ if (tab === activeTab()) scrollEnd(true);
10443
+ }
10444
+
10445
+ // The user sends a message of their own in the tab of a run that ended (Codex's
10446
+ // releaseAutomationForManualUse): the run's end card goes, the tab stops following
10447
+ // the run, and the router leaves the run alone, so nothing of it comes back here.
10448
+ // A run that is still going keeps its tab (the composer refuses a manual turn then).
10449
+ function releaseAutomationForManualUse(tab) {
10450
+ if (!tab?.automationRunId || tab.automationActive) return false;
10451
+ const runId = tab.automationRunId;
10452
+ forgetWorkerEnd(runId);
10453
+ showWorkerEnd(tab, null);
10454
+ tab.automationRunId = null;
10455
+ tab.automationOwnerId = null;
10456
+ tab.releasedRunId = runId;
10457
+ window.dispatchEvent(new CustomEvent('native-loop:dismissed', { detail: { runId } }));
10458
+ saveTabs();
10326
10459
  return true;
10327
10460
  }
10328
10461
 
@@ -11051,6 +11184,13 @@ function wireEvents() {
11051
11184
  const $close = _panel.querySelector('.cp-close-btn');
11052
11185
  const $minimize = _panel.querySelector('.cp-minimize-btn');
11053
11186
  const $slide = _panel.querySelector('.cp-slide-btn');
11187
+ _mountAccountMenu();
11188
+ // Any window or surface changed an account (signed in, a key saved, its mode, renamed, added,
11189
+ // removed): the account menu re-reads the list, and the tabs on it re-check sign-in.
11190
+ on('sync:claude:accounts-changed', (msg) => {
11191
+ _loadClaudeAccounts({ force: true }).catch(() => {});
11192
+ for (const t of _tabs) if (!msg?.accountId || serverAccountId(t.accountId) === msg.accountId) void t.terminalAuthView?.refresh();
11193
+ });
11054
11194
  const $new = _panel.querySelector('.cp-new-btn');
11055
11195
 
11056
11196
  const $brand = _panel.querySelector('.cp-brand');