synabun 2.0.7 → 2.0.10

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 (565) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +1 -1
  3. package/THIRD-PARTY-LICENSES.md +46 -4
  4. package/hooks/claude-code/post-plan.mjs +6 -30
  5. package/hooks/claude-code/session-start.mjs +1 -4
  6. package/hooks/claude-code/shared.mjs +106 -72
  7. package/launcher.mjs +9 -3
  8. package/lib/package-processes.js +670 -0
  9. package/lib/runtime-dependencies.js +225 -0
  10. package/lib/start-launcher.js +6 -1
  11. package/mcp-server/dist/services/browser-bench.d.ts +1 -1
  12. package/mcp-server/dist/services/browser-bench.d.ts.map +1 -1
  13. package/mcp-server/dist/services/browser-bench.js +1 -1
  14. package/mcp-server/dist/services/browser-bench.js.map +1 -1
  15. package/mcp-server/dist/services/categories.d.ts +35 -2
  16. package/mcp-server/dist/services/categories.d.ts.map +1 -1
  17. package/mcp-server/dist/services/categories.js +77 -33
  18. package/mcp-server/dist/services/categories.js.map +1 -1
  19. package/mcp-server/dist/services/desktop-bench.d.ts +7 -7
  20. package/mcp-server/dist/services/desktop-bench.d.ts.map +1 -1
  21. package/mcp-server/dist/services/desktop-bench.js +1 -1
  22. package/mcp-server/dist/services/desktop-bench.js.map +1 -1
  23. package/mcp-server/dist/tools/agents.d.ts +1 -1
  24. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  25. package/mcp-server/dist/tools/agents.js +1 -1
  26. package/mcp-server/dist/tools/agents.js.map +1 -1
  27. package/mcp-server/dist/tools/bluesky.js +1 -1
  28. package/mcp-server/dist/tools/bluesky.js.map +1 -1
  29. package/mcp-server/dist/tools/browser-advanced.d.ts +1 -1
  30. package/mcp-server/dist/tools/browser-advanced.d.ts.map +1 -1
  31. package/mcp-server/dist/tools/browser-advanced.js +1 -1
  32. package/mcp-server/dist/tools/browser-advanced.js.map +1 -1
  33. package/mcp-server/dist/tools/browser-batch.d.ts +1 -1
  34. package/mcp-server/dist/tools/browser-batch.d.ts.map +1 -1
  35. package/mcp-server/dist/tools/browser-batch.js +1 -1
  36. package/mcp-server/dist/tools/browser-batch.js.map +1 -1
  37. package/mcp-server/dist/tools/browser-cheatsheet.d.ts +1 -1
  38. package/mcp-server/dist/tools/browser-cheatsheet.d.ts.map +1 -1
  39. package/mcp-server/dist/tools/browser-cheatsheet.js +1 -1
  40. package/mcp-server/dist/tools/browser-cheatsheet.js.map +1 -1
  41. package/mcp-server/dist/tools/browser-interact.d.ts +1 -1
  42. package/mcp-server/dist/tools/browser-interact.d.ts.map +1 -1
  43. package/mcp-server/dist/tools/browser-interact.js +1 -1
  44. package/mcp-server/dist/tools/browser-interact.js.map +1 -1
  45. package/mcp-server/dist/tools/browser-navigate.d.ts +1 -1
  46. package/mcp-server/dist/tools/browser-navigate.d.ts.map +1 -1
  47. package/mcp-server/dist/tools/browser-navigate.js +1 -1
  48. package/mcp-server/dist/tools/browser-navigate.js.map +1 -1
  49. package/mcp-server/dist/tools/browser-observe.d.ts +1 -1
  50. package/mcp-server/dist/tools/browser-observe.d.ts.map +1 -1
  51. package/mcp-server/dist/tools/browser-observe.js +1 -1
  52. package/mcp-server/dist/tools/browser-observe.js.map +1 -1
  53. package/mcp-server/dist/tools/card-tools.d.ts +1 -1
  54. package/mcp-server/dist/tools/card-tools.d.ts.map +1 -1
  55. package/mcp-server/dist/tools/card-tools.js +1 -1
  56. package/mcp-server/dist/tools/card-tools.js.map +1 -1
  57. package/mcp-server/dist/tools/category.d.ts +1 -1
  58. package/mcp-server/dist/tools/category.d.ts.map +1 -1
  59. package/mcp-server/dist/tools/category.js +5 -1
  60. package/mcp-server/dist/tools/category.js.map +1 -1
  61. package/mcp-server/dist/tools/choice.d.ts +1 -1
  62. package/mcp-server/dist/tools/choice.d.ts.map +1 -1
  63. package/mcp-server/dist/tools/choice.js +1 -1
  64. package/mcp-server/dist/tools/choice.js.map +1 -1
  65. package/mcp-server/dist/tools/computer.d.ts +1 -1
  66. package/mcp-server/dist/tools/computer.d.ts.map +1 -1
  67. package/mcp-server/dist/tools/computer.js +1 -1
  68. package/mcp-server/dist/tools/computer.js.map +1 -1
  69. package/mcp-server/dist/tools/discord-channel.d.ts +1 -1
  70. package/mcp-server/dist/tools/discord-channel.d.ts.map +1 -1
  71. package/mcp-server/dist/tools/discord-channel.js +1 -1
  72. package/mcp-server/dist/tools/discord-channel.js.map +1 -1
  73. package/mcp-server/dist/tools/discord-guild.d.ts +1 -1
  74. package/mcp-server/dist/tools/discord-guild.d.ts.map +1 -1
  75. package/mcp-server/dist/tools/discord-guild.js +1 -1
  76. package/mcp-server/dist/tools/discord-guild.js.map +1 -1
  77. package/mcp-server/dist/tools/discord-member.d.ts +1 -1
  78. package/mcp-server/dist/tools/discord-member.d.ts.map +1 -1
  79. package/mcp-server/dist/tools/discord-member.js +1 -1
  80. package/mcp-server/dist/tools/discord-member.js.map +1 -1
  81. package/mcp-server/dist/tools/discord-message.d.ts +1 -1
  82. package/mcp-server/dist/tools/discord-message.d.ts.map +1 -1
  83. package/mcp-server/dist/tools/discord-message.js +1 -1
  84. package/mcp-server/dist/tools/discord-message.js.map +1 -1
  85. package/mcp-server/dist/tools/discord-onboarding.d.ts +1 -1
  86. package/mcp-server/dist/tools/discord-onboarding.d.ts.map +1 -1
  87. package/mcp-server/dist/tools/discord-onboarding.js +1 -1
  88. package/mcp-server/dist/tools/discord-onboarding.js.map +1 -1
  89. package/mcp-server/dist/tools/discord-role.d.ts +1 -1
  90. package/mcp-server/dist/tools/discord-role.d.ts.map +1 -1
  91. package/mcp-server/dist/tools/discord-role.js +1 -1
  92. package/mcp-server/dist/tools/discord-role.js.map +1 -1
  93. package/mcp-server/dist/tools/discord-thread.d.ts +1 -1
  94. package/mcp-server/dist/tools/discord-thread.d.ts.map +1 -1
  95. package/mcp-server/dist/tools/discord-thread.js +1 -1
  96. package/mcp-server/dist/tools/discord-thread.js.map +1 -1
  97. package/mcp-server/dist/tools/discord-webhook.d.ts +1 -1
  98. package/mcp-server/dist/tools/discord-webhook.d.ts.map +1 -1
  99. package/mcp-server/dist/tools/discord-webhook.js +1 -1
  100. package/mcp-server/dist/tools/discord-webhook.js.map +1 -1
  101. package/mcp-server/dist/tools/fb-groups.d.ts +1 -1
  102. package/mcp-server/dist/tools/fb-groups.d.ts.map +1 -1
  103. package/mcp-server/dist/tools/fb-groups.js +1 -1
  104. package/mcp-server/dist/tools/fb-groups.js.map +1 -1
  105. package/mcp-server/dist/tools/forget.d.ts +1 -1
  106. package/mcp-server/dist/tools/forget.d.ts.map +1 -1
  107. package/mcp-server/dist/tools/forget.js +1 -1
  108. package/mcp-server/dist/tools/forget.js.map +1 -1
  109. package/mcp-server/dist/tools/git-tools.d.ts +1 -1
  110. package/mcp-server/dist/tools/git-tools.d.ts.map +1 -1
  111. package/mcp-server/dist/tools/git-tools.js +1 -1
  112. package/mcp-server/dist/tools/git-tools.js.map +1 -1
  113. package/mcp-server/dist/tools/gsc/experience.d.ts +1 -1
  114. package/mcp-server/dist/tools/gsc/experience.d.ts.map +1 -1
  115. package/mcp-server/dist/tools/gsc/experience.js +1 -1
  116. package/mcp-server/dist/tools/gsc/experience.js.map +1 -1
  117. package/mcp-server/dist/tools/gsc/indexing.d.ts +1 -1
  118. package/mcp-server/dist/tools/gsc/indexing.d.ts.map +1 -1
  119. package/mcp-server/dist/tools/gsc/indexing.js +1 -1
  120. package/mcp-server/dist/tools/gsc/indexing.js.map +1 -1
  121. package/mcp-server/dist/tools/gsc/inspect.d.ts +1 -1
  122. package/mcp-server/dist/tools/gsc/inspect.d.ts.map +1 -1
  123. package/mcp-server/dist/tools/gsc/inspect.js +1 -1
  124. package/mcp-server/dist/tools/gsc/inspect.js.map +1 -1
  125. package/mcp-server/dist/tools/gsc/navigate.d.ts +1 -1
  126. package/mcp-server/dist/tools/gsc/navigate.d.ts.map +1 -1
  127. package/mcp-server/dist/tools/gsc/navigate.js +1 -1
  128. package/mcp-server/dist/tools/gsc/navigate.js.map +1 -1
  129. package/mcp-server/dist/tools/gsc/performance.d.ts +1 -1
  130. package/mcp-server/dist/tools/gsc/performance.d.ts.map +1 -1
  131. package/mcp-server/dist/tools/gsc/performance.js +1 -1
  132. package/mcp-server/dist/tools/gsc/performance.js.map +1 -1
  133. package/mcp-server/dist/tools/gsc/settings.d.ts +1 -1
  134. package/mcp-server/dist/tools/gsc/settings.d.ts.map +1 -1
  135. package/mcp-server/dist/tools/gsc/settings.js +1 -1
  136. package/mcp-server/dist/tools/gsc/settings.js.map +1 -1
  137. package/mcp-server/dist/tools/image-tools.d.ts +1 -1
  138. package/mcp-server/dist/tools/image-tools.d.ts.map +1 -1
  139. package/mcp-server/dist/tools/image-tools.js +1 -1
  140. package/mcp-server/dist/tools/image-tools.js.map +1 -1
  141. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +1 -1
  142. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +1 -1
  143. package/mcp-server/dist/tools/leonardo-browser-tools.js +1 -1
  144. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +1 -1
  145. package/mcp-server/dist/tools/loop.d.ts +1 -1
  146. package/mcp-server/dist/tools/loop.d.ts.map +1 -1
  147. package/mcp-server/dist/tools/loop.js +1 -1
  148. package/mcp-server/dist/tools/loop.js.map +1 -1
  149. package/mcp-server/dist/tools/memories.d.ts +1 -1
  150. package/mcp-server/dist/tools/memories.d.ts.map +1 -1
  151. package/mcp-server/dist/tools/memories.js +1 -1
  152. package/mcp-server/dist/tools/memories.js.map +1 -1
  153. package/mcp-server/dist/tools/morelogin.d.ts +1 -1
  154. package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
  155. package/mcp-server/dist/tools/morelogin.js +1 -1
  156. package/mcp-server/dist/tools/morelogin.js.map +1 -1
  157. package/mcp-server/dist/tools/profile.d.ts +1 -1
  158. package/mcp-server/dist/tools/profile.d.ts.map +1 -1
  159. package/mcp-server/dist/tools/profile.js +1 -1
  160. package/mcp-server/dist/tools/profile.js.map +1 -1
  161. package/mcp-server/dist/tools/recall.d.ts +1 -1
  162. package/mcp-server/dist/tools/recall.d.ts.map +1 -1
  163. package/mcp-server/dist/tools/recall.js +1 -1
  164. package/mcp-server/dist/tools/recall.js.map +1 -1
  165. package/mcp-server/dist/tools/reflect.d.ts +1 -1
  166. package/mcp-server/dist/tools/reflect.d.ts.map +1 -1
  167. package/mcp-server/dist/tools/reflect.js +1 -1
  168. package/mcp-server/dist/tools/reflect.js.map +1 -1
  169. package/mcp-server/dist/tools/remember.d.ts +1 -1
  170. package/mcp-server/dist/tools/remember.d.ts.map +1 -1
  171. package/mcp-server/dist/tools/remember.js +13 -11
  172. package/mcp-server/dist/tools/remember.js.map +1 -1
  173. package/mcp-server/dist/tools/restore.d.ts +1 -1
  174. package/mcp-server/dist/tools/restore.d.ts.map +1 -1
  175. package/mcp-server/dist/tools/restore.js +1 -1
  176. package/mcp-server/dist/tools/restore.js.map +1 -1
  177. package/mcp-server/dist/tools/style-guide-tools.d.ts +1 -1
  178. package/mcp-server/dist/tools/style-guide-tools.d.ts.map +1 -1
  179. package/mcp-server/dist/tools/style-guide-tools.js +1 -1
  180. package/mcp-server/dist/tools/style-guide-tools.js.map +1 -1
  181. package/mcp-server/dist/tools/sync.d.ts +1 -1
  182. package/mcp-server/dist/tools/sync.d.ts.map +1 -1
  183. package/mcp-server/dist/tools/sync.js +1 -1
  184. package/mcp-server/dist/tools/sync.js.map +1 -1
  185. package/mcp-server/dist/tools/tictactoe-tools.d.ts +1 -1
  186. package/mcp-server/dist/tools/tictactoe-tools.d.ts.map +1 -1
  187. package/mcp-server/dist/tools/tictactoe-tools.js +1 -1
  188. package/mcp-server/dist/tools/tictactoe-tools.js.map +1 -1
  189. package/mcp-server/dist/tools/utils.d.ts +1 -1
  190. package/mcp-server/dist/tools/utils.d.ts.map +1 -1
  191. package/mcp-server/dist/tools/utils.js +1 -1
  192. package/mcp-server/dist/tools/utils.js.map +1 -1
  193. package/mcp-server/dist/tools/whatsapp-send-file.d.ts +1 -1
  194. package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -1
  195. package/mcp-server/dist/tools/whatsapp-send-file.js +1 -1
  196. package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -1
  197. package/mcp-server/dist/tools/whiteboard-tools.d.ts +3 -3
  198. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
  199. package/mcp-server/dist/tools/whiteboard-tools.js +1 -1
  200. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
  201. package/mcp-server/dist/tools/youtube/analytics.d.ts +1 -1
  202. package/mcp-server/dist/tools/youtube/analytics.d.ts.map +1 -1
  203. package/mcp-server/dist/tools/youtube/analytics.js +1 -1
  204. package/mcp-server/dist/tools/youtube/analytics.js.map +1 -1
  205. package/mcp-server/dist/tools/youtube/download.d.ts +1 -1
  206. package/mcp-server/dist/tools/youtube/download.d.ts.map +1 -1
  207. package/mcp-server/dist/tools/youtube/download.js +1 -1
  208. package/mcp-server/dist/tools/youtube/download.js.map +1 -1
  209. package/mcp-server/dist/tools/youtube/media.d.ts +1 -1
  210. package/mcp-server/dist/tools/youtube/media.d.ts.map +1 -1
  211. package/mcp-server/dist/tools/youtube/media.js +1 -1
  212. package/mcp-server/dist/tools/youtube/media.js.map +1 -1
  213. package/mcp-server/dist/tools/youtube/metadata.d.ts +1 -1
  214. package/mcp-server/dist/tools/youtube/metadata.d.ts.map +1 -1
  215. package/mcp-server/dist/tools/youtube/metadata.js +1 -1
  216. package/mcp-server/dist/tools/youtube/metadata.js.map +1 -1
  217. package/mcp-server/dist/tools/youtube/navigate.d.ts +1 -1
  218. package/mcp-server/dist/tools/youtube/navigate.d.ts.map +1 -1
  219. package/mcp-server/dist/tools/youtube/navigate.js +1 -1
  220. package/mcp-server/dist/tools/youtube/navigate.js.map +1 -1
  221. package/mcp-server/dist/tools/youtube/playlist.d.ts +1 -1
  222. package/mcp-server/dist/tools/youtube/playlist.d.ts.map +1 -1
  223. package/mcp-server/dist/tools/youtube/playlist.js +1 -1
  224. package/mcp-server/dist/tools/youtube/playlist.js.map +1 -1
  225. package/mcp-server/dist/tools/youtube/schedule.d.ts +1 -1
  226. package/mcp-server/dist/tools/youtube/schedule.d.ts.map +1 -1
  227. package/mcp-server/dist/tools/youtube/schedule.js +1 -1
  228. package/mcp-server/dist/tools/youtube/schedule.js.map +1 -1
  229. package/mcp-server/dist/tools/youtube/source.d.ts +1 -1
  230. package/mcp-server/dist/tools/youtube/source.d.ts.map +1 -1
  231. package/mcp-server/dist/tools/youtube/source.js +1 -1
  232. package/mcp-server/dist/tools/youtube/source.js.map +1 -1
  233. package/mcp-server/dist/tools/youtube/upload.d.ts +1 -1
  234. package/mcp-server/dist/tools/youtube/upload.d.ts.map +1 -1
  235. package/mcp-server/dist/tools/youtube/upload.js +4 -9
  236. package/mcp-server/dist/tools/youtube/upload.js.map +1 -1
  237. package/mcp-server/dist/tui.js +16 -4
  238. package/mcp-server/dist/tui.js.map +1 -1
  239. package/mcp-server/package-lock.json +3 -3
  240. package/mcp-server/package.json +3 -3
  241. package/mcp-server/src/services/browser-bench.ts +1 -1
  242. package/mcp-server/src/services/categories.ts +77 -32
  243. package/mcp-server/src/services/desktop-bench.ts +1 -1
  244. package/mcp-server/src/tools/agents.ts +1 -1
  245. package/mcp-server/src/tools/bluesky.ts +1 -1
  246. package/mcp-server/src/tools/browser-advanced.ts +1 -1
  247. package/mcp-server/src/tools/browser-batch.ts +1 -1
  248. package/mcp-server/src/tools/browser-cheatsheet.ts +1 -1
  249. package/mcp-server/src/tools/browser-interact.ts +1 -1
  250. package/mcp-server/src/tools/browser-navigate.ts +1 -1
  251. package/mcp-server/src/tools/browser-observe.ts +1 -1
  252. package/mcp-server/src/tools/card-tools.ts +1 -1
  253. package/mcp-server/src/tools/category.ts +6 -1
  254. package/mcp-server/src/tools/choice.ts +1 -1
  255. package/mcp-server/src/tools/computer.ts +1 -1
  256. package/mcp-server/src/tools/discord-channel.ts +1 -1
  257. package/mcp-server/src/tools/discord-guild.ts +1 -1
  258. package/mcp-server/src/tools/discord-member.ts +1 -1
  259. package/mcp-server/src/tools/discord-message.ts +1 -1
  260. package/mcp-server/src/tools/discord-onboarding.ts +1 -1
  261. package/mcp-server/src/tools/discord-role.ts +1 -1
  262. package/mcp-server/src/tools/discord-thread.ts +1 -1
  263. package/mcp-server/src/tools/discord-webhook.ts +1 -1
  264. package/mcp-server/src/tools/fb-groups.ts +1 -1
  265. package/mcp-server/src/tools/forget.ts +1 -1
  266. package/mcp-server/src/tools/git-tools.ts +1 -1
  267. package/mcp-server/src/tools/gsc/experience.ts +1 -1
  268. package/mcp-server/src/tools/gsc/indexing.ts +1 -1
  269. package/mcp-server/src/tools/gsc/inspect.ts +1 -1
  270. package/mcp-server/src/tools/gsc/navigate.ts +1 -1
  271. package/mcp-server/src/tools/gsc/performance.ts +1 -1
  272. package/mcp-server/src/tools/gsc/settings.ts +1 -1
  273. package/mcp-server/src/tools/image-tools.ts +1 -1
  274. package/mcp-server/src/tools/leonardo-browser-tools.ts +1 -1
  275. package/mcp-server/src/tools/loop.ts +1 -1
  276. package/mcp-server/src/tools/memories.ts +1 -1
  277. package/mcp-server/src/tools/morelogin.ts +1 -1
  278. package/mcp-server/src/tools/profile.ts +1 -1
  279. package/mcp-server/src/tools/recall.ts +1 -1
  280. package/mcp-server/src/tools/reflect.ts +1 -1
  281. package/mcp-server/src/tools/remember.ts +13 -12
  282. package/mcp-server/src/tools/restore.ts +1 -1
  283. package/mcp-server/src/tools/style-guide-tools.ts +1 -1
  284. package/mcp-server/src/tools/sync.ts +1 -1
  285. package/mcp-server/src/tools/tictactoe-tools.ts +1 -1
  286. package/mcp-server/src/tools/utils.ts +1 -1
  287. package/mcp-server/src/tools/whatsapp-send-file.ts +1 -1
  288. package/mcp-server/src/tools/whiteboard-tools.ts +1 -1
  289. package/mcp-server/src/tools/youtube/analytics.ts +1 -1
  290. package/mcp-server/src/tools/youtube/download.ts +1 -1
  291. package/mcp-server/src/tools/youtube/media.ts +1 -1
  292. package/mcp-server/src/tools/youtube/metadata.ts +1 -1
  293. package/mcp-server/src/tools/youtube/navigate.ts +1 -1
  294. package/mcp-server/src/tools/youtube/playlist.ts +1 -1
  295. package/mcp-server/src/tools/youtube/schedule.ts +1 -1
  296. package/mcp-server/src/tools/youtube/source.ts +1 -1
  297. package/mcp-server/src/tools/youtube/upload.ts +4 -12
  298. package/mcp-server/src/tui.ts +22 -9
  299. package/neural-interface/i18n/en.json +236 -7
  300. package/neural-interface/i18n/pt-BR.json +238 -9
  301. package/neural-interface/lib/account-usage.js +5 -3
  302. package/neural-interface/lib/agent-sandbox-elevation.js +102 -0
  303. package/neural-interface/lib/agent-sandbox-setting.js +57 -0
  304. package/neural-interface/lib/app-controls-api.js +161 -0
  305. package/neural-interface/lib/assistant-api.js +40 -7
  306. package/neural-interface/lib/assistant-brains/opencode.js +13 -1
  307. package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +44 -0
  308. package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +57 -0
  309. package/neural-interface/lib/assistant-catalog.js +17 -2
  310. package/neural-interface/lib/assistant-dispatch.js +3 -0
  311. package/neural-interface/lib/assistant-envelope.js +19 -1
  312. package/neural-interface/lib/assistant-route-gate-codex.js +3 -1
  313. package/neural-interface/lib/assistant-runtime.js +131 -8
  314. package/neural-interface/lib/claude-model-catalog.js +64 -14
  315. package/neural-interface/lib/codex-account-auth.js +4 -2
  316. package/neural-interface/lib/codex-accounts.js +14 -1
  317. package/neural-interface/lib/codex-settings-schema.js +111 -47
  318. package/neural-interface/lib/editor-completion-opencode.js +132 -35
  319. package/neural-interface/lib/exec-loop-command.js +11 -2
  320. package/neural-interface/lib/install-details.js +23 -5
  321. package/neural-interface/lib/launch-cache.js +293 -0
  322. package/neural-interface/lib/login-start.js +524 -0
  323. package/neural-interface/lib/native-loop-providers.js +24 -4
  324. package/neural-interface/lib/native-loop-runtime.js +19 -0
  325. package/neural-interface/lib/opencode-db-schema.js +75 -0
  326. package/neural-interface/lib/opencode-history.js +16 -1
  327. package/neural-interface/lib/opencode-protocol.js +173 -0
  328. package/neural-interface/lib/opencode-provider-view.js +81 -0
  329. package/neural-interface/lib/opencode-runtime-config.js +10 -3
  330. package/neural-interface/lib/opencode-serve-records.js +606 -0
  331. package/neural-interface/lib/opencode-v2-client.js +33 -6
  332. package/neural-interface/lib/opencode-v2-ws-requests.js +19 -5
  333. package/neural-interface/lib/opencode-worker-activity.js +229 -0
  334. package/neural-interface/lib/opencode2-client.js +760 -0
  335. package/neural-interface/lib/opencode2-proxy.js +116 -0
  336. package/neural-interface/lib/opencode2-translate.js +1008 -0
  337. package/neural-interface/lib/process-identity.js +361 -0
  338. package/neural-interface/lib/provider-panel-terminal-auth.js +3 -2
  339. package/neural-interface/lib/session-cache-builder.js +26 -5
  340. package/neural-interface/lib/session-indexer.js +3 -1
  341. package/neural-interface/lib/session-title-generator.js +20 -0
  342. package/neural-interface/lib/style-guide/api.js +4 -3
  343. package/neural-interface/lib/untrusted-file-headers.js +27 -0
  344. package/neural-interface/package-lock.json +1342 -44
  345. package/neural-interface/package.json +5 -1
  346. package/neural-interface/public/index.html +2 -2
  347. package/neural-interface/public/shared/account-menu.js +4 -1
  348. package/neural-interface/public/shared/account-usage.js +62 -2
  349. package/neural-interface/public/shared/account-view.js +2 -1
  350. package/neural-interface/public/shared/agent-runtime-options.js +55 -11
  351. package/neural-interface/public/shared/assistant/asst-account-menu.js +298 -0
  352. package/neural-interface/public/shared/assistant/asst-agents-dock.js +172 -17
  353. package/neural-interface/public/shared/assistant/asst-api.js +18 -3
  354. package/neural-interface/public/shared/assistant/asst-brain-picker.js +424 -152
  355. package/neural-interface/public/shared/assistant/asst-composer.js +1 -1
  356. package/neural-interface/public/shared/assistant/asst-hosts.js +49 -2
  357. package/neural-interface/public/shared/assistant/asst-menu.js +275 -77
  358. package/neural-interface/public/shared/assistant/asst-model-chip.js +135 -0
  359. package/neural-interface/public/shared/assistant/asst-panel.js +236 -35
  360. package/neural-interface/public/shared/assistant/asst-rack-styles.js +28 -10
  361. package/neural-interface/public/shared/assistant/asst-redact.js +186 -0
  362. package/neural-interface/public/shared/assistant/asst-render.js +382 -36
  363. package/neural-interface/public/shared/assistant/asst-run-stage.js +231 -0
  364. package/neural-interface/public/shared/assistant/asst-sanitize.js +193 -3
  365. package/neural-interface/public/shared/assistant/asst-scene-styles.js +273 -0
  366. package/neural-interface/public/shared/assistant/asst-scenes.js +528 -0
  367. package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +3 -15
  368. package/neural-interface/public/shared/assistant/asst-state.js +14 -0
  369. package/neural-interface/public/shared/assistant/asst-styles.js +109 -17
  370. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +13 -1
  371. package/neural-interface/public/shared/cc-dropdown.js +480 -0
  372. package/neural-interface/public/shared/cdx/cdx-panel.js +14 -2
  373. package/neural-interface/public/shared/cdx/cdx-render.js +15 -3
  374. package/neural-interface/public/shared/cdx/cdx-styles.js +49 -5
  375. package/neural-interface/public/shared/cdx/cdx-tabs.js +152 -35
  376. package/neural-interface/public/shared/cp/cp-markdown.js +4 -57
  377. package/neural-interface/public/shared/data-cache.js +223 -0
  378. package/neural-interface/public/shared/desktop-bridge.js +11 -0
  379. package/neural-interface/public/shared/desktop-titlebar-view.js +78 -1
  380. package/neural-interface/public/shared/desktop-titlebar.js +214 -28
  381. package/neural-interface/public/shared/desktop-update-view.js +15 -5
  382. package/neural-interface/public/shared/file-links.js +73 -8
  383. package/neural-interface/public/shared/html-shell.js +74 -80
  384. package/neural-interface/public/shared/launch-prefetch.js +59 -0
  385. package/neural-interface/public/shared/ocp-v2/ocp-v2-caps.js +3 -2
  386. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +11 -3
  387. package/neural-interface/public/shared/ocp-v2/ocp-v2-modelpicker.js +54 -17
  388. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +97 -3
  389. package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +3 -2
  390. package/neural-interface/public/shared/ocp-v2/ocp-v2-providers.js +11 -9
  391. package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +15 -4
  392. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +1 -1
  393. package/neural-interface/public/shared/ocp-v2/ocp-v2-send-logic.js +401 -0
  394. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +16 -9
  395. package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +60 -0
  396. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +83 -5
  397. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +195 -19
  398. package/neural-interface/public/shared/opencode-providers.js +39 -0
  399. package/neural-interface/public/shared/schedules-view.js +318 -0
  400. package/neural-interface/public/shared/settings/settings-app-pane.js +335 -0
  401. package/neural-interface/public/shared/settings/settings-app.js +243 -0
  402. package/neural-interface/public/shared/settings/settings-data.js +88 -0
  403. package/neural-interface/public/shared/settings/settings-ia.js +17 -0
  404. package/neural-interface/public/shared/showcase-privacy.js +2 -1
  405. package/neural-interface/public/shared/sidepanel-session-status.js +49 -0
  406. package/neural-interface/public/shared/styles.css +543 -372
  407. package/neural-interface/public/shared/ui-assistant.js +27 -5
  408. package/neural-interface/public/shared/ui-automation-studio.js +67 -27
  409. package/neural-interface/public/shared/ui-bookmarks.js +2 -1
  410. package/neural-interface/public/shared/ui-claude-panel.js +126 -12
  411. package/neural-interface/public/shared/ui-codex-panel.js +1 -0
  412. package/neural-interface/public/shared/ui-detail.js +6 -6
  413. package/neural-interface/public/shared/ui-explorer.js +2 -0
  414. package/neural-interface/public/shared/ui-file-links.js +1 -0
  415. package/neural-interface/public/shared/ui-image-gallery.js +6 -5
  416. package/neural-interface/public/shared/ui-invite.js +18 -2
  417. package/neural-interface/public/shared/ui-link.js +9 -9
  418. package/neural-interface/public/shared/ui-menubar.js +232 -33
  419. package/neural-interface/public/shared/ui-native-loop-router.js +2 -0
  420. package/neural-interface/public/shared/ui-opencode-panel-v2.js +1 -0
  421. package/neural-interface/public/shared/ui-schedules-studio.js +2099 -869
  422. package/neural-interface/public/shared/ui-sessions.js +15 -4
  423. package/neural-interface/public/shared/ui-settings.js +87 -36
  424. package/neural-interface/public/shared/ui-sidebar.js +4 -4
  425. package/neural-interface/public/shared/ui-sidepanel-session-entry.js +13 -0
  426. package/neural-interface/public/shared/ui-sidepanel-sessions.js +55 -4
  427. package/neural-interface/public/shared/ui-sidepanel-tray.js +84 -8
  428. package/neural-interface/public/shared/ui-skills.js +70 -12
  429. package/neural-interface/public/shared/ui-sync.js +9 -0
  430. package/neural-interface/public/shared/ui-terminal.js +36 -20
  431. package/neural-interface/public/shared/ui-trash.js +8 -1
  432. package/neural-interface/public/shared/ui-tutorial-steps.js +7 -3
  433. package/neural-interface/public/shared/ui-tutorial.js +15 -5
  434. package/neural-interface/public/shared/ui-update.js +87 -16
  435. package/neural-interface/public/shared/ui-whiteboard.js +2 -1
  436. package/neural-interface/public/variant/2d/main.js +4 -1
  437. package/neural-interface/public/variant/3d/main.js +3 -0
  438. package/neural-interface/public/vendor/highlight/LICENSE +29 -0
  439. package/neural-interface/public/vendor/highlight/github-dark-dimmed-11.12.0.min.css +9 -0
  440. package/neural-interface/public/vendor/highlight/highlight-11.12.0.min.js +1262 -0
  441. package/neural-interface/public/vendor/highlight/manifest.json +5 -0
  442. package/neural-interface/public/vendor/html2canvas/LICENSE +22 -0
  443. package/neural-interface/public/vendor/html2canvas/html2canvas-1.4.1.min.js +20 -0
  444. package/neural-interface/public/vendor/html2canvas/manifest.json +4 -0
  445. package/neural-interface/public/vendor/marked/LICENSE.md +44 -0
  446. package/neural-interface/public/vendor/marked/manifest.json +4 -0
  447. package/neural-interface/public/vendor/marked/marked-14.1.4.esm.js +2502 -0
  448. package/neural-interface/public/vendor/mermaid/LICENSE +21 -0
  449. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/abnfDiagram-PAR5IXJI.mjs +1 -0
  450. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/architecture-7GRP2DOG-M7G3SWCO.mjs +1 -0
  451. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/architectureDiagram-2PNWDENW.mjs +36 -0
  452. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/blockDiagram-PUWGUBOA.mjs +129 -0
  453. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/c4Diagram-3NLNHZP5.mjs +38 -0
  454. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-2NMSUPJH.mjs +1 -0
  455. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-32MCUW2P.mjs +206 -0
  456. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-3NPNU5GP.mjs +1 -0
  457. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-3SMGFRGC.mjs +1 -0
  458. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-4CAJECRU.mjs +14 -0
  459. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-4OADUZ5Q.mjs +10 -0
  460. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5IMINLNL.mjs +1 -0
  461. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5IQCK5RY.mjs +3 -0
  462. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5MVFYWFU.mjs +1 -0
  463. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-AQ6EADP3.mjs +1 -0
  464. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-AZZRMDJM.mjs +15 -0
  465. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-CLS4B6BI.mjs +168 -0
  466. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-DZP67EKU.mjs +70 -0
  467. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-EICP7K5X.mjs +1 -0
  468. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-GDY2UAB4.mjs +1 -0
  469. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-H7VHZCWX.mjs +1 -0
  470. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-IJBDOHL6.mjs +1 -0
  471. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JFFVMJIA.mjs +1 -0
  472. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JQRUD6KW.mjs +1 -0
  473. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JZGDI5JK.mjs +1 -0
  474. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-KD6SPIPN.mjs +2 -0
  475. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-NF366YI6.mjs +231 -0
  476. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-NUDPKBEF.mjs +32 -0
  477. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-OJINMHTS.mjs +1 -0
  478. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-Q6NGPIAH.mjs +2 -0
  479. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-QJSWEUOL.mjs +132 -0
  480. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-R7JDMUKV.mjs +1 -0
  481. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-RCXAM2GK.mjs +1 -0
  482. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-RT4MAWR3.mjs +1 -0
  483. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-SFMINX4W.mjs +1 -0
  484. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-SLDQ463B.mjs +88 -0
  485. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-STOV2HOB.mjs +1 -0
  486. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-T5OCTHI4.mjs +1 -0
  487. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-TCEHX7R3.mjs +1 -0
  488. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VQC2EELJ.mjs +1 -0
  489. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VRZKL3EH.mjs +1 -0
  490. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VTHDQ7BV.mjs +1 -0
  491. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VY2OSTJR.mjs +2 -0
  492. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-WUBWJARI.mjs +1 -0
  493. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-YBMQ5JGJ.mjs +2 -0
  494. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-YDMLL4PJ.mjs +1 -0
  495. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-Z6V44ARX.mjs +62 -0
  496. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZJJ7VQJP.mjs +339 -0
  497. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZNLK7HRR.mjs +1 -0
  498. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZQN7UHIL.mjs +1 -0
  499. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZS6VBONO.mjs +174 -0
  500. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZUNWM646.mjs +1 -0
  501. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-FGAMPOII.mjs +1 -0
  502. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-v2-AI5RJEK4.mjs +1 -0
  503. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cose-bilkent-Q72C2OJ3.mjs +1 -0
  504. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cynefin-OW5HDTMX-6OCTWOGG.mjs +1 -0
  505. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cynefinDiagram-2OOWUY4M.mjs +62 -0
  506. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/dagre-MPVFI544.mjs +4 -0
  507. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-AE4NWXFW.mjs +24 -0
  508. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-BUJID5UG.mjs +41 -0
  509. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-C44CZ5NI.mjs +24 -0
  510. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-EYOLEXXS.mjs +30 -0
  511. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-P7B6PWOJ.mjs +3 -0
  512. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ebnfDiagram-FTPZ3PV6.mjs +1 -0
  513. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/erDiagram-H3ERR26V.mjs +99 -0
  514. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/eventmodeling-NTZA5JFV-WKSWQHFS.mjs +1 -0
  515. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/flowDiagram-YHGXBVSY.mjs +1 -0
  516. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ganttDiagram-RWFTB7ED.mjs +292 -0
  517. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/gitGraph-4MIJSDKK-JH2HKYUO.mjs +1 -0
  518. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/gitGraphDiagram-MAR6GKOP.mjs +106 -0
  519. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/info-A6RAGUB7-3TX3OFWW.mjs +1 -0
  520. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/infoDiagram-LV547AFH.mjs +2 -0
  521. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ishikawaDiagram-PUJQGNDS.mjs +70 -0
  522. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/journeyDiagram-EW5QJT4W.mjs +139 -0
  523. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/kanban-definition-JSQERAZB.mjs +89 -0
  524. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/katex-CZ4GXH2S.mjs +257 -0
  525. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/mindmap-definition-VBJCLQLM.mjs +96 -0
  526. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/packet-AYTQ26CC-MKII6P3Y.mjs +1 -0
  527. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pegDiagram-7WILOQTJ.mjs +1 -0
  528. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pie-WAS4IAKB-6B4OPMJ5.mjs +1 -0
  529. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pieDiagram-EFA7CBSB.mjs +39 -0
  530. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/quadrantDiagram-WARVYA7P.mjs +7 -0
  531. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/radar-RG4KPBEZ-UPTR3XUL.mjs +1 -0
  532. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-74A4TZTK-MEFUPFMS.mjs +1 -0
  533. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-abnf-HS5TGJTU-R5YG2S5D.mjs +1 -0
  534. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-ebnf-LZEXJU2U-G5QEX2L6.mjs +1 -0
  535. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-peg-WCYAUIDC-H5I2ZRY2.mjs +1 -0
  536. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroadDiagram-5NIQVNLQ.mjs +1 -0
  537. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/requirementDiagram-SXREGOKZ.mjs +84 -0
  538. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sankeyDiagram-VV74M2VP.mjs +40 -0
  539. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sequenceDiagram-52ZFIFDL.mjs +162 -0
  540. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sizeCapture-FGA5AGYP.mjs +1 -0
  541. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-3QAKPIVI.mjs +1 -0
  542. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-v2-HN2P4RDZ.mjs +1 -0
  543. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/swimlanes-QLVP42QD.mjs +1 -0
  544. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/swimlanesDiagram-JKPDBFCN.mjs +8 -0
  545. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/timeline-definition-GS45XAXW.mjs +120 -0
  546. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/treeView-Q6P3EWNA-ZRJGANNQ.mjs +1 -0
  547. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/treemap-WGGIJYW6-K64VN5AS.mjs +1 -0
  548. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/vennDiagram-QIQU2ZRJ.mjs +34 -0
  549. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/wardley-WFR3VGLG-D67OWCCH.mjs +1 -0
  550. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/wardleyDiagram-U6YNM66N.mjs +78 -0
  551. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/xychartDiagram-XJAZEDNM.mjs +7 -0
  552. package/neural-interface/public/vendor/mermaid/manifest.json +4 -0
  553. package/neural-interface/public/vendor/mermaid/mermaid-11.17.2.esm.min.mjs +14 -0
  554. package/neural-interface/scripts/build-vendor.mjs +84 -3
  555. package/neural-interface/scripts/rebuild-pty.js +14 -2
  556. package/neural-interface/server.js +1279 -372
  557. package/npm-shrinkwrap.json +4424 -0
  558. package/package.json +54 -2
  559. package/postinstall.js +61 -47
  560. package/preuninstall.js +8 -35
  561. package/setup.js +76 -44
  562. package/skills/synabun/modules/idea.md +1 -1
  563. package/skills/synabun/modules/schedule.md +2 -2
  564. package/updater.cmd +2 -2
  565. package/updater.mjs +97 -3
@@ -29,11 +29,14 @@ import {
29
29
  import { getHiddenModels, HIDDEN_MODELS_EVENT, hiddenModelMatch } from '../ocp-hidden-models.js';
30
30
  import { getProviderMeta } from '../provider-icons.js';
31
31
  import { on } from '../state.js';
32
+ import { registerLaunchPrefetch } from '../launch-prefetch.js';
32
33
  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';
34
+ import { accountBadge, accountIdentity, accountName, accountText } from '../account-view.js';
35
+ import { autoAccountLabel } from '../account-usage.js';
36
+ import { accountChoiceItems, accountManageItem, accountMenuRow, accountModeItem, openAccountMenu } from './asst-account-menu.js';
35
37
  import { closeMenu, currentMenu, isMenuOpen, openMenu } from './asst-menu.js';
36
38
  import { createMascotControl } from './asst-mascot.js';
39
+ import { modelChipForms, modelChipName, modelChipTag } from './asst-model-chip.js';
37
40
  import {
38
41
  ACCOUNT_PROVIDERS,
39
42
  brainRequiresSwitch,
@@ -52,6 +55,9 @@ import {
52
55
  } from './asst-state.js';
53
56
 
54
57
  let _projectsCache = null;
58
+ // Providers whose field menus have everything they read loaded once (models, catalog, accounts or agents,
59
+ // presets, projects): their menus open from it at once and read it again in the background.
60
+ const _warmProviders = new Set();
55
61
 
56
62
  /** SynaBun's registered projects as the last load saw them ([{ path, label }]; [] before the first). */
57
63
  export function cachedProjects() { return _projectsCache || []; }
@@ -79,6 +85,10 @@ const TEST_DENSITY_COPY = {
79
85
  thorough: ['Thorough', 'Full coverage, whole suite, live checks.'],
80
86
  };
81
87
 
88
+ // English copy of the Admin sandbox row (i18n: assistant.adminSandbox.hint). Names only Codex: the
89
+ // one provider it changes (lib/agent-sandbox-elevation.js; Claude Code and OpenCode are no-ops).
90
+ 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.';
91
+
82
92
  /** "Light" — the level's name (a missing or unknown level is the default one). */
83
93
  export function testDensityLabel(level, t) {
84
94
  const id = normalizeTestDensity(level);
@@ -315,11 +325,41 @@ export async function loadModelCatalog({ force = false } = {}) {
315
325
  // while it loaded: the next open asks again.
316
326
  _catalogAt = remote && epoch === _catalogEpoch ? Date.now() : 0;
317
327
  _catalogPartial = !remote;
328
+ if (_catalogAt) keepCatalog(out, sets);
318
329
  return out;
319
330
  })().finally(() => { _catalogLoading = null; });
320
331
  return _catalogLoading;
321
332
  }
322
333
 
334
+ // The last complete catalog is also kept in sessionStorage (this tab only): after a reload the model menus
335
+ // paint from it at once. Read back, it is due: the first load asks the server again.
336
+ const CATALOG_STORE_KEY = 'synabun:assistant-model-catalog:v1';
337
+ function keepCatalog(catalog, sets) {
338
+ try {
339
+ if (typeof sessionStorage === 'undefined') return;
340
+ const hidden = {};
341
+ for (const provider of PROVIDERS) hidden[provider] = [...(sets?.[provider] || [])];
342
+ const rowOnly = {};
343
+ for (const provider of PROVIDERS) rowOnly[provider] = [...(sets?.rowOnly?.[provider] || [])];
344
+ sessionStorage.setItem(CATALOG_STORE_KEY, JSON.stringify({ catalog, hidden, rowOnly }));
345
+ } catch { /* full or unavailable */ }
346
+ }
347
+ (() => {
348
+ try {
349
+ if (typeof sessionStorage === 'undefined' || _catalog) return;
350
+ const kept = JSON.parse(sessionStorage.getItem(CATALOG_STORE_KEY) || 'null');
351
+ if (!kept?.catalog || typeof kept.catalog !== 'object') return;
352
+ _catalog = kept.catalog;
353
+ _catalogAt = 0;
354
+ const sets = { rowOnly: {} };
355
+ for (const provider of PROVIDERS) {
356
+ sets[provider] = new Set(Array.isArray(kept.hidden?.[provider]) ? kept.hidden[provider] : []);
357
+ sets.rowOnly[provider] = new Set(Array.isArray(kept.rowOnly?.[provider]) ? kept.rowOnly[provider] : []);
358
+ }
359
+ _hidden = sets;
360
+ } catch { /* unreadable: the first load builds it */ }
361
+ })();
362
+
323
363
  /** The last loadModelCatalog() result, synchronously (null before the first load). */
324
364
  export function cachedModelCatalog() { return _catalog; }
325
365
 
@@ -340,6 +380,8 @@ export function invalidateModelCatalog() {
340
380
  _catalogAt = 0;
341
381
  _catalogPartial = false;
342
382
  _catalogEpoch += 1;
383
+ // A reload must not paint the lists this replaced.
384
+ try { if (typeof sessionStorage !== 'undefined') sessionStorage.removeItem(CATALOG_STORE_KEY); } catch { /* unavailable */ }
343
385
  }
344
386
 
345
387
  /**
@@ -360,6 +402,15 @@ function modelListsChanged() {
360
402
  for (const listener of [..._catalogListeners]) {
361
403
  try { listener(); } catch { /* a listener's failure is its own */ }
362
404
  }
405
+ scheduleCatalogReload();
406
+ }
407
+
408
+ // In a page: the new lists are read right away, so the next menu opens from them (an open that comes
409
+ // first joins this read).
410
+ let _catalogReloadTimer = null;
411
+ function scheduleCatalogReload() {
412
+ if (typeof document === 'undefined' || _catalogReloadTimer) return;
413
+ _catalogReloadTimer = setTimeout(() => { _catalogReloadTimer = null; loadModelCatalog().catch(() => {}); }, 250);
363
414
  }
364
415
 
365
416
  /**
@@ -391,6 +442,12 @@ on('sync:opencode:hidden-models-changed', modelListsChanged);
391
442
  on('sync:assistant:hidden-models-changed', modelListsChanged);
392
443
  on('sync:assistant:catalog-changed', modelListsChanged);
393
444
 
445
+ // Launch: the menus' lists are read once the page has painted, so the first model menu opens from them.
446
+ if (typeof document !== 'undefined') {
447
+ registerLaunchPrefetch('assistant-model-catalog', () => loadModelCatalog(), { order: 10, owner: true });
448
+ registerLaunchPrefetch('assistant-accounts', () => Promise.allSettled([fetchClaudeAccounts(), fetchCodexAccounts(), fetchMcpProfilePresets(), fetchOpencodeAgents()]), { order: 14, owner: true });
449
+ }
450
+
394
451
  function sameModel(selected, m) {
395
452
  if (!selected) return false;
396
453
  return selected === m.id || selected === m.selector;
@@ -416,7 +473,8 @@ function tierTag(m, tr) {
416
473
  * opts: { brain, onSelect(target), includeHere, hereLabel, title, t, placement,
417
474
  * current:{provider, model}|null, warnSwitch, providers, onToast(text),
418
475
  * accept(provider, model) → bool (only those models), emptyLabel (when none
419
- * passes), allowCustom (default true), zIndex (see asst-menu.js) }
476
+ * passes), allowCustom (default true), zIndex (see asst-menu.js),
477
+ * accountItems (menu items listed first, under accountTitle) }
420
478
  * target: { provider, model, label, tier, vision, outputs, efforts, price } | { here: true }
421
479
  */
422
480
  export async function openModelMenu(anchor, opts = {}) {
@@ -428,74 +486,28 @@ export async function openModelMenu(anchor, opts = {}) {
428
486
  if (!anchor) return null;
429
487
  const tr = tf(t);
430
488
  const b = normalizeBrain(brain);
431
- anchor.setAttribute('aria-busy', 'true');
432
- // Open immediately: a slow provider still has a visible, cancellable state.
433
- const loading = opts.routingEditor ? openMenu(anchor, {
434
- title, className: 'asst-model-menu asst-route-model-menu',
435
- items: [{ kind: 'info', label: tr('assistant.routes.editorState.loadingModels', 'Loading models…') }],
436
- width: 380, zIndex: opts.zIndex ?? null, repositionOnResize: true,
437
- }) : null;
438
- if (loading) loading.el.setAttribute('aria-busy', 'true');
439
- if (loading) { loading.el.setAttribute('role', 'dialog'); anchor.setAttribute('aria-haspopup', 'dialog'); }
489
+ // The kept catalog paints the menu at once (it is read again in the background below, and an open menu
490
+ // follows it in place); only a first open, or a reload the user asked for, waits for the lists.
491
+ const kept = opts.forceReload === true ? null : cachedModelCatalog();
440
492
  let catalog = {};
441
493
  let failed = false;
442
- try { catalog = await loadModelCatalog({ force: opts.forceReload === true }); } catch { failed = true; } finally { anchor.removeAttribute('aria-busy'); }
443
- if (!anchor.isConnected || (loading && currentMenu() !== loading.el)) return null;
444
- const metadata = modelCatalogStatus();
445
- const items = [];
446
- if (Array.isArray(opts.accountItems) && opts.accountItems.length) {
447
- items.push({ kind: 'header', label: tr('assistant.brain.account', 'Account profile') }, ...opts.accountItems, { kind: 'separator' });
448
- }
449
- if (includeHere) {
450
- items.push({ kind: 'radio', id: '__here', label: hereLabel || tr('assistant.routes.brainDecides', 'Let the brain decide'), selected: !current, onSelect: () => onSelect?.({ here: true }) });
451
- }
452
- const list = Array.isArray(providers) && providers.length ? PROVIDERS.filter(p => providers.includes(p)) : PROVIDERS;
453
- let listed = 0;
454
- for (const provider of list) {
455
- const models = (catalog[provider] || []).filter(m => typeof accept !== 'function' || accept(provider, m));
456
- if (!models.length && !opts.routingEditor) continue;
457
- listed += models.length;
458
- const meta = getProviderMeta(provider);
459
- const hiddenCount = metadata.hiddenCounts[provider] || 0;
460
- const note = opts.routingEditor ? tr('assistant.routes.menu.hiddenCount', '{count} hidden in Models', { count: hiddenCount }) : warnSwitch && provider !== b.provider ? tr('assistant.brain.newConversation', 'starts a new conversation') : '';
461
- items.push({ kind: 'header', label: meta.label, icon: meta.icon, color: meta.color, note });
462
- for (const m of models) {
463
- items.push({
464
- kind: 'radio',
465
- id: `${provider}:${m.id}`,
466
- label: m.label,
467
- desc: modelDesc(m, tr, opts.routingEditor),
468
- title: `${m.label || m.id} · ${m.id}`,
469
- tag: tierTag(m, tr),
470
- keywords: `${meta.label} ${m.id}`,
471
- selected: !!current && current.provider === provider && sameModel(current.model, m),
472
- onSelect: () => onSelect?.({ provider, model: m.id, label: m.label, tier: m.tier || null, vision: m.vision ?? null, outputs: m.outputs || null, efforts: m.efforts || null, price: m.price || null }),
473
- });
474
- }
475
- if (opts.routingEditor && !models.length) items.push({ kind: 'info', label: typeof accept === 'function' ? tr('assistant.routes.menu.noCompatible', 'No compatible models shown for this provider.') : tr('assistant.routes.menu.providerEmpty', 'No models shown for this provider.') });
476
- }
477
- // Nothing passes the filter (e.g. no model makes video yet): say so instead of an empty list.
478
- if (!listed && typeof accept === 'function' && emptyLabel) items.push({ kind: 'info', label: emptyLabel });
479
- if (opts.routingEditor && !listed && typeof accept !== 'function') items.push({ kind: 'info', label: tr('assistant.routes.editorState.modelsEmpty', 'No models are available. Check Models or connect a provider, then reload.') });
480
- if (allowCustom) {
481
- items.push({ kind: 'separator' });
482
- items.push({
483
- kind: 'input',
484
- placeholder: tr('assistant.brain.modelCustom', 'Custom model id…'),
485
- onSubmit: (v) => {
486
- const provider = current?.provider || b.provider;
487
- const disabled = hiddenModelFor(provider, v);
488
- if (disabled) {
489
- onToast?.(tr('assistant.brain.modelDisabled', "{model} is disabled in the Assistant's Models list.", { model: disabled }));
490
- return;
491
- }
492
- onSelect?.({ provider, model: v, label: modelShortName(v), custom: true });
493
- },
494
- });
495
- }
496
- if (!opts.routingEditor && typeof opts.onManageModels === 'function') {
497
- items.push({ id: '__manage_models', label: tr('assistant.models.manage', 'Manage models…'), onSelect: () => opts.onManageModels() });
494
+ let loading = null;
495
+ if (kept) {
496
+ catalog = kept;
497
+ } else {
498
+ anchor.setAttribute('aria-busy', 'true');
499
+ // Open immediately: a slow provider still has a visible, cancellable state.
500
+ loading = opts.routingEditor ? openMenu(anchor, {
501
+ title, className: 'asst-model-menu asst-route-model-menu',
502
+ items: [{ kind: 'info', label: tr('assistant.routes.editorState.loadingModels', 'Loading models…') }],
503
+ width: 380, zIndex: opts.zIndex ?? null, repositionOnResize: true,
504
+ }) : null;
505
+ if (loading) loading.el.setAttribute('aria-busy', 'true');
506
+ if (loading) { loading.el.setAttribute('role', 'dialog'); anchor.setAttribute('aria-haspopup', 'dialog'); }
507
+ try { catalog = await loadModelCatalog({ force: opts.forceReload === true }); } catch { failed = true; } finally { anchor.removeAttribute('aria-busy'); }
508
+ if (!anchor.isConnected || (loading && currentMenu() !== loading.el)) return null;
498
509
  }
510
+ const items = buildModelMenuItems(catalog);
499
511
  const menu = openMenu(anchor, {
500
512
  title: title || tr('assistant.brain.model', 'Model'),
501
513
  items,
@@ -509,7 +521,79 @@ export async function openModelMenu(anchor, opts = {}) {
509
521
  repositionOnResize: true,
510
522
  filterEmptyLabel: tr('assistant.routes.menu.noMatches', 'No matching models. Check Models for hidden choices.'),
511
523
  });
512
- if (opts.routingEditor && menu) {
524
+ if (opts.routingEditor && menu) decorateRouteMenu(menu, failed);
525
+ // Painted from the kept catalog: read it again (fresh within its minute, else a background request) and
526
+ // redraw the open menu in place when the lists changed, leaving the filter, the scroll and the focus alone.
527
+ if (kept && menu && !opts.routingEditor) {
528
+ loadModelCatalog().then((next) => {
529
+ if (!next || next === kept || currentMenu() !== menu.el || !anchor.isConnected) return;
530
+ const nextItems = buildModelMenuItems(next);
531
+ if (menuItemsSignature(nextItems) !== menuItemsSignature(items)) menu.update({ items: nextItems, keepPlace: true });
532
+ }).catch(() => { /* the kept list stays */ });
533
+ }
534
+ return menu;
535
+
536
+ function buildModelMenuItems(catalog) {
537
+ const metadata = modelCatalogStatus();
538
+ const items = [];
539
+ if (Array.isArray(opts.accountItems) && opts.accountItems.length) {
540
+ items.push({ kind: 'header', label: opts.accountTitle || tr('assistant.brain.account', 'Account profile') }, ...opts.accountItems, { kind: 'separator' });
541
+ }
542
+ if (includeHere) {
543
+ items.push({ kind: 'radio', id: '__here', label: hereLabel || tr('assistant.routes.brainDecides', 'Let the brain decide'), selected: !current, onSelect: () => onSelect?.({ here: true }) });
544
+ }
545
+ const list = Array.isArray(providers) && providers.length ? PROVIDERS.filter(p => providers.includes(p)) : PROVIDERS;
546
+ let listed = 0;
547
+ for (const provider of list) {
548
+ const models = (catalog[provider] || []).filter(m => typeof accept !== 'function' || accept(provider, m));
549
+ if (!models.length && !opts.routingEditor) continue;
550
+ listed += models.length;
551
+ const meta = getProviderMeta(provider);
552
+ const hiddenCount = metadata.hiddenCounts[provider] || 0;
553
+ const note = opts.routingEditor ? tr('assistant.routes.menu.hiddenCount', '{count} hidden in Models', { count: hiddenCount }) : warnSwitch && provider !== b.provider ? tr('assistant.brain.newConversation', 'starts a new conversation') : '';
554
+ items.push({ kind: 'header', label: meta.label, icon: meta.icon, color: meta.color, note });
555
+ for (const m of models) {
556
+ items.push({
557
+ kind: 'radio',
558
+ id: `${provider}:${m.id}`,
559
+ label: m.label,
560
+ desc: modelDesc(m, tr, opts.routingEditor),
561
+ title: `${m.label || m.id} · ${m.id}`,
562
+ tag: tierTag(m, tr),
563
+ keywords: `${meta.label} ${m.id}`,
564
+ selected: !!current && current.provider === provider && sameModel(current.model, m),
565
+ onSelect: () => onSelect?.({ provider, model: m.id, label: m.label, tier: m.tier || null, vision: m.vision ?? null, outputs: m.outputs || null, efforts: m.efforts || null, price: m.price || null }),
566
+ });
567
+ }
568
+ if (opts.routingEditor && !models.length) items.push({ kind: 'info', label: typeof accept === 'function' ? tr('assistant.routes.menu.noCompatible', 'No compatible models shown for this provider.') : tr('assistant.routes.menu.providerEmpty', 'No models shown for this provider.') });
569
+ }
570
+ // Nothing passes the filter (e.g. no model makes video yet): say so instead of an empty list.
571
+ if (!listed && typeof accept === 'function' && emptyLabel) items.push({ kind: 'info', label: emptyLabel });
572
+ if (opts.routingEditor && !listed && typeof accept !== 'function') items.push({ kind: 'info', label: tr('assistant.routes.editorState.modelsEmpty', 'No models are available. Check Models or connect a provider, then reload.') });
573
+ if (allowCustom) {
574
+ items.push({ kind: 'separator' });
575
+ items.push({
576
+ kind: 'input',
577
+ placeholder: tr('assistant.brain.modelCustom', 'Custom model id…'),
578
+ onSubmit: (v) => {
579
+ const provider = current?.provider || b.provider;
580
+ const disabled = hiddenModelFor(provider, v);
581
+ if (disabled) {
582
+ onToast?.(tr('assistant.brain.modelDisabled', "{model} is disabled in the Assistant's Models list.", { model: disabled }));
583
+ return;
584
+ }
585
+ onSelect?.({ provider, model: v, label: modelShortName(v), custom: true });
586
+ },
587
+ });
588
+ }
589
+ if (!opts.routingEditor && typeof opts.onManageModels === 'function') {
590
+ items.push({ id: '__manage_models', label: tr('assistant.models.manage', 'Manage models…'), onSelect: () => opts.onManageModels() });
591
+ }
592
+ return items;
593
+ }
594
+
595
+ function decorateRouteMenu(menu, failed) {
596
+ const metadata = modelCatalogStatus();
513
597
  // Search and a custom-id input belong to a dialog; choices are a radio group.
514
598
  // A menu role cannot contain these form fields or recovery buttons.
515
599
  menu.el.setAttribute('role', 'dialog');
@@ -539,7 +623,11 @@ export async function openModelMenu(anchor, opts = {}) {
539
623
  }
540
624
  menu.el.append(footer); menu.reposition();
541
625
  }
542
- return menu;
626
+ }
627
+
628
+ /** What a model menu lists, for telling a fresher read from the one on screen. */
629
+ function menuItemsSignature(items) {
630
+ return JSON.stringify((items || []).map((item) => [item.kind || '', item.id ?? '', item.label || '', item.desc || '', item.tag || '', item.note || '', !!item.selected]));
543
631
  }
544
632
 
545
633
  // ── Picker ──────────────────────────────────────────────────────────────────
@@ -642,11 +730,39 @@ export function createBrainPicker(host, hooks = {}) {
642
730
  return name;
643
731
  }
644
732
 
733
+ // The "⋯" item's value, in the account menu's words: the name and its plan, "API key" or "Signed out" ("Default · Max").
734
+ function accountValue(brain) {
735
+ const acc = accountOf(brain);
736
+ if (!acc) return brain.accountId === 'auto' ? autoAccountLabel(hooks.t) : (brain.accountId || t('assistant.brain.accountDefault', 'Default'));
737
+ const row = accountMenuRow(acc, { provider: accountProvider(brain), t: hooks.t });
738
+ return [row.name, row.badge.text].filter(Boolean).join(' · ');
739
+ }
740
+
741
+ // The model menu's own row first: it also names the "[1m]" / "[extended]" variants the provider list lacks.
742
+ function modelRow(brain) {
743
+ return catalogModelRow(brain.provider, brain.model) || modelOf(brain);
744
+ }
745
+
645
746
  function modelLabel(brain = currentBrain()) {
646
- const model = modelOf(brain);
747
+ const model = modelRow(brain);
647
748
  return model ? (model.label || modelShortName(model.id)) : (brain.model ? modelShortName(brain.model) : t('assistant.brain.modelDefault', 'Default model'));
648
749
  }
649
750
 
751
+ // The composer chip's account cue: the account menu's initial avatar when the provider has more than one
752
+ // account (or the account is signed out), "Auto" for Auto; null otherwise. Its full words are the tooltip's.
753
+ function accountCue(brain) {
754
+ if (!ACCOUNT_PROVIDERS.has(brain.provider)) return null;
755
+ if (brain.accountId === 'auto') return { text: t('assistant.brain.accountAutoShort', 'Auto'), tone: 'muted' };
756
+ const accounts = brain.provider === 'codex' ? getCachedCodexAccounts() : getCachedClaudeAccounts();
757
+ const acc = accountOf(brain);
758
+ const signedOut = acc?.signedIn === false;
759
+ if (accounts.length < 2 && !signedOut) return null;
760
+ const initial = acc
761
+ ? accountMenuRow(acc, { provider: accountProvider(brain), t: hooks.t }).initial
762
+ : Array.from(String(brain.accountId || t('assistant.brain.accountDefault', 'Default')).trim())[0]?.toLocaleUpperCase();
763
+ return { text: initial || '?', tone: signedOut ? 'warn' : 'muted' };
764
+ }
765
+
650
766
  function values(brain = currentBrain()) {
651
767
  const meta = getProviderMeta(brain.provider);
652
768
  const efforts = effortsFor(brain);
@@ -669,6 +785,26 @@ export function createBrainPicker(host, hooks = {}) {
669
785
  };
670
786
  }
671
787
 
788
+ /**
789
+ * The permission menu's Admin sandbox row (a check: the global setting, every agent SynaBun starts),
790
+ * or null while the host has none to show (a server that does not serve it). Its tooltip and
791
+ * aria-label say the value in effect.
792
+ */
793
+ function adminSandboxItem() {
794
+ const on = typeof hooks.getAdminSandbox === 'function' ? hooks.getAdminSandbox() : null;
795
+ if (typeof on !== 'boolean') return null;
796
+ const label = t('assistant.adminSandbox.label', 'Admin sandbox');
797
+ const value = on ? t('assistant.adminSandbox.on', 'On') : t('assistant.adminSandbox.off', 'Off');
798
+ const hint = t('assistant.adminSandbox.hint', ADMIN_SANDBOX_HINT);
799
+ return {
800
+ kind: 'check', id: 'admin-sandbox', label, desc: hint, checked: on,
801
+ className: 'asst-admin-sandbox-item',
802
+ title: `${label}: ${value}. ${hint}`,
803
+ ariaLabel: t('assistant.adminSandbox.aria', '{label}: {value}. Only changes anything when SynaBun runs on Windows.', { label, value }),
804
+ onSelect: () => hooks.onAdminSandbox?.(!on),
805
+ };
806
+ }
807
+
672
808
  /** The session's Test density, or null while the host has none to show (a server that does not know it). */
673
809
  function testDensity() {
674
810
  const level = typeof hooks.getTestDensity === 'function' ? hooks.getTestDensity() : null;
@@ -689,21 +825,117 @@ export function createBrainPicker(host, hooks = {}) {
689
825
  btn.setAttribute('aria-label', aria || tooltip || label || field);
690
826
  }
691
827
 
828
+ // The composer's model chip (asst-model-chip.js): the model's short name, a context tag for a variant,
829
+ // the account cue and "API" on an API key, never the credential's name (the provider icon says it).
830
+ // Model label, id, account, who it is and what it runs on are the tooltip and aria-label.
831
+ // fitModelChip() draws the longest form that fits the row.
832
+ let chipForms = [];
833
+ let chipParts = null;
834
+ let chipCatalog = null;
835
+ function renderModelChip(brain, v) {
836
+ const btn = buttons.model;
837
+ // The catalog was dropped (a model hidden, a provider's list changed): read it again, then the label.
838
+ if (!cachedModelCatalog() && !chipCatalog) {
839
+ chipCatalog = loadModelCatalog().catch(() => null).then(() => {
840
+ chipCatalog = null;
841
+ if (!destroyed && cachedModelCatalog()) render();
842
+ });
843
+ }
844
+ const row = modelRow(brain);
845
+ const id = brain.model || row?.id || '';
846
+ const { name, raw } = id ? modelChipName(row?.label, id) : { name: v.model, raw: false };
847
+ const tag = modelChipTag(id, { contextWindow: row?.contextWindow, label: row?.label });
848
+ const cue = accountCue(brain);
849
+ const p = accountProvider(brain);
850
+ const acc = ACCOUNT_PROVIDERS.has(brain.provider) ? accountOf(brain) : null;
851
+ const api = acc && acc.signedIn !== false && acc.mode === 'apiKey' ? accountText('apiShort', {}, p) : '';
852
+ const names = (cachedModelCatalog()?.[brain.provider] || []).map(m => modelChipName(m.label, m.id).name);
853
+ chipForms = modelChipForms({ name, names, tag: !!tag, cue: !!cue, api: !!api });
854
+
855
+ const who = !ACCOUNT_PROVIDERS.has(brain.provider) ? ''
856
+ : brain.accountId === 'auto' ? autoAccountLabel(hooks.t)
857
+ : acc ? accountName(acc, p) : (brain.accountId || t('assistant.brain.accountDefault', 'Default'));
858
+ const model = brain.model && v.model !== brain.model ? `${v.model} · ${brain.model}` : (brain.model || v.model);
859
+ const tip = [
860
+ v.meta.label, model, who ? t('assistant.brain.asAccount', 'as {account}', { account: who }) : '',
861
+ acc ? accountIdentity(acc) : '', acc ? accountBadge(acc, p).text : '',
862
+ ].filter(Boolean).join(' · ');
863
+ setBtn('model', { label: '', icon: v.meta.icon, color: v.meta.color, tooltip: tip, aria: `${t('assistant.brain.model', 'Model')}: ${tip}` });
864
+
865
+ const label = btn.querySelector('.asst-dd-label');
866
+ label.classList.add('asst-model-label');
867
+ chipParts = {
868
+ name: el('span', 'asst-model-name', name),
869
+ tag: el('span', 'asst-model-tag', tag),
870
+ cue: el('span', 'asst-acct-avatar asst-model-cue', cue?.text || ''),
871
+ api: el('span', 'asst-acct-badge asst-model-api', api),
872
+ };
873
+ if (cue) chipParts.cue.dataset.tone = cue.tone;
874
+ label.replaceChildren(chipParts.name, chipParts.tag, chipParts.cue, chipParts.api);
875
+ // A display name ("Sonnet 5") reads in sans; a raw id nothing names ("opus") stays mono.
876
+ btn.toggleAttribute('data-raw', raw);
877
+ fitModelChip();
878
+ }
879
+
880
+ function drawChipForm(form) {
881
+ chipParts.name.textContent = form.name;
882
+ chipParts.tag.hidden = !form.tag;
883
+ chipParts.cue.hidden = !form.cue;
884
+ chipParts.api.hidden = !form.api;
885
+ }
886
+
887
+ // The first form, longest first, that leaves the row as the shortest one does: no part cut, no other
888
+ // field narrower, the row no taller (it wraps below 320px). None does: the shortest, its name squeezed.
889
+ function fitModelChip() {
890
+ const btn = buttons.model;
891
+ if (destroyed || !btn || !chipParts || !chipForms.length) return;
892
+ btn.removeAttribute('data-squeezed');
893
+ const label = btn.querySelector('.asst-dd-label');
894
+ const slot = btn.parentElement;
895
+ // Nothing to measure (not on screen, no layout): the whole chip, until the row has a width.
896
+ if (chipForms.length === 1 || !isRendered(btn) || !slot || typeof label?.getBoundingClientRect !== 'function') { drawChipForm(chipForms[0]); return; }
897
+ const row = slot.parentElement || null;
898
+ const others = [...slot.querySelectorAll('.asst-dd-label')].filter(n => n !== label && n.getClientRects().length > 0);
899
+ const width = n => n.getBoundingClientRect().width;
900
+ const cut = () => {
901
+ const right = label.getBoundingClientRect().right + 0.5;
902
+ return Object.values(chipParts).some(n => !n.hidden && n.getBoundingClientRect().right > right);
903
+ };
904
+ const shortest = chipForms[chipForms.length - 1];
905
+ drawChipForm(shortest);
906
+ const base = { height: row?.offsetHeight || 0, widths: others.map(width) };
907
+ const fits = () => !cut()
908
+ && (row?.offsetHeight || 0) <= base.height + 0.5
909
+ && others.every((n, i) => width(n) >= base.widths[i] - 0.5)
910
+ && !(slot.scrollWidth > slot.clientWidth + 1);
911
+ for (const form of chipForms.slice(0, -1)) {
912
+ drawChipForm(form);
913
+ if (fits()) return;
914
+ }
915
+ drawChipForm(shortest);
916
+ btn.toggleAttribute('data-squeezed', cut());
917
+ }
918
+
919
+ // The row's width decides the form (a panel resized, a tab shown, the fields that collapse to glyphs).
920
+ const fitHost = buttons.model.parentElement?.parentElement || buttons.model.parentElement;
921
+ let fitFrame = 0;
922
+ let fitWidth = -1;
923
+ const scheduleFit = () => {
924
+ if (fitFrame || destroyed || typeof requestAnimationFrame !== 'function') return;
925
+ fitFrame = requestAnimationFrame(() => { fitFrame = 0; fitModelChip(); });
926
+ };
927
+ const fitObserver = typeof ResizeObserver === 'function' && fitHost ? new ResizeObserver((entries) => {
928
+ const w = Math.round(entries[entries.length - 1]?.contentRect?.width || 0);
929
+ if (w !== fitWidth) { fitWidth = w; scheduleFit(); }
930
+ }) : null;
931
+ fitObserver?.observe(fitHost);
932
+ if (typeof document !== 'undefined') document.fonts?.ready?.then(scheduleFit).catch(() => {});
933
+
692
934
  function render() {
693
935
  if (destroyed) return;
694
936
  const brain = currentBrain();
695
937
  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 || '')));
938
+ renderModelChip(brain, v);
707
939
 
708
940
  if (brain.provider === 'opencode') {
709
941
  setBtn('effort', { label: v.effort, icon: ICON_AGENT, tooltip: `${t('assistant.brain.agent', 'OpenCode agent')}: ${v.effort}` });
@@ -791,7 +1023,7 @@ export function createBrainPicker(host, hooks = {}) {
791
1023
  { field: 'mode', label: t('assistant.brain.mode', 'Permission mode'), value: v.mode, icon: ICON_SHIELD, hidden: false },
792
1024
  { field: 'tests', label: t('assistant.tests.title', 'Test density'), value: testDensity() ? testDensityLabel(testDensity(), hooks.t) : '', icon: testsIcon(testDensityBars(testDensity())), hidden: !testDensity() },
793
1025
  { 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) },
1026
+ { field: 'account', label: t('assistant.brain.accountShort', 'Account'), value: accountValue(brain), icon: ICON_ACCOUNT, hidden: !ACCOUNT_PROVIDERS.has(brain.provider) },
795
1027
  ];
796
1028
  }
797
1029
 
@@ -817,6 +1049,7 @@ export function createBrainPicker(host, hooks = {}) {
817
1049
  }).catch(() => { _projectsCache = _projectsCache || []; }));
818
1050
  }
819
1051
  await Promise.allSettled(jobs);
1052
+ if (cachedModelCatalog()) _warmProviders.add(provider);
820
1053
  }
821
1054
 
822
1055
  async function refresh(force = false) {
@@ -848,14 +1081,19 @@ export function createBrainPicker(host, hooks = {}) {
848
1081
  const anchor = anchorFor(field, anchorOverride);
849
1082
  const brain = currentBrain();
850
1083
  if (field === 'model') {
851
- if (ACCOUNT_PROVIDERS.has(brain.provider)) await ensureLoaded(brain.provider);
1084
+ // The account rows come from the kept lists; they are read again in the background once loaded.
1085
+ if (ACCOUNT_PROVIDERS.has(brain.provider)) {
1086
+ if (_warmProviders.has(brain.provider)) ensureLoaded(brain.provider).catch(() => {});
1087
+ else await ensureLoaded(brain.provider);
1088
+ }
852
1089
  await openModelMenu(anchor, {
853
1090
  brain,
854
1091
  t: hooks.t,
855
1092
  placement,
856
1093
  current: { provider: brain.provider, model: brain.model },
857
1094
  warnSwitch: true,
858
- accountItems: ACCOUNT_PROVIDERS.has(brain.provider) ? accountItems(brain, { composer: true }) : [],
1095
+ accountItems: ACCOUNT_PROVIDERS.has(brain.provider) ? accountItems(brain) : [],
1096
+ accountTitle: accountText('menuTitle', {}, accountProvider(brain)),
859
1097
  onToast: hooks.onToast,
860
1098
  onManageModels: hooks.onManageModels,
861
1099
  onSelect: (target) => {
@@ -882,12 +1120,48 @@ export function createBrainPicker(host, hooks = {}) {
882
1120
  });
883
1121
  return;
884
1122
  }
885
- // Everything else needs the provider catalog — load it lazily on first open.
886
- anchor.setAttribute('aria-busy', 'true');
887
- try { await ensureLoaded(brain.provider); } finally { anchor.removeAttribute('aria-busy'); }
888
- if (destroyed || !anchor.isConnected) return;
889
- const menu = (title, items, extra = {}) => openMenu(anchor, { title, items, placement, role: 'menu', ...extra });
1123
+ // The account menu reads the kept rows and asks again by itself (asst-account-menu.js).
1124
+ if (field === 'account') {
1125
+ if (!ACCOUNT_PROVIDERS.has(brain.provider)) return;
1126
+ if (!_warmProviders.has(brain.provider)) {
1127
+ anchor.setAttribute('aria-busy', 'true');
1128
+ try { await ensureLoaded(brain.provider); } finally { anchor.removeAttribute('aria-busy'); }
1129
+ if (destroyed || !anchor.isConnected) return;
1130
+ } else {
1131
+ ensureLoaded(brain.provider).catch(() => {});
1132
+ }
1133
+ // Kept inside the panel that holds the anchor (the sidepanel, or the tab's own root).
1134
+ openAccountMenu(anchor, accountMenu(brain, anchor.closest?.('.assistant-panel') || anchor.closest?.('.asst-root') || null));
1135
+ return;
1136
+ }
1137
+ // Everything else needs the provider catalog — load it lazily on first open. Once it is loaded the menu
1138
+ // opens from what is kept, the lists are read again in the background, and an open menu they change is
1139
+ // redrawn in place (the filter, the scroll and the focus stay).
1140
+ const warm = _warmProviders.has(brain.provider);
1141
+ if (!warm) {
1142
+ anchor.setAttribute('aria-busy', 'true');
1143
+ try { await ensureLoaded(brain.provider); } finally { anchor.removeAttribute('aria-busy'); }
1144
+ if (destroyed || !anchor.isConnected) return;
1145
+ }
1146
+ let shown = null;
1147
+ drawField(field, brain, (title, items, extra = {}) => {
1148
+ const opened = openMenu(anchor, { title, items, placement, role: 'menu', ...extra });
1149
+ shown = opened ? { opened, items } : null;
1150
+ return opened;
1151
+ }, { anchor, restoreFocus });
1152
+ if (warm && shown) {
1153
+ ensureLoaded(brain.provider).then(() => {
1154
+ if (destroyed || !shown || currentMenu() !== shown.opened.el || !anchor.isConnected) return;
1155
+ let fresh = null;
1156
+ drawField(field, currentBrain(), (_title, items) => { fresh = items; return null; }, { anchor, restoreFocus });
1157
+ if (fresh && menuItemsSignature(fresh) !== menuItemsSignature(shown.items)) shown.opened.update({ items: fresh, keepPlace: true });
1158
+ }).catch(() => {});
1159
+ }
1160
+ }
890
1161
 
1162
+ // The menu of one field, from the lists as they are kept now: `menu(title, items, extra)` opens it (or, for a
1163
+ // redraw, only takes the items).
1164
+ function drawField(field, brain, menu, { anchor, restoreFocus = false } = {}) {
891
1165
  if (field === 'effort') {
892
1166
  if (brain.provider === 'opencode') {
893
1167
  const agents = getCachedOpencodeAgents();
@@ -918,12 +1192,6 @@ export function createBrainPicker(host, hooks = {}) {
918
1192
  return;
919
1193
  }
920
1194
 
921
- if (field === 'account') {
922
- if (!ACCOUNT_PROVIDERS.has(brain.provider)) return;
923
- menu(t('assistant.brain.account', 'Account profile'), accountItems(brain), { width: 300 });
924
- return;
925
- }
926
-
927
1195
  if (field === 'project') {
928
1196
  const actor = anchor === heroProject ? heroProjectMascot : null;
929
1197
  const pick = project => {
@@ -975,70 +1243,74 @@ export function createBrainPicker(host, hooks = {}) {
975
1243
  onSelect: () => commit({ permissionMode: m.id }, 'mode'),
976
1244
  });
977
1245
  }
1246
+ // The global Admin sandbox switch (every agent SynaBun starts, not this conversation), under its own header.
1247
+ const adminSandbox = adminSandboxItem();
1248
+ if (adminSandbox) items.push({ kind: 'separator' }, { kind: 'header', label: t('assistant.adminSandbox.group', 'Every agent') }, adminSandbox);
978
1249
  menu(t('assistant.brain.mode', 'Permission mode'), items);
979
1250
  }
980
1251
  }
981
1252
 
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 } = {}) {
1253
+ // The cog's account menu (asst-account-menu.js): every account once, the conversation's and the
1254
+ // workers' choice, kept inside `bounds`. What each choice does is the same as the composer's.
1255
+ function accountMenu(brain, bounds) {
1256
+ const codex = brain.provider === 'codex';
1257
+ const refetch = () => (codex ? fetchCodexAccounts(true) : fetchClaudeAccounts(true));
1258
+ return {
1259
+ provider: codex ? 'codex' : 'claude',
1260
+ t: hooks.t,
1261
+ placement,
1262
+ bounds,
1263
+ read: () => ({
1264
+ accounts: codex ? getCachedCodexAccounts() : getCachedClaudeAccounts(),
1265
+ currentId: currentBrain().accountId,
1266
+ workerId: codex ? getCodexWorkerAccountId() : getClaudeWorkerAccountId(),
1267
+ running: hooks.getRunning?.() === true,
1268
+ }),
1269
+ onAuto: () => commit({ accountId: 'auto' }, 'account'),
1270
+ onPick: (id) => commit({ accountId: id }, 'account'),
1271
+ onSetWorker: async (id) => { await (codex ? setCodexWorkerAccount : setClaudeWorkerAccount)(id); await refetch(); render(); },
1272
+ onSetMode: async (id, mode) => {
1273
+ const before = currentBrain();
1274
+ try {
1275
+ await (codex ? patchCodexAccount : patchClaudeAccount)(id, { mode }); await refetch();
1276
+ hooks.onChange?.(currentBrain(), { requiresSwitch: true, restartBrain: true, field: 'accountMode', previous: before }); render();
1277
+ }
1278
+ catch (err) { hooks.onToast?.(err?.message || String(err)); }
1279
+ },
1280
+ onAdd: () => hooks.onAddAccount?.(brain.provider),
1281
+ onManage: (id) => (id ? hooks.onManageAccounts?.(brain.provider, id) : hooks.onManageAccounts?.(brain.provider)),
1282
+ onError: (err) => hooks.onToast?.(err?.message || String(err)),
1283
+ };
1284
+ }
1285
+
1286
+ // The composer's Model menu lists the brain's accounts too (Claude or Codex), as the cog's account menu
1287
+ // does (asst-account-menu.js): Auto, every account once (a signed-out one opens its sign-in), the Uses
1288
+ // switch of an account holding both credentials, Add account… and Manage….
1289
+ function accountItems(brain) {
986
1290
  const codex = brain.provider === 'codex';
987
1291
  const p = codex ? 'codex' : 'claude';
988
1292
  const accounts = codex ? getCachedCodexAccounts() : getCachedClaudeAccounts();
989
1293
  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
- };
1294
+ const items = accountChoiceItems({
1295
+ t: hooks.t,
1296
+ rows: accounts.map((a) => accountMenuRow(a, { provider: p, t: hooks.t })),
1297
+ currentId: brain.accountId,
1298
+ onAuto: () => commit({ accountId: 'auto' }, 'account'),
1299
+ onPick: (id) => commit({ accountId: id }, 'account'),
1300
+ onSignIn: (id) => hooks.onManageAccounts?.(brain.provider, id),
1000
1301
  });
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) });
1302
+ const uses = accountModeItem({
1303
+ provider: p, t: hooks.t, account: accounts.find((a) => a.id === brain.accountId), running: hooks.getRunning?.() === true,
1304
+ onSetMode: async (id, mode) => {
1305
+ try {
1306
+ await (codex ? patchCodexAccount : patchClaudeAccount)(id, { mode }); await refetch();
1307
+ hooks.onChange?.(currentBrain(), { requiresSwitch: true, restartBrain: true, field: 'accountMode', previous: brain }); render();
1308
+ }
1309
+ catch (err) { hooks.onToast?.(err?.message || String(err)); }
1310
+ },
1311
+ });
1312
+ if (uses) items.push(uses);
1313
+ items.push(accountManageItem({ t: hooks.t, onAdd: () => hooks.onAddAccount?.(brain.provider), onManage: () => hooks.onManageAccounts?.(brain.provider) }));
1042
1314
  return items;
1043
1315
  }
1044
1316
 
@@ -1088,6 +1360,6 @@ export function createBrainPicker(host, hooks = {}) {
1088
1360
  mountEmptyProject,
1089
1361
  setBrain() { render(); },
1090
1362
  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(); },
1363
+ 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
1364
  };
1093
1365
  }