synabun 2.0.5 → 2.0.9

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 (527) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +2 -1
  3. package/THIRD-PARTY-LICENSES.md +13 -0
  4. package/docs/whatsapp.md +151 -16
  5. package/hooks/claude-code/post-plan.mjs +6 -30
  6. package/hooks/claude-code/session-start.mjs +1 -4
  7. package/hooks/claude-code/shared.mjs +112 -75
  8. package/lib/child-path.js +303 -0
  9. package/lib/claude-hooks.js +2 -1
  10. package/lib/data-home-migration.js +2 -0
  11. package/lib/desktop-app.js +265 -0
  12. package/lib/external-tools.js +7 -3
  13. package/lib/log-redact.js +102 -0
  14. package/lib/package-processes.js +670 -0
  15. package/lib/packaged-runtime.js +138 -11
  16. package/lib/runtime-dependencies.js +225 -0
  17. package/mcp-server/dist/http.d.ts +3 -1
  18. package/mcp-server/dist/http.d.ts.map +1 -1
  19. package/mcp-server/dist/http.js +15 -4
  20. package/mcp-server/dist/http.js.map +1 -1
  21. package/mcp-server/dist/index.d.ts.map +1 -1
  22. package/mcp-server/dist/index.js +9 -6
  23. package/mcp-server/dist/index.js.map +1 -1
  24. package/mcp-server/dist/services/browser-bench.d.ts +1 -1
  25. package/mcp-server/dist/services/browser-bench.d.ts.map +1 -1
  26. package/mcp-server/dist/services/browser-bench.js +1 -1
  27. package/mcp-server/dist/services/browser-bench.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/categories.d.ts +35 -2
  33. package/mcp-server/dist/services/categories.d.ts.map +1 -1
  34. package/mcp-server/dist/services/categories.js +77 -33
  35. package/mcp-server/dist/services/categories.js.map +1 -1
  36. package/mcp-server/dist/services/codex-config-heal.d.ts +5 -1
  37. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  38. package/mcp-server/dist/services/codex-config-heal.js +41 -4
  39. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  40. package/mcp-server/dist/services/desktop-bench.d.ts +7 -7
  41. package/mcp-server/dist/services/desktop-bench.d.ts.map +1 -1
  42. package/mcp-server/dist/services/desktop-bench.js +1 -1
  43. package/mcp-server/dist/services/desktop-bench.js.map +1 -1
  44. package/mcp-server/dist/services/identity.d.ts +11 -0
  45. package/mcp-server/dist/services/identity.d.ts.map +1 -1
  46. package/mcp-server/dist/services/identity.js +24 -0
  47. package/mcp-server/dist/services/identity.js.map +1 -1
  48. package/mcp-server/dist/services/neural-interface.d.ts +13 -0
  49. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  50. package/mcp-server/dist/services/neural-interface.js +34 -2
  51. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  52. package/mcp-server/dist/services/typesafe-config.d.ts +1 -1
  53. package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -1
  54. package/mcp-server/dist/services/typesafe-config.js +4 -1
  55. package/mcp-server/dist/services/typesafe-config.js.map +1 -1
  56. package/mcp-server/dist/tools/agents.d.ts +11 -8
  57. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  58. package/mcp-server/dist/tools/agents.js +35 -14
  59. package/mcp-server/dist/tools/agents.js.map +1 -1
  60. package/mcp-server/dist/tools/bluesky.js +1 -1
  61. package/mcp-server/dist/tools/bluesky.js.map +1 -1
  62. package/mcp-server/dist/tools/browser-advanced.d.ts +1 -1
  63. package/mcp-server/dist/tools/browser-advanced.d.ts.map +1 -1
  64. package/mcp-server/dist/tools/browser-advanced.js +1 -1
  65. package/mcp-server/dist/tools/browser-advanced.js.map +1 -1
  66. package/mcp-server/dist/tools/browser-batch.d.ts +1 -1
  67. package/mcp-server/dist/tools/browser-batch.d.ts.map +1 -1
  68. package/mcp-server/dist/tools/browser-batch.js +1 -1
  69. package/mcp-server/dist/tools/browser-batch.js.map +1 -1
  70. package/mcp-server/dist/tools/browser-cheatsheet.d.ts +1 -1
  71. package/mcp-server/dist/tools/browser-cheatsheet.d.ts.map +1 -1
  72. package/mcp-server/dist/tools/browser-cheatsheet.js +1 -1
  73. package/mcp-server/dist/tools/browser-cheatsheet.js.map +1 -1
  74. package/mcp-server/dist/tools/browser-interact.d.ts +1 -1
  75. package/mcp-server/dist/tools/browser-interact.d.ts.map +1 -1
  76. package/mcp-server/dist/tools/browser-interact.js +1 -1
  77. package/mcp-server/dist/tools/browser-interact.js.map +1 -1
  78. package/mcp-server/dist/tools/browser-navigate.d.ts +1 -1
  79. package/mcp-server/dist/tools/browser-navigate.d.ts.map +1 -1
  80. package/mcp-server/dist/tools/browser-navigate.js +1 -1
  81. package/mcp-server/dist/tools/browser-navigate.js.map +1 -1
  82. package/mcp-server/dist/tools/browser-observe.d.ts +1 -1
  83. package/mcp-server/dist/tools/browser-observe.d.ts.map +1 -1
  84. package/mcp-server/dist/tools/browser-observe.js +1 -1
  85. package/mcp-server/dist/tools/browser-observe.js.map +1 -1
  86. package/mcp-server/dist/tools/card-tools.d.ts +1 -1
  87. package/mcp-server/dist/tools/card-tools.d.ts.map +1 -1
  88. package/mcp-server/dist/tools/card-tools.js +1 -1
  89. package/mcp-server/dist/tools/card-tools.js.map +1 -1
  90. package/mcp-server/dist/tools/category.d.ts +1 -1
  91. package/mcp-server/dist/tools/category.d.ts.map +1 -1
  92. package/mcp-server/dist/tools/category.js +5 -1
  93. package/mcp-server/dist/tools/category.js.map +1 -1
  94. package/mcp-server/dist/tools/choice.d.ts +1 -1
  95. package/mcp-server/dist/tools/choice.d.ts.map +1 -1
  96. package/mcp-server/dist/tools/choice.js +1 -1
  97. package/mcp-server/dist/tools/choice.js.map +1 -1
  98. package/mcp-server/dist/tools/computer.d.ts +1 -1
  99. package/mcp-server/dist/tools/computer.d.ts.map +1 -1
  100. package/mcp-server/dist/tools/computer.js +1 -1
  101. package/mcp-server/dist/tools/computer.js.map +1 -1
  102. package/mcp-server/dist/tools/discord-channel.d.ts +1 -1
  103. package/mcp-server/dist/tools/discord-channel.d.ts.map +1 -1
  104. package/mcp-server/dist/tools/discord-channel.js +1 -1
  105. package/mcp-server/dist/tools/discord-channel.js.map +1 -1
  106. package/mcp-server/dist/tools/discord-guild.d.ts +1 -1
  107. package/mcp-server/dist/tools/discord-guild.d.ts.map +1 -1
  108. package/mcp-server/dist/tools/discord-guild.js +1 -1
  109. package/mcp-server/dist/tools/discord-guild.js.map +1 -1
  110. package/mcp-server/dist/tools/discord-member.d.ts +1 -1
  111. package/mcp-server/dist/tools/discord-member.d.ts.map +1 -1
  112. package/mcp-server/dist/tools/discord-member.js +1 -1
  113. package/mcp-server/dist/tools/discord-member.js.map +1 -1
  114. package/mcp-server/dist/tools/discord-message.d.ts +1 -1
  115. package/mcp-server/dist/tools/discord-message.d.ts.map +1 -1
  116. package/mcp-server/dist/tools/discord-message.js +1 -1
  117. package/mcp-server/dist/tools/discord-message.js.map +1 -1
  118. package/mcp-server/dist/tools/discord-onboarding.d.ts +1 -1
  119. package/mcp-server/dist/tools/discord-onboarding.d.ts.map +1 -1
  120. package/mcp-server/dist/tools/discord-onboarding.js +1 -1
  121. package/mcp-server/dist/tools/discord-onboarding.js.map +1 -1
  122. package/mcp-server/dist/tools/discord-role.d.ts +1 -1
  123. package/mcp-server/dist/tools/discord-role.d.ts.map +1 -1
  124. package/mcp-server/dist/tools/discord-role.js +1 -1
  125. package/mcp-server/dist/tools/discord-role.js.map +1 -1
  126. package/mcp-server/dist/tools/discord-thread.d.ts +1 -1
  127. package/mcp-server/dist/tools/discord-thread.d.ts.map +1 -1
  128. package/mcp-server/dist/tools/discord-thread.js +1 -1
  129. package/mcp-server/dist/tools/discord-thread.js.map +1 -1
  130. package/mcp-server/dist/tools/discord-webhook.d.ts +1 -1
  131. package/mcp-server/dist/tools/discord-webhook.d.ts.map +1 -1
  132. package/mcp-server/dist/tools/discord-webhook.js +1 -1
  133. package/mcp-server/dist/tools/discord-webhook.js.map +1 -1
  134. package/mcp-server/dist/tools/fb-groups.d.ts +1 -1
  135. package/mcp-server/dist/tools/fb-groups.d.ts.map +1 -1
  136. package/mcp-server/dist/tools/fb-groups.js +1 -1
  137. package/mcp-server/dist/tools/fb-groups.js.map +1 -1
  138. package/mcp-server/dist/tools/forget.d.ts +1 -1
  139. package/mcp-server/dist/tools/forget.d.ts.map +1 -1
  140. package/mcp-server/dist/tools/forget.js +1 -1
  141. package/mcp-server/dist/tools/forget.js.map +1 -1
  142. package/mcp-server/dist/tools/git-tools.d.ts +1 -1
  143. package/mcp-server/dist/tools/git-tools.d.ts.map +1 -1
  144. package/mcp-server/dist/tools/git-tools.js +1 -1
  145. package/mcp-server/dist/tools/git-tools.js.map +1 -1
  146. package/mcp-server/dist/tools/gsc/experience.d.ts +1 -1
  147. package/mcp-server/dist/tools/gsc/experience.d.ts.map +1 -1
  148. package/mcp-server/dist/tools/gsc/experience.js +1 -1
  149. package/mcp-server/dist/tools/gsc/experience.js.map +1 -1
  150. package/mcp-server/dist/tools/gsc/indexing.d.ts +1 -1
  151. package/mcp-server/dist/tools/gsc/indexing.d.ts.map +1 -1
  152. package/mcp-server/dist/tools/gsc/indexing.js +1 -1
  153. package/mcp-server/dist/tools/gsc/indexing.js.map +1 -1
  154. package/mcp-server/dist/tools/gsc/inspect.d.ts +1 -1
  155. package/mcp-server/dist/tools/gsc/inspect.d.ts.map +1 -1
  156. package/mcp-server/dist/tools/gsc/inspect.js +1 -1
  157. package/mcp-server/dist/tools/gsc/inspect.js.map +1 -1
  158. package/mcp-server/dist/tools/gsc/navigate.d.ts +1 -1
  159. package/mcp-server/dist/tools/gsc/navigate.d.ts.map +1 -1
  160. package/mcp-server/dist/tools/gsc/navigate.js +1 -1
  161. package/mcp-server/dist/tools/gsc/navigate.js.map +1 -1
  162. package/mcp-server/dist/tools/gsc/performance.d.ts +1 -1
  163. package/mcp-server/dist/tools/gsc/performance.d.ts.map +1 -1
  164. package/mcp-server/dist/tools/gsc/performance.js +1 -1
  165. package/mcp-server/dist/tools/gsc/performance.js.map +1 -1
  166. package/mcp-server/dist/tools/gsc/settings.d.ts +1 -1
  167. package/mcp-server/dist/tools/gsc/settings.d.ts.map +1 -1
  168. package/mcp-server/dist/tools/gsc/settings.js +1 -1
  169. package/mcp-server/dist/tools/gsc/settings.js.map +1 -1
  170. package/mcp-server/dist/tools/image-tools.d.ts +1 -1
  171. package/mcp-server/dist/tools/image-tools.d.ts.map +1 -1
  172. package/mcp-server/dist/tools/image-tools.js +1 -1
  173. package/mcp-server/dist/tools/image-tools.js.map +1 -1
  174. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +1 -1
  175. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +1 -1
  176. package/mcp-server/dist/tools/leonardo-browser-tools.js +1 -1
  177. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +1 -1
  178. package/mcp-server/dist/tools/loop.d.ts +1 -1
  179. package/mcp-server/dist/tools/loop.d.ts.map +1 -1
  180. package/mcp-server/dist/tools/loop.js +1 -1
  181. package/mcp-server/dist/tools/loop.js.map +1 -1
  182. package/mcp-server/dist/tools/memories.d.ts +1 -1
  183. package/mcp-server/dist/tools/memories.d.ts.map +1 -1
  184. package/mcp-server/dist/tools/memories.js +1 -1
  185. package/mcp-server/dist/tools/memories.js.map +1 -1
  186. package/mcp-server/dist/tools/morelogin.d.ts +1 -1
  187. package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
  188. package/mcp-server/dist/tools/morelogin.js +1 -1
  189. package/mcp-server/dist/tools/morelogin.js.map +1 -1
  190. package/mcp-server/dist/tools/profile.d.ts +1 -1
  191. package/mcp-server/dist/tools/profile.d.ts.map +1 -1
  192. package/mcp-server/dist/tools/profile.js +1 -1
  193. package/mcp-server/dist/tools/profile.js.map +1 -1
  194. package/mcp-server/dist/tools/recall.d.ts +1 -1
  195. package/mcp-server/dist/tools/recall.d.ts.map +1 -1
  196. package/mcp-server/dist/tools/recall.js +1 -1
  197. package/mcp-server/dist/tools/recall.js.map +1 -1
  198. package/mcp-server/dist/tools/reflect.d.ts +1 -1
  199. package/mcp-server/dist/tools/reflect.d.ts.map +1 -1
  200. package/mcp-server/dist/tools/reflect.js +1 -1
  201. package/mcp-server/dist/tools/reflect.js.map +1 -1
  202. package/mcp-server/dist/tools/remember.d.ts +1 -1
  203. package/mcp-server/dist/tools/remember.d.ts.map +1 -1
  204. package/mcp-server/dist/tools/remember.js +13 -11
  205. package/mcp-server/dist/tools/remember.js.map +1 -1
  206. package/mcp-server/dist/tools/restore.d.ts +1 -1
  207. package/mcp-server/dist/tools/restore.d.ts.map +1 -1
  208. package/mcp-server/dist/tools/restore.js +1 -1
  209. package/mcp-server/dist/tools/restore.js.map +1 -1
  210. package/mcp-server/dist/tools/style-guide-tools.d.ts +1 -1
  211. package/mcp-server/dist/tools/style-guide-tools.d.ts.map +1 -1
  212. package/mcp-server/dist/tools/style-guide-tools.js +1 -1
  213. package/mcp-server/dist/tools/style-guide-tools.js.map +1 -1
  214. package/mcp-server/dist/tools/sync.d.ts +1 -1
  215. package/mcp-server/dist/tools/sync.d.ts.map +1 -1
  216. package/mcp-server/dist/tools/sync.js +1 -1
  217. package/mcp-server/dist/tools/sync.js.map +1 -1
  218. package/mcp-server/dist/tools/tictactoe-tools.d.ts +1 -1
  219. package/mcp-server/dist/tools/tictactoe-tools.d.ts.map +1 -1
  220. package/mcp-server/dist/tools/tictactoe-tools.js +1 -1
  221. package/mcp-server/dist/tools/tictactoe-tools.js.map +1 -1
  222. package/mcp-server/dist/tools/utils.d.ts +1 -1
  223. package/mcp-server/dist/tools/utils.d.ts.map +1 -1
  224. package/mcp-server/dist/tools/utils.js +1 -1
  225. package/mcp-server/dist/tools/utils.js.map +1 -1
  226. package/mcp-server/dist/tools/whatsapp-send-file.d.ts +17 -0
  227. package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -0
  228. package/mcp-server/dist/tools/whatsapp-send-file.js +44 -0
  229. package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -0
  230. package/mcp-server/dist/tools/whiteboard-tools.d.ts +3 -3
  231. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
  232. package/mcp-server/dist/tools/whiteboard-tools.js +1 -1
  233. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
  234. package/mcp-server/dist/tools/youtube/analytics.d.ts +1 -1
  235. package/mcp-server/dist/tools/youtube/analytics.d.ts.map +1 -1
  236. package/mcp-server/dist/tools/youtube/analytics.js +1 -1
  237. package/mcp-server/dist/tools/youtube/analytics.js.map +1 -1
  238. package/mcp-server/dist/tools/youtube/download.d.ts +1 -1
  239. package/mcp-server/dist/tools/youtube/download.d.ts.map +1 -1
  240. package/mcp-server/dist/tools/youtube/download.js +1 -1
  241. package/mcp-server/dist/tools/youtube/download.js.map +1 -1
  242. package/mcp-server/dist/tools/youtube/media.d.ts +1 -1
  243. package/mcp-server/dist/tools/youtube/media.d.ts.map +1 -1
  244. package/mcp-server/dist/tools/youtube/media.js +1 -1
  245. package/mcp-server/dist/tools/youtube/media.js.map +1 -1
  246. package/mcp-server/dist/tools/youtube/metadata.d.ts +1 -1
  247. package/mcp-server/dist/tools/youtube/metadata.d.ts.map +1 -1
  248. package/mcp-server/dist/tools/youtube/metadata.js +1 -1
  249. package/mcp-server/dist/tools/youtube/metadata.js.map +1 -1
  250. package/mcp-server/dist/tools/youtube/navigate.d.ts +1 -1
  251. package/mcp-server/dist/tools/youtube/navigate.d.ts.map +1 -1
  252. package/mcp-server/dist/tools/youtube/navigate.js +1 -1
  253. package/mcp-server/dist/tools/youtube/navigate.js.map +1 -1
  254. package/mcp-server/dist/tools/youtube/playlist.d.ts +1 -1
  255. package/mcp-server/dist/tools/youtube/playlist.d.ts.map +1 -1
  256. package/mcp-server/dist/tools/youtube/playlist.js +1 -1
  257. package/mcp-server/dist/tools/youtube/playlist.js.map +1 -1
  258. package/mcp-server/dist/tools/youtube/schedule.d.ts +1 -1
  259. package/mcp-server/dist/tools/youtube/schedule.d.ts.map +1 -1
  260. package/mcp-server/dist/tools/youtube/schedule.js +1 -1
  261. package/mcp-server/dist/tools/youtube/schedule.js.map +1 -1
  262. package/mcp-server/dist/tools/youtube/source.d.ts +1 -1
  263. package/mcp-server/dist/tools/youtube/source.d.ts.map +1 -1
  264. package/mcp-server/dist/tools/youtube/source.js +1 -1
  265. package/mcp-server/dist/tools/youtube/source.js.map +1 -1
  266. package/mcp-server/dist/tools/youtube/upload.d.ts +1 -1
  267. package/mcp-server/dist/tools/youtube/upload.d.ts.map +1 -1
  268. package/mcp-server/dist/tools/youtube/upload.js +4 -9
  269. package/mcp-server/dist/tools/youtube/upload.js.map +1 -1
  270. package/mcp-server/dist/tui.js +18 -5
  271. package/mcp-server/dist/tui.js.map +1 -1
  272. package/mcp-server/dist/version.d.ts +5 -0
  273. package/mcp-server/dist/version.d.ts.map +1 -0
  274. package/mcp-server/dist/version.js +21 -0
  275. package/mcp-server/dist/version.js.map +1 -0
  276. package/mcp-server/package-lock.json +3 -3
  277. package/mcp-server/package.json +3 -3
  278. package/mcp-server/run.mjs +15 -9
  279. package/mcp-server/src/http.ts +18 -3
  280. package/mcp-server/src/index.ts +9 -6
  281. package/mcp-server/src/services/browser-bench.ts +1 -1
  282. package/mcp-server/src/services/budget-judgments.ts +183 -0
  283. package/mcp-server/src/services/categories.ts +77 -32
  284. package/mcp-server/src/services/codex-config-heal.ts +35 -4
  285. package/mcp-server/src/services/desktop-bench.ts +1 -1
  286. package/mcp-server/src/services/identity.ts +27 -0
  287. package/mcp-server/src/services/neural-interface.ts +34 -2
  288. package/mcp-server/src/services/typesafe-config.ts +4 -1
  289. package/mcp-server/src/tools/agents.ts +34 -15
  290. package/mcp-server/src/tools/bluesky.ts +1 -1
  291. package/mcp-server/src/tools/browser-advanced.ts +1 -1
  292. package/mcp-server/src/tools/browser-batch.ts +1 -1
  293. package/mcp-server/src/tools/browser-cheatsheet.ts +1 -1
  294. package/mcp-server/src/tools/browser-interact.ts +1 -1
  295. package/mcp-server/src/tools/browser-navigate.ts +1 -1
  296. package/mcp-server/src/tools/browser-observe.ts +1 -1
  297. package/mcp-server/src/tools/card-tools.ts +1 -1
  298. package/mcp-server/src/tools/category.ts +6 -1
  299. package/mcp-server/src/tools/choice.ts +1 -1
  300. package/mcp-server/src/tools/computer.ts +1 -1
  301. package/mcp-server/src/tools/discord-channel.ts +1 -1
  302. package/mcp-server/src/tools/discord-guild.ts +1 -1
  303. package/mcp-server/src/tools/discord-member.ts +1 -1
  304. package/mcp-server/src/tools/discord-message.ts +1 -1
  305. package/mcp-server/src/tools/discord-onboarding.ts +1 -1
  306. package/mcp-server/src/tools/discord-role.ts +1 -1
  307. package/mcp-server/src/tools/discord-thread.ts +1 -1
  308. package/mcp-server/src/tools/discord-webhook.ts +1 -1
  309. package/mcp-server/src/tools/fb-groups.ts +1 -1
  310. package/mcp-server/src/tools/forget.ts +1 -1
  311. package/mcp-server/src/tools/git-tools.ts +1 -1
  312. package/mcp-server/src/tools/gsc/experience.ts +1 -1
  313. package/mcp-server/src/tools/gsc/indexing.ts +1 -1
  314. package/mcp-server/src/tools/gsc/inspect.ts +1 -1
  315. package/mcp-server/src/tools/gsc/navigate.ts +1 -1
  316. package/mcp-server/src/tools/gsc/performance.ts +1 -1
  317. package/mcp-server/src/tools/gsc/settings.ts +1 -1
  318. package/mcp-server/src/tools/image-tools.ts +1 -1
  319. package/mcp-server/src/tools/leonardo-browser-tools.ts +1 -1
  320. package/mcp-server/src/tools/loop.ts +1 -1
  321. package/mcp-server/src/tools/memories.ts +1 -1
  322. package/mcp-server/src/tools/morelogin.ts +1 -1
  323. package/mcp-server/src/tools/profile.ts +1 -1
  324. package/mcp-server/src/tools/recall.ts +1 -1
  325. package/mcp-server/src/tools/reflect.ts +1 -1
  326. package/mcp-server/src/tools/remember.ts +13 -12
  327. package/mcp-server/src/tools/restore.ts +1 -1
  328. package/mcp-server/src/tools/style-guide-tools.ts +1 -1
  329. package/mcp-server/src/tools/sync.ts +1 -1
  330. package/mcp-server/src/tools/tictactoe-tools.ts +1 -1
  331. package/mcp-server/src/tools/utils.ts +1 -1
  332. package/mcp-server/src/tools/whatsapp-send-file.ts +51 -0
  333. package/mcp-server/src/tools/whiteboard-tools.ts +1 -1
  334. package/mcp-server/src/tools/youtube/analytics.ts +1 -1
  335. package/mcp-server/src/tools/youtube/download.ts +1 -1
  336. package/mcp-server/src/tools/youtube/media.ts +1 -1
  337. package/mcp-server/src/tools/youtube/metadata.ts +1 -1
  338. package/mcp-server/src/tools/youtube/navigate.ts +1 -1
  339. package/mcp-server/src/tools/youtube/playlist.ts +1 -1
  340. package/mcp-server/src/tools/youtube/schedule.ts +1 -1
  341. package/mcp-server/src/tools/youtube/source.ts +1 -1
  342. package/mcp-server/src/tools/youtube/upload.ts +4 -12
  343. package/mcp-server/src/tui.ts +24 -10
  344. package/mcp-server/src/version.ts +22 -0
  345. package/neural-interface/i18n/en.json +645 -57
  346. package/neural-interface/i18n/pt-BR.json +644 -56
  347. package/neural-interface/lib/account-usage.js +163 -0
  348. package/neural-interface/lib/agent-sandbox-elevation.js +102 -0
  349. package/neural-interface/lib/agent-sandbox-setting.js +57 -0
  350. package/neural-interface/lib/assistant-api.js +274 -52
  351. package/neural-interface/lib/assistant-brains/claude.js +15 -2
  352. package/neural-interface/lib/assistant-brains/codex.js +3 -0
  353. package/neural-interface/lib/assistant-brains/opencode.js +17 -4
  354. package/neural-interface/lib/assistant-budget-planner.js +390 -0
  355. package/neural-interface/lib/assistant-budget-split.js +408 -0
  356. package/neural-interface/lib/assistant-budget.js +94 -95
  357. package/neural-interface/lib/assistant-config.js +62 -14
  358. package/neural-interface/lib/assistant-dispatch.js +432 -125
  359. package/neural-interface/lib/assistant-jev.js +26 -3
  360. package/neural-interface/lib/assistant-persona.js +68 -11
  361. package/neural-interface/lib/assistant-plan-permissions.js +8 -1
  362. package/neural-interface/lib/assistant-route-accounts.js +14 -0
  363. package/neural-interface/lib/assistant-route-gate-codex.js +3 -1
  364. package/neural-interface/lib/assistant-route-gate.js +19 -3
  365. package/neural-interface/lib/assistant-router.js +373 -42
  366. package/neural-interface/lib/assistant-runtime.js +312 -48
  367. package/neural-interface/lib/assistant-user-writer.js +4 -2
  368. package/neural-interface/lib/augmented-path.js +317 -53
  369. package/neural-interface/lib/claude-accounts.js +342 -31
  370. package/neural-interface/lib/claude-agent-bridge.js +90 -3
  371. package/neural-interface/lib/claude-executable.js +6 -1
  372. package/neural-interface/lib/claude-model-catalog.js +3 -3
  373. package/neural-interface/lib/claude-panel-auth.js +221 -0
  374. package/neural-interface/lib/claude-profile-api-key.js +62 -0
  375. package/neural-interface/lib/cli-install-api.js +137 -0
  376. package/neural-interface/lib/cli-install.js +797 -0
  377. package/neural-interface/lib/cli-path.js +130 -0
  378. package/neural-interface/lib/cli-resolver.js +744 -0
  379. package/neural-interface/lib/cli-update-planner.js +143 -7
  380. package/neural-interface/lib/codex-account-auth.js +180 -0
  381. package/neural-interface/lib/codex-accounts.js +502 -0
  382. package/neural-interface/lib/codex-capabilities.js +2 -1
  383. package/neural-interface/lib/codex-compat-rpc.js +8 -2
  384. package/neural-interface/lib/codex-mcp-approvals.js +5 -1
  385. package/neural-interface/lib/codex-panel-auth.js +62 -0
  386. package/neural-interface/lib/codex-session-settings.js +1 -1
  387. package/neural-interface/lib/codex-settings-schema.js +122 -0
  388. package/neural-interface/lib/desktop/config.js +3 -0
  389. package/neural-interface/lib/exec-loop-command.js +3 -1
  390. package/neural-interface/lib/git-bash/bzip2.js +339 -0
  391. package/neural-interface/lib/git-bash/claude-settings.js +73 -0
  392. package/neural-interface/lib/git-bash/extract-worker.js +34 -0
  393. package/neural-interface/lib/git-bash/install.js +529 -0
  394. package/neural-interface/lib/git-bash/licenses/COPYING +360 -0
  395. package/neural-interface/lib/git-bash/licenses/LGPL-2.1 +511 -0
  396. package/neural-interface/lib/git-bash/manager.js +268 -0
  397. package/neural-interface/lib/git-bash/pins.js +206 -0
  398. package/neural-interface/lib/git-bash/pins.json +34 -0
  399. package/neural-interface/lib/git-bash/resolver.js +194 -0
  400. package/neural-interface/lib/git-read.js +34 -2
  401. package/neural-interface/lib/install-details.js +163 -0
  402. package/neural-interface/lib/mcp-installer.js +14 -2
  403. package/neural-interface/lib/native-loop-providers.js +22 -4
  404. package/neural-interface/lib/native-loop-runtime.js +9 -4
  405. package/neural-interface/lib/os-path.js +319 -0
  406. package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
  407. package/neural-interface/lib/provider-panel-terminal-auth.js +96 -0
  408. package/neural-interface/lib/pty-host/core.js +2 -1
  409. package/neural-interface/lib/pty-host/manager.js +27 -2
  410. package/neural-interface/lib/remote-policy.js +20 -11
  411. package/neural-interface/lib/session-indexer.js +3 -1
  412. package/neural-interface/lib/session-title-generator.js +6 -3
  413. package/neural-interface/lib/system-about.js +340 -0
  414. package/neural-interface/lib/system-activity.js +66 -0
  415. package/neural-interface/lib/terminal-env.js +11 -3
  416. package/neural-interface/lib/terminal-path.js +55 -0
  417. package/neural-interface/lib/virtual-socket.js +12 -1
  418. package/neural-interface/lib/whatsapp/baileys-adapter.js +165 -45
  419. package/neural-interface/lib/whatsapp/bridge.js +78 -34
  420. package/neural-interface/lib/whatsapp/commands.js +13 -14
  421. package/neural-interface/lib/whatsapp/config.js +134 -20
  422. package/neural-interface/lib/whatsapp/fake-baileys.js +99 -14
  423. package/neural-interface/lib/whatsapp/host-core.js +168 -35
  424. package/neural-interface/lib/whatsapp/inbound.js +5 -3
  425. package/neural-interface/lib/whatsapp/local-file.js +524 -0
  426. package/neural-interface/lib/whatsapp/manager.js +92 -32
  427. package/neural-interface/lib/whatsapp/paths.js +11 -1
  428. package/neural-interface/lib/whatsapp/protocol.js +65 -5
  429. package/neural-interface/lib/whatsapp/redact.js +63 -0
  430. package/neural-interface/lib/whatsapp/service.js +343 -91
  431. package/neural-interface/lib/whatsapp/transcription.js +463 -0
  432. package/neural-interface/package-lock.json +2 -2
  433. package/neural-interface/package.json +1 -1
  434. package/neural-interface/public/index.html +2 -0
  435. package/neural-interface/public/index2d.html +2 -0
  436. package/neural-interface/public/offline.html +16 -1
  437. package/neural-interface/public/onboarding.html +96 -2
  438. package/neural-interface/public/shared/account-i18n.js +7 -0
  439. package/neural-interface/public/shared/account-menu.js +542 -0
  440. package/neural-interface/public/shared/account-usage.js +79 -0
  441. package/neural-interface/public/shared/account-view.js +336 -0
  442. package/neural-interface/public/shared/agent-runtime-options.js +28 -8
  443. package/neural-interface/public/shared/assistant/asst-account-menu.js +298 -0
  444. package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
  445. package/neural-interface/public/shared/assistant/asst-api.js +39 -2
  446. package/neural-interface/public/shared/assistant/asst-brain-picker.js +288 -32
  447. package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
  448. package/neural-interface/public/shared/assistant/asst-mascot.js +126 -7
  449. package/neural-interface/public/shared/assistant/asst-menu.js +219 -80
  450. package/neural-interface/public/shared/assistant/asst-model-chip.js +135 -0
  451. package/neural-interface/public/shared/assistant/asst-panel.js +87 -16
  452. package/neural-interface/public/shared/assistant/asst-render.js +38 -7
  453. package/neural-interface/public/shared/assistant/asst-route.js +72 -14
  454. package/neural-interface/public/shared/assistant/asst-routes-editor.js +45 -6
  455. package/neural-interface/public/shared/assistant/asst-slash.js +1 -1
  456. package/neural-interface/public/shared/assistant/asst-state.js +1 -1
  457. package/neural-interface/public/shared/assistant/asst-styles.js +160 -29
  458. package/neural-interface/public/shared/cdx/cdx-commands.js +4 -1
  459. package/neural-interface/public/shared/cdx/cdx-features.js +22 -14
  460. package/neural-interface/public/shared/cdx/cdx-icons.js +2 -0
  461. package/neural-interface/public/shared/cdx/cdx-panel.js +49 -108
  462. package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
  463. package/neural-interface/public/shared/cdx/cdx-styles.js +172 -111
  464. package/neural-interface/public/shared/cdx/cdx-tabs.js +264 -664
  465. package/neural-interface/public/shared/cdx/cdx-ws.js +2 -0
  466. package/neural-interface/public/shared/claude-account-view.js +3 -0
  467. package/neural-interface/public/shared/cli-install-view.js +392 -0
  468. package/neural-interface/public/shared/cli-status.js +282 -22
  469. package/neural-interface/public/shared/cp/cp-events.js +2 -1
  470. package/neural-interface/public/shared/cp/cp-styles.js +46 -0
  471. package/neural-interface/public/shared/desktop-bridge.js +39 -0
  472. package/neural-interface/public/shared/desktop-titlebar-page.js +59 -0
  473. package/neural-interface/public/shared/desktop-titlebar-view.js +122 -0
  474. package/neural-interface/public/shared/desktop-titlebar.js +153 -0
  475. package/neural-interface/public/shared/desktop-update-view.js +462 -0
  476. package/neural-interface/public/shared/editor/editor-panel.js +1 -1
  477. package/neural-interface/public/shared/explorer/fx-main.js +1 -1
  478. package/neural-interface/public/shared/git-bash-view.js +145 -0
  479. package/neural-interface/public/shared/html-shell.js +1 -0
  480. package/neural-interface/public/shared/i18n.js +19 -1
  481. package/neural-interface/public/shared/install-details-view.js +86 -0
  482. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -1
  483. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +36 -1
  484. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +17 -0
  485. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +169 -2
  486. package/neural-interface/public/shared/pwa-install-page.js +3 -1
  487. package/neural-interface/public/shared/settings/settings-about-pane.js +271 -0
  488. package/neural-interface/public/shared/settings/settings-about.js +254 -0
  489. package/neural-interface/public/shared/settings/settings-ia.js +15 -0
  490. package/neural-interface/public/shared/showcase-privacy.js +490 -0
  491. package/neural-interface/public/shared/storage.js +30 -3
  492. package/neural-interface/public/shared/styles.css +281 -32
  493. package/neural-interface/public/shared/synabun-mascot.js +48 -2
  494. package/neural-interface/public/shared/ui-claude-panel.js +262 -217
  495. package/neural-interface/public/shared/ui-cli-banner.js +355 -0
  496. package/neural-interface/public/shared/ui-explorer.js +2 -0
  497. package/neural-interface/public/shared/ui-git-bash.js +124 -0
  498. package/neural-interface/public/shared/ui-judgments.js +10 -1
  499. package/neural-interface/public/shared/ui-loading.js +30 -6
  500. package/neural-interface/public/shared/ui-menubar.js +13 -3
  501. package/neural-interface/public/shared/ui-navbar.js +3 -1
  502. package/neural-interface/public/shared/ui-provider-auth.js +104 -0
  503. package/neural-interface/public/shared/ui-provider-terminal-auth.js +223 -0
  504. package/neural-interface/public/shared/ui-sessions.js +9 -3
  505. package/neural-interface/public/shared/ui-settings.js +55 -3
  506. package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
  507. package/neural-interface/public/shared/ui-sync.js +8 -0
  508. package/neural-interface/public/shared/ui-terminal.js +17 -0
  509. package/neural-interface/public/shared/ui-tool-updates.js +5 -0
  510. package/neural-interface/public/shared/ui-update.js +598 -7
  511. package/neural-interface/public/shared/ui-whatsapp.js +21 -6
  512. package/neural-interface/public/shared/whatsapp/wa-view.js +51 -8
  513. package/neural-interface/public/shared/worker-end.js +2 -0
  514. package/neural-interface/public/sw.js +1 -1
  515. package/neural-interface/public/variant/2d/main.js +10 -1
  516. package/neural-interface/public/variant/3d/main.js +10 -1
  517. package/neural-interface/scripts/rebuild-pty.js +14 -2
  518. package/neural-interface/server.js +1384 -679
  519. package/npm-shrinkwrap.json +4424 -0
  520. package/package.json +57 -2
  521. package/postinstall.js +61 -47
  522. package/preuninstall.js +8 -35
  523. package/setup.js +80 -47
  524. package/skills/synabun/modules/idea.md +1 -1
  525. package/skills/synabun/modules/schedule.md +1 -1
  526. package/updater.cmd +2 -2
  527. package/updater.mjs +97 -3
