synabun 2.0.10 → 2.1.0

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 (517) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +25 -2
  3. package/THIRD-PARTY-LICENSES.md +22 -0
  4. package/bin/synabun.js +71 -24
  5. package/docs/whatsapp.md +2 -2
  6. package/hooks/claude-code/pre-websearch.mjs +12 -1
  7. package/hooks/claude-code/shared.mjs +5 -0
  8. package/hooks/claude-code/stop.mjs +1 -1
  9. package/lib/claude-hooks.js +172 -18
  10. package/lib/client-config-repair.js +17 -8
  11. package/lib/desktop-app.js +7 -1
  12. package/lib/external-tools.js +1 -1
  13. package/lib/first-launch-protection.js +57 -12
  14. package/lib/mcp-dist.js +176 -0
  15. package/lib/release-npm-ci.mjs +138 -0
  16. package/lib/release-publish.mjs +91 -0
  17. package/lib/release-scan.mjs +50 -0
  18. package/lib/server-crash-record.js +123 -0
  19. package/lib/server-exit.js +318 -0
  20. package/lib/server-supervisor.js +508 -0
  21. package/lib/start-launcher.js +37 -4
  22. package/lib/tools-cli.js +45 -0
  23. package/lib/update-snapshot.js +228 -0
  24. package/lib/virustotal.mjs +192 -0
  25. package/lib/windows-crash-event.js +269 -0
  26. package/mcp-server/dist/http.d.ts +20 -1
  27. package/mcp-server/dist/http.d.ts.map +1 -1
  28. package/mcp-server/dist/http.js +59 -12
  29. package/mcp-server/dist/http.js.map +1 -1
  30. package/mcp-server/dist/index.d.ts +33 -27
  31. package/mcp-server/dist/index.d.ts.map +1 -1
  32. package/mcp-server/dist/index.js +128 -143
  33. package/mcp-server/dist/index.js.map +1 -1
  34. package/mcp-server/dist/preload.js +1 -1
  35. package/mcp-server/dist/preload.js.map +1 -1
  36. package/mcp-server/dist/services/codex-config-heal.d.ts +6 -2
  37. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  38. package/mcp-server/dist/services/codex-config-heal.js +81 -28
  39. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  40. package/mcp-server/dist/services/memory-judge-backfill.d.ts +6 -0
  41. package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -1
  42. package/mcp-server/dist/services/memory-judge-backfill.js +7 -1
  43. package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -1
  44. package/mcp-server/dist/services/neural-interface.d.ts +16 -10
  45. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  46. package/mcp-server/dist/services/neural-interface.js +67 -11
  47. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  48. package/mcp-server/dist/services/server-instructions.d.ts +7 -2
  49. package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
  50. package/mcp-server/dist/services/server-instructions.js +20 -62
  51. package/mcp-server/dist/services/server-instructions.js.map +1 -1
  52. package/mcp-server/dist/services/tool-catalog.d.ts +120 -0
  53. package/mcp-server/dist/services/tool-catalog.d.ts.map +1 -0
  54. package/mcp-server/dist/services/tool-catalog.js +339 -0
  55. package/mcp-server/dist/services/tool-catalog.js.map +1 -0
  56. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts +12 -0
  57. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts.map +1 -0
  58. package/mcp-server/dist/services/tool-list-refresh-barrier.js +40 -0
  59. package/mcp-server/dist/services/tool-list-refresh-barrier.js.map +1 -0
  60. package/mcp-server/dist/services/tool-list.d.ts +108 -0
  61. package/mcp-server/dist/services/tool-list.d.ts.map +1 -0
  62. package/mcp-server/dist/services/tool-list.js +334 -0
  63. package/mcp-server/dist/services/tool-list.js.map +1 -0
  64. package/mcp-server/dist/services/tool-runtime.d.ts +161 -0
  65. package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -0
  66. package/mcp-server/dist/services/tool-runtime.js +400 -0
  67. package/mcp-server/dist/services/tool-runtime.js.map +1 -0
  68. package/mcp-server/dist/services/whiteboard-geometry.d.ts +26 -2
  69. package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -1
  70. package/mcp-server/dist/services/whiteboard-geometry.js +91 -8
  71. package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -1
  72. package/mcp-server/dist/tools/agents.d.ts +10 -10
  73. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  74. package/mcp-server/dist/tools/agents.js +22 -13
  75. package/mcp-server/dist/tools/agents.js.map +1 -1
  76. package/mcp-server/dist/tools/browser-batch.d.ts +3 -3
  77. package/mcp-server/dist/tools/image-tools.d.ts +1 -1
  78. package/mcp-server/dist/tools/image-tools.js +1 -1
  79. package/mcp-server/dist/tools/skill-tools.d.ts +505 -0
  80. package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -0
  81. package/mcp-server/dist/tools/skill-tools.js +639 -0
  82. package/mcp-server/dist/tools/skill-tools.js.map +1 -0
  83. package/mcp-server/dist/tools/skill.d.ts +7 -0
  84. package/mcp-server/dist/tools/skill.d.ts.map +1 -0
  85. package/mcp-server/dist/tools/skill.js +11 -0
  86. package/mcp-server/dist/tools/skill.js.map +1 -0
  87. package/mcp-server/dist/tools/tool-groups.d.ts +68 -0
  88. package/mcp-server/dist/tools/tool-groups.d.ts.map +1 -0
  89. package/mcp-server/dist/tools/tool-groups.js +147 -0
  90. package/mcp-server/dist/tools/tool-groups.js.map +1 -0
  91. package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -1
  92. package/mcp-server/dist/tools/whiteboard-format.js +7 -4
  93. package/mcp-server/dist/tools/whiteboard-format.js.map +1 -1
  94. package/mcp-server/dist/tools/whiteboard-tools.d.ts +64 -13
  95. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
  96. package/mcp-server/dist/tools/whiteboard-tools.js +30 -5
  97. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
  98. package/mcp-server/package-lock.json +2 -2
  99. package/mcp-server/package.json +1 -1
  100. package/mcp-server/src/http.ts +75 -12
  101. package/mcp-server/src/index.ts +147 -154
  102. package/mcp-server/src/preload.ts +12 -12
  103. package/mcp-server/src/services/codex-config-heal.ts +80 -29
  104. package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
  105. package/mcp-server/src/services/neural-interface.ts +79 -20
  106. package/mcp-server/src/services/server-instructions.ts +27 -63
  107. package/mcp-server/src/services/tool-catalog.ts +403 -0
  108. package/mcp-server/src/services/tool-list-refresh-barrier.ts +33 -0
  109. package/mcp-server/src/services/tool-list.ts +410 -0
  110. package/mcp-server/src/services/tool-runtime.ts +457 -0
  111. package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
  112. package/mcp-server/src/tools/agents.ts +22 -14
  113. package/mcp-server/src/tools/image-tools.ts +1 -1
  114. package/mcp-server/src/tools/skill-tools.ts +589 -0
  115. package/mcp-server/src/tools/skill.ts +13 -0
  116. package/mcp-server/src/tools/tool-groups.ts +164 -0
  117. package/mcp-server/src/tools/whiteboard-format.ts +9 -3
  118. package/mcp-server/src/tools/whiteboard-tools.ts +39 -5
  119. package/neural-interface/i18n/en.json +3463 -200
  120. package/neural-interface/i18n/pt-BR.json +3411 -151
  121. package/neural-interface/lib/assistant-api.js +127 -28
  122. package/neural-interface/lib/assistant-brains/claude.js +4 -3
  123. package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +12 -2
  124. package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +32 -9
  125. package/neural-interface/lib/assistant-brains/codex.js +9 -5
  126. package/neural-interface/lib/assistant-brains/opencode.js +120 -54
  127. package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +28 -13
  128. package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +59 -19
  129. package/neural-interface/lib/assistant-catalog.js +6 -7
  130. package/neural-interface/lib/assistant-dispatch.js +871 -156
  131. package/neural-interface/lib/assistant-envelope.js +3 -1
  132. package/neural-interface/lib/assistant-persona.js +80 -11
  133. package/neural-interface/lib/assistant-plan-permissions.js +10 -9
  134. package/neural-interface/lib/assistant-route-gate-codex.js +3 -0
  135. package/neural-interface/lib/assistant-route-gate.js +6 -4
  136. package/neural-interface/lib/assistant-router.js +13 -8
  137. package/neural-interface/lib/assistant-runtime.js +56 -18
  138. package/neural-interface/lib/assistant-task-prompt.js +78 -14
  139. package/neural-interface/lib/assistant-tools.js +51 -0
  140. package/neural-interface/lib/backup-service.js +620 -3
  141. package/neural-interface/lib/backup-zip-reader.js +10 -5
  142. package/neural-interface/lib/backup-zip-writer.js +257 -0
  143. package/neural-interface/lib/browser-execution.js +10 -2
  144. package/neural-interface/lib/browser-guard-cdp.js +83 -0
  145. package/neural-interface/lib/browser-network-guard.js +284 -0
  146. package/neural-interface/lib/browser-policy-roots.js +64 -0
  147. package/neural-interface/lib/browser-tool-policy.js +186 -31
  148. package/neural-interface/lib/claude-agent-bridge.js +4 -2
  149. package/neural-interface/lib/claude-hooks-status.js +103 -0
  150. package/neural-interface/lib/claude-temporary.js +5 -11
  151. package/neural-interface/lib/cli-install.js +18 -138
  152. package/neural-interface/lib/cli-resolver.js +4 -1
  153. package/neural-interface/lib/cli-update-planner.js +32 -31
  154. package/neural-interface/lib/cloudflared/tunnel.js +42 -2
  155. package/neural-interface/lib/codex-mcp-approvals.js +11 -0
  156. package/neural-interface/lib/codex-worker-app-server.js +172 -0
  157. package/neural-interface/lib/confined-fs.js +30 -3
  158. package/neural-interface/lib/crash-notice-api.js +98 -0
  159. package/neural-interface/lib/editor-completion-opencode.js +68 -68
  160. package/neural-interface/lib/exec-loop-command.js +4 -8
  161. package/neural-interface/lib/http-guards.js +8 -2
  162. package/neural-interface/lib/install-details.js +4 -1
  163. package/neural-interface/lib/mcp-agent-servers.js +67 -0
  164. package/neural-interface/lib/mcp-client-config.js +0 -29
  165. package/neural-interface/lib/mcp-external-servers.js +323 -0
  166. package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
  167. package/neural-interface/lib/morelogin.js +30 -0
  168. package/neural-interface/lib/native-loop-providers.js +547 -47
  169. package/neural-interface/lib/native-loop-runtime.js +268 -41
  170. package/neural-interface/lib/opencode-config.js +557 -0
  171. package/neural-interface/lib/opencode-db-schema.js +17 -34
  172. package/neural-interface/lib/opencode-history.js +48 -218
  173. package/neural-interface/lib/opencode-mcp-ready.js +108 -0
  174. package/neural-interface/lib/opencode-native-boundary.js +44 -0
  175. package/neural-interface/lib/opencode-protocol.js +249 -112
  176. package/neural-interface/lib/opencode-runtime-config.js +32 -22
  177. package/neural-interface/lib/opencode-serve-records.js +39 -21
  178. package/neural-interface/lib/opencode-v2-client.js +48 -153
  179. package/neural-interface/lib/opencode-v2-ws-requests.js +36 -210
  180. package/neural-interface/lib/opencode-worker-data.js +27 -0
  181. package/neural-interface/lib/opencode2-client.js +73 -37
  182. package/neural-interface/lib/opencode2-proxy.js +23 -63
  183. package/neural-interface/lib/opencode2-translate.js +51 -2
  184. package/neural-interface/lib/owner-authority.js +249 -0
  185. package/neural-interface/lib/page-session.js +275 -0
  186. package/neural-interface/lib/peer-identity.js +763 -0
  187. package/neural-interface/lib/request-gate.js +17 -2
  188. package/neural-interface/lib/rulesets/api.js +1 -1
  189. package/neural-interface/lib/rulesets/fs-safe.js +1 -1
  190. package/neural-interface/lib/rulesets/installer.js +92 -27
  191. package/neural-interface/lib/rulesets/json-edit.js +1 -1
  192. package/neural-interface/lib/session-cache-builder.js +17 -30
  193. package/neural-interface/lib/session-title-generator.js +58 -36
  194. package/neural-interface/lib/skills/api-v2.js +495 -0
  195. package/neural-interface/lib/skills/bundle.js +418 -0
  196. package/neural-interface/lib/skills/catalog.js +294 -0
  197. package/neural-interface/lib/skills/creation-api.js +191 -0
  198. package/neural-interface/lib/skills/creation-brief.js +247 -0
  199. package/neural-interface/lib/skills/creation-sources.js +267 -0
  200. package/neural-interface/lib/skills/creation.js +843 -0
  201. package/neural-interface/lib/skills/deploy.js +564 -0
  202. package/neural-interface/lib/skills/digest.js +119 -0
  203. package/neural-interface/lib/skills/discover.js +111 -0
  204. package/neural-interface/lib/skills/draft-confinement.js +214 -0
  205. package/neural-interface/lib/skills/eval-confinement.js +397 -0
  206. package/neural-interface/lib/skills/eval-sandbox.js +280 -0
  207. package/neural-interface/lib/skills/eval-trace.js +119 -0
  208. package/neural-interface/lib/skills/evals-api.js +212 -0
  209. package/neural-interface/lib/skills/evals.js +1573 -0
  210. package/neural-interface/lib/skills/export.js +200 -0
  211. package/neural-interface/lib/skills/frontmatter.js +64 -0
  212. package/neural-interface/lib/skills/health.js +75 -0
  213. package/neural-interface/lib/skills/json-edit.js +165 -0
  214. package/neural-interface/lib/skills/known-bridge-digests.json +387 -0
  215. package/neural-interface/lib/skills/known-bridges.js +96 -0
  216. package/neural-interface/lib/skills/library.js +823 -0
  217. package/neural-interface/lib/skills/local-store.js +21 -0
  218. package/neural-interface/lib/skills/migration.js +433 -0
  219. package/neural-interface/lib/skills/operations.js +45 -0
  220. package/neural-interface/lib/skills/ownership.js +427 -0
  221. package/neural-interface/lib/skills/packages.js +88 -0
  222. package/neural-interface/lib/skills/reviews.js +212 -0
  223. package/neural-interface/lib/skills/revision.js +80 -0
  224. package/neural-interface/lib/skills/roots.js +262 -0
  225. package/neural-interface/lib/skills/runtime-checks.js +355 -0
  226. package/neural-interface/lib/skills/scan-assets.js +252 -0
  227. package/neural-interface/lib/skills/scan-context.js +26 -0
  228. package/neural-interface/lib/skills/scan-shell.js +324 -0
  229. package/neural-interface/lib/skills/scan.js +364 -0
  230. package/neural-interface/lib/skills/scanners.js +54 -0
  231. package/neural-interface/lib/skills/service.js +522 -0
  232. package/neural-interface/lib/skills/telemetry.js +113 -0
  233. package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
  234. package/neural-interface/lib/tool-lists.js +124 -0
  235. package/neural-interface/lib/whatsapp/bridge.js +6 -3
  236. package/neural-interface/lib/worker-confinement.js +1192 -0
  237. package/neural-interface/lib/worker-guard-setting.js +56 -0
  238. package/neural-interface/lib/worker-mcp-channel.js +175 -0
  239. package/neural-interface/lib/worker-relay-ports.js +227 -0
  240. package/neural-interface/lib/worker-resume.js +229 -0
  241. package/neural-interface/lib/worker-sandbox.js +299 -0
  242. package/neural-interface/package-lock.json +18 -12
  243. package/neural-interface/package.json +2 -2
  244. package/neural-interface/public/index.html +3 -1
  245. package/neural-interface/public/index2d.html +19 -11
  246. package/neural-interface/public/onboarding.html +2 -2
  247. package/neural-interface/public/shared/account-menu.js +8 -5
  248. package/neural-interface/public/shared/agent-runtime-options.js +35 -19
  249. package/neural-interface/public/shared/api.js +1527 -1336
  250. package/neural-interface/public/shared/app-frame.js +192 -0
  251. package/neural-interface/public/shared/assistant/asst-agents-dock.js +33 -2
  252. package/neural-interface/public/shared/assistant/asst-ask-styles.js +270 -0
  253. package/neural-interface/public/shared/assistant/asst-ask-tail.js +118 -0
  254. package/neural-interface/public/shared/assistant/asst-ask.js +397 -0
  255. package/neural-interface/public/shared/assistant/asst-brain-picker.js +49 -35
  256. package/neural-interface/public/shared/assistant/asst-control.js +157 -26
  257. package/neural-interface/public/shared/assistant/asst-followups-card.js +383 -0
  258. package/neural-interface/public/shared/assistant/asst-followups.js +417 -0
  259. package/neural-interface/public/shared/assistant/asst-limit-dock.js +40 -0
  260. package/neural-interface/public/shared/assistant/asst-limit-wait.js +294 -0
  261. package/neural-interface/public/shared/assistant/asst-limit.js +168 -0
  262. package/neural-interface/public/shared/assistant/asst-mascot.js +49 -12
  263. package/neural-interface/public/shared/assistant/asst-menu.js +7 -6
  264. package/neural-interface/public/shared/assistant/asst-panel.js +106 -11
  265. package/neural-interface/public/shared/assistant/asst-rack-styles.js +66 -49
  266. package/neural-interface/public/shared/assistant/asst-render.js +401 -77
  267. package/neural-interface/public/shared/assistant/asst-route.js +83 -12
  268. package/neural-interface/public/shared/assistant/asst-scenes.js +2 -0
  269. package/neural-interface/public/shared/assistant/asst-sidepanel.js +67 -4
  270. package/neural-interface/public/shared/assistant/asst-slash.js +9 -7
  271. package/neural-interface/public/shared/assistant/asst-state.js +11 -15
  272. package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
  273. package/neural-interface/public/shared/assistant/asst-styles.js +59 -21
  274. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +156 -16
  275. package/neural-interface/public/shared/automation-context.js +5 -2
  276. package/neural-interface/public/shared/cc-dropdown.js +89 -19
  277. package/neural-interface/public/shared/cdx/cdx-context-menu.js +10 -9
  278. package/neural-interface/public/shared/cdx/cdx-copy.js +16 -0
  279. package/neural-interface/public/shared/cdx/cdx-output-model.js +22 -1
  280. package/neural-interface/public/shared/cdx/cdx-output.js +8 -7
  281. package/neural-interface/public/shared/cdx/cdx-panel.js +45 -149
  282. package/neural-interface/public/shared/cdx/cdx-protocol.js +28 -1
  283. package/neural-interface/public/shared/cdx/cdx-render.js +47 -29
  284. package/neural-interface/public/shared/cdx/cdx-requests.js +3 -3
  285. package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
  286. package/neural-interface/public/shared/cdx/cdx-styles.js +11 -3
  287. package/neural-interface/public/shared/cdx/cdx-tabs.js +52 -39
  288. package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
  289. package/neural-interface/public/shared/cli-install-view.js +4 -8
  290. package/neural-interface/public/shared/cli-status.js +2 -2
  291. package/neural-interface/public/shared/constants.js +2 -2
  292. package/neural-interface/public/shared/context-menu-areas.js +34 -0
  293. package/neural-interface/public/shared/context-menu-logic.js +243 -0
  294. package/neural-interface/public/shared/context-menu-model.js +366 -0
  295. package/neural-interface/public/shared/context-menu.js +372 -0
  296. package/neural-interface/public/shared/cp/cp-context-menu.js +10 -9
  297. package/neural-interface/public/shared/crash-notice-view.js +168 -0
  298. package/neural-interface/public/shared/desktop-update-view.js +20 -0
  299. package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
  300. package/neural-interface/public/shared/html-shell.js +9 -11
  301. package/neural-interface/public/shared/i18n.js +2 -0
  302. package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +7 -7
  303. package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +4 -4
  304. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +3 -101
  305. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +2 -0
  306. package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +1 -3
  307. package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +2 -6
  308. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +11 -11
  309. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +0 -3
  310. package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +8 -4
  311. package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +7 -7
  312. package/neural-interface/public/shared/ocp-v2/ocp-v2-outcome-notifications.js +16 -0
  313. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +47 -252
  314. package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
  315. package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +95 -202
  316. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +6 -21
  317. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +4 -4
  318. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +8 -61
  319. package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +2 -91
  320. package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +16 -15
  321. package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +8 -21
  322. package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +1 -99
  323. package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +5 -35
  324. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +0 -21
  325. package/neural-interface/public/shared/ocp-v2/{ocp-v2-profile-inheritance.js → ocp-v2-tools-inheritance.js} +16 -16
  326. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +14 -47
  327. package/neural-interface/public/shared/page-session.js +96 -0
  328. package/neural-interface/public/shared/registry.js +5 -3
  329. package/neural-interface/public/shared/retired-2d.js +3 -0
  330. package/neural-interface/public/shared/schedules-view.js +195 -61
  331. package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
  332. package/neural-interface/public/shared/settings/settings-data.js +3 -1
  333. package/neural-interface/public/shared/settings/settings-ia.js +6 -6
  334. package/neural-interface/public/shared/settings/settings-opencode.js +89 -0
  335. package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +265 -0
  336. package/neural-interface/public/shared/settings/settings-skill-folders.js +130 -0
  337. package/neural-interface/public/shared/settings/settings-worker-guard.js +72 -0
  338. package/neural-interface/public/shared/skills/ss-args.js +96 -0
  339. package/neural-interface/public/shared/skills/ss-creation.js +219 -0
  340. package/neural-interface/public/shared/skills/ss-dialog.js +130 -0
  341. package/neural-interface/public/shared/skills/ss-doc.js +173 -0
  342. package/neural-interface/public/shared/skills/ss-eval-adapters.js +269 -0
  343. package/neural-interface/public/shared/skills/ss-eval-grade.js +359 -0
  344. package/neural-interface/public/shared/skills/ss-eval-optimise.js +153 -0
  345. package/neural-interface/public/shared/skills/ss-eval-plan.js +315 -0
  346. package/neural-interface/public/shared/skills/ss-evals.js +417 -0
  347. package/neural-interface/public/shared/skills/ss-form.js +159 -0
  348. package/neural-interface/public/shared/skills/ss-frontmatter.js +376 -0
  349. package/neural-interface/public/shared/skills/ss-ids.js +32 -0
  350. package/neural-interface/public/shared/skills/ss-lint.js +141 -0
  351. package/neural-interface/public/shared/skills/ss-presets.js +117 -0
  352. package/neural-interface/public/shared/skills/ss-profiles.js +475 -0
  353. package/neural-interface/public/shared/skills/ss-resources.js +165 -0
  354. package/neural-interface/public/shared/skills/ss-review-view.js +190 -0
  355. package/neural-interface/public/shared/skills/ss-tabs.js +79 -0
  356. package/neural-interface/public/shared/skills/ss-unicode.js +135 -0
  357. package/neural-interface/public/shared/skills/studio/sx-actions.js +222 -0
  358. package/neural-interface/public/shared/skills/studio/sx-cm.js +101 -0
  359. package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +749 -0
  360. package/neural-interface/public/shared/skills/studio/sx-create-ai.js +236 -0
  361. package/neural-interface/public/shared/skills/studio/sx-create-view.js +254 -0
  362. package/neural-interface/public/shared/skills/studio/sx-create.js +126 -0
  363. package/neural-interface/public/shared/skills/studio/sx-diff-view.js +43 -0
  364. package/neural-interface/public/shared/skills/studio/sx-diff.js +133 -0
  365. package/neural-interface/public/shared/skills/studio/sx-docs.js +480 -0
  366. package/neural-interface/public/shared/skills/studio/sx-dom.js +229 -0
  367. package/neural-interface/public/shared/skills/studio/sx-edit-view.js +848 -0
  368. package/neural-interface/public/shared/skills/studio/sx-editor-model.js +193 -0
  369. package/neural-interface/public/shared/skills/studio/sx-errors.js +22 -0
  370. package/neural-interface/public/shared/skills/studio/sx-geometry.js +82 -0
  371. package/neural-interface/public/shared/skills/studio/sx-import.js +262 -0
  372. package/neural-interface/public/shared/skills/studio/sx-inspector.js +308 -0
  373. package/neural-interface/public/shared/skills/studio/sx-install-flow.js +279 -0
  374. package/neural-interface/public/shared/skills/studio/sx-install.js +296 -0
  375. package/neural-interface/public/shared/skills/studio/sx-library-view.js +639 -0
  376. package/neural-interface/public/shared/skills/studio/sx-library.js +475 -0
  377. package/neural-interface/public/shared/skills/studio/sx-main.js +681 -0
  378. package/neural-interface/public/shared/skills/studio/sx-operations-view.js +98 -0
  379. package/neural-interface/public/shared/skills/studio/sx-operations.js +11 -0
  380. package/neural-interface/public/shared/skills/studio/sx-plan.js +158 -0
  381. package/neural-interface/public/shared/skills/studio/sx-publish-view.js +621 -0
  382. package/neural-interface/public/shared/skills/studio/sx-review-view.js +369 -0
  383. package/neural-interface/public/shared/skills/studio/sx-review.js +148 -0
  384. package/neural-interface/public/shared/skills/studio/sx-scan.js +35 -0
  385. package/neural-interface/public/shared/skills/studio/sx-snippets.js +96 -0
  386. package/neural-interface/public/shared/skills/studio/sx-subject.js +248 -0
  387. package/neural-interface/public/shared/skills/studio/sx-test-view.js +994 -0
  388. package/neural-interface/public/shared/skills/studio/sx-test.js +328 -0
  389. package/neural-interface/public/shared/skills/studio/sx-versions-view.js +241 -0
  390. package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +425 -0
  391. package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
  392. package/neural-interface/public/shared/styles.css +1525 -1779
  393. package/neural-interface/public/shared/synabun-mascot.js +1115 -9
  394. package/neural-interface/public/shared/tool-list-picker.js +55 -0
  395. package/neural-interface/public/shared/tool-list-storage.js +24 -0
  396. package/neural-interface/public/shared/tool-list-view.js +112 -0
  397. package/neural-interface/public/shared/tour/tour-actor.js +268 -0
  398. package/neural-interface/public/shared/tour/tour-beats.js +336 -0
  399. package/neural-interface/public/shared/tour/tour-engine.js +884 -0
  400. package/neural-interface/public/shared/tour/tour-ink.js +390 -0
  401. package/neural-interface/public/shared/tour/tour-stage.js +440 -0
  402. package/neural-interface/public/shared/tour/tour-state.js +40 -0
  403. package/neural-interface/public/shared/tour/tour-tips.js +99 -0
  404. package/neural-interface/public/shared/tour/tour.css +144 -0
  405. package/neural-interface/public/shared/ui-automation-studio.js +91 -128
  406. package/neural-interface/public/shared/ui-claude-panel.js +77 -5
  407. package/neural-interface/public/shared/ui-command-runner.js +6 -3
  408. package/neural-interface/public/shared/ui-context-menu.js +295 -0
  409. package/neural-interface/public/shared/ui-cost-widget.js +57 -59
  410. package/neural-interface/public/shared/ui-crash-notice.js +187 -0
  411. package/neural-interface/public/shared/ui-detail.js +15 -3
  412. package/neural-interface/public/shared/ui-image-gallery.js +50 -47
  413. package/neural-interface/public/shared/ui-keybinds.js +26 -1
  414. package/neural-interface/public/shared/ui-menubar.js +8 -18
  415. package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
  416. package/neural-interface/public/shared/ui-native-loop-router.js +7 -1
  417. package/neural-interface/public/shared/ui-navbar.js +4 -24
  418. package/neural-interface/public/shared/ui-panels.js +96 -65
  419. package/neural-interface/public/shared/ui-schedules-studio.js +507 -423
  420. package/neural-interface/public/shared/ui-sessions.js +5 -3
  421. package/neural-interface/public/shared/ui-settings.js +594 -462
  422. package/neural-interface/public/shared/ui-sidepanel-sessions.js +1 -1
  423. package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
  424. package/neural-interface/public/shared/ui-sidepanel-windows.js +16 -11
  425. package/neural-interface/public/shared/ui-skills.js +33 -2830
  426. package/neural-interface/public/shared/ui-styleguide.js +5 -2
  427. package/neural-interface/public/shared/ui-sync.js +15 -5
  428. package/neural-interface/public/shared/ui-terminal.js +124 -180
  429. package/neural-interface/public/shared/ui-tool-updates.js +3 -1
  430. package/neural-interface/public/shared/ui-tooltip.js +10 -5
  431. package/neural-interface/public/shared/ui-trash.js +6 -4
  432. package/neural-interface/public/shared/ui-tutorial-layout.js +3 -117
  433. package/neural-interface/public/shared/ui-tutorial-preview.js +103 -0
  434. package/neural-interface/public/shared/ui-tutorial-steps.js +3 -65
  435. package/neural-interface/public/shared/ui-tutorial.css +47 -100
  436. package/neural-interface/public/shared/ui-tutorial.js +285 -509
  437. package/neural-interface/public/shared/ui-update.js +38 -2
  438. package/neural-interface/public/shared/ui-whiteboard.js +311 -58
  439. package/neural-interface/public/shared/ui-worker-end.js +4 -0
  440. package/neural-interface/public/shared/ui-workspaces.js +3 -2
  441. package/neural-interface/public/shared/view-variant.js +66 -0
  442. package/neural-interface/public/shared/whatsapp/wa-view.js +1 -1
  443. package/neural-interface/public/shared/whiteboard-list.js +172 -0
  444. package/neural-interface/public/shared/worker-end.js +10 -3
  445. package/neural-interface/public/shared/worker-restart.js +12 -0
  446. package/neural-interface/public/sidepanel-session.html +1 -1
  447. package/neural-interface/public/variant/2d/background.js +2 -1
  448. package/neural-interface/public/variant/2d/context-menu.js +8 -2
  449. package/neural-interface/public/variant/2d/graph.js +42 -20
  450. package/neural-interface/public/variant/2d/hulls.js +14 -9
  451. package/neural-interface/public/variant/2d/lasso.js +11 -7
  452. package/neural-interface/public/variant/2d/main.js +32 -1
  453. package/neural-interface/public/variant/2d/minimap.js +11 -13
  454. package/neural-interface/public/variant/3d/main.js +69 -6
  455. package/neural-interface/public/variant/3d/map-controls.js +24 -9
  456. package/neural-interface/public/vendor/caveat/OFL.txt +93 -0
  457. package/neural-interface/public/vendor/caveat/caveat-latin-400-normal.woff2 +0 -0
  458. package/neural-interface/public/vendor/caveat/caveat-latin-700-normal.woff2 +0 -0
  459. package/neural-interface/public/vendor/caveat/caveat.css +17 -0
  460. package/neural-interface/public/vendor/caveat/manifest.json +12 -0
  461. package/neural-interface/public/vendor/yaml/LICENSE +13 -0
  462. package/neural-interface/public/vendor/yaml/manifest.json +4 -0
  463. package/neural-interface/public/vendor/yaml/yaml-2.9.1.min.js +137 -0
  464. package/neural-interface/scripts/build-vendor.mjs +32 -0
  465. package/neural-interface/server.js +3934 -2454
  466. package/neural-interface/templates/rulesets/core.md +1 -1
  467. package/neural-interface/templates/rulesets/legacy-hashes.json +18 -0
  468. package/neural-interface/templates/rulesets/manifest.json +8 -8
  469. package/npm-shrinkwrap.json +18 -12
  470. package/package.json +16 -3
  471. package/postinstall.js +9 -4
  472. package/preuninstall.js +1 -1
  473. package/setup.js +97 -322
  474. package/skills/skill-forge/SKILL.md +177 -0
  475. package/skills/skill-forge/evals/evals.json +54 -0
  476. package/skills/skill-forge/references/package.md +105 -0
  477. package/skills/skill-forge/references/review-and-recovery.md +63 -0
  478. package/skills/skill-forge/references/runtime-profiles.md +20 -0
  479. package/skills/skill-forge/references/sources-and-capabilities.md +51 -0
  480. package/skills/synabun/SKILL.md +10 -7
  481. package/skills/synabun/modules/audit.md +1 -5
  482. package/skills/synabun/modules/changelog.md +1 -5
  483. package/skills/synabun/modules/idea.md +1 -1
  484. package/skills/synabun/modules/memorize.md +6 -9
  485. package/skills/synabun/modules/schedule.md +4 -4
  486. package/mcp-server/dist/services/profiles.d.ts +0 -120
  487. package/mcp-server/dist/services/profiles.d.ts.map +0 -1
  488. package/mcp-server/dist/services/profiles.js +0 -387
  489. package/mcp-server/dist/services/profiles.js.map +0 -1
  490. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +0 -97
  491. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +0 -1
  492. package/mcp-server/dist/tools/leonardo-browser-tools.js +0 -228
  493. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +0 -1
  494. package/mcp-server/dist/tools/leonardo.d.ts +0 -13
  495. package/mcp-server/dist/tools/leonardo.d.ts.map +0 -1
  496. package/mcp-server/dist/tools/leonardo.js +0 -21
  497. package/mcp-server/dist/tools/leonardo.js.map +0 -1
  498. package/mcp-server/dist/tools/profile.d.ts +0 -17
  499. package/mcp-server/dist/tools/profile.d.ts.map +0 -1
  500. package/mcp-server/dist/tools/profile.js +0 -114
  501. package/mcp-server/dist/tools/profile.js.map +0 -1
  502. package/mcp-server/src/services/profiles.ts +0 -428
  503. package/mcp-server/src/tools/leonardo-browser-tools.ts +0 -291
  504. package/mcp-server/src/tools/leonardo.ts +0 -30
  505. package/mcp-server/src/tools/profile.ts +0 -122
  506. package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +0 -47
  507. package/neural-interface/lib/assistant-brains/opencode-route-gate.js +0 -62
  508. package/neural-interface/lib/mcp-profile-servers.js +0 -120
  509. package/neural-interface/public/shared/ui-tutorial-draw.js +0 -311
  510. package/skills/leonardo/SKILL.md +0 -168
  511. package/skills/leonardo/icon.svg +0 -1
  512. package/skills/leonardo/modules/browser-guide.md +0 -378
  513. package/skills/leonardo/modules/image-prompter.md +0 -283
  514. package/skills/leonardo/modules/model-advisor.md +0 -100
  515. package/skills/leonardo/modules/prompt-library.md +0 -110
  516. package/skills/leonardo/modules/style-guide.md +0 -186
  517. package/skills/leonardo/modules/video-prompter.md +0 -317
@@ -1,20 +1,23 @@
1
+ import { OPENCODE_BRAIN_TOOLS } from '../assistant-tools.js';
1
2
  // ═══════════════════════════════════════════
2
3
  // SynaBun — Assistant brain: OpenCode (isolated serve + SDK client)
3
4
  // ═══════════════════════════════════════════
4
5
  //
5
6
  // One isolated `opencode serve` per assistant session (same helpers the
6
7
  // OpenCode sidepanel uses). The persona is installed as a custom primary agent
7
- // (`agent.assistant.prompt`) in the per-serve config so the model sees it as
8
- // its system prompt; SSE events are translated to the unified envelope.
8
+ // (`agents.assistant.system`) in the per-serve opencode.json so the model sees
9
+ // it as its system prompt; SSE events are translated to the unified envelope.
9
10
 
10
- import { chmodSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
11
+ import { chmodSync, writeFileSync } from 'node:fs';
11
12
  import { dirname, resolve } from 'node:path';
12
13
  import { fileURLToPath, pathToFileURL } from 'node:url';
13
14
  import { normalizeOpenCodeModel } from '../native-loop-providers.js';
14
- import { compactOrSummarize } from '../opencode-v2-client.js';
15
+ import { agentOf, mcpServersOf, opencodeConfigFilePath, readOpencodeConfigDocument, setAgent, setMcpServer, withPlugin, withoutPlugin } from '../opencode-config.js';
15
16
  import { createOpenCodeEnvelopeTranslator, opencodeControlResponse } from '../assistant-envelope.js';
16
17
  import { opencodePlanPermission, opencodeReadOnlyPermission, planDenyMessage, readOnlyDenyMessage } from '../assistant-plan-permissions.js';
17
18
  import { planModeInstructions } from '../assistant-persona.js';
19
+ import { waitForSynabunTools, synabunConfigured, synabunToolsTimeoutText, OPENCODE_MCP_READY_TIMEOUT_MS, OPENCODE_MCP_SETTLE_MS, OPENCODE_MCP_LIST_GRACE_MS } from '../opencode-mcp-ready.js';
20
+ export { synabunMcpState } from '../opencode-mcp-ready.js';
18
21
 
19
22
  /** The MCP terminal pin of an assistant session's OpenCode brain. */
20
23
  export function opencodeTermIdFor(sessionId) {
@@ -34,14 +37,8 @@ export function opencodeImageParts(images = []) {
34
37
  return parts;
35
38
  }
36
39
 
37
- /** The route-gate plugin (opencode-route-gate.js) as a file URL the serve can load. */
38
- export const OPENCODE_ROUTE_GATE_PLUGIN = pathToFileURL(resolve(dirname(fileURLToPath(import.meta.url)), 'opencode-route-gate.js')).href;
39
- /**
40
- * The same gate for OpenCode 2 (opencode2-route-gate/: OpenCode 2 loads a plugin
41
- * folder from the config's `plugins` list). Both entries are written: OpenCode 1
42
- * ignores `plugins`, OpenCode 2 skips the 1.x file (a warning in its log).
43
- */
44
- export const OPENCODE2_ROUTE_GATE_PLUGIN = pathToFileURL(resolve(dirname(fileURLToPath(import.meta.url)), 'opencode2-route-gate')).href;
40
+ /** The route-gate plugin (opencode2-route-gate/, a plugin folder OpenCode loads from `plugins`) as a file URL. */
41
+ export const OPENCODE_ROUTE_GATE_PLUGIN = pathToFileURL(resolve(dirname(fileURLToPath(import.meta.url)), 'opencode2-route-gate')).href;
45
42
 
46
43
  /**
47
44
  * SynaBun's plan agent. OpenCode adds its own read-only reminder ("commands may
@@ -52,71 +49,111 @@ export const OPENCODE2_ROUTE_GATE_PLUGIN = pathToFileURL(resolve(dirname(fileURL
52
49
  */
53
50
  export const OPENCODE_PLAN_AGENT = 'synabun-plan';
54
51
 
52
+ /**
53
+ * Tools this brain's agents must never call. Code Mode's `execute` runs arbitrary code (a `fetch`
54
+ * included) that no argument check can read, so the route gate refuses it before routing - and this
55
+ * brain's SynaBun tools are direct (codemode: false), so the only thing `execute` can do here is earn
56
+ * that refusal. Denied at the agent, the model is not offered it at all.
57
+ */
58
+ export const OPENCODE_BRAIN_DENIED_TOOLS = Object.freeze(['execute']);
59
+
55
60
  /** The brain's SynaBun MCP tool-call timeout (ms): as long as the Codex brain's tool_timeout_sec (server.js). */
56
61
  export const OPENCODE_BRAIN_MCP_TIMEOUT_MS = 180_000;
57
62
 
63
+ // Compatibility exports for the brain's existing consumers and tests.
64
+ export const OPENCODE_BRAIN_MCP_READY_TIMEOUT_MS = OPENCODE_MCP_READY_TIMEOUT_MS;
65
+ export const OPENCODE_BRAIN_MCP_SETTLE_MS = OPENCODE_MCP_SETTLE_MS;
66
+ export const OPENCODE_BRAIN_MCP_LIST_GRACE_MS = OPENCODE_MCP_LIST_GRACE_MS;
67
+ export const assistantSynabunConfigured = synabunConfigured;
68
+ export { OPENCODE_BRAIN_TOOLS } from '../assistant-tools.js';
69
+
70
+ /**
71
+ * Why the brain's serve would start with SynaBun behind Code Mode (`execute` only, which the route gate
72
+ * refuses before routing, so agent_route would be unreachable), read back from the written config; null
73
+ * when it is fine. No config file at all: the serve has no SynaBun server, the gate turns itself off.
74
+ */
75
+ export function assistantConfigProblem(xdgRoot) {
76
+ let doc;
77
+ try { doc = readOpencodeConfigDocument(opencodeConfigFilePath(resolve(xdgRoot, 'opencode'))); } catch (err) { return `its OpenCode configuration could not be read (${err?.message || err})`; }
78
+ if (!doc.exists) return null;
79
+ if (doc.error) return 'its OpenCode configuration could not be read';
80
+ const synabun = mcpServersOf(doc.config).SynaBun;
81
+ if (synabun && synabun.codemode !== false) return 'SynaBun\'s tools could not be set up for direct use (OpenCode Code Mode stayed on)';
82
+ return null;
83
+ }
84
+
58
85
  /**
59
86
  * `routeGate` ({ url, session, token }) installs the route-gate plugin in this
60
- * isolated serve's config only (assistant brains; never the shared sidepanel setup).
87
+ * isolated serve's opencode.json only (assistant brains; never the shared
88
+ * sidepanel setup). Everything is written in OpenCode 2's schema.
61
89
  */
62
90
  export function patchOpenCodeAssistantConfig(xdgRoot, { persona = '', role = 'assistant', model = null, desktopGrant = null, routeGate = null, brainCredential = null } = {}) {
63
- const configPath = resolve(xdgRoot, 'opencode', 'config.json');
64
- if (!existsSync(configPath)) return false;
65
- const config = JSON.parse(readFileSync(configPath, 'utf8'));
66
- if (config.mcp?.SynaBun && typeof config.mcp.SynaBun === 'object') {
67
- const envKey = config.mcp.SynaBun.environment ? 'environment' : (config.mcp.SynaBun.env ? 'env' : 'environment');
68
- const env = { ...(config.mcp.SynaBun[envKey] || {}), SYNABUN_ROLE: role };
91
+ const configPath = opencodeConfigFilePath(resolve(xdgRoot, 'opencode'));
92
+ const doc = readOpencodeConfigDocument(configPath);
93
+ if (!doc.exists || doc.error) return false;
94
+ const config = doc.config;
95
+ const synabun = mcpServersOf(config).SynaBun;
96
+ if (synabun) {
97
+ const env = { ...(synabun.environment || {}), SYNABUN_ROLE: role };
69
98
  // Computer use: an unforgeable per-brain grant unlocks the `computer` group.
70
99
  if (desktopGrant) env.SYNABUN_DESKTOP_GRANT = desktopGrant;
71
100
  else delete env.SYNABUN_DESKTOP_GRANT;
72
- // A WhatsApp conversation's brain credential (whatsapp_send_file), for this serve's SynaBun MCP child only.
101
+ // The brain's launch credential (whatsapp_send_file, the Skills Service), for this serve's SynaBun MCP child only.
73
102
  // This file names the variable; the value is in the serve's own environment (ensureIsolatedServe), so
74
103
  // neither this file (it is in every backup of data/) nor OPENCODE_CONFIG_CONTENT ever holds it.
75
104
  if (brainCredential) env.SYNABUN_BRAIN_TOKEN = '{env:SYNABUN_BRAIN_TOKEN}';
76
105
  else delete env.SYNABUN_BRAIN_TOKEN;
77
- config.mcp.SynaBun[envKey] = env;
78
106
  // OpenCode gives an MCP tool call 60 s unless the server says otherwise; agent_route may wait on the
79
107
  // user's route card and whatsapp_send_file on an upload (130 s): the Codex brain's 180 s here too.
80
- config.mcp.SynaBun.timeout = OPENCODE_BRAIN_MCP_TIMEOUT_MS;
108
+ // OpenCode 2.1's MCP default is Code Mode: only `execute` reaches these tools. The route gate must
109
+ // refuse arbitrary execute code until routed, so wrapping agent_route (and exempt memory tools) deadlocks
110
+ // the brain. Expose this brain's SynaBun tools directly instead. The serve's OPENCODE_CONFIG_CONTENT
111
+ // overlay carries this entry past project config too; other servers, workers and sidepanels keep their mode.
112
+ setMcpServer(config, 'SynaBun', { ...synabun, codemode: false, environment: env, timeout: { ...(synabun.timeout || {}), execution: OPENCODE_BRAIN_MCP_TIMEOUT_MS } });
81
113
  }
82
- config.agent = { ...(config.agent || {}) };
83
- config.agent.assistant = {
84
- ...(config.agent.assistant || {}),
114
+ const rule = (action, effect, resource = '*') => ({ action, resource, effect });
115
+ // An agent's rules with ours last, in place of any it had for the same tools (as OpenCode 1's
116
+ // `permission.<tool>` replaced the user's value): every command asks whatever was allowed before, and the
117
+ // list stays as it is when the config is patched again.
118
+ const withOurRules = (agent, ours) => [...(agent.permissions || []).filter((own) => !ours.some((r) => r.action === own?.action)), ...ours];
119
+ const withoutExecute = OPENCODE_BRAIN_DENIED_TOOLS.map((name) => rule(name, 'deny'));
120
+ const assistant = agentOf(config, 'assistant');
121
+ setAgent(config, 'assistant', {
122
+ ...assistant,
85
123
  mode: 'primary',
86
124
  description: 'SynaBun central assistant',
87
- prompt: persona || undefined,
125
+ ...(persona ? { system: persona } : {}),
88
126
  ...(model ? { model } : {}),
89
- };
127
+ permissions: withOurRules(assistant, withoutExecute),
128
+ });
90
129
  // SynaBun's plan agent (desktop plan turns): no code changes, anything else as the approval mode says.
91
- config.agent[OPENCODE_PLAN_AGENT] = {
92
- ...(config.agent[OPENCODE_PLAN_AGENT] || {}),
130
+ const synabunPlan = agentOf(config, OPENCODE_PLAN_AGENT);
131
+ setAgent(config, OPENCODE_PLAN_AGENT, {
132
+ ...synabunPlan,
93
133
  mode: 'primary',
94
134
  hidden: true,
95
135
  description: 'SynaBun plan mode: explore, run commands and use every tool; no code changes',
96
- prompt: persona || undefined,
136
+ ...(persona ? { system: persona } : {}),
97
137
  ...(model ? { model } : {}),
98
- permission: { ...(config.agent[OPENCODE_PLAN_AGENT]?.permission || {}), edit: 'deny', bash: 'ask', question: 'allow', task: { general: 'deny' } },
99
- };
138
+ permissions: withOurRules(synabunPlan, [...withoutExecute, rule('edit', 'deny'), rule('shell', 'ask'), rule('question', 'allow'), rule('subagent', 'deny', 'general')]),
139
+ });
100
140
  // OpenCode's own plan agent (a WhatsApp session's read-only brain) refuses only
101
141
  // edits and runs any command without asking: here every command asks, so the
102
142
  // read-only classifier decides on the whole line.
103
- config.agent.plan = { ...(config.agent.plan || {}), permission: { ...(config.agent.plan?.permission || {}), bash: 'ask' } };
104
- const plugins = (Array.isArray(config.plugin) ? config.plugin : []).filter((entry) => (Array.isArray(entry) ? entry[0] : entry) !== OPENCODE_ROUTE_GATE_PLUGIN);
105
- const plugins2 = (Array.isArray(config.plugins) ? config.plugins : []).filter((entry) => (entry && typeof entry === 'object' ? entry.package : entry) !== OPENCODE2_ROUTE_GATE_PLUGIN);
143
+ const plan = agentOf(config, 'plan');
144
+ setAgent(config, 'plan', { ...plan, permissions: withOurRules(plan, [rule('shell', 'ask')]) });
145
+ withoutPlugin(config, OPENCODE_ROUTE_GATE_PLUGIN);
106
146
  // `remote`: a WhatsApp session's serve — the plugin refuses a call it could not check (fail closed).
107
147
  if (routeGate?.url && routeGate?.session && routeGate?.token) {
108
- const options = { url: routeGate.url, session: routeGate.session, token: routeGate.token, ...(routeGate.remote === true ? { remote: true } : {}) };
109
- plugins.push([OPENCODE_ROUTE_GATE_PLUGIN, options]);
110
- plugins2.push({ package: OPENCODE2_ROUTE_GATE_PLUGIN, options });
148
+ withPlugin(config, OPENCODE_ROUTE_GATE_PLUGIN, { url: routeGate.url, session: routeGate.session, token: routeGate.token, ...(routeGate.remote === true ? { remote: true } : {}) });
111
149
  }
112
- if (plugins.length) config.plugin = plugins; else delete config.plugin;
113
- if (plugins2.length) config.plugins = plugins2; else delete config.plugins;
114
- writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
150
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', { encoding: 'utf8', mode: 0o600 });
115
151
  // It carries this brain's grants (the desktop grant): readable by this user only.
116
152
  try { chmodSync(configPath, 0o600); } catch { /* a filesystem without modes */ }
117
153
  return true;
118
154
  }
119
155
 
156
+
120
157
  /** The assistant's OpenCode approval mode: 'auto' (auto-accept) or 'default'. */
121
158
  export function opencodeApprovalMode(mode) {
122
159
  return mode === 'auto' || mode === 'acceptEdits' || mode === 'bypassPermissions' ? 'auto' : 'default';
@@ -169,8 +206,13 @@ export function createOpenCodeBrain({ session, sink, deps = {}, persona = '' } =
169
206
  let keepAliveTimer = null;
170
207
  let costUsd = 0;
171
208
  let startPromise = null;
172
- // The model the last turn ran on: what a compaction summarises with.
173
- let lastTurnModel = null;
209
+ let synabunExpected = false;
210
+ let stops = 0;
211
+ let waitingAbort = null;
212
+ const mcpWait = {
213
+ readyTimeoutMs: deps.mcpReadyTimeoutMs, settleMs: deps.mcpSettleMs,
214
+ listGraceMs: deps.mcpListGraceMs, pollMs: deps.mcpPollMs,
215
+ };
174
216
  // Plan mode runs SynaBun's plan agent (OPENCODE_PLAN_AGENT; OpenCode's own
175
217
  // `plan` agent, the persona riding as `system`, when the config patch failed).
176
218
  let approvalMode = opencodeApprovalMode(brain.permissionMode);
@@ -219,17 +261,29 @@ export function createOpenCodeBrain({ session, sink, deps = {}, persona = '' } =
219
261
  }
220
262
 
221
263
  /** The serve's MCP servers as an `mcp_status` event (the runtime reads whether SynaBun, and so agent_route, is reachable). */
264
+ function sendMcpStatus(servers) { sink.send({ type: 'event', event: { type: 'system', subtype: 'mcp_status', servers } }); }
222
265
  async function reportMcpStatus() {
223
- try { const status = await client?.mcp?.status?.({ directory: cwd }); sink.send({ type: 'event', event: { type: 'system', subtype: 'mcp_status', servers: status?.data || status || [] } }); } catch {}
266
+ try { const status = await client?.mcp?.status?.({ directory: cwd }); sendMcpStatus(status?.data || status || []); } catch {}
224
267
  }
225
268
 
226
269
  async function start() {
227
270
  if (disposed) throw new Error('OpenCode brain disposed');
228
271
  if (startPromise) return startPromise;
229
272
  startPromise = (async () => {
230
- const xdgRoot = deps.setupOpencodeSidepanelConfig(termId, brain.mcpProfile || null);
231
- try { personaInstalled = patchOpenCodeAssistantConfig(xdgRoot, { persona, model: null, desktopGrant: deps.desktopGrant || null, routeGate: deps.routeGate || null, brainCredential: deps.brainCredential || null }); } catch { personaInstalled = false; }
232
- entry = await deps.ensureIsolatedServe(termId, { xdgRoot, keepAlive: true, mcpProfile: brain.mcpProfile || null, brainCredential: deps.brainCredential || null });
273
+ const xdgRoot = deps.setupOpencodeSidepanelConfig(termId, brain.tools ?? OPENCODE_BRAIN_TOOLS, { cwd: brain.cwd, scopeExternal: false });
274
+ // A brain whose config could not be patched would start with Code Mode on: SynaBun's tools only behind
275
+ // `execute`, which the route gate refuses before routing, so agent_route is unreachable. Refuse to start.
276
+ let patchError = null;
277
+ try { personaInstalled = patchOpenCodeAssistantConfig(xdgRoot, { persona, model: null, desktopGrant: deps.desktopGrant || null, routeGate: deps.routeGate || null, brainCredential: deps.brainCredential || null }); } catch (err) { personaInstalled = false; patchError = err; }
278
+ const unready = assistantConfigProblem(xdgRoot);
279
+ if (unready) {
280
+ throw new Error(`OpenCode Assistant could not start: ${unready}${patchError?.message ? ` (${patchError.message})` : ''}. Check Settings → OpenCode, or switch the Assistant to another provider.`);
281
+ }
282
+ // Its turns go out once SynaBun's tools are on the model's list (waitForSynabunTools).
283
+ synabunExpected = assistantSynabunConfigured(xdgRoot);
284
+ // An inherited OPENCODE_CONFIG_DIR would replace this isolated configuration (route-gate plugin,
285
+ // the assistant and plan agents): the brain's serve never reads it.
286
+ entry = await deps.ensureIsolatedServe(termId, { xdgRoot, keepAlive: true, tools: brain.tools ?? OPENCODE_BRAIN_TOOLS, brainCredential: deps.brainCredential || null, extraEnv: { OPENCODE_CONFIG_DIR: null } });
233
287
  if (!entry?.client) throw new Error('OpenCode isolated serve failed to start');
234
288
  client = entry.client;
235
289
  await client.waitUntilConnected?.(20_000);
@@ -253,7 +307,9 @@ export function createOpenCodeBrain({ session, sink, deps = {}, persona = '' } =
253
307
  kind: 'opencode',
254
308
  start,
255
309
  async sendUserTurn({ text, images = [], model = null, effort = null, permissionMode: mode = null, planMode: plan = null } = {}) {
310
+ const stop = stops;
256
311
  await start();
312
+ if (disposed || stops !== stop) return false;
257
313
  applyModes(mode, plan);
258
314
  turnActive = true;
259
315
  // The runtime passes the effective model/effort on every turn (the record
@@ -262,7 +318,6 @@ export function createOpenCodeBrain({ session, sink, deps = {}, persona = '' } =
262
318
  const request = {
263
319
  sessionID: sessionId, parts, model: normalizeOpenCodeModel(model || brain.model), directory: cwd,
264
320
  };
265
- if (request.model) lastTurnModel = request.model;
266
321
  if (effort) request.variant = String(effort);
267
322
  turnPlanned = planMode && !remoteReadOnly;
268
323
  if (turnPlanned && personaInstalled) {
@@ -277,14 +332,25 @@ export function createOpenCodeBrain({ session, sink, deps = {}, persona = '' } =
277
332
  else if (personaInstalled) request.agent = brain.agent || 'assistant';
278
333
  else { request.agent = brain.agent || 'build'; if (persona) request.system = persona; }
279
334
  if (entry) entry.lastUsed = Date.now();
280
- // SynaBun may have connected (or failed) since the last turn: the route gate follows it.
281
- await reportMcpStatus();
335
+ // SynaBun's tools on the model's list first (a fresh serve may still be connecting them); the route gate
336
+ // follows what was seen. Stopped meanwhile (its `aborted` is out) or disposed: the turn never goes out.
337
+ const controller = new AbortController();
338
+ waitingAbort = controller;
339
+ let ready;
340
+ try {
341
+ ready = await waitForSynabunTools(client, { ...mcpWait, directory: cwd, expected: synabunExpected,
342
+ signal: controller.signal, stopped: () => disposed || stops !== stop,
343
+ onStatus: sendMcpStatus,
344
+ onTimeout: (ms) => sink.send({ type: 'event', event: { type: 'system', subtype: 'status', text: synabunToolsTimeoutText(ms, 'brain') } }),
345
+ });
346
+ } finally { if (waitingAbort === controller) waitingAbort = null; }
347
+ if (!ready || disposed || stops !== stop) { turnActive = false; return false; }
282
348
  const response = await client.session.promptAsync(request);
283
349
  const status = Number(response?.status);
284
350
  if (Number.isFinite(status) && status >= 400) { turnActive = false; throw new Error(`OpenCode prompt failed (${status})`); }
285
351
  return true;
286
352
  },
287
- async abort() { if (client && sessionId) await client.session.abort({ sessionID: sessionId, directory: cwd }).catch(() => {}); emit({ type: 'aborted' }); },
353
+ async abort() { stops += 1; waitingAbort?.abort(); if (client && sessionId) await client.session.abort({ sessionID: sessionId, directory: cwd }).catch(() => {}); emit({ type: 'aborted' }); },
288
354
  async setPermissionMode(mode, { planMode: plan = null } = {}) { applyModes(mode, plan); sink.send({ type: 'mode_changed', mode: approvalMode, planMode }); },
289
355
  respondControl(requestId, response) {
290
356
  const pending = pendingControls.get(String(requestId));
@@ -296,12 +362,11 @@ export function createOpenCodeBrain({ session, sink, deps = {}, persona = '' } =
296
362
  : client.permission.reply({ requestID: body.requestID, reply: body.reply, directory: cwd });
297
363
  Promise.resolve(call).catch((error) => sink.send({ type: 'error', message: `OpenCode reply failed: ${error?.message || error}` }));
298
364
  },
299
- // OpenCode 1.18.34 answers v2.session.compact with 503 "not available yet":
300
- // compactOrSummarize then summarises on the model of the last turn.
365
+ // OpenCode compacts the session on the model it runs on.
301
366
  async compact() {
302
367
  await start();
303
368
  turnPlanned = false;
304
- await compactOrSummarize(client, { sessionID: sessionId, directory: cwd, model: lastTurnModel || normalizeOpenCodeModel(brain.model) });
369
+ await client.session.compact({ sessionID: sessionId, directory: cwd });
305
370
  },
306
371
  async mcpStatus() { await reportMcpStatus(); },
307
372
  async reconcileUsage(meter) {
@@ -337,6 +402,7 @@ export function createOpenCodeBrain({ session, sink, deps = {}, persona = '' } =
337
402
  async dispose() {
338
403
  if (disposed) return;
339
404
  disposed = true;
405
+ waitingAbort?.abort();
340
406
  if (keepAliveTimer) clearInterval(keepAliveTimer);
341
407
  if (unsubscribe) { try { unsubscribe(); } catch {} }
342
408
  try { deps.stopIsolatedServe(termId); } catch {}
@@ -1,19 +1,33 @@
1
- // SynaBun — an Assistant task run's browser policy as an OpenCode 2 plugin.
2
- // The same policy as ../opencode-browser-policy.js (read that file for what it
3
- // does and why), for OpenCode 2's plugin API: a directory OpenCode loads from
4
- // the config's `plugins` entry, whose default export is `{ id, setup(context) }`
5
- // with a hook on `tool.execute.before`; a refusal is thrown. OpenCode 2 calls
6
- // the shell tool `shell` and the sub-agent tool `subagent`: the policy, written
7
- // for OpenCode 1's names, is asked with `bash` and `task`.
1
+ // SynaBun — OpenCode plugin: the browser policy of an Assistant task run.
2
+ // Loaded only by the isolated `opencode serve` of a worker the Assistant
3
+ // dispatched (its opencode.json's `plugins` entry, { package: <this folder as a
4
+ // file URL>, options }, added by withOpenCodeBrowserPolicy from server.js
5
+ // setupOpencodeLoopConfig); loops, schedules and the OpenCode sidepanel never
6
+ // load it. OpenCode 2's plugin API: the default export is `{ id, setup(context) }`,
7
+ // registering `context.tool.hook('execute.before', …)`, which sees `{ tool, sessionID, input }`;
8
+ // a refusal is thrown as a plain Error, which OpenCode records as that tool's
9
+ // error, and the loop continues. Before every tool call it asks
10
+ // lib/browser-tool-policy.js here, with no server round trip. OpenCode calls the
11
+ // shell tool `shell` and the sub-agent tool `subagent`: the policy is asked with
12
+ // the names the rest of SynaBun knows (`bash`, `task`). Script paths resolve
13
+ // against the run's cwd (the plugin options), else the serve's directory; the
14
+ // options' `roots` name the registered code projects its test runners may also
15
+ // run in (the serve is sandboxed and cannot read SynaBun's data home: server.js
16
+ // lists them as the run starts). A
17
+ // policy module that cannot load fails closed: the failure is logged once, and
18
+ // the shell, webfetch, websearch and every tool that is not OpenCode's own or
19
+ // SynaBun's (another MCP server's, another plugin's) are refused with that
20
+ // reason until the run ends.
8
21
 
9
22
  // An absolute file URL: the policy loads from SynaBun's lib/ wherever OpenCode imports this file from.
10
23
  const POLICY_URL = new URL('../../browser-tool-policy.js', import.meta.url).href;
11
24
  const V1_NAMES = { shell: 'bash', subagent: 'task' };
12
25
 
13
- // OpenCode's own tools that run no command and fetch no page (OpenCode 1's names).
26
+ // OpenCode's own tools that run no command, fetch no page and open no browser (by the names above). The shell,
27
+ // webfetch, websearch and OpenCode's browser are not here; any other name belongs to an MCP server or a plugin.
14
28
  const OWN_TOOLS = new Set([
15
- 'read', 'list', 'glob', 'grep', 'edit', 'write', 'patch', 'multiedit', 'apply_patch', 'todowrite', 'todoread',
16
- 'task', 'skill', 'lsp', 'codesearch', 'question', 'batch', 'invalid', 'plan_exit', 'plan_enter',
29
+ 'read', 'list', 'glob', 'grep', 'edit', 'write', 'patch', 'todo', 'task', 'skill', 'lsp', 'question',
30
+ 'invalid', 'plan_exit', 'plan_enter',
17
31
  ]);
18
32
 
19
33
  export default {
@@ -24,20 +38,21 @@ export default {
24
38
  let loadError = null;
25
39
  try { policy = await import(POLICY_URL); } catch (error) { loadError = error; }
26
40
  const cwd = (typeof options.cwd === 'string' && options.cwd) || context?.location?.directory || process.cwd();
41
+ const roots = Array.isArray(options.roots) ? options.roots.filter((path) => typeof path === 'string') : [];
27
42
  if (typeof policy?.browserToolDenial !== 'function') {
28
43
  const why = loadError?.message || 'browserToolDenial is missing';
29
- console.error(`[SynaBun] browser policy could not load from ${POLICY_URL} (${why}); refusing bash, webfetch, websearch and other servers' tools for this run`);
44
+ console.error(`[SynaBun] browser policy could not load from ${POLICY_URL} (${why}); refusing the shell, webfetch, websearch and other servers' tools for this run`);
30
45
  await context.tool.hook('execute.before', async (call) => {
31
46
  const raw = String(call?.tool || '');
32
47
  const tool = V1_NAMES[raw] || raw;
33
48
  if (/^synabun_/i.test(tool) || OWN_TOOLS.has(tool.toLowerCase())) return;
34
- throw new Error(`SynaBun browser policy: ${tool} was refused — SynaBun's browser policy could not load (${why}), so no command, web tool or other server's tool can be checked for a browser launch. SynaBun's own tools (SynaBun_*) and file tools still work. Stop and report this.`);
49
+ throw new Error(`SynaBun browser policy: ${raw} was refused — SynaBun's browser policy could not load (${why}), so no command, web tool or other server's tool can be checked for a browser launch. SynaBun's own tools (SynaBun_*) and file tools still work. Stop and report this.`);
35
50
  });
36
51
  return;
37
52
  }
38
53
  await context.tool.hook('execute.before', async (call) => {
39
54
  const raw = String(call?.tool || '');
40
- const reason = policy.browserToolDenial(V1_NAMES[raw] || raw, call?.input || {}, { host: 'opencode', cwd });
55
+ const reason = policy.browserToolDenial(V1_NAMES[raw] || raw, call?.input || {}, { host: 'opencode', cwd, roots });
41
56
  if (reason) throw new Error(reason);
42
57
  });
43
58
  },
@@ -1,18 +1,54 @@
1
- // SynaBun — the Assistant's route gate as an OpenCode 2 plugin.
2
- // The same gate as ../opencode-route-gate.js (read that file for what it does
3
- // and why), for OpenCode 2's plugin API: a directory OpenCode loads from the
4
- // config's `plugins` entry ({ package: <this folder as a file URL>, options }),
5
- // whose default export is `{ id, setup(context) }`; a hook on
6
- // `tool.execute.before` sees `{ tool, sessionID, input }` and refuses a call by
7
- // throwing (OpenCode records the message as that tool's error). OpenCode 2
8
- // calls two tools by other names than OpenCode 1 (`shell` for bash,
9
- // `subagent` for task): the gate is asked with OpenCode 1's names, which the
10
- // rest of SynaBun knows. OpenCode 1 never reads this folder (it loads the
11
- // `plugin` entry), and OpenCode 2 never loads the 1.x file.
1
+ // SynaBun — OpenCode plugin for the Assistant's route gate.
2
+ // Loaded only by the assistant brain's isolated `opencode serve` (its
3
+ // opencode.json's `plugins` entry, { package: <this folder as a file URL>,
4
+ // options }, written by patchOpenCodeAssistantConfig); the OpenCode sidepanel
5
+ // never loads it. OpenCode 2's plugin API: the default export is
6
+ // `{ id, setup(context) }`; the hook it registers with
7
+ // `context.tool.hook('execute.before', …)` sees `{ tool, sessionID, input }`
8
+ // and refuses a call by throwing (OpenCode records
9
+ // the message as that tool's error and the loop continues; only
10
+ // permission/question rejections stop a turn). Before every tool call it asks
11
+ // the Neural Interface whether the call may run (the route gate, and plan
12
+ // mode's read-only policy on every session of this serve). When the gate
13
+ // allows, the user's own OpenCode permission rules apply exactly as before.
14
+ // OpenCode calls two tools by other names than the rest of SynaBun knows
15
+ // (`shell` for bash, `subagent` for task): the gate is asked with those names.
16
+ // The call's whole arguments travel (the remote denylist reads paths from
17
+ // them), marked inputComplete; arguments too big to send go as their
18
+ // command/action only. No answer at all (the server unreachable or slow)
19
+ // allows the call — except with `remote` (a WhatsApp session's serve), where a
20
+ // call SynaBun could not check is refused. An answer that is not a decision
21
+ // (the check itself refused, such as 403 PEER_REFUSED, an error status, a body
22
+ // without `allow`) refuses the call in every session and says what SynaBun
23
+ // answered: a refusal never passes for an allow.
24
+ //
25
+ // Every request carries the session's pin (X-Synabun-Assistant): this plugin
26
+ // runs inside the brain's `opencode serve`, an AI agent's process tree, which
27
+ // SynaBun's owner gate (lib/owner-authority.js) refuses unless the call says
28
+ // it is an agent's own. The per-session `token` in the body is what the route
29
+ // itself checks.
30
+ //
31
+ // On load it says hello: until the Neural Interface hears it, it enforces the
32
+ // gate reactively (interrupting a turn after an unrouted tool started), so a
33
+ // plugin that never loads still leaves the brain gated. A WhatsApp session
34
+ // runs no turn at all until the hello arrives.
12
35
 
13
36
  const MAX_INPUT_BYTES = 256 * 1024;
14
37
  const UNCHECKED = 'SynaBun could not check this call, so a WhatsApp session does not run it.';
15
38
  const V1_NAMES = { shell: 'bash', subagent: 'task' };
39
+ const PIN_HEADER = 'x-synabun-assistant';
40
+
41
+ /** The session id as a header value (printable ASCII only: fetch refuses anything else before sending). */
42
+ function pinOf(session) {
43
+ return String(session).replace(/[^\x21-\x7e]/g, '').slice(0, 200);
44
+ }
45
+
46
+ /** The refusal of a call whose check got an answer that is not a decision: what SynaBun answered, in short. */
47
+ function notADecision(status, body) {
48
+ const code = body && typeof body === 'object' && typeof body.code === 'string' ? ` ${body.code.slice(0, 64)}` : '';
49
+ const said = body && typeof body === 'object' && typeof body.error === 'string' ? `: ${body.error.slice(0, 300)}` : '';
50
+ return `SynaBun could not check this call (its route gate answered ${status}${code}${said}), so it did not run.`;
51
+ }
16
52
 
17
53
  /** { input, inputComplete }: the whole arguments when small enough to send, else their command/action. */
18
54
  function callArguments(args) {
@@ -34,24 +70,28 @@ export default {
34
70
  if (!url || !session || !token) return;
35
71
  const post = (body) => fetch(url, {
36
72
  method: 'POST',
37
- headers: { 'content-type': 'application/json' },
73
+ headers: { 'content-type': 'application/json', [PIN_HEADER]: pinOf(session) },
38
74
  body: JSON.stringify({ session, token, host: 'opencode', ...body }),
39
75
  signal: AbortSignal.timeout(5000),
40
76
  });
41
- // Never blocks loading; a lost hello only keeps the reactive fallback on.
42
- post({ hello: true }).catch(() => {});
77
+ // Never blocks loading; a lost hello only keeps the reactive fallback on (a refused one is said in the serve's log).
78
+ post({ hello: true }).then((response) => {
79
+ if (!response.ok) console.error(`[SynaBun] the route gate refused this serve's hello (${response.status}); every tool call will be refused until it answers`);
80
+ }).catch(() => {});
43
81
  await context.tool.hook('execute.before', async (call) => {
44
82
  const name = String(call?.tool || '');
45
- let decision = null;
83
+ let response;
46
84
  try {
47
- const response = await post({ tool: V1_NAMES[name] || name, providerSessionId: call?.sessionID || null, ...callArguments(call?.input) });
48
- decision = await response.json();
85
+ response = await post({ tool: V1_NAMES[name] || name, providerSessionId: call?.sessionID || null, ...callArguments(call?.input) });
49
86
  } catch {
50
87
  if (remote === true) throw new Error(UNCHECKED);
51
88
  return;
52
89
  }
53
- if (decision && decision.allow === false) throw new Error(String(decision.reason || 'SynaBun route gate: call agent_route first.'));
54
- if (remote === true && !(decision && decision.allow === true)) throw new Error(UNCHECKED);
90
+ let decision = null;
91
+ try { decision = await response.json(); } catch { decision = null; }
92
+ if (response.ok && decision && decision.allow === true) return;
93
+ if (response.ok && decision && decision.allow === false) throw new Error(String(decision.reason || 'SynaBun route gate: call agent_route first.'));
94
+ throw new Error(notADecision(response.status, decision));
55
95
  });
56
96
  },
57
97
  };
@@ -1,3 +1,5 @@
1
+ import { EXTERNAL_SERVER_LIMITATIONS } from './mcp-external-servers.js';
2
+ import { toolCatalogView } from '../../mcp-server/dist/services/tool-catalog.js';
1
3
  // ═══════════════════════════════════════════
2
4
  // SynaBun — Assistant model catalog (providers · models · tier · price · vision)
3
5
  // ═══════════════════════════════════════════
@@ -610,6 +612,7 @@ export function compactRow(row) {
610
612
  */
611
613
  export function createAssistantCatalog({
612
614
  fetchJson,
615
+ getExternalServers = () => [],
613
616
  claudePricing = null,
614
617
  codexPrice = (model) => listPrice('codex', model),
615
618
  listAccounts = () => ({}),
@@ -659,14 +662,11 @@ export function createAssistantCatalog({
659
662
  async function build({ deep = false } = {}) {
660
663
  // `deep` also drops the providers' own discovery caches (a CLI or app-server round trip).
661
664
  const fresh = deep ? '?refresh=1' : '';
662
- const [claudeModels, codexModels, opencodeProviders, projects, mcpProfile] = await Promise.all([
665
+ const [claudeModels, codexModels, opencodeProviders, projects] = await Promise.all([
663
666
  fetchJson(`/api/claude/models${fresh}`), fetchJson(`/api/codex/models${fresh}`), fetchJson('/api/opencode/providers/full'),
664
- fetchJson('/api/projects'), fetchJson('/api/mcp/profile'),
667
+ fetchJson('/api/projects'),
665
668
  ]);
666
669
  const filter = (() => { try { return readFilter() || {}; } catch { return {}; } })();
667
- const presets = mcpProfile?.presets && typeof mcpProfile.presets === 'object'
668
- ? Object.entries(mcpProfile.presets).map(([name, preset]) => ({ name, label: preset?.label || name, groups: preset?.groups || [], tools: preset?.tools || null }))
669
- : [];
670
670
  let accounts = {};
671
671
  try { accounts = listAccounts() || {}; } catch (error) { log('catalog:accounts-error', error?.message || String(error)); }
672
672
  // Models the user hid (Assistant → Models, Settings → OpenCode) never reach
@@ -737,8 +737,7 @@ export function createAssistantCatalog({
737
737
  opencodeListed: listedCounts.opencode,
738
738
  opencodeHidden: hidden.opencode,
739
739
  projects: pickArray(projects, ['projects']),
740
- profiles: presets,
741
- mcpProfiles: { presets, default: mcpProfile?.defaultProfile || mcpProfile?.profile || 'full' },
740
+ tools: { ...toolCatalogView(), servers: getExternalServers(pickArray(projects, ['projects'])), externalServerLimitations: EXTERNAL_SERVER_LIMITATIONS },
742
741
  accounts,
743
742
  // Efforts are per model (row.efforts); a provider's list is the union of its rows'.
744
743
  providers: [