cowork-os 0.3.21

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 (526) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1638 -0
  3. package/bin/cowork.js +42 -0
  4. package/build/entitlements.mac.plist +16 -0
  5. package/build/icon.icns +0 -0
  6. package/build/icon.png +0 -0
  7. package/dist/electron/electron/activity/ActivityRepository.js +190 -0
  8. package/dist/electron/electron/agent/browser/browser-service.js +639 -0
  9. package/dist/electron/electron/agent/context-manager.js +225 -0
  10. package/dist/electron/electron/agent/custom-skill-loader.js +566 -0
  11. package/dist/electron/electron/agent/daemon.js +975 -0
  12. package/dist/electron/electron/agent/executor.js +3561 -0
  13. package/dist/electron/electron/agent/llm/anthropic-provider.js +155 -0
  14. package/dist/electron/electron/agent/llm/bedrock-provider.js +202 -0
  15. package/dist/electron/electron/agent/llm/gemini-provider.js +375 -0
  16. package/dist/electron/electron/agent/llm/index.js +34 -0
  17. package/dist/electron/electron/agent/llm/ollama-provider.js +263 -0
  18. package/dist/electron/electron/agent/llm/openai-oauth.js +101 -0
  19. package/dist/electron/electron/agent/llm/openai-provider.js +657 -0
  20. package/dist/electron/electron/agent/llm/openrouter-provider.js +232 -0
  21. package/dist/electron/electron/agent/llm/pricing.js +160 -0
  22. package/dist/electron/electron/agent/llm/provider-factory.js +880 -0
  23. package/dist/electron/electron/agent/llm/types.js +178 -0
  24. package/dist/electron/electron/agent/queue-manager.js +378 -0
  25. package/dist/electron/electron/agent/sandbox/docker-sandbox.js +402 -0
  26. package/dist/electron/electron/agent/sandbox/macos-sandbox.js +407 -0
  27. package/dist/electron/electron/agent/sandbox/runner.js +410 -0
  28. package/dist/electron/electron/agent/sandbox/sandbox-factory.js +228 -0
  29. package/dist/electron/electron/agent/sandbox/security-utils.js +258 -0
  30. package/dist/electron/electron/agent/search/brave-provider.js +119 -0
  31. package/dist/electron/electron/agent/search/google-provider.js +100 -0
  32. package/dist/electron/electron/agent/search/index.js +28 -0
  33. package/dist/electron/electron/agent/search/provider-factory.js +395 -0
  34. package/dist/electron/electron/agent/search/serpapi-provider.js +112 -0
  35. package/dist/electron/electron/agent/search/tavily-provider.js +90 -0
  36. package/dist/electron/electron/agent/search/types.js +40 -0
  37. package/dist/electron/electron/agent/security/index.js +12 -0
  38. package/dist/electron/electron/agent/security/input-sanitizer.js +303 -0
  39. package/dist/electron/electron/agent/security/output-filter.js +217 -0
  40. package/dist/electron/electron/agent/skill-eligibility.js +281 -0
  41. package/dist/electron/electron/agent/skill-registry.js +396 -0
  42. package/dist/electron/electron/agent/skills/document.js +878 -0
  43. package/dist/electron/electron/agent/skills/image-generator.js +225 -0
  44. package/dist/electron/electron/agent/skills/organizer.js +141 -0
  45. package/dist/electron/electron/agent/skills/presentation.js +367 -0
  46. package/dist/electron/electron/agent/skills/spreadsheet.js +165 -0
  47. package/dist/electron/electron/agent/tools/browser-tools.js +523 -0
  48. package/dist/electron/electron/agent/tools/builtin-settings.js +384 -0
  49. package/dist/electron/electron/agent/tools/canvas-tools.js +530 -0
  50. package/dist/electron/electron/agent/tools/cron-tools.js +577 -0
  51. package/dist/electron/electron/agent/tools/edit-tools.js +194 -0
  52. package/dist/electron/electron/agent/tools/file-tools.js +719 -0
  53. package/dist/electron/electron/agent/tools/glob-tools.js +283 -0
  54. package/dist/electron/electron/agent/tools/grep-tools.js +387 -0
  55. package/dist/electron/electron/agent/tools/image-tools.js +111 -0
  56. package/dist/electron/electron/agent/tools/mention-tools.js +282 -0
  57. package/dist/electron/electron/agent/tools/node-tools.js +476 -0
  58. package/dist/electron/electron/agent/tools/registry.js +2719 -0
  59. package/dist/electron/electron/agent/tools/search-tools.js +91 -0
  60. package/dist/electron/electron/agent/tools/shell-tools.js +574 -0
  61. package/dist/electron/electron/agent/tools/skill-tools.js +274 -0
  62. package/dist/electron/electron/agent/tools/system-tools.js +578 -0
  63. package/dist/electron/electron/agent/tools/web-fetch-tools.js +444 -0
  64. package/dist/electron/electron/agent/tools/x-tools.js +264 -0
  65. package/dist/electron/electron/agents/AgentRoleRepository.js +420 -0
  66. package/dist/electron/electron/agents/HeartbeatService.js +356 -0
  67. package/dist/electron/electron/agents/MentionRepository.js +197 -0
  68. package/dist/electron/electron/agents/TaskSubscriptionRepository.js +168 -0
  69. package/dist/electron/electron/agents/WorkingStateRepository.js +229 -0
  70. package/dist/electron/electron/canvas/canvas-manager.js +714 -0
  71. package/dist/electron/electron/canvas/canvas-preload.js +53 -0
  72. package/dist/electron/electron/canvas/canvas-protocol.js +195 -0
  73. package/dist/electron/electron/canvas/canvas-store.js +174 -0
  74. package/dist/electron/electron/canvas/index.js +13 -0
  75. package/dist/electron/electron/control-plane/client.js +364 -0
  76. package/dist/electron/electron/control-plane/handlers.js +572 -0
  77. package/dist/electron/electron/control-plane/index.js +41 -0
  78. package/dist/electron/electron/control-plane/node-manager.js +264 -0
  79. package/dist/electron/electron/control-plane/protocol.js +194 -0
  80. package/dist/electron/electron/control-plane/remote-client.js +437 -0
  81. package/dist/electron/electron/control-plane/server.js +640 -0
  82. package/dist/electron/electron/control-plane/settings.js +369 -0
  83. package/dist/electron/electron/control-plane/ssh-tunnel.js +549 -0
  84. package/dist/electron/electron/cron/index.js +30 -0
  85. package/dist/electron/electron/cron/schedule.js +190 -0
  86. package/dist/electron/electron/cron/service.js +614 -0
  87. package/dist/electron/electron/cron/store.js +155 -0
  88. package/dist/electron/electron/cron/types.js +82 -0
  89. package/dist/electron/electron/cron/webhook.js +258 -0
  90. package/dist/electron/electron/database/SecureSettingsRepository.js +444 -0
  91. package/dist/electron/electron/database/TaskLabelRepository.js +120 -0
  92. package/dist/electron/electron/database/repositories.js +1781 -0
  93. package/dist/electron/electron/database/schema.js +978 -0
  94. package/dist/electron/electron/extensions/index.js +33 -0
  95. package/dist/electron/electron/extensions/loader.js +313 -0
  96. package/dist/electron/electron/extensions/registry.js +485 -0
  97. package/dist/electron/electron/extensions/types.js +11 -0
  98. package/dist/electron/electron/gateway/channel-registry.js +1102 -0
  99. package/dist/electron/electron/gateway/channels/bluebubbles-client.js +479 -0
  100. package/dist/electron/electron/gateway/channels/bluebubbles.js +432 -0
  101. package/dist/electron/electron/gateway/channels/discord.js +975 -0
  102. package/dist/electron/electron/gateway/channels/email-client.js +593 -0
  103. package/dist/electron/electron/gateway/channels/email.js +443 -0
  104. package/dist/electron/electron/gateway/channels/google-chat.js +631 -0
  105. package/dist/electron/electron/gateway/channels/imessage-client.js +363 -0
  106. package/dist/electron/electron/gateway/channels/imessage.js +465 -0
  107. package/dist/electron/electron/gateway/channels/index.js +36 -0
  108. package/dist/electron/electron/gateway/channels/line-client.js +470 -0
  109. package/dist/electron/electron/gateway/channels/line.js +479 -0
  110. package/dist/electron/electron/gateway/channels/matrix-client.js +432 -0
  111. package/dist/electron/electron/gateway/channels/matrix.js +592 -0
  112. package/dist/electron/electron/gateway/channels/mattermost-client.js +394 -0
  113. package/dist/electron/electron/gateway/channels/mattermost.js +496 -0
  114. package/dist/electron/electron/gateway/channels/signal-client.js +500 -0
  115. package/dist/electron/electron/gateway/channels/signal.js +582 -0
  116. package/dist/electron/electron/gateway/channels/slack.js +415 -0
  117. package/dist/electron/electron/gateway/channels/teams.js +596 -0
  118. package/dist/electron/electron/gateway/channels/telegram.js +1390 -0
  119. package/dist/electron/electron/gateway/channels/twitch-client.js +502 -0
  120. package/dist/electron/electron/gateway/channels/twitch.js +396 -0
  121. package/dist/electron/electron/gateway/channels/types.js +8 -0
  122. package/dist/electron/electron/gateway/channels/whatsapp.js +953 -0
  123. package/dist/electron/electron/gateway/context-policy.js +268 -0
  124. package/dist/electron/electron/gateway/index.js +1063 -0
  125. package/dist/electron/electron/gateway/infrastructure.js +496 -0
  126. package/dist/electron/electron/gateway/router.js +2700 -0
  127. package/dist/electron/electron/gateway/security.js +375 -0
  128. package/dist/electron/electron/gateway/session.js +115 -0
  129. package/dist/electron/electron/gateway/tunnel.js +503 -0
  130. package/dist/electron/electron/guardrails/guardrail-manager.js +348 -0
  131. package/dist/electron/electron/hooks/gmail-watcher.js +300 -0
  132. package/dist/electron/electron/hooks/index.js +46 -0
  133. package/dist/electron/electron/hooks/mappings.js +381 -0
  134. package/dist/electron/electron/hooks/server.js +480 -0
  135. package/dist/electron/electron/hooks/settings.js +447 -0
  136. package/dist/electron/electron/hooks/types.js +41 -0
  137. package/dist/electron/electron/ipc/canvas-handlers.js +158 -0
  138. package/dist/electron/electron/ipc/handlers.js +3138 -0
  139. package/dist/electron/electron/ipc/mission-control-handlers.js +141 -0
  140. package/dist/electron/electron/main.js +448 -0
  141. package/dist/electron/electron/mcp/client/MCPClientManager.js +330 -0
  142. package/dist/electron/electron/mcp/client/MCPServerConnection.js +437 -0
  143. package/dist/electron/electron/mcp/client/transports/SSETransport.js +304 -0
  144. package/dist/electron/electron/mcp/client/transports/StdioTransport.js +307 -0
  145. package/dist/electron/electron/mcp/client/transports/WebSocketTransport.js +329 -0
  146. package/dist/electron/electron/mcp/host/MCPHostServer.js +354 -0
  147. package/dist/electron/electron/mcp/host/ToolAdapter.js +100 -0
  148. package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +497 -0
  149. package/dist/electron/electron/mcp/settings.js +446 -0
  150. package/dist/electron/electron/mcp/types.js +59 -0
  151. package/dist/electron/electron/memory/MemoryService.js +435 -0
  152. package/dist/electron/electron/notifications/index.js +17 -0
  153. package/dist/electron/electron/notifications/service.js +118 -0
  154. package/dist/electron/electron/notifications/store.js +144 -0
  155. package/dist/electron/electron/preload.js +842 -0
  156. package/dist/electron/electron/reports/StandupReportService.js +272 -0
  157. package/dist/electron/electron/security/concurrency.js +293 -0
  158. package/dist/electron/electron/security/index.js +15 -0
  159. package/dist/electron/electron/security/policy-manager.js +435 -0
  160. package/dist/electron/electron/settings/appearance-manager.js +193 -0
  161. package/dist/electron/electron/settings/personality-manager.js +724 -0
  162. package/dist/electron/electron/settings/x-manager.js +58 -0
  163. package/dist/electron/electron/tailscale/exposure.js +188 -0
  164. package/dist/electron/electron/tailscale/index.js +28 -0
  165. package/dist/electron/electron/tailscale/settings.js +205 -0
  166. package/dist/electron/electron/tailscale/tailscale.js +355 -0
  167. package/dist/electron/electron/tray/QuickInputWindow.js +568 -0
  168. package/dist/electron/electron/tray/TrayManager.js +895 -0
  169. package/dist/electron/electron/tray/index.js +9 -0
  170. package/dist/electron/electron/updater/index.js +6 -0
  171. package/dist/electron/electron/updater/update-manager.js +418 -0
  172. package/dist/electron/electron/utils/env-migration.js +209 -0
  173. package/dist/electron/electron/utils/process.js +102 -0
  174. package/dist/electron/electron/utils/rate-limiter.js +104 -0
  175. package/dist/electron/electron/utils/validation.js +419 -0
  176. package/dist/electron/electron/utils/x-cli.js +177 -0
  177. package/dist/electron/electron/voice/VoiceService.js +507 -0
  178. package/dist/electron/electron/voice/index.js +14 -0
  179. package/dist/electron/electron/voice/voice-settings-manager.js +359 -0
  180. package/dist/electron/shared/channelMessages.js +170 -0
  181. package/dist/electron/shared/types.js +1185 -0
  182. package/package.json +159 -0
  183. package/resources/skills/1password.json +10 -0
  184. package/resources/skills/add-documentation.json +31 -0
  185. package/resources/skills/analyze-csv.json +17 -0
  186. package/resources/skills/apple-notes.json +10 -0
  187. package/resources/skills/apple-reminders.json +10 -0
  188. package/resources/skills/auto-commenter.json +10 -0
  189. package/resources/skills/bear-notes.json +10 -0
  190. package/resources/skills/bird.json +35 -0
  191. package/resources/skills/blogwatcher.json +10 -0
  192. package/resources/skills/blucli.json +10 -0
  193. package/resources/skills/bluebubbles.json +10 -0
  194. package/resources/skills/camsnap.json +10 -0
  195. package/resources/skills/clean-imports.json +18 -0
  196. package/resources/skills/code-review.json +18 -0
  197. package/resources/skills/coding-agent.json +10 -0
  198. package/resources/skills/compare-files.json +23 -0
  199. package/resources/skills/convert-code.json +34 -0
  200. package/resources/skills/create-changelog.json +24 -0
  201. package/resources/skills/debug-error.json +17 -0
  202. package/resources/skills/dependency-check.json +10 -0
  203. package/resources/skills/discord.json +10 -0
  204. package/resources/skills/eightctl.json +10 -0
  205. package/resources/skills/explain-code.json +29 -0
  206. package/resources/skills/extract-todos.json +18 -0
  207. package/resources/skills/food-order.json +10 -0
  208. package/resources/skills/gemini.json +10 -0
  209. package/resources/skills/generate-readme.json +10 -0
  210. package/resources/skills/gifgrep.json +10 -0
  211. package/resources/skills/git-commit.json +10 -0
  212. package/resources/skills/github.json +10 -0
  213. package/resources/skills/gog.json +10 -0
  214. package/resources/skills/goplaces.json +10 -0
  215. package/resources/skills/himalaya.json +10 -0
  216. package/resources/skills/imsg.json +10 -0
  217. package/resources/skills/karpathy-guidelines.json +12 -0
  218. package/resources/skills/last30days.json +26 -0
  219. package/resources/skills/local-places.json +10 -0
  220. package/resources/skills/mcporter.json +10 -0
  221. package/resources/skills/model-usage.json +10 -0
  222. package/resources/skills/nano-banana-pro.json +10 -0
  223. package/resources/skills/nano-pdf.json +10 -0
  224. package/resources/skills/notion.json +10 -0
  225. package/resources/skills/obsidian.json +10 -0
  226. package/resources/skills/openai-image-gen.json +10 -0
  227. package/resources/skills/openai-whisper-api.json +10 -0
  228. package/resources/skills/openai-whisper.json +10 -0
  229. package/resources/skills/openhue.json +10 -0
  230. package/resources/skills/oracle.json +10 -0
  231. package/resources/skills/ordercli.json +10 -0
  232. package/resources/skills/peekaboo.json +10 -0
  233. package/resources/skills/project-structure.json +10 -0
  234. package/resources/skills/proofread.json +17 -0
  235. package/resources/skills/refactor-code.json +31 -0
  236. package/resources/skills/rename-symbol.json +23 -0
  237. package/resources/skills/sag.json +10 -0
  238. package/resources/skills/security-audit.json +18 -0
  239. package/resources/skills/session-logs.json +10 -0
  240. package/resources/skills/sherpa-onnx-tts.json +10 -0
  241. package/resources/skills/skill-creator.json +15 -0
  242. package/resources/skills/skill-hub.json +29 -0
  243. package/resources/skills/slack.json +10 -0
  244. package/resources/skills/songsee.json +10 -0
  245. package/resources/skills/sonoscli.json +10 -0
  246. package/resources/skills/spotify-player.json +10 -0
  247. package/resources/skills/startup-cfo.json +55 -0
  248. package/resources/skills/summarize-folder.json +18 -0
  249. package/resources/skills/summarize.json +10 -0
  250. package/resources/skills/things-mac.json +10 -0
  251. package/resources/skills/tmux.json +10 -0
  252. package/resources/skills/translate.json +36 -0
  253. package/resources/skills/trello.json +10 -0
  254. package/resources/skills/video-frames.json +10 -0
  255. package/resources/skills/voice-call.json +10 -0
  256. package/resources/skills/wacli.json +10 -0
  257. package/resources/skills/weather.json +10 -0
  258. package/resources/skills/write-tests.json +31 -0
  259. package/src/electron/activity/ActivityRepository.ts +238 -0
  260. package/src/electron/agent/browser/browser-service.ts +721 -0
  261. package/src/electron/agent/context-manager.ts +257 -0
  262. package/src/electron/agent/custom-skill-loader.ts +634 -0
  263. package/src/electron/agent/daemon.ts +1097 -0
  264. package/src/electron/agent/executor.ts +4017 -0
  265. package/src/electron/agent/llm/anthropic-provider.ts +175 -0
  266. package/src/electron/agent/llm/bedrock-provider.ts +236 -0
  267. package/src/electron/agent/llm/gemini-provider.ts +422 -0
  268. package/src/electron/agent/llm/index.ts +9 -0
  269. package/src/electron/agent/llm/ollama-provider.ts +347 -0
  270. package/src/electron/agent/llm/openai-oauth.ts +127 -0
  271. package/src/electron/agent/llm/openai-provider.ts +686 -0
  272. package/src/electron/agent/llm/openrouter-provider.ts +273 -0
  273. package/src/electron/agent/llm/pricing.ts +180 -0
  274. package/src/electron/agent/llm/provider-factory.ts +971 -0
  275. package/src/electron/agent/llm/types.ts +291 -0
  276. package/src/electron/agent/queue-manager.ts +408 -0
  277. package/src/electron/agent/sandbox/docker-sandbox.ts +453 -0
  278. package/src/electron/agent/sandbox/macos-sandbox.ts +426 -0
  279. package/src/electron/agent/sandbox/runner.ts +453 -0
  280. package/src/electron/agent/sandbox/sandbox-factory.ts +337 -0
  281. package/src/electron/agent/sandbox/security-utils.ts +251 -0
  282. package/src/electron/agent/search/brave-provider.ts +141 -0
  283. package/src/electron/agent/search/google-provider.ts +131 -0
  284. package/src/electron/agent/search/index.ts +6 -0
  285. package/src/electron/agent/search/provider-factory.ts +450 -0
  286. package/src/electron/agent/search/serpapi-provider.ts +138 -0
  287. package/src/electron/agent/search/tavily-provider.ts +108 -0
  288. package/src/electron/agent/search/types.ts +118 -0
  289. package/src/electron/agent/security/index.ts +20 -0
  290. package/src/electron/agent/security/input-sanitizer.ts +380 -0
  291. package/src/electron/agent/security/output-filter.ts +259 -0
  292. package/src/electron/agent/skill-eligibility.ts +334 -0
  293. package/src/electron/agent/skill-registry.ts +457 -0
  294. package/src/electron/agent/skills/document.ts +1070 -0
  295. package/src/electron/agent/skills/image-generator.ts +272 -0
  296. package/src/electron/agent/skills/organizer.ts +131 -0
  297. package/src/electron/agent/skills/presentation.ts +418 -0
  298. package/src/electron/agent/skills/spreadsheet.ts +166 -0
  299. package/src/electron/agent/tools/browser-tools.ts +546 -0
  300. package/src/electron/agent/tools/builtin-settings.ts +422 -0
  301. package/src/electron/agent/tools/canvas-tools.ts +572 -0
  302. package/src/electron/agent/tools/cron-tools.ts +723 -0
  303. package/src/electron/agent/tools/edit-tools.ts +196 -0
  304. package/src/electron/agent/tools/file-tools.ts +811 -0
  305. package/src/electron/agent/tools/glob-tools.ts +303 -0
  306. package/src/electron/agent/tools/grep-tools.ts +432 -0
  307. package/src/electron/agent/tools/image-tools.ts +126 -0
  308. package/src/electron/agent/tools/mention-tools.ts +371 -0
  309. package/src/electron/agent/tools/node-tools.ts +550 -0
  310. package/src/electron/agent/tools/registry.ts +3052 -0
  311. package/src/electron/agent/tools/search-tools.ts +111 -0
  312. package/src/electron/agent/tools/shell-tools.ts +651 -0
  313. package/src/electron/agent/tools/skill-tools.ts +340 -0
  314. package/src/electron/agent/tools/system-tools.ts +665 -0
  315. package/src/electron/agent/tools/web-fetch-tools.ts +528 -0
  316. package/src/electron/agent/tools/x-tools.ts +267 -0
  317. package/src/electron/agents/AgentRoleRepository.ts +557 -0
  318. package/src/electron/agents/HeartbeatService.ts +469 -0
  319. package/src/electron/agents/MentionRepository.ts +242 -0
  320. package/src/electron/agents/TaskSubscriptionRepository.ts +231 -0
  321. package/src/electron/agents/WorkingStateRepository.ts +278 -0
  322. package/src/electron/canvas/canvas-manager.ts +818 -0
  323. package/src/electron/canvas/canvas-preload.ts +102 -0
  324. package/src/electron/canvas/canvas-protocol.ts +174 -0
  325. package/src/electron/canvas/canvas-store.ts +200 -0
  326. package/src/electron/canvas/index.ts +8 -0
  327. package/src/electron/control-plane/client.ts +527 -0
  328. package/src/electron/control-plane/handlers.ts +723 -0
  329. package/src/electron/control-plane/index.ts +51 -0
  330. package/src/electron/control-plane/node-manager.ts +322 -0
  331. package/src/electron/control-plane/protocol.ts +269 -0
  332. package/src/electron/control-plane/remote-client.ts +517 -0
  333. package/src/electron/control-plane/server.ts +853 -0
  334. package/src/electron/control-plane/settings.ts +401 -0
  335. package/src/electron/control-plane/ssh-tunnel.ts +624 -0
  336. package/src/electron/cron/index.ts +9 -0
  337. package/src/electron/cron/schedule.ts +217 -0
  338. package/src/electron/cron/service.ts +743 -0
  339. package/src/electron/cron/store.ts +165 -0
  340. package/src/electron/cron/types.ts +291 -0
  341. package/src/electron/cron/webhook.ts +303 -0
  342. package/src/electron/database/SecureSettingsRepository.ts +514 -0
  343. package/src/electron/database/TaskLabelRepository.ts +148 -0
  344. package/src/electron/database/repositories.ts +2397 -0
  345. package/src/electron/database/schema.ts +1017 -0
  346. package/src/electron/extensions/index.ts +18 -0
  347. package/src/electron/extensions/loader.ts +336 -0
  348. package/src/electron/extensions/registry.ts +546 -0
  349. package/src/electron/extensions/types.ts +372 -0
  350. package/src/electron/gateway/channel-registry.ts +1267 -0
  351. package/src/electron/gateway/channels/bluebubbles-client.ts +641 -0
  352. package/src/electron/gateway/channels/bluebubbles.ts +509 -0
  353. package/src/electron/gateway/channels/discord.ts +1150 -0
  354. package/src/electron/gateway/channels/email-client.ts +708 -0
  355. package/src/electron/gateway/channels/email.ts +516 -0
  356. package/src/electron/gateway/channels/google-chat.ts +760 -0
  357. package/src/electron/gateway/channels/imessage-client.ts +473 -0
  358. package/src/electron/gateway/channels/imessage.ts +520 -0
  359. package/src/electron/gateway/channels/index.ts +21 -0
  360. package/src/electron/gateway/channels/line-client.ts +598 -0
  361. package/src/electron/gateway/channels/line.ts +559 -0
  362. package/src/electron/gateway/channels/matrix-client.ts +632 -0
  363. package/src/electron/gateway/channels/matrix.ts +655 -0
  364. package/src/electron/gateway/channels/mattermost-client.ts +526 -0
  365. package/src/electron/gateway/channels/mattermost.ts +550 -0
  366. package/src/electron/gateway/channels/signal-client.ts +722 -0
  367. package/src/electron/gateway/channels/signal.ts +666 -0
  368. package/src/electron/gateway/channels/slack.ts +458 -0
  369. package/src/electron/gateway/channels/teams.ts +681 -0
  370. package/src/electron/gateway/channels/telegram.ts +1727 -0
  371. package/src/electron/gateway/channels/twitch-client.ts +665 -0
  372. package/src/electron/gateway/channels/twitch.ts +468 -0
  373. package/src/electron/gateway/channels/types.ts +1002 -0
  374. package/src/electron/gateway/channels/whatsapp.ts +1101 -0
  375. package/src/electron/gateway/context-policy.ts +382 -0
  376. package/src/electron/gateway/index.ts +1274 -0
  377. package/src/electron/gateway/infrastructure.ts +645 -0
  378. package/src/electron/gateway/router.ts +3206 -0
  379. package/src/electron/gateway/security.ts +422 -0
  380. package/src/electron/gateway/session.ts +144 -0
  381. package/src/electron/gateway/tunnel.ts +626 -0
  382. package/src/electron/guardrails/guardrail-manager.ts +380 -0
  383. package/src/electron/hooks/gmail-watcher.ts +355 -0
  384. package/src/electron/hooks/index.ts +30 -0
  385. package/src/electron/hooks/mappings.ts +404 -0
  386. package/src/electron/hooks/server.ts +574 -0
  387. package/src/electron/hooks/settings.ts +466 -0
  388. package/src/electron/hooks/types.ts +245 -0
  389. package/src/electron/ipc/canvas-handlers.ts +223 -0
  390. package/src/electron/ipc/handlers.ts +3661 -0
  391. package/src/electron/ipc/mission-control-handlers.ts +182 -0
  392. package/src/electron/main.ts +496 -0
  393. package/src/electron/mcp/client/MCPClientManager.ts +406 -0
  394. package/src/electron/mcp/client/MCPServerConnection.ts +514 -0
  395. package/src/electron/mcp/client/transports/SSETransport.ts +360 -0
  396. package/src/electron/mcp/client/transports/StdioTransport.ts +355 -0
  397. package/src/electron/mcp/client/transports/WebSocketTransport.ts +384 -0
  398. package/src/electron/mcp/host/MCPHostServer.ts +388 -0
  399. package/src/electron/mcp/host/ToolAdapter.ts +140 -0
  400. package/src/electron/mcp/registry/MCPRegistryManager.ts +565 -0
  401. package/src/electron/mcp/settings.ts +468 -0
  402. package/src/electron/mcp/types.ts +371 -0
  403. package/src/electron/memory/MemoryService.ts +523 -0
  404. package/src/electron/notifications/index.ts +16 -0
  405. package/src/electron/notifications/service.ts +161 -0
  406. package/src/electron/notifications/store.ts +163 -0
  407. package/src/electron/preload.ts +2845 -0
  408. package/src/electron/reports/StandupReportService.ts +356 -0
  409. package/src/electron/security/concurrency.ts +333 -0
  410. package/src/electron/security/index.ts +17 -0
  411. package/src/electron/security/policy-manager.ts +539 -0
  412. package/src/electron/settings/appearance-manager.ts +182 -0
  413. package/src/electron/settings/personality-manager.ts +800 -0
  414. package/src/electron/settings/x-manager.ts +62 -0
  415. package/src/electron/tailscale/exposure.ts +262 -0
  416. package/src/electron/tailscale/index.ts +34 -0
  417. package/src/electron/tailscale/settings.ts +218 -0
  418. package/src/electron/tailscale/tailscale.ts +379 -0
  419. package/src/electron/tray/QuickInputWindow.ts +609 -0
  420. package/src/electron/tray/TrayManager.ts +1005 -0
  421. package/src/electron/tray/index.ts +6 -0
  422. package/src/electron/updater/index.ts +1 -0
  423. package/src/electron/updater/update-manager.ts +447 -0
  424. package/src/electron/utils/env-migration.ts +203 -0
  425. package/src/electron/utils/process.ts +124 -0
  426. package/src/electron/utils/rate-limiter.ts +130 -0
  427. package/src/electron/utils/validation.ts +493 -0
  428. package/src/electron/utils/x-cli.ts +198 -0
  429. package/src/electron/voice/VoiceService.ts +583 -0
  430. package/src/electron/voice/index.ts +9 -0
  431. package/src/electron/voice/voice-settings-manager.ts +403 -0
  432. package/src/renderer/App.tsx +775 -0
  433. package/src/renderer/components/ActivityFeed.tsx +407 -0
  434. package/src/renderer/components/ActivityFeedItem.tsx +285 -0
  435. package/src/renderer/components/AgentRoleCard.tsx +343 -0
  436. package/src/renderer/components/AgentRoleEditor.tsx +805 -0
  437. package/src/renderer/components/AgentSquadSettings.tsx +295 -0
  438. package/src/renderer/components/AgentWorkingStatePanel.tsx +411 -0
  439. package/src/renderer/components/AppearanceSettings.tsx +122 -0
  440. package/src/renderer/components/ApprovalDialog.tsx +100 -0
  441. package/src/renderer/components/BlueBubblesSettings.tsx +505 -0
  442. package/src/renderer/components/BuiltinToolsSettings.tsx +307 -0
  443. package/src/renderer/components/CanvasPreview.tsx +1189 -0
  444. package/src/renderer/components/CommandOutput.tsx +202 -0
  445. package/src/renderer/components/ContextPolicySettings.tsx +523 -0
  446. package/src/renderer/components/ControlPlaneSettings.tsx +1134 -0
  447. package/src/renderer/components/DisclaimerModal.tsx +124 -0
  448. package/src/renderer/components/DiscordSettings.tsx +436 -0
  449. package/src/renderer/components/EmailSettings.tsx +606 -0
  450. package/src/renderer/components/ExtensionsSettings.tsx +542 -0
  451. package/src/renderer/components/FileViewer.tsx +224 -0
  452. package/src/renderer/components/GoogleChatSettings.tsx +535 -0
  453. package/src/renderer/components/GuardrailSettings.tsx +487 -0
  454. package/src/renderer/components/HooksSettings.tsx +581 -0
  455. package/src/renderer/components/ImessageSettings.tsx +484 -0
  456. package/src/renderer/components/LineSettings.tsx +483 -0
  457. package/src/renderer/components/MCPRegistryBrowser.tsx +386 -0
  458. package/src/renderer/components/MCPSettings.tsx +943 -0
  459. package/src/renderer/components/MainContent.tsx +2433 -0
  460. package/src/renderer/components/MatrixSettings.tsx +510 -0
  461. package/src/renderer/components/MattermostSettings.tsx +473 -0
  462. package/src/renderer/components/MemorySettings.tsx +247 -0
  463. package/src/renderer/components/MentionBadge.tsx +87 -0
  464. package/src/renderer/components/MentionInput.tsx +409 -0
  465. package/src/renderer/components/MentionList.tsx +476 -0
  466. package/src/renderer/components/MissionControlPanel.tsx +1995 -0
  467. package/src/renderer/components/NodesSettings.tsx +316 -0
  468. package/src/renderer/components/NotificationPanel.tsx +481 -0
  469. package/src/renderer/components/Onboarding/AwakeningOrb.tsx +44 -0
  470. package/src/renderer/components/Onboarding/Onboarding.tsx +443 -0
  471. package/src/renderer/components/Onboarding/TypewriterText.tsx +102 -0
  472. package/src/renderer/components/Onboarding/index.ts +3 -0
  473. package/src/renderer/components/OnboardingModal.tsx +698 -0
  474. package/src/renderer/components/PairingCodeDisplay.tsx +324 -0
  475. package/src/renderer/components/PersonalitySettings.tsx +597 -0
  476. package/src/renderer/components/QueueSettings.tsx +119 -0
  477. package/src/renderer/components/QuickTaskFAB.tsx +71 -0
  478. package/src/renderer/components/RightPanel.tsx +413 -0
  479. package/src/renderer/components/ScheduledTasksSettings.tsx +1328 -0
  480. package/src/renderer/components/SearchSettings.tsx +328 -0
  481. package/src/renderer/components/Settings.tsx +1504 -0
  482. package/src/renderer/components/Sidebar.tsx +344 -0
  483. package/src/renderer/components/SignalSettings.tsx +673 -0
  484. package/src/renderer/components/SkillHubBrowser.tsx +458 -0
  485. package/src/renderer/components/SkillParameterModal.tsx +185 -0
  486. package/src/renderer/components/SkillsSettings.tsx +451 -0
  487. package/src/renderer/components/SlackSettings.tsx +442 -0
  488. package/src/renderer/components/StandupReportViewer.tsx +614 -0
  489. package/src/renderer/components/TaskBoard.tsx +498 -0
  490. package/src/renderer/components/TaskBoardCard.tsx +357 -0
  491. package/src/renderer/components/TaskBoardColumn.tsx +211 -0
  492. package/src/renderer/components/TaskLabelManager.tsx +472 -0
  493. package/src/renderer/components/TaskQueuePanel.tsx +144 -0
  494. package/src/renderer/components/TaskQuickActions.tsx +492 -0
  495. package/src/renderer/components/TaskTimeline.tsx +216 -0
  496. package/src/renderer/components/TaskView.tsx +162 -0
  497. package/src/renderer/components/TeamsSettings.tsx +518 -0
  498. package/src/renderer/components/TelegramSettings.tsx +421 -0
  499. package/src/renderer/components/Toast.tsx +76 -0
  500. package/src/renderer/components/TraySettings.tsx +189 -0
  501. package/src/renderer/components/TwitchSettings.tsx +511 -0
  502. package/src/renderer/components/UpdateSettings.tsx +295 -0
  503. package/src/renderer/components/VoiceIndicator.tsx +270 -0
  504. package/src/renderer/components/VoiceSettings.tsx +867 -0
  505. package/src/renderer/components/WhatsAppSettings.tsx +721 -0
  506. package/src/renderer/components/WorkingStateEditor.tsx +309 -0
  507. package/src/renderer/components/WorkingStateHistory.tsx +481 -0
  508. package/src/renderer/components/WorkspaceSelector.tsx +150 -0
  509. package/src/renderer/components/XSettings.tsx +311 -0
  510. package/src/renderer/global.d.ts +9 -0
  511. package/src/renderer/hooks/useAgentContext.ts +153 -0
  512. package/src/renderer/hooks/useOnboardingFlow.ts +548 -0
  513. package/src/renderer/hooks/useVoiceInput.ts +268 -0
  514. package/src/renderer/index.html +12 -0
  515. package/src/renderer/main.tsx +10 -0
  516. package/src/renderer/public/cowork-os-logo.png +0 -0
  517. package/src/renderer/quick-input.html +164 -0
  518. package/src/renderer/styles/index.css +14504 -0
  519. package/src/renderer/utils/agentMessages.ts +749 -0
  520. package/src/renderer/utils/voice-directives.ts +169 -0
  521. package/src/shared/channelMessages.ts +213 -0
  522. package/src/shared/types.ts +3608 -0
  523. package/tsconfig.electron.json +26 -0
  524. package/tsconfig.json +26 -0
  525. package/tsconfig.node.json +10 -0
  526. package/vite.config.ts +23 -0
@@ -0,0 +1,1101 @@
1
+ /**
2
+ * WhatsApp Channel Adapter
3
+ *
4
+ * Implements the ChannelAdapter interface using Baileys for WhatsApp Web API.
5
+ *
6
+ * Features:
7
+ * - QR code authentication for WhatsApp Web
8
+ * - Multi-file auth state persistence
9
+ * - Message deduplication
10
+ * - Group and DM message handling
11
+ * - Media message support (images, documents, audio, video)
12
+ * - Typing indicators (composing presence)
13
+ * - Message reactions
14
+ * - Auto-reconnection with exponential backoff
15
+ * - Read receipts
16
+ */
17
+
18
+ import {
19
+ makeWASocket,
20
+ useMultiFileAuthState,
21
+ fetchLatestBaileysVersion,
22
+ makeCacheableSignalKeyStore,
23
+ DisconnectReason,
24
+ isJidGroup,
25
+ downloadContentFromMessage,
26
+ type WASocket,
27
+ type WAMessage,
28
+ type AnyMessageContent,
29
+ type ConnectionState,
30
+ type proto,
31
+ type DownloadableMessage,
32
+ } from '@whiskeysockets/baileys';
33
+ import * as fs from 'fs';
34
+ import * as path from 'path';
35
+ import { app } from 'electron';
36
+ import {
37
+ ChannelAdapter,
38
+ ChannelStatus,
39
+ IncomingMessage,
40
+ OutgoingMessage,
41
+ MessageHandler,
42
+ ErrorHandler,
43
+ StatusHandler,
44
+ ChannelInfo,
45
+ MessageAttachment,
46
+ CallbackQueryHandler,
47
+ WhatsAppConfig,
48
+ } from './types';
49
+
50
+ /**
51
+ * Exponential backoff configuration
52
+ */
53
+ interface BackoffConfig {
54
+ initialDelay: number;
55
+ maxDelay: number;
56
+ multiplier: number;
57
+ jitter: number;
58
+ maxAttempts: number;
59
+ }
60
+
61
+ /**
62
+ * QR code event handler
63
+ */
64
+ export type QrCodeHandler = (qr: string) => void;
65
+
66
+ /**
67
+ * WhatsApp inbound message
68
+ */
69
+ interface WhatsAppInboundMessage {
70
+ id?: string;
71
+ from: string;
72
+ to: string;
73
+ body: string;
74
+ timestamp?: number;
75
+ chatType: 'direct' | 'group';
76
+ chatId: string;
77
+ senderJid?: string;
78
+ senderE164?: string;
79
+ senderName?: string;
80
+ groupSubject?: string;
81
+ mediaPath?: string;
82
+ mediaType?: string;
83
+ replyToId?: string;
84
+ replyToBody?: string;
85
+ }
86
+
87
+ export class WhatsAppAdapter implements ChannelAdapter {
88
+ readonly type = 'whatsapp' as const;
89
+
90
+ private sock: WASocket | null = null;
91
+ private _status: ChannelStatus = 'disconnected';
92
+ private _selfJid?: string;
93
+ private _selfE164?: string;
94
+ private messageHandlers: MessageHandler[] = [];
95
+ private errorHandlers: ErrorHandler[] = [];
96
+ private statusHandlers: StatusHandler[] = [];
97
+ private qrCodeHandlers: QrCodeHandler[] = [];
98
+ private config: WhatsAppConfig;
99
+ private authDir: string;
100
+
101
+ // Message deduplication
102
+ private processedMessages: Map<string, number> = new Map();
103
+ private readonly DEDUP_CACHE_TTL = 60000; // 1 minute
104
+ private readonly DEDUP_CACHE_MAX_SIZE = 1000;
105
+ private dedupCleanupTimer?: ReturnType<typeof setTimeout>;
106
+
107
+ // Connection state
108
+ private connectedAtMs: number = 0;
109
+ private isReconnecting = false;
110
+ private backoffAttempt = 0;
111
+ private backoffTimer?: ReturnType<typeof setTimeout>;
112
+ private currentQr?: string;
113
+
114
+ private readonly DEFAULT_BACKOFF: BackoffConfig = {
115
+ initialDelay: 2000,
116
+ maxDelay: 30000,
117
+ multiplier: 1.8,
118
+ jitter: 0.25,
119
+ maxAttempts: 10,
120
+ };
121
+
122
+ // Group metadata cache
123
+ private groupMetaCache: Map<string, { subject?: string; expires: number }> = new Map();
124
+ private readonly GROUP_META_TTL_MS = 5 * 60 * 1000; // 5 minutes
125
+
126
+ constructor(config: WhatsAppConfig) {
127
+ this.config = {
128
+ deduplicationEnabled: true,
129
+ sendReadReceipts: true,
130
+ printQrToTerminal: false,
131
+ selfChatMode: true, // Default to self-chat mode since most users use their own number
132
+ responsePrefix: '🤖', // Default prefix for bot responses
133
+ ...config,
134
+ };
135
+
136
+ // In self-chat mode, disable read receipts by default
137
+ if (this.config.selfChatMode && config.sendReadReceipts === undefined) {
138
+ this.config.sendReadReceipts = false;
139
+ }
140
+
141
+ // Set auth directory
142
+ this.authDir = config.authDir || path.join(app.getPath('userData'), 'whatsapp-auth');
143
+ }
144
+
145
+ /**
146
+ * Check if self-chat mode is enabled
147
+ */
148
+ get isSelfChatMode(): boolean {
149
+ return this.config.selfChatMode === true;
150
+ }
151
+
152
+ /**
153
+ * Get the response prefix for bot messages
154
+ */
155
+ get responsePrefix(): string {
156
+ return this.config.responsePrefix || '🤖';
157
+ }
158
+
159
+ get status(): ChannelStatus {
160
+ return this._status;
161
+ }
162
+
163
+ get botUsername(): string | undefined {
164
+ return this._selfE164;
165
+ }
166
+
167
+ /**
168
+ * Get the current QR code (if in login state)
169
+ */
170
+ get qrCode(): string | undefined {
171
+ return this.currentQr;
172
+ }
173
+
174
+ /**
175
+ * Check if WhatsApp auth credentials exist
176
+ */
177
+ async hasCredentials(): Promise<boolean> {
178
+ const credsPath = path.join(this.authDir, 'creds.json');
179
+ return fs.existsSync(credsPath);
180
+ }
181
+
182
+ /**
183
+ * Connect to WhatsApp Web
184
+ */
185
+ async connect(): Promise<void> {
186
+ if (this._status === 'connected' || this._status === 'connecting') {
187
+ return;
188
+ }
189
+
190
+ this.setStatus('connecting');
191
+ this.resetBackoff();
192
+
193
+ try {
194
+ // Ensure auth directory exists
195
+ await this.ensureDir(this.authDir);
196
+
197
+ // Load auth state
198
+ const { state, saveCreds } = await useMultiFileAuthState(this.authDir);
199
+
200
+ // Get latest Baileys version
201
+ const { version } = await fetchLatestBaileysVersion();
202
+
203
+ // Create silent logger to suppress Baileys logs
204
+ const logger = {
205
+ level: 'silent' as const,
206
+ trace: () => {},
207
+ debug: () => {},
208
+ info: () => {},
209
+ warn: () => {},
210
+ error: () => {},
211
+ fatal: () => {},
212
+ child: () => logger,
213
+ };
214
+
215
+ // Create WhatsApp socket
216
+ this.sock = makeWASocket({
217
+ auth: {
218
+ creds: state.creds,
219
+ keys: makeCacheableSignalKeyStore(state.keys, logger as any),
220
+ },
221
+ version,
222
+ logger: logger as any,
223
+ // Note: printQRInTerminal is deprecated - QR codes are handled via connection.update event
224
+ browser: ['CoWork-OS', 'Desktop', '1.0.0'],
225
+ syncFullHistory: false,
226
+ markOnlineOnConnect: false,
227
+ });
228
+
229
+ // Handle credential updates
230
+ this.sock.ev.on('creds.update', saveCreds);
231
+
232
+ // Handle connection updates
233
+ this.sock.ev.on('connection.update', (update) => {
234
+ this.handleConnectionUpdate(update);
235
+ });
236
+
237
+ // Handle incoming messages
238
+ this.sock.ev.on('messages.upsert', (upsert) => {
239
+ this.handleMessagesUpsert(upsert);
240
+ });
241
+
242
+ // Start deduplication cleanup
243
+ if (this.config.deduplicationEnabled) {
244
+ this.startDedupCleanup();
245
+ }
246
+ } catch (error) {
247
+ const err = error instanceof Error ? error : new Error(String(error));
248
+ this.setStatus('error', err);
249
+ throw err;
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Handle connection state updates
255
+ */
256
+ private handleConnectionUpdate(update: Partial<ConnectionState>): void {
257
+ const { connection, lastDisconnect, qr } = update;
258
+
259
+ // Handle QR code for authentication
260
+ if (qr) {
261
+ this.currentQr = qr;
262
+ console.log('WhatsApp QR code received - scan with WhatsApp mobile app');
263
+
264
+ // Notify QR handlers
265
+ for (const handler of this.qrCodeHandlers) {
266
+ try {
267
+ handler(qr);
268
+ } catch (e) {
269
+ console.error('Error in QR code handler:', e);
270
+ }
271
+ }
272
+ }
273
+
274
+ // Handle connection open
275
+ if (connection === 'open') {
276
+ this.currentQr = undefined;
277
+ this.connectedAtMs = Date.now();
278
+ this._selfJid = this.sock?.user?.id;
279
+ this._selfE164 = this._selfJid ? this.jidToE164(this._selfJid) ?? undefined : undefined;
280
+
281
+ console.log(`WhatsApp connected as ${this._selfE164 || this._selfJid}`);
282
+ this.setStatus('connected');
283
+ this.resetBackoff();
284
+
285
+ // Send available presence
286
+ this.sock?.sendPresenceUpdate('available').catch(() => {});
287
+ }
288
+
289
+ // Handle connection close
290
+ if (connection === 'close') {
291
+ this.currentQr = undefined;
292
+ const statusCode = this.getStatusCode(lastDisconnect?.error);
293
+
294
+ if (statusCode === DisconnectReason.loggedOut) {
295
+ console.error('WhatsApp session logged out');
296
+ this.setStatus('error', new Error('WhatsApp session logged out. Please re-authenticate.'));
297
+ // Clear credentials on logout
298
+ this.clearCredentials().catch(() => {});
299
+ } else if (statusCode === DisconnectReason.restartRequired) {
300
+ console.log('WhatsApp restart required, reconnecting...');
301
+ this.attemptReconnection();
302
+ } else {
303
+ console.log(`WhatsApp connection closed (status: ${statusCode}), attempting reconnection...`);
304
+ this.attemptReconnection();
305
+ }
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Handle incoming messages
311
+ */
312
+ private async handleMessagesUpsert(upsert: { type?: string; messages?: WAMessage[] }): Promise<void> {
313
+ if (upsert.type !== 'notify' && upsert.type !== 'append') return;
314
+
315
+ for (const msg of upsert.messages ?? []) {
316
+ try {
317
+ await this.processInboundMessage(msg, upsert.type);
318
+ } catch (error) {
319
+ console.error('Error processing WhatsApp message:', error);
320
+ this.handleError(
321
+ error instanceof Error ? error : new Error(String(error)),
322
+ 'messageProcessing'
323
+ );
324
+ }
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Process a single inbound message
330
+ */
331
+ private async processInboundMessage(msg: WAMessage, upsertType: string): Promise<void> {
332
+ const id = msg.key?.id;
333
+ const remoteJid = msg.key?.remoteJid;
334
+ if (!remoteJid) return;
335
+
336
+ // Skip status and broadcast messages
337
+ if (remoteJid.endsWith('@status') || remoteJid.endsWith('@broadcast')) return;
338
+
339
+ // CRITICAL: In self-chat mode, ONLY process messages from self-chat
340
+ // This prevents the bot from responding to messages sent to other people
341
+ if (this.isSelfChatMode && this._selfJid) {
342
+ // Normalize JIDs by removing device suffix (e.g., "123:5@s.whatsapp.net" -> "123@s.whatsapp.net")
343
+ const normalizeJid = (jid: string) => jid.replace(/:[\d]+@/, '@');
344
+ const selfJidNormalized = normalizeJid(this._selfJid);
345
+ const remoteJidNormalized = normalizeJid(remoteJid);
346
+
347
+ if (remoteJidNormalized !== selfJidNormalized) {
348
+ // Message is NOT in self-chat, silently ignore it
349
+ return;
350
+ }
351
+ }
352
+
353
+ // Deduplication
354
+ if (id && this.config.deduplicationEnabled) {
355
+ const dedupeKey = `${remoteJid}:${id}`;
356
+ if (this.processedMessages.has(dedupeKey)) return;
357
+ this.processedMessages.set(dedupeKey, Date.now());
358
+
359
+ // Cleanup if cache is too large
360
+ if (this.processedMessages.size > this.DEDUP_CACHE_MAX_SIZE) {
361
+ this.cleanupDedupCache();
362
+ }
363
+ }
364
+
365
+ const isGroup = isJidGroup(remoteJid) === true;
366
+ const participantJid = msg.key?.participant;
367
+ const from = isGroup ? remoteJid : this.jidToE164(remoteJid) || remoteJid;
368
+ const senderE164 = isGroup
369
+ ? participantJid ? this.jidToE164(participantJid) : null
370
+ : from;
371
+
372
+ // Check access control
373
+ if (this.config.allowedNumbers && this.config.allowedNumbers.length > 0) {
374
+ const senderNumber = senderE164?.replace(/[^0-9]/g, '');
375
+ if (senderNumber && !this.config.allowedNumbers.includes(senderNumber)) {
376
+ console.log(`WhatsApp: Ignoring message from unauthorized number: ${senderNumber}`);
377
+ return;
378
+ }
379
+ }
380
+
381
+ // Get group metadata if applicable
382
+ let groupSubject: string | undefined;
383
+ if (isGroup && this.sock) {
384
+ const meta = await this.getGroupMeta(remoteJid);
385
+ groupSubject = meta.subject;
386
+ }
387
+
388
+ // Extract message text
389
+ const body = this.extractText(msg.message);
390
+ if (!body) {
391
+ // Check for media placeholder
392
+ const mediaPlaceholder = this.extractMediaPlaceholder(msg.message);
393
+ if (!mediaPlaceholder) return;
394
+ }
395
+
396
+ // Send read receipt
397
+ if (id && this.config.sendReadReceipts && upsertType === 'notify') {
398
+ try {
399
+ await this.sock?.readMessages([{
400
+ remoteJid,
401
+ id,
402
+ participant: participantJid,
403
+ fromMe: false,
404
+ }]);
405
+ } catch {
406
+ // Ignore read receipt errors
407
+ }
408
+ }
409
+
410
+ // Skip history/offline catch-up messages
411
+ if (upsertType === 'append') return;
412
+
413
+ const messageTimestampMs = msg.messageTimestamp
414
+ ? Number(msg.messageTimestamp) * 1000
415
+ : undefined;
416
+
417
+ // Download audio attachment if present
418
+ const attachments: MessageAttachment[] = [];
419
+ if (msg.message?.audioMessage) {
420
+ const audioAttachment = await this.downloadAudioAttachment(msg.message);
421
+ if (audioAttachment) {
422
+ attachments.push(audioAttachment);
423
+ }
424
+ }
425
+
426
+ // Create incoming message
427
+ const incomingMessage: IncomingMessage = {
428
+ messageId: id || `wa-${Date.now()}`,
429
+ channel: 'whatsapp',
430
+ userId: senderE164 || participantJid || remoteJid,
431
+ userName: msg.pushName || senderE164 || 'Unknown',
432
+ chatId: remoteJid,
433
+ text: body || (attachments.length === 0 ? this.extractMediaPlaceholder(msg.message) : '') || '',
434
+ timestamp: messageTimestampMs ? new Date(messageTimestampMs) : new Date(),
435
+ attachments: attachments.length > 0 ? attachments : undefined,
436
+ raw: msg,
437
+ };
438
+
439
+ // Notify message handlers
440
+ await this.handleIncomingMessage(incomingMessage);
441
+ }
442
+
443
+ /**
444
+ * Disconnect from WhatsApp
445
+ */
446
+ async disconnect(): Promise<void> {
447
+ this.resetBackoff();
448
+
449
+ // Clear timers
450
+ if (this.dedupCleanupTimer) {
451
+ clearInterval(this.dedupCleanupTimer);
452
+ this.dedupCleanupTimer = undefined;
453
+ }
454
+
455
+ // Clear caches
456
+ this.processedMessages.clear();
457
+ this.groupMetaCache.clear();
458
+ this.currentQr = undefined;
459
+
460
+ if (this.sock) {
461
+ try {
462
+ this.sock.ws?.close();
463
+ } catch {
464
+ // Ignore close errors
465
+ }
466
+ this.sock = null;
467
+ }
468
+
469
+ this._selfJid = undefined;
470
+ this._selfE164 = undefined;
471
+ this.setStatus('disconnected');
472
+ }
473
+
474
+ /**
475
+ * Convert standard Markdown to WhatsApp-compatible formatting
476
+ * WhatsApp supports: *bold*, _italic_, ~strikethrough~, ```monospace```
477
+ */
478
+ private convertMarkdownToWhatsApp(text: string): string {
479
+ let result = text;
480
+
481
+ // Convert headers (### Header) to bold text
482
+ result = result.replace(/^#{1,6}\s+(.+)$/gm, '*$1*');
483
+
484
+ // Convert **bold** to *bold* (WhatsApp style)
485
+ result = result.replace(/\*\*(.+?)\*\*/g, '*$1*');
486
+
487
+ // Convert __bold__ to *bold*
488
+ result = result.replace(/__(.+?)__/g, '*$1*');
489
+
490
+ // Convert _italic_ - already WhatsApp compatible, but handle markdown style
491
+ // Note: Single underscores are already WhatsApp italic
492
+
493
+ // Convert ~~strikethrough~~ to ~strikethrough~
494
+ result = result.replace(/~~(.+?)~~/g, '~$1~');
495
+
496
+ // Convert inline code `code` to monospace (WhatsApp uses triple backticks but single works in some clients)
497
+ // Keep as-is since WhatsApp renders `code` reasonably
498
+
499
+ // Convert code blocks ```code``` - already WhatsApp compatible
500
+
501
+ // Convert [link text](url) to "link text (url)" since WhatsApp auto-links URLs
502
+ result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1 ($2)');
503
+
504
+ // Convert horizontal rules (---, ***, ___) to a line
505
+ result = result.replace(/^[-*_]{3,}$/gm, '───────────');
506
+
507
+ // Clean up excessive newlines
508
+ result = result.replace(/\n{3,}/g, '\n\n');
509
+
510
+ return result;
511
+ }
512
+
513
+ /**
514
+ * Send a message to a WhatsApp chat
515
+ */
516
+ async sendMessage(message: OutgoingMessage): Promise<string> {
517
+ if (!this.sock || this._status !== 'connected') {
518
+ throw new Error('WhatsApp is not connected');
519
+ }
520
+
521
+ const jid = this.toWhatsAppJid(message.chatId);
522
+ let messageId = '';
523
+
524
+ // Convert markdown to WhatsApp formatting and apply response prefix
525
+ let textToSend = message.text ? this.convertMarkdownToWhatsApp(message.text) : message.text;
526
+ if (this.isSelfChatMode && textToSend && textToSend.trim()) {
527
+ const prefix = this.responsePrefix;
528
+ // Only add prefix if not already present
529
+ if (!textToSend.startsWith(prefix)) {
530
+ textToSend = `${prefix} ${textToSend}`;
531
+ }
532
+ }
533
+
534
+ // Send media attachments first
535
+ if (message.attachments && message.attachments.length > 0) {
536
+ for (const attachment of message.attachments) {
537
+ const result = await this.sendMediaAttachment(jid, attachment, textToSend);
538
+ messageId = result;
539
+ // Clear text after first media with caption
540
+ if (textToSend) {
541
+ textToSend = '';
542
+ }
543
+ }
544
+ }
545
+
546
+ // Send text message if no media or text remains
547
+ if (textToSend && textToSend.trim()) {
548
+ // Send composing presence
549
+ await this.sendComposingTo(jid);
550
+
551
+ const result = await this.sock.sendMessage(jid, { text: textToSend });
552
+ messageId = result?.key?.id || `wa-${Date.now()}`;
553
+ }
554
+
555
+ return messageId;
556
+ }
557
+
558
+ /**
559
+ * Send a media attachment
560
+ */
561
+ private async sendMediaAttachment(
562
+ jid: string,
563
+ attachment: MessageAttachment,
564
+ caption?: string
565
+ ): Promise<string> {
566
+ if (!this.sock) throw new Error('WhatsApp is not connected');
567
+
568
+ let content: AnyMessageContent;
569
+
570
+ if (attachment.type === 'image' && attachment.url) {
571
+ const buffer = fs.readFileSync(attachment.url);
572
+ content = {
573
+ image: buffer,
574
+ caption,
575
+ mimetype: attachment.mimeType || 'image/jpeg',
576
+ };
577
+ } else if (attachment.type === 'document' && attachment.url) {
578
+ const buffer = fs.readFileSync(attachment.url);
579
+ content = {
580
+ document: buffer,
581
+ fileName: attachment.fileName || path.basename(attachment.url),
582
+ mimetype: attachment.mimeType || 'application/octet-stream',
583
+ caption,
584
+ };
585
+ } else if (attachment.type === 'audio' && attachment.url) {
586
+ const buffer = fs.readFileSync(attachment.url);
587
+ content = {
588
+ audio: buffer,
589
+ mimetype: attachment.mimeType || 'audio/mpeg',
590
+ ptt: true, // Voice note
591
+ };
592
+ } else if (attachment.type === 'video' && attachment.url) {
593
+ const buffer = fs.readFileSync(attachment.url);
594
+ content = {
595
+ video: buffer,
596
+ caption,
597
+ mimetype: attachment.mimeType || 'video/mp4',
598
+ };
599
+ } else {
600
+ throw new Error(`Unsupported attachment type: ${attachment.type}`);
601
+ }
602
+
603
+ const result = await this.sock.sendMessage(jid, content);
604
+ return result?.key?.id || `wa-${Date.now()}`;
605
+ }
606
+
607
+ /**
608
+ * Send composing (typing) indicator
609
+ */
610
+ async sendComposingTo(chatId: string): Promise<void> {
611
+ if (!this.sock) return;
612
+
613
+ const jid = this.toWhatsAppJid(chatId);
614
+ try {
615
+ await this.sock.sendPresenceUpdate('composing', jid);
616
+ } catch {
617
+ // Ignore presence errors
618
+ }
619
+ }
620
+
621
+ /**
622
+ * Send typing indicator (alias for sendComposingTo)
623
+ */
624
+ async sendTyping(chatId: string): Promise<void> {
625
+ await this.sendComposingTo(chatId);
626
+ }
627
+
628
+ /**
629
+ * Edit an existing message (not supported by WhatsApp Web API)
630
+ */
631
+ async editMessage(_chatId: string, _messageId: string, _text: string): Promise<void> {
632
+ throw new Error('WhatsApp does not support message editing');
633
+ }
634
+
635
+ /**
636
+ * Delete a message
637
+ */
638
+ async deleteMessage(chatId: string, messageId: string): Promise<void> {
639
+ if (!this.sock) throw new Error('WhatsApp is not connected');
640
+
641
+ const jid = this.toWhatsAppJid(chatId);
642
+ await this.sock.sendMessage(jid, {
643
+ delete: {
644
+ remoteJid: jid,
645
+ fromMe: true,
646
+ id: messageId,
647
+ },
648
+ });
649
+ }
650
+
651
+ /**
652
+ * Send a document/file
653
+ */
654
+ async sendDocument(chatId: string, filePath: string, caption?: string): Promise<string> {
655
+ if (!this.sock) throw new Error('WhatsApp is not connected');
656
+
657
+ const jid = this.toWhatsAppJid(chatId);
658
+ const buffer = fs.readFileSync(filePath);
659
+ const fileName = path.basename(filePath);
660
+
661
+ const result = await this.sock.sendMessage(jid, {
662
+ document: buffer,
663
+ fileName,
664
+ mimetype: 'application/octet-stream',
665
+ caption,
666
+ });
667
+
668
+ return result?.key?.id || `wa-${Date.now()}`;
669
+ }
670
+
671
+ /**
672
+ * Send a photo/image
673
+ */
674
+ async sendPhoto(chatId: string, filePath: string, caption?: string): Promise<string> {
675
+ if (!this.sock) throw new Error('WhatsApp is not connected');
676
+
677
+ const jid = this.toWhatsAppJid(chatId);
678
+ const buffer = fs.readFileSync(filePath);
679
+
680
+ const result = await this.sock.sendMessage(jid, {
681
+ image: buffer,
682
+ caption,
683
+ });
684
+
685
+ return result?.key?.id || `wa-${Date.now()}`;
686
+ }
687
+
688
+ /**
689
+ * Add a reaction to a message
690
+ */
691
+ async addReaction(chatId: string, messageId: string, emoji: string): Promise<void> {
692
+ if (!this.sock) throw new Error('WhatsApp is not connected');
693
+
694
+ const jid = this.toWhatsAppJid(chatId);
695
+ await this.sock.sendMessage(jid, {
696
+ react: {
697
+ text: emoji,
698
+ key: {
699
+ remoteJid: jid,
700
+ id: messageId,
701
+ fromMe: false,
702
+ },
703
+ },
704
+ });
705
+ }
706
+
707
+ /**
708
+ * Remove a reaction from a message
709
+ */
710
+ async removeReaction(chatId: string, messageId: string): Promise<void> {
711
+ await this.addReaction(chatId, messageId, ''); // Empty string removes reaction
712
+ }
713
+
714
+ /**
715
+ * Register a message handler
716
+ */
717
+ onMessage(handler: MessageHandler): void {
718
+ this.messageHandlers.push(handler);
719
+ }
720
+
721
+ /**
722
+ * Register a callback query handler (not supported by WhatsApp)
723
+ */
724
+ onCallbackQuery(_handler: CallbackQueryHandler): void {
725
+ // WhatsApp doesn't support inline keyboards/callback queries
726
+ }
727
+
728
+ /**
729
+ * Register an error handler
730
+ */
731
+ onError(handler: ErrorHandler): void {
732
+ this.errorHandlers.push(handler);
733
+ }
734
+
735
+ /**
736
+ * Register a status change handler
737
+ */
738
+ onStatusChange(handler: StatusHandler): void {
739
+ this.statusHandlers.push(handler);
740
+ }
741
+
742
+ /**
743
+ * Register a QR code handler
744
+ */
745
+ onQrCode(handler: QrCodeHandler): void {
746
+ this.qrCodeHandlers.push(handler);
747
+ }
748
+
749
+ /**
750
+ * Get channel info
751
+ */
752
+ async getInfo(): Promise<ChannelInfo> {
753
+ return {
754
+ type: 'whatsapp',
755
+ status: this._status,
756
+ botId: this._selfJid,
757
+ botUsername: this._selfE164,
758
+ botDisplayName: this._selfE164,
759
+ extra: {
760
+ qrCode: this.currentQr,
761
+ hasCredentials: await this.hasCredentials(),
762
+ },
763
+ };
764
+ }
765
+
766
+ /**
767
+ * Logout and clear credentials
768
+ */
769
+ async logout(): Promise<void> {
770
+ await this.disconnect();
771
+ await this.clearCredentials();
772
+ }
773
+
774
+ /**
775
+ * Clear stored credentials
776
+ */
777
+ private async clearCredentials(): Promise<void> {
778
+ try {
779
+ if (fs.existsSync(this.authDir)) {
780
+ const files = fs.readdirSync(this.authDir);
781
+ for (const file of files) {
782
+ fs.unlinkSync(path.join(this.authDir, file));
783
+ }
784
+ }
785
+ } catch (error) {
786
+ console.error('Error clearing WhatsApp credentials:', error);
787
+ }
788
+ }
789
+
790
+ // ============================================================================
791
+ // Private Helper Methods
792
+ // ============================================================================
793
+
794
+ /**
795
+ * Handle incoming message notification
796
+ */
797
+ private async handleIncomingMessage(message: IncomingMessage): Promise<void> {
798
+ for (const handler of this.messageHandlers) {
799
+ try {
800
+ await handler(message);
801
+ } catch (error) {
802
+ console.error('Error in WhatsApp message handler:', error);
803
+ this.handleError(
804
+ error instanceof Error ? error : new Error(String(error)),
805
+ 'messageHandler'
806
+ );
807
+ }
808
+ }
809
+ }
810
+
811
+ /**
812
+ * Handle errors
813
+ */
814
+ private handleError(error: Error, context?: string): void {
815
+ for (const handler of this.errorHandlers) {
816
+ try {
817
+ handler(error, context);
818
+ } catch (e) {
819
+ console.error('Error in error handler:', e);
820
+ }
821
+ }
822
+ }
823
+
824
+ /**
825
+ * Set status and notify handlers
826
+ */
827
+ private setStatus(status: ChannelStatus, error?: Error): void {
828
+ this._status = status;
829
+ for (const handler of this.statusHandlers) {
830
+ try {
831
+ handler(status, error);
832
+ } catch (e) {
833
+ console.error('Error in status handler:', e);
834
+ }
835
+ }
836
+ }
837
+
838
+ /**
839
+ * Attempt reconnection with exponential backoff
840
+ */
841
+ private async attemptReconnection(): Promise<void> {
842
+ if (this.isReconnecting) return;
843
+
844
+ const config = this.DEFAULT_BACKOFF;
845
+
846
+ if (this.backoffAttempt >= config.maxAttempts) {
847
+ console.error(`WhatsApp: Max reconnection attempts (${config.maxAttempts}) reached`);
848
+ this.setStatus('error', new Error('Max reconnection attempts reached'));
849
+ return;
850
+ }
851
+
852
+ this.isReconnecting = true;
853
+ this.backoffAttempt++;
854
+
855
+ const delay = this.calculateBackoffDelay(config);
856
+ console.log(`WhatsApp: Reconnection attempt ${this.backoffAttempt}/${config.maxAttempts} in ${delay}ms`);
857
+
858
+ this.backoffTimer = setTimeout(async () => {
859
+ try {
860
+ this.sock = null;
861
+ this.isReconnecting = false;
862
+ this.setStatus('disconnected');
863
+ await this.connect();
864
+ } catch (error) {
865
+ this.isReconnecting = false;
866
+ console.error('WhatsApp reconnection attempt failed:', error);
867
+ await this.attemptReconnection();
868
+ }
869
+ }, delay);
870
+ }
871
+
872
+ /**
873
+ * Calculate backoff delay with jitter
874
+ */
875
+ private calculateBackoffDelay(config: BackoffConfig): number {
876
+ let delay = config.initialDelay * Math.pow(config.multiplier, this.backoffAttempt - 1);
877
+ delay = Math.min(delay, config.maxDelay);
878
+
879
+ const jitterAmount = delay * config.jitter;
880
+ const jitter = (Math.random() * 2 - 1) * jitterAmount;
881
+ delay = Math.round(delay + jitter);
882
+
883
+ return Math.max(1000, delay);
884
+ }
885
+
886
+ /**
887
+ * Reset backoff state
888
+ */
889
+ private resetBackoff(): void {
890
+ this.backoffAttempt = 0;
891
+ this.isReconnecting = false;
892
+ if (this.backoffTimer) {
893
+ clearTimeout(this.backoffTimer);
894
+ this.backoffTimer = undefined;
895
+ }
896
+ }
897
+
898
+ /**
899
+ * Start deduplication cache cleanup
900
+ */
901
+ private startDedupCleanup(): void {
902
+ this.dedupCleanupTimer = setInterval(() => {
903
+ this.cleanupDedupCache();
904
+ }, this.DEDUP_CACHE_TTL);
905
+ }
906
+
907
+ /**
908
+ * Clean up old dedup cache entries
909
+ */
910
+ private cleanupDedupCache(): void {
911
+ const now = Date.now();
912
+ for (const [key, timestamp] of this.processedMessages) {
913
+ if (now - timestamp > this.DEDUP_CACHE_TTL) {
914
+ this.processedMessages.delete(key);
915
+ }
916
+ }
917
+ }
918
+
919
+ /**
920
+ * Get group metadata with caching
921
+ */
922
+ private async getGroupMeta(jid: string): Promise<{ subject?: string }> {
923
+ const cached = this.groupMetaCache.get(jid);
924
+ if (cached && cached.expires > Date.now()) {
925
+ return cached;
926
+ }
927
+
928
+ try {
929
+ const meta = await this.sock?.groupMetadata(jid);
930
+ const entry = {
931
+ subject: meta?.subject,
932
+ expires: Date.now() + this.GROUP_META_TTL_MS,
933
+ };
934
+ this.groupMetaCache.set(jid, entry);
935
+ return entry;
936
+ } catch {
937
+ return { subject: undefined };
938
+ }
939
+ }
940
+
941
+ /**
942
+ * Convert JID to E.164 phone number format
943
+ */
944
+ private jidToE164(jid: string | null | undefined): string | null {
945
+ if (!jid) return null;
946
+
947
+ // Remove @s.whatsapp.net or @c.us suffix
948
+ const match = jid.match(/^(\d+)@/);
949
+ if (!match) return null;
950
+
951
+ return match[1];
952
+ }
953
+
954
+ /**
955
+ * Convert phone number/chat ID to WhatsApp JID
956
+ */
957
+ private toWhatsAppJid(chatId: string): string {
958
+ // Already a JID
959
+ if (chatId.includes('@')) return chatId;
960
+
961
+ // Group ID
962
+ if (chatId.includes('-')) {
963
+ return `${chatId}@g.us`;
964
+ }
965
+
966
+ // Phone number - remove any non-numeric characters
967
+ const cleaned = chatId.replace(/[^0-9]/g, '');
968
+ return `${cleaned}@s.whatsapp.net`;
969
+ }
970
+
971
+ /**
972
+ * Extract text from WhatsApp message
973
+ */
974
+ private extractText(message: proto.IMessage | null | undefined): string | undefined {
975
+ if (!message) return undefined;
976
+
977
+ // Direct text message
978
+ if (message.conversation) {
979
+ return message.conversation.trim();
980
+ }
981
+
982
+ // Extended text message
983
+ if (message.extendedTextMessage?.text) {
984
+ return message.extendedTextMessage.text.trim();
985
+ }
986
+
987
+ // Image/video/document caption
988
+ const caption =
989
+ message.imageMessage?.caption ||
990
+ message.videoMessage?.caption ||
991
+ message.documentMessage?.caption;
992
+
993
+ if (caption) return caption.trim();
994
+
995
+ return undefined;
996
+ }
997
+
998
+ /**
999
+ * Extract media placeholder from message
1000
+ */
1001
+ private extractMediaPlaceholder(message: proto.IMessage | null | undefined): string | undefined {
1002
+ if (!message) return undefined;
1003
+
1004
+ if (message.imageMessage) return '<media:image>';
1005
+ if (message.videoMessage) return '<media:video>';
1006
+ if (message.audioMessage) return '<media:audio>';
1007
+ if (message.documentMessage) return '<media:document>';
1008
+ if (message.stickerMessage) return '<media:sticker>';
1009
+ if (message.contactMessage) return '<contact>';
1010
+ if (message.locationMessage) return '<location>';
1011
+
1012
+ return undefined;
1013
+ }
1014
+
1015
+ /**
1016
+ * Download audio from a WhatsApp message and return as attachment
1017
+ */
1018
+ private async downloadAudioAttachment(message: proto.IMessage): Promise<MessageAttachment | null> {
1019
+ const audioMessage = message.audioMessage;
1020
+ if (!audioMessage) return null;
1021
+
1022
+ try {
1023
+ console.log('[WhatsApp] Downloading audio message...');
1024
+
1025
+ // Download the audio content
1026
+ const stream = await downloadContentFromMessage(
1027
+ audioMessage as DownloadableMessage,
1028
+ 'audio'
1029
+ );
1030
+
1031
+ // Collect the stream into a buffer
1032
+ const chunks: Buffer[] = [];
1033
+ for await (const chunk of stream) {
1034
+ chunks.push(chunk as Buffer);
1035
+ }
1036
+ const audioBuffer = Buffer.concat(chunks);
1037
+
1038
+ console.log(`[WhatsApp] Downloaded audio: ${audioBuffer.length} bytes`);
1039
+
1040
+ // Determine mime type (usually audio/ogg for voice messages)
1041
+ const mimeType = audioMessage.mimetype || 'audio/ogg; codecs=opus';
1042
+ const isVoiceNote = audioMessage.ptt === true;
1043
+ const fileName = isVoiceNote
1044
+ ? `voice_message_${Date.now()}.ogg`
1045
+ : `audio_${Date.now()}.${this.getAudioExtension(mimeType)}`;
1046
+
1047
+ return {
1048
+ type: 'audio',
1049
+ data: audioBuffer,
1050
+ mimeType,
1051
+ fileName,
1052
+ size: audioBuffer.length,
1053
+ };
1054
+ } catch (error) {
1055
+ console.error('[WhatsApp] Failed to download audio:', error);
1056
+ return null;
1057
+ }
1058
+ }
1059
+
1060
+ /**
1061
+ * Get file extension from mime type
1062
+ */
1063
+ private getAudioExtension(mimeType: string): string {
1064
+ if (mimeType.includes('ogg')) return 'ogg';
1065
+ if (mimeType.includes('mp3') || mimeType.includes('mpeg')) return 'mp3';
1066
+ if (mimeType.includes('wav')) return 'wav';
1067
+ if (mimeType.includes('m4a') || mimeType.includes('mp4')) return 'm4a';
1068
+ if (mimeType.includes('webm')) return 'webm';
1069
+ return 'audio';
1070
+ }
1071
+
1072
+ /**
1073
+ * Get status code from disconnect error
1074
+ */
1075
+ private getStatusCode(err: unknown): number | undefined {
1076
+ if (!err) return undefined;
1077
+
1078
+ const asAny = err as any;
1079
+ return (
1080
+ asAny?.output?.statusCode ||
1081
+ asAny?.status ||
1082
+ undefined
1083
+ );
1084
+ }
1085
+
1086
+ /**
1087
+ * Ensure directory exists
1088
+ */
1089
+ private async ensureDir(dirPath: string): Promise<void> {
1090
+ if (!fs.existsSync(dirPath)) {
1091
+ fs.mkdirSync(dirPath, { recursive: true });
1092
+ }
1093
+ }
1094
+ }
1095
+
1096
+ /**
1097
+ * Create a WhatsApp adapter from configuration
1098
+ */
1099
+ export function createWhatsAppAdapter(config: WhatsAppConfig): WhatsAppAdapter {
1100
+ return new WhatsAppAdapter(config);
1101
+ }