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
@@ -4,7 +4,8 @@ import { claudeAuthEnv } from './claude-panel-auth.js';
4
4
  import { applyCodexLaunchEnv } from './codex-accounts.js';
5
5
 
6
6
  // Only server-owned account records and resolved executables enter the PTY.
7
- export function providerLoginSpec({ provider, accountId, claudeAccounts, codexAccount, claudeExecutable, codexExecutable, env }) {
7
+ // `codexConfigArgs`: the `-c` words every Codex SynaBun starts gets (the Admin sandbox setting's Windows sandbox).
8
+ export function providerLoginSpec({ provider, accountId, claudeAccounts, codexAccount, claudeExecutable, codexExecutable, env, codexConfigArgs = [] }) {
8
9
  if (!['claude', 'codex'].includes(provider) || typeof accountId !== 'string' || !/^[a-z0-9-]{1,64}$/.test(accountId)) {
9
10
  throw Object.assign(new Error('Unknown provider account'), { status: 404 });
10
11
  }
@@ -24,7 +25,7 @@ export function providerLoginSpec({ provider, accountId, claudeAccounts, codexAc
24
25
  if (!account.home) throw new Error('This Claude account has no isolated home.');
25
26
  baseEnv = claudeAuthEnv(baseEnv, { CLAUDE_CONFIG_DIR: account.home });
26
27
  }
27
- const args = provider === 'claude' ? ['auth', 'login', '--claudeai'] : ['login'];
28
+ const args = provider === 'claude' ? ['auth', 'login', '--claudeai'] : ['login', ...codexConfigArgs];
28
29
  return { launch: /\.[cm]?js$/i.test(file) ? { shell: process.execPath, args: [file, ...args] } : { shell: file, args }, baseEnv };
29
30
  }
30
31
 
@@ -227,9 +227,10 @@ export async function startIndexing(options = {}) {
227
227
  * Uses vectors already stored in SQLite — no re-embedding needed.
228
228
  * Skips chunks that already exist in memories (same UUID) or have dedup_memory_id.
229
229
  * @param {function} [onProgress] - Progress callback
230
+ * @param {{ ensureCategory?: () => void }} [options] - ensureCategory creates "conversations" before the first write (a fresh install has none)
230
231
  * @returns {Promise<{ mirrored: number, skipped: number, errors: number }>}
231
232
  */
232
- export async function mirrorExistingChunks(onProgress) {
233
+ export async function mirrorExistingChunks(onProgress, { ensureCategory = null } = {}) {
233
234
  const emit = onProgress || (() => {});
234
235
  const d = getDb();
235
236
 
@@ -296,6 +297,7 @@ export async function mirrorExistingChunks(onProgress) {
296
297
  // Batch upsert to memories
297
298
  if (mirrorPoints.length > 0) {
298
299
  try {
300
+ if (typeof ensureCategory === 'function') ensureCategory();
299
301
  for (let i = 0; i < mirrorPoints.length; i += UPSERT_BATCH_SIZE) {
300
302
  const batch = mirrorPoints.slice(i, i + UPSERT_BATCH_SIZE);
301
303
  upsertMemories(batch);
@@ -118,6 +118,8 @@ async function generateCodexTitle(input, deps) {
118
118
  const args = [
119
119
  'exec', '--ephemeral', '--skip-git-repo-check', '--ignore-rules',
120
120
  '--sandbox', 'read-only', '--color', 'never', '--json',
121
+ // The Windows sandbox the Admin sandbox setting allows (lib/agent-sandbox-elevation.js).
122
+ ...(typeof deps.codexConfigArgs === 'function' ? deps.codexConfigArgs() : []),
121
123
  ];
122
124
  if (input.cwd) args.push('-C', input.cwd);
123
125
  if (input.model) args.push('-m', input.model);
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "neural-memory-interface",
3
- "version": "2.0.7",
3
+ "version": "2.0.9",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "neural-memory-interface",
9
- "version": "2.0.7",
9
+ "version": "2.0.9",
10
10
  "hasInstallScript": true,
11
11
  "license": "Apache-2.0",
12
12
  "dependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neural-memory-interface",
3
- "version": "2.0.7",
3
+ "version": "2.0.9",
4
4
  "private": true,
5
5
  "description": "Private local SynaBun interface for memory visualization and multi-agent workflows",
6
6
  "license": "Apache-2.0",
@@ -332,7 +332,10 @@ export function mountAccountMenu({ provider = 'claude', container, before = null
332
332
 
333
333
  function place() {
334
334
  if (!open) return;
335
- const rect = trigger.getBoundingClientRect();
335
+ let rect = trigger.getBoundingClientRect();
336
+ // A header too narrow for the trigger hides it (the panels' container queries); the menu,
337
+ // opened from the cog's Account row, then hangs from the row the trigger sits in.
338
+ if (!rect.width && !rect.height && trigger.parentElement) rect = trigger.parentElement.getBoundingClientRect();
336
339
  const owner = layer?.() || trigger.closest('[class*="panel"]');
337
340
  const ownerWidth = owner?.getBoundingClientRect?.().width;
338
341
  popup.style.width = `${Math.max(0, Math.min(320, ownerWidth > 240 ? ownerWidth - 16 : 320, window.innerWidth - 16))}px`;
@@ -1,9 +1,12 @@
1
1
  // Text only: keep the account menus in their existing component/token scope.
2
- export function accountUsageText(row, t) {
3
- const tr = (key, fallback, params = {}) => {
2
+ function translator(t) {
3
+ return (key, fallback, params = {}) => {
4
4
  const value = t?.(`assistant.accountUsage.${key}`, params);
5
5
  return value && value !== `assistant.accountUsage.${key}` ? value : fallback.replace(/\{(\w+)\}/g, (_, k) => params[k] ?? '');
6
6
  };
7
+ }
8
+ export function accountUsageText(row, t) {
9
+ const tr = translator(t);
7
10
  if (row?.mode === 'apiKey' || row?.mode === 'api') return tr('notApplicable', 'Usage n/a');
8
11
  const u = row?.usage;
9
12
  const reset = u?.resetsAt ? new Date(u.resetsAt) : null;
@@ -17,3 +20,60 @@ export function autoAccountLabel(t) {
17
20
  const value = t?.(key);
18
21
  return value && value !== key ? value : 'Auto · most usage left';
19
22
  }
23
+
24
+ function pageLocale() {
25
+ try { return globalThis.document?.documentElement?.lang || undefined; } catch { return undefined; }
26
+ }
27
+ function dateFormat(locale, options) {
28
+ try { return new Intl.DateTimeFormat(locale, options); } catch { return new Intl.DateTimeFormat(undefined, options); }
29
+ }
30
+ const dayStart = (ms) => { const day = new Date(ms); day.setHours(0, 0, 0, 0); return day.getTime(); };
31
+ const bare = (text) => String(text).replace(/\.$/, '');
32
+
33
+ /**
34
+ * When an account's usage comes back, short enough for one menu line in the page's language:
35
+ * "15:59" today, "Thu 15:59" within the week, "Oct 19 15:59" / "19 out 15:59" later; '' without a
36
+ * valid date. `long` is the full form for a tooltip ("Thursday, October 22 at 15:59").
37
+ */
38
+ export function resetTimeText(value, { now = Date.now(), locale = pageLocale(), long = false } = {}) {
39
+ const at = value ? new Date(value) : null;
40
+ if (!at || !Number.isFinite(at.getTime())) return '';
41
+ if (long) return dateFormat(locale, { weekday: 'long', day: 'numeric', month: 'long', hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }).format(at);
42
+ const time = dateFormat(locale, { hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }).format(at);
43
+ const days = Math.round((dayStart(at.getTime()) - dayStart(now)) / 86_400_000);
44
+ if (days === 0) return time;
45
+ if (days > 0 && days < 7) return `${bare(dateFormat(locale, { weekday: 'short' }).format(at))} ${time}`;
46
+ const date = dateFormat(locale, { day: 'numeric', month: 'short' }).formatToParts(at)
47
+ .filter((part) => part.type === 'day' || part.type === 'month').map((part) => bare(part.value)).join(' ');
48
+ return `${date} ${time}`;
49
+ }
50
+
51
+ /**
52
+ * An account's usage as one short line of a menu row: { text, tone: 'muted' | 'warn', title }
53
+ * ("50% left · resets Thu 15:59", "Limited until 23:10", "Usage unknown"), the title with the full
54
+ * reset date. null where usage does not apply: an API key, a signed-out or unchecked account.
55
+ */
56
+ export function accountUsageLine(row, t, { now = Date.now(), locale = pageLocale() } = {}) {
57
+ if (!row || row.signedIn !== true || row.mode !== 'subscription') return null;
58
+ const tr = translator(t);
59
+ const u = row.usage;
60
+ const short = resetTimeText(u?.resetsAt, { now, locale });
61
+ const full = resetTimeText(u?.resetsAt, { now, locale, long: true });
62
+ if (u?.limited) {
63
+ return {
64
+ text: short ? tr('limitedUntil', 'Limited until {time}', { time: short }) : tr('limited', 'Limited'),
65
+ tone: 'warn',
66
+ title: full ? tr('limitedUntil', 'Limited until {time}', { time: full }) : tr('limited', 'Limited'),
67
+ };
68
+ }
69
+ if (typeof u?.remainingPercent !== 'number' || !Number.isFinite(u.remainingPercent)) {
70
+ const text = tr('unknown', 'Usage unknown');
71
+ return { text, tone: 'muted', title: text };
72
+ }
73
+ const left = tr('left', '{percent}% left', { percent: Math.round(Math.max(0, Math.min(100, u.remainingPercent))) });
74
+ return {
75
+ text: [left, short ? tr('resets', 'resets {time}', { time: short }) : ''].filter(Boolean).join(' · '),
76
+ tone: 'muted',
77
+ title: [left, full ? tr('resets', 'resets {time}', { time: full }) : ''].filter(Boolean).join(' · '),
78
+ };
79
+ }
@@ -145,7 +145,8 @@ export function accountBadge(row, provider = null) {
145
145
  return { text: accountText('checking', {}, p), tone: 'muted', mode: row.mode || null };
146
146
  }
147
147
 
148
- function planName(plan) {
148
+ /** A subscription plan as its name ("max" → "Max"); '' for none. */
149
+ export function planName(plan) {
149
150
  const value = String(plan || '').trim();
150
151
  return value ? value.charAt(0).toUpperCase() + value.slice(1) : '';
151
152
  }
@@ -0,0 +1,298 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Assistant — account menu (⚙ → Account profile)
3
+ // ═══════════════════════════════════════════
4
+ // Which Claude or Codex account this conversation runs on and which one
5
+ // dispatched workers use, in one popover that stays inside the panel
6
+ // (asst-menu.js `bounds`; only the list scrolls, the footer stays). Every
7
+ // account is listed once: its name, who it is when that adds to the name, the
8
+ // plan or API key, the usage left or the limit, and when it comes back. Auto is
9
+ // one line (its sentence is the tooltip). "Workers use" opens the workers'
10
+ // choice in the same popover (Esc goes back). The words come from
11
+ // account-view.js and account-usage.js; what a choice does is the caller's
12
+ // (asst-brain-picker.js). accountMenuRow is DOM-free (node --test loads it).
13
+ // The composer's Model menu lists the same rows (accountChoiceItems,
14
+ // accountModeItem, accountManageItem), so both show an account the same way.
15
+
16
+ import { accountBadge, accountName, accountText, accountUsable, modeChoices, planName } from '../account-view.js';
17
+ import { accountUsageLine, autoAccountLabel } from '../account-usage.js';
18
+ import { openMenu } from './asst-menu.js';
19
+
20
+ const ICON_CHECK = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 8.5l3 3 6-7"/></svg>';
21
+ // Auto: the usage gauge (the same drawing as the brain picker's effort icon).
22
+ const ICON_AUTO = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 16a8 8 0 1 1 16 0"/><path d="m12 16 4-5"/></svg>';
23
+ const ICON_NEXT = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3.5 10.5 8 6 12.5"/></svg>';
24
+ const ICON_BACK = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3.5 5.5 8l4.5 4.5"/></svg>';
25
+ // As wide as this in a wide panel; a narrow panel gets its own width less the menu's margins.
26
+ const MENU_WIDTH = 340;
27
+
28
+ function tf(t) {
29
+ return (key, fallback, params) => {
30
+ const v = typeof t === 'function' ? t(key, params) : undefined;
31
+ if (v && v !== key && typeof v === 'string') return v;
32
+ return params ? String(fallback).replace(/\{(\w+)\}/g, (_, k) => (params[k] != null ? params[k] : `{${k}}`)) : fallback;
33
+ };
34
+ }
35
+
36
+ /** Who the account is, when that adds to its name: the subscription's email or the key's hint (never the name again). */
37
+ function identityOf(row, name) {
38
+ const raw = row?.mode === 'apiKey' ? (row.apiKey?.hint || row.identity) : (row?.subscription?.email || row?.identity);
39
+ const text = String(raw || '').trim();
40
+ return text && text.toLowerCase() !== String(name || '').trim().toLowerCase() ? text : '';
41
+ }
42
+
43
+ /**
44
+ * One account as the menu shows it: { id, name, initial, badge: { text, tone }, identity,
45
+ * line: { text, tone, title } | null, usable, signedOut }. The badge is what it runs on (the
46
+ * plan, "API key"), or that it is signed out; `line` is its usage, or how to sign it in.
47
+ */
48
+ export function accountMenuRow(row, { provider = 'claude', t = null, now = Date.now(), locale } = {}) {
49
+ const p = provider === 'codex' ? 'codex' : 'claude';
50
+ const name = accountName(row, p) || String(row?.id || '');
51
+ const signedOut = row?.signedIn === false;
52
+ let badge;
53
+ if (!signedOut && row?.mode === 'apiKey') badge = { text: accountText('apiKeyBadge', {}, p), tone: 'muted' };
54
+ else if (!signedOut && row?.mode === 'subscription' && row.signedIn === true) badge = { text: planName(row.subscription?.plan) || accountText('subscriptionShort', {}, p), tone: 'muted' };
55
+ else badge = accountBadge(row, p);
56
+ let line = accountUsageLine(row, t, { now, locale });
57
+ if (signedOut) {
58
+ const text = accountText(row?.apiKey?.error ? 'keyUnreadableLong' : 'signIn', {}, p);
59
+ line = { text, tone: 'muted', title: text };
60
+ }
61
+ return {
62
+ id: row?.id,
63
+ name,
64
+ initial: Array.from(name.trim())[0]?.toLocaleUpperCase() || '?',
65
+ badge: { text: badge.text || '', tone: badge.tone === 'warn' ? 'warn' : 'muted' },
66
+ identity: signedOut ? '' : identityOf(row, name),
67
+ line,
68
+ usable: accountUsable(row),
69
+ signedOut,
70
+ };
71
+ }
72
+
73
+ /** The full words of a row, for its tooltip and its accessible name. */
74
+ function rowWords(r, extra = []) {
75
+ return [r.name, r.identity, r.badge.text, r.line?.title || r.line?.text, ...extra].filter(Boolean).join(' · ');
76
+ }
77
+
78
+ function node(tag, className, text) {
79
+ const n = document.createElement(tag);
80
+ if (className) n.className = className;
81
+ if (text != null) n.textContent = text;
82
+ return n;
83
+ }
84
+ function icon(className, svg) {
85
+ const n = node('span', className);
86
+ n.setAttribute('aria-hidden', 'true');
87
+ n.innerHTML = svg;
88
+ return n;
89
+ }
90
+
91
+ // avatar | name ✓ … badge / identity / usage
92
+ function drawAccount(btn, r, checked) {
93
+ btn.classList.add('asst-acct-item');
94
+ const avatar = node('span', 'asst-acct-avatar', r.initial);
95
+ avatar.setAttribute('aria-hidden', 'true');
96
+ const head = node('span', 'asst-acct-head');
97
+ head.append(node('span', 'asst-acct-name', r.name));
98
+ if (checked) head.append(icon('asst-acct-check', ICON_CHECK));
99
+ if (r.badge.text) { const badge = node('span', 'asst-acct-badge', r.badge.text); badge.dataset.tone = r.badge.tone; head.append(badge); }
100
+ const main = node('span', 'asst-acct-main');
101
+ main.append(head);
102
+ if (r.identity) main.append(node('span', 'asst-acct-line', r.identity));
103
+ if (r.line) { const line = node('span', 'asst-acct-line', r.line.text); line.dataset.tone = r.line.tone; main.append(line); }
104
+ btn.append(avatar, main);
105
+ }
106
+ function drawAuto(btn, label, checked) {
107
+ btn.classList.add('asst-acct-item', 'asst-acct-auto');
108
+ const head = node('span', 'asst-acct-head');
109
+ head.append(node('span', 'asst-acct-name', label));
110
+ if (checked) head.append(icon('asst-acct-check', ICON_CHECK));
111
+ const main = node('span', 'asst-acct-main');
112
+ main.append(head);
113
+ btn.append(icon('asst-acct-avatar', ICON_AUTO), main);
114
+ }
115
+ // label … value ›
116
+ function drawValue(btn, label, value, tone) {
117
+ btn.classList.add('asst-acct-value');
118
+ const text = node('span', 'asst-acct-value-text', value);
119
+ text.dataset.tone = tone;
120
+ btn.append(node('span', 'asst-acct-value-label', label), text, icon('asst-acct-next', ICON_NEXT));
121
+ }
122
+ function drawBack(btn, label) {
123
+ btn.classList.add('asst-acct-back');
124
+ btn.append(icon('asst-acct-next', ICON_BACK), node('span', 'asst-acct-value-label', label));
125
+ }
126
+ function drawChoice(btn, label, checked) {
127
+ btn.classList.add('asst-acct-choice');
128
+ if (checked) btn.append(icon('asst-acct-check', ICON_CHECK));
129
+ btn.append(node('span', '', label));
130
+ }
131
+
132
+ /**
133
+ * The account choices as menu items (this menu and the composer's Model menu): Auto, every account
134
+ * once, and the note that another account starts a new conversation. rows: accountMenuRow()s;
135
+ * onAuto(), onPick(id) (never for the current account), onSignIn(id) (a signed-out account is its sign-in).
136
+ */
137
+ export function accountChoiceItems({ t = null, rows = [], currentId = null, onAuto, onPick, onSignIn } = {}) {
138
+ const tr = tf(t);
139
+ const newConversation = tr('assistant.brain.newConversation', 'starts a new conversation');
140
+ const items = [{
141
+ kind: 'radio', id: 'auto', label: autoAccountLabel(t), selected: currentId === 'auto',
142
+ title: tr('assistant.accountUsage.composerHint', 'Pick the subscription with most usage left and start a new conversation.'),
143
+ render: (btn, { checked }) => drawAuto(btn, autoAccountLabel(t), checked),
144
+ onSelect: () => onAuto?.(),
145
+ }];
146
+ for (const r of rows) {
147
+ const current = r.id === currentId;
148
+ // A signed-out account is listed with its way back in: choosing it opens its sign-in.
149
+ const words = rowWords(r, [!current && !r.signedOut ? newConversation : '']);
150
+ items.push({
151
+ kind: current ? 'radio' : r.signedOut ? 'item' : 'radio', id: r.id, label: r.name, keywords: r.identity,
152
+ selected: current, title: words, ariaLabel: words,
153
+ render: (btn, { checked }) => drawAccount(btn, r, checked),
154
+ onSelect: () => (r.signedOut ? onSignIn?.(r.id) : current ? null : onPick?.(r.id)),
155
+ });
156
+ }
157
+ items.push({ kind: 'info', className: 'asst-acct-note', label: tr('assistant.accountMenu.switchNote', 'Another account starts a new conversation.') });
158
+ return items;
159
+ }
160
+
161
+ /**
162
+ * What an account holding both credentials uses, as one row of two choices ("Work uses Subscription |
163
+ * API key"); null for any other account. Waits while a turn runs. onSetMode(id, mode) for the other one.
164
+ */
165
+ export function accountModeItem({ provider = 'claude', t = null, account = null, running = false, onSetMode } = {}) {
166
+ const p = provider === 'codex' ? 'codex' : 'claude';
167
+ const modes = modeChoices(account, p);
168
+ if (!modes.length) return null;
169
+ const tr = tf(t);
170
+ const newConversation = tr('assistant.brain.newConversation', 'starts a new conversation');
171
+ const name = accountName(account, p);
172
+ return {
173
+ kind: 'buttons', id: 'uses', label: tr('assistant.accountMenu.uses', '{label} uses', { label: name }), ariaLabel: accountText('usesGroup', { label: name }, p),
174
+ buttons: modes.map((m) => {
175
+ const text = accountText(m.mode === 'apiKey' ? 'apiKeyBadge' : 'subscriptionShort', {}, p);
176
+ return {
177
+ kind: 'radio', id: `mode:${m.mode}`, label: text, selected: m.current, disabled: running,
178
+ title: [m.label, m.detail, !m.current ? newConversation : ''].filter(Boolean).join(' · '),
179
+ render: (btn, { checked }) => drawChoice(btn, text, checked),
180
+ onSelect: () => { if (!m.current) onSetMode?.(account.id, m.mode); },
181
+ };
182
+ }),
183
+ };
184
+ }
185
+
186
+ /** Add account… and Manage… side by side. */
187
+ export function accountManageItem({ t = null, onAdd, onManage } = {}) {
188
+ const tr = tf(t);
189
+ return {
190
+ kind: 'buttons', id: 'manage-row',
191
+ buttons: [
192
+ { id: '__add', label: tr('assistant.accounts.add', 'Add account…'), onSelect: () => onAdd?.() },
193
+ { id: '__manage', label: tr('assistant.accounts.manage', 'Manage…'), onSelect: () => onManage?.() },
194
+ ],
195
+ };
196
+ }
197
+
198
+ /**
199
+ * Open the menu from `anchor`. ctx: { provider: 'claude' | 'codex', t, placement, bounds,
200
+ * read() → { accounts, currentId, workerId, running },
201
+ * onAuto(), onPick(id), onSetWorker(id) → Promise, onSetMode(id, mode), onAdd(), onManage(id?), onError(err),
202
+ * now, locale (the clock and language of the reset times; default: now and the page's) }
203
+ * Returns the asst-menu handle (or null).
204
+ */
205
+ export function openAccountMenu(anchor, ctx = {}) {
206
+ const tr = tf(ctx.t);
207
+ const p = ctx.provider === 'codex' ? 'codex' : 'claude';
208
+ const workersTitle = p === 'codex' ? tr('assistant.accounts.codexWorkersHeader', 'Codex workers use') : tr('assistant.accounts.workersHeader', 'Claude Code workers use');
209
+ let view = 'main';
210
+ let busy = false;
211
+ let handle = null;
212
+
213
+ const state = () => {
214
+ const s = ctx.read?.() || {};
215
+ const accounts = Array.isArray(s.accounts) ? s.accounts : [];
216
+ const rows = accounts.map((a) => accountMenuRow(a, { provider: p, t: ctx.t, now: ctx.now, locale: ctx.locale }));
217
+ return { accounts, rows, currentId: s.currentId || null, workerId: s.workerId || 'default', running: s.running === true };
218
+ };
219
+
220
+ function mainItems(s) {
221
+ return accountChoiceItems({ t: ctx.t, rows: s.rows, currentId: s.currentId, onAuto: () => ctx.onAuto?.(), onPick: (id) => ctx.onPick?.(id), onSignIn: (id) => ctx.onManage?.(id) });
222
+ }
223
+
224
+ function mainFooter(s) {
225
+ const footer = [];
226
+ const uses = accountModeItem({ provider: p, t: ctx.t, account: s.accounts.find((a) => a.id === s.currentId), running: s.running, onSetMode: (id, mode) => ctx.onSetMode?.(id, mode) });
227
+ if (uses) footer.push(uses);
228
+ const worker = s.rows.find((r) => r.id === s.workerId);
229
+ const workerName = worker ? worker.name : (s.workerId === 'default' ? accountText('defaultName', {}, p) : s.workerId);
230
+ const workerState = worker?.signedOut ? worker.badge.text : worker?.line?.tone === 'warn' ? tr('assistant.accountUsage.limited', 'Limited') : '';
231
+ const value = [workerName, workerState].filter(Boolean).join(' · ');
232
+ const workersLabel = tr('assistant.accountMenu.workers', 'Workers use');
233
+ footer.push({
234
+ kind: 'item', id: 'workers', label: workersLabel, keywords: workerName, keepOpen: true,
235
+ title: [`${workersTitle}: ${value}`, tr('assistant.accountMenu.workersHint', 'Used when a dispatch names no account.')].join(' · '),
236
+ ariaLabel: `${workersTitle}: ${value}`,
237
+ render: (btn) => drawValue(btn, workersLabel, value, workerState ? 'warn' : 'muted'),
238
+ onSelect: () => showWorkers(),
239
+ });
240
+ footer.push(accountManageItem({ t: ctx.t, onAdd: () => ctx.onAdd?.(), onManage: () => ctx.onManage?.() }));
241
+ return footer;
242
+ }
243
+
244
+ function workerItems(s) {
245
+ const items = [{
246
+ kind: 'item', id: 'back', label: accountText('back', {}, p), keepOpen: true,
247
+ render: (btn) => drawBack(btn, accountText('back', {}, p)),
248
+ onSelect: () => showMain('workers'),
249
+ }];
250
+ for (const r of s.rows) {
251
+ const chosen = r.id === s.workerId;
252
+ const words = rowWords(r);
253
+ items.push({
254
+ kind: 'radio', id: `worker:${r.id}`, label: r.name, keywords: r.identity, selected: chosen,
255
+ disabled: !r.usable && !chosen, keepOpen: true, title: words, ariaLabel: words,
256
+ render: (btn, { checked }) => drawAccount(btn, r, checked),
257
+ onSelect: async () => {
258
+ if (chosen) { showMain('workers'); return; }
259
+ if (busy) return;
260
+ busy = true;
261
+ try { await ctx.onSetWorker?.(r.id); showMain('workers'); }
262
+ catch (err) { ctx.onError?.(err); }
263
+ finally { busy = false; }
264
+ },
265
+ });
266
+ }
267
+ items.push({ kind: 'info', className: 'asst-acct-note', label: tr('assistant.accountMenu.workersHint', 'Used when a dispatch names no account.') });
268
+ return items;
269
+ }
270
+
271
+ function showMain(focus = null) {
272
+ if (view === 'closed') return;
273
+ view = 'main';
274
+ const s = state();
275
+ handle?.update({ title: accountText('menuTitle', {}, p), items: mainItems(s), footer: mainFooter(s), focus });
276
+ }
277
+ function showWorkers() {
278
+ if (view === 'closed') return;
279
+ view = 'workers';
280
+ const s = state();
281
+ handle?.update({ title: workersTitle, items: workerItems(s), footer: [], focus: `worker:${s.workerId}` });
282
+ }
283
+
284
+ const s = state();
285
+ handle = openMenu(anchor, {
286
+ title: accountText('menuTitle', {}, p),
287
+ items: mainItems(s),
288
+ footer: mainFooter(s),
289
+ role: 'menu',
290
+ placement: ctx.placement || 'auto',
291
+ width: MENU_WIDTH,
292
+ bounds: ctx.bounds || null,
293
+ className: 'asst-account-menu',
294
+ onEscape: () => { if (view !== 'workers') return false; showMain('workers'); return true; },
295
+ onClose: () => { view = 'closed'; },
296
+ });
297
+ return handle;
298
+ }
@@ -294,6 +294,13 @@ export function setCodexWorkerAccount(accountId) {
294
294
  return jsonFetch('/api/assistant/codex/worker-account', jsonBody({ accountId: accountId || null }, 'PUT'));
295
295
  }
296
296
 
297
+ // ── Admin sandbox (global, every agent SynaBun starts) ──
298
+
299
+ /** PUT { adminSandbox } → { ok, adminSandbox, providers }. Agents get 403 UI_ONLY. */
300
+ export function setAdminSandbox(enabled) {
301
+ return jsonFetch('/api/assistant/admin-sandbox', jsonBody({ adminSandbox: enabled === true }, 'PUT'));
302
+ }
303
+
297
304
  /** Remove one of an account's credentials: 'subscription' (Codex's own sign-out) or 'apiKey'. */
298
305
  export function signOutCodexAccount(id, credential) {
299
306
  return jsonFetch(`/api/codex/accounts/${enc(id)}/auth/logout`, jsonBody({ credential }));