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
@@ -2,10 +2,10 @@
2
2
  // SynaBun — WhatsApp Link: the owner's slash commands
3
3
  // ═══════════════════════════════════════════
4
4
  //
5
- // Pure parsing and fixed texts. A command is the owner's own text — never a
6
- // forwarded or quoted message — that starts with one of COMMANDS
7
- // (case-insensitive). Commands run at once, busy or paused. Any other "/x" is
8
- // a prompt; "//x" sends the literal "/x". No command raises the level or
5
+ // Pure parsing and fixed texts. A command is the owner's own typed text —
6
+ // never a forwarded or quoted message, never a voice note — that starts with
7
+ // one of COMMANDS (case-insensitive). Commands run at once, busy or paused.
8
+ // Any other "/x" is a prompt; "//x" sends the literal "/x". No command raises the level or
9
9
  // changes a setting: those names get a fixed refusal (they would otherwise
10
10
  // reach the brain's CLI as its own slash commands).
11
11
 
@@ -21,7 +21,7 @@ const REFUSED = new Set([
21
21
 
22
22
  export const HELP_TEXT = [
23
23
  '*SynaBun on WhatsApp*',
24
- 'Write to me like you would in SynaBun. Pictures work too.',
24
+ 'Write to me like you would in SynaBun. Pictures work too, and voice notes when this computer can transcribe them.',
25
25
  '',
26
26
  '/status — what I am doing, spend and level',
27
27
  '/stop — stop the current task, its agents and anything waiting for your answer',
@@ -31,7 +31,7 @@ export const HELP_TEXT = [
31
31
  '/cards — ask again what is still waiting for your answer',
32
32
  '/help — this list',
33
33
  '',
34
- 'When I ask something, just answer in your own words. An approval takes a plain yes or no; any other message approves nothing and carries on the conversation. Start with // to send text that begins with a slash.',
34
+ 'When I ask something, just answer in your own words. An approval takes a plain yes or no, typed; any other message (a voice note too) approves nothing and carries on the conversation. Start with // to send text that begins with a slash.',
35
35
  'Levels, models and other settings change only in SynaBun on your computer (Settings → WhatsApp).',
36
36
  ].join('\n');
37
37
 
@@ -40,10 +40,10 @@ export const SETTINGS_REFUSAL = 'Settings change only in SynaBun on your compute
40
40
  /**
41
41
  * An inbound message → { kind: 'command', name, args } | { kind: 'refused', name }
42
42
  * | { kind: 'literal', text } ("//x" → "/x") | null (not a command: a prompt).
43
- * Only the owner's own text counts: forwarded or quoted messages are prompts.
43
+ * Only the owner's own typed text counts: forwarded or quoted messages and voice notes are prompts.
44
44
  */
45
45
  export function parseCommand(message) {
46
- if (!message || message.owner === false || message.forwarded || message.quoted) return null;
46
+ if (!message || message.owner === false || message.forwarded || message.quoted || message.voice === true) return null;
47
47
  const text = String(message.text ?? '').trim();
48
48
  if (!text.startsWith('/')) return null;
49
49
  if (text.startsWith('//')) return { kind: 'literal', text: text.slice(1) };
@@ -66,8 +66,8 @@ function levelLabel(level) {
66
66
 
67
67
  /**
68
68
  * /status text from a snapshot: { connected, sessionId, title, brain:{provider,model},
69
- * running, turnKind, queued, pendingCards, currentCard, budget:{totalUsd, hardUsd},
70
- * brainUsd, brainCapUsd, level, paused, pausedBy, autonomousUntil }.
69
+ * running, turnKind, queued, pendingCards, currentCard, budget:{ unlimited, totalUsd, amountUsd, leftUsd },
70
+ * brainUsd, level, paused, pausedBy }. Autonomous has no time limit: it holds until it is turned off in Settings.
71
71
  */
72
72
  export function statusText(s = {}) {
73
73
  const lines = ['*SynaBun status*'];
@@ -83,10 +83,9 @@ export function statusText(s = {}) {
83
83
  : 'idle';
84
84
  lines.push(`Now: ${doing}${s.queued ? ` · ${s.queued} message${s.queued === 1 ? '' : 's'} queued` : ''}`);
85
85
  if (s.pendingCards) lines.push(`Waiting for you: ${s.pendingCards} question${s.pendingCards === 1 ? '' : 's'} or approval${s.pendingCards === 1 ? '' : 's'}${s.currentCard ? ` (${s.currentCard})` : ''} — /cards asks again`);
86
- if (s.budget && Number.isFinite(Number(s.budget.hardUsd))) lines.push(`Spend: ${usd(s.budget.totalUsd)} of ${usd(s.budget.hardUsd)} session cap${Number.isFinite(Number(s.brainCapUsd)) ? ` (brain ${usd(s.brainUsd)} of ${usd(s.brainCapUsd)})` : ''}`);
87
- let level = `Level: ${levelLabel(s.level)}`;
88
- if (s.level === 'autonomous' && s.autonomousUntil) level += ` until ${new Date(Number(s.autonomousUntil)).toTimeString().slice(0, 5)}`;
89
- lines.push(level);
86
+ if (s.budget?.unlimited === true) lines.push(`Spend: ${usd(s.budget.totalUsd)} (no limit)`);
87
+ else if (s.budget && Number.isFinite(Number(s.budget.amountUsd))) lines.push(`Spend: ${usd(s.budget.totalUsd)} of the ${usd(s.budget.amountUsd)} budget${Number.isFinite(Number(s.brainUsd)) ? ` (brain ${usd(s.brainUsd)})` : ''}`);
88
+ lines.push(`Level: ${levelLabel(s.level)}${s.level === 'autonomous' ? ' until turned off' : ''}`);
90
89
  if (s.paused) lines.push(`Paused${s.pausedBy === 'desktop' ? ' from your computer' : ''} — ${s.pausedBy === 'desktop' ? 'resume it in SynaBun' : 'send /resume'}`);
91
90
  return lines.join('\n');
92
91
  }
@@ -10,12 +10,17 @@
10
10
  // Two kinds of fields:
11
11
  // user what Settings edits (update(), validated, versioned: a stale
12
12
  // expectedVersion is a 409 like lib/assistant-config.js)
13
- // service what SynaBun owns (mode, the conversation, the bound owner, the
14
- // autonomous window, pause) — written by the service and the
15
- // bridge through write(), refused in update().
13
+ // service what SynaBun owns (mode, the conversation, the bound owner,
14
+ // pause) — written by the service and the bridge through
15
+ // write(), refused in update().
16
16
  // Raising the level to 'autonomous' never applies here: update() needs
17
17
  // confirmEscalation and answers {pending:true}; the service applies it after
18
- // the phone answers "ALLOW <code>". Lowering applies at once.
18
+ // the phone answers "ALLOW <code>". Lowering applies at once. Autonomous then
19
+ // stays on until the owner lowers it in Settings, restarts included: there is
20
+ // no time limit. `autonomousUntil` is a legacy field (the end of the 8-hour
21
+ // window of 2.0.5 and before): a level 'autonomous' stored with one was turned
22
+ // on under that rule and is read as 'ask' (migrateLegacyAutonomous), so an
23
+ // update never puts anyone back into Autonomous.
19
24
  // `computerUse` is the owner's switch for controlling this Mac from a
20
25
  // WhatsApp conversation: off by default, a plain boolean, changed only through
21
26
  // update() (Settings on this computer: the guarded PUT /api/whatsapp/config).
@@ -25,9 +30,19 @@
25
30
  // Assistant) or { provider, model, effort }. Its shape is checked here; the
26
31
  // service checks the model and the effort against the Assistant's catalog
27
32
  // before it calls update() (lib/whatsapp/brain.js).
33
+ // Voice notes (lib/whatsapp/transcription.js): `transcriptionEnabled`, the
34
+ // spoken `transcriptionLanguage` ('auto' or a 2-3 letter code) and three
35
+ // paths, '' meaning "find it by itself": the whisper-cli program, the ggml
36
+ // model and FFmpeg. A path is the absolute path of an existing regular file
37
+ // (a program executable), checked when update() stores it and again by the
38
+ // host right before it runs; it is never looked up on PATH. Only update()
39
+ // (Settings on this computer) sets a path: a service write may only clear one,
40
+ // so nothing but the person at this computer chooses what the host runs.
28
41
 
29
42
  import { LEVELS } from '../remote-policy.js';
30
43
  import { cleanBrainChoice } from './brain.js';
44
+ import { TRANSCRIPTION_LANGUAGE_RE } from './protocol.js';
45
+ import { checkToolPath } from './transcription.js';
31
46
 
32
47
  export const WHATSAPP_CONFIG_KEY = 'whatsapp_config';
33
48
 
@@ -44,6 +59,11 @@ export const USER_DEFAULTS = Object.freeze({
44
59
  strictWorkerApprovals: false,
45
60
  computerUse: false,
46
61
  brain: null,
62
+ transcriptionEnabled: true,
63
+ transcriptionLanguage: 'auto',
64
+ transcriptionEnginePath: '',
65
+ transcriptionModelPath: '',
66
+ transcriptionFfmpegPath: '',
47
67
  });
48
68
 
49
69
  export const SERVICE_DEFAULTS = Object.freeze({
@@ -51,7 +71,7 @@ export const SERVICE_DEFAULTS = Object.freeze({
51
71
  sessionId: null,
52
72
  previousSessionIds: Object.freeze([]),
53
73
  owner: null, // { masked, boundAt, via } — never a number
54
- autonomousUntil: null, // epoch ms while an autonomous window is open
74
+ autonomousUntil: null, // legacy: the end of 2.0.5's 8-hour Autonomous window; always null now (migrateLegacyAutonomous)
55
75
  paused: false,
56
76
  pausedBy: null, // 'desktop' | 'phone'
57
77
  version: 0,
@@ -68,6 +88,11 @@ export const ROTATIONS = Object.freeze(['idle6h', 'daily', 'never']);
68
88
  export const SELF_TRIGGERS = Object.freeze(['all', 'prefix']);
69
89
  export const MODES = Object.freeze(['self', 'dedicated']);
70
90
  export const REPLY_LABEL_MAX = 24;
91
+ /** The voice-note settings the host gets (service prefsOf → protocol.js PREFS_SPEC). */
92
+ export const TRANSCRIPTION_FIELDS = Object.freeze(['transcriptionEnabled', 'transcriptionLanguage', 'transcriptionEnginePath', 'transcriptionModelPath', 'transcriptionFfmpegPath']);
93
+ /** The path fields and what each must name (transcription.js checkToolPath). */
94
+ export const TOOL_PATH_FIELDS = Object.freeze({ transcriptionEnginePath: 'program', transcriptionModelPath: 'file', transcriptionFfmpegPath: 'program' });
95
+ const TOOL_PATH_MAX = 4096;
71
96
 
72
97
  const RANK = Object.freeze({ 'read-only': 0, ask: 1, autonomous: 2 });
73
98
 
@@ -81,6 +106,28 @@ function configError(field, message, { code = 'CONFIG_INVALID', status = 400 } =
81
106
  return error;
82
107
  }
83
108
 
109
+ /** A tool path's shape: '' (find it by itself) or an absolute path of one line; undefined when it has neither shape. */
110
+ function cleanToolPath(value) {
111
+ if (value === null || value === undefined) return '';
112
+ if (typeof value !== 'string') return undefined;
113
+ const text = value.trim();
114
+ if (!text) return '';
115
+ if (text.length > TOOL_PATH_MAX || /[\u0000-\u001f\u007f]/.test(text)) return undefined;
116
+ return checkToolPath(text, 'program', { fs: SHAPE_ONLY_FS }) === 'not_absolute' ? undefined : text;
117
+ }
118
+ // checkToolPath against nothing on disk: only its shape rules run (absolute, not a network path).
119
+ const SHAPE_ONLY_FS = Object.freeze({ statSync: () => { throw new Error('shape only'); }, accessSync: () => {}, constants: {} });
120
+
121
+ const TOOL_PATH_PROBLEMS = Object.freeze({
122
+ not_absolute: 'must be an absolute path (it is never looked up on PATH)',
123
+ network: 'must be a path on this computer, not on a network share',
124
+ missing: 'names a file that does not exist',
125
+ not_file: 'must name a regular file',
126
+ not_executable: process.platform === 'win32' ? 'must name a program (.exe)' : 'names a file that is not executable (chmod +x it)',
127
+ unreadable: 'names a file this account cannot read',
128
+ empty: 'names an empty file',
129
+ });
130
+
84
131
  /** A reply label: printable, one line, 1-24 characters; '' → null (the mode's default label). */
85
132
  function cleanLabel(value) {
86
133
  if (value === null || value === undefined) return null;
@@ -102,6 +149,8 @@ const CHECKS = {
102
149
  activityText: (v) => typeof v === 'boolean',
103
150
  strictWorkerApprovals: (v) => typeof v === 'boolean',
104
151
  computerUse: (v) => typeof v === 'boolean',
152
+ transcriptionEnabled: (v) => typeof v === 'boolean',
153
+ transcriptionLanguage: (v) => typeof v === 'string' && TRANSCRIPTION_LANGUAGE_RE.test(v),
105
154
  };
106
155
  const MESSAGES = {
107
156
  enabled: 'enabled must be true or false',
@@ -116,10 +165,21 @@ const MESSAGES = {
116
165
  strictWorkerApprovals: 'strictWorkerApprovals must be true or false',
117
166
  computerUse: 'computerUse must be true or false',
118
167
  brain: 'brain must be null (same as the Assistant) or { provider, model, effort } with a provider of claude-code, codex or opencode',
168
+ transcriptionEnabled: 'transcriptionEnabled must be true or false',
169
+ transcriptionLanguage: "transcriptionLanguage must be 'auto' or a 2-3 letter lowercase language code, such as en or pt",
170
+ transcriptionEnginePath: 'transcriptionEnginePath must be empty (find whisper-cli by itself) or the absolute path of the whisper-cli program',
171
+ transcriptionModelPath: 'transcriptionModelPath must be empty (ggml-small.bin in the transcription folder) or the absolute path of a whisper.cpp model file',
172
+ transcriptionFfmpegPath: 'transcriptionFfmpegPath must be empty (find ffmpeg by itself) or the absolute path of the ffmpeg program',
119
173
  };
120
174
 
121
- /** Validate a user patch (Settings fields only). → the cleaned patch; throws 400 CONFIG_INVALID {field}. */
122
- export function validateConfigPatch(patch = {}) {
175
+ /** Whether a tool path can be used on this computer: null, or why not (transcription.js checkToolPath). */
176
+ const defaultCheckPath = (value, kind) => checkToolPath(value, kind);
177
+
178
+ /**
179
+ * Validate a user patch (Settings fields only). → the cleaned patch; throws 400 CONFIG_INVALID {field}.
180
+ * `checkPath(value, kind)` → null or a problem: how a tool path is checked on disk (tests pass a fake).
181
+ */
182
+ export function validateConfigPatch(patch = {}, { checkPath = defaultCheckPath } = {}) {
123
183
  if (!isPlainObject(patch)) throw configError('config', 'config must be an object');
124
184
  const out = {};
125
185
  for (const [key, value] of Object.entries(patch)) {
@@ -138,6 +198,14 @@ export function validateConfigPatch(patch = {}) {
138
198
  out.brain = brain;
139
199
  continue;
140
200
  }
201
+ if (TOOL_PATH_FIELDS[key]) {
202
+ const toolPath = cleanToolPath(value);
203
+ if (toolPath === undefined) throw configError(key, MESSAGES[key]);
204
+ const problem = toolPath ? checkPath(toolPath, TOOL_PATH_FIELDS[key]) : null;
205
+ if (problem) throw configError(key, `${key} ${TOOL_PATH_PROBLEMS[problem] || 'names a file that cannot be used'}: ${toolPath}`);
206
+ out[key] = toolPath;
207
+ continue;
208
+ }
141
209
  if (!CHECKS[key](value)) throw configError(key, MESSAGES[key]);
142
210
  out[key] = value;
143
211
  }
@@ -157,13 +225,28 @@ function cleanOwner(value) {
157
225
  return { masked, boundAt: toMs(value.boundAt), via: value.via === 'claim' ? 'claim' : 'self_confirm' };
158
226
  }
159
227
 
228
+ /**
229
+ * A level 'autonomous' stored with an `autonomousUntil` (any value but null) was turned on under
230
+ * the 8-hour rule of 2.0.5 and before, whether that window is still open or not: it does not
231
+ * become Autonomous without a time limit. → the stored value as level 'ask' with no
232
+ * autonomousUntil, or null when `stored` is not such a value. Autonomous stored with
233
+ * autonomousUntil null is today's state and stays.
234
+ */
235
+ export function migrateLegacyAutonomous(stored) {
236
+ if (!isPlainObject(stored) || stored.level !== 'autonomous') return null;
237
+ if (stored.autonomousUntil === null || stored.autonomousUntil === undefined) return null;
238
+ return { ...stored, level: 'ask', autonomousUntil: null };
239
+ }
240
+
160
241
  /** Defaults + stored values; a bad stored value falls back to its default, field by field. */
161
242
  export function effectiveConfig(stored = {}) {
162
- const raw = isPlainObject(stored) ? stored : {};
243
+ const raw = isPlainObject(stored) ? migrateLegacyAutonomous(stored) || stored : {};
163
244
  const out = {};
164
245
  for (const key of USER_FIELDS) {
165
246
  if (key === 'replyLabel') { const label = cleanLabel(raw.replyLabel); out.replyLabel = label === undefined ? null : label; continue; }
166
247
  if (key === 'brain') { const brain = cleanBrainChoice(raw.brain); out.brain = brain === undefined ? null : brain; continue; }
248
+ // A stored path keeps its shape only: whether the file still works is the host's check, right before it runs.
249
+ if (TOOL_PATH_FIELDS[key]) { const toolPath = cleanToolPath(raw[key]); out[key] = toolPath === undefined ? '' : toolPath; continue; }
167
250
  out[key] = raw[key] !== undefined && CHECKS[key](raw[key]) ? raw[key] : USER_DEFAULTS[key];
168
251
  }
169
252
  out.mode = MODES.includes(raw.mode) ? raw.mode : SERVICE_DEFAULTS.mode;
@@ -172,7 +255,7 @@ export function effectiveConfig(stored = {}) {
172
255
  ? raw.previousSessionIds.map((id) => (typeof id === 'string' ? id : id?.id)).filter((id) => typeof id === 'string' && id).slice(0, 10)
173
256
  : [];
174
257
  out.owner = cleanOwner(raw.owner);
175
- out.autonomousUntil = toMs(raw.autonomousUntil);
258
+ out.autonomousUntil = null; // legacy: Autonomous has no time limit (migrateLegacyAutonomous)
176
259
  out.paused = raw.paused === true;
177
260
  out.pausedBy = out.paused && (raw.pausedBy === 'desktop' || raw.pausedBy === 'phone') ? raw.pausedBy : null;
178
261
  out.version = Number.isSafeInteger(raw.version) && raw.version >= 0 ? raw.version : 0;
@@ -181,18 +264,23 @@ export function effectiveConfig(stored = {}) {
181
264
  return out;
182
265
  }
183
266
 
184
- /** 'autonomous' only while its window is open; an expired window reads as 'ask'. */
185
- export function currentLevel(config, now = Date.now()) {
186
- if (config?.level !== 'autonomous') return LEVELS.includes(config?.level) ? config.level : 'ask';
187
- return toMs(config.autonomousUntil) > now ? 'autonomous' : 'ask';
267
+ /**
268
+ * The configured level ('ask' when it is not a known one). Autonomous has no time limit: it
269
+ * holds until the owner lowers it. A legacy 8-hour Autonomous (migrateLegacyAutonomous) is 'ask'.
270
+ */
271
+ export function currentLevel(config) {
272
+ if (migrateLegacyAutonomous(config)) return 'ask';
273
+ return LEVELS.includes(config?.level) ? config.level : 'ask';
188
274
  }
189
275
 
190
276
  /**
191
277
  * @param {object} deps
192
278
  * @param {(key:string)=>string|null} deps.get getKvConfig
193
279
  * @param {(key:string, value:string)=>void} deps.set setKvConfig
280
+ * @param {() => void} [deps.onLegacyAutonomous] a legacy 8-hour Autonomous was stored as Ask (once, after it was saved)
281
+ * @param {(value:string, kind:'program'|'file') => string|null} [deps.checkPath] how update() checks a tool path on disk
194
282
  */
195
- export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.now, log = () => {} } = {}) {
283
+ export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.now, log = () => {}, onLegacyAutonomous = null, checkPath = defaultCheckPath } = {}) {
196
284
  let cache = null; // { at, value }
197
285
 
198
286
  function readStored() {
@@ -202,12 +290,34 @@ export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.n
202
290
  if (!raw) return {};
203
291
  try {
204
292
  const value = JSON.parse(raw);
205
- return isPlainObject(value) ? value : {};
293
+ return isPlainObject(value) ? migrateStored(value) : {};
206
294
  } catch (error) {
207
295
  log('whatsapp:config-corrupt', error?.message || String(error));
208
296
  return {};
209
297
  }
210
298
  }
299
+ /**
300
+ * A legacy 8-hour Autonomous (migrateLegacyAutonomous) found by any read is stored as Ask at
301
+ * once, with the version bumped (an open Settings tab reloads before it saves), and reported
302
+ * once. A store that cannot be written still reads it as Ask, and tries again at the next read.
303
+ */
304
+ function migrateStored(value) {
305
+ const migrated = migrateLegacyAutonomous(value);
306
+ if (!migrated) return value;
307
+ const version = Number.isSafeInteger(value.version) && value.version >= 0 ? value.version : 0;
308
+ const next = { ...migrated, version: version + 1 };
309
+ try {
310
+ if (typeof set !== 'function') return next;
311
+ set(WHATSAPP_CONFIG_KEY, JSON.stringify(next));
312
+ } catch (error) {
313
+ log('whatsapp:config-unwritable', `the earlier Autonomous could not be stored as Ask: ${error?.message || String(error)}`);
314
+ return next;
315
+ }
316
+ cache = null;
317
+ log('whatsapp:config-migrated', 'Autonomous from an earlier version (8-hour window) ended; the level is Ask');
318
+ try { onLegacyAutonomous?.(); } catch {}
319
+ return next;
320
+ }
211
321
  function persist(value) {
212
322
  if (typeof set === 'function') set(WHATSAPP_CONFIG_KEY, JSON.stringify(value));
213
323
  cache = null;
@@ -221,8 +331,8 @@ export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.n
221
331
 
222
332
  /**
223
333
  * Service-owned write: service fields, internal state and user fields the
224
- * service decides (the confirmed autonomous level, its expiry). A change to
225
- * a user field bumps the version so an open Settings tab reloads first.
334
+ * service decides (the confirmed autonomous level). A change to a user field
335
+ * bumps the version so an open Settings tab reloads first.
226
336
  */
227
337
  function write(patch = {}) {
228
338
  if (!isPlainObject(patch)) return read();
@@ -239,6 +349,10 @@ export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.n
239
349
  if (key === 'version') continue;
240
350
  // Computer use is turned on by the person at this computer only (update()); the service may only turn it off.
241
351
  if (key === 'computerUse' && value !== false) { log('whatsapp:config-ignored', 'computerUse is turned on in Settings only'); continue; }
352
+ // What the host runs (whisper-cli, FFmpeg) and the model it loads are chosen in Settings only; a service write may clear them.
353
+ if (TOOL_PATH_FIELDS[key] && value !== '' && value !== null) { log('whatsapp:config-ignored', `${key} is set in Settings only`); continue; }
354
+ // A legacy field: Autonomous has no time limit, so nothing writes an expiry (only null, which clears it).
355
+ if (key === 'autonomousUntil' && value !== null) { log('whatsapp:config-ignored', 'autonomousUntil is a legacy field: Autonomous has no time limit'); continue; }
242
356
  next[key] = value;
243
357
  if (USER_FIELDS.includes(key) && JSON.stringify(effectiveConfig(next)[key]) !== JSON.stringify(before[key])) userChanged = true;
244
358
  }
@@ -253,7 +367,7 @@ export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.n
253
367
  * level waits for the phone (the service holds the escalation).
254
368
  */
255
369
  function update(patch = {}, { expectedVersion = null, confirmEscalation = false } = {}) {
256
- const clean = validateConfigPatch(patch);
370
+ const clean = validateConfigPatch(patch, { checkPath });
257
371
  const stored = readStored();
258
372
  const current = effectiveConfig(stored);
259
373
  if (expectedVersion !== null && expectedVersion !== undefined && Number(expectedVersion) !== current.version) {
@@ -263,7 +377,7 @@ export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.n
263
377
  }
264
378
  let pending = false;
265
379
  if (clean.level !== undefined) {
266
- const effective = currentLevel(current, now());
380
+ const effective = currentLevel(current);
267
381
  if (clean.level === 'autonomous') {
268
382
  if (effective !== 'autonomous') {
269
383
  if (confirmEscalation !== true) throw configError('level', 'Autonomous needs a second confirmation on this computer, then an ALLOW code from your phone.', { code: 'CONFIRM_REQUIRED' });
@@ -271,7 +385,7 @@ export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.n
271
385
  }
272
386
  delete clean.level;
273
387
  } else if (RANK[clean.level] < RANK[effective] || clean.level !== current.level) {
274
- // Lowering (or leaving an expired autonomous) applies at once and closes the window.
388
+ // Lowering applies at once (and clears the legacy autonomousUntil).
275
389
  clean.autonomousUntil = null;
276
390
  }
277
391
  }
@@ -1,14 +1,16 @@
1
1
  // Fake Baileys runtime for tests and SYNABUN_WHATSAPP_FAKE=1. Exports the same
2
2
  // names as connector/entry.mjs, with a socket that implements exactly the
3
3
  // surface baileys-adapter.js and host-core.js use, plus `fake`: a controller
4
- // that plays the phone side (scan, inbound from the owner or a stranger, text
5
- // or image, disconnect by reason name, forbidden, crash) and records every
6
- // outgoing call so tests can assert who the host talked to.
4
+ // that plays the phone side (scan, inbound from the owner or a stranger, text,
5
+ // image or voice note, disconnect by reason name, forbidden, crash), closes the socket
6
+ // with the errors Baileys 7.0.0-rc14 builds (a stream error, a WebSocket
7
+ // error, a connection failure), can make socket construction throw, and
8
+ // records every outgoing call so tests can assert who the host talked to.
7
9
  //
8
10
  // Module state is per process: call fake.reset() between tests.
9
11
 
10
12
  import { EventEmitter } from 'node:events';
11
- import { randomBytes } from 'node:crypto';
13
+ import { createHash, randomBytes } from 'node:crypto';
12
14
  import { FALLBACK_CODEC } from './auth-store.js';
13
15
 
14
16
  export const BAILEYS_VERSION = 'fake';
@@ -102,13 +104,17 @@ export function getContentType(content) {
102
104
  }
103
105
 
104
106
  export async function downloadMediaMessage(message, _type, _options, ctx) {
105
- const img = normalizeMessageContent(message?.message)?.imageMessage;
106
- if (!img) throw new Error('not a media message');
107
- if (img.fakeFail) {
107
+ const content = normalizeMessageContent(message?.message);
108
+ const media = content?.imageMessage || content?.audioMessage;
109
+ if (!media) throw new Error('not a media message');
110
+ state.downloads += 1;
111
+ // A download that never answers (the adapter's 30 s timeout).
112
+ if (media.fakeHang) return new Promise(() => {});
113
+ if (media.fakeFail) {
108
114
  if (ctx?.reuploadRequest) await ctx.reuploadRequest(message);
109
115
  throw Object.assign(new Error('fake download failed'), { status: 404 });
110
116
  }
111
- return Buffer.from(img.fakeData || []);
117
+ return Buffer.from(media.fakeData || []);
112
118
  }
113
119
 
114
120
  export async function fetchLatestBaileysVersion() {
@@ -137,6 +143,9 @@ const state = {
137
143
  ignoredByJid: 0,
138
144
  latestVersion: [2, 3000, 1027934701],
139
145
  failNextSend: false,
146
+ failSockets: 0, // makeWASocket throws this many more times
147
+ socketFailures: 0,
148
+ downloads: 0, // downloadMediaMessage calls (a refused voice note is never downloaded)
140
149
  seq: 0,
141
150
  account: { pn: '15550001111', lid: '99887766554433', name: 'Fake Account' },
142
151
  people: {
@@ -145,10 +154,11 @@ const state = {
145
154
  },
146
155
  };
147
156
 
148
- function boom(statusCode, message = 'fake disconnect') {
157
+ function boom(statusCode, message = 'fake disconnect', data = undefined) {
149
158
  const err = new Error(message);
150
159
  err.isBoom = true;
151
160
  err.output = { statusCode, payload: { statusCode, message } };
161
+ if (data !== undefined) err.data = data;
152
162
  return err;
153
163
  }
154
164
 
@@ -199,16 +209,32 @@ function makeSocket(config) {
199
209
  throw new Error('fake send failure');
200
210
  }
201
211
  const id = `FAKEOUT${++state.seq}`;
212
+ // A file (whatsapp_send_file): what Baileys would upload, recorded by size and hash, never the bytes.
213
+ const media = ['document', 'image', 'video', 'audio'].find((key) => content && Buffer.isBuffer(content[key])) || null;
202
214
  state.sent.push({
203
215
  jid,
204
- kind: content?.react ? 'react' : content?.text !== undefined ? 'text' : 'other',
216
+ kind: content?.react ? 'react' : content?.text !== undefined ? 'text' : media || 'other',
205
217
  text: content?.text,
206
218
  react: content?.react ? { text: content.react.text, keyId: content.react.key?.id, keyJid: content.react.key?.remoteJid } : undefined,
207
219
  linkPreview: content?.linkPreview,
208
220
  quotedId: options?.quoted?.key?.id ?? null,
221
+ ...(media ? {
222
+ file: {
223
+ bytes: content[media].length,
224
+ sha256: createHash('sha256').update(content[media]).digest('hex'),
225
+ mimetype: content.mimetype ?? null,
226
+ fileName: content.fileName ?? null,
227
+ caption: content.caption ?? null,
228
+ ptt: content.ptt ?? null,
229
+ keys: Object.keys(content).sort(),
230
+ jpegThumbnail: Object.hasOwn(content, 'jpegThumbnail') ? content.jpegThumbnail : 'absent',
231
+ },
232
+ } : {}),
209
233
  at: Date.now(),
210
234
  });
211
- const message = content?.react ? { reactionMessage: content.react } : { extendedTextMessage: { text: content?.text ?? '' } };
235
+ const message = content?.react ? { reactionMessage: content.react }
236
+ : media ? { [`${media}Message`]: { mimetype: content.mimetype ?? null, fileLength: content[media].length, ...(content.fileName ? { fileName: content.fileName } : {}), ...(content.caption ? { caption: content.caption } : {}) } }
237
+ : { extendedTextMessage: { text: content?.text ?? '' } };
212
238
  return { key: { remoteJid: jid, fromMe: true, id }, message, messageTimestamp: Math.floor(Date.now() / 1000) };
213
239
  },
214
240
  requestPairingCode: async (phone) => {
@@ -263,6 +289,11 @@ function makeSocket(config) {
263
289
  }
264
290
 
265
291
  export function makeWASocket(config) {
292
+ if (state.failSockets > 0) {
293
+ state.failSockets -= 1;
294
+ state.socketFailures += 1;
295
+ throw new Error('fake socket construction failure');
296
+ }
266
297
  const sock = makeSocket(config);
267
298
  state.sockets.push(sock);
268
299
  return sock;
@@ -274,13 +305,32 @@ function current() {
274
305
  return sock;
275
306
  }
276
307
 
277
- function buildContent({ text, image, forwarded, quoted }) {
308
+ function buildContent({ text, image, audio, forwarded, quoted }) {
278
309
  const contextInfo = forwarded || quoted
279
310
  ? {
280
311
  ...(forwarded ? { isForwarded: true, forwardingScore: 1 } : {}),
281
312
  ...(quoted ? { stanzaId: quoted.id, quotedMessage: { conversation: quoted.text ?? '' } } : {}),
282
313
  }
283
314
  : undefined;
315
+ if (audio) {
316
+ // A voice note (ptt, OGG/Opus) or an audio file; `seconds` / `fileLength` are what the message declares
317
+ // (null leaves it out). A caption is not something WhatsApp offers on audio: `text` plays one anyway.
318
+ const data = Buffer.isBuffer(audio.data) ? audio.data : Buffer.from(audio.base64 || '', 'base64');
319
+ return {
320
+ audioMessage: {
321
+ mimetype: audio.mimetype || 'audio/ogg; codecs=opus',
322
+ ptt: audio.ptt !== false,
323
+ ...(audio.seconds === null ? {} : { seconds: audio.seconds ?? 3 }),
324
+ ...(audio.fileLength === null ? {} : { fileLength: audio.fileLength ?? data.length }),
325
+ ...(text ? { caption: text } : {}),
326
+ url: 'https://fake.invalid/media',
327
+ fakeData: data,
328
+ fakeFail: !!audio.fail,
329
+ fakeHang: !!audio.hang,
330
+ ...(contextInfo ? { contextInfo } : {}),
331
+ },
332
+ };
333
+ }
284
334
  if (image) {
285
335
  const data = Buffer.isBuffer(image.data) ? image.data : Buffer.from(image.base64 || '', 'base64');
286
336
  return {
@@ -315,6 +365,9 @@ export const fake = {
315
365
  state.versionFetches = 0;
316
366
  state.ignoredByJid = 0;
317
367
  state.failNextSend = false;
368
+ state.failSockets = 0;
369
+ state.socketFailures = 0;
370
+ state.downloads = 0;
318
371
  state.latestVersion = [2, 3000, 1027934701];
319
372
  state.seq = 0;
320
373
  state.account = { pn: '15550001111', lid: '99887766554433', name: 'Fake Account' };
@@ -346,7 +399,7 @@ export const fake = {
346
399
  * (epoch ms, default now) is when the phone sent it.
347
400
  */
348
401
  inbound({
349
- from = 'self', text, image = null, device = 0, type = 'notify', addressing = 'pn', id, forwarded = false, quoted = null,
402
+ from = 'self', text, image = null, audio = null, device = 0, type = 'notify', addressing = 'pn', id, forwarded = false, quoted = null,
350
403
  requestId, noStanza = false, stanzaFrom, content, stub, at = null, offline = null,
351
404
  } = {}) {
352
405
  const sock = current();
@@ -364,7 +417,7 @@ export const fake = {
364
417
  key: { remoteJid: chatJid, remoteJidAlt: fromMe ? undefined : chatAlt, fromMe, id: msgId, addressingMode: lid ? 'lid' : 'pn' },
365
418
  messageTimestamp: ts,
366
419
  pushName: 'fake',
367
- message: stub ? undefined : content || buildContent({ text, image, forwarded, quoted }),
420
+ message: stub ? undefined : content || buildContent({ text, image, audio, forwarded, quoted }),
368
421
  ...(stub ? { messageStubType: 1 } : {}),
369
422
  };
370
423
  const stanzaSender = stanzaFrom || senderJid;
@@ -395,6 +448,32 @@ export const fake = {
395
448
  forbidden() {
396
449
  return fake.disconnect('forbidden');
397
450
  },
451
+ /**
452
+ * A <stream:error> the way Baileys rc14 closes on it (getErrorCodeFromStreamError + the
453
+ * CB:stream:error handler): "Stream Errored (<first child tag>)", the code from the node's
454
+ * own `code` attribute, else 440 for <conflict>, else 500; the child node in `data`.
455
+ */
456
+ streamError({ tag = 'unknown', code = null } = {}) {
457
+ const statusCode = Number(code) || (tag === 'conflict' ? DisconnectReason.connectionReplaced : DisconnectReason.badSession);
458
+ const reason = statusCode === DisconnectReason.restartRequired ? 'restart required' : tag;
459
+ current().end(boom(statusCode, `Stream Errored (${reason})`, { tag, attrs: code ? { code: String(code) } : {} }));
460
+ return { ok: true };
461
+ },
462
+ /** A WebSocket error the way mapWebSocketError wraps it: 500 unless getCodeFromWSError places it. */
463
+ wsError({ message = 'socket hang up', code = 500 } = {}) {
464
+ current().end(boom(Number(code) || 500, `WebSocket Error (${message})`, { message }));
465
+ return { ok: true };
466
+ },
467
+ /** A <failure> node (CB:failure): "Connection Failure", its `reason` attribute or 500. */
468
+ connectionFailure({ reason = null } = {}) {
469
+ current().end(boom(Number(reason) || 500, 'Connection Failure', reason ? { reason: String(reason) } : {}));
470
+ return { ok: true };
471
+ },
472
+ /** The next `n` makeWASocket calls throw (a socket that cannot even be built). */
473
+ failNextSocket(n = 1) {
474
+ state.failSockets = Math.max(0, Number(n) || 0);
475
+ return { ok: true };
476
+ },
398
477
  crash() {
399
478
  setImmediate(() => { throw new Error('fake runtime crash'); });
400
479
  return { ok: true };
@@ -412,6 +491,8 @@ export const fake = {
412
491
  reads: state.reads.map((r) => ({ ...r })),
413
492
  pairingRequests: [...state.pairingRequests],
414
493
  logouts: state.logouts,
494
+ socketFailures: state.socketFailures,
495
+ downloads: state.downloads,
415
496
  versionFetches: state.versionFetches,
416
497
  ignoredByJid: state.ignoredByJid,
417
498
  account: { ...state.account },
@@ -427,6 +508,10 @@ export const fake = {
427
508
  inbound: () => fake.inbound(args),
428
509
  disconnect: () => fake.disconnect(args.reason),
429
510
  forbidden: () => fake.forbidden(),
511
+ streamError: () => fake.streamError(args),
512
+ wsError: () => fake.wsError(args),
513
+ connectionFailure: () => fake.connectionFailure(args),
514
+ failNextSocket: () => fake.failNextSocket(args.n ?? 1),
430
515
  crash: () => fake.crash(),
431
516
  failNextSend: () => fake.failNextSend(),
432
517
  snapshot: () => fake.snapshot(),