@@ -29,6 +29,9 @@ import { emit, on, state as appState } from '../state.js';
29
29
  import { storage } from '../storage.js';
30
30
  import { KEYS } from '../constants.js';
31
31
  import { t } from '../i18n.js';
32
+ // The account words of the brain picker and the accounts manager (account-view.js) in the page's language.
33
+ // Here, not in those modules: node tests load them, and i18n.js reads the page's storage.
34
+ import '../account-i18n.js';
32
35
  import { forgetGreeting, mountGreeting } from '../greeting/greeting-card.js';
33
36
  import { notify, NOTIF_TYPE } from '../ui-notifications.js';
34
37
  import { getProviderMeta } from '../provider-icons.js';
@@ -51,7 +54,7 @@ import { createAgentsDock } from './asst-agents-dock.js';
51
54
  import { createUsageGauge } from './asst-usage.js';
52
55
  import { closeMenu, isMenuOpen, openMenu } from './asst-menu.js';
53
56
  import { createRouteModeControl, openModelsManager, openRoutesEditor, refreshModelsManager, renderRouteCard, routeModeLabel } from './asst-route.js';
54
- import { budgetChipText, budgetTone, openBudgetEditor } from './asst-budget.js';
57
+ import { budgetChipText, budgetSpendText, budgetTone, closeBudgetEditor, createBudgetController, mountBudgetChip, openBudgetEditor } from './asst-budget.js';
55
58
  import { computerRemoteView, createComputerPanel, createComputerToggle, normalizeComputerRemote, openFrameLightbox, closeFrameLightbox, requestDesktopStop } from './asst-computer.js';
56
59
  import {
57
60
  applyLimit, applyLimitPacket, applyUsagePacket, brainLabel, createRenameWriter, createTabState, DEFAULT_ROUTE_MODE, deserializeTabState, fmtCost, fmtRunLine, limitExpiresIn, limitNoticeText, modelShortName,
@@ -61,7 +64,7 @@ import {
61
64
  import {
62
65
  closeAssistantSession, dispatchAssistantRun, escalateAssistantRun, focusAssistantRun, getAssistantRun,
63
66
  fetchManageCatalog, getAssistantRunResult, getAssistantSession, getAssistantSessionUsage, getDesktopStatus, getRouting, killAllAssistantRuns, removeAssistantRun, clearFinishedAssistantRuns,
64
- listAssistantSessions, patchAssistantSession, patchHiddenModels, stopAssistantRun, uploadAssistantAttachment,
67
+ listAssistantSessions, patchAssistantSession, patchHiddenModels, setAdminSandbox, stopAssistantRun, uploadAssistantAttachment,
65
68
  } from './asst-api.js';
66
69
 
67
70
  const ICON_SESSIONS = '<svg viewBox="0 0 24 24"><path d="M3 6h18M3 12h18M3 18h12"/></svg>';
@@ -151,8 +154,9 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
151
154
  engine: '',
152
155
  sdkVersion: '',
153
156
  routing: null, // server view { mode, effectiveMode, defaultMode, askBelow }
154
- features: { routing: null, computer: null, testDensity: null, rename: null }, // null = unknown yet
157
+ features: { routing: null, computer: null, testDensity: null, rename: null, adminSandbox: null }, // null = unknown yet
155
158
  testDensityEffective: null, // server view: the level in effect
159
+ adminSandbox: null, // server view: the global Admin sandbox setting (every agent)
156
160
  brainCapabilities: null,
157
161
  computerEffective: st.computerUse === true,
158
162
  desktopSupported: null,
@@ -365,11 +369,11 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
365
369
  }
366
370
 
367
371
  function renderCost() {
368
- // The server's budget view (brain + agents, the caps) when it sends one.
369
- const budget = st.budget && Number.isFinite(Number(st.budget.totalUsd)) ? st.budget : null;
370
- const total = budget ? Number(budget.totalUsd) : totalCost();
371
- costEl.textContent = fmtCost(total);
372
- costEl.hidden = !(total > 0);
372
+ // The confirmed SessionBudget includes both brain and worker spend.
373
+ const budget = st.budget && Number.isFinite(Number(st.budget.spentUsd)) ? st.budget : null;
374
+ const total = budget ? Number(budget.spentUsd) : totalCost();
375
+ costEl.textContent = budget ? budgetSpendText(budget, { t }) : fmtCost(total);
376
+ costEl.hidden = !budget && !(total > 0);
373
377
  const tone = budgetTone(budget);
374
378
  if (tone && tone !== 'ok') costEl.dataset.tone = tone;
375
379
  else delete costEl.dataset.tone;
@@ -379,6 +383,7 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
379
383
  ? tt('assistant.topbar.costSplit', 'Session {session} · agents {agents}', { session: fmtCost(st.costUsd || 0), agents: fmtCost(st.dispatchUsd) })
380
384
  : tt('assistant.topbar.cost', 'Session cost');
381
385
  costEl.setAttribute('data-tooltip', tip);
386
+ costEl.setAttribute('aria-label', `${tt('assistant.budget.title', 'Budget')}: ${tip}`);
382
387
  }
383
388
 
384
389
  function brainModelLabel() {
@@ -441,6 +446,12 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
441
446
  });
442
447
 
443
448
  // ── Renderer ──
449
+ let emptyBudgetChip = null;
450
+ const budgetController = createBudgetController({ t, sessionId, onSaved: (data) => {
451
+ if (destroyed) return;
452
+ if (data?.session) st.budget = data.session;
453
+ renderCost();
454
+ } });
444
455
  const renderer = createRenderer(transcriptEl, {
445
456
  t,
446
457
  providerIcon,
@@ -463,6 +474,16 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
463
474
  emptySuggestions: () => emptySuggestions(),
464
475
  // "Choose a project" in the empty state is the toolbar's Working project field (asst-brain-picker.js).
465
476
  mountEmptyProject: (slot) => picker.mountEmptyProject(slot),
477
+ mountEmptyBudget: (slot) => {
478
+ emptyBudgetChip?.destroy();
479
+ emptyBudgetChip = mountBudgetChip(slot, budgetController, { t, mascot: () => director });
480
+ return emptyBudgetChip.el;
481
+ },
482
+ unmountEmptyControls: () => {
483
+ picker.mountEmptyProject(null);
484
+ emptyBudgetChip?.destroy();
485
+ emptyBudgetChip = null;
486
+ },
466
487
  onSuggestion: (template) => composer.fillTemplate(template),
467
488
  heroOrigin: () => { try { return host.takeHeroOrigin?.() || null; } catch { return null; } },
468
489
  onMascotChange: () => director?.retarget(),
@@ -549,7 +570,9 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
549
570
  // Model · effort · permission mode · account sit in the composer row next to attach; project and MCP in the toolbar.
550
571
  const picker = createBrainPicker({ brain: composer.el.querySelector('.asst-composer-brain'), context: barGroups.context }, {
551
572
  t,
573
+ mascot: () => director,
552
574
  getBrain: () => st.brain,
575
+ getRunning: () => st.running,
553
576
  onChange: (next, meta) => applyBrain(next, meta),
554
577
  onAddAccount: (provider) => openAccountsManager({ provider, ...accountHooks() }).openAdd(),
555
578
  onManageAccounts: (provider) => openAccountsManager({ provider, ...accountHooks() }),
@@ -561,6 +584,9 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
561
584
  // Test density sits right after the permission mode; hidden on a server that does not know it.
562
585
  getTestDensity: () => testDensityView(),
563
586
  onTestDensity: (level) => setTestDensity(level),
587
+ // The global Admin sandbox switch, in the permission menu; hidden on a server that does not serve it.
588
+ getAdminSandbox: () => adminSandboxView(),
589
+ onAdminSandbox: (enabled) => changeAdminSandbox(enabled),
564
590
  // The registered projects arrived: their labels name the project in the fields and the "Fix … in" suggestion.
565
591
  onProjects: () => { picker.render(); renderer.refreshEmpty(); },
566
592
  });
@@ -625,7 +651,10 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
625
651
  function fetchUsage() {
626
652
  const seq = ++usageFetchSeq;
627
653
  getAssistantSessionUsage(sessionId).then((data) => {
628
- if (!destroyed && seq === usageFetchSeq && data?.ok && data.usage) applyUsage(data.usage);
654
+ if (!destroyed && seq === usageFetchSeq && data?.ok) {
655
+ if (data.budget) budgetController.receiveSession(data.budget);
656
+ if (data.usage) applyUsage(data.usage);
657
+ }
629
658
  }).catch(() => { /* older servers do not expose usage */ });
630
659
  }
631
660
  function applyUsage(packet) {
@@ -813,6 +842,9 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
813
842
  case 'assistant:mailbox':
814
843
  renderer.appendMailbox(packet.items || packet.events || packet.mailbox || []);
815
844
  return;
845
+ case 'assistant:budget':
846
+ budgetController.receiveSession(packet.budget);
847
+ return;
816
848
  case 'assistant:cost':
817
849
  applyCost(packet);
818
850
  return;
@@ -885,7 +917,7 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
885
917
  if (session != null) st.costUsd = session;
886
918
  else if (!packet?.runId && num(packet?.costUsd) != null) st.costUsd = num(packet.costUsd);
887
919
  if (num(packet?.dispatchUsd) != null) st.dispatchUsd = num(packet.dispatchUsd);
888
- if (packet?.budget && typeof packet.budget === 'object') st.budget = packet.budget;
920
+ if (packet?.budget && typeof packet.budget === 'object') budgetController.receiveSession(packet.budget);
889
921
  if (typeof packet?.brainMetered === 'boolean') st.brainMetered = packet.brainMetered;
890
922
  if (packet?.runId && num(packet.costUsd) != null) dock.patchRun(packet.runId, { costUsd: num(packet.costUsd) });
891
923
  if (session != null) st.serverCost = true;
@@ -905,7 +937,7 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
905
937
  const next = normalizeBrain({ ...st.brain, provider: meta.brain, model: meta.model ?? st.brain.model, effort: meta.effort ?? st.brain.effort, accountId: meta.accountId ?? st.brain.accountId, mcpProfile: meta.mcpProfile ?? st.brain.mcpProfile, project: meta.cwd ?? st.brain.project });
906
938
  if (JSON.stringify(next) !== JSON.stringify(st.brain)) { st.brain = next; picker.render(); host.setBrain?.(next); persist(); }
907
939
  }
908
- if (meta.budget && typeof meta.budget === 'object') st.budget = meta.budget;
940
+ if (meta.budget && typeof meta.budget === 'object') budgetController.receiveSession(meta.budget);
909
941
  if (Number.isFinite(Number(meta.costUsd))) { st.costUsd = Number(meta.costUsd); renderCost(); }
910
942
  if (meta.providerSessionId) st.providerSessionId = meta.providerSessionId;
911
943
  if (meta.routing && typeof meta.routing === 'object') {
@@ -922,7 +954,9 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
922
954
  if (typeof meta.features.computer === 'boolean') st.features.computer = meta.features.computer;
923
955
  if (typeof meta.features.testDensity === 'boolean') st.features.testDensity = meta.features.testDensity;
924
956
  if (typeof meta.features.rename === 'boolean') st.features.rename = meta.features.rename;
957
+ if (typeof meta.features.adminSandbox === 'boolean') st.features.adminSandbox = meta.features.adminSandbox;
925
958
  }
959
+ if (typeof meta.adminSandbox === 'boolean') st.adminSandbox = meta.adminSandbox;
926
960
  if ('testDensityExplicit' in meta) st.testDensity = normalizeTestDensity(meta.testDensityExplicit, null);
927
961
  if (meta.testDensity) st.testDensityEffective = normalizeTestDensity(meta.testDensity);
928
962
  if (typeof meta.computerUse === 'boolean') st.computerEffective = meta.computerUse;
@@ -1038,6 +1072,31 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
1038
1072
  if (!quiet) renderer.appendStatus(tt('assistant.status.testDensity', 'Test density: {level}', { level: testDensityLabel(next, t) }));
1039
1073
  }
1040
1074
 
1075
+ /** The global Admin sandbox setting (true / false), or null while the server does not serve it. */
1076
+ function adminSandboxView() {
1077
+ if (st.features.adminSandbox !== true || typeof st.adminSandbox !== 'boolean') return null;
1078
+ return st.adminSandbox;
1079
+ }
1080
+
1081
+ /** Every agent SynaBun starts follows it; every window hears the change (sync:agents:admin-sandbox-changed). */
1082
+ async function changeAdminSandbox(enabled) {
1083
+ const before = st.adminSandbox;
1084
+ st.adminSandbox = enabled === true;
1085
+ picker.render();
1086
+ try {
1087
+ const view = await setAdminSandbox(st.adminSandbox);
1088
+ if (typeof view?.adminSandbox === 'boolean') st.adminSandbox = view.adminSandbox;
1089
+ picker.render();
1090
+ renderer.appendStatus(st.adminSandbox
1091
+ ? tt('assistant.adminSandbox.statusOn', 'Admin sandbox: on. On Windows, Codex may ask for administrator approval once per account to set up its sandbox.')
1092
+ : tt('assistant.adminSandbox.statusOff', 'Admin sandbox: off. No agent SynaBun starts asks for administrator approval for a sandbox.'));
1093
+ } catch (err) {
1094
+ st.adminSandbox = before;
1095
+ picker.render();
1096
+ toast(err?.message || String(err));
1097
+ }
1098
+ }
1099
+
1041
1100
  /** Fresh tabs inherit the last-used test density (like the route mode). */
1042
1101
  function applyLastUsedTestDensity() {
1043
1102
  if (saved || st.features.testDensity !== true || st.testDensity != null) return;
@@ -1468,13 +1527,13 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
1468
1527
  if (meta.silent) return;
1469
1528
  if (meta.requiresSwitch) {
1470
1529
  const b = st.brain;
1471
- sock.send({ type: 'switch_brain', brain: b.provider, model: b.model || undefined, effort: b.effort || undefined, accountId: b.accountId || undefined, mcpProfile: b.mcpProfile || undefined, cwd: b.project || undefined, agent: b.agent || undefined, permissionMode: b.permissionMode, planMode: b.planMode === true });
1530
+ if (!meta.restartBrain) sock.send({ type: 'switch_brain', brain: b.provider, model: b.model || undefined, effort: b.effort || undefined, accountId: b.accountId || undefined, mcpProfile: b.mcpProfile || undefined, cwd: b.project || undefined, agent: b.agent || undefined, permissionMode: b.permissionMode, planMode: b.planMode === true });
1472
1531
  renderer.appendDivider(`${tt('assistant.status.brain', 'brain')} → ${brainLabel(b)}`);
1473
1532
  renderer.forgetSeen(); // the new brain's ids start over
1474
1533
  } else if (before.permissionMode !== st.brain.permissionMode || !!before.planMode !== !!st.brain.planMode) {
1475
1534
  sock.send({ type: 'set_permission_mode', mode: st.brain.permissionMode, planMode: st.brain.planMode === true });
1476
1535
  }
1477
- patchAssistantSession(sessionId, { brain: st.brain }).catch((err) => {
1536
+ patchAssistantSession(sessionId, { brain: st.brain, ...(meta.restartBrain ? { restartBrain: true } : {}) }).catch((err) => {
1478
1537
  // Refused (a model disabled in Assistant → Models): undo, so the refused
1479
1538
  // brain is neither shown nor saved as the last used one — the next start
1480
1539
  // would ask for it again. A brain switch already got the error on the socket.
@@ -1653,7 +1712,7 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
1653
1712
  items.push({ id: 'compact', label: tt('assistant.topbar.compact', 'Compact context'), onSelect: () => compact() });
1654
1713
  if (routingAvailable()) items.push({ id: 'routes', label: tt('assistant.routes.editor', 'Model routes…'), icon: ICON_ROUTE, onSelect: () => openRoutes() });
1655
1714
  items.push({ id: 'models', label: tt('assistant.models.menu', 'Models…'), onSelect: () => openModels() });
1656
- items.push({ id: 'budget', label: tt('assistant.budget.menu', 'Budget…'), desc: st.budget ? `${fmtCost(st.budget.totalUsd) || '$0.00'} / ${fmtCost(st.budget.hardUsd)}` : '', onSelect: () => openBudget() });
1715
+ items.push({ id: 'budget', label: tt('assistant.budget.menu', 'Budget…'), desc: budgetSpendText(st.budget, { t }), onSelect: () => openBudget() });
1657
1716
  items.push({ id: 'clear', label: tt('assistant.topbar.clear', 'Clear transcript'), onSelect: () => clearTranscript() });
1658
1717
  items.push({ id: 'help', label: tt('assistant.topbar.helpShortcuts', 'Help & shortcuts'), onSelect: () => openHelp() });
1659
1718
  items.push({ kind: 'separator' });
@@ -1707,12 +1766,12 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
1707
1766
  }
1708
1767
  }
1709
1768
 
1710
- // Assistant → Budget: the money caps (applied live by the server).
1769
+ // Assistant → Budget: one saved amount plus the current session's live split.
1711
1770
  function openBudget() {
1712
1771
  openBudgetEditor({
1713
1772
  t,
1714
1773
  sessionId,
1715
- brainMetered: st.brainMetered !== false && st.brain?.provider !== 'codex',
1774
+ controller: budgetController,
1716
1775
  onToast: toast,
1717
1776
  onSaved: (res) => { if (res?.session) { st.budget = res.session; renderCost(); } },
1718
1777
  }).catch(() => {});
@@ -1821,6 +1880,8 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
1821
1880
 
1822
1881
  // ── Bus ──
1823
1882
  unsubs.push(on('sync:assistant:session-updated', (msg) => { const s = msg?.session || msg; if (s?.id === sessionId) applySessionMeta(s); }));
1883
+ unsubs.push(on('sync:assistant:budget', (msg) => { if ((msg?.assistantSessionId || msg?.sessionId) === sessionId) budgetController.receiveSession(msg.budget); }));
1884
+ unsubs.push(on('sync:assistant:budget-changed', () => budgetController.load()));
1824
1885
  unsubs.push(on('sync:assistant:cost', (msg) => { if (!msg?.assistantSessionId || msg.assistantSessionId === sessionId) applyCost(msg); }));
1825
1886
  unsubs.push(on('sync:assistant:mailbox', (msg) => { if (msg?.assistantSessionId === sessionId) renderer.appendMailbox(msg.items || msg.events || []); }));
1826
1887
  unsubs.push(on('sync:assistant:permission-request', (msg) => {
@@ -1849,6 +1910,12 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
1849
1910
  st.routing = { ...(st.routing || {}), defaultMode: def, effectiveMode: st.routeMode || def };
1850
1911
  routeChip.render();
1851
1912
  }));
1913
+ // The global Admin sandbox setting changed in some window (this one included): every panel shows it.
1914
+ unsubs.push(on('sync:agents:admin-sandbox-changed', (msg) => {
1915
+ if (typeof msg?.adminSandbox !== 'boolean' || msg.adminSandbox === st.adminSandbox) return;
1916
+ st.adminSandbox = msg.adminSandbox;
1917
+ picker.render();
1918
+ }));
1852
1919
  unsubs.push(on('session:info', () => updateGuest()));
1853
1920
  unsubs.push(on('permissions:changed', () => updateGuest()));
1854
1921
 
@@ -1891,6 +1958,7 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
1891
1958
  sock.connect();
1892
1959
  });
1893
1960
  fetchUsage();
1961
+ budgetController.load();
1894
1962
 
1895
1963
  const controller = {
1896
1964
  sessionId,
@@ -1934,6 +2002,9 @@ export function mountAssistant(viewport, { sessionId, brain, host = {}, session
1934
2002
  if (persistTimer) { clearTimeout(persistTimer); persistTimer = null; }
1935
2003
  clearTimeout(announceTimer);
1936
2004
  if (replay) { clearTimeout(replay.timer); replay = null; }
2005
+ closeBudgetEditor(budgetController);
2006
+ emptyBudgetChip?.destroy();
2007
+ budgetController.destroy();
1937
2008
  director.destroy();
1938
2009
  usage.destroy();
1939
2010
  clearTimeout(limitTimer);
@@ -228,6 +228,7 @@ export function updateRunCard(card, run, opts = {}) {
228
228
  const r = run.route;
229
229
  const routeBits = [r.taskClassLabel || r.taskClass || t('assistant.routes.task', 'task')];
230
230
  if (r.status === 'pending') routeBits.push(t('assistant.routes.state.pending', 'waiting for your choice'));
231
+ else if (r.decidedBy === 'budget' || r.source === 'budget') routeBits.push(t('assistant.routes.budgetDecision', 'budget re-route'));
231
232
  else if (r.decidedBy === 'user') routeBits.push(t('assistant.routes.youChose', 'you chose'));
232
233
  else if (r.remembered || r.decidedBy === 'remembered') routeBits.push(t('assistant.routes.remembered', 'your saved choice'));
233
234
  else if (Number.isFinite(Number(r.confidence))) routeBits.push(`${Math.round(Number(r.confidence) <= 1 ? Number(r.confidence) * 100 : Number(r.confidence))}%`);
@@ -548,8 +549,10 @@ export function createRenderer(transcriptEl, hooks = {}) {
548
549
 
549
550
  /** A menu opened from the empty state's project picker does not outlive it. */
550
551
  function closeEmptyMenu() {
551
- const btn = emptyEl?.querySelector('.asst-empty-project-btn');
552
- if (btn && isMenuOpen(btn)) closeMenu();
552
+ for (const btn of emptyEl?.querySelectorAll('.asst-empty-project-btn') || []) {
553
+ if (isMenuOpen(btn)) closeMenu();
554
+ }
555
+ hooks.unmountEmptyControls?.();
553
556
  }
554
557
 
555
558
  // ── The hero ──
@@ -2689,7 +2692,12 @@ export function createRenderer(transcriptEl, hooks = {}) {
2689
2692
 
2690
2693
  function upsertRunCard(run, { result = null } = {}) {
2691
2694
  if (!run?.runId) return null;
2692
- runInfo.set(run.runId, { ...runInfo.get(run.runId), title: run.title || run.task || runInfo.get(run.runId)?.title, state: run.status || runInfo.get(run.runId)?.state, provider: run.provider });
2695
+ const prev = runInfo.get(run.runId);
2696
+ const status = run.status || prev?.state;
2697
+ const tone = status ? runTone({ ...run, status }) : '';
2698
+ const routeId = run.route?.routeId || prev?.routeId || '';
2699
+ runInfo.set(run.runId, { ...prev, title: run.title || run.task || prev?.title, state: status, provider: run.provider, routeId, working: tone === 'running' || tone === 'queued' });
2700
+ if (routeId) syncRouteLive(routeId);
2693
2701
  const cardOpts = runCardOpts(result);
2694
2702
  let card = runCards.get(run.runId);
2695
2703
  if (card && card.isConnected) {
@@ -2745,21 +2753,42 @@ export function createRenderer(transcriptEl, hooks = {}) {
2745
2753
  return { phase, t: hooks.t, providerIcon: hooks.providerIcon, brainLabel: hooks.brainLabel, onChangeRoutes: hooks.onChangeRoutes };
2746
2754
  }
2747
2755
 
2748
- /** Create or update the line for `route.routeId` in place. */
2756
+ /** Create or update the line for `route.routeId` in place. Only a new line enters (never on replay). */
2749
2757
  function upsertRouteLine(route, phase = 'decided') {
2750
2758
  const routeId = route?.routeId;
2751
2759
  if (!routeId) return null;
2752
2760
  const existing = routeLines.get(routeId);
2753
- if (existing && existing.el.isConnected) { existing.update(route, phase); return existing; }
2761
+ if (existing && existing.el.isConnected) { existing.update(route, phase); syncRouteLive(routeId); return existing; }
2754
2762
  const line = createRouteLine(route, routeLineOpts(phase));
2755
2763
  routeLines.set(routeId, line);
2756
2764
  append(line.el);
2765
+ syncRouteLive(routeId);
2766
+ enterRow(line.el);
2757
2767
  return line;
2758
2768
  }
2759
2769
 
2760
2770
  /** Adopt a line created by a collapsing route card so later events update it. */
2761
2771
  function registerRouteLine(routeId, line) {
2762
- if (routeId && line) routeLines.set(routeId, line);
2772
+ if (!routeId || !line) return;
2773
+ const fresh = routeLines.get(routeId) !== line;
2774
+ routeLines.set(routeId, line);
2775
+ syncRouteLive(routeId);
2776
+ if (fresh && line.el?.isConnected) enterRow(line.el);
2777
+ }
2778
+
2779
+ /** The line is live while a run it dispatched is queued or working (run cards keep runInfo current). */
2780
+ function syncRouteLive(routeId) {
2781
+ const line = routeLines.get(routeId);
2782
+ if (!line?.setRunning) return;
2783
+ let working = false;
2784
+ for (const info of runInfo.values()) if (info.routeId === routeId && info.working) { working = true; break; }
2785
+ line.setRunning(working);
2786
+ }
2787
+
2788
+ /** A new row's entrance: the rack ledger's (4px rise and fade, --asst-dur-fast, --asst-ease-enter); none when calm. */
2789
+ function enterRow(node) {
2790
+ if (calm() || typeof node?.animate !== 'function') return;
2791
+ node.animate([{ opacity: 0, transform: 'translateY(4px)' }, { opacity: 1, transform: 'none' }], { duration: 140, easing: cssEase('--asst-ease-enter', 'cubic-bezier(0,0,.38,.9)') });
2763
2792
  }
2764
2793
 
2765
2794
  function removeRouteLine(routeId) {
@@ -2899,7 +2928,9 @@ export function createRenderer(transcriptEl, hooks = {}) {
2899
2928
  const projectSlot = emptyEl.querySelector('.asst-empty-project');
2900
2929
  let picker = null;
2901
2930
  try { picker = typeof hooks.mountEmptyProject === 'function' ? hooks.mountEmptyProject(projectSlot) : null; } catch { picker = null; }
2902
- projectSlot.hidden = !picker;
2931
+ let budget = null;
2932
+ try { budget = hooks.mountEmptyBudget?.(projectSlot); } catch { /* unavailable */ }
2933
+ projectSlot.hidden = !picker && !budget;
2903
2934
  renderSuggestions();
2904
2935
  $msgs.appendChild(emptyEl);
2905
2936
  // The host's "Starting…" character flies into place (not on replay or reduced motion).
@@ -22,6 +22,8 @@ import { catalogModelRow, fmtModelPrice, hiddenModelFor, ICON_CARET, invalidateM
22
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) => {
@@ -93,6 +107,7 @@ function normalizeRouteOption(o) {
93
107
  provider: normalizeProvider(src.provider, str(src.provider) || null),
94
108
  model: str(src.model),
95
109
  effort: str(src.effort) || null,
110
+ ...(Object.hasOwn(src, 'accountId') ? { accountId: src.accountId, account: obj(src.account), accountError: obj(src.accountError) } : {}),
96
111
  label: str(src.label),
97
112
  badge: str(src.badge) || null,
98
113
  tier: str(src.tier) || null,
@@ -101,6 +116,7 @@ function normalizeRouteOption(o) {
101
116
  contextWindow: num(src.contextWindow),
102
117
  reason: str(src.reason),
103
118
  warning: str(src.warning) || null,
119
+ budget: str(src.budget) || null,
104
120
  disabled: src.disabled === true,
105
121
  disabledReason: str(src.disabledReason) || null,
106
122
  };
@@ -174,7 +190,7 @@ export function sameRouteTarget(a, b) {
174
190
  return (str(a.kind) || 'direct') === (str(b.kind) || 'direct')
175
191
  && str(a.provider) === str(b.provider)
176
192
  && modelKey(a.model) === modelKey(b.model)
177
- && (str(a.effort) || '') === (str(b.effort) || '');
193
+ && (str(a.effort) || '') === (str(b.effort) || '') && str(a.accountId) === str(b.accountId);
178
194
  }
179
195
 
180
196
  const BADGE_RANK = { suggested: 0, remembered: 1, current: 2, capable: 3, cheaper: 4, stronger: 5 };
@@ -269,6 +285,7 @@ export function routeOptions(n, { brainLabel = '', t = null } = {}) {
269
285
  // "here" is the brain's own model: prefer its display label over a raw id.
270
286
  title: isHere ? tr('assistant.routes.doHere', 'Do it here with {model}', { model: str(o.label) || hereModel }) : (label || o.id),
271
287
  badgeText: badgeText(o, n, tr),
288
+ disabledReason: o.budget === 'share_used_up' ? routeBudgetReason({ provider: o.provider }, t) : o.disabledReason,
272
289
  selected: o.id === defaultId,
273
290
  };
274
291
  });
@@ -350,6 +367,16 @@ export function routeCatalogRow(pref, catalog) {
350
367
  return pref.model ? catalogModelRow(pref.provider, pref.model, catalog) || aliasRow(pref.provider, pref.model, rows) : rows.find(m => m.isDefault) || null;
351
368
  }
352
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
+
353
380
  /** Text parts of a route line (pure). */
354
381
  export function routeLineParts(route, phase = 'decided', { brainLabel = '', t = null } = {}) {
355
382
  const tr = tf(t);
@@ -366,10 +393,12 @@ export function routeLineParts(route, phase = 'decided', { brainLabel = '', t =
366
393
  else if (phase === 'declined' && str(r.reasonCode) === 'no_capable_model') {
367
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');
368
395
  }
396
+ else if (str(r.reasonCode) === 'budget_exhausted') state = routeBudgetReason({ dry: r.dry }, t);
369
397
  else if (phase === 'declined') state = tr('assistant.routes.state.declined', 'declined');
370
398
  else if (phase === 'cancelled') state = tr('assistant.routes.state.cancelled', 'cancelled');
371
399
  let decided = '';
372
- 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;
373
402
  case 'brain': decided = tr('assistant.routes.decidedBy', 'decided by {model}', { model: brainLabel || tr('assistant.routes.theBrainLower', 'the assistant') }); break;
374
403
  case 'user': decided = tr('assistant.routes.youChose', 'you chose'); break;
375
404
  case 'remembered': decided = tr('assistant.routes.remembered', 'your saved choice'); break;
@@ -377,6 +406,8 @@ export function routeLineParts(route, phase = 'decided', { brainLabel = '', t =
377
406
  default: decided = r.remembered ? tr('assistant.routes.remembered', 'your saved choice') : '';
378
407
  }
379
408
  const meta = [];
409
+ const accountLabel = routeDisplayAccount(target, t);
410
+ if (accountLabel) meta.push(accountLabel);
380
411
  if (effort && effort !== 'off') meta.push(effort);
381
412
  if (confidence) meta.push(confidence);
382
413
  return {
@@ -390,7 +421,7 @@ export function routeLineParts(route, phase = 'decided', { brainLabel = '', t =
390
421
  muted: phase === 'expired' || phase === 'declined' || phase === 'cancelled',
391
422
  pending: phase === 'pending' || phase === 'card',
392
423
  decidedText: decided,
393
- reason: str(r.reason),
424
+ reason: target?.accountError ? routeAccountError(target.accountError, t) : str(r.reason),
394
425
  alternatives: arr(r.alternatives).map(a => routeTargetLabel(a, { effort: true })).filter(Boolean),
395
426
  corrections: arr(r.corrections).map(c => correctionText(c, tr)).filter(Boolean),
396
427
  };
@@ -400,9 +431,15 @@ export function routeLineParts(route, phase = 'decided', { brainLabel = '', t =
400
431
  function correctionText(c, tr) {
401
432
  if (typeof c === 'string') return c;
402
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
+ }
403
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) });
404
441
  if (c.reason === 'remembered_disabled') return tr('assistant.routes.correction.rememberedDisabled', 'saved route {from} is disabled, kept the pick', { from: str(c.from) });
405
- const text = firstStr(c.text, c.summary);
442
+ const text = firstStr(c.text, c.summary, c.message);
406
443
  if (text) return text;
407
444
  if (c.field && (c.from || c.to)) return `${c.field}: ${str(c.from) || '—'} → ${str(c.to) || 'default'}`;
408
445
  return str(c.field);
@@ -416,6 +453,14 @@ export function routeLineText(route, phase, opts) {
416
453
  return [`⇄ ${p.taskClass} → ${head}`, ...p.meta].join(' · ');
417
454
  }
418
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
+
419
464
  // ═══════════════════════════════════════════
420
465
  // DOM
421
466
  // ═══════════════════════════════════════════
@@ -449,8 +494,12 @@ function providerColor(provider) {
449
494
  let _lineUid = 0;
450
495
 
451
496
  /**
452
- * The compact route line. opts: { phase, t, providerIcon, brainLabel() , onChangeRoutes() }
453
- * 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) }.
454
503
  */
455
504
  export function createRouteLine(route, opts = {}) {
456
505
  const tr = tf(opts.t);
@@ -458,20 +507,19 @@ export function createRouteLine(route, opts = {}) {
458
507
  const detailId = `asst-route-detail-${++_lineUid}`;
459
508
  node.innerHTML = `
460
509
  <button type="button" class="asst-route-line-head" aria-expanded="false" aria-controls="${detailId}">
461
- <span class="asst-route-glyph" aria-hidden="true">⇄</span>
510
+ <span class="asst-route-glyph asst-icon" aria-hidden="true">${ICON_ROUTE}</span>
462
511
  <span class="asst-route-class"></span>
463
512
  <span class="asst-route-arrow" aria-hidden="true">→</span>
464
513
  <span class="asst-route-kind"></span>
465
514
  <span class="asst-route-icon asst-icon" aria-hidden="true"></span>
466
- <span class="asst-route-target"></span>
467
- <span class="asst-route-meta"></span>
468
- <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>
469
516
  <span class="asst-route-chev" aria-hidden="true">&#x203A;</span>
517
+ <span class="asst-route-sweep" aria-hidden="true"></span>
470
518
  </button>
471
519
  <div class="asst-route-detail" id="${detailId}" hidden></div>`;
472
520
  const head = node.querySelector('.asst-route-line-head');
473
521
  const detail = node.querySelector('.asst-route-detail');
474
- let current = { route: obj(route) || {}, phase: opts.phase || 'decided' };
522
+ let current = { route: obj(route) || {}, phase: opts.phase || 'decided', running: false };
475
523
 
476
524
  head.addEventListener('click', () => {
477
525
  const open = head.getAttribute('aria-expanded') !== 'true';
@@ -487,6 +535,8 @@ export function createRouteLine(route, opts = {}) {
487
535
  node.dataset.phase = current.phase;
488
536
  node.classList.toggle('muted', p.muted);
489
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;
490
540
  node.querySelector('.asst-route-class').textContent = p.taskClass;
491
541
  const arrow = node.querySelector('.asst-route-arrow');
492
542
  const kindEl = node.querySelector('.asst-route-kind');
@@ -494,7 +544,7 @@ export function createRouteLine(route, opts = {}) {
494
544
  const targetEl = node.querySelector('.asst-route-target');
495
545
  const metaEl = node.querySelector('.asst-route-meta');
496
546
  const stateEl = node.querySelector('.asst-route-state');
497
- arrow.hidden = !p.targetLabel && !p.pending;
547
+ arrow.hidden = !p.targetLabel && !p.pending && !p.kindLabel;
498
548
  kindEl.textContent = p.kindLabel;
499
549
  kindEl.hidden = !p.kindLabel;
500
550
  iconEl.innerHTML = p.targetLabel ? providerIconHtml(p.provider, opts) : '';
@@ -506,7 +556,9 @@ export function createRouteLine(route, opts = {}) {
506
556
  metaEl.hidden = !p.meta.length;
507
557
  stateEl.textContent = p.stateText;
508
558
  stateEl.hidden = !p.stateText;
559
+ node.querySelector('.asst-route-rest').hidden = targetEl.hidden && metaEl.hidden && stateEl.hidden;
509
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 });
510
562
 
511
563
  detail.innerHTML = '';
512
564
  const rows = [];
@@ -535,6 +587,12 @@ export function createRouteLine(route, opts = {}) {
535
587
  if (phase) current.phase = phase;
536
588
  render();
537
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
+ },
538
596
  };
539
597
  }
540
598
 
@@ -705,7 +763,7 @@ export function renderRouteCard(container, input, hooks = {}) {
705
763
  row.setAttribute('aria-disabled', o.disabled ? 'true' : 'false');
706
764
  row.tabIndex = -1;
707
765
  const kindWord = o.kind && o.kind !== 'direct' ? routeKindLabel(o.kind) : '';
708
- 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(' · ');
709
767
  row.innerHTML = `
710
768
  <span class="asst-route-num" aria-hidden="true">${esc(o.number)}</span>
711
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>
@@ -718,7 +776,7 @@ export function renderRouteCard(container, input, hooks = {}) {
718
776
  row.querySelector('.asst-route-opt-title').textContent = o.title;
719
777
  if (sub) row.querySelector('.asst-route-opt-sub').textContent = sub;
720
778
  const warn = row.querySelector('.asst-route-opt-warn');
721
- 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;
722
780
  if (o.badgeText) row.querySelector('.asst-route-badge').textContent = o.badgeText;
723
781
  row.addEventListener('click', () => { if (!o.disabled) select(o.id, { focus: true }); });
724
782
  return row;