synabun 2.0.5 → 2.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (527) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +2 -1
  3. package/THIRD-PARTY-LICENSES.md +13 -0
  4. package/docs/whatsapp.md +151 -16
  5. package/hooks/claude-code/post-plan.mjs +6 -30
  6. package/hooks/claude-code/session-start.mjs +1 -4
  7. package/hooks/claude-code/shared.mjs +112 -75
  8. package/lib/child-path.js +303 -0
  9. package/lib/claude-hooks.js +2 -1
  10. package/lib/data-home-migration.js +2 -0
  11. package/lib/desktop-app.js +265 -0
  12. package/lib/external-tools.js +7 -3
  13. package/lib/log-redact.js +102 -0
  14. package/lib/package-processes.js +670 -0
  15. package/lib/packaged-runtime.js +138 -11
  16. package/lib/runtime-dependencies.js +225 -0
  17. package/mcp-server/dist/http.d.ts +3 -1
  18. package/mcp-server/dist/http.d.ts.map +1 -1
  19. package/mcp-server/dist/http.js +15 -4
  20. package/mcp-server/dist/http.js.map +1 -1
  21. package/mcp-server/dist/index.d.ts.map +1 -1
  22. package/mcp-server/dist/index.js +9 -6
  23. package/mcp-server/dist/index.js.map +1 -1
  24. package/mcp-server/dist/services/browser-bench.d.ts +1 -1
  25. package/mcp-server/dist/services/browser-bench.d.ts.map +1 -1
  26. package/mcp-server/dist/services/browser-bench.js +1 -1
  27. package/mcp-server/dist/services/browser-bench.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/categories.d.ts +35 -2
  33. package/mcp-server/dist/services/categories.d.ts.map +1 -1
  34. package/mcp-server/dist/services/categories.js +77 -33
  35. package/mcp-server/dist/services/categories.js.map +1 -1
  36. package/mcp-server/dist/services/codex-config-heal.d.ts +5 -1
  37. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  38. package/mcp-server/dist/services/codex-config-heal.js +41 -4
  39. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  40. package/mcp-server/dist/services/desktop-bench.d.ts +7 -7
  41. package/mcp-server/dist/services/desktop-bench.d.ts.map +1 -1
  42. package/mcp-server/dist/services/desktop-bench.js +1 -1
  43. package/mcp-server/dist/services/desktop-bench.js.map +1 -1
  44. package/mcp-server/dist/services/identity.d.ts +11 -0
  45. package/mcp-server/dist/services/identity.d.ts.map +1 -1
  46. package/mcp-server/dist/services/identity.js +24 -0
  47. package/mcp-server/dist/services/identity.js.map +1 -1
  48. package/mcp-server/dist/services/neural-interface.d.ts +13 -0
  49. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  50. package/mcp-server/dist/services/neural-interface.js +34 -2
  51. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  52. package/mcp-server/dist/services/typesafe-config.d.ts +1 -1
  53. package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -1
  54. package/mcp-server/dist/services/typesafe-config.js +4 -1
  55. package/mcp-server/dist/services/typesafe-config.js.map +1 -1
  56. package/mcp-server/dist/tools/agents.d.ts +11 -8
  57. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  58. package/mcp-server/dist/tools/agents.js +35 -14
  59. package/mcp-server/dist/tools/agents.js.map +1 -1
  60. package/mcp-server/dist/tools/bluesky.js +1 -1
  61. package/mcp-server/dist/tools/bluesky.js.map +1 -1
  62. package/mcp-server/dist/tools/browser-advanced.d.ts +1 -1
  63. package/mcp-server/dist/tools/browser-advanced.d.ts.map +1 -1
  64. package/mcp-server/dist/tools/browser-advanced.js +1 -1
  65. package/mcp-server/dist/tools/browser-advanced.js.map +1 -1
  66. package/mcp-server/dist/tools/browser-batch.d.ts +1 -1
  67. package/mcp-server/dist/tools/browser-batch.d.ts.map +1 -1
  68. package/mcp-server/dist/tools/browser-batch.js +1 -1
  69. package/mcp-server/dist/tools/browser-batch.js.map +1 -1
  70. package/mcp-server/dist/tools/browser-cheatsheet.d.ts +1 -1
  71. package/mcp-server/dist/tools/browser-cheatsheet.d.ts.map +1 -1
  72. package/mcp-server/dist/tools/browser-cheatsheet.js +1 -1
  73. package/mcp-server/dist/tools/browser-cheatsheet.js.map +1 -1
  74. package/mcp-server/dist/tools/browser-interact.d.ts +1 -1
  75. package/mcp-server/dist/tools/browser-interact.d.ts.map +1 -1
  76. package/mcp-server/dist/tools/browser-interact.js +1 -1
  77. package/mcp-server/dist/tools/browser-interact.js.map +1 -1
  78. package/mcp-server/dist/tools/browser-navigate.d.ts +1 -1
  79. package/mcp-server/dist/tools/browser-navigate.d.ts.map +1 -1
  80. package/mcp-server/dist/tools/browser-navigate.js +1 -1
  81. package/mcp-server/dist/tools/browser-navigate.js.map +1 -1
  82. package/mcp-server/dist/tools/browser-observe.d.ts +1 -1
  83. package/mcp-server/dist/tools/browser-observe.d.ts.map +1 -1
  84. package/mcp-server/dist/tools/browser-observe.js +1 -1
  85. package/mcp-server/dist/tools/browser-observe.js.map +1 -1
  86. package/mcp-server/dist/tools/card-tools.d.ts +1 -1
  87. package/mcp-server/dist/tools/card-tools.d.ts.map +1 -1
  88. package/mcp-server/dist/tools/card-tools.js +1 -1
  89. package/mcp-server/dist/tools/card-tools.js.map +1 -1
  90. package/mcp-server/dist/tools/category.d.ts +1 -1
  91. package/mcp-server/dist/tools/category.d.ts.map +1 -1
  92. package/mcp-server/dist/tools/category.js +5 -1
  93. package/mcp-server/dist/tools/category.js.map +1 -1
  94. package/mcp-server/dist/tools/choice.d.ts +1 -1
  95. package/mcp-server/dist/tools/choice.d.ts.map +1 -1
  96. package/mcp-server/dist/tools/choice.js +1 -1
  97. package/mcp-server/dist/tools/choice.js.map +1 -1
  98. package/mcp-server/dist/tools/computer.d.ts +1 -1
  99. package/mcp-server/dist/tools/computer.d.ts.map +1 -1
  100. package/mcp-server/dist/tools/computer.js +1 -1
  101. package/mcp-server/dist/tools/computer.js.map +1 -1
  102. package/mcp-server/dist/tools/discord-channel.d.ts +1 -1
  103. package/mcp-server/dist/tools/discord-channel.d.ts.map +1 -1
  104. package/mcp-server/dist/tools/discord-channel.js +1 -1
  105. package/mcp-server/dist/tools/discord-channel.js.map +1 -1
  106. package/mcp-server/dist/tools/discord-guild.d.ts +1 -1
  107. package/mcp-server/dist/tools/discord-guild.d.ts.map +1 -1
  108. package/mcp-server/dist/tools/discord-guild.js +1 -1
  109. package/mcp-server/dist/tools/discord-guild.js.map +1 -1
  110. package/mcp-server/dist/tools/discord-member.d.ts +1 -1
  111. package/mcp-server/dist/tools/discord-member.d.ts.map +1 -1
  112. package/mcp-server/dist/tools/discord-member.js +1 -1
  113. package/mcp-server/dist/tools/discord-member.js.map +1 -1
  114. package/mcp-server/dist/tools/discord-message.d.ts +1 -1
  115. package/mcp-server/dist/tools/discord-message.d.ts.map +1 -1
  116. package/mcp-server/dist/tools/discord-message.js +1 -1
  117. package/mcp-server/dist/tools/discord-message.js.map +1 -1
  118. package/mcp-server/dist/tools/discord-onboarding.d.ts +1 -1
  119. package/mcp-server/dist/tools/discord-onboarding.d.ts.map +1 -1
  120. package/mcp-server/dist/tools/discord-onboarding.js +1 -1
  121. package/mcp-server/dist/tools/discord-onboarding.js.map +1 -1
  122. package/mcp-server/dist/tools/discord-role.d.ts +1 -1
  123. package/mcp-server/dist/tools/discord-role.d.ts.map +1 -1
  124. package/mcp-server/dist/tools/discord-role.js +1 -1
  125. package/mcp-server/dist/tools/discord-role.js.map +1 -1
  126. package/mcp-server/dist/tools/discord-thread.d.ts +1 -1
  127. package/mcp-server/dist/tools/discord-thread.d.ts.map +1 -1
  128. package/mcp-server/dist/tools/discord-thread.js +1 -1
  129. package/mcp-server/dist/tools/discord-thread.js.map +1 -1
  130. package/mcp-server/dist/tools/discord-webhook.d.ts +1 -1
  131. package/mcp-server/dist/tools/discord-webhook.d.ts.map +1 -1
  132. package/mcp-server/dist/tools/discord-webhook.js +1 -1
  133. package/mcp-server/dist/tools/discord-webhook.js.map +1 -1
  134. package/mcp-server/dist/tools/fb-groups.d.ts +1 -1
  135. package/mcp-server/dist/tools/fb-groups.d.ts.map +1 -1
  136. package/mcp-server/dist/tools/fb-groups.js +1 -1
  137. package/mcp-server/dist/tools/fb-groups.js.map +1 -1
  138. package/mcp-server/dist/tools/forget.d.ts +1 -1
  139. package/mcp-server/dist/tools/forget.d.ts.map +1 -1
  140. package/mcp-server/dist/tools/forget.js +1 -1
  141. package/mcp-server/dist/tools/forget.js.map +1 -1
  142. package/mcp-server/dist/tools/git-tools.d.ts +1 -1
  143. package/mcp-server/dist/tools/git-tools.d.ts.map +1 -1
  144. package/mcp-server/dist/tools/git-tools.js +1 -1
  145. package/mcp-server/dist/tools/git-tools.js.map +1 -1
  146. package/mcp-server/dist/tools/gsc/experience.d.ts +1 -1
  147. package/mcp-server/dist/tools/gsc/experience.d.ts.map +1 -1
  148. package/mcp-server/dist/tools/gsc/experience.js +1 -1
  149. package/mcp-server/dist/tools/gsc/experience.js.map +1 -1
  150. package/mcp-server/dist/tools/gsc/indexing.d.ts +1 -1
  151. package/mcp-server/dist/tools/gsc/indexing.d.ts.map +1 -1
  152. package/mcp-server/dist/tools/gsc/indexing.js +1 -1
  153. package/mcp-server/dist/tools/gsc/indexing.js.map +1 -1
  154. package/mcp-server/dist/tools/gsc/inspect.d.ts +1 -1
  155. package/mcp-server/dist/tools/gsc/inspect.d.ts.map +1 -1
  156. package/mcp-server/dist/tools/gsc/inspect.js +1 -1
  157. package/mcp-server/dist/tools/gsc/inspect.js.map +1 -1
  158. package/mcp-server/dist/tools/gsc/navigate.d.ts +1 -1
  159. package/mcp-server/dist/tools/gsc/navigate.d.ts.map +1 -1
  160. package/mcp-server/dist/tools/gsc/navigate.js +1 -1
  161. package/mcp-server/dist/tools/gsc/navigate.js.map +1 -1
  162. package/mcp-server/dist/tools/gsc/performance.d.ts +1 -1
  163. package/mcp-server/dist/tools/gsc/performance.d.ts.map +1 -1
  164. package/mcp-server/dist/tools/gsc/performance.js +1 -1
  165. package/mcp-server/dist/tools/gsc/performance.js.map +1 -1
  166. package/mcp-server/dist/tools/gsc/settings.d.ts +1 -1
  167. package/mcp-server/dist/tools/gsc/settings.d.ts.map +1 -1
  168. package/mcp-server/dist/tools/gsc/settings.js +1 -1
  169. package/mcp-server/dist/tools/gsc/settings.js.map +1 -1
  170. package/mcp-server/dist/tools/image-tools.d.ts +1 -1
  171. package/mcp-server/dist/tools/image-tools.d.ts.map +1 -1
  172. package/mcp-server/dist/tools/image-tools.js +1 -1
  173. package/mcp-server/dist/tools/image-tools.js.map +1 -1
  174. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +1 -1
  175. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +1 -1
  176. package/mcp-server/dist/tools/leonardo-browser-tools.js +1 -1
  177. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +1 -1
  178. package/mcp-server/dist/tools/loop.d.ts +1 -1
  179. package/mcp-server/dist/tools/loop.d.ts.map +1 -1
  180. package/mcp-server/dist/tools/loop.js +1 -1
  181. package/mcp-server/dist/tools/loop.js.map +1 -1
  182. package/mcp-server/dist/tools/memories.d.ts +1 -1
  183. package/mcp-server/dist/tools/memories.d.ts.map +1 -1
  184. package/mcp-server/dist/tools/memories.js +1 -1
  185. package/mcp-server/dist/tools/memories.js.map +1 -1
  186. package/mcp-server/dist/tools/morelogin.d.ts +1 -1
  187. package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
  188. package/mcp-server/dist/tools/morelogin.js +1 -1
  189. package/mcp-server/dist/tools/morelogin.js.map +1 -1
  190. package/mcp-server/dist/tools/profile.d.ts +1 -1
  191. package/mcp-server/dist/tools/profile.d.ts.map +1 -1
  192. package/mcp-server/dist/tools/profile.js +1 -1
  193. package/mcp-server/dist/tools/profile.js.map +1 -1
  194. package/mcp-server/dist/tools/recall.d.ts +1 -1
  195. package/mcp-server/dist/tools/recall.d.ts.map +1 -1
  196. package/mcp-server/dist/tools/recall.js +1 -1
  197. package/mcp-server/dist/tools/recall.js.map +1 -1
  198. package/mcp-server/dist/tools/reflect.d.ts +1 -1
  199. package/mcp-server/dist/tools/reflect.d.ts.map +1 -1
  200. package/mcp-server/dist/tools/reflect.js +1 -1
  201. package/mcp-server/dist/tools/reflect.js.map +1 -1
  202. package/mcp-server/dist/tools/remember.d.ts +1 -1
  203. package/mcp-server/dist/tools/remember.d.ts.map +1 -1
  204. package/mcp-server/dist/tools/remember.js +13 -11
  205. package/mcp-server/dist/tools/remember.js.map +1 -1
  206. package/mcp-server/dist/tools/restore.d.ts +1 -1
  207. package/mcp-server/dist/tools/restore.d.ts.map +1 -1
  208. package/mcp-server/dist/tools/restore.js +1 -1
  209. package/mcp-server/dist/tools/restore.js.map +1 -1
  210. package/mcp-server/dist/tools/style-guide-tools.d.ts +1 -1
  211. package/mcp-server/dist/tools/style-guide-tools.d.ts.map +1 -1
  212. package/mcp-server/dist/tools/style-guide-tools.js +1 -1
  213. package/mcp-server/dist/tools/style-guide-tools.js.map +1 -1
  214. package/mcp-server/dist/tools/sync.d.ts +1 -1
  215. package/mcp-server/dist/tools/sync.d.ts.map +1 -1
  216. package/mcp-server/dist/tools/sync.js +1 -1
  217. package/mcp-server/dist/tools/sync.js.map +1 -1
  218. package/mcp-server/dist/tools/tictactoe-tools.d.ts +1 -1
  219. package/mcp-server/dist/tools/tictactoe-tools.d.ts.map +1 -1
  220. package/mcp-server/dist/tools/tictactoe-tools.js +1 -1
  221. package/mcp-server/dist/tools/tictactoe-tools.js.map +1 -1
  222. package/mcp-server/dist/tools/utils.d.ts +1 -1
  223. package/mcp-server/dist/tools/utils.d.ts.map +1 -1
  224. package/mcp-server/dist/tools/utils.js +1 -1
  225. package/mcp-server/dist/tools/utils.js.map +1 -1
  226. package/mcp-server/dist/tools/whatsapp-send-file.d.ts +17 -0
  227. package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -0
  228. package/mcp-server/dist/tools/whatsapp-send-file.js +44 -0
  229. package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -0
  230. package/mcp-server/dist/tools/whiteboard-tools.d.ts +3 -3
  231. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
  232. package/mcp-server/dist/tools/whiteboard-tools.js +1 -1
  233. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
  234. package/mcp-server/dist/tools/youtube/analytics.d.ts +1 -1
  235. package/mcp-server/dist/tools/youtube/analytics.d.ts.map +1 -1
  236. package/mcp-server/dist/tools/youtube/analytics.js +1 -1
  237. package/mcp-server/dist/tools/youtube/analytics.js.map +1 -1
  238. package/mcp-server/dist/tools/youtube/download.d.ts +1 -1
  239. package/mcp-server/dist/tools/youtube/download.d.ts.map +1 -1
  240. package/mcp-server/dist/tools/youtube/download.js +1 -1
  241. package/mcp-server/dist/tools/youtube/download.js.map +1 -1
  242. package/mcp-server/dist/tools/youtube/media.d.ts +1 -1
  243. package/mcp-server/dist/tools/youtube/media.d.ts.map +1 -1
  244. package/mcp-server/dist/tools/youtube/media.js +1 -1
  245. package/mcp-server/dist/tools/youtube/media.js.map +1 -1
  246. package/mcp-server/dist/tools/youtube/metadata.d.ts +1 -1
  247. package/mcp-server/dist/tools/youtube/metadata.d.ts.map +1 -1
  248. package/mcp-server/dist/tools/youtube/metadata.js +1 -1
  249. package/mcp-server/dist/tools/youtube/metadata.js.map +1 -1
  250. package/mcp-server/dist/tools/youtube/navigate.d.ts +1 -1
  251. package/mcp-server/dist/tools/youtube/navigate.d.ts.map +1 -1
  252. package/mcp-server/dist/tools/youtube/navigate.js +1 -1
  253. package/mcp-server/dist/tools/youtube/navigate.js.map +1 -1
  254. package/mcp-server/dist/tools/youtube/playlist.d.ts +1 -1
  255. package/mcp-server/dist/tools/youtube/playlist.d.ts.map +1 -1
  256. package/mcp-server/dist/tools/youtube/playlist.js +1 -1
  257. package/mcp-server/dist/tools/youtube/playlist.js.map +1 -1
  258. package/mcp-server/dist/tools/youtube/schedule.d.ts +1 -1
  259. package/mcp-server/dist/tools/youtube/schedule.d.ts.map +1 -1
  260. package/mcp-server/dist/tools/youtube/schedule.js +1 -1
  261. package/mcp-server/dist/tools/youtube/schedule.js.map +1 -1
  262. package/mcp-server/dist/tools/youtube/source.d.ts +1 -1
  263. package/mcp-server/dist/tools/youtube/source.d.ts.map +1 -1
  264. package/mcp-server/dist/tools/youtube/source.js +1 -1
  265. package/mcp-server/dist/tools/youtube/source.js.map +1 -1
  266. package/mcp-server/dist/tools/youtube/upload.d.ts +1 -1
  267. package/mcp-server/dist/tools/youtube/upload.d.ts.map +1 -1
  268. package/mcp-server/dist/tools/youtube/upload.js +4 -9
  269. package/mcp-server/dist/tools/youtube/upload.js.map +1 -1
  270. package/mcp-server/dist/tui.js +18 -5
  271. package/mcp-server/dist/tui.js.map +1 -1
  272. package/mcp-server/dist/version.d.ts +5 -0
  273. package/mcp-server/dist/version.d.ts.map +1 -0
  274. package/mcp-server/dist/version.js +21 -0
  275. package/mcp-server/dist/version.js.map +1 -0
  276. package/mcp-server/package-lock.json +3 -3
  277. package/mcp-server/package.json +3 -3
  278. package/mcp-server/run.mjs +15 -9
  279. package/mcp-server/src/http.ts +18 -3
  280. package/mcp-server/src/index.ts +9 -6
  281. package/mcp-server/src/services/browser-bench.ts +1 -1
  282. package/mcp-server/src/services/budget-judgments.ts +183 -0
  283. package/mcp-server/src/services/categories.ts +77 -32
  284. package/mcp-server/src/services/codex-config-heal.ts +35 -4
  285. package/mcp-server/src/services/desktop-bench.ts +1 -1
  286. package/mcp-server/src/services/identity.ts +27 -0
  287. package/mcp-server/src/services/neural-interface.ts +34 -2
  288. package/mcp-server/src/services/typesafe-config.ts +4 -1
  289. package/mcp-server/src/tools/agents.ts +34 -15
  290. package/mcp-server/src/tools/bluesky.ts +1 -1
  291. package/mcp-server/src/tools/browser-advanced.ts +1 -1
  292. package/mcp-server/src/tools/browser-batch.ts +1 -1
  293. package/mcp-server/src/tools/browser-cheatsheet.ts +1 -1
  294. package/mcp-server/src/tools/browser-interact.ts +1 -1
  295. package/mcp-server/src/tools/browser-navigate.ts +1 -1
  296. package/mcp-server/src/tools/browser-observe.ts +1 -1
  297. package/mcp-server/src/tools/card-tools.ts +1 -1
  298. package/mcp-server/src/tools/category.ts +6 -1
  299. package/mcp-server/src/tools/choice.ts +1 -1
  300. package/mcp-server/src/tools/computer.ts +1 -1
  301. package/mcp-server/src/tools/discord-channel.ts +1 -1
  302. package/mcp-server/src/tools/discord-guild.ts +1 -1
  303. package/mcp-server/src/tools/discord-member.ts +1 -1
  304. package/mcp-server/src/tools/discord-message.ts +1 -1
  305. package/mcp-server/src/tools/discord-onboarding.ts +1 -1
  306. package/mcp-server/src/tools/discord-role.ts +1 -1
  307. package/mcp-server/src/tools/discord-thread.ts +1 -1
  308. package/mcp-server/src/tools/discord-webhook.ts +1 -1
  309. package/mcp-server/src/tools/fb-groups.ts +1 -1
  310. package/mcp-server/src/tools/forget.ts +1 -1
  311. package/mcp-server/src/tools/git-tools.ts +1 -1
  312. package/mcp-server/src/tools/gsc/experience.ts +1 -1
  313. package/mcp-server/src/tools/gsc/indexing.ts +1 -1
  314. package/mcp-server/src/tools/gsc/inspect.ts +1 -1
  315. package/mcp-server/src/tools/gsc/navigate.ts +1 -1
  316. package/mcp-server/src/tools/gsc/performance.ts +1 -1
  317. package/mcp-server/src/tools/gsc/settings.ts +1 -1
  318. package/mcp-server/src/tools/image-tools.ts +1 -1
  319. package/mcp-server/src/tools/leonardo-browser-tools.ts +1 -1
  320. package/mcp-server/src/tools/loop.ts +1 -1
  321. package/mcp-server/src/tools/memories.ts +1 -1
  322. package/mcp-server/src/tools/morelogin.ts +1 -1
  323. package/mcp-server/src/tools/profile.ts +1 -1
  324. package/mcp-server/src/tools/recall.ts +1 -1
  325. package/mcp-server/src/tools/reflect.ts +1 -1
  326. package/mcp-server/src/tools/remember.ts +13 -12
  327. package/mcp-server/src/tools/restore.ts +1 -1
  328. package/mcp-server/src/tools/style-guide-tools.ts +1 -1
  329. package/mcp-server/src/tools/sync.ts +1 -1
  330. package/mcp-server/src/tools/tictactoe-tools.ts +1 -1
  331. package/mcp-server/src/tools/utils.ts +1 -1
  332. package/mcp-server/src/tools/whatsapp-send-file.ts +51 -0
  333. package/mcp-server/src/tools/whiteboard-tools.ts +1 -1
  334. package/mcp-server/src/tools/youtube/analytics.ts +1 -1
  335. package/mcp-server/src/tools/youtube/download.ts +1 -1
  336. package/mcp-server/src/tools/youtube/media.ts +1 -1
  337. package/mcp-server/src/tools/youtube/metadata.ts +1 -1
  338. package/mcp-server/src/tools/youtube/navigate.ts +1 -1
  339. package/mcp-server/src/tools/youtube/playlist.ts +1 -1
  340. package/mcp-server/src/tools/youtube/schedule.ts +1 -1
  341. package/mcp-server/src/tools/youtube/source.ts +1 -1
  342. package/mcp-server/src/tools/youtube/upload.ts +4 -12
  343. package/mcp-server/src/tui.ts +24 -10
  344. package/mcp-server/src/version.ts +22 -0
  345. package/neural-interface/i18n/en.json +645 -57
  346. package/neural-interface/i18n/pt-BR.json +644 -56
  347. package/neural-interface/lib/account-usage.js +163 -0
  348. package/neural-interface/lib/agent-sandbox-elevation.js +102 -0
  349. package/neural-interface/lib/agent-sandbox-setting.js +57 -0
  350. package/neural-interface/lib/assistant-api.js +274 -52
  351. package/neural-interface/lib/assistant-brains/claude.js +15 -2
  352. package/neural-interface/lib/assistant-brains/codex.js +3 -0
  353. package/neural-interface/lib/assistant-brains/opencode.js +17 -4
  354. package/neural-interface/lib/assistant-budget-planner.js +390 -0
  355. package/neural-interface/lib/assistant-budget-split.js +408 -0
  356. package/neural-interface/lib/assistant-budget.js +94 -95
  357. package/neural-interface/lib/assistant-config.js +62 -14
  358. package/neural-interface/lib/assistant-dispatch.js +432 -125
  359. package/neural-interface/lib/assistant-jev.js +26 -3
  360. package/neural-interface/lib/assistant-persona.js +68 -11
  361. package/neural-interface/lib/assistant-plan-permissions.js +8 -1
  362. package/neural-interface/lib/assistant-route-accounts.js +14 -0
  363. package/neural-interface/lib/assistant-route-gate-codex.js +3 -1
  364. package/neural-interface/lib/assistant-route-gate.js +19 -3
  365. package/neural-interface/lib/assistant-router.js +373 -42
  366. package/neural-interface/lib/assistant-runtime.js +312 -48
  367. package/neural-interface/lib/assistant-user-writer.js +4 -2
  368. package/neural-interface/lib/augmented-path.js +317 -53
  369. package/neural-interface/lib/claude-accounts.js +342 -31
  370. package/neural-interface/lib/claude-agent-bridge.js +90 -3
  371. package/neural-interface/lib/claude-executable.js +6 -1
  372. package/neural-interface/lib/claude-model-catalog.js +3 -3
  373. package/neural-interface/lib/claude-panel-auth.js +221 -0
  374. package/neural-interface/lib/claude-profile-api-key.js +62 -0
  375. package/neural-interface/lib/cli-install-api.js +137 -0
  376. package/neural-interface/lib/cli-install.js +797 -0
  377. package/neural-interface/lib/cli-path.js +130 -0
  378. package/neural-interface/lib/cli-resolver.js +744 -0
  379. package/neural-interface/lib/cli-update-planner.js +143 -7
  380. package/neural-interface/lib/codex-account-auth.js +180 -0
  381. package/neural-interface/lib/codex-accounts.js +502 -0
  382. package/neural-interface/lib/codex-capabilities.js +2 -1
  383. package/neural-interface/lib/codex-compat-rpc.js +8 -2
  384. package/neural-interface/lib/codex-mcp-approvals.js +5 -1
  385. package/neural-interface/lib/codex-panel-auth.js +62 -0
  386. package/neural-interface/lib/codex-session-settings.js +1 -1
  387. package/neural-interface/lib/codex-settings-schema.js +122 -0
  388. package/neural-interface/lib/desktop/config.js +3 -0
  389. package/neural-interface/lib/exec-loop-command.js +3 -1
  390. package/neural-interface/lib/git-bash/bzip2.js +339 -0
  391. package/neural-interface/lib/git-bash/claude-settings.js +73 -0
  392. package/neural-interface/lib/git-bash/extract-worker.js +34 -0
  393. package/neural-interface/lib/git-bash/install.js +529 -0
  394. package/neural-interface/lib/git-bash/licenses/COPYING +360 -0
  395. package/neural-interface/lib/git-bash/licenses/LGPL-2.1 +511 -0
  396. package/neural-interface/lib/git-bash/manager.js +268 -0
  397. package/neural-interface/lib/git-bash/pins.js +206 -0
  398. package/neural-interface/lib/git-bash/pins.json +34 -0
  399. package/neural-interface/lib/git-bash/resolver.js +194 -0
  400. package/neural-interface/lib/git-read.js +34 -2
  401. package/neural-interface/lib/install-details.js +163 -0
  402. package/neural-interface/lib/mcp-installer.js +14 -2
  403. package/neural-interface/lib/native-loop-providers.js +22 -4
  404. package/neural-interface/lib/native-loop-runtime.js +9 -4
  405. package/neural-interface/lib/os-path.js +319 -0
  406. package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
  407. package/neural-interface/lib/provider-panel-terminal-auth.js +96 -0
  408. package/neural-interface/lib/pty-host/core.js +2 -1
  409. package/neural-interface/lib/pty-host/manager.js +27 -2
  410. package/neural-interface/lib/remote-policy.js +20 -11
  411. package/neural-interface/lib/session-indexer.js +3 -1
  412. package/neural-interface/lib/session-title-generator.js +6 -3
  413. package/neural-interface/lib/system-about.js +340 -0
  414. package/neural-interface/lib/system-activity.js +66 -0
  415. package/neural-interface/lib/terminal-env.js +11 -3
  416. package/neural-interface/lib/terminal-path.js +55 -0
  417. package/neural-interface/lib/virtual-socket.js +12 -1
  418. package/neural-interface/lib/whatsapp/baileys-adapter.js +165 -45
  419. package/neural-interface/lib/whatsapp/bridge.js +78 -34
  420. package/neural-interface/lib/whatsapp/commands.js +13 -14
  421. package/neural-interface/lib/whatsapp/config.js +134 -20
  422. package/neural-interface/lib/whatsapp/fake-baileys.js +99 -14
  423. package/neural-interface/lib/whatsapp/host-core.js +168 -35
  424. package/neural-interface/lib/whatsapp/inbound.js +5 -3
  425. package/neural-interface/lib/whatsapp/local-file.js +524 -0
  426. package/neural-interface/lib/whatsapp/manager.js +92 -32
  427. package/neural-interface/lib/whatsapp/paths.js +11 -1
  428. package/neural-interface/lib/whatsapp/protocol.js +65 -5
  429. package/neural-interface/lib/whatsapp/redact.js +63 -0
  430. package/neural-interface/lib/whatsapp/service.js +343 -91
  431. package/neural-interface/lib/whatsapp/transcription.js +463 -0
  432. package/neural-interface/package-lock.json +2 -2
  433. package/neural-interface/package.json +1 -1
  434. package/neural-interface/public/index.html +2 -0
  435. package/neural-interface/public/index2d.html +2 -0
  436. package/neural-interface/public/offline.html +16 -1
  437. package/neural-interface/public/onboarding.html +96 -2
  438. package/neural-interface/public/shared/account-i18n.js +7 -0
  439. package/neural-interface/public/shared/account-menu.js +542 -0
  440. package/neural-interface/public/shared/account-usage.js +79 -0
  441. package/neural-interface/public/shared/account-view.js +336 -0
  442. package/neural-interface/public/shared/agent-runtime-options.js +28 -8
  443. package/neural-interface/public/shared/assistant/asst-account-menu.js +298 -0
  444. package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
  445. package/neural-interface/public/shared/assistant/asst-api.js +39 -2
  446. package/neural-interface/public/shared/assistant/asst-brain-picker.js +288 -32
  447. package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
  448. package/neural-interface/public/shared/assistant/asst-mascot.js +126 -7
  449. package/neural-interface/public/shared/assistant/asst-menu.js +219 -80
  450. package/neural-interface/public/shared/assistant/asst-model-chip.js +135 -0
  451. package/neural-interface/public/shared/assistant/asst-panel.js +87 -16
  452. package/neural-interface/public/shared/assistant/asst-render.js +38 -7
  453. package/neural-interface/public/shared/assistant/asst-route.js +72 -14
  454. package/neural-interface/public/shared/assistant/asst-routes-editor.js +45 -6
  455. package/neural-interface/public/shared/assistant/asst-slash.js +1 -1
  456. package/neural-interface/public/shared/assistant/asst-state.js +1 -1
  457. package/neural-interface/public/shared/assistant/asst-styles.js +160 -29
  458. package/neural-interface/public/shared/cdx/cdx-commands.js +4 -1
  459. package/neural-interface/public/shared/cdx/cdx-features.js +22 -14
  460. package/neural-interface/public/shared/cdx/cdx-icons.js +2 -0
  461. package/neural-interface/public/shared/cdx/cdx-panel.js +49 -108
  462. package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
  463. package/neural-interface/public/shared/cdx/cdx-styles.js +172 -111
  464. package/neural-interface/public/shared/cdx/cdx-tabs.js +264 -664
  465. package/neural-interface/public/shared/cdx/cdx-ws.js +2 -0
  466. package/neural-interface/public/shared/claude-account-view.js +3 -0
  467. package/neural-interface/public/shared/cli-install-view.js +392 -0
  468. package/neural-interface/public/shared/cli-status.js +282 -22
  469. package/neural-interface/public/shared/cp/cp-events.js +2 -1
  470. package/neural-interface/public/shared/cp/cp-styles.js +46 -0
  471. package/neural-interface/public/shared/desktop-bridge.js +39 -0
  472. package/neural-interface/public/shared/desktop-titlebar-page.js +59 -0
  473. package/neural-interface/public/shared/desktop-titlebar-view.js +122 -0
  474. package/neural-interface/public/shared/desktop-titlebar.js +153 -0
  475. package/neural-interface/public/shared/desktop-update-view.js +462 -0
  476. package/neural-interface/public/shared/editor/editor-panel.js +1 -1
  477. package/neural-interface/public/shared/explorer/fx-main.js +1 -1
  478. package/neural-interface/public/shared/git-bash-view.js +145 -0
  479. package/neural-interface/public/shared/html-shell.js +1 -0
  480. package/neural-interface/public/shared/i18n.js +19 -1
  481. package/neural-interface/public/shared/install-details-view.js +86 -0
  482. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -1
  483. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +36 -1
  484. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +17 -0
  485. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +169 -2
  486. package/neural-interface/public/shared/pwa-install-page.js +3 -1
  487. package/neural-interface/public/shared/settings/settings-about-pane.js +271 -0
  488. package/neural-interface/public/shared/settings/settings-about.js +254 -0
  489. package/neural-interface/public/shared/settings/settings-ia.js +15 -0
  490. package/neural-interface/public/shared/showcase-privacy.js +490 -0
  491. package/neural-interface/public/shared/storage.js +30 -3
  492. package/neural-interface/public/shared/styles.css +281 -32
  493. package/neural-interface/public/shared/synabun-mascot.js +48 -2
  494. package/neural-interface/public/shared/ui-claude-panel.js +262 -217
  495. package/neural-interface/public/shared/ui-cli-banner.js +355 -0
  496. package/neural-interface/public/shared/ui-explorer.js +2 -0
  497. package/neural-interface/public/shared/ui-git-bash.js +124 -0
  498. package/neural-interface/public/shared/ui-judgments.js +10 -1
  499. package/neural-interface/public/shared/ui-loading.js +30 -6
  500. package/neural-interface/public/shared/ui-menubar.js +13 -3
  501. package/neural-interface/public/shared/ui-navbar.js +3 -1
  502. package/neural-interface/public/shared/ui-provider-auth.js +104 -0
  503. package/neural-interface/public/shared/ui-provider-terminal-auth.js +223 -0
  504. package/neural-interface/public/shared/ui-sessions.js +9 -3
  505. package/neural-interface/public/shared/ui-settings.js +55 -3
  506. package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
  507. package/neural-interface/public/shared/ui-sync.js +8 -0
  508. package/neural-interface/public/shared/ui-terminal.js +17 -0
  509. package/neural-interface/public/shared/ui-tool-updates.js +5 -0
  510. package/neural-interface/public/shared/ui-update.js +598 -7
  511. package/neural-interface/public/shared/ui-whatsapp.js +21 -6
  512. package/neural-interface/public/shared/whatsapp/wa-view.js +51 -8
  513. package/neural-interface/public/shared/worker-end.js +2 -0
  514. package/neural-interface/public/sw.js +1 -1
  515. package/neural-interface/public/variant/2d/main.js +10 -1
  516. package/neural-interface/public/variant/3d/main.js +10 -1
  517. package/neural-interface/scripts/rebuild-pty.js +14 -2
  518. package/neural-interface/server.js +1384 -679
  519. package/npm-shrinkwrap.json +4424 -0
  520. package/package.json +57 -2
  521. package/postinstall.js +61 -47
  522. package/preuninstall.js +8 -35
  523. package/setup.js +80 -47
  524. package/skills/synabun/modules/idea.md +1 -1
  525. package/skills/synabun/modules/schedule.md +1 -1
  526. package/updater.cmd +2 -2
  527. package/updater.mjs +97 -3
@@ -0,0 +1,183 @@
1
+ /**
2
+ * The SynaBun Assistant's budget split (surface budget-split) — the questions,
3
+ * not the transport.
4
+ *
5
+ * A session has one dollar budget for the assistant's own turns and every
6
+ * worker run. What is left is divided across the providers (Claude Code,
7
+ * Codex, OpenCode) so each provider's runs draw from a share. Jev cannot
8
+ * answer an amount, so each provider gets one Score question over the same
9
+ * state: how much of the spend still to come will land on it. The questions
10
+ * cannot see each other's answers; the caller (neural-interface
11
+ * lib/assistant-budget-split.js) turns the scores into weights, normalises
12
+ * them, applies the brain's reserve, rounds to cents and makes the shares add
13
+ * up to exactly what is left.
14
+ *
15
+ * Jev only divides money the user already allowed: the total, what was spent
16
+ * and what is left are facts in the state, never something it sets. It never
17
+ * picks a model for a task: the questions are about providers' shares of the
18
+ * spend, and routing stays with the brain and the router.
19
+ *
20
+ * Keep the wording of the instructions and levels stable: changing it
21
+ * invalidates the cache and moves the numbers the split was tuned on (the
22
+ * caller's SHARE_LEVEL_FRACTIONS map these five levels). Returns null
23
+ * whenever no judgment is available, including an answer missing for any
24
+ * provider asked; the caller then uses its deterministic split.
25
+ */
26
+
27
+ import { judge, score, readScore, type Answer } from './typesafe.js';
28
+ import { redactCredentials } from './typesafe-config.js';
29
+ import type { JudgeContext } from './memory-judgments.js';
30
+
31
+ export const BUDGET_PROVIDERS = ['claude-code', 'codex', 'opencode'] as const;
32
+ export type BudgetProvider = typeof BUDGET_PROVIDERS[number];
33
+ const NAMES: Record<BudgetProvider, string> = { 'claude-code': 'Claude Code', codex: 'Codex', opencode: 'OpenCode' };
34
+ /** Question ids (code only; never sent as meaning). */
35
+ const QUESTION_IDS: Record<BudgetProvider, string> = { 'claude-code': 'share_claude_code', codex: 'share_codex', opencode: 'share_opencode' };
36
+
37
+ /** The ordered levels of every provider's question, lowest first. */
38
+ export const SHARE_LEVELS = [
39
+ 'None of it: no remaining work is likely to run on this provider, or it cannot run anything now.',
40
+ 'A small part: an occasional task, such as one review or a side task, or only cheap work.',
41
+ 'A moderate part: a regular share of the remaining tasks, alongside the other providers.',
42
+ 'A large part: most of the expensive remaining work, such as the main coding tasks.',
43
+ 'Nearly all of it: practically every remaining dollar is spent on this provider.',
44
+ ] as const;
45
+
46
+ export interface BudgetSplitModel { id?: string | null; price?: string | null; billing?: string | null; tier?: string | null }
47
+ export interface BudgetSplitProvider {
48
+ eligible?: boolean; why?: string | null;
49
+ spentUsd?: number; brainUsd?: number; runsUsd?: number; shareUsd?: number | null; leftUsd?: number | null;
50
+ liveRuns?: number; runningRuns?: number; recentUsd?: number | null;
51
+ account?: { mode?: string | null; usageLeftPercent?: number | null; limited?: boolean | null } | null;
52
+ models?: BudgetSplitModel[];
53
+ }
54
+ export interface BudgetSplitTask { taskClass?: string | null; summary?: string | null; provider?: string | null; model?: string | null }
55
+ export interface BudgetSplitInput {
56
+ /** The providers to ask about (eligible ones); the rest get no share. */
57
+ ask: string[];
58
+ budget: { amountUsd: number; spentUsd: number; leftUsd: number; warnAtUsd?: number | null };
59
+ checkpoint?: { reason?: string | null; needs?: string[] } | null;
60
+ brain?: { provider?: string | null; model?: string | null; price?: string | null; billing?: string | null } | null;
61
+ routeMode?: string | null;
62
+ savedRoutes?: Array<{ taskClass?: string | null; provider?: string | null; model?: string | null; effort?: string | null; price?: string | null; billing?: string | null }>;
63
+ providers?: Partial<Record<string, BudgetSplitProvider>>;
64
+ tasks?: BudgetSplitTask[];
65
+ task?: BudgetSplitTask | null;
66
+ }
67
+
68
+ export interface BudgetShareScore { score: number; confidence: number; probabilities: Record<string, number> | null }
69
+ export interface BudgetSplitJudgment { scores: Partial<Record<BudgetProvider, BudgetShareScore>> }
70
+
71
+ // State budgets (characters, items).
72
+ const SUMMARY_CLIP = 200;
73
+ const MAX_TASKS = 12;
74
+ const MAX_ROUTES = 16;
75
+ const MAX_MODELS = 4;
76
+
77
+ const text = (value: unknown) => (typeof value === 'string' ? value : value === null || value === undefined ? '' : String(value));
78
+ /** Credentials out first, then the cut: a secret split by a cut would slip past the patterns. */
79
+ const safe = (value: unknown, max: number) => {
80
+ const clean = redactCredentials(text(value));
81
+ return clean.length > max ? `${clean.slice(0, max - 1)}…` : clean;
82
+ };
83
+ const usd = (value: unknown) => (Number.isFinite(Number(value)) ? Math.round(Number(value) * 100) / 100 : null);
84
+ const isProvider = (value: unknown): value is BudgetProvider => typeof value === 'string' && (BUDGET_PROVIDERS as readonly string[]).includes(value);
85
+ const label = (value: unknown) => (isProvider(value) ? value : value ? safe(value, 40) : null);
86
+
87
+ function taskRow(row: BudgetSplitTask | null | undefined) {
88
+ if (!row || typeof row !== 'object') return null;
89
+ return { task_class: row.taskClass ? safe(row.taskClass, 20) : null, summary: row.summary ? safe(row.summary, SUMMARY_CLIP) : null, provider: label(row.provider), model: row.model ? safe(row.model, 80) : null };
90
+ }
91
+
92
+ /** The state every provider's question reads, bounded and redacted. Amounts in US dollars. */
93
+ export function budgetSplitState(input: BudgetSplitInput): Record<string, unknown> {
94
+ const budget = input.budget || { amountUsd: 0, spentUsd: 0, leftUsd: 0 };
95
+ const providers: Record<string, unknown> = {};
96
+ for (const provider of BUDGET_PROVIDERS) {
97
+ const row = input.providers?.[provider];
98
+ if (!row) continue;
99
+ providers[provider] = {
100
+ name: NAMES[provider],
101
+ can_run: row.eligible !== false,
102
+ why_not: row.eligible === false && row.why ? safe(row.why, 40) : null,
103
+ spent_usd: usd(row.spentUsd) ?? 0,
104
+ spent_by_assistant_usd: usd(row.brainUsd) ?? 0,
105
+ spent_by_workers_usd: usd(row.runsUsd) ?? 0,
106
+ current_share_usd: usd(row.shareUsd),
107
+ current_share_left_usd: usd(row.leftUsd),
108
+ runs_live: Math.max(0, Math.floor(Number(row.liveRuns) || 0)),
109
+ runs_working_now: Math.max(0, Math.floor(Number(row.runningRuns) || 0)),
110
+ recent_spend_usd: usd(row.recentUsd),
111
+ account: row.account ? {
112
+ billing: row.account.mode ? safe(row.account.mode, 20) : null,
113
+ usage_left_percent: Number.isFinite(Number(row.account.usageLeftPercent)) ? Math.round(Number(row.account.usageLeftPercent)) : null,
114
+ limited: typeof row.account.limited === 'boolean' ? row.account.limited : null,
115
+ } : null,
116
+ models_routed_here: (Array.isArray(row.models) ? row.models : []).slice(0, MAX_MODELS).map((m) => ({
117
+ model: m?.id ? safe(m.id, 80) : null, list_price_per_mtok: m?.price ? safe(m.price, 40) : null, billing: m?.billing ? safe(m.billing, 20) : null, tier: m?.tier ? safe(m.tier, 10) : null,
118
+ })),
119
+ };
120
+ }
121
+ return {
122
+ session_budget: {
123
+ total_usd: usd(budget.amountUsd) ?? 0,
124
+ spent_usd: usd(budget.spentUsd) ?? 0,
125
+ left_usd: usd(budget.leftUsd) ?? 0,
126
+ warning_at_usd: usd(budget.warnAtUsd),
127
+ note: 'Spend is counted at list price for every provider, subscription work included. The budget pays for the assistant\'s own turns and every worker run.',
128
+ },
129
+ checkpoint: {
130
+ reason: input.checkpoint?.reason ? safe(input.checkpoint.reason, 40) : null,
131
+ providers_short_of_money: (Array.isArray(input.checkpoint?.needs) ? input.checkpoint!.needs : []).filter(isProvider),
132
+ },
133
+ assistant: input.brain ? {
134
+ runs_on: label(input.brain.provider), model: input.brain.model ? safe(input.brain.model, 80) : null,
135
+ list_price_per_mtok: input.brain.price ? safe(input.brain.price, 40) : null, billing: input.brain.billing ? safe(input.brain.billing, 20) : null,
136
+ } : null,
137
+ route_mode: input.routeMode ? safe(input.routeMode, 20) : null,
138
+ saved_routes: (Array.isArray(input.savedRoutes) ? input.savedRoutes : []).slice(0, MAX_ROUTES).map((r) => ({
139
+ task_class: r?.taskClass ? safe(r.taskClass, 20) : null, provider: label(r?.provider), model: r?.model ? safe(r.model, 80) : null,
140
+ list_price_per_mtok: r?.price ? safe(r.price, 40) : null, billing: r?.billing ? safe(r.billing, 20) : null,
141
+ })),
142
+ providers,
143
+ session_tasks_so_far: (Array.isArray(input.tasks) ? input.tasks : []).slice(-MAX_TASKS).map(taskRow).filter(Boolean),
144
+ task_being_routed_now: taskRow(input.task),
145
+ };
146
+ }
147
+
148
+ const shareQuestion = (provider: BudgetProvider) => score(
149
+ 'An AI assistant session has a dollar budget (`session_budget`). It pays for the assistant\'s own turns (the assistant runs on `assistant.runs_on`) ' +
150
+ 'and for worker runs on three providers (`providers`). `saved_routes` say which provider each kind of task is sent to, `session_tasks_so_far` are the tasks routed so far, ' +
151
+ '`task_being_routed_now` is the task being routed at this moment, and `checkpoint` says why the budget is being divided again. ' +
152
+ `Of the money still to be spent in this session (\`session_budget.left_usd\`), how much will go to work on ${NAMES[provider]} (\`providers.${provider}\`): its worker runs, ` +
153
+ `plus the assistant's own turns when \`assistant.runs_on\` is ${provider}? Judge from the tasks likely to come next, where the saved routes send them, the runs still working, and what the models cost.`,
154
+ [...SHARE_LEVELS],
155
+ );
156
+
157
+ function probabilitiesOf(answer: Answer | undefined): Record<string, number> | null {
158
+ if (answer?.type !== 'score' || !answer.probabilities || typeof answer.probabilities !== 'object') return null;
159
+ const out: Record<string, number> = {};
160
+ for (const [key, value] of Object.entries(answer.probabilities)) if (Number.isFinite(Number(value))) out[key] = Number(value);
161
+ return Object.keys(out).length ? out : null;
162
+ }
163
+
164
+ /**
165
+ * One request: a Score for each provider in `input.ask`. All or nothing: a
166
+ * missing or wrong-typed answer for any provider asked is no judgment (null).
167
+ */
168
+ export async function judgeBudgetSplit(input: BudgetSplitInput, options: JudgeContext = {}): Promise<BudgetSplitJudgment | null> {
169
+ const ask = [...new Set((Array.isArray(input?.ask) ? input.ask : []).filter(isProvider))];
170
+ if (!ask.length) return null;
171
+ const questions: Record<string, ReturnType<typeof score>> = {};
172
+ for (const provider of ask) questions[QUESTION_IDS[provider]] = shareQuestion(provider);
173
+ const answers = await judge(budgetSplitState(input), questions, { ...options, surface: 'budget-split' });
174
+ if (!answers) return null;
175
+ const scores: BudgetSplitJudgment['scores'] = {};
176
+ for (const provider of ask) {
177
+ const answer = answers[QUESTION_IDS[provider]];
178
+ const read = readScore(answer);
179
+ if (!read) return null;
180
+ scores[provider] = { score: read.score, confidence: read.confidence, probabilities: probabilitiesOf(answer) };
181
+ }
182
+ return { scores };
183
+ }
@@ -166,39 +166,12 @@ function saveCategoriesToDisk(categories: CustomCategory[]): void {
166
166
  });
167
167
  }
168
168
 
169
- /**
170
- * Built-in categories that must always exist (created if missing on startup).
171
- * youtube-videos is the trailer dedup ledger that youtube_upload writes and
172
- * youtube_source reads — pre-seeding it means manual remember / the UI / routing
173
- * all see it, not just the validation-bypassing auto-record path.
174
- */
175
- const BUILTIN_CATEGORIES: Array<{ name: string; description: string; parent?: string }> = [
176
- {
177
- name: 'youtube-videos',
178
- description: 'One memory per uploaded YouTube trailer — the dedup ledger. Written automatically by youtube_upload; tagged with canonical source ids (igdb:/appid:/ytid:/name:) that youtube_source reads to skip already-posted trailers. Do not store anything else here.',
179
- parent: 'social',
180
- },
181
- ];
182
-
183
- /** Append any missing built-in categories and persist. Idempotent. */
184
- function ensureBuiltinCategories(): void {
185
- const cats = loadCategoriesFromDisk();
186
- const have = new Set(cats.map((c) => c.name));
187
- let changed = false;
188
- for (const b of BUILTIN_CATEGORIES) {
189
- if (have.has(b.name)) continue;
190
- const cat: CustomCategory = { name: b.name, description: b.description, created_at: new Date().toISOString() };
191
- if (b.parent && have.has(b.parent)) cat.parent = b.parent; // only attach the parent if it exists
192
- cats.push(cat);
193
- changed = true;
194
- }
195
- if (changed) saveCategoriesToDisk(cats);
196
- }
197
-
198
169
  /**
199
170
  * Initialize the local category cache for the active connection.
200
- * Seeds the cache file from SQLite (source of truth) or empty, then ensures the
201
- * built-in categories exist — even when the cache file already existed.
171
+ * Seeds the cache file from SQLite (source of truth), or empty when the DB has none.
172
+ * No category is created here: a fresh install has none. A project's defaults are
173
+ * made when the project is added, a feature's category the first time it saves
174
+ * (ensureCategory / ensureFeatureCategory).
202
175
  */
203
176
  export async function initCategoryCache(): Promise<void> {
204
177
  const filePath = getCategoriesPath();
@@ -214,10 +187,82 @@ export async function initCategoryCache(): Promise<void> {
214
187
  fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
215
188
  }
216
189
 
217
- ensureBuiltinCategories();
218
190
  invalidateCategoryCache();
219
191
  }
220
192
 
193
+ /**
194
+ * Categories that features save into by name. None exists on a fresh install:
195
+ * each is created the first time something is stored in it (ensureFeatureCategory),
196
+ * whether the writer is SynaBun itself or a model told to use that name by a hook.
197
+ * Keep the descriptions in step with hooks/claude-code/shared.mjs FEATURE_CATEGORIES.
198
+ */
199
+ export const FEATURE_CATEGORIES: Record<string, { description: string; is_parent?: boolean }> = {
200
+ conversations: {
201
+ description: 'Indexed conversation sessions with compacted summaries for cross-session recall: session metadata (date, branch, session id) and topic summaries, so past conversations can be found by asking for them.',
202
+ },
203
+ 'communication-style': {
204
+ description: 'How the user communicates and works: patterns in how they give instructions, correct, decide and signal frustration, and their stated preferences.',
205
+ },
206
+ plans: {
207
+ description: 'Implementation plans stored after plan mode approval, one child category per project.',
208
+ is_parent: true,
209
+ },
210
+ uncategorized: {
211
+ description: 'Fallback for memories stored without a category.',
212
+ },
213
+ ideas: {
214
+ description: 'Brainstormed features, experiments, and creative explorations (the /synabun idea skill).',
215
+ },
216
+ schedules: {
217
+ description: 'One memory per schedule created from a conversation (the /synabun schedule skill): name, template, schedule id, cron, group, profile and model.',
218
+ },
219
+ 'youtube-videos': {
220
+ description: 'One memory per uploaded YouTube trailer — the dedup ledger. Written automatically by youtube_upload; tagged with canonical source ids (igdb:/appid:/ytid:/name:) that youtube_source reads to skip already-posted trailers. Do not store anything else here.',
221
+ },
222
+ };
223
+
224
+ /**
225
+ * Create a category the first time something saves into it. Idempotent: an existing
226
+ * category is left exactly as it is. The parent is attached only when it already
227
+ * exists. Returns true when the category exists afterwards.
228
+ */
229
+ export function ensureCategory(name: string, description: string, options: { parent?: string; is_parent?: boolean } = {}): boolean {
230
+ try {
231
+ if (categoryExists(name)) return true;
232
+ const parent = options.parent && categoryExists(options.parent) ? options.parent : undefined;
233
+ addCategory(name, description, parent, undefined, options.is_parent && !parent);
234
+ return true;
235
+ } catch (err) {
236
+ console.error(`Failed to create category "${name}":`, err instanceof Error ? err.message : err);
237
+ return categoryExists(name);
238
+ }
239
+ }
240
+
241
+ /**
242
+ * ensureCategory for a name of FEATURE_CATEGORIES, or a project's plan category
243
+ * `plans-<project>` (made under "plans", as hooks/claude-code/post-plan.mjs does);
244
+ * false for any other name.
245
+ */
246
+ export function ensureFeatureCategory(name: string): boolean {
247
+ const feature = Object.prototype.hasOwnProperty.call(FEATURE_CATEGORIES, name) ? FEATURE_CATEGORIES[name] : null;
248
+ if (feature) return ensureCategory(name, feature.description, { is_parent: feature.is_parent });
249
+ if (/^plans-[a-z0-9][a-z0-9-]*$/.test(name) && name.length <= MAX_LENGTH && ensureFeatureCategory('plans')) {
250
+ return ensureCategory(name, `Implementation plans for ${name.slice('plans-'.length)} project`, { parent: 'plans' });
251
+ }
252
+ return false;
253
+ }
254
+
255
+ /**
256
+ * The name of a project's default parent category, as the project's defaults
257
+ * are created (hooks/claude-code/shared.mjs projectCategoryLabel): the label
258
+ * normalized and cut to 16 characters, so `<label>-architecture` stays within 30.
259
+ */
260
+ export function projectCategoryLabel(project: string): string {
261
+ let label = String(project || '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
262
+ if (label.length > 16) label = label.slice(0, 16).replace(/-$/, '');
263
+ return label || 'global';
264
+ }
265
+
221
266
  /**
222
267
  * Call after a connection switch to re-point the file watcher
223
268
  * at the new connection's category file and sync from database if needed.
@@ -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');
@@ -16,7 +16,7 @@
16
16
  import { readdirSync, readFileSync } from 'node:fs';
17
17
  import path from 'node:path';
18
18
  import { performance } from 'node:perf_hooks';
19
- import { z } from 'zod';
19
+ import { z } from 'zod/v3';
20
20
  import type { JudgeUsage } from './typesafe.js';
21
21
  import { typesafeConfig, mutateTypeSafeConfig, type TypeSafeConfig } from './typesafe-config.js';
22
22
  import { hashCorpusFiles, isPinnedModel } from './browser-assist-gate.js';
@@ -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 ---