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
@@ -7,7 +7,7 @@
7
7
  // (`agent.assistant.prompt`) in the per-serve config so the model sees it as
8
8
  // its system prompt; SSE events are translated to the unified envelope.
9
9
 
10
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
10
+ import { chmodSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
11
11
  import { dirname, resolve } from 'node:path';
12
12
  import { fileURLToPath, pathToFileURL } from 'node:url';
13
13
  import { normalizeOpenCodeModel } from '../native-loop-providers.js';
@@ -46,11 +46,14 @@ export const OPENCODE_ROUTE_GATE_PLUGIN = pathToFileURL(resolve(dirname(fileURLT
46
46
  */
47
47
  export const OPENCODE_PLAN_AGENT = 'synabun-plan';
48
48
 
49
+ /** The brain's SynaBun MCP tool-call timeout (ms): as long as the Codex brain's tool_timeout_sec (server.js). */
50
+ export const OPENCODE_BRAIN_MCP_TIMEOUT_MS = 180_000;
51
+
49
52
  /**
50
53
  * `routeGate` ({ url, session, token }) installs the route-gate plugin in this
51
54
  * isolated serve's config only (assistant brains; never the shared sidepanel setup).
52
55
  */
53
- export function patchOpenCodeAssistantConfig(xdgRoot, { persona = '', role = 'assistant', model = null, desktopGrant = null, routeGate = null } = {}) {
56
+ export function patchOpenCodeAssistantConfig(xdgRoot, { persona = '', role = 'assistant', model = null, desktopGrant = null, routeGate = null, brainCredential = null } = {}) {
54
57
  const configPath = resolve(xdgRoot, 'opencode', 'config.json');
55
58
  if (!existsSync(configPath)) return false;
56
59
  const config = JSON.parse(readFileSync(configPath, 'utf8'));
@@ -60,7 +63,15 @@ export function patchOpenCodeAssistantConfig(xdgRoot, { persona = '', role = 'as
60
63
  // Computer use: an unforgeable per-brain grant unlocks the `computer` group.
61
64
  if (desktopGrant) env.SYNABUN_DESKTOP_GRANT = desktopGrant;
62
65
  else delete env.SYNABUN_DESKTOP_GRANT;
66
+ // A WhatsApp conversation's brain credential (whatsapp_send_file), for this serve's SynaBun MCP child only.
67
+ // This file names the variable; the value is in the serve's own environment (ensureIsolatedServe), so
68
+ // neither this file (it is in every backup of data/) nor OPENCODE_CONFIG_CONTENT ever holds it.
69
+ if (brainCredential) env.SYNABUN_BRAIN_TOKEN = '{env:SYNABUN_BRAIN_TOKEN}';
70
+ else delete env.SYNABUN_BRAIN_TOKEN;
63
71
  config.mcp.SynaBun[envKey] = env;
72
+ // OpenCode gives an MCP tool call 60 s unless the server says otherwise; agent_route may wait on the
73
+ // user's route card and whatsapp_send_file on an upload (130 s): the Codex brain's 180 s here too.
74
+ config.mcp.SynaBun.timeout = OPENCODE_BRAIN_MCP_TIMEOUT_MS;
64
75
  }
65
76
  config.agent = { ...(config.agent || {}) };
66
77
  config.agent.assistant = {
@@ -89,6 +100,8 @@ export function patchOpenCodeAssistantConfig(xdgRoot, { persona = '', role = 'as
89
100
  if (routeGate?.url && routeGate?.session && routeGate?.token) plugins.push([OPENCODE_ROUTE_GATE_PLUGIN, { url: routeGate.url, session: routeGate.session, token: routeGate.token, ...(routeGate.remote === true ? { remote: true } : {}) }]);
90
101
  if (plugins.length) config.plugin = plugins; else delete config.plugin;
91
102
  writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
103
+ // It carries this brain's grants (the desktop grant): readable by this user only.
104
+ try { chmodSync(configPath, 0o600); } catch { /* a filesystem without modes */ }
92
105
  return true;
93
106
  }
94
107
 
@@ -203,8 +216,8 @@ export function createOpenCodeBrain({ session, sink, deps = {}, persona = '' } =
203
216
  if (startPromise) return startPromise;
204
217
  startPromise = (async () => {
205
218
  const xdgRoot = deps.setupOpencodeSidepanelConfig(termId, brain.mcpProfile || null);
206
- try { personaInstalled = patchOpenCodeAssistantConfig(xdgRoot, { persona, model: null, desktopGrant: deps.desktopGrant || null, routeGate: deps.routeGate || null }); } catch { personaInstalled = false; }
207
- entry = await deps.ensureIsolatedServe(termId, { xdgRoot, keepAlive: true, mcpProfile: brain.mcpProfile || null });
219
+ try { personaInstalled = patchOpenCodeAssistantConfig(xdgRoot, { persona, model: null, desktopGrant: deps.desktopGrant || null, routeGate: deps.routeGate || null, brainCredential: deps.brainCredential || null }); } catch { personaInstalled = false; }
220
+ entry = await deps.ensureIsolatedServe(termId, { xdgRoot, keepAlive: true, mcpProfile: brain.mcpProfile || null, brainCredential: deps.brainCredential || null });
208
221
  if (!entry?.client) throw new Error('OpenCode isolated serve failed to start');
209
222
  client = entry.client;
210
223
  await client.waitUntilConnected?.(20_000);
@@ -0,0 +1,390 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun — Assistant budget planner (per-session split, checkpoints, Jev)
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // The state behind the budget split (the math is assistant-budget-split.js):
6
+ //
7
+ // - The split of each session, cached on it: made at the session's first
8
+ // actionable route or dispatch (ensureSplit), made again only at a
9
+ // checkpoint (resplit): a route, dispatch or brain turn its provider's
10
+ // share cannot cover (ensureCovered), a change of the budget
11
+ // (onBudgetChanged), a run ending (onRunEnded). Never per tick or per
12
+ // metered step: coverage() and covers() only read.
13
+ // - Jev (surface budget-split) weighs the providers; when it is off,
14
+ // unreachable or answers nothing usable, the deterministic weights are
15
+ // used. The split records which (`source`: jev | fallback) and when.
16
+ // - Re-splits of one session run one at a time; calls that arrive while one
17
+ // waits to start are merged into it.
18
+ //
19
+ // Under No limit nothing is split and every check passes.
20
+ //
21
+ // Every dependency is injected (server.js wires the dispatcher, the runtime,
22
+ // the catalog, the accounts, the config store and Jev); nothing here throws
23
+ // at its callers.
24
+
25
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
26
+ import { dirname } from 'node:path';
27
+ import { BUDGET_DEFAULT_USD, BUDGET_WARN_RATIO, roundUsd } from './assistant-budget.js';
28
+ import {
29
+ BUDGET_PROVIDERS, MIN_SPEND_USD, billingOf, bucketEligibility, budgetView, coverageOf, coversWork, divideLeft,
30
+ dryProviders, fallbackWeights, jevWeights, listPriceOf, priceLabel, providerSpendOf, rowOf, totalSpentOf,
31
+ } from './assistant-budget-split.js';
32
+
33
+ export const SPLIT_REASONS = Object.freeze(['first_route', 'share_used_up', 'brain_turn', 'budget_changed', 'run_ended']);
34
+ const MAX_SESSIONS = 200;
35
+ const MAX_TASKS = 20;
36
+ // A provider found short again this soon after a re-split for it is not re-split again: the split stands
37
+ // (its runs stop, its tasks move). The brain's own turns re-split again as soon as it spent since.
38
+ const NEED_RETRY_MS = 5_000;
39
+ const RUN_END_DEBOUNCE_MS = 1500;
40
+
41
+ const arr = (value) => (Array.isArray(value) ? value : []);
42
+ const clip = (value, max = 200) => { const text = String(value ?? '').replace(/\s+/g, ' ').trim(); return text.length > max ? `${text.slice(0, max - 1)}…` : text; };
43
+ const iso = (ms) => new Date(ms).toISOString();
44
+
45
+ export function createBudgetPlanner({
46
+ setting = () => ({ unlimited: false, amountUsd: BUDGET_DEFAULT_USD, version: 0 }),
47
+ // (sessionId) => { providers: { [p]: { brainUsd, runsUsd, liveRuns, runningRuns } }, unpricedRuns, liveRuns: [{ provider, model, taskClass }] }
48
+ spendOf = () => ({ providers: {}, unpricedRuns: 0, liveRuns: [] }),
49
+ // (sessionId) => { provider, model, accountId, project } | null
50
+ brainOf = () => null,
51
+ // (sessionId) => is the session still open (a split of an ended one is not redone)
52
+ isOpen = () => true,
53
+ catalog = () => null,
54
+ refreshCatalog = null,
55
+ accounts = () => null,
56
+ routing = () => ({ preferences: {} }),
57
+ routeMode = () => null,
58
+ workers = () => ({}),
59
+ pricing = null,
60
+ // (sessionId) => { [provider]: usd } of this session from the usage ledger, or null
61
+ recentSpend = () => null,
62
+ // assistant-jev.js: { available(), budgetSplit(input, ctx), annotate(logId, outcome) } or null
63
+ judge = null,
64
+ emit = () => {},
65
+ statePath = null,
66
+ now = Date.now,
67
+ log = () => {},
68
+ } = {}) {
69
+ const sessions = new Map(); // id → { split, tasks: [], updatedAt }
70
+ const chains = new Map(); // id → { promise, queued, waiting }
71
+ const needTries = new Map(); // `${id}:${provider}` → { at, version }
72
+ const runEndTimers = new Map();
73
+ let persistTimer = null;
74
+
75
+ // ── persistence ────────────────────────────────────────────────────────
76
+ function load() {
77
+ if (!statePath || !existsSync(statePath)) return;
78
+ try {
79
+ const raw = JSON.parse(readFileSync(statePath, 'utf8'));
80
+ for (const [id, row] of Object.entries(raw?.sessions || {})) {
81
+ if (!row || typeof row !== 'object') continue;
82
+ sessions.set(id, { split: row.split && typeof row.split === 'object' ? row.split : null, tasks: arr(row.tasks).slice(-MAX_TASKS), updatedAt: row.updatedAt || null });
83
+ }
84
+ } catch (error) { log('budget:state-unreadable', error?.message || String(error)); }
85
+ }
86
+ function writeState() {
87
+ if (!statePath) return;
88
+ try {
89
+ const rows = [...sessions.entries()].sort((a, b) => String(b[1].updatedAt || '').localeCompare(String(a[1].updatedAt || ''))).slice(0, MAX_SESSIONS);
90
+ for (const [id] of [...sessions.entries()].filter(([id]) => !rows.some(([kept]) => kept === id))) sessions.delete(id);
91
+ mkdirSync(dirname(statePath), { recursive: true });
92
+ const tmp = `${statePath}.${process.pid}.tmp`;
93
+ writeFileSync(tmp, JSON.stringify({ version: 1, sessions: Object.fromEntries(rows) }, null, 2) + '\n', 'utf8');
94
+ renameSync(tmp, statePath);
95
+ } catch (error) { log('budget:state-error', error?.message || String(error)); }
96
+ }
97
+ function persist() {
98
+ if (!statePath || persistTimer) return;
99
+ persistTimer = setTimeout(() => { persistTimer = null; writeState(); }, 300);
100
+ persistTimer.unref?.();
101
+ }
102
+ function rowOfSession(id, { create = false } = {}) {
103
+ let row = sessions.get(id) || null;
104
+ if (!row && create) { row = { split: null, tasks: [], updatedAt: iso(now()) }; sessions.set(id, row); }
105
+ return row;
106
+ }
107
+
108
+ // ── reads ──────────────────────────────────────────────────────────────
109
+ function readSetting() {
110
+ try {
111
+ const value = setting() || {};
112
+ return { unlimited: value.unlimited === true, amountUsd: Number(value.amountUsd) > 0 ? Number(value.amountUsd) : BUDGET_DEFAULT_USD, version: Number(value.version) || 0 };
113
+ } catch { return { unlimited: false, amountUsd: BUDGET_DEFAULT_USD, version: 0 }; }
114
+ }
115
+ function limited() { return !readSetting().unlimited; }
116
+ function safe(fn, fallback = null) { try { const value = fn(); return value === undefined ? fallback : value; } catch { return fallback; } }
117
+ function spend(id) { return safe(() => spendOf(id), null) || { providers: {}, unpricedRuns: 0, liveRuns: [] }; }
118
+ function brain(id) { return safe(() => brainOf(id), null); }
119
+ function splitOf(id) { return sessions.get(String(id || ''))?.split || null; }
120
+ function coverage(id) {
121
+ const value = readSetting();
122
+ if (value.unlimited) return null;
123
+ const sessionId = String(id || '');
124
+ return coverageOf({ amountUsd: value.amountUsd, split: splitOf(sessionId), spend: spend(sessionId), brainProvider: brain(sessionId)?.provider || null });
125
+ }
126
+ /** Can `provider` pay for more work in the session now (No limit: always). */
127
+ function covers(id, { provider, forBrain = false, free = false } = {}) {
128
+ if (!id) return true;
129
+ const cov = coverage(id);
130
+ return cov ? coversWork(cov, { provider, forBrain, free }) : true;
131
+ }
132
+ function view(id) {
133
+ const sessionId = String(id || '');
134
+ const value = readSetting();
135
+ const used = spend(sessionId);
136
+ const brainProvider = brain(sessionId)?.provider || null;
137
+ return budgetView({
138
+ sessionId, setting: value, split: value.unlimited ? null : splitOf(sessionId), spend: used, unpricedRuns: used.unpricedRuns || 0, brainProvider,
139
+ coverage: value.unlimited ? null : coverageOf({ amountUsd: value.amountUsd, split: splitOf(sessionId), spend: used, brainProvider }),
140
+ });
141
+ }
142
+
143
+ // ── Jev's state ────────────────────────────────────────────────────────
144
+ function accountFor(provider, rows, brainInfo) {
145
+ const list = arr(rows?.[provider]);
146
+ if (!list.length) return null;
147
+ const wanted = provider === brainInfo?.provider ? (brainInfo.accountId || 'default')
148
+ : (safe(() => workers()?.[provider === 'codex' ? 'codexAccount' : 'claudeAccount']) || 'default');
149
+ return list.find((row) => row?.id === wanted) || list.find((row) => row?.id === 'default') || list[0];
150
+ }
151
+ function modelInfo(cat, provider, model, account = null) {
152
+ const row = rowOf(cat, provider, model);
153
+ const price = listPriceOf(row, { pricing, provider, model });
154
+ return { id: model || row?.id || null, price: priceLabel(row, price), billing: billingOf(provider, price, account), tier: row?.tier || null };
155
+ }
156
+ function jevInput(id, { value, used, cov, eligible, brainInfo, cat, needs, task, reason, tasks }) {
157
+ const rows = safe(() => accounts(), null);
158
+ const prefs = safe(() => routing()?.preferences, {}) || {};
159
+ const savedRoutes = Object.entries(prefs).filter(([, pref]) => pref?.provider).map(([taskClass, pref]) => {
160
+ const info = modelInfo(cat, pref.provider, pref.model || null, accountFor(pref.provider, rows, brainInfo));
161
+ return { taskClass, provider: pref.provider, model: pref.model || null, effort: pref.effort || null, price: info.price, billing: info.billing };
162
+ });
163
+ const recent = safe(() => recentSpend(id), null) || {};
164
+ const providers = {};
165
+ for (const provider of BUDGET_PROVIDERS) {
166
+ const own = providerSpendOf(used, provider);
167
+ const account = accountFor(provider, rows, brainInfo);
168
+ const routed = savedRoutes.filter((r) => r.provider === provider);
169
+ const models = [];
170
+ for (const r of routed) if (!models.some((m) => m.id === r.model)) models.push({ id: r.model, price: r.price, billing: r.billing, tier: rowOf(cat, provider, r.model)?.tier || null });
171
+ providers[provider] = {
172
+ eligible: eligible[provider]?.eligible !== false, why: eligible[provider]?.reason || null,
173
+ spentUsd: own.spentUsd, brainUsd: own.brainUsd, runsUsd: own.runsUsd,
174
+ shareUsd: cov?.hasSplit ? cov.providers[provider].shareUsd : null, leftUsd: cov?.hasSplit ? cov.providers[provider].leftUsd : null,
175
+ liveRuns: own.liveRuns, runningRuns: own.runningRuns, recentUsd: Number.isFinite(Number(recent[provider])) ? Number(recent[provider]) : null,
176
+ account: account ? { mode: account.mode === 'apiKey' ? 'api key' : account.mode || null, usageLeftPercent: account.usage?.remainingPercent ?? null, limited: account.usage?.limited ?? null } : null,
177
+ models,
178
+ };
179
+ }
180
+ const brainModel = brainInfo?.provider ? modelInfo(cat, brainInfo.provider, brainInfo.model || null, accountFor(brainInfo.provider, rows, brainInfo)) : null;
181
+ const spentUsd = totalSpentOf(used);
182
+ return {
183
+ ask: BUDGET_PROVIDERS.filter((p) => eligible[p]?.eligible !== false),
184
+ budget: { amountUsd: value.amountUsd, spentUsd, leftUsd: Math.max(0, value.amountUsd - spentUsd), warnAtUsd: roundUsd(value.amountUsd * BUDGET_WARN_RATIO) },
185
+ checkpoint: { reason, needs },
186
+ brain: brainInfo?.provider ? { provider: brainInfo.provider, model: brainInfo.model || null, price: brainModel?.price || null, billing: brainModel?.billing || null } : null,
187
+ routeMode: safe(() => routeMode(id), null),
188
+ savedRoutes, providers,
189
+ tasks: arr(tasks).map((t) => ({ taskClass: t.taskClass || null, summary: t.summary || null, provider: t.provider || null, model: t.model || null })),
190
+ task: task ? { taskClass: task.taskClass || null, summary: task.summary || null, provider: task.provider || null, model: task.model || null } : null,
191
+ };
192
+ }
193
+
194
+ // ── making a split ─────────────────────────────────────────────────────
195
+ async function catalogNow() {
196
+ const cached = safe(() => catalog(), null);
197
+ if (cached || typeof refreshCatalog !== 'function') return cached;
198
+ try { return (await refreshCatalog()) || null; } catch { return null; }
199
+ }
200
+ async function compute(id, want = {}) {
201
+ const value = readSetting();
202
+ if (value.unlimited) return null;
203
+ const reason = SPLIT_REASONS.includes(want.reason) ? want.reason : 'first_route';
204
+ const needs = [...new Set(arr(want.needs).filter((p) => BUDGET_PROVIDERS.includes(p)))];
205
+ const row = rowOfSession(id, { create: true });
206
+ if (want.task) noteTaskRow(row, want.task, { current: true });
207
+ const cat = await catalogNow();
208
+ const used = spend(id);
209
+ const brainInfo = brain(id);
210
+ const brainProvider = BUDGET_PROVIDERS.includes(brainInfo?.provider) ? brainInfo.provider : null;
211
+ const eligible = bucketEligibility({ catalog: cat, accounts: safe(() => accounts(), null), brainProvider });
212
+ const cov = coverageOf({ amountUsd: value.amountUsd, split: row.split, spend: used, brainProvider });
213
+ const leftUsd = Math.max(0, value.amountUsd - totalSpentOf(used));
214
+ const tasks = row.tasks.filter((t) => !t.current);
215
+ const task = want.task || row.tasks.find((t) => t.current) || null;
216
+ let weights = null;
217
+ let jev = null;
218
+ let fallbackWhy = null;
219
+ if (leftUsd < MIN_SPEND_USD) fallbackWhy = 'nothing_left';
220
+ else if (!judge?.budgetSplit) fallbackWhy = 'jev_off';
221
+ else if (typeof judge.available === 'function' && !safe(() => judge.available(), false)) fallbackWhy = 'jev_off';
222
+ else {
223
+ let judged = null;
224
+ try {
225
+ judged = await judge.budgetSplit(jevInput(id, { value, used, cov, eligible, brainInfo, cat, needs, task, reason, tasks }), { sessionId: id, project: brainInfo?.project || null });
226
+ } catch { judged = null; }
227
+ weights = judged ? jevWeights(judged, eligible) : null;
228
+ if (weights) jev = { logId: judged.logId ?? null, scores: judged.scores };
229
+ else fallbackWhy = judged ? 'jev_incomplete' : 'jev_unavailable';
230
+ // A setting changed while Jev answered: this split is for an old budget.
231
+ if (readSetting().version !== value.version) return compute(id, want);
232
+ }
233
+ if (!weights) {
234
+ weights = fallbackWeights({
235
+ eligible, routing: safe(() => routing(), {}) || {}, brain: brainInfo, catalog: cat, pricing,
236
+ tasks, task, liveRuns: arr(used.liveRuns), needs,
237
+ });
238
+ }
239
+ // What was spent is read again: Jev took up to its timeout, and money may have moved meanwhile.
240
+ const fresh = spend(id);
241
+ const freshLeft = Math.max(0, value.amountUsd - totalSpentOf(fresh));
242
+ const divided = divideLeft({ leftUsd: freshLeft, weights, eligible, brainProvider });
243
+ const spentAtSplit = Object.fromEntries(BUDGET_PROVIDERS.map((p) => [p, providerSpendOf(fresh, p).spentUsd]));
244
+ const shares = Object.fromEntries(BUDGET_PROVIDERS.map((p) => [p, Number((spentAtSplit[p] + divided.allocCents[p] / 100).toFixed(8))]));
245
+ const split = {
246
+ source: jev ? 'jev' : 'fallback', fallbackWhy: jev ? null : fallbackWhy, reason, at: iso(now()),
247
+ settingVersion: value.version, amountUsd: value.amountUsd,
248
+ leftUsd: divided.leftCents / 100,
249
+ shares, alloc: Object.fromEntries(BUDGET_PROVIDERS.map((p) => [p, divided.allocCents[p] / 100])), spentAtSplit,
250
+ brainProvider, brainFloorUsd: divided.brainFloorCents / 100,
251
+ brainSpentAtSplitUsd: brainProvider ? providerSpendOf(fresh, brainProvider).brainUsd : 0,
252
+ eligible, weights, needs, jev,
253
+ };
254
+ row.split = split;
255
+ row.updatedAt = split.at;
256
+ for (const t of row.tasks) delete t.current;
257
+ persist();
258
+ if (jev?.logId && typeof judge?.annotate === 'function') {
259
+ try { judge.annotate(jev.logId, { applied: true, reason, needs, alloc: split.alloc, brainFloorUsd: split.brainFloorUsd, leftUsd: split.leftUsd }); } catch { /* diagnostics */ }
260
+ }
261
+ log('budget:split', `${id}: ${split.source}${fallbackWhy && !jev ? ` (${fallbackWhy})` : ''} · ${reason} · left $${split.leftUsd.toFixed(2)} · ${BUDGET_PROVIDERS.map((p) => `${p} $${split.alloc[p].toFixed(2)}`).join(', ')}`);
262
+ try { emit(id, view(id), { reason }); } catch { /* the live packet is best effort */ }
263
+ return split;
264
+ }
265
+ function mergeWant(base, next = {}) {
266
+ const order = (reason) => ['share_used_up', 'brain_turn', 'first_route', 'budget_changed', 'run_ended'].indexOf(reason);
267
+ if (!base) return { reason: next.reason || 'first_route', needs: [...new Set(arr(next.needs))], task: next.task || null };
268
+ const reason = order(next.reason) !== -1 && (order(base.reason) === -1 || order(next.reason) < order(base.reason)) ? next.reason : base.reason;
269
+ return { reason, needs: [...new Set([...arr(base.needs), ...arr(next.needs)])], task: next.task || base.task || null };
270
+ }
271
+ /** A checkpoint: divide what is left again. Calls made while one waits to start join it. Never rejects. */
272
+ function resplit(sessionId, want = {}) {
273
+ const id = String(sessionId || '');
274
+ if (!id || !limited()) return Promise.resolve(null);
275
+ let chain = chains.get(id);
276
+ if (!chain) { chain = { promise: Promise.resolve(), queued: null, waiting: null }; chains.set(id, chain); }
277
+ if (chain.queued) { chain.queued = mergeWant(chain.queued, want); return chain.waiting; }
278
+ chain.queued = mergeWant(null, want);
279
+ const waiting = chain.promise.then(() => {
280
+ const next = chain.queued;
281
+ chain.queued = null;
282
+ return compute(id, next);
283
+ }).catch((error) => { log('budget:split-error', `${id}: ${error?.message || String(error)}`); return splitOf(id); });
284
+ chain.waiting = waiting;
285
+ chain.promise = waiting;
286
+ return waiting;
287
+ }
288
+ /** The session's split for the budget as it is now, made when there is none (the first route or dispatch). */
289
+ async function ensureSplit(sessionId, { task = null, reason = null } = {}) {
290
+ const id = String(sessionId || '');
291
+ const value = readSetting();
292
+ if (!id || value.unlimited) return null;
293
+ const current = splitOf(id);
294
+ if (current && current.settingVersion === value.version && current.amountUsd === value.amountUsd) {
295
+ if (task) noteTask(id, task);
296
+ return current;
297
+ }
298
+ return resplit(id, { reason: reason || (current ? 'budget_changed' : 'first_route'), task });
299
+ }
300
+ /**
301
+ * Can `provider` pay for this work? When its share is used up but the session has money left, the
302
+ * unspent budget is divided again first (a checkpoint: Jev, else the fallback, decides whether the
303
+ * provider gets more; at most once per provider every few seconds). `forBrain`: a brain turn (it may
304
+ * use its reserve). `free`: a free model (only the session limit counts).
305
+ */
306
+ async function ensureCovered(sessionId, { provider, forBrain = false, free = false, task = null, reason = null } = {}) {
307
+ const id = String(sessionId || '');
308
+ if (!id || !limited()) return true;
309
+ let cov = coverage(id);
310
+ if (coversWork(cov, { provider, forBrain, free })) return true;
311
+ if (cov.leftUsd < MIN_SPEND_USD || free) return false;
312
+ const key = `${id}:${provider}:${forBrain ? 'brain' : 'runs'}`;
313
+ const tried = needTries.get(key);
314
+ const value = readSetting();
315
+ const spentNow = cov.spentUsd;
316
+ // Never a re-split per metered step: a provider the split left short a moment ago stays short.
317
+ if (tried && now() - tried.at < NEED_RETRY_MS && tried.version === value.version && cov.hasSplit && (!forBrain || tried.spent === spentNow)) return false;
318
+ needTries.set(key, { at: now(), version: value.version, spent: spentNow });
319
+ await resplit(id, { reason: reason || (forBrain ? 'brain_turn' : 'share_used_up'), needs: provider ? [provider] : [], task });
320
+ cov = coverage(id);
321
+ return coversWork(cov, { provider, forBrain, free });
322
+ }
323
+
324
+ // ── tasks ──────────────────────────────────────────────────────────────
325
+ function noteTaskRow(row, task, { current = false } = {}) {
326
+ if (!task || typeof task !== 'object') return;
327
+ const entry = {
328
+ taskClass: task.taskClass ? String(task.taskClass).slice(0, 20) : null, summary: clip(task.summary || '', 160) || null,
329
+ provider: BUDGET_PROVIDERS.includes(task.provider) ? task.provider : null, model: task.model ? String(task.model).slice(0, 120) : null,
330
+ at: iso(now()), ...(current ? { current: true } : {}),
331
+ };
332
+ const last = row.tasks.at(-1);
333
+ if (last && last.summary === entry.summary && last.taskClass === entry.taskClass && last.provider === entry.provider) { Object.assign(last, entry); return; }
334
+ row.tasks.push(entry);
335
+ if (row.tasks.length > MAX_TASKS) row.tasks.splice(0, row.tasks.length - MAX_TASKS);
336
+ }
337
+ /** A task the session routed (agent_route approved it, or a dispatch started): Jev's next state lists it. */
338
+ function noteTask(sessionId, task) {
339
+ const id = String(sessionId || '');
340
+ if (!id || !task) return;
341
+ const row = rowOfSession(id, { create: true });
342
+ noteTaskRow(row, task);
343
+ row.updatedAt = iso(now());
344
+ persist();
345
+ }
346
+
347
+ // ── checkpoints from outside ───────────────────────────────────────────
348
+ /** The budget setting changed: every open session with a split divides what is left again. */
349
+ function onBudgetChanged() {
350
+ needTries.clear();
351
+ if (!limited()) return;
352
+ for (const [id, row] of sessions) {
353
+ if (!row.split || !safe(() => isOpen(id), true)) continue;
354
+ resplit(id, { reason: 'budget_changed' }).catch(() => {});
355
+ }
356
+ }
357
+ /** A run of the session ended: what it did not spend goes back into the split (debounced). */
358
+ function onRunEnded(sessionId) {
359
+ const id = String(sessionId || '');
360
+ if (!id || !limited() || !splitOf(id)) return;
361
+ if (runEndTimers.has(id)) clearTimeout(runEndTimers.get(id));
362
+ const timer = setTimeout(() => {
363
+ runEndTimers.delete(id);
364
+ if (safe(() => isOpen(id), true)) resplit(id, { reason: 'run_ended' }).catch(() => {});
365
+ }, RUN_END_DEBOUNCE_MS);
366
+ timer.unref?.();
367
+ runEndTimers.set(id, timer);
368
+ }
369
+ function forget(sessionId) {
370
+ const id = String(sessionId || '');
371
+ sessions.delete(id);
372
+ chains.delete(id);
373
+ persist();
374
+ }
375
+ /** Settled for a test or a shutdown: pending timers cleared, state written now. */
376
+ function flush() {
377
+ for (const timer of runEndTimers.values()) clearTimeout(timer);
378
+ runEndTimers.clear();
379
+ if (persistTimer) { clearTimeout(persistTimer); persistTimer = null; }
380
+ writeState();
381
+ }
382
+ /** What every queued re-split resolves to (tests). */
383
+ function settled(sessionId) { return chains.get(String(sessionId || ''))?.promise || Promise.resolve(splitOf(sessionId)); }
384
+
385
+ load();
386
+ return {
387
+ setting: readSetting, limited, coverage, covers, view, splitOf, dry: (id) => dryProviders(coverage(id)),
388
+ ensureSplit, resplit, ensureCovered, noteTask, onBudgetChanged, onRunEnded, forget, flush, settled,
389
+ };
390
+ }