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
@@ -16,12 +16,14 @@
16
16
  // open. `synabun.route` events (phase card|auto|decided|pending|expired|
17
17
  // declined|cancelled) update the line in place by routeId.
18
18
 
19
- import { clampEffortId, getEffortLevelsForModel, modelEffortIds } from '../agent-runtime-options.js';
20
19
  import { getProviderMeta } from '../provider-icons.js';
21
- import { fetchManageCatalog, getRouting, patchHiddenModels, putRouting } from './asst-api.js';
22
- import { catalogModelRow, fmtModelPrice, hiddenModelFor, ICON_CARET, invalidateModelCatalog, loadModelCatalog, openModelMenu } from './asst-brain-picker.js';
20
+ import { fetchManageCatalog, patchHiddenModels } from './asst-api.js';
21
+ import { catalogModelRow, fmtModelPrice, hiddenModelFor, ICON_CARET, invalidateModelCatalog, openModelMenu } from './asst-brain-picker.js';
22
+ // The Routes editor's model lists (where they come from, how they stay current): asst-route-models.js.
23
23
  import { closeMenu, isMenuOpen, openMenu } from './asst-menu.js';
24
24
  import { decorateControlHead } from './asst-control.js';
25
+ import { accountSummary } from '../account-view.js';
26
+ import { accountUsageText, autoAccountLabel } from '../account-usage.js';
25
27
  import {
26
28
  DEFAULT_ROUTE_MODE,
27
29
  fmtConfidence,
@@ -39,6 +41,18 @@ function obj(v) { return v && typeof v === 'object' && !Array.isArray(v) ? v : n
39
41
  function arr(v) { return Array.isArray(v) ? v : []; }
40
42
  function firstStr(...values) { for (const v of values) { const s = str(v); if (s) return s; } return ''; }
41
43
 
44
+ export function routeDisplayAccount(target, t = null) {
45
+ if (target?.accountId === 'auto') return autoAccountLabel(t);
46
+ if (target?.account) return [accountSummary(target.account, target.provider === 'codex' ? 'codex' : 'claude'), accountUsageText(target.account, t)].join(' · ');
47
+ return target?.accountId ? tf(t)('assistant.routes.account.removed', '{id} · account removed', { id: target.accountId }) : '';
48
+ }
49
+ function routeAccountError(error, t) {
50
+ if (error?.code === 'ACCOUNT_NO_SUBSCRIPTION') return tf(t)('assistant.accountUsage.noSubscription', 'Auto requires a signed-in subscription account.');
51
+ return error?.code === 'ACCOUNT_NOT_FOUND'
52
+ ? tf(t)('assistant.routes.account.removedHint', 'Account removed. Save another account choice before running this route.')
53
+ : tf(t)('assistant.routes.account.signedOut', 'Signed out. Sign in from the provider account menu before running this route.');
54
+ }
55
+
42
56
  /** Local translator: hooks.t when it resolves, else the fallback with {param} interpolation. */
43
57
  function tf(t) {
44
58
  return (key, fallback, params) => {
@@ -63,13 +77,12 @@ export function routeModeLabel(mode, t) {
63
77
  return tr('assistant.routes.mode.unsure', 'Ask when unsure');
64
78
  }
65
79
 
66
- export function routeModeDescription(mode, model, t) {
80
+ export function routeModeDescription(mode, model, t, askBelow = 0.75) {
67
81
  const tr = tf(t);
68
- const who = str(model) || tr('assistant.routes.theBrain', 'The assistant');
69
82
  const m = normalizeRouteMode(mode, DEFAULT_ROUTE_MODE);
70
- if (m === 'always-ask') return tr('assistant.routes.mode.alwaysDesc', 'Show the route card before every task.');
71
- if (m === 'never') return tr('assistant.routes.mode.neverDesc', '{model} sorts each request; your task routes apply without asking.', { model: who });
72
- return tr('assistant.routes.mode.unsureDesc', '{model} routes on its own when confident, asks otherwise.', { model: who });
83
+ if (m === 'always-ask') return tr('assistant.routes.mode.alwaysDesc', 'Confirm every actionable task.');
84
+ if (m === 'never') return tr('assistant.routes.mode.neverDesc', 'Run saved routes immediately; otherwise the assistant chooses.');
85
+ return tr('assistant.routes.mode.unsureDesc', 'Confirm below {pct}% confidence or when a model cannot run the task.', { pct: Math.round(askBelow * 100) });
73
86
  }
74
87
 
75
88
  // ── Pure model ──────────────────────────────────────────────────────────────
@@ -94,6 +107,7 @@ function normalizeRouteOption(o) {
94
107
  provider: normalizeProvider(src.provider, str(src.provider) || null),
95
108
  model: str(src.model),
96
109
  effort: str(src.effort) || null,
110
+ ...(Object.hasOwn(src, 'accountId') ? { accountId: src.accountId, account: obj(src.account), accountError: obj(src.accountError) } : {}),
97
111
  label: str(src.label),
98
112
  badge: str(src.badge) || null,
99
113
  tier: str(src.tier) || null,
@@ -102,6 +116,7 @@ function normalizeRouteOption(o) {
102
116
  contextWindow: num(src.contextWindow),
103
117
  reason: str(src.reason),
104
118
  warning: str(src.warning) || null,
119
+ budget: str(src.budget) || null,
105
120
  disabled: src.disabled === true,
106
121
  disabledReason: str(src.disabledReason) || null,
107
122
  };
@@ -175,7 +190,7 @@ export function sameRouteTarget(a, b) {
175
190
  return (str(a.kind) || 'direct') === (str(b.kind) || 'direct')
176
191
  && str(a.provider) === str(b.provider)
177
192
  && modelKey(a.model) === modelKey(b.model)
178
- && (str(a.effort) || '') === (str(b.effort) || '');
193
+ && (str(a.effort) || '') === (str(b.effort) || '') && str(a.accountId) === str(b.accountId);
179
194
  }
180
195
 
181
196
  const BADGE_RANK = { suggested: 0, remembered: 1, current: 2, capable: 3, cheaper: 4, stronger: 5 };
@@ -208,7 +223,7 @@ function badgeText(o, n, tr) {
208
223
  const pct = fmtConfidence(n?.confidence);
209
224
  switch (o.badge) {
210
225
  case 'suggested': return pct ? tr('assistant.routes.badge.suggestedPct', 'Suggested · {pct}', { pct }) : tr('assistant.routes.badge.suggested', 'Suggested');
211
- case 'remembered': return tr('assistant.routes.badge.remembered', 'Remembered');
226
+ case 'remembered': return tr('assistant.routes.badge.remembered', 'Your choice');
212
227
  case 'current': return tr('assistant.routes.badge.current', 'Current');
213
228
  case 'capable':
214
229
  if (!n?.requires && n?.visionOnly) return tr('assistant.routes.badge.canSee', 'Can see');
@@ -270,6 +285,7 @@ export function routeOptions(n, { brainLabel = '', t = null } = {}) {
270
285
  // "here" is the brain's own model: prefer its display label over a raw id.
271
286
  title: isHere ? tr('assistant.routes.doHere', 'Do it here with {model}', { model: str(o.label) || hereModel }) : (label || o.id),
272
287
  badgeText: badgeText(o, n, tr),
288
+ disabledReason: o.budget === 'share_used_up' ? routeBudgetReason({ provider: o.provider }, t) : o.disabledReason,
273
289
  selected: o.id === defaultId,
274
290
  };
275
291
  });
@@ -344,6 +360,23 @@ export function designRouteSight(pref, catalog) {
344
360
  return row.status === 'unavailable' ? 'unavailable' : 'ok';
345
361
  }
346
362
 
363
+ /** Saved ids may resolve through a listed alias; all metadata still comes from this catalog. */
364
+ export function routeCatalogRow(pref, catalog) {
365
+ if (!pref?.provider || !catalog) return null;
366
+ const rows = Array.isArray(catalog[pref.provider]) ? catalog[pref.provider] : [];
367
+ return pref.model ? catalogModelRow(pref.provider, pref.model, catalog) || aliasRow(pref.provider, pref.model, rows) : rows.find(m => m.isDefault) || null;
368
+ }
369
+
370
+ /** Budget messages are UI copy; never show the server's English disabledReason. */
371
+ export function routeBudgetReason({ provider, dry } = {}, t) {
372
+ const tr = tf(t), names = { 'claude-code': 'Claude Code', codex: 'Codex', opencode: 'OpenCode' };
373
+ if (provider) return tr('assistant.routes.budgetProvider', "Budget: the {provider} share of this session's budget is used up.", { provider: names[provider] || provider });
374
+ const list = [...new Set(arr(dry))].map(id => names[id] || id);
375
+ if (!list.length) return tr('assistant.routes.budgetSpent', "Budget: this session's budget is spent.");
376
+ return tr(list.length === 1 ? 'assistant.routes.budgetDryOne' : 'assistant.routes.budgetDryMany',
377
+ list.length === 1 ? 'Budget: the {list} share is used up.' : 'Budget: the {list} shares are used up.', { list: list.join(', ') });
378
+ }
379
+
347
380
  /** Text parts of a route line (pure). */
348
381
  export function routeLineParts(route, phase = 'decided', { brainLabel = '', t = null } = {}) {
349
382
  const tr = tf(t);
@@ -360,17 +393,21 @@ export function routeLineParts(route, phase = 'decided', { brainLabel = '', t =
360
393
  else if (phase === 'declined' && str(r.reasonCode) === 'no_capable_model') {
361
394
  state = str(r.needs) === 'vision' ? tr('assistant.routes.reason.noSeeingModel', 'no model can see images') : tr('assistant.routes.reason.noCapableModel', 'no model can make this');
362
395
  }
396
+ else if (str(r.reasonCode) === 'budget_exhausted') state = routeBudgetReason({ dry: r.dry }, t);
363
397
  else if (phase === 'declined') state = tr('assistant.routes.state.declined', 'declined');
364
398
  else if (phase === 'cancelled') state = tr('assistant.routes.state.cancelled', 'cancelled');
365
399
  let decided = '';
366
- switch (str(r.decidedBy)) {
400
+ switch (r.source === 'budget' ? 'budget' : str(r.decidedBy)) {
401
+ case 'budget': decided = tr('assistant.routes.budgetDecision', 'budget re-route'); break;
367
402
  case 'brain': decided = tr('assistant.routes.decidedBy', 'decided by {model}', { model: brainLabel || tr('assistant.routes.theBrainLower', 'the assistant') }); break;
368
403
  case 'user': decided = tr('assistant.routes.youChose', 'you chose'); break;
369
- case 'remembered': decided = tr('assistant.routes.remembered', 'remembered'); break;
404
+ case 'remembered': decided = tr('assistant.routes.remembered', 'your saved choice'); break;
370
405
  case 'timeout': decided = tr('assistant.routes.timeout', 'default after timeout'); break;
371
- default: decided = r.remembered ? tr('assistant.routes.remembered', 'remembered') : '';
406
+ default: decided = r.remembered ? tr('assistant.routes.remembered', 'your saved choice') : '';
372
407
  }
373
408
  const meta = [];
409
+ const accountLabel = routeDisplayAccount(target, t);
410
+ if (accountLabel) meta.push(accountLabel);
374
411
  if (effort && effort !== 'off') meta.push(effort);
375
412
  if (confidence) meta.push(confidence);
376
413
  return {
@@ -384,7 +421,7 @@ export function routeLineParts(route, phase = 'decided', { brainLabel = '', t =
384
421
  muted: phase === 'expired' || phase === 'declined' || phase === 'cancelled',
385
422
  pending: phase === 'pending' || phase === 'card',
386
423
  decidedText: decided,
387
- reason: str(r.reason),
424
+ reason: target?.accountError ? routeAccountError(target.accountError, t) : str(r.reason),
388
425
  alternatives: arr(r.alternatives).map(a => routeTargetLabel(a, { effort: true })).filter(Boolean),
389
426
  corrections: arr(r.corrections).map(c => correctionText(c, tr)).filter(Boolean),
390
427
  };
@@ -394,9 +431,15 @@ export function routeLineParts(route, phase = 'decided', { brainLabel = '', t =
394
431
  function correctionText(c, tr) {
395
432
  if (typeof c === 'string') return c;
396
433
  if (!c || typeof c !== 'object') return '';
434
+ if (['account_auto', 'account_limited', 'accounts_all_limited'].includes(c.reason) && c.usage) {
435
+ const key = c.reason === 'account_auto' ? 'autoPicked' : c.reason === 'account_limited' ? 'fallbackPicked' : 'allLimitedPicked';
436
+ const fallbacks = { autoPicked: 'Auto chose {to}: {usage}', fallbackPicked: '{from} out of usage → {to}: {usage}', allLimitedPicked: 'All subscriptions limited; soonest reset → {to}: {usage}' };
437
+ const text = tr(`assistant.accountUsage.${key}`, fallbacks[key], { from: str(c.from), to: str(c.to), usage: accountUsageText({ mode: 'subscription', usage: c.usage }, (key, params) => tr(key, key, params)) });
438
+ return text + (c.workerForAccount ? ' · ' + tr('assistant.accountUsage.directWorkerHint', 'Runs on a worker because this conversation belongs to another account.') : '');
439
+ }
397
440
  if (c.reason === 'remembered_route') return tr('assistant.routes.correction.remembered', 'your saved route {to} replaced {from}', { to: str(c.to), from: str(c.from) });
398
441
  if (c.reason === 'remembered_disabled') return tr('assistant.routes.correction.rememberedDisabled', 'saved route {from} is disabled, kept the pick', { from: str(c.from) });
399
- const text = firstStr(c.text, c.summary);
442
+ const text = firstStr(c.text, c.summary, c.message);
400
443
  if (text) return text;
401
444
  if (c.field && (c.from || c.to)) return `${c.field}: ${str(c.from) || '—'} → ${str(c.to) || 'default'}`;
402
445
  return str(c.field);
@@ -410,6 +453,14 @@ export function routeLineText(route, phase, opts) {
410
453
  return [`⇄ ${p.taskClass} → ${head}`, ...p.meta].join(' · ');
411
454
  }
412
455
 
456
+ /** Everything the one-line row can cut short (meta, state included): its tooltip. */
457
+ export function routeLineTitle(route, phase, opts) {
458
+ const p = routeLineParts(route, phase, opts);
459
+ const tail = [p.kindLabel, p.targetLabel, ...p.meta, p.stateText].filter(Boolean).join(' · ');
460
+ if (!tail) return `⇄ ${p.taskClass}`;
461
+ return `⇄ ${p.taskClass} ${p.kindLabel || p.targetLabel ? '→' : '·'} ${tail}`;
462
+ }
463
+
413
464
  // ═══════════════════════════════════════════
414
465
  // DOM
415
466
  // ═══════════════════════════════════════════
@@ -443,8 +494,12 @@ function providerColor(provider) {
443
494
  let _lineUid = 0;
444
495
 
445
496
  /**
446
- * The compact route line. opts: { phase, t, providerIcon, brainLabel() , onChangeRoutes() }
447
- * Returns { el, routeId, update(route, phase) }.
497
+ * The compact route line, always one line: the glyph, arrow, kind and mark
498
+ * hold; the rest (model · account · usage, or the state) gives way first,
499
+ * then the class; the title has it all. data-live: "waiting" while it waits
500
+ * for a choice, "working" while a run it dispatched is (setRunning).
501
+ * opts: { phase, t, providerIcon, brainLabel() , onChangeRoutes() }
502
+ * Returns { el, routeId, update(route, phase), setRunning(on) }.
448
503
  */
449
504
  export function createRouteLine(route, opts = {}) {
450
505
  const tr = tf(opts.t);
@@ -452,20 +507,19 @@ export function createRouteLine(route, opts = {}) {
452
507
  const detailId = `asst-route-detail-${++_lineUid}`;
453
508
  node.innerHTML = `
454
509
  <button type="button" class="asst-route-line-head" aria-expanded="false" aria-controls="${detailId}">
455
- <span class="asst-route-glyph" aria-hidden="true">⇄</span>
510
+ <span class="asst-route-glyph asst-icon" aria-hidden="true">${ICON_ROUTE}</span>
456
511
  <span class="asst-route-class"></span>
457
512
  <span class="asst-route-arrow" aria-hidden="true">→</span>
458
513
  <span class="asst-route-kind"></span>
459
514
  <span class="asst-route-icon asst-icon" aria-hidden="true"></span>
460
- <span class="asst-route-target"></span>
461
- <span class="asst-route-meta"></span>
462
- <span class="asst-route-state"></span>
515
+ <span class="asst-route-rest"><span class="asst-route-target"></span> <span class="asst-route-meta"></span> <span class="asst-route-state"></span></span>
463
516
  <span class="asst-route-chev" aria-hidden="true">&#x203A;</span>
517
+ <span class="asst-route-sweep" aria-hidden="true"></span>
464
518
  </button>
465
519
  <div class="asst-route-detail" id="${detailId}" hidden></div>`;
466
520
  const head = node.querySelector('.asst-route-line-head');
467
521
  const detail = node.querySelector('.asst-route-detail');
468
- let current = { route: obj(route) || {}, phase: opts.phase || 'decided' };
522
+ let current = { route: obj(route) || {}, phase: opts.phase || 'decided', running: false };
469
523
 
470
524
  head.addEventListener('click', () => {
471
525
  const open = head.getAttribute('aria-expanded') !== 'true';
@@ -481,6 +535,8 @@ export function createRouteLine(route, opts = {}) {
481
535
  node.dataset.phase = current.phase;
482
536
  node.classList.toggle('muted', p.muted);
483
537
  node.classList.toggle('pending', p.pending);
538
+ const live = p.pending ? 'waiting' : current.running && !p.muted && !p.stateText ? 'working' : '';
539
+ if (live) node.dataset.live = live; else delete node.dataset.live;
484
540
  node.querySelector('.asst-route-class').textContent = p.taskClass;
485
541
  const arrow = node.querySelector('.asst-route-arrow');
486
542
  const kindEl = node.querySelector('.asst-route-kind');
@@ -488,7 +544,7 @@ export function createRouteLine(route, opts = {}) {
488
544
  const targetEl = node.querySelector('.asst-route-target');
489
545
  const metaEl = node.querySelector('.asst-route-meta');
490
546
  const stateEl = node.querySelector('.asst-route-state');
491
- arrow.hidden = !p.targetLabel && !p.pending;
547
+ arrow.hidden = !p.targetLabel && !p.pending && !p.kindLabel;
492
548
  kindEl.textContent = p.kindLabel;
493
549
  kindEl.hidden = !p.kindLabel;
494
550
  iconEl.innerHTML = p.targetLabel ? providerIconHtml(p.provider, opts) : '';
@@ -500,7 +556,9 @@ export function createRouteLine(route, opts = {}) {
500
556
  metaEl.hidden = !p.meta.length;
501
557
  stateEl.textContent = p.stateText;
502
558
  stateEl.hidden = !p.stateText;
559
+ node.querySelector('.asst-route-rest').hidden = targetEl.hidden && metaEl.hidden && stateEl.hidden;
503
560
  head.setAttribute('aria-label', routeLineText(current.route, current.phase, { brainLabel, t: opts.t }));
561
+ head.title = routeLineTitle(current.route, current.phase, { brainLabel, t: opts.t });
504
562
 
505
563
  detail.innerHTML = '';
506
564
  const rows = [];
@@ -529,6 +587,12 @@ export function createRouteLine(route, opts = {}) {
529
587
  if (phase) current.phase = phase;
530
588
  render();
531
589
  },
590
+ /** A run this route dispatched is queued or working (the renderer knows from its run cards). */
591
+ setRunning(on) {
592
+ if (current.running === !!on) return;
593
+ current.running = !!on;
594
+ render();
595
+ },
532
596
  };
533
597
  }
534
598
 
@@ -598,7 +662,7 @@ export function createRouteModeControl(hostEl, hooks = {}) {
598
662
  kind: 'radio',
599
663
  id,
600
664
  label: routeModeLabel(id, hooks.t),
601
- desc: routeModeDescription(id, model, hooks.t),
665
+ desc: routeModeDescription(id, model, hooks.t, hooks.getMode?.()?.askBelow ?? 0.75),
602
666
  selected: id === mode,
603
667
  onSelect: () => { if (id !== mode) hooks.onChange?.(id); },
604
668
  })),
@@ -699,7 +763,7 @@ export function renderRouteCard(container, input, hooks = {}) {
699
763
  row.setAttribute('aria-disabled', o.disabled ? 'true' : 'false');
700
764
  row.tabIndex = -1;
701
765
  const kindWord = o.kind && o.kind !== 'direct' ? routeKindLabel(o.kind) : '';
702
- const sub = [kindWord, o.effort && o.effort !== 'off' ? o.effort : '', o.price ? priceText(o.price) : '', o.vision ? tr('assistant.brain.vision', 'vision') : '', o.reason].filter(Boolean).join(' · ');
766
+ const sub = [kindWord, routeDisplayAccount(o, hooks.t), o.effort && o.effort !== 'off' ? o.effort : '', o.price ? priceText(o.price) : '', o.vision ? tr('assistant.brain.vision', 'vision') : '', o.reason].filter(Boolean).join(' · ');
703
767
  row.innerHTML = `
704
768
  <span class="asst-route-num" aria-hidden="true">${esc(o.number)}</span>
705
769
  <span class="asst-route-opt-icon asst-icon" aria-hidden="true"${o.provider ? ` style="color:${esc(providerColor(o.provider))}"` : ''}>${providerIconHtml(o.provider, hooks)}</span>
@@ -712,7 +776,7 @@ export function renderRouteCard(container, input, hooks = {}) {
712
776
  row.querySelector('.asst-route-opt-title').textContent = o.title;
713
777
  if (sub) row.querySelector('.asst-route-opt-sub').textContent = sub;
714
778
  const warn = row.querySelector('.asst-route-opt-warn');
715
- if (warn) warn.textContent = o.disabled && o.disabledReason ? o.disabledReason : o.warning;
779
+ if (warn) warn.textContent = o.accountError ? routeAccountError(o.accountError, hooks.t) : o.disabled && o.disabledReason ? o.disabledReason : o.warning;
716
780
  if (o.badgeText) row.querySelector('.asst-route-badge').textContent = o.badgeText;
717
781
  row.addEventListener('click', () => { if (!o.disabled) select(o.id, { focus: true }); });
718
782
  return row;
@@ -779,7 +843,7 @@ export function renderRouteCard(container, input, hooks = {}) {
779
843
  state.other = {
780
844
  id: 'other', kind, provider: target.provider, model: target.model, effort: null,
781
845
  label: target.label || '', badge: null, number: options.length + 1, here: false,
782
- title: target.label || target.model, badgeText: tr('assistant.routes.badge.other', 'Your pick'),
846
+ title: target.label || target.model, badgeText: tr('assistant.routes.badge.other', 'Your choice'),
783
847
  vision: target.vision === true, price: target.price || null, reason: '', warning: null, disabled: false,
784
848
  };
785
849
  state.selectedId = 'other';
@@ -798,7 +862,7 @@ export function renderRouteCard(container, input, hooks = {}) {
798
862
  rememberInput.type = 'checkbox';
799
863
  rememberInput.addEventListener('change', () => { state.remember = rememberInput.checked; });
800
864
  // `remember.label` is the task-class label ("Code"), not a sentence.
801
- const text = tr('assistant.routes.rememberFor', 'Remember for {taskClass} tasks', { taskClass: (n.remember.label || n.taskClassLabel).toLowerCase() });
865
+ const text = tr('assistant.routes.rememberFor', 'Use this choice for {taskClass} tasks', { taskClass: (n.remember.label || n.taskClassLabel).toLowerCase() });
802
866
  label.append(rememberInput, el('span', null, text));
803
867
  if (n.remember.current) label.appendChild(el('span', 'asst-route-remember-current', tr('assistant.routes.rememberCurrent', 'now: {current}', { current: n.remember.current })));
804
868
  card.appendChild(label);
@@ -1005,321 +1069,9 @@ export function renderRouteCard(container, input, hooks = {}) {
1005
1069
  return entry;
1006
1070
  }
1007
1071
 
1008
- // ── Routes editor (modal) ───────────────────────────────────────────────────
1009
-
1010
- let _editor = null;
1011
-
1012
- /**
1013
- * "Model routes…" editor. hooks: { t, providerIcon, getBrain() → brain, onSaved(routing), onClose() }
1014
- * Returns { el, close } (async: resolves after the first load).
1015
- */
1016
- export async function openRoutesEditor(hooks = {}) {
1017
- if (_editor) { _editor.close(); }
1018
- const tr = tf(hooks.t);
1019
- const overlay = el('div', 'asst-modal-overlay');
1020
- overlay.setAttribute('role', 'dialog');
1021
- overlay.setAttribute('aria-modal', 'true');
1022
- overlay.setAttribute('aria-label', tr('assistant.routes.editorTitle', 'Model routes'));
1023
- const modal = el('div', 'asst-modal asst-routes-modal');
1024
- modal.innerHTML = `
1025
- <div class="asst-modal-head"><span class="asst-icon" aria-hidden="true">${ICON_ROUTE}</span><span>${esc(tr('assistant.routes.editorTitle', 'Model routes'))}</span><button type="button" class="asst-iconbtn asst-modal-close" aria-label="${esc(tr('common.close', 'Close'))}">${ICON_X}</button></div>
1026
- <div class="asst-modal-body"><div class="asst-modal-note">${esc(tr('common.loading', 'Loading...'))}</div></div>
1027
- <div class="asst-modal-foot">
1028
- <button type="button" class="asst-btn asst-btn-secondary asst-routes-reset-all">${esc(tr('assistant.routes.resetAll', 'Reset all'))}</button>
1029
- <span class="asst-modal-spacer"></span>
1030
- <button type="button" class="asst-btn asst-btn-secondary asst-routes-cancel">${esc(tr('common.cancel', 'Cancel'))}</button>
1031
- <button type="button" class="asst-btn asst-btn-primary asst-routes-save">${esc(tr('common.save', 'Save'))}</button>
1032
- </div>`;
1033
- overlay.appendChild(modal);
1034
- const body = modal.querySelector('.asst-modal-body');
1035
- const saveBtn = modal.querySelector('.asst-routes-save');
1036
- const resetAllBtn = modal.querySelector('.asst-routes-reset-all');
1037
- const returnTo = document.activeElement;
1038
-
1039
- const state = { data: null, catalog: null, version: null, defaultMode: DEFAULT_ROUTE_MODE, prefs: {}, patch: {}, error: '', conflict: false, saving: false };
1040
-
1041
- function close() {
1042
- if (!overlay.isConnected) return;
1043
- closeMenu();
1044
- document.removeEventListener('keydown', onKey, true);
1045
- overlay.remove();
1046
- _editor = null;
1047
- try { hooks.onClose?.(); } catch { /* ignore */ }
1048
- if (returnTo?.isConnected) { try { returnTo.focus({ preventScroll: true }); } catch { /* ignore */ } }
1049
- }
1050
- function onKey(e) {
1051
- if (e.key === 'Escape') {
1052
- if (isMenuOpen()) return; // the menu closes itself first
1053
- e.preventDefault(); e.stopPropagation(); close();
1054
- return;
1055
- }
1056
- e.stopPropagation();
1057
- }
1058
- document.addEventListener('keydown', onKey, true);
1059
- overlay.addEventListener('mousedown', (e) => { if (e.target === overlay) close(); });
1060
- modal.querySelector('.asst-modal-close').addEventListener('click', close);
1061
- modal.querySelector('.asst-routes-cancel').addEventListener('click', close);
1062
- document.body.appendChild(overlay);
1063
- _editor = { close };
1064
-
1065
- function effectivePref(key) {
1066
- return Object.prototype.hasOwnProperty.call(state.patch, key) ? state.patch[key] : (state.prefs[key] || null);
1067
- }
1068
-
1069
- function sourceOf(key) {
1070
- if (Object.prototype.hasOwnProperty.call(state.patch, key)) return state.patch[key] ? 'yours' : 'default';
1071
- const pref = state.prefs[key];
1072
- if (!pref) return 'default';
1073
- return pref.by === 'user' ? 'yours' : 'remembered';
1074
- }
1075
-
1076
- function sourceLabel(source) {
1077
- if (source === 'disabled') return tr('assistant.routes.source.disabled', 'disabled');
1078
- if (source === 'incapable') return tr('assistant.routes.source.incapable', 'incapable');
1079
- if (source === 'yours') return tr('assistant.routes.source.yours', 'yours');
1080
- if (source === 'remembered') return tr('assistant.routes.source.remembered', 'remembered');
1081
- return tr('assistant.routes.source.default', 'default');
1082
- }
1083
-
1084
- /** Does the route's model generate `medium`? true | false | null (the catalog cannot tell). */
1085
- function routeMakes(pref, medium) {
1086
- if (!medium || !pref?.provider || !state.catalog) return null;
1087
- const rows = Array.isArray(state.catalog[pref.provider]) ? state.catalog[pref.provider] : [];
1088
- const row = pref.model ? catalogModelRow(pref.provider, pref.model, state.catalog) : rows.find(m => m.isDefault) || null;
1089
- if (!row?.outputs || typeof row.outputs !== 'object') return null;
1090
- return row.outputs[medium] === true;
1091
- }
1092
- /** No model in the loaded catalog makes `medium` (false while the catalog has no output data). */
1093
- function noMaker(medium) {
1094
- const rows = Object.values(state.catalog || {}).flatMap(list => (Array.isArray(list) ? list : []));
1095
- return rows.some(m => m?.outputs && typeof m.outputs === 'object') && !rows.some(m => m?.outputs?.[medium] === true);
1096
- }
1097
- /** No model in the loaded catalog can see (false while no catalog is loaded). */
1098
- function noSeer() {
1099
- const rows = Object.values(state.catalog || {}).flatMap(list => (Array.isArray(list) ? list : []));
1100
- return rows.length > 0 && !rows.some(m => m?.vision === true);
1101
- }
1102
-
1103
- function taskRows() {
1104
- const classes = Array.isArray(state.data?.taskClasses) ? state.data.taskClasses.filter(c => c?.id) : [];
1105
- const rows = classes.map(c => ({
1106
- id: String(c.id), label: str(c.label) || humanizeClass(c.id), description: str(c.description), defaultKind: str(c.defaultKind) || 'direct',
1107
- // Image / video creation: only models that generate the medium, always on a worker; design: only models that can see.
1108
- requires: c.requires === 'image' || c.requires === 'video' ? c.requires : null, dispatchOnly: c.dispatchOnly === true, visionOnly: c.vision === true,
1109
- }));
1110
- if (!rows.some(r => r.id === 'vision')) rows.push({ id: 'vision', label: tr('assistant.routes.visionRow', 'Screenshots & images'), description: tr('assistant.routes.visionDesc', 'Used when a task needs a model that can see.'), defaultKind: 'direct' });
1111
- return rows;
1112
- }
1113
-
1114
- function render() {
1115
- body.innerHTML = '';
1116
- if (state.error) {
1117
- const banner = el('div', 'asst-routes-error');
1118
- banner.setAttribute('role', 'alert');
1119
- banner.appendChild(el('span', null, state.error));
1120
- if (state.conflict) {
1121
- const reload = el('button', 'asst-btn asst-btn-secondary', tr('assistant.routes.reload', 'Reload'));
1122
- reload.type = 'button';
1123
- reload.addEventListener('click', () => load());
1124
- banner.appendChild(reload);
1125
- }
1126
- body.appendChild(banner);
1127
- }
1128
- if (!state.data) return;
1129
-
1130
- // When to ask (default route mode)
1131
- const modeSection = el('section', 'asst-routes-section');
1132
- modeSection.appendChild(el('div', 'asst-routes-section-title', tr('assistant.routes.whenToAsk', 'When to ask')));
1133
- const modes = el('div', 'asst-routes-modes');
1134
- modes.setAttribute('role', 'radiogroup');
1135
- modes.setAttribute('aria-label', tr('assistant.routes.whenToAsk', 'When to ask'));
1136
- const brainName = hooks.brainName?.() || '';
1137
- for (const id of ROUTE_MODES) {
1138
- const b = el('button', 'asst-routes-mode');
1139
- b.type = 'button';
1140
- b.setAttribute('role', 'radio');
1141
- b.setAttribute('aria-checked', state.defaultMode === id ? 'true' : 'false');
1142
- b.dataset.mode = id;
1143
- b.innerHTML = `<span class="asst-routes-mode-label">${esc(routeModeLabel(id, hooks.t))}</span><span class="asst-routes-mode-desc">${esc(routeModeDescription(id, brainName, hooks.t))}</span>`;
1144
- b.addEventListener('click', () => { state.defaultMode = id; render(); modal.querySelector(`.asst-routes-mode[data-mode="${id}"]`)?.focus(); });
1145
- modes.appendChild(b);
1146
- }
1147
- modeSection.appendChild(modes);
1148
- body.appendChild(modeSection);
1149
-
1150
- // Task routes
1151
- const section = el('section', 'asst-routes-section');
1152
- section.appendChild(el('div', 'asst-routes-section-title', tr('assistant.routes.taskRoutes', 'Task routes')));
1153
- const list = el('div', 'asst-routes-list');
1154
- for (const row of taskRows()) list.appendChild(taskRow(row));
1155
- section.appendChild(list);
1156
- body.appendChild(section);
1157
- saveBtn.disabled = state.saving;
1158
- }
1159
-
1160
- function taskRow(row) {
1161
- const pref = effectivePref(row.id);
1162
- const medium = row.requires || null;
1163
- const seeing = !medium && row.visionOnly === true;
1164
- // A saved route onto a model the user hid is never applied (the brain decides); nor, for
1165
- // image / video creation, one onto a model that cannot generate the medium, or for design one
1166
- // the router would skip (not known to see, missing from the catalog, unavailable).
1167
- const sight = seeing && pref ? designRouteSight(pref, state.catalog) : null;
1168
- const source = pref?.model && hiddenModelFor(pref.provider, pref.model) ? 'disabled'
1169
- : medium && pref && routeMakes(pref, medium) === false ? 'incapable'
1170
- : sight && sight !== 'ok' ? 'incapable' : sourceOf(row.id);
1171
- const emptyLabel = medium ? tr('assistant.routes.noCapableModel', 'No model in your catalog can make {medium} yet.', { medium: mediumWords(medium, tr) })
1172
- : seeing ? tr('assistant.routes.noSeeingModel', 'No model in your catalog can see images yet.') : '';
1173
- const node = el('div', 'asst-routes-row');
1174
- node.dataset.taskClass = row.id;
1175
- const main = el('div', 'asst-routes-row-main');
1176
- main.appendChild(el('div', 'asst-routes-row-label', row.label));
1177
- if (row.description) main.appendChild(el('div', 'asst-routes-row-desc', row.description));
1178
- if ((medium && noMaker(medium)) || (seeing && noSeer())) main.appendChild(el('div', 'asst-routes-row-note', emptyLabel));
1179
- node.appendChild(main);
1180
-
1181
- const modelBtn = el('button', 'asst-dd asst-routes-model');
1182
- modelBtn.type = 'button';
1183
- modelBtn.setAttribute('aria-haspopup', 'menu');
1184
- const label = pref ? routeTargetLabel(pref) : tr('assistant.routes.brainDecides', 'Let the brain decide');
1185
- modelBtn.innerHTML = `${pref?.provider ? `<span class="asst-icon" aria-hidden="true" style="color:${esc(providerColor(pref.provider))}">${providerIconHtml(pref.provider, hooks)}</span>` : ''}<span class="asst-dd-label"></span>`;
1186
- modelBtn.querySelector('.asst-dd-label').textContent = label;
1187
- modelBtn.setAttribute('aria-label', `${row.label}: ${label}`);
1188
- modelBtn.addEventListener('click', (e) => {
1189
- e.stopPropagation();
1190
- if (isMenuOpen(modelBtn)) { closeMenu(); return; }
1191
- openModelMenu(modelBtn, {
1192
- brain: hooks.getBrain?.() || null,
1193
- t: hooks.t,
1194
- includeHere: true,
1195
- hereLabel: tr('assistant.routes.brainDecides', 'Let the brain decide'),
1196
- title: row.label,
1197
- current: pref ? { provider: pref.provider, model: pref.model } : null,
1198
- onToast: hooks.onToast,
1199
- // Image / video creation: only models that generate the medium (no custom id: its output is unknown);
1200
- // design: only models that can see.
1201
- ...(medium ? { accept: (_p, m) => m?.outputs?.[medium] === true, allowCustom: false, emptyLabel } : {}),
1202
- ...(seeing ? { accept: (_p, m) => m?.vision === true && m?.status !== 'unavailable', allowCustom: false, emptyLabel } : {}),
1203
- onSelect: (target) => {
1204
- if (target?.here) state.patch[row.id] = null;
1205
- else if (target?.model) {
1206
- // The effort survives when the new model runs it; otherwise the nearest level it does.
1207
- const ids = modelEffortIds(catalogModelRow(target.provider, target.model, state.catalog));
1208
- const effort = !pref?.effort ? null : (ids ? clampEffortId(pref.effort, ids) : (pref.provider === target.provider ? pref.effort : null));
1209
- state.patch[row.id] = { kind: row.defaultKind || 'direct', provider: target.provider, model: target.model, effort, label: target.label || '' };
1210
- }
1211
- render();
1212
- modal.querySelector(`.asst-routes-row[data-task-class="${CSS.escape(row.id)}"] .asst-routes-model`)?.focus();
1213
- },
1214
- });
1215
- });
1216
- node.appendChild(modelBtn);
1217
-
1218
- // Exactly the selected model's levels (the provider list only for an unknown model).
1219
- const modelRow = pref?.provider ? catalogModelRow(pref.provider, pref.model, state.catalog) : null;
1220
- const efforts = pref?.provider ? getEffortLevelsForModel(pref.provider, modelRow) : [];
1221
- if (pref && efforts.some(e => e.id !== 'off')) {
1222
- const effortBtn = el('button', 'asst-dd asst-routes-effort');
1223
- effortBtn.type = 'button';
1224
- effortBtn.setAttribute('aria-haspopup', 'menu');
1225
- const level = efforts.find(e => e.id === pref.effort);
1226
- const autoLabel = modelRow?.defaultEffort
1227
- ? tr('assistant.routes.effortAutoDefault', 'auto ({effort})', { effort: efforts.find(e => e.id === modelRow.defaultEffort)?.label?.toLowerCase() || modelRow.defaultEffort })
1228
- : tr('assistant.routes.effortAuto', 'auto effort');
1229
- const effortLabel = level ? level.label
1230
- : pref.effort ? tr('assistant.routes.effortUnsupported', '{effort} · unsupported', { effort: pref.effort })
1231
- : autoLabel;
1232
- effortBtn.innerHTML = '<span class="asst-dd-label"></span>';
1233
- effortBtn.querySelector('.asst-dd-label').textContent = effortLabel;
1234
- effortBtn.setAttribute('aria-label', `${tr('assistant.brain.effort', 'Thinking effort')}: ${effortLabel}`);
1235
- effortBtn.addEventListener('click', (e) => {
1236
- e.stopPropagation();
1237
- if (isMenuOpen(effortBtn)) { closeMenu(); return; }
1238
- openMenu(effortBtn, {
1239
- title: tr('assistant.brain.effort', 'Thinking effort'),
1240
- role: 'menu',
1241
- items: [
1242
- { kind: 'radio', id: '', label: autoLabel, selected: !pref.effort, onSelect: () => { state.patch[row.id] = { ...pref, effort: null }; render(); } },
1243
- ...efforts.filter(x => x.id !== 'off').map(x => ({ kind: 'radio', id: x.id, label: x.label, desc: x.desc || '', selected: pref.effort === x.id, onSelect: () => { state.patch[row.id] = { ...pref, effort: x.id }; render(); } })),
1244
- ],
1245
- });
1246
- });
1247
- node.appendChild(effortBtn);
1248
- }
1249
-
1250
- const badge = el('span', 'asst-routes-source', sourceLabel(source));
1251
- badge.dataset.source = source;
1252
- if (source === 'incapable') {
1253
- badge.setAttribute('data-tooltip', !seeing ? tr('assistant.routes.incapableTip', "This model can't generate {medium}, so the route is not applied.", { medium: mediumWords(medium, tr) })
1254
- : sight === 'blind' ? tr('assistant.routes.incapableSeeTip', "This model can't see images, so the route is not applied.")
1255
- : sight === 'unavailable' ? tr('assistant.routes.unavailableTip', "This model isn't available right now, so the route is not applied.")
1256
- : tr('assistant.routes.unknownSeeTip', "This model isn't known to see images, so the route is not applied."));
1257
- }
1258
- node.appendChild(badge);
1259
-
1260
- const reset = el('button', 'asst-iconbtn asst-routes-reset', '↺');
1261
- reset.type = 'button';
1262
- reset.setAttribute('aria-label', tr('assistant.routes.reset', 'Reset {taskClass}', { taskClass: row.label }));
1263
- reset.setAttribute('data-tooltip', tr('assistant.routes.resetTip', 'Back to default'));
1264
- reset.hidden = source === 'default';
1265
- reset.addEventListener('click', () => { state.patch[row.id] = null; render(); });
1266
- node.appendChild(reset);
1267
- return node;
1268
- }
1269
-
1270
- async function load() {
1271
- state.error = '';
1272
- state.conflict = false;
1273
- try {
1274
- const [data, catalog] = await Promise.all([getRouting(), loadModelCatalog().catch(() => null)]);
1275
- state.catalog = catalog || null;
1276
- state.data = data || {};
1277
- state.version = data?.version ?? null;
1278
- state.defaultMode = normalizeRouteMode(data?.routing?.defaultMode, DEFAULT_ROUTE_MODE);
1279
- state.prefs = obj(data?.routing?.preferences) ? { ...data.routing.preferences } : {};
1280
- state.patch = {};
1281
- } catch (err) {
1282
- state.data = null;
1283
- state.error = err?.status === 404
1284
- ? tr('assistant.routes.unavailable', 'Model routing is not available on this server yet.')
1285
- : `${tr('assistant.routes.loadFailed', 'Could not load model routes')}: ${err?.message || err}`;
1286
- }
1287
- render();
1288
- modal.querySelector('.asst-routes-mode[aria-checked="true"]')?.focus();
1289
- }
1290
-
1291
- resetAllBtn.addEventListener('click', () => {
1292
- for (const key of new Set([...Object.keys(state.prefs), ...Object.keys(state.patch)])) state.patch[key] = null;
1293
- render();
1294
- });
1295
-
1296
- saveBtn.addEventListener('click', async () => {
1297
- if (!state.data || state.saving) return;
1298
- state.saving = true;
1299
- saveBtn.disabled = true;
1300
- const patch = { defaultMode: state.defaultMode };
1301
- if (Object.keys(state.patch).length) patch.preferences = { ...state.patch };
1302
- try {
1303
- const res = await putRouting(patch, state.version);
1304
- try { hooks.onSaved?.(res?.routing || null, res); } catch { /* ignore */ }
1305
- close();
1306
- } catch (err) {
1307
- state.saving = false;
1308
- if (err?.status === 409 || err?.code === 'VERSION_CONFLICT') {
1309
- state.error = tr('assistant.routes.conflict', 'Changed elsewhere — reload');
1310
- state.conflict = true;
1311
- } else if (err?.code === 'ROUTING_INVALID') {
1312
- state.error = tr('assistant.routes.invalidField', 'Invalid routing setting: {field}', { field: err?.message || '' });
1313
- } else {
1314
- state.error = `${tr('assistant.routes.saveFailed', 'Could not save')}: ${err?.message || err}`;
1315
- }
1316
- render();
1317
- }
1318
- });
1319
-
1320
- await load();
1321
- return { el: overlay, close };
1322
- }
1072
+ // ── Routes editor (draft, modes, task choices) ───────────────────────────────
1073
+ // Its model lists still come only through asst-route-models.js.
1074
+ export { openRoutesEditor } from './asst-routes-editor.js';
1323
1075
 
1324
1076
  // ── Models manager (Assistant → Models…) ───────────────────────────────────
1325
1077