synabun 2.0.7 → 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 (346) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +1 -1
  3. package/hooks/claude-code/post-plan.mjs +6 -30
  4. package/hooks/claude-code/session-start.mjs +1 -4
  5. package/hooks/claude-code/shared.mjs +106 -72
  6. package/lib/package-processes.js +670 -0
  7. package/lib/runtime-dependencies.js +225 -0
  8. package/mcp-server/dist/services/browser-bench.d.ts +1 -1
  9. package/mcp-server/dist/services/browser-bench.d.ts.map +1 -1
  10. package/mcp-server/dist/services/browser-bench.js +1 -1
  11. package/mcp-server/dist/services/browser-bench.js.map +1 -1
  12. package/mcp-server/dist/services/categories.d.ts +35 -2
  13. package/mcp-server/dist/services/categories.d.ts.map +1 -1
  14. package/mcp-server/dist/services/categories.js +77 -33
  15. package/mcp-server/dist/services/categories.js.map +1 -1
  16. package/mcp-server/dist/services/desktop-bench.d.ts +7 -7
  17. package/mcp-server/dist/services/desktop-bench.d.ts.map +1 -1
  18. package/mcp-server/dist/services/desktop-bench.js +1 -1
  19. package/mcp-server/dist/services/desktop-bench.js.map +1 -1
  20. package/mcp-server/dist/tools/agents.d.ts +1 -1
  21. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  22. package/mcp-server/dist/tools/agents.js +1 -1
  23. package/mcp-server/dist/tools/agents.js.map +1 -1
  24. package/mcp-server/dist/tools/bluesky.js +1 -1
  25. package/mcp-server/dist/tools/bluesky.js.map +1 -1
  26. package/mcp-server/dist/tools/browser-advanced.d.ts +1 -1
  27. package/mcp-server/dist/tools/browser-advanced.d.ts.map +1 -1
  28. package/mcp-server/dist/tools/browser-advanced.js +1 -1
  29. package/mcp-server/dist/tools/browser-advanced.js.map +1 -1
  30. package/mcp-server/dist/tools/browser-batch.d.ts +1 -1
  31. package/mcp-server/dist/tools/browser-batch.d.ts.map +1 -1
  32. package/mcp-server/dist/tools/browser-batch.js +1 -1
  33. package/mcp-server/dist/tools/browser-batch.js.map +1 -1
  34. package/mcp-server/dist/tools/browser-cheatsheet.d.ts +1 -1
  35. package/mcp-server/dist/tools/browser-cheatsheet.d.ts.map +1 -1
  36. package/mcp-server/dist/tools/browser-cheatsheet.js +1 -1
  37. package/mcp-server/dist/tools/browser-cheatsheet.js.map +1 -1
  38. package/mcp-server/dist/tools/browser-interact.d.ts +1 -1
  39. package/mcp-server/dist/tools/browser-interact.d.ts.map +1 -1
  40. package/mcp-server/dist/tools/browser-interact.js +1 -1
  41. package/mcp-server/dist/tools/browser-interact.js.map +1 -1
  42. package/mcp-server/dist/tools/browser-navigate.d.ts +1 -1
  43. package/mcp-server/dist/tools/browser-navigate.d.ts.map +1 -1
  44. package/mcp-server/dist/tools/browser-navigate.js +1 -1
  45. package/mcp-server/dist/tools/browser-navigate.js.map +1 -1
  46. package/mcp-server/dist/tools/browser-observe.d.ts +1 -1
  47. package/mcp-server/dist/tools/browser-observe.d.ts.map +1 -1
  48. package/mcp-server/dist/tools/browser-observe.js +1 -1
  49. package/mcp-server/dist/tools/browser-observe.js.map +1 -1
  50. package/mcp-server/dist/tools/card-tools.d.ts +1 -1
  51. package/mcp-server/dist/tools/card-tools.d.ts.map +1 -1
  52. package/mcp-server/dist/tools/card-tools.js +1 -1
  53. package/mcp-server/dist/tools/card-tools.js.map +1 -1
  54. package/mcp-server/dist/tools/category.d.ts +1 -1
  55. package/mcp-server/dist/tools/category.d.ts.map +1 -1
  56. package/mcp-server/dist/tools/category.js +5 -1
  57. package/mcp-server/dist/tools/category.js.map +1 -1
  58. package/mcp-server/dist/tools/choice.d.ts +1 -1
  59. package/mcp-server/dist/tools/choice.d.ts.map +1 -1
  60. package/mcp-server/dist/tools/choice.js +1 -1
  61. package/mcp-server/dist/tools/choice.js.map +1 -1
  62. package/mcp-server/dist/tools/computer.d.ts +1 -1
  63. package/mcp-server/dist/tools/computer.d.ts.map +1 -1
  64. package/mcp-server/dist/tools/computer.js +1 -1
  65. package/mcp-server/dist/tools/computer.js.map +1 -1
  66. package/mcp-server/dist/tools/discord-channel.d.ts +1 -1
  67. package/mcp-server/dist/tools/discord-channel.d.ts.map +1 -1
  68. package/mcp-server/dist/tools/discord-channel.js +1 -1
  69. package/mcp-server/dist/tools/discord-channel.js.map +1 -1
  70. package/mcp-server/dist/tools/discord-guild.d.ts +1 -1
  71. package/mcp-server/dist/tools/discord-guild.d.ts.map +1 -1
  72. package/mcp-server/dist/tools/discord-guild.js +1 -1
  73. package/mcp-server/dist/tools/discord-guild.js.map +1 -1
  74. package/mcp-server/dist/tools/discord-member.d.ts +1 -1
  75. package/mcp-server/dist/tools/discord-member.d.ts.map +1 -1
  76. package/mcp-server/dist/tools/discord-member.js +1 -1
  77. package/mcp-server/dist/tools/discord-member.js.map +1 -1
  78. package/mcp-server/dist/tools/discord-message.d.ts +1 -1
  79. package/mcp-server/dist/tools/discord-message.d.ts.map +1 -1
  80. package/mcp-server/dist/tools/discord-message.js +1 -1
  81. package/mcp-server/dist/tools/discord-message.js.map +1 -1
  82. package/mcp-server/dist/tools/discord-onboarding.d.ts +1 -1
  83. package/mcp-server/dist/tools/discord-onboarding.d.ts.map +1 -1
  84. package/mcp-server/dist/tools/discord-onboarding.js +1 -1
  85. package/mcp-server/dist/tools/discord-onboarding.js.map +1 -1
  86. package/mcp-server/dist/tools/discord-role.d.ts +1 -1
  87. package/mcp-server/dist/tools/discord-role.d.ts.map +1 -1
  88. package/mcp-server/dist/tools/discord-role.js +1 -1
  89. package/mcp-server/dist/tools/discord-role.js.map +1 -1
  90. package/mcp-server/dist/tools/discord-thread.d.ts +1 -1
  91. package/mcp-server/dist/tools/discord-thread.d.ts.map +1 -1
  92. package/mcp-server/dist/tools/discord-thread.js +1 -1
  93. package/mcp-server/dist/tools/discord-thread.js.map +1 -1
  94. package/mcp-server/dist/tools/discord-webhook.d.ts +1 -1
  95. package/mcp-server/dist/tools/discord-webhook.d.ts.map +1 -1
  96. package/mcp-server/dist/tools/discord-webhook.js +1 -1
  97. package/mcp-server/dist/tools/discord-webhook.js.map +1 -1
  98. package/mcp-server/dist/tools/fb-groups.d.ts +1 -1
  99. package/mcp-server/dist/tools/fb-groups.d.ts.map +1 -1
  100. package/mcp-server/dist/tools/fb-groups.js +1 -1
  101. package/mcp-server/dist/tools/fb-groups.js.map +1 -1
  102. package/mcp-server/dist/tools/forget.d.ts +1 -1
  103. package/mcp-server/dist/tools/forget.d.ts.map +1 -1
  104. package/mcp-server/dist/tools/forget.js +1 -1
  105. package/mcp-server/dist/tools/forget.js.map +1 -1
  106. package/mcp-server/dist/tools/git-tools.d.ts +1 -1
  107. package/mcp-server/dist/tools/git-tools.d.ts.map +1 -1
  108. package/mcp-server/dist/tools/git-tools.js +1 -1
  109. package/mcp-server/dist/tools/git-tools.js.map +1 -1
  110. package/mcp-server/dist/tools/gsc/experience.d.ts +1 -1
  111. package/mcp-server/dist/tools/gsc/experience.d.ts.map +1 -1
  112. package/mcp-server/dist/tools/gsc/experience.js +1 -1
  113. package/mcp-server/dist/tools/gsc/experience.js.map +1 -1
  114. package/mcp-server/dist/tools/gsc/indexing.d.ts +1 -1
  115. package/mcp-server/dist/tools/gsc/indexing.d.ts.map +1 -1
  116. package/mcp-server/dist/tools/gsc/indexing.js +1 -1
  117. package/mcp-server/dist/tools/gsc/indexing.js.map +1 -1
  118. package/mcp-server/dist/tools/gsc/inspect.d.ts +1 -1
  119. package/mcp-server/dist/tools/gsc/inspect.d.ts.map +1 -1
  120. package/mcp-server/dist/tools/gsc/inspect.js +1 -1
  121. package/mcp-server/dist/tools/gsc/inspect.js.map +1 -1
  122. package/mcp-server/dist/tools/gsc/navigate.d.ts +1 -1
  123. package/mcp-server/dist/tools/gsc/navigate.d.ts.map +1 -1
  124. package/mcp-server/dist/tools/gsc/navigate.js +1 -1
  125. package/mcp-server/dist/tools/gsc/navigate.js.map +1 -1
  126. package/mcp-server/dist/tools/gsc/performance.d.ts +1 -1
  127. package/mcp-server/dist/tools/gsc/performance.d.ts.map +1 -1
  128. package/mcp-server/dist/tools/gsc/performance.js +1 -1
  129. package/mcp-server/dist/tools/gsc/performance.js.map +1 -1
  130. package/mcp-server/dist/tools/gsc/settings.d.ts +1 -1
  131. package/mcp-server/dist/tools/gsc/settings.d.ts.map +1 -1
  132. package/mcp-server/dist/tools/gsc/settings.js +1 -1
  133. package/mcp-server/dist/tools/gsc/settings.js.map +1 -1
  134. package/mcp-server/dist/tools/image-tools.d.ts +1 -1
  135. package/mcp-server/dist/tools/image-tools.d.ts.map +1 -1
  136. package/mcp-server/dist/tools/image-tools.js +1 -1
  137. package/mcp-server/dist/tools/image-tools.js.map +1 -1
  138. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +1 -1
  139. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +1 -1
  140. package/mcp-server/dist/tools/leonardo-browser-tools.js +1 -1
  141. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +1 -1
  142. package/mcp-server/dist/tools/loop.d.ts +1 -1
  143. package/mcp-server/dist/tools/loop.d.ts.map +1 -1
  144. package/mcp-server/dist/tools/loop.js +1 -1
  145. package/mcp-server/dist/tools/loop.js.map +1 -1
  146. package/mcp-server/dist/tools/memories.d.ts +1 -1
  147. package/mcp-server/dist/tools/memories.d.ts.map +1 -1
  148. package/mcp-server/dist/tools/memories.js +1 -1
  149. package/mcp-server/dist/tools/memories.js.map +1 -1
  150. package/mcp-server/dist/tools/morelogin.d.ts +1 -1
  151. package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
  152. package/mcp-server/dist/tools/morelogin.js +1 -1
  153. package/mcp-server/dist/tools/morelogin.js.map +1 -1
  154. package/mcp-server/dist/tools/profile.d.ts +1 -1
  155. package/mcp-server/dist/tools/profile.d.ts.map +1 -1
  156. package/mcp-server/dist/tools/profile.js +1 -1
  157. package/mcp-server/dist/tools/profile.js.map +1 -1
  158. package/mcp-server/dist/tools/recall.d.ts +1 -1
  159. package/mcp-server/dist/tools/recall.d.ts.map +1 -1
  160. package/mcp-server/dist/tools/recall.js +1 -1
  161. package/mcp-server/dist/tools/recall.js.map +1 -1
  162. package/mcp-server/dist/tools/reflect.d.ts +1 -1
  163. package/mcp-server/dist/tools/reflect.d.ts.map +1 -1
  164. package/mcp-server/dist/tools/reflect.js +1 -1
  165. package/mcp-server/dist/tools/reflect.js.map +1 -1
  166. package/mcp-server/dist/tools/remember.d.ts +1 -1
  167. package/mcp-server/dist/tools/remember.d.ts.map +1 -1
  168. package/mcp-server/dist/tools/remember.js +13 -11
  169. package/mcp-server/dist/tools/remember.js.map +1 -1
  170. package/mcp-server/dist/tools/restore.d.ts +1 -1
  171. package/mcp-server/dist/tools/restore.d.ts.map +1 -1
  172. package/mcp-server/dist/tools/restore.js +1 -1
  173. package/mcp-server/dist/tools/restore.js.map +1 -1
  174. package/mcp-server/dist/tools/style-guide-tools.d.ts +1 -1
  175. package/mcp-server/dist/tools/style-guide-tools.d.ts.map +1 -1
  176. package/mcp-server/dist/tools/style-guide-tools.js +1 -1
  177. package/mcp-server/dist/tools/style-guide-tools.js.map +1 -1
  178. package/mcp-server/dist/tools/sync.d.ts +1 -1
  179. package/mcp-server/dist/tools/sync.d.ts.map +1 -1
  180. package/mcp-server/dist/tools/sync.js +1 -1
  181. package/mcp-server/dist/tools/sync.js.map +1 -1
  182. package/mcp-server/dist/tools/tictactoe-tools.d.ts +1 -1
  183. package/mcp-server/dist/tools/tictactoe-tools.d.ts.map +1 -1
  184. package/mcp-server/dist/tools/tictactoe-tools.js +1 -1
  185. package/mcp-server/dist/tools/tictactoe-tools.js.map +1 -1
  186. package/mcp-server/dist/tools/utils.d.ts +1 -1
  187. package/mcp-server/dist/tools/utils.d.ts.map +1 -1
  188. package/mcp-server/dist/tools/utils.js +1 -1
  189. package/mcp-server/dist/tools/utils.js.map +1 -1
  190. package/mcp-server/dist/tools/whatsapp-send-file.d.ts +1 -1
  191. package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -1
  192. package/mcp-server/dist/tools/whatsapp-send-file.js +1 -1
  193. package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -1
  194. package/mcp-server/dist/tools/whiteboard-tools.d.ts +3 -3
  195. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
  196. package/mcp-server/dist/tools/whiteboard-tools.js +1 -1
  197. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
  198. package/mcp-server/dist/tools/youtube/analytics.d.ts +1 -1
  199. package/mcp-server/dist/tools/youtube/analytics.d.ts.map +1 -1
  200. package/mcp-server/dist/tools/youtube/analytics.js +1 -1
  201. package/mcp-server/dist/tools/youtube/analytics.js.map +1 -1
  202. package/mcp-server/dist/tools/youtube/download.d.ts +1 -1
  203. package/mcp-server/dist/tools/youtube/download.d.ts.map +1 -1
  204. package/mcp-server/dist/tools/youtube/download.js +1 -1
  205. package/mcp-server/dist/tools/youtube/download.js.map +1 -1
  206. package/mcp-server/dist/tools/youtube/media.d.ts +1 -1
  207. package/mcp-server/dist/tools/youtube/media.d.ts.map +1 -1
  208. package/mcp-server/dist/tools/youtube/media.js +1 -1
  209. package/mcp-server/dist/tools/youtube/media.js.map +1 -1
  210. package/mcp-server/dist/tools/youtube/metadata.d.ts +1 -1
  211. package/mcp-server/dist/tools/youtube/metadata.d.ts.map +1 -1
  212. package/mcp-server/dist/tools/youtube/metadata.js +1 -1
  213. package/mcp-server/dist/tools/youtube/metadata.js.map +1 -1
  214. package/mcp-server/dist/tools/youtube/navigate.d.ts +1 -1
  215. package/mcp-server/dist/tools/youtube/navigate.d.ts.map +1 -1
  216. package/mcp-server/dist/tools/youtube/navigate.js +1 -1
  217. package/mcp-server/dist/tools/youtube/navigate.js.map +1 -1
  218. package/mcp-server/dist/tools/youtube/playlist.d.ts +1 -1
  219. package/mcp-server/dist/tools/youtube/playlist.d.ts.map +1 -1
  220. package/mcp-server/dist/tools/youtube/playlist.js +1 -1
  221. package/mcp-server/dist/tools/youtube/playlist.js.map +1 -1
  222. package/mcp-server/dist/tools/youtube/schedule.d.ts +1 -1
  223. package/mcp-server/dist/tools/youtube/schedule.d.ts.map +1 -1
  224. package/mcp-server/dist/tools/youtube/schedule.js +1 -1
  225. package/mcp-server/dist/tools/youtube/schedule.js.map +1 -1
  226. package/mcp-server/dist/tools/youtube/source.d.ts +1 -1
  227. package/mcp-server/dist/tools/youtube/source.d.ts.map +1 -1
  228. package/mcp-server/dist/tools/youtube/source.js +1 -1
  229. package/mcp-server/dist/tools/youtube/source.js.map +1 -1
  230. package/mcp-server/dist/tools/youtube/upload.d.ts +1 -1
  231. package/mcp-server/dist/tools/youtube/upload.d.ts.map +1 -1
  232. package/mcp-server/dist/tools/youtube/upload.js +4 -9
  233. package/mcp-server/dist/tools/youtube/upload.js.map +1 -1
  234. package/mcp-server/dist/tui.js +16 -4
  235. package/mcp-server/dist/tui.js.map +1 -1
  236. package/mcp-server/package-lock.json +3 -3
  237. package/mcp-server/package.json +3 -3
  238. package/mcp-server/src/services/browser-bench.ts +1 -1
  239. package/mcp-server/src/services/categories.ts +77 -32
  240. package/mcp-server/src/services/desktop-bench.ts +1 -1
  241. package/mcp-server/src/tools/agents.ts +1 -1
  242. package/mcp-server/src/tools/bluesky.ts +1 -1
  243. package/mcp-server/src/tools/browser-advanced.ts +1 -1
  244. package/mcp-server/src/tools/browser-batch.ts +1 -1
  245. package/mcp-server/src/tools/browser-cheatsheet.ts +1 -1
  246. package/mcp-server/src/tools/browser-interact.ts +1 -1
  247. package/mcp-server/src/tools/browser-navigate.ts +1 -1
  248. package/mcp-server/src/tools/browser-observe.ts +1 -1
  249. package/mcp-server/src/tools/card-tools.ts +1 -1
  250. package/mcp-server/src/tools/category.ts +6 -1
  251. package/mcp-server/src/tools/choice.ts +1 -1
  252. package/mcp-server/src/tools/computer.ts +1 -1
  253. package/mcp-server/src/tools/discord-channel.ts +1 -1
  254. package/mcp-server/src/tools/discord-guild.ts +1 -1
  255. package/mcp-server/src/tools/discord-member.ts +1 -1
  256. package/mcp-server/src/tools/discord-message.ts +1 -1
  257. package/mcp-server/src/tools/discord-onboarding.ts +1 -1
  258. package/mcp-server/src/tools/discord-role.ts +1 -1
  259. package/mcp-server/src/tools/discord-thread.ts +1 -1
  260. package/mcp-server/src/tools/discord-webhook.ts +1 -1
  261. package/mcp-server/src/tools/fb-groups.ts +1 -1
  262. package/mcp-server/src/tools/forget.ts +1 -1
  263. package/mcp-server/src/tools/git-tools.ts +1 -1
  264. package/mcp-server/src/tools/gsc/experience.ts +1 -1
  265. package/mcp-server/src/tools/gsc/indexing.ts +1 -1
  266. package/mcp-server/src/tools/gsc/inspect.ts +1 -1
  267. package/mcp-server/src/tools/gsc/navigate.ts +1 -1
  268. package/mcp-server/src/tools/gsc/performance.ts +1 -1
  269. package/mcp-server/src/tools/gsc/settings.ts +1 -1
  270. package/mcp-server/src/tools/image-tools.ts +1 -1
  271. package/mcp-server/src/tools/leonardo-browser-tools.ts +1 -1
  272. package/mcp-server/src/tools/loop.ts +1 -1
  273. package/mcp-server/src/tools/memories.ts +1 -1
  274. package/mcp-server/src/tools/morelogin.ts +1 -1
  275. package/mcp-server/src/tools/profile.ts +1 -1
  276. package/mcp-server/src/tools/recall.ts +1 -1
  277. package/mcp-server/src/tools/reflect.ts +1 -1
  278. package/mcp-server/src/tools/remember.ts +13 -12
  279. package/mcp-server/src/tools/restore.ts +1 -1
  280. package/mcp-server/src/tools/style-guide-tools.ts +1 -1
  281. package/mcp-server/src/tools/sync.ts +1 -1
  282. package/mcp-server/src/tools/tictactoe-tools.ts +1 -1
  283. package/mcp-server/src/tools/utils.ts +1 -1
  284. package/mcp-server/src/tools/whatsapp-send-file.ts +1 -1
  285. package/mcp-server/src/tools/whiteboard-tools.ts +1 -1
  286. package/mcp-server/src/tools/youtube/analytics.ts +1 -1
  287. package/mcp-server/src/tools/youtube/download.ts +1 -1
  288. package/mcp-server/src/tools/youtube/media.ts +1 -1
  289. package/mcp-server/src/tools/youtube/metadata.ts +1 -1
  290. package/mcp-server/src/tools/youtube/navigate.ts +1 -1
  291. package/mcp-server/src/tools/youtube/playlist.ts +1 -1
  292. package/mcp-server/src/tools/youtube/schedule.ts +1 -1
  293. package/mcp-server/src/tools/youtube/source.ts +1 -1
  294. package/mcp-server/src/tools/youtube/upload.ts +4 -12
  295. package/mcp-server/src/tui.ts +22 -9
  296. package/neural-interface/i18n/en.json +18 -0
  297. package/neural-interface/i18n/pt-BR.json +18 -0
  298. package/neural-interface/lib/agent-sandbox-elevation.js +102 -0
  299. package/neural-interface/lib/agent-sandbox-setting.js +57 -0
  300. package/neural-interface/lib/assistant-api.js +19 -0
  301. package/neural-interface/lib/assistant-route-gate-codex.js +3 -1
  302. package/neural-interface/lib/assistant-runtime.js +5 -1
  303. package/neural-interface/lib/codex-account-auth.js +4 -2
  304. package/neural-interface/lib/codex-accounts.js +14 -1
  305. package/neural-interface/lib/codex-settings-schema.js +5 -3
  306. package/neural-interface/lib/exec-loop-command.js +3 -1
  307. package/neural-interface/lib/install-details.js +23 -5
  308. package/neural-interface/lib/native-loop-providers.js +6 -1
  309. package/neural-interface/lib/provider-panel-terminal-auth.js +3 -2
  310. package/neural-interface/lib/session-indexer.js +3 -1
  311. package/neural-interface/lib/session-title-generator.js +2 -0
  312. package/neural-interface/package-lock.json +2 -2
  313. package/neural-interface/package.json +1 -1
  314. package/neural-interface/public/shared/account-menu.js +4 -1
  315. package/neural-interface/public/shared/account-usage.js +62 -2
  316. package/neural-interface/public/shared/account-view.js +2 -1
  317. package/neural-interface/public/shared/assistant/asst-account-menu.js +298 -0
  318. package/neural-interface/public/shared/assistant/asst-api.js +7 -0
  319. package/neural-interface/public/shared/assistant/asst-brain-picker.js +232 -75
  320. package/neural-interface/public/shared/assistant/asst-menu.js +204 -77
  321. package/neural-interface/public/shared/assistant/asst-model-chip.js +135 -0
  322. package/neural-interface/public/shared/assistant/asst-panel.js +39 -2
  323. package/neural-interface/public/shared/assistant/asst-styles.js +66 -17
  324. package/neural-interface/public/shared/cdx/cdx-panel.js +1 -1
  325. package/neural-interface/public/shared/cdx/cdx-styles.js +45 -5
  326. package/neural-interface/public/shared/cdx/cdx-tabs.js +3 -0
  327. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -1
  328. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +4 -1
  329. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +51 -2
  330. package/neural-interface/public/shared/showcase-privacy.js +2 -1
  331. package/neural-interface/public/shared/styles.css +3 -1
  332. package/neural-interface/public/shared/ui-claude-panel.js +50 -4
  333. package/neural-interface/public/shared/ui-explorer.js +2 -0
  334. package/neural-interface/public/shared/ui-sync.js +2 -0
  335. package/neural-interface/public/shared/ui-update.js +4 -2
  336. package/neural-interface/scripts/rebuild-pty.js +14 -2
  337. package/neural-interface/server.js +180 -86
  338. package/npm-shrinkwrap.json +4424 -0
  339. package/package.json +54 -2
  340. package/postinstall.js +61 -47
  341. package/preuninstall.js +8 -35
  342. package/setup.js +76 -44
  343. package/skills/synabun/modules/idea.md +1 -1
  344. package/skills/synabun/modules/schedule.md +1 -1
  345. package/updater.cmd +2 -2
  346. package/updater.mjs +97 -3
@@ -30,10 +30,12 @@ import { getHiddenModels, HIDDEN_MODELS_EVENT, hiddenModelMatch } from '../ocp-h
30
30
  import { getProviderMeta } from '../provider-icons.js';
31
31
  import { on } from '../state.js';
32
32
  import { fetchAssistantCatalog, fetchProjects, patchClaudeAccount, patchCodexAccount, setClaudeWorkerAccount, setCodexWorkerAccount } from './asst-api.js';
33
- import { accountBadge, accountCompactMode, accountIdentity, accountName, accountUsable, modeChoices } from '../account-view.js';
34
- import { accountUsageText, autoAccountLabel } from '../account-usage.js';
33
+ import { accountBadge, accountIdentity, accountName, accountText } from '../account-view.js';
34
+ import { autoAccountLabel } from '../account-usage.js';
35
+ import { accountChoiceItems, accountManageItem, accountMenuRow, accountModeItem, openAccountMenu } from './asst-account-menu.js';
35
36
  import { closeMenu, currentMenu, isMenuOpen, openMenu } from './asst-menu.js';
36
37
  import { createMascotControl } from './asst-mascot.js';
38
+ import { modelChipForms, modelChipName, modelChipTag } from './asst-model-chip.js';
37
39
  import {
38
40
  ACCOUNT_PROVIDERS,
39
41
  brainRequiresSwitch,
@@ -79,6 +81,10 @@ const TEST_DENSITY_COPY = {
79
81
  thorough: ['Thorough', 'Full coverage, whole suite, live checks.'],
80
82
  };
81
83
 
84
+ // English copy of the Admin sandbox row (i18n: assistant.adminSandbox.hint). Names only Codex: the
85
+ // one provider it changes (lib/agent-sandbox-elevation.js; Claude Code and OpenCode are no-ops).
86
+ export const ADMIN_SANDBOX_HINT = 'Only changes anything when SynaBun runs on Windows. On: Codex can set up its strongest sandbox, which asks for administrator approval once per account. Off: no admin prompt; Codex uses a sandbox that needs no admin, with weaker isolation.';
87
+
82
88
  /** "Light" — the level's name (a missing or unknown level is the default one). */
83
89
  export function testDensityLabel(level, t) {
84
90
  const id = normalizeTestDensity(level);
@@ -416,7 +422,8 @@ function tierTag(m, tr) {
416
422
  * opts: { brain, onSelect(target), includeHere, hereLabel, title, t, placement,
417
423
  * current:{provider, model}|null, warnSwitch, providers, onToast(text),
418
424
  * accept(provider, model) → bool (only those models), emptyLabel (when none
419
- * passes), allowCustom (default true), zIndex (see asst-menu.js) }
425
+ * passes), allowCustom (default true), zIndex (see asst-menu.js),
426
+ * accountItems (menu items listed first, under accountTitle) }
420
427
  * target: { provider, model, label, tier, vision, outputs, efforts, price } | { here: true }
421
428
  */
422
429
  export async function openModelMenu(anchor, opts = {}) {
@@ -444,7 +451,7 @@ export async function openModelMenu(anchor, opts = {}) {
444
451
  const metadata = modelCatalogStatus();
445
452
  const items = [];
446
453
  if (Array.isArray(opts.accountItems) && opts.accountItems.length) {
447
- items.push({ kind: 'header', label: tr('assistant.brain.account', 'Account profile') }, ...opts.accountItems, { kind: 'separator' });
454
+ items.push({ kind: 'header', label: opts.accountTitle || tr('assistant.brain.account', 'Account profile') }, ...opts.accountItems, { kind: 'separator' });
448
455
  }
449
456
  if (includeHere) {
450
457
  items.push({ kind: 'radio', id: '__here', label: hereLabel || tr('assistant.routes.brainDecides', 'Let the brain decide'), selected: !current, onSelect: () => onSelect?.({ here: true }) });
@@ -642,11 +649,39 @@ export function createBrainPicker(host, hooks = {}) {
642
649
  return name;
643
650
  }
644
651
 
652
+ // The "⋯" item's value, in the account menu's words: the name and its plan, "API key" or "Signed out" ("Default · Max").
653
+ function accountValue(brain) {
654
+ const acc = accountOf(brain);
655
+ if (!acc) return brain.accountId === 'auto' ? autoAccountLabel(hooks.t) : (brain.accountId || t('assistant.brain.accountDefault', 'Default'));
656
+ const row = accountMenuRow(acc, { provider: accountProvider(brain), t: hooks.t });
657
+ return [row.name, row.badge.text].filter(Boolean).join(' · ');
658
+ }
659
+
660
+ // The model menu's own row first: it also names the "[1m]" / "[extended]" variants the provider list lacks.
661
+ function modelRow(brain) {
662
+ return catalogModelRow(brain.provider, brain.model) || modelOf(brain);
663
+ }
664
+
645
665
  function modelLabel(brain = currentBrain()) {
646
- const model = modelOf(brain);
666
+ const model = modelRow(brain);
647
667
  return model ? (model.label || modelShortName(model.id)) : (brain.model ? modelShortName(brain.model) : t('assistant.brain.modelDefault', 'Default model'));
648
668
  }
649
669
 
670
+ // The composer chip's account cue: the account menu's initial avatar when the provider has more than one
671
+ // account (or the account is signed out), "Auto" for Auto; null otherwise. Its full words are the tooltip's.
672
+ function accountCue(brain) {
673
+ if (!ACCOUNT_PROVIDERS.has(brain.provider)) return null;
674
+ if (brain.accountId === 'auto') return { text: t('assistant.brain.accountAutoShort', 'Auto'), tone: 'muted' };
675
+ const accounts = brain.provider === 'codex' ? getCachedCodexAccounts() : getCachedClaudeAccounts();
676
+ const acc = accountOf(brain);
677
+ const signedOut = acc?.signedIn === false;
678
+ if (accounts.length < 2 && !signedOut) return null;
679
+ const initial = acc
680
+ ? accountMenuRow(acc, { provider: accountProvider(brain), t: hooks.t }).initial
681
+ : Array.from(String(brain.accountId || t('assistant.brain.accountDefault', 'Default')).trim())[0]?.toLocaleUpperCase();
682
+ return { text: initial || '?', tone: signedOut ? 'warn' : 'muted' };
683
+ }
684
+
650
685
  function values(brain = currentBrain()) {
651
686
  const meta = getProviderMeta(brain.provider);
652
687
  const efforts = effortsFor(brain);
@@ -669,6 +704,26 @@ export function createBrainPicker(host, hooks = {}) {
669
704
  };
670
705
  }
671
706
 
707
+ /**
708
+ * The permission menu's Admin sandbox row (a check: the global setting, every agent SynaBun starts),
709
+ * or null while the host has none to show (a server that does not serve it). Its tooltip and
710
+ * aria-label say the value in effect.
711
+ */
712
+ function adminSandboxItem() {
713
+ const on = typeof hooks.getAdminSandbox === 'function' ? hooks.getAdminSandbox() : null;
714
+ if (typeof on !== 'boolean') return null;
715
+ const label = t('assistant.adminSandbox.label', 'Admin sandbox');
716
+ const value = on ? t('assistant.adminSandbox.on', 'On') : t('assistant.adminSandbox.off', 'Off');
717
+ const hint = t('assistant.adminSandbox.hint', ADMIN_SANDBOX_HINT);
718
+ return {
719
+ kind: 'check', id: 'admin-sandbox', label, desc: hint, checked: on,
720
+ className: 'asst-admin-sandbox-item',
721
+ title: `${label}: ${value}. ${hint}`,
722
+ ariaLabel: t('assistant.adminSandbox.aria', '{label}: {value}. Only changes anything when SynaBun runs on Windows.', { label, value }),
723
+ onSelect: () => hooks.onAdminSandbox?.(!on),
724
+ };
725
+ }
726
+
672
727
  /** The session's Test density, or null while the host has none to show (a server that does not know it). */
673
728
  function testDensity() {
674
729
  const level = typeof hooks.getTestDensity === 'function' ? hooks.getTestDensity() : null;
@@ -689,21 +744,117 @@ export function createBrainPicker(host, hooks = {}) {
689
744
  btn.setAttribute('aria-label', aria || tooltip || label || field);
690
745
  }
691
746
 
747
+ // The composer's model chip (asst-model-chip.js): the model's short name, a context tag for a variant,
748
+ // the account cue and "API" on an API key, never the credential's name (the provider icon says it).
749
+ // Model label, id, account, who it is and what it runs on are the tooltip and aria-label.
750
+ // fitModelChip() draws the longest form that fits the row.
751
+ let chipForms = [];
752
+ let chipParts = null;
753
+ let chipCatalog = null;
754
+ function renderModelChip(brain, v) {
755
+ const btn = buttons.model;
756
+ // The catalog was dropped (a model hidden, a provider's list changed): read it again, then the label.
757
+ if (!cachedModelCatalog() && !chipCatalog) {
758
+ chipCatalog = loadModelCatalog().catch(() => null).then(() => {
759
+ chipCatalog = null;
760
+ if (!destroyed && cachedModelCatalog()) render();
761
+ });
762
+ }
763
+ const row = modelRow(brain);
764
+ const id = brain.model || row?.id || '';
765
+ const { name, raw } = id ? modelChipName(row?.label, id) : { name: v.model, raw: false };
766
+ const tag = modelChipTag(id, { contextWindow: row?.contextWindow, label: row?.label });
767
+ const cue = accountCue(brain);
768
+ const p = accountProvider(brain);
769
+ const acc = ACCOUNT_PROVIDERS.has(brain.provider) ? accountOf(brain) : null;
770
+ const api = acc && acc.signedIn !== false && acc.mode === 'apiKey' ? accountText('apiShort', {}, p) : '';
771
+ const names = (cachedModelCatalog()?.[brain.provider] || []).map(m => modelChipName(m.label, m.id).name);
772
+ chipForms = modelChipForms({ name, names, tag: !!tag, cue: !!cue, api: !!api });
773
+
774
+ const who = !ACCOUNT_PROVIDERS.has(brain.provider) ? ''
775
+ : brain.accountId === 'auto' ? autoAccountLabel(hooks.t)
776
+ : acc ? accountName(acc, p) : (brain.accountId || t('assistant.brain.accountDefault', 'Default'));
777
+ const model = brain.model && v.model !== brain.model ? `${v.model} · ${brain.model}` : (brain.model || v.model);
778
+ const tip = [
779
+ v.meta.label, model, who ? t('assistant.brain.asAccount', 'as {account}', { account: who }) : '',
780
+ acc ? accountIdentity(acc) : '', acc ? accountBadge(acc, p).text : '',
781
+ ].filter(Boolean).join(' · ');
782
+ setBtn('model', { label: '', icon: v.meta.icon, color: v.meta.color, tooltip: tip, aria: `${t('assistant.brain.model', 'Model')}: ${tip}` });
783
+
784
+ const label = btn.querySelector('.asst-dd-label');
785
+ label.classList.add('asst-model-label');
786
+ chipParts = {
787
+ name: el('span', 'asst-model-name', name),
788
+ tag: el('span', 'asst-model-tag', tag),
789
+ cue: el('span', 'asst-acct-avatar asst-model-cue', cue?.text || ''),
790
+ api: el('span', 'asst-acct-badge asst-model-api', api),
791
+ };
792
+ if (cue) chipParts.cue.dataset.tone = cue.tone;
793
+ label.replaceChildren(chipParts.name, chipParts.tag, chipParts.cue, chipParts.api);
794
+ // A display name ("Sonnet 5") reads in sans; a raw id nothing names ("opus") stays mono.
795
+ btn.toggleAttribute('data-raw', raw);
796
+ fitModelChip();
797
+ }
798
+
799
+ function drawChipForm(form) {
800
+ chipParts.name.textContent = form.name;
801
+ chipParts.tag.hidden = !form.tag;
802
+ chipParts.cue.hidden = !form.cue;
803
+ chipParts.api.hidden = !form.api;
804
+ }
805
+
806
+ // The first form, longest first, that leaves the row as the shortest one does: no part cut, no other
807
+ // field narrower, the row no taller (it wraps below 320px). None does: the shortest, its name squeezed.
808
+ function fitModelChip() {
809
+ const btn = buttons.model;
810
+ if (destroyed || !btn || !chipParts || !chipForms.length) return;
811
+ btn.removeAttribute('data-squeezed');
812
+ const label = btn.querySelector('.asst-dd-label');
813
+ const slot = btn.parentElement;
814
+ // Nothing to measure (not on screen, no layout): the whole chip, until the row has a width.
815
+ if (chipForms.length === 1 || !isRendered(btn) || !slot || typeof label?.getBoundingClientRect !== 'function') { drawChipForm(chipForms[0]); return; }
816
+ const row = slot.parentElement || null;
817
+ const others = [...slot.querySelectorAll('.asst-dd-label')].filter(n => n !== label && n.getClientRects().length > 0);
818
+ const width = n => n.getBoundingClientRect().width;
819
+ const cut = () => {
820
+ const right = label.getBoundingClientRect().right + 0.5;
821
+ return Object.values(chipParts).some(n => !n.hidden && n.getBoundingClientRect().right > right);
822
+ };
823
+ const shortest = chipForms[chipForms.length - 1];
824
+ drawChipForm(shortest);
825
+ const base = { height: row?.offsetHeight || 0, widths: others.map(width) };
826
+ const fits = () => !cut()
827
+ && (row?.offsetHeight || 0) <= base.height + 0.5
828
+ && others.every((n, i) => width(n) >= base.widths[i] - 0.5)
829
+ && !(slot.scrollWidth > slot.clientWidth + 1);
830
+ for (const form of chipForms.slice(0, -1)) {
831
+ drawChipForm(form);
832
+ if (fits()) return;
833
+ }
834
+ drawChipForm(shortest);
835
+ btn.toggleAttribute('data-squeezed', cut());
836
+ }
837
+
838
+ // The row's width decides the form (a panel resized, a tab shown, the fields that collapse to glyphs).
839
+ const fitHost = buttons.model.parentElement?.parentElement || buttons.model.parentElement;
840
+ let fitFrame = 0;
841
+ let fitWidth = -1;
842
+ const scheduleFit = () => {
843
+ if (fitFrame || destroyed || typeof requestAnimationFrame !== 'function') return;
844
+ fitFrame = requestAnimationFrame(() => { fitFrame = 0; fitModelChip(); });
845
+ };
846
+ const fitObserver = typeof ResizeObserver === 'function' && fitHost ? new ResizeObserver((entries) => {
847
+ const w = Math.round(entries[entries.length - 1]?.contentRect?.width || 0);
848
+ if (w !== fitWidth) { fitWidth = w; scheduleFit(); }
849
+ }) : null;
850
+ fitObserver?.observe(fitHost);
851
+ if (typeof document !== 'undefined') document.fonts?.ready?.then(scheduleFit).catch(() => {});
852
+
692
853
  function render() {
693
854
  if (destroyed) return;
694
855
  const brain = currentBrain();
695
856
  const v = values(brain);
696
- const modelTip = [v.meta.label, brain.model || t('assistant.brain.modelDefault', 'Default model'), v.account ? t('assistant.brain.asAccount', 'as {account}', { account: v.account }) : ''].filter(Boolean).join(' · ');
697
- setBtn('model', { label: v.model, icon: v.meta.icon, color: v.meta.color, tooltip: modelTip, aria: `${t('assistant.brain.model', 'Model')}: ${modelTip}` });
698
- if (ACCOUNT_PROVIDERS.has(brain.provider)) {
699
- const acc = accountOf(brain);
700
- const label = buttons.model.querySelector('.asst-dd-label');
701
- label.classList.add('asst-model-account-label');
702
- const mode = acc ? accountCompactMode(acc, accountProvider(brain)) : t('accounts.checking', 'Checking…');
703
- label.replaceChildren(el('span', 'asst-model-name', v.model), el('span', '', '·'), el('span', 'asst-model-account', acc ? accountName(acc, accountProvider(brain)) : brain.accountId || t('assistant.brain.accountDefault', 'Default')), el('span', '', '·'), el('span', 'asst-model-mode', mode));
704
- } else buttons.model.querySelector('.asst-dd-label').classList.remove('asst-model-account-label');
705
- // A display name ("Sonnet 5") reads in sans; a raw id nothing names ("opus[1m]") stays mono.
706
- buttons.model?.toggleAttribute('data-raw', /^[a-z0-9][\w.\-[\]:/]*$/.test(String(v.model || '')));
857
+ renderModelChip(brain, v);
707
858
 
708
859
  if (brain.provider === 'opencode') {
709
860
  setBtn('effort', { label: v.effort, icon: ICON_AGENT, tooltip: `${t('assistant.brain.agent', 'OpenCode agent')}: ${v.effort}` });
@@ -791,7 +942,7 @@ export function createBrainPicker(host, hooks = {}) {
791
942
  { field: 'mode', label: t('assistant.brain.mode', 'Permission mode'), value: v.mode, icon: ICON_SHIELD, hidden: false },
792
943
  { field: 'tests', label: t('assistant.tests.title', 'Test density'), value: testDensity() ? testDensityLabel(testDensity(), hooks.t) : '', icon: testsIcon(testDensityBars(testDensity())), hidden: !testDensity() },
793
944
  { field: 'mcp', label: t('assistant.brain.mcp', 'MCP tool profile'), value: v.mcp, icon: ICON_PLUG, hidden: false },
794
- { field: 'account', label: t('assistant.brain.account', 'Account profile'), value: v.account, icon: ICON_ACCOUNT, hidden: !ACCOUNT_PROVIDERS.has(brain.provider) },
945
+ { field: 'account', label: t('assistant.brain.accountShort', 'Account'), value: accountValue(brain), icon: ICON_ACCOUNT, hidden: !ACCOUNT_PROVIDERS.has(brain.provider) },
795
946
  ];
796
947
  }
797
948
 
@@ -855,7 +1006,8 @@ export function createBrainPicker(host, hooks = {}) {
855
1006
  placement,
856
1007
  current: { provider: brain.provider, model: brain.model },
857
1008
  warnSwitch: true,
858
- accountItems: ACCOUNT_PROVIDERS.has(brain.provider) ? accountItems(brain, { composer: true }) : [],
1009
+ accountItems: ACCOUNT_PROVIDERS.has(brain.provider) ? accountItems(brain) : [],
1010
+ accountTitle: accountText('menuTitle', {}, accountProvider(brain)),
859
1011
  onToast: hooks.onToast,
860
1012
  onManageModels: hooks.onManageModels,
861
1013
  onSelect: (target) => {
@@ -920,7 +1072,8 @@ export function createBrainPicker(host, hooks = {}) {
920
1072
 
921
1073
  if (field === 'account') {
922
1074
  if (!ACCOUNT_PROVIDERS.has(brain.provider)) return;
923
- menu(t('assistant.brain.account', 'Account profile'), accountItems(brain), { width: 300 });
1075
+ // Kept inside the panel that holds the anchor (the sidepanel, or the tab's own root).
1076
+ openAccountMenu(anchor, accountMenu(brain, anchor.closest?.('.assistant-panel') || anchor.closest?.('.asst-root') || null));
924
1077
  return;
925
1078
  }
926
1079
 
@@ -975,70 +1128,74 @@ export function createBrainPicker(host, hooks = {}) {
975
1128
  onSelect: () => commit({ permissionMode: m.id }, 'mode'),
976
1129
  });
977
1130
  }
1131
+ // The global Admin sandbox switch (every agent SynaBun starts, not this conversation), under its own header.
1132
+ const adminSandbox = adminSandboxItem();
1133
+ if (adminSandbox) items.push({ kind: 'separator' }, { kind: 'header', label: t('assistant.adminSandbox.group', 'Every agent') }, adminSandbox);
978
1134
  menu(t('assistant.brain.mode', 'Permission mode'), items);
979
1135
  }
980
1136
  }
981
1137
 
982
- // The brain's account menu (Claude or Codex): every account with what it runs on and who it is (a
983
- // signed-out one is listed, not offered), the mode of the brain's account when it holds both credentials,
984
- // and the account dispatched workers of that provider use when a dispatch names none.
985
- function accountItems(brain, { composer = false } = {}) {
1138
+ // The cog's account menu (asst-account-menu.js): every account once, the conversation's and the
1139
+ // workers' choice, kept inside `bounds`. What each choice does is the same as the composer's.
1140
+ function accountMenu(brain, bounds) {
1141
+ const codex = brain.provider === 'codex';
1142
+ const refetch = () => (codex ? fetchCodexAccounts(true) : fetchClaudeAccounts(true));
1143
+ return {
1144
+ provider: codex ? 'codex' : 'claude',
1145
+ t: hooks.t,
1146
+ placement,
1147
+ bounds,
1148
+ read: () => ({
1149
+ accounts: codex ? getCachedCodexAccounts() : getCachedClaudeAccounts(),
1150
+ currentId: currentBrain().accountId,
1151
+ workerId: codex ? getCodexWorkerAccountId() : getClaudeWorkerAccountId(),
1152
+ running: hooks.getRunning?.() === true,
1153
+ }),
1154
+ onAuto: () => commit({ accountId: 'auto' }, 'account'),
1155
+ onPick: (id) => commit({ accountId: id }, 'account'),
1156
+ onSetWorker: async (id) => { await (codex ? setCodexWorkerAccount : setClaudeWorkerAccount)(id); await refetch(); render(); },
1157
+ onSetMode: async (id, mode) => {
1158
+ const before = currentBrain();
1159
+ try {
1160
+ await (codex ? patchCodexAccount : patchClaudeAccount)(id, { mode }); await refetch();
1161
+ hooks.onChange?.(currentBrain(), { requiresSwitch: true, restartBrain: true, field: 'accountMode', previous: before }); render();
1162
+ }
1163
+ catch (err) { hooks.onToast?.(err?.message || String(err)); }
1164
+ },
1165
+ onAdd: () => hooks.onAddAccount?.(brain.provider),
1166
+ onManage: (id) => (id ? hooks.onManageAccounts?.(brain.provider, id) : hooks.onManageAccounts?.(brain.provider)),
1167
+ onError: (err) => hooks.onToast?.(err?.message || String(err)),
1168
+ };
1169
+ }
1170
+
1171
+ // The composer's Model menu lists the brain's accounts too (Claude or Codex), as the cog's account menu
1172
+ // does (asst-account-menu.js): Auto, every account once (a signed-out one opens its sign-in), the Uses
1173
+ // switch of an account holding both credentials, Add account… and Manage….
1174
+ function accountItems(brain) {
986
1175
  const codex = brain.provider === 'codex';
987
1176
  const p = codex ? 'codex' : 'claude';
988
1177
  const accounts = codex ? getCachedCodexAccounts() : getCachedClaudeAccounts();
989
1178
  const refetch = () => (codex ? fetchCodexAccounts(true) : fetchClaudeAccounts(true));
990
- const items = accounts.map((a) => {
991
- const badge = accountBadge(a, p);
992
- const usable = accountUsable(a);
993
- return {
994
- kind: 'radio', id: a.id, label: accountName(a, p),
995
- desc: [accountIdentity(a), accountUsageText(a, hooks.t), !usable ? t('assistant.accounts.signInFirst', 'Signed out: sign it in from Manage…') : composer && a.id !== brain.accountId ? t('assistant.brain.newConversation', 'starts a new conversation') : ''].filter(Boolean).join(' · '),
996
- tag: badge.text, disabled: !usable,
997
- selected: a.id === brain.accountId,
998
- onSelect: () => commit({ accountId: a.id }, 'account'),
999
- };
1179
+ const items = accountChoiceItems({
1180
+ t: hooks.t,
1181
+ rows: accounts.map((a) => accountMenuRow(a, { provider: p, t: hooks.t })),
1182
+ currentId: brain.accountId,
1183
+ onAuto: () => commit({ accountId: 'auto' }, 'account'),
1184
+ onPick: (id) => commit({ accountId: id }, 'account'),
1185
+ onSignIn: (id) => hooks.onManageAccounts?.(brain.provider, id),
1000
1186
  });
1001
- items.unshift({ kind: 'radio', id: 'auto', label: autoAccountLabel(hooks.t), desc: t('assistant.accountUsage.composerHint', 'Pick the subscription with most usage left and start a new conversation.'), onSelect: () => commit({ accountId: 'auto' }, 'account') });
1002
- if (composer) for (const a of accounts.filter(a => !accountUsable(a))) items.push({ id: `signin:${a.id}`, label: t('assistant.accounts.signInAccount', 'Sign in to {label}…', { label: accountName(a, p) }), onSelect: () => hooks.onManageAccounts?.(brain.provider, a.id) });
1003
- const current = accounts.find((a) => a.id === brain.accountId);
1004
- const modes = modeChoices(current, p);
1005
- if (modes.length) {
1006
- items.push({ kind: 'separator' });
1007
- items.push({ kind: 'header', label: t('assistant.accounts.usesHeader', '{label} uses (every session on it)', { label: accountName(current, p) }) });
1008
- for (const m of modes) {
1009
- items.push({
1010
- kind: 'radio', id: `mode:${m.mode}`, label: m.label, desc: [m.detail, !m.current ? t('assistant.brain.newConversation', 'starts a new conversation') : ''].filter(Boolean).join(' · '), selected: m.current, disabled: hooks.getRunning?.() === true,
1011
- onSelect: async () => {
1012
- if (m.current) return;
1013
- try {
1014
- await (codex ? patchCodexAccount : patchClaudeAccount)(current.id, { mode: m.mode }); await refetch();
1015
- hooks.onChange?.(currentBrain(), { requiresSwitch: true, restartBrain: true, field: 'accountMode', previous: brain }); render();
1016
- }
1017
- catch (err) { hooks.onToast?.(err?.message || String(err)); }
1018
- },
1019
- });
1020
- }
1021
- }
1022
- const worker = codex ? getCodexWorkerAccountId() : getClaudeWorkerAccountId();
1023
- if (!composer) {
1024
- items.push({ kind: 'separator' });
1025
- items.push({ kind: 'header', label: codex ? t('assistant.accounts.codexWorkersHeader', 'Codex workers use') : t('assistant.accounts.workersHeader', 'Claude Code workers use') });
1026
- for (const a of accounts) {
1027
- const usable = accountUsable(a);
1028
- items.push({
1029
- kind: 'radio', id: `worker:${a.id}`, label: accountName(a, p), tag: accountBadge(a, p).text,
1030
- disabled: !usable && a.id !== worker, selected: a.id === worker,
1031
- onSelect: async () => {
1032
- if (a.id === worker) return;
1033
- try { await (codex ? setCodexWorkerAccount : setClaudeWorkerAccount)(a.id); await refetch(); render(); }
1034
- catch (err) { hooks.onToast?.(err?.message || String(err)); }
1035
- },
1036
- });
1037
- }
1038
- }
1039
- items.push({ kind: 'separator' });
1040
- items.push({ id: '__add', label: t('assistant.accounts.add', 'Add account…'), onSelect: () => hooks.onAddAccount?.(brain.provider) });
1041
- items.push({ id: '__manage', label: t('assistant.accounts.manage', 'Manage…'), onSelect: () => hooks.onManageAccounts?.(brain.provider) });
1187
+ const uses = accountModeItem({
1188
+ provider: p, t: hooks.t, account: accounts.find((a) => a.id === brain.accountId), running: hooks.getRunning?.() === true,
1189
+ onSetMode: async (id, mode) => {
1190
+ try {
1191
+ await (codex ? patchCodexAccount : patchClaudeAccount)(id, { mode }); await refetch();
1192
+ hooks.onChange?.(currentBrain(), { requiresSwitch: true, restartBrain: true, field: 'accountMode', previous: brain }); render();
1193
+ }
1194
+ catch (err) { hooks.onToast?.(err?.message || String(err)); }
1195
+ },
1196
+ });
1197
+ if (uses) items.push(uses);
1198
+ items.push(accountManageItem({ t: hooks.t, onAdd: () => hooks.onAddAccount?.(brain.provider), onManage: () => hooks.onManageAccounts?.(brain.provider) }));
1042
1199
  return items;
1043
1200
  }
1044
1201
 
@@ -1088,6 +1245,6 @@ export function createBrainPicker(host, hooks = {}) {
1088
1245
  mountEmptyProject,
1089
1246
  setBrain() { render(); },
1090
1247
  close: closeMenu,
1091
- destroy() { destroyed = true; for (const off of offAccounts) { try { off?.(); } catch {} } closeMenu(); heroProjectMascot?.destroy(); Object.values(buttons).forEach((b) => b.remove()); heroProject?.remove(); heroProject = null; root.remove(); },
1248
+ destroy() { destroyed = true; fitObserver?.disconnect(); if (fitFrame && typeof cancelAnimationFrame === 'function') cancelAnimationFrame(fitFrame); for (const off of offAccounts) { try { off?.(); } catch {} } closeMenu(); heroProjectMascot?.destroy(); Object.values(buttons).forEach((b) => b.remove()); heroProject?.remove(); heroProject = null; root.remove(); },
1092
1249
  };
1093
1250
  }