synabun 2.0.0 → 2.0.7

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 (507) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/NOTICE +5 -0
  3. package/README.md +2 -1
  4. package/THIRD-PARTY-LICENSES.md +59 -0
  5. package/benchmarks/fixtures/browser/README.md +1 -1
  6. package/benchmarks/fixtures/desktop/README.md +1 -1
  7. package/docs/whatsapp.md +153 -18
  8. package/hooks/claude-code/pre-compact.mjs +11 -0
  9. package/hooks/claude-code/prompt-submit.mjs +60 -184
  10. package/hooks/claude-code/session-start.mjs +73 -16
  11. package/hooks/claude-code/shared.mjs +26 -3
  12. package/hooks/claude-code/stop.mjs +5 -5
  13. package/lib/child-path.js +303 -0
  14. package/lib/claude-hooks.js +2 -1
  15. package/lib/data-home-migration.js +2 -0
  16. package/lib/desktop-app.js +265 -0
  17. package/lib/external-tools.js +7 -3
  18. package/lib/log-redact.js +102 -0
  19. package/lib/packaged-runtime.js +138 -11
  20. package/lib/start-launcher.js +88 -1
  21. package/mcp-server/dist/http.d.ts +3 -1
  22. package/mcp-server/dist/http.d.ts.map +1 -1
  23. package/mcp-server/dist/http.js +15 -4
  24. package/mcp-server/dist/http.js.map +1 -1
  25. package/mcp-server/dist/index.d.ts.map +1 -1
  26. package/mcp-server/dist/index.js +9 -6
  27. package/mcp-server/dist/index.js.map +1 -1
  28. package/mcp-server/dist/services/budget-judgments.d.ts +108 -0
  29. package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -0
  30. package/mcp-server/dist/services/budget-judgments.js +153 -0
  31. package/mcp-server/dist/services/budget-judgments.js.map +1 -0
  32. package/mcp-server/dist/services/codex-config-heal.d.ts +5 -1
  33. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  34. package/mcp-server/dist/services/codex-config-heal.js +41 -4
  35. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  36. package/mcp-server/dist/services/identity.d.ts +11 -0
  37. package/mcp-server/dist/services/identity.d.ts.map +1 -1
  38. package/mcp-server/dist/services/identity.js +24 -0
  39. package/mcp-server/dist/services/identity.js.map +1 -1
  40. package/mcp-server/dist/services/neural-interface.d.ts +13 -0
  41. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  42. package/mcp-server/dist/services/neural-interface.js +34 -2
  43. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  44. package/mcp-server/dist/services/typesafe-config.d.ts +1 -1
  45. package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -1
  46. package/mcp-server/dist/services/typesafe-config.js +4 -1
  47. package/mcp-server/dist/services/typesafe-config.js.map +1 -1
  48. package/mcp-server/dist/tools/agents.d.ts +10 -7
  49. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  50. package/mcp-server/dist/tools/agents.js +34 -13
  51. package/mcp-server/dist/tools/agents.js.map +1 -1
  52. package/mcp-server/dist/tools/whatsapp-send-file.d.ts +17 -0
  53. package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -0
  54. package/mcp-server/dist/tools/whatsapp-send-file.js +44 -0
  55. package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -0
  56. package/mcp-server/dist/tui.js +2 -1
  57. package/mcp-server/dist/tui.js.map +1 -1
  58. package/mcp-server/dist/version.d.ts +5 -0
  59. package/mcp-server/dist/version.d.ts.map +1 -0
  60. package/mcp-server/dist/version.js +21 -0
  61. package/mcp-server/dist/version.js.map +1 -0
  62. package/mcp-server/package-lock.json +2 -2
  63. package/mcp-server/package.json +1 -3
  64. package/mcp-server/run.mjs +15 -9
  65. package/mcp-server/src/http.ts +18 -3
  66. package/mcp-server/src/index.ts +9 -6
  67. package/mcp-server/src/services/budget-judgments.ts +183 -0
  68. package/mcp-server/src/services/codex-config-heal.ts +35 -4
  69. package/mcp-server/src/services/identity.ts +27 -0
  70. package/mcp-server/src/services/neural-interface.ts +34 -2
  71. package/mcp-server/src/services/typesafe-config.ts +4 -1
  72. package/mcp-server/src/tools/agents.ts +33 -14
  73. package/mcp-server/src/tools/whatsapp-send-file.ts +51 -0
  74. package/mcp-server/src/tui.ts +2 -1
  75. package/mcp-server/src/version.ts +22 -0
  76. package/neural-interface/i18n/en.json +2126 -209
  77. package/neural-interface/i18n/pt-BR.json +2123 -207
  78. package/neural-interface/lib/account-usage.js +163 -0
  79. package/neural-interface/lib/assistant-api.js +316 -57
  80. package/neural-interface/lib/assistant-brains/claude.js +19 -2
  81. package/neural-interface/lib/assistant-brains/codex.js +3 -0
  82. package/neural-interface/lib/assistant-brains/opencode.js +17 -4
  83. package/neural-interface/lib/assistant-budget-planner.js +390 -0
  84. package/neural-interface/lib/assistant-budget-split.js +408 -0
  85. package/neural-interface/lib/assistant-budget.js +94 -95
  86. package/neural-interface/lib/assistant-catalog.js +179 -27
  87. package/neural-interface/lib/assistant-config.js +91 -14
  88. package/neural-interface/lib/assistant-dispatch.js +494 -138
  89. package/neural-interface/lib/assistant-hidden-models.js +393 -66
  90. package/neural-interface/lib/assistant-jev.js +26 -3
  91. package/neural-interface/lib/assistant-memory.js +24 -10
  92. package/neural-interface/lib/assistant-persona.js +73 -12
  93. package/neural-interface/lib/assistant-plan-permissions.js +8 -1
  94. package/neural-interface/lib/assistant-pricing.js +21 -6
  95. package/neural-interface/lib/assistant-route-accounts.js +14 -0
  96. package/neural-interface/lib/assistant-route-gate.js +19 -3
  97. package/neural-interface/lib/assistant-router.js +373 -42
  98. package/neural-interface/lib/assistant-runtime.js +488 -59
  99. package/neural-interface/lib/assistant-task-prompt.js +13 -3
  100. package/neural-interface/lib/assistant-test-density.js +142 -0
  101. package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
  102. package/neural-interface/lib/assistant-user-context.js +908 -0
  103. package/neural-interface/lib/assistant-user-knowledge.js +328 -0
  104. package/neural-interface/lib/assistant-user-learn.js +404 -0
  105. package/neural-interface/lib/assistant-user-threads.js +226 -0
  106. package/neural-interface/lib/assistant-user-writer.js +183 -0
  107. package/neural-interface/lib/augmented-path.js +317 -53
  108. package/neural-interface/lib/browser-background-tab.js +134 -0
  109. package/neural-interface/lib/browser-screenshot.js +678 -61
  110. package/neural-interface/lib/claude-accounts.js +342 -31
  111. package/neural-interface/lib/claude-agent-bridge.js +123 -9
  112. package/neural-interface/lib/claude-executable.js +6 -1
  113. package/neural-interface/lib/claude-model-catalog.js +33 -5
  114. package/neural-interface/lib/claude-panel-auth.js +221 -0
  115. package/neural-interface/lib/claude-profile-api-key.js +62 -0
  116. package/neural-interface/lib/claude-temporary.js +46 -7
  117. package/neural-interface/lib/cli-install-api.js +137 -0
  118. package/neural-interface/lib/cli-install.js +797 -0
  119. package/neural-interface/lib/cli-path.js +130 -0
  120. package/neural-interface/lib/cli-resolver.js +744 -0
  121. package/neural-interface/lib/cli-update-planner.js +143 -7
  122. package/neural-interface/lib/cloudflared/install.js +533 -0
  123. package/neural-interface/lib/cloudflared/manager.js +545 -0
  124. package/neural-interface/lib/cloudflared/pins.js +115 -0
  125. package/neural-interface/lib/cloudflared/pins.json +63 -0
  126. package/neural-interface/lib/cloudflared/tunnel.js +265 -0
  127. package/neural-interface/lib/codex-account-auth.js +178 -0
  128. package/neural-interface/lib/codex-accounts.js +489 -0
  129. package/neural-interface/lib/codex-capabilities.js +2 -1
  130. package/neural-interface/lib/codex-compat-rpc.js +8 -2
  131. package/neural-interface/lib/codex-mcp-approvals.js +5 -1
  132. package/neural-interface/lib/codex-panel-auth.js +62 -0
  133. package/neural-interface/lib/codex-session-settings.js +1 -1
  134. package/neural-interface/lib/codex-settings-schema.js +120 -0
  135. package/neural-interface/lib/desktop/config.js +3 -0
  136. package/neural-interface/lib/editor-completion-endpoint.js +264 -0
  137. package/neural-interface/lib/editor-completion-opencode.js +436 -0
  138. package/neural-interface/lib/editor-completion.js +714 -0
  139. package/neural-interface/lib/editor-config.js +149 -0
  140. package/neural-interface/lib/editor-files-api.js +268 -0
  141. package/neural-interface/lib/editor-git-api.js +134 -0
  142. package/neural-interface/lib/file-explorer-api.js +1039 -0
  143. package/neural-interface/lib/file-explorer-system.js +355 -0
  144. package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
  145. package/neural-interface/lib/file-explorer-watch.js +239 -0
  146. package/neural-interface/lib/git-bash/bzip2.js +339 -0
  147. package/neural-interface/lib/git-bash/claude-settings.js +73 -0
  148. package/neural-interface/lib/git-bash/extract-worker.js +34 -0
  149. package/neural-interface/lib/git-bash/install.js +529 -0
  150. package/neural-interface/lib/git-bash/licenses/COPYING +360 -0
  151. package/neural-interface/lib/git-bash/licenses/LGPL-2.1 +511 -0
  152. package/neural-interface/lib/git-bash/manager.js +268 -0
  153. package/neural-interface/lib/git-bash/pins.js +206 -0
  154. package/neural-interface/lib/git-bash/pins.json +34 -0
  155. package/neural-interface/lib/git-bash/resolver.js +194 -0
  156. package/neural-interface/lib/git-diff-api.js +103 -0
  157. package/neural-interface/lib/git-read.js +234 -0
  158. package/neural-interface/lib/greeting/api.js +89 -0
  159. package/neural-interface/lib/greeting/budgets.js +90 -0
  160. package/neural-interface/lib/greeting/engine.js +461 -0
  161. package/neural-interface/lib/greeting/facts.js +320 -0
  162. package/neural-interface/lib/greeting/sources.js +252 -0
  163. package/neural-interface/lib/greeting/writer.js +139 -0
  164. package/neural-interface/lib/http-guards.js +189 -13
  165. package/neural-interface/lib/install-details.js +145 -0
  166. package/neural-interface/lib/machine-names.js +88 -0
  167. package/neural-interface/lib/mcp-installer.js +14 -2
  168. package/neural-interface/lib/memory-api.js +31 -7
  169. package/neural-interface/lib/native-loop-providers.js +26 -8
  170. package/neural-interface/lib/native-loop-runtime.js +9 -4
  171. package/neural-interface/lib/os-path.js +319 -0
  172. package/neural-interface/lib/plugin-installer.js +4 -2
  173. package/neural-interface/lib/project-storage.js +5 -7
  174. package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
  175. package/neural-interface/lib/provider-panel-terminal-auth.js +95 -0
  176. package/neural-interface/lib/pty-host/core.js +2 -1
  177. package/neural-interface/lib/pty-host/manager.js +27 -2
  178. package/neural-interface/lib/remote-policy.js +20 -11
  179. package/neural-interface/lib/request-gate.js +589 -0
  180. package/neural-interface/lib/session-title-generator.js +4 -3
  181. package/neural-interface/lib/system-about.js +340 -0
  182. package/neural-interface/lib/system-activity.js +66 -0
  183. package/neural-interface/lib/terminal-env.js +11 -3
  184. package/neural-interface/lib/terminal-path.js +55 -0
  185. package/neural-interface/lib/ui-blob-api.js +108 -0
  186. package/neural-interface/lib/ui-blob-store.js +68 -0
  187. package/neural-interface/lib/virtual-socket.js +12 -1
  188. package/neural-interface/lib/whatsapp/baileys-adapter.js +165 -45
  189. package/neural-interface/lib/whatsapp/bridge.js +78 -34
  190. package/neural-interface/lib/whatsapp/commands.js +13 -14
  191. package/neural-interface/lib/whatsapp/config.js +134 -20
  192. package/neural-interface/lib/whatsapp/fake-baileys.js +99 -14
  193. package/neural-interface/lib/whatsapp/host-core.js +168 -35
  194. package/neural-interface/lib/whatsapp/inbound.js +5 -3
  195. package/neural-interface/lib/whatsapp/local-file.js +524 -0
  196. package/neural-interface/lib/whatsapp/manager.js +92 -32
  197. package/neural-interface/lib/whatsapp/paths.js +11 -1
  198. package/neural-interface/lib/whatsapp/protocol.js +65 -5
  199. package/neural-interface/lib/whatsapp/redact.js +63 -0
  200. package/neural-interface/lib/whatsapp/service.js +343 -91
  201. package/neural-interface/lib/whatsapp/transcription.js +463 -0
  202. package/neural-interface/package-lock.json +701 -2
  203. package/neural-interface/package.json +13 -2
  204. package/neural-interface/public/index.html +2 -0
  205. package/neural-interface/public/index2d.html +2 -0
  206. package/neural-interface/public/install-app.html +9 -6
  207. package/neural-interface/public/offline.html +19 -3
  208. package/neural-interface/public/onboarding.html +107 -2
  209. package/neural-interface/public/shared/account-i18n.js +7 -0
  210. package/neural-interface/public/shared/account-menu.js +539 -0
  211. package/neural-interface/public/shared/account-usage.js +19 -0
  212. package/neural-interface/public/shared/account-view.js +335 -0
  213. package/neural-interface/public/shared/agent-runtime-options.js +50 -14
  214. package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
  215. package/neural-interface/public/shared/assistant/asst-api.js +32 -2
  216. package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
  217. package/neural-interface/public/shared/assistant/asst-brain-picker.js +430 -81
  218. package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
  219. package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
  220. package/neural-interface/public/shared/assistant/asst-mascot.js +140 -61
  221. package/neural-interface/public/shared/assistant/asst-menu.js +27 -11
  222. package/neural-interface/public/shared/assistant/asst-panel.js +198 -42
  223. package/neural-interface/public/shared/assistant/asst-render.js +197 -23
  224. package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
  225. package/neural-interface/public/shared/assistant/asst-route.js +95 -343
  226. package/neural-interface/public/shared/assistant/asst-routes-editor.js +534 -0
  227. package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
  228. package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
  229. package/neural-interface/public/shared/assistant/asst-slash.js +1 -1
  230. package/neural-interface/public/shared/assistant/asst-state.js +186 -2
  231. package/neural-interface/public/shared/assistant/asst-styles.js +247 -11
  232. package/neural-interface/public/shared/blob-store.js +111 -22
  233. package/neural-interface/public/shared/cdx/cdx-commands.js +4 -1
  234. package/neural-interface/public/shared/cdx/cdx-features.js +22 -14
  235. package/neural-interface/public/shared/cdx/cdx-icons.js +2 -0
  236. package/neural-interface/public/shared/cdx/cdx-panel.js +80 -118
  237. package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
  238. package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
  239. package/neural-interface/public/shared/cdx/cdx-styles.js +164 -107
  240. package/neural-interface/public/shared/cdx/cdx-tabs.js +348 -668
  241. package/neural-interface/public/shared/cdx/cdx-ws.js +2 -0
  242. package/neural-interface/public/shared/claude-account-view.js +3 -0
  243. package/neural-interface/public/shared/cli-install-view.js +392 -0
  244. package/neural-interface/public/shared/cli-status.js +282 -22
  245. package/neural-interface/public/shared/constants.js +6 -1
  246. package/neural-interface/public/shared/cp/cp-events.js +2 -1
  247. package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
  248. package/neural-interface/public/shared/cp/cp-styles.js +61 -0
  249. package/neural-interface/public/shared/desktop-bridge.js +39 -0
  250. package/neural-interface/public/shared/desktop-titlebar-page.js +59 -0
  251. package/neural-interface/public/shared/desktop-titlebar-view.js +122 -0
  252. package/neural-interface/public/shared/desktop-titlebar.js +153 -0
  253. package/neural-interface/public/shared/desktop-update-view.js +462 -0
  254. package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
  255. package/neural-interface/public/shared/editor/editor-ai.js +619 -0
  256. package/neural-interface/public/shared/editor/editor-commands.js +212 -0
  257. package/neural-interface/public/shared/editor/editor-compare.js +103 -0
  258. package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
  259. package/neural-interface/public/shared/editor/editor-doc.js +257 -0
  260. package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
  261. package/neural-interface/public/shared/editor/editor-git.js +95 -0
  262. package/neural-interface/public/shared/editor/editor-languages.js +156 -0
  263. package/neural-interface/public/shared/editor/editor-lint.js +229 -0
  264. package/neural-interface/public/shared/editor/editor-loader.js +51 -0
  265. package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
  266. package/neural-interface/public/shared/editor/editor-menu.js +233 -0
  267. package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
  268. package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
  269. package/neural-interface/public/shared/editor/editor-palette.js +125 -0
  270. package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
  271. package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
  272. package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
  273. package/neural-interface/public/shared/editor/editor-safety.js +500 -0
  274. package/neural-interface/public/shared/editor/editor-search.js +352 -0
  275. package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
  276. package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
  277. package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
  278. package/neural-interface/public/shared/editor/editor-theme.js +244 -0
  279. package/neural-interface/public/shared/editor/editor-view.js +332 -0
  280. package/neural-interface/public/shared/explorer/fx-api.js +216 -0
  281. package/neural-interface/public/shared/explorer/fx-links.js +204 -0
  282. package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
  283. package/neural-interface/public/shared/explorer/fx-main.js +865 -0
  284. package/neural-interface/public/shared/explorer/fx-names.js +87 -0
  285. package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
  286. package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
  287. package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
  288. package/neural-interface/public/shared/file-links.js +339 -0
  289. package/neural-interface/public/shared/git-bash-view.js +145 -0
  290. package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
  291. package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
  292. package/neural-interface/public/shared/html-shell.js +161 -165
  293. package/neural-interface/public/shared/i18n.js +19 -1
  294. package/neural-interface/public/shared/install-details-view.js +86 -0
  295. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
  296. package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
  297. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +131 -17
  298. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
  299. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +22 -0
  300. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +158 -0
  301. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
  302. package/neural-interface/public/shared/pwa-install-page.js +32 -2
  303. package/neural-interface/public/shared/pwa-install.css +3 -3
  304. package/neural-interface/public/shared/pwa-install.js +100 -1
  305. package/neural-interface/public/shared/settings/settings-about-pane.js +271 -0
  306. package/neural-interface/public/shared/settings/settings-about.js +254 -0
  307. package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
  308. package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
  309. package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
  310. package/neural-interface/public/shared/settings/settings-ia.js +33 -4
  311. package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
  312. package/neural-interface/public/shared/showcase-privacy.js +489 -0
  313. package/neural-interface/public/shared/start-bridge.js +238 -19
  314. package/neural-interface/public/shared/storage.js +30 -3
  315. package/neural-interface/public/shared/styles.css +1710 -770
  316. package/neural-interface/public/shared/synabun-mascot.js +68 -58
  317. package/neural-interface/public/shared/tunnel-view.js +167 -0
  318. package/neural-interface/public/shared/ui-claude-panel.js +369 -229
  319. package/neural-interface/public/shared/ui-cli-banner.js +355 -0
  320. package/neural-interface/public/shared/ui-detail.js +15 -0
  321. package/neural-interface/public/shared/ui-explorer.js +22 -0
  322. package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
  323. package/neural-interface/public/shared/ui-file-links.js +103 -0
  324. package/neural-interface/public/shared/ui-git-bash.js +124 -0
  325. package/neural-interface/public/shared/ui-help.js +26 -0
  326. package/neural-interface/public/shared/ui-invite.js +126 -116
  327. package/neural-interface/public/shared/ui-judgments.js +10 -1
  328. package/neural-interface/public/shared/ui-keybinds.js +4 -0
  329. package/neural-interface/public/shared/ui-loading.js +187 -19
  330. package/neural-interface/public/shared/ui-menubar.js +25 -33
  331. package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
  332. package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
  333. package/neural-interface/public/shared/ui-navbar.js +48 -22
  334. package/neural-interface/public/shared/ui-provider-auth.js +104 -0
  335. package/neural-interface/public/shared/ui-provider-terminal-auth.js +223 -0
  336. package/neural-interface/public/shared/ui-sessions.js +9 -3
  337. package/neural-interface/public/shared/ui-settings.js +509 -490
  338. package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
  339. package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
  340. package/neural-interface/public/shared/ui-sync.js +22 -0
  341. package/neural-interface/public/shared/ui-terminal.js +40 -5
  342. package/neural-interface/public/shared/ui-tool-updates.js +5 -0
  343. package/neural-interface/public/shared/ui-tunnel.js +130 -0
  344. package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
  345. package/neural-interface/public/shared/ui-update.js +594 -5
  346. package/neural-interface/public/shared/ui-whatsapp.js +21 -6
  347. package/neural-interface/public/shared/ui-whiteboard.js +12 -0
  348. package/neural-interface/public/shared/ui-worker-end.js +245 -0
  349. package/neural-interface/public/shared/ui-workspaces.js +41 -27
  350. package/neural-interface/public/shared/whatsapp/wa-view.js +51 -8
  351. package/neural-interface/public/shared/worker-end.js +253 -0
  352. package/neural-interface/public/sw.js +2 -2
  353. package/neural-interface/public/variant/2d/main.js +10 -1
  354. package/neural-interface/public/variant/3d/main.js +10 -1
  355. package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
  356. package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
  357. package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
  358. package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
  359. package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
  360. package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
  361. package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
  362. package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
  363. package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
  364. package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
  365. package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
  366. package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
  367. package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
  368. package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
  369. package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
  370. package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
  371. package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
  372. package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
  373. package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
  374. package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
  375. package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
  376. package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
  377. package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
  378. package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
  379. package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
  380. package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
  381. package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
  382. package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
  383. package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
  384. package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
  385. package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
  386. package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
  387. package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
  388. package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
  389. package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
  390. package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
  391. package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
  392. package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
  393. package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
  394. package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
  395. package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
  396. package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
  397. package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
  398. package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
  399. package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
  400. package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
  401. package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
  402. package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
  403. package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
  404. package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
  405. package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
  406. package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
  407. package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
  408. package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
  409. package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
  410. package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
  411. package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
  412. package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
  413. package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
  414. package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
  415. package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
  416. package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
  417. package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
  418. package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
  419. package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
  420. package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
  421. package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
  422. package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
  423. package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
  424. package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
  425. package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
  426. package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
  427. package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
  428. package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
  429. package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
  430. package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
  431. package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
  432. package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
  433. package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
  434. package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
  435. package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
  436. package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
  437. package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
  438. package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
  439. package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
  440. package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
  441. package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
  442. package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
  443. package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
  444. package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
  445. package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
  446. package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
  447. package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
  448. package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
  449. package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
  450. package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
  451. package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
  452. package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
  453. package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
  454. package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
  455. package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
  456. package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
  457. package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
  458. package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
  459. package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
  460. package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
  461. package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
  462. package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
  463. package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
  464. package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
  465. package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
  466. package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
  467. package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
  468. package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
  469. package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
  470. package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
  471. package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
  472. package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
  473. package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
  474. package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
  475. package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
  476. package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
  477. package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
  478. package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
  479. package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
  480. package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
  481. package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
  482. package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
  483. package/neural-interface/scripts/build-vendor.mjs +78 -2
  484. package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
  485. package/neural-interface/server.js +2085 -1629
  486. package/neural-interface/templates/rulesets/README.md +1 -1
  487. package/package.json +5 -8
  488. package/setup.js +53 -15
  489. package/benchmarks/browser-live-results.json +0 -79
  490. package/benchmarks/browser-v2-results.json +0 -258
  491. package/neural-interface/lib/codex-capabilities.d.ts +0 -41
  492. package/neural-interface/public/category-logos/claude.svg +0 -1
  493. package/neural-interface/public/category-logos/gemini-color.svg +0 -1
  494. package/neural-interface/public/category-logos/openai.svg +0 -1
  495. package/neural-interface/public/category-logos/synabun.png +0 -0
  496. package/neural-interface/public/claude.svg +0 -1
  497. package/neural-interface/public/gallery.svg +0 -6
  498. package/neural-interface/public/leonardoai.svg +0 -1
  499. package/neural-interface/public/mcp.svg +0 -4
  500. package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
  501. package/neural-interface/public/pin-svgrepo-com.svg +0 -25
  502. package/neural-interface/public/sessionicon.svg +0 -8
  503. package/neural-interface/public/toolbar-album.svg +0 -8
  504. package/neural-interface/public/toolbar-question.svg +0 -6
  505. package/neural-interface/public/toolbar-sessions.svg +0 -6
  506. package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
  507. package/skills/synabun/synabunicon.png +0 -0
@@ -7,8 +7,9 @@
7
7
  * Having both forms defines the same TOML key twice, so Codex rejects the
8
8
  * configuration and starts with defaults.
9
9
  *
10
- * This module deliberately edits only the SynaBun MCP tables. All unrelated
11
- * Codex configuration is left byte-for-byte intact. File writes are atomic,
10
+ * This module edits the SynaBun MCP tables and the known ignored feature keys
11
+ * emitted by its old Settings writer. All unrelated configuration is left
12
+ * byte-for-byte intact. Repairs keep a backup; file writes are atomic,
12
13
  * preserve the original mode, and are idempotent.
13
14
  */
14
15
  import {
@@ -56,6 +57,7 @@ export interface CodexMcpDefinition {
56
57
  }
57
58
 
58
59
  export type HealReason =
60
+ | 'removed_ignored_settings'
59
61
  | 'removed_duplicate_inline_env'
60
62
  | 'normalized_inline_env'
61
63
  | 'removed_managed_profile_override'
@@ -364,15 +366,20 @@ export function healCodexConfig(configPath: string): HealResult {
364
366
  }
365
367
 
366
368
  const before = inspectCodexConfigContent(content);
367
- if (!before.connected && !before.issues.length) {
369
+ const cleaned = removeIgnoredCodexSettings(content);
370
+ if (cleaned === content && !before.connected && !before.issues.length) {
368
371
  return { healed: false, reason: 'no_synabun_section' };
369
372
  }
370
- const repaired = repairCodexConfigContent(content);
373
+ const repaired = repairCodexConfigContent(cleaned);
374
+ if (cleaned !== content) repaired.reason = 'removed_ignored_settings';
371
375
  if (!repaired.reason || repaired.content === content) {
372
376
  return { healed: false, reason: 'no_duplicate', inspection: before };
373
377
  }
374
378
 
375
379
  try {
380
+ // Keep the exact original, including unrelated tables and its permissions.
381
+ const backup = `${configPath}.synabun-backup-${Date.now()}-${process.pid}`;
382
+ copyFileSync(configPath, backup);
376
383
  atomicWrite(configPath, repaired.content);
377
384
  } catch (err) {
378
385
  return { healed: false, reason: 'write_error', error: err instanceof Error ? err.message : String(err) };
@@ -384,6 +391,30 @@ export function healCodexConfig(configPath: string): HealResult {
384
391
  };
385
392
  }
386
393
 
394
+ /** Narrow repair for keys emitted by the old Settings writer. These literal
395
+ * dotted keys are ignored by Codex; personality's feature flag is removed.
396
+ * Do not sweep arbitrary user flags or normalize unrelated TOML. */
397
+ export function removeIgnoredCodexSettings(content: string): string {
398
+ let section = '';
399
+ let multiline: string | null = null;
400
+ return content.split(/(?<=\n)/).filter(line => {
401
+ // Avoid treating text inside TOML multiline strings as configuration.
402
+ const delimiters = line.match(/"""|'''/g) || [];
403
+ if (multiline) {
404
+ if (delimiters.filter(d => d === multiline).length % 2) multiline = null;
405
+ return true;
406
+ }
407
+ for (const delimiter of delimiters) if (delimiters.filter(d => d === delimiter).length % 2) multiline = delimiter;
408
+ const header = /^\s*\[([^\]]+)\]\s*(?:#.*)?$/.exec(line.trim());
409
+ if (header) section = header[1].replace(/["']/g, '').trim();
410
+ else if (/^\s*\[/.test(line)) section = '__other_table__';
411
+ if (!section && /^\s*["']features\.(?:personality|fast_mode|apps|unified_exec)["']\s*=\s*(?:true|false)\s*(?:#.*)?$/.test(line.trim())) return false;
412
+ if ((!section && /^\s*features\.personality\s*=\s*(?:true|false)\s*(?:#.*)?$/.test(line.trim()))
413
+ || (section === 'features' && /^\s*(?:personality|"personality"|'personality')\s*=\s*(?:true|false)\s*(?:#.*)?$/.test(line.trim()))) return false;
414
+ return true;
415
+ }).join('');
416
+ }
417
+
387
418
  export function upsertCodexMcpConfig(configPath: string, definition: CodexMcpDefinition): CodexConfigInspection {
388
419
  let content = '';
389
420
  if (existsSync(configPath)) content = readFileSync(configPath, 'utf-8');
@@ -173,6 +173,33 @@ export function callerDesktopGrant(): string | null {
173
173
  return sanitizeDesktopGrant(process.env.SYNABUN_DESKTOP_GRANT);
174
174
  }
175
175
 
176
+ /** WhatsApp brain credentials look exactly like the ones the Neural Interface mints (assistant-runtime.js mintBrainCredential). */
177
+ export function sanitizeBrainCredential(v: string | undefined | null): string | null {
178
+ return typeof v === 'string' && /^sbb_[A-Za-z0-9_-]{43}$/.test(v.trim()) ? v.trim() : null;
179
+ }
180
+
181
+ // The brain credential of the request being handled (whatsapp_send_file). Its own context per HTTP
182
+ // request, never a field of the CallerIdentity: identities are shared by every request that names the
183
+ // same X-Synabun-Terminal pin (not a secret), so a credential kept there could be read by a concurrent
184
+ // request that only forged the pin.
185
+ const credentialAls = new AsyncLocalStorage<string | null>();
186
+
187
+ /** Run `fn` with this request's brain credential (http.ts, around every transport.handleRequest). */
188
+ export function runWithBrainCredential<T>(credential: string | null, fn: () => T): T {
189
+ return credentialAls.run(credential, fn);
190
+ }
191
+
192
+ /**
193
+ * The WhatsApp brain credential of the current caller (whatsapp_send_file sends it on its one request
194
+ * and nowhere else): the request's own header over HTTP (X-Synabun-Brain-Token, the Claude brain),
195
+ * SYNABUN_BRAIN_TOKEN for stdio children (the Codex and OpenCode brains). Never the host process's
196
+ * env for an HTTP caller.
197
+ */
198
+ export function callerBrainCredential(): string | null {
199
+ if (httpMode) return credentialAls.getStore() ?? null;
200
+ return sanitizeBrainCredential(process.env.SYNABUN_BRAIN_TOKEN);
201
+ }
202
+
176
203
  /**
177
204
  * Role of the current caller: the ALS identity's role over HTTP (derived from
178
205
  * X-Synabun-Role on every request), or SYNABUN_ROLE for stdio children —
@@ -132,7 +132,8 @@ async function requestOnce(
132
132
  method: string,
133
133
  path: string,
134
134
  body?: Record<string, unknown>,
135
- timeout = DEFAULT_TIMEOUT
135
+ timeout = DEFAULT_TIMEOUT,
136
+ extraHeaders: Record<string, string> = {},
136
137
  ): Promise<NiResponse> {
137
138
  const cancellation = browserCancellation.getStore();
138
139
  const callerSignal = cancellation?.signal;
@@ -166,6 +167,8 @@ async function requestOnce(
166
167
  headers['X-Synabun-Deadline'] = String(deadline);
167
168
  // Lets the server refuse advisory payloads and auto-heal for a batch step on its own authority.
168
169
  if (batchContext.getStore()) headers['X-Synabun-Batch'] = '1';
170
+ // One request's own headers (whatsappSendFile's brain credential): never sent on any other request.
171
+ for (const [name, value] of Object.entries(extraHeaders)) if (value) headers[name] = value;
169
172
  const opts: RequestInit = {
170
173
  method,
171
174
  headers,
@@ -199,7 +202,9 @@ async function requestOnce(
199
202
  if (controller.signal.aborted || /abort/i.test(msg)) {
200
203
  return { error: `Request timed out after ${timeout}ms; outcome may be uncertain. Inspect the page before retrying.`, code: 'REQUEST_TIMEOUT', outcome: 'uncertain' };
201
204
  }
202
- return { error: `Neural Interface unreachable: ${msg}. Is the Neural Interface server running?`, code: 'TRANSPORT_ERROR', outcome: 'uncertain' };
205
+ // The system's own code for it (fetch puts it on `cause`): ECONNREFUSED means the request never left.
206
+ const cause = (err as { cause?: { code?: unknown }; code?: unknown } | null)?.cause?.code ?? (err as { code?: unknown } | null)?.code ?? /\b(E[A-Z_]{3,})\b/.exec(msg)?.[1];
207
+ return { error: `Neural Interface unreachable: ${msg}. Is the Neural Interface server running?`, code: 'TRANSPORT_ERROR', outcome: 'uncertain', ...(typeof cause === 'string' ? { transportCode: cause } : {}) };
203
208
  } finally {
204
209
  clearTimeout(timer);
205
210
  callerSignal?.removeEventListener('abort', cancel);
@@ -1486,3 +1491,30 @@ export async function assistantKillAll(body: { assistantSessionId?: string; work
1486
1491
  export async function assistantFocus(runId: string, focus: boolean): Promise<NiResponse> {
1487
1492
  return request('POST', assistantRunPath(runId, '/focus'), { focus });
1488
1493
  }
1494
+
1495
+ // whatsapp_send_file: the server's own deadline for the send is 120 s (lib/whatsapp/manager.js); this
1496
+ // one outlives it so a server timeout is reported as the server's.
1497
+ const WHATSAPP_SEND_FILE_TIMEOUT = 130_000;
1498
+ export const WHATSAPP_SEND_FILE_UNCERTAIN = 'SynaBun did not answer in time: the file may or may not have arrived. Do not send it again unless the user asks.';
1499
+ export const WHATSAPP_SEND_FILE_LOST = 'SynaBun\'s answer was lost: the file may or may not have arrived. Do not send it again unless the user asks.';
1500
+ // The request never reached SynaBun (nothing listens, no route to it): nothing was sent, for certain.
1501
+ const WHATSAPP_SEND_FILE_NEVER_LEFT = new Set(['ECONNREFUSED', 'ENOTFOUND', 'EAI_AGAIN', 'EHOSTUNREACH', 'ENETUNREACH', 'EADDRNOTAVAIL']);
1502
+
1503
+ /**
1504
+ * whatsapp_send_file → POST /api/assistant/whatsapp/send-file: exactly one request (requestOnce, no
1505
+ * retry of any kind), because a timeout may come after WhatsApp took the file. `credential` (the
1506
+ * WhatsApp brain's own, identity.ts callerBrainCredential) rides on this request alone; without it
1507
+ * the server refuses (403).
1508
+ */
1509
+ export async function whatsappSendFile(body: { path: string; caption?: string; as?: string }, credential: string | null): Promise<NiResponse> {
1510
+ const result = await requestOnce('POST', '/api/assistant/whatsapp/send-file', body as Record<string, unknown>, WHATSAPP_SEND_FILE_TIMEOUT,
1511
+ credential ? { 'X-Synabun-Brain-Token': credential } : {});
1512
+ if (result.code === 'REQUEST_TIMEOUT') return { ok: false, code: 'TIMEOUT', uncertain: true, error: WHATSAPP_SEND_FILE_UNCERTAIN };
1513
+ // Any other failure of the transport after the request left (a reset socket, an answer that is not JSON) may
1514
+ // come after WhatsApp took the file: as uncertain as a timeout, never "is the server running?".
1515
+ if (result.code === 'TRANSPORT_ERROR') {
1516
+ if (WHATSAPP_SEND_FILE_NEVER_LEFT.has(String(result.transportCode))) return { ok: false, code: 'UNREACHABLE', error: 'SynaBun is not reachable (is it running?): nothing was sent.' };
1517
+ return { ok: false, code: 'TRANSPORT_ERROR', uncertain: true, error: WHATSAPP_SEND_FILE_LOST };
1518
+ }
1519
+ return result;
1520
+ }
@@ -31,7 +31,7 @@ export const SURFACES = [
31
31
  'browser-page-state', 'browser-target', 'browser-social',
32
32
  'task-boundary', 'session-lookup', 'user-learning', 'claim-check',
33
33
  'edit-stale', 'supersession', 'compact-digest', 'plan-conflict',
34
- 'worker-outcome', 'worker-claim', 'desktop-target',
34
+ 'worker-outcome', 'worker-claim', 'desktop-target', 'budget-split',
35
35
  ] as const;
36
36
  export type SurfaceName = typeof SURFACES[number];
37
37
 
@@ -94,6 +94,7 @@ export const SURFACE_META: Record<SurfaceName, SurfaceMeta> = {
94
94
  'worker-outcome': { label: 'Worker outcome', description: 'Assistant workers: reads done / blocked / needs input from a final message that has no ## Result block (instead of spending a retry turn), and what kept a failed or blocked run from finishing (capability, access, needs the user, transient), which decides whether escalation is offered.', fallback: 'one result-retry worker turn; every failed, blocked or result-less run offers a one-tier escalation' },
95
95
  'worker-claim': { label: 'Worker claim', description: 'Assistant workers: whether a run reported done while claiming success (tests pass, fixed, verified) that its own command output does not show. Flags the run so the Assistant verifies before reporting; asked only when the turn exposed commands or tool results.', fallback: 'no check', ridesWith: 'worker-outcome' },
96
96
  'desktop-target': { label: 'Desktop target', description: 'Computer use: ranks the visible controls of the front window against a computer_ax intent. Advisory; one low-risk press by intent only behind the desktop benchmark gate.', fallback: 'the full accessibility tree, or a word-overlap ranking when an intent is given' },
97
+ 'budget-split': { label: 'Budget split', description: 'Assistant sessions with a budget: how much of the money still to be spent will land on each provider (Claude Code, Codex, OpenCode), asked at the first route and at checkpoints (a share used up, a budget change, a run ending). It only divides what the user allowed: shares never add up to more than is left, and it never picks a model.', fallback: 'weights from the saved routes, the session\'s tasks and live runs, times the list price of the routed models; the brain keeps its reserve either way' },
97
98
  };
98
99
 
99
100
  const SURFACE_DEFAULTS: Record<SurfaceName, SurfaceSettings> = {
@@ -132,6 +133,8 @@ const SURFACE_DEFAULTS: Record<SurfaceName, SurfaceSettings> = {
132
133
  // Rider of worker-outcome. Flags a run for verification, so the bar is the claim-check bar.
133
134
  'worker-claim': { enabled: true, timeoutMs: 2500, minProbability: 0.9 },
134
135
  'desktop-target': { ...DESKTOP_TARGET_DEFAULTS },
136
+ // On the route path (agent_route waits for the first split), never per tick: one call per checkpoint.
137
+ 'budget-split': { enabled: true, timeoutMs: 2000 },
135
138
  };
136
139
 
137
140
  // --- Config shape ---
@@ -54,6 +54,7 @@ export const agentRouteSchema = {
54
54
  provider: z.enum(PROVIDERS).optional().describe('Worker provider for "dispatch" (ignored for "direct").'),
55
55
  model: z.string().max(200).optional().describe('Model id from the routing sheet / agent_catalog. Omit for the provider default (or a remembered route).'),
56
56
  effort: z.string().max(40).optional().describe('Reasoning effort for that model, per agent_catalog (e.g. low…max/ultra; OpenCode uses the model\'s variant names). Unsupported values are corrected to the nearest level the model runs.'),
57
+ account_id: z.string().max(64).optional().describe('Claude Code or Codex account id, or "auto" to choose the signed-in subscription with most usage left. API-key accounts are excluded from Auto. A limited pinned/default account falls back within the provider; corrections explain the selection. Another account runs on a worker when the proposal was direct.'),
57
58
  reason: z.string().max(240).optional().describe('Why this model (one short clause, shown on the card).'),
58
59
  })).min(1).max(3).describe('1-3 candidate routes, best first. The first is your pick.'),
59
60
  independent: z.boolean().optional().describe(
@@ -120,7 +121,7 @@ export const agentDispatchSchema = {
120
121
  'SynaBun MCP profile preset for the worker (agent_catalog mcpPresets): "core" for memory-only work, "standard" for coding, a platform preset for browser/social jobs (design runs default to "browser" unless uses_browser is false).'
121
122
  ),
122
123
  account_id: z.string().max(200).optional().describe(
123
- 'Account profile id: a Codex account for provider "codex" or a Claude account for provider "claude-code" (ignored for opencode). Omit for the default account.'
124
+ 'Account profile id, or "auto" for the signed-in subscription with most usage left (API keys excluded; limited pinned/default accounts fall back within the provider, with corrections). An approved route binds its resolved account unless it becomes limited before dispatch. A Codex account for provider "codex" or a Claude account for provider "claude-code" (ignored for opencode), an id from agent_catalog accounts["codex"] / accounts["claude-code"]. The worker uses that account\'s own mode (its ChatGPT or Claude subscription, or its API key). Omit to use the user\'s worker account for that provider (agent_catalog codexWorkerAccount / claudeWorkerAccount; Default when null). A signed-out account is refused with ACCOUNT_SIGNED_OUT.'
124
125
  ),
125
126
  permission_policy: z.enum(['auto', 'ask', 'restricted']).optional().describe(
126
127
  '"auto" (default): fully autonomous. "ask": tool permissions and questions are relayed to you — collect them with agent_wait until="event" and answer with agent_send reply. "restricted": workspace-write sandbox without prompts (the Codex alternative to ask).'
@@ -129,10 +130,10 @@ export const agentDispatchSchema = {
129
130
  '"full" (default; "workspace" for design runs), "workspace" (edits limited to cwd) or "read-only" (reviews, research). Browser jobs with "full" require the tag "user-authorized-full".'
130
131
  ),
131
132
  max_minutes: z.number().int().min(1).max(240).optional().describe(
132
- 'Wall-clock cap in minutes (1-240, default 45; 60 for design runs); the run is stopped when exceeded.'
133
+ 'Work-time cap in minutes (at most 240, default 45; 60 for design runs; values under 20 are raised to 20, never refused): only the time the worker is working a turn counts, not time queued, idle between turns or waiting on a permission or question, and a turn still working when it is used up is stopped. The run view shows workMs (used) and workLeftMs (left).'
133
134
  ),
134
- budget_usd: z.number().min(0.1).max(50).optional().describe(
135
- 'Spend cap in USD (0.1-50; the user\'s Budget settings set the default and may lower the ceiling). Every provider is held to it: Claude between model calls, OpenCode per step, Codex after each turn (priced at list price). It is lowered to what the session hard cap (brain + all runs) has left.'
135
+ budget_usd: z.number().min(0.01).max(1000).optional().describe(
136
+ 'Optional ceiling in USD for this one run (0.01-1000), inside its provider\'s share of the session budget. Omit it: the run may use what its provider\'s share has left (nothing is reserved; idle runs hold no money). Every provider is held to it: Claude between model calls, OpenCode per step, Codex per rollout record or turn (at list price). Ignored, with a correction, when the user chose No limit.'
136
137
  ),
137
138
  uses_browser: z.boolean().optional().describe(
138
139
  'true when the worker drives the SynaBun browser: it gets a loop browser tab and the browser enforcement block; page content is treated as untrusted data. Design runs default to true; false keeps one off the browser (no tab, no browser profile).'
@@ -231,28 +232,28 @@ export const agentUsageSchema = {
231
232
  // ── Descriptions (routing instructions) ──
232
233
 
233
234
  export const agentCatalogDescription =
234
- 'Discover what you can route to: providers (claude-code, codex, opencode) with models (tier, price per 1M tokens, vision, context, efforts), accounts, registered projects (cwd candidates), MCP profile presets, rails/limits and active runs. Without arguments it returns a compact routing view (OpenCode sampled); pass provider and/or search to list more models. Call it when the routing sheet in your instructions lacks a model, or after a MODEL_UNKNOWN rejection. Returns JSON.';
235
+ 'Discover what you can route to: providers (claude-code, codex, opencode) with models (tier, price per 1M tokens, vision, context, efforts), accounts with subscription usage remaining (or usage unknown; API keys are n/a), registered projects (cwd candidates), MCP profile presets, rails/limits and active runs. Without arguments it returns a compact routing view (OpenCode sampled); pass provider and/or search to list more models. Call it when the routing sheet in your instructions lacks a model, or after a MODEL_UNKNOWN rejection. Returns JSON.';
235
236
 
236
237
  export const agentRouteDescription =
237
- 'Route an actionable task BEFORE starting it (you are the planner). Give task_class, a one-line summary, your honest confidence (0-1) and 1-3 proposals (direct = here on your model; dispatch = a worker provider/model). The server applies the session\'s route mode (always ask / ask when unsure / never ask): it approves at once, or shows the user a route card and waits briefly. Follow the returned status and next: approved+dispatch → agent_dispatch with route_id; approved+direct → do it yourself; continuation → end your turn (SynaBun continues on the chosen model); pending → end your turn, the choice arrives as a [SynaBun Mailbox] route_decided event; declined → do not start (reason no_capable_model: no model can make the image or video, or see for design; tell the user which Model routes row needs one); clarifying → end your turn, the user has not answered your agent_clarify questions yet (independent:true for a task that does not depend on them). image_gen / video_gen always route to a worker whose model generates the medium, design to a worker whose model can see. Never use it for conversation or bookkeeping. Returns JSON.';
238
+ 'Route an actionable task BEFORE starting it (you are the planner). Give task_class, a one-line summary, your honest confidence (0-1) and 1-3 proposals (direct = here on your model; dispatch = a worker provider/model). proposals.account_id accepts an account id or "auto" (most subscription usage left); limited pinned/default accounts fall back within the provider. The returned target.account includes usage and corrections explain the choice. The server applies the session\'s route mode (always ask / ask when unsure / never ask): it approves at once, or shows the user a route card and waits briefly. Follow the returned status and next: approved+dispatch → agent_dispatch with route_id; approved+direct → do it yourself; continuation → end your turn (SynaBun continues on the chosen model); pending → end your turn, the choice arrives as a [SynaBun Mailbox] route_decided event; declined → do not start (reason no_capable_model: no model can make the image or video, or see for design; tell the user which Model routes row needs one); clarifying → end your turn, the user has not answered your agent_clarify questions yet (independent:true for a task that does not depend on them). image_gen / video_gen always route to a worker whose model generates the medium, design to a worker whose model can see. With a session budget, a provider whose share is used up cannot take the task: SynaBun first divides the unspent budget again, then moves the task to the next capable provider with money left (your next proposal, the same tier on another provider, then a free model) and says so in corrections (reason budget_share, "budget: codex share used up -> claude-code/sonnet"); the saved routes yield to this. When no provider can pay it declines with reason budget_exhausted and names the dry providers (dry). The result carries budget: each provider\'s share, spent and left. Never use it for conversation or bookkeeping. Returns JSON.';
238
239
 
239
240
  export const agentClarifyDescription =
240
241
  'Ask the user 1-3 targeted questions BEFORE routing a new request that is materially ambiguous: two plausible readings with different results, an unclear target (project, feature, files, audience), destructive or irreversible scope, spend, or memories that conflict with it. Not for clear requests, details a sensible default covers, anything the request, memory or the project answers, or the model (agent_route asks that). Each question gets 2-4 options (recommended first; the user can always type their own); add constraints (what the request fixes) and assumptions (what you will assume for the rest). The card waits briefly. Follow the returned status and next: answered → route, then agent_dispatch with the brief_id (SynaBun adds the user\'s original words, their answers, the constraints and assumptions to the worker\'s context); declined → proceed on your assumptions; pending → end your turn, the answers arrive as a [SynaBun Mailbox] clarify_answered event or as the user\'s next message, and dependent routes and dispatches wait until then. Returns JSON.';
241
242
 
242
243
  export const agentListDescription =
243
- 'List dispatched worker runs with status, provider/model, title, elapsed time and cost. Use it before dispatching more work, to recover run ids, or after a mailbox event. Filter with active_only, assistant_session_id (your own session) or workflow_id. Returns JSON.';
244
+ 'List dispatched worker runs with status, provider/model, title, elapsed time, work time left (workLeftMs) and cost. Use it before dispatching more work, to recover run ids, or after a mailbox event. Filter with active_only, assistant_session_id (your own session) or workflow_id. Returns JSON.';
244
245
 
245
246
  export const agentDispatchDescription =
246
- 'Dispatch a task to a worker agent (Claude Code, Codex or OpenCode). Route it first with agent_route and pass the approved route_id (the server applies that provider/model/effort); without one, the dispatch may be held until the user picks a model (awaitingRoute:true — do not dispatch it again). After agent_clarify, pass its brief_id: the worker gets the user\'s own words and answers; while its questions are open a dependent dispatch is refused (CLARIFICATION_PENDING). The run attaches as a live automation tab in the matching sidepanel without stealing focus. Write a self-contained task that ends with a ## Result block, pass the absolute cwd and your assistant_session_id. Defaults: permission_policy "auto", capability "full", 45 minutes, $5. Use "ask" only for sensitive scopes (Codex cannot ask: use "restricted"). uses_computer:true for desktop work (one at a time, vision model). An image_gen / video_gen run makes the asset with its model\'s own generation tool; SynaBun collects the files into the run\'s result (media: path + url); with no model that generates the medium an un-routed dispatch is refused (NO_CAPABLE_MODEL), and a route_id approved for other work is refused for image_gen / video_gen / design (and the other way round: ROUTE_CLASS_MISMATCH; route the task again with its own class). A design run gets SynaBun\'s design rules and the project\'s style guide, defaults to capability "workspace", uses_browser true, mcp_profile "browser" and 60 minutes (explicit values win), runs only on a model confirmed to see (MODEL_CANNOT_SEE otherwise), and its screenshots land in the result\'s media. Group fan-outs with workflow_id, chain with parent_run_id, retry safely with idempotency_key. Over-limit dispatches are queued (queued:true, position). After dispatching, block with agent_wait rather than polling. Returns JSON.';
247
+ 'Dispatch a task to a worker agent (Claude Code, Codex or OpenCode). Route it first with agent_route and pass the approved route_id (the server applies that provider/model/effort); without one, the dispatch may be held until the user picks a model (awaitingRoute:true — do not dispatch it again). After agent_clarify, pass its brief_id: the worker gets the user\'s own words and answers; while its questions are open a dependent dispatch is refused (CLARIFICATION_PENDING). The run attaches as a live automation tab in the matching sidepanel without stealing focus. Write a self-contained task that ends with a ## Result block, pass the absolute cwd and your assistant_session_id. Defaults: permission_policy "auto", capability "full", 45 minutes of work time (max_minutes, at least 20), no per-run dollar ceiling (with a budget the run draws from its provider\'s share; a provider whose share is used up and cannot get more is refused with BUDGET_SHARE_EXHAUSTED: route again). Use "ask" only for sensitive scopes (Codex cannot ask: use "restricted"). uses_computer:true for desktop work (one at a time, vision model). An image_gen / video_gen run makes the asset with its model\'s own generation tool; SynaBun collects the files into the run\'s result (media: path + url); with no model that generates the medium an un-routed dispatch is refused (NO_CAPABLE_MODEL), and a route_id approved for other work is refused for image_gen / video_gen / design (and the other way round: ROUTE_CLASS_MISMATCH; route the task again with its own class). A design run gets SynaBun\'s design rules and the project\'s style guide, defaults to capability "workspace", uses_browser true, mcp_profile "browser" and 60 minutes of work time (explicit values win), runs only on a model confirmed to see (MODEL_CANNOT_SEE otherwise), and its screenshots land in the result\'s media. Group fan-outs with workflow_id, chain with parent_run_id, retry safely with idempotency_key. Over-limit dispatches are queued (queued:true, position). After dispatching, block with agent_wait rather than polling. Returns JSON.';
247
248
 
248
249
  export const agentStatusDescription =
249
- 'Get one run\'s current descriptor: status (queued, running, completed, failed, stopped, interrupted), turnState (running, idle, awaiting_permission), provider/model/account, elapsed time, cost, the last parsed result summary and any pending permission or question requests. Cheap; use it between agent_wait calls or before agent_send / agent_stop. Returns JSON.';
250
+ 'Get one run\'s current descriptor: status (queued, running, completed, failed, stopped, interrupted), turnState (running, idle, awaiting_permission), provider/model/account, elapsed time (elapsedMs, wall clock), work time used and left of max_minutes (workMs, workLeftMs), cost, the last parsed result summary and any pending permission or question requests. Cheap; use it between agent_wait calls or before agent_send / agent_stop. Returns JSON.';
250
251
 
251
252
  export const agentReadDescription =
252
253
  'Read a run\'s output. format "result" (default) returns the parsed ## Result contract (status, summary, changes, follow_ups, question, json, and media: the images / videos an image_gen or video_gen run generated or a design run captured, each { kind, path, url, mime, bytes } — show one with ![](url) plus its path); "tail" returns the last transcript events; "text" the worker\'s assistant text; "files" the files it reported. Call after agent_wait reports idle or terminal; keep max_chars small and read again if truncated. Returns JSON.';
253
254
 
254
255
  export const agentSendDescription =
255
- 'Continue a worker\'s conversation. text sends a follow-up turn (queue:true enqueues it while the worker is still running). reply answers a pending permission or question request: decision allow/deny for tools, answer (with answers) for questions, note as an optional message. Obtain request ids from agent_status or agent_wait until="event". Returns JSON.';
256
+ 'Continue a worker\'s conversation. text sends a follow-up turn (queue:true enqueues it while the worker is still running). reply answers a pending permission or question request: decision allow/deny for tools, answer (with answers) for questions, note as an optional message. Obtain request ids from agent_status or agent_wait until="event". A follow-up to a run whose work time (max_minutes) is used up is refused with TIME_CAP: dispatch a new run with its result as context. Returns JSON.';
256
257
 
257
258
  export const agentWaitDescription =
258
259
  'Blocking barrier for orchestration. Waits up to timeout_seconds (max 120) for one run (run_id), several runs (run_ids with mode all/any) or a whole workflow to reach until="idle" (turn finished; default), "terminal" (completed, failed, stopped) or "event" (next mailbox event: result, needs_input, permission_request, failed). Returns the run descriptors and whether the wait timed out; call again to keep waiting. Prefer this over polling agent_status. Returns JSON.';
@@ -264,7 +265,7 @@ export const agentFocusDescription =
264
265
  'Bring a run\'s sidepanel automation tab to the front (focus:false releases it). Use when the user asks to watch a worker; dispatches never steal focus by themselves. Returns JSON.';
265
266
 
266
267
  export const agentUsageDescription =
267
- 'Get token usage for a task, run, or entire Assistant session. Returns compact JSON. `session` is the headline the panel shows: every task of this session added up (it only grows, and survives a restart), with a per-model breakdown (models: inputTotal, outputTotal, total, costUsd, costBasis). Below it the task asked for: tokens, USD cost, fidelity, unsynced (rows not on disk yet, normally 0), and each agent\'s key, title, provider, model, total, sub-agents total, fidelity and state (partialReason when a count is partial; calls on the Jev judgments row). Every tokens object has the five additive classes (input = uncached input, cacheWrite, cacheRead, output = visible output, reasoning), total, inputTotal (input + cacheWrite + cacheRead: everything sent to the model) and outputTotal (output + reasoning): the same meaning for every provider, nothing counted twice. costUsd is at API list prices; costBasis says how it is known: reported (the provider reported it), estimated (tokens at list price: Codex and other plan-billed work, where it is the list-price equivalent, not a charge), free, or unpriced (no list price: tokens counted, no dollars). Counts are exact provider counts when fidelity is exact; live marks provisional counts and partial marks incomplete counts. metered:false means this server keeps no usage ledger. No routing is needed.';
268
+ 'Get token usage for a task, run, or entire Assistant session. Returns compact JSON. `session` is the headline the panel shows: every task of this session added up (it only grows, and survives a restart), with a per-model breakdown (models: inputTotal, outputTotal, total, costUsd, costBasis). Below it the task asked for: tokens, USD cost, fidelity, unsynced (rows not on disk yet, normally 0), and each agent\'s key, title, provider, model, total, sub-agents total, fidelity and state (partialReason when a count is partial; calls on the Jev judgments row). `budget` is the session budget: mode (limited / unlimited: the user chose No limit), the amount, spent and left, warned / exceeded, how the split was made (source jev | fallback, updatedAt) and each provider\'s share, spent and left (brain marks your own provider; leftUsd is what its workers may still spend), and dry: the providers whose share is used up. Every tokens object has the five additive classes (input = uncached input, cacheWrite, cacheRead, output = visible output, reasoning), total, inputTotal (input + cacheWrite + cacheRead: everything sent to the model) and outputTotal (output + reasoning): the same meaning for every provider, nothing counted twice. costUsd is at API list prices; costBasis says how it is known: reported (the provider reported it), estimated (tokens at list price: Codex and other plan-billed work, where it is the list-price equivalent, not a charge), free, or unpriced (no list price: tokens counted, no dollars). Counts are exact provider counts when fidelity is exact; live marks provisional counts and partial marks incomplete counts. metered:false means this server keeps no usage ledger. No routing is needed.';
268
269
 
269
270
  // ── Handlers ──
270
271
 
@@ -473,6 +474,23 @@ export function handleAgentUsage(args: AgentUsageArgs): Promise<ToolResult> {
473
474
  tasks: Array.isArray(session.tasks) ? session.tasks.length : session.tasks ?? 0,
474
475
  models: (Array.isArray(session.models) ? session.models : []).map(compactModel),
475
476
  });
477
+ /** The session budget: the mode, the total, and each provider's share / spent / left. */
478
+ const compactBudget = (b: any) => {
479
+ if (!b || typeof b !== 'object') return null;
480
+ if (b.unlimited) return { mode: 'unlimited', spentUsd: b.spentUsd ?? 0 };
481
+ return {
482
+ mode: 'limited', amountUsd: b.amountUsd ?? null, spentUsd: b.spentUsd ?? 0, leftUsd: b.leftUsd ?? null,
483
+ warnAtUsd: b.warnAtUsd ?? null, warned: !!b.warned, exceeded: !!b.exceeded,
484
+ split: b.split ? { source: b.split.source ?? null, reason: b.split.reason ?? null, updatedAt: b.split.updatedAt ?? null } : null,
485
+ providers: (Array.isArray(b.providers) ? b.providers : []).map((row: any) => ({
486
+ provider: row.provider, ...(row.isBrain ? { brain: true } : {}), eligible: row.eligible !== false,
487
+ shareUsd: row.shareUsd ?? null, spentUsd: row.spentUsd ?? 0, leftUsd: row.workerLeftUsd ?? row.leftUsd ?? null,
488
+ ...(row.isBrain ? { brainLeftUsd: row.leftUsd ?? null } : {}),
489
+ })),
490
+ dry: Array.isArray(b.dry) ? b.dry : [],
491
+ };
492
+ };
493
+ const budget = compactBudget((answer as any).budget);
476
494
  const compactAgent = (agent: any) => ({
477
495
  key: agent.key, title: agent.title ?? null, provider: agent.provider, model: agent.model,
478
496
  tokens: agent.tokens, total: agent.tokens?.total ?? 0, subagents: { total: agent.subagents?.total ?? 0 },
@@ -484,9 +502,9 @@ export function handleAgentUsage(args: AgentUsageArgs): Promise<ToolResult> {
484
502
  if (task === 'all') {
485
503
  const session = answer.session as any;
486
504
  // The server has no usage ledger: say so, never fail on the missing view.
487
- if (!session) return text(JSON.stringify({ sessionId: args.assistant_session_id, metered: false, tokens: null, costUsd: 0, tasks: [] }));
505
+ if (!session) return text(JSON.stringify({ sessionId: args.assistant_session_id, metered: false, tokens: null, costUsd: 0, tasks: [], budget }));
488
506
  const { tasks: _taskCount, ...totals } = sessionHead(session);
489
- const head = { sessionId: session.sessionId, ...totals, unsynced: session.unsynced ?? 0 };
507
+ const head = { sessionId: session.sessionId, ...totals, unsynced: session.unsynced ?? 0, budget };
490
508
  if (args.run_id) {
491
509
  const run = answer.run as any;
492
510
  const worked = new Set<string>(Array.isArray(run?.taskIds) ? run.taskIds : []);
@@ -500,7 +518,7 @@ export function handleAgentUsage(args: AgentUsageArgs): Promise<ToolResult> {
500
518
  return text(JSON.stringify({ ...head, tasks: session.tasks }));
501
519
  }
502
520
  const view = answer.usage as any;
503
- if (!view) return text(JSON.stringify({ sessionId: args.assistant_session_id, metered: false, task: null, tokens: null, costUsd: 0, fidelity: null, agents: [] }));
521
+ if (!view) return text(JSON.stringify({ sessionId: args.assistant_session_id, metered: false, task: null, tokens: null, costUsd: 0, fidelity: null, agents: [], budget }));
504
522
  const detail = view.task;
505
523
  return text(JSON.stringify({
506
524
  sessionId: view.sessionId,
@@ -511,6 +529,7 @@ export function handleAgentUsage(args: AgentUsageArgs): Promise<ToolResult> {
511
529
  // Rows the ledger has not written to disk yet (normally 0).
512
530
  unsynced: view.session?.unsynced ?? 0,
513
531
  agents: (detail?.agents || []).filter((agent: any) => !args.run_id || agent.key === args.run_id || agent.runId === args.run_id).map(compactAgent),
532
+ budget,
514
533
  }));
515
534
  });
516
535
  }
@@ -0,0 +1,51 @@
1
+ import { z } from 'zod';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import * as ni from '../services/neural-interface.js';
4
+ import { callerBrainCredential, callerRole } from '../services/identity.js';
5
+ import { text } from './response.js';
6
+
7
+ // ═══════════════════════════════════════════
8
+ // whatsapp_send_file — a local file to the owner, in the WhatsApp conversation
9
+ // ═══════════════════════════════════════════
10
+ //
11
+ // In the role-gated `agents` group (profiles.ts ROLE_GATED_GROUPS): advertised
12
+ // to assistant brains only, and the handler refuses any other caller. One
13
+ // POST to /api/assistant/whatsapp/send-file (neural-interface/lib/assistant-api.js)
14
+ // carrying the brain's own credential (identity.ts callerBrainCredential), which
15
+ // the server resolves to the WhatsApp conversation; the WhatsApp host checks and
16
+ // reads the file (neural-interface/lib/whatsapp/local-file.js). Never retried:
17
+ // a timeout may come after WhatsApp took the file. No recipient, no session id.
18
+
19
+ export const WHATSAPP_SEND_FILE_DENIED = 'whatsapp_send_file is only available to the SynaBun assistant runtime.';
20
+
21
+ export const WHATSAPP_SEND_AS = ['auto', 'document', 'image', 'video', 'audio'] as const;
22
+
23
+ export const whatsappSendFileSchema = {
24
+ path: z.string().min(1).max(2048).describe('Absolute path of an existing regular file on this computer. No URL or network share.'),
25
+ caption: z.string().max(1024).optional().describe('Text shown under the file (at most 1024 characters). Audio has none: leave it out, or choose as "document".'),
26
+ as: z.enum(WHATSAPP_SEND_AS).optional().describe(
27
+ 'Default "auto": JPEG/PNG/WebP as a photo, MP4 as a video, OGG/Opus/MP3/M4A as audio, everything else as a document. "document" sends the original file as it is. Photos, videos and audio over 16 MiB go as documents; nothing over 100 MiB is sent. A native form that does not match the extension is refused.'
28
+ ),
29
+ };
30
+
31
+ export const whatsappSendFileDescription = 'Send a file already on this computer as a real WhatsApp attachment to the owner, in THIS WhatsApp conversation. Use it only when the user asked you to send a file (a generated image or video they asked for included: take its local path from the worker result\'s media). Only the active WhatsApp conversation\'s own Assistant can use it: never workers, desktop chats or other contacts; there is no recipient or session parameter. Sensitive places (keys, credentials, browser and app data, SynaBun\'s own data) are refused, and links are resolved and checked too. Returns the delivery id, type, mimetype, size, file name and canonical path, or a clear error code. An answer with uncertain: true (a timeout, a lost answer), or a call of yours that times out, means delivery is uncertain: never retry it on your own.';
32
+
33
+ export type WhatsAppSendFileArgs = z.infer<z.ZodObject<typeof whatsappSendFileSchema>>;
34
+
35
+ /** Role guard, one request, never throws: every failure reaches the brain as data. */
36
+ export async function handleWhatsAppSendFile(args: WhatsAppSendFileArgs): Promise<ReturnType<typeof text>> {
37
+ if (callerRole() !== 'assistant') return text(WHATSAPP_SEND_FILE_DENIED);
38
+ try {
39
+ const body: { path: string; caption?: string; as?: string } = { path: args.path };
40
+ if (typeof args.caption === 'string') body.caption = args.caption;
41
+ if (typeof args.as === 'string') body.as = args.as;
42
+ return text(JSON.stringify(await ni.whatsappSendFile(body, callerBrainCredential()), null, 2));
43
+ } catch (err) {
44
+ return text(JSON.stringify({ ok: false, error: err instanceof Error ? err.message : String(err), code: 'WHATSAPP_SEND_FILE_ERROR' }));
45
+ }
46
+ }
47
+
48
+ /** Registered into the `agents` group (index.ts): the returned handle is gated on the caller role. */
49
+ export function registerWhatsAppSendFileTool(server: McpServer) {
50
+ return server.tool('whatsapp_send_file', whatsappSendFileDescription, whatsappSendFileSchema, (args) => handleWhatsAppSendFile(args));
51
+ }
@@ -27,6 +27,7 @@ import { ensureCollection, scrollMemories, searchMemories, getMemory, deleteMemo
27
27
  import { getAllCategories, getCategories, getCustomCategories, getCategoryDescription, validateCategoryName, addCategory, removeCategory } from './services/categories.js';
28
28
  import { generateEmbedding } from './services/local-embeddings.js';
29
29
  import { detectProject } from './config.js';
30
+ import { SERVER_VERSION } from './version.js';
30
31
  import type { MemoryPayload, MemorySource } from './types.js';
31
32
 
32
33
  // ── Box drawing characters ────────────────────────────────────────
@@ -228,7 +229,7 @@ function printLogo(): void {
228
229
  console.log(dboxRow(W, ''));
229
230
 
230
231
  // Tagline (centered)
231
- const tagline = accent2('Vector-Powered') + dim(' \u2022 ') + info('SQLite') + dim(' \u2022 ') + chalk.white('v2.0.0');
232
+ const tagline = accent2('Vector-Powered') + dim(' \u2022 ') + info('SQLite') + dim(' \u2022 ') + chalk.white(`v${SERVER_VERSION}`);
232
233
  console.log(dboxRow(W, tagline));
233
234
 
234
235
  console.log(dim(BOX.dbl + BOX.dh.repeat(W) + BOX.dbr));
@@ -0,0 +1,22 @@
1
+ import { readFileSync } from 'fs';
2
+
3
+ // The version the server reports (MCP initialize serverInfo, the TUI banner)
4
+ // is the one in mcp-server/package.json, never a literal. src/ and dist/ both
5
+ // sit one level under mcp-server/, so `../package.json` is that file in the
6
+ // checkout, under vitest, in the npm package and in the packaged desktop app
7
+ // (both ship mcp-server/package.json beside mcp-server/dist/).
8
+ const PACKAGE_URL = new URL('../package.json', import.meta.url);
9
+ const PACKAGE_NAME = 'claude-memory-mcp';
10
+
11
+ /** Reported when the package file is missing, unreadable or another package's. */
12
+ export const UNKNOWN_SERVER_VERSION = '0.0.0-unknown';
13
+
14
+ export function readServerVersion(packageUrl: URL = PACKAGE_URL): string {
15
+ try {
16
+ const pkg = JSON.parse(readFileSync(packageUrl, 'utf-8')) as { name?: unknown; version?: unknown };
17
+ if (pkg?.name === PACKAGE_NAME && typeof pkg.version === 'string' && pkg.version.trim()) return pkg.version.trim();
18
+ } catch { /* missing or malformed: fall through */ }
19
+ return UNKNOWN_SERVER_VERSION;
20
+ }
21
+
22
+ export const SERVER_VERSION = readServerVersion();