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,1274 @@
1
+ /**
2
+ * Channel Gateway
3
+ *
4
+ * Main entry point for multi-channel messaging support.
5
+ * Manages channel adapters, routing, and sessions.
6
+ */
7
+
8
+ import { BrowserWindow } from 'electron';
9
+ import Database from 'better-sqlite3';
10
+ import { MessageRouter, RouterConfig } from './router';
11
+ import { SecurityManager } from './security';
12
+ import { SessionManager } from './session';
13
+ import {
14
+ ChannelAdapter,
15
+ ChannelType,
16
+ TelegramConfig,
17
+ DiscordConfig,
18
+ SlackConfig,
19
+ WhatsAppConfig,
20
+ ImessageConfig,
21
+ SignalConfig,
22
+ MattermostConfig,
23
+ MatrixConfig,
24
+ TwitchConfig,
25
+ LineConfig,
26
+ BlueBubblesConfig,
27
+ EmailConfig,
28
+ GatewayEventHandler,
29
+ } from './channels/types';
30
+ import { TelegramAdapter, createTelegramAdapter } from './channels/telegram';
31
+ import { DiscordAdapter, createDiscordAdapter } from './channels/discord';
32
+ import { SlackAdapter, createSlackAdapter } from './channels/slack';
33
+ import { WhatsAppAdapter, createWhatsAppAdapter } from './channels/whatsapp';
34
+ import { ImessageAdapter, createImessageAdapter } from './channels/imessage';
35
+ import { SignalAdapter, createSignalAdapter } from './channels/signal';
36
+ import { MattermostAdapter, createMattermostAdapter } from './channels/mattermost';
37
+ import { MatrixAdapter, createMatrixAdapter } from './channels/matrix';
38
+ import { TwitchAdapter, createTwitchAdapter } from './channels/twitch';
39
+ import { LineAdapter, createLineAdapter } from './channels/line';
40
+ import { BlueBubblesAdapter, createBlueBubblesAdapter } from './channels/bluebubbles';
41
+ import { EmailAdapter, createEmailAdapter } from './channels/email';
42
+ import {
43
+ ChannelRepository,
44
+ ChannelUserRepository,
45
+ ChannelSessionRepository,
46
+ ChannelMessageRepository,
47
+ Channel,
48
+ } from '../database/repositories';
49
+ import { AgentDaemon } from '../agent/daemon';
50
+ import { PersonalityManager } from '../settings/personality-manager';
51
+ import {
52
+ getChannelMessage,
53
+ DEFAULT_CHANNEL_CONTEXT,
54
+ type ChannelMessageContext,
55
+ } from '../../shared/channelMessages';
56
+ import { DEFAULT_QUIRKS } from '../../shared/types';
57
+
58
+ export interface GatewayConfig {
59
+ /** Router configuration */
60
+ router?: RouterConfig;
61
+ /** Auto-connect enabled channels on startup */
62
+ autoConnect?: boolean;
63
+ /** Agent daemon for task execution */
64
+ agentDaemon?: AgentDaemon;
65
+ }
66
+
67
+ const DEFAULT_CONFIG: GatewayConfig = {
68
+ autoConnect: true,
69
+ };
70
+
71
+ /**
72
+ * Channel Gateway - Main class for managing multi-channel messaging
73
+ */
74
+ export class ChannelGateway {
75
+ private db: Database.Database;
76
+ private router: MessageRouter;
77
+ private securityManager: SecurityManager;
78
+ private sessionManager: SessionManager;
79
+ private channelRepo: ChannelRepository;
80
+ private userRepo: ChannelUserRepository;
81
+ private sessionRepo: ChannelSessionRepository;
82
+ private messageRepo: ChannelMessageRepository;
83
+ private config: GatewayConfig;
84
+ private initialized = false;
85
+ private agentDaemon?: AgentDaemon;
86
+ private daemonListeners: Array<{ event: string; handler: (...args: any[]) => void }> = [];
87
+
88
+ constructor(db: Database.Database, config: GatewayConfig = {}) {
89
+ this.db = db;
90
+ this.config = { ...DEFAULT_CONFIG, ...config };
91
+
92
+ // Initialize components
93
+ this.router = new MessageRouter(db, config.router, config.agentDaemon);
94
+ this.securityManager = new SecurityManager(db);
95
+ this.sessionManager = new SessionManager(db);
96
+ this.channelRepo = new ChannelRepository(db);
97
+ this.userRepo = new ChannelUserRepository(db);
98
+ this.sessionRepo = new ChannelSessionRepository(db);
99
+ this.messageRepo = new ChannelMessageRepository(db);
100
+
101
+ // Listen for agent daemon events to send responses back to channels
102
+ if (config.agentDaemon) {
103
+ this.agentDaemon = config.agentDaemon;
104
+ this.setupAgentDaemonListeners(config.agentDaemon);
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Get the channel message context from personality settings
110
+ */
111
+ private getMessageContext(): ChannelMessageContext {
112
+ try {
113
+ if (PersonalityManager.isInitialized()) {
114
+ const settings = PersonalityManager.loadSettings();
115
+ return {
116
+ agentName: settings.agentName || 'CoWork',
117
+ userName: settings.relationship?.userName,
118
+ personality: settings.activePersonality || 'professional',
119
+ emojiUsage: settings.responseStyle?.emojiUsage || 'minimal',
120
+ quirks: settings.quirks || DEFAULT_QUIRKS,
121
+ };
122
+ }
123
+ } catch (error) {
124
+ console.error('[ChannelGateway] Failed to load personality settings:', error);
125
+ }
126
+ return DEFAULT_CHANNEL_CONTEXT;
127
+ }
128
+
129
+ /**
130
+ * Set up listeners for agent daemon events
131
+ */
132
+ private setupAgentDaemonListeners(agentDaemon: AgentDaemon): void {
133
+ // Track the last assistant message for each task to send as completion result
134
+ const lastMessages = new Map<string, string>();
135
+
136
+ // Listen for assistant messages (streaming responses)
137
+ // Note: daemon emits { taskId, message } not { taskId, content }
138
+ const onAssistantMessage = (data: { taskId: string; message?: string }) => {
139
+ const message = data.message;
140
+ if (message && message.length > 10) {
141
+ // Keep the BEST (longest substantive) answer, not just the last one
142
+ // This prevents confused step messages from overwriting good answers
143
+ const existingMessage = lastMessages.get(data.taskId);
144
+ const isConfusedMessage = message.toLowerCase().includes("don't have") ||
145
+ message.toLowerCase().includes("please provide") ||
146
+ message.toLowerCase().includes("i cannot") ||
147
+ message.toLowerCase().includes("not available");
148
+
149
+ // Only overwrite if new message is better (longer and not confused)
150
+ if (!existingMessage || (!isConfusedMessage && message.length >= existingMessage.length)) {
151
+ lastMessages.set(data.taskId, message);
152
+ }
153
+
154
+ // Always stream updates to channel (so user sees progress)
155
+ this.router.sendTaskUpdate(data.taskId, message);
156
+ }
157
+ };
158
+ agentDaemon.on('assistant_message', onAssistantMessage);
159
+ this.daemonListeners.push({ event: 'assistant_message', handler: onAssistantMessage });
160
+
161
+ // Listen for task completion
162
+ const onTaskCompleted = (data: { taskId: string; message?: string }) => {
163
+ // Use the last assistant message as the result
164
+ const result = lastMessages.get(data.taskId);
165
+ this.router.handleTaskCompletion(data.taskId, result);
166
+ lastMessages.delete(data.taskId);
167
+ };
168
+ agentDaemon.on('task_completed', onTaskCompleted);
169
+ this.daemonListeners.push({ event: 'task_completed', handler: onTaskCompleted });
170
+
171
+ // Listen for task errors
172
+ // Note: daemon emits { taskId, error } or { taskId, message }
173
+ const onError = (data: { taskId: string; error?: string; message?: string }) => {
174
+ const errorMsg = data.error || data.message || 'Unknown error';
175
+ this.router.handleTaskFailure(data.taskId, errorMsg);
176
+ lastMessages.delete(data.taskId);
177
+ };
178
+ agentDaemon.on('error', onError);
179
+ this.daemonListeners.push({ event: 'error', handler: onError });
180
+
181
+ // Listen for tool errors (individual tool execution failures)
182
+ const onToolError = (data: { taskId: string; tool?: string; error?: string }) => {
183
+ const toolName = data.tool || 'Unknown tool';
184
+ const errorMsg = data.error || 'Unknown error';
185
+ const message = getChannelMessage('toolError', this.getMessageContext(), { tool: toolName, error: errorMsg });
186
+ this.router.sendTaskUpdate(data.taskId, message);
187
+ };
188
+ agentDaemon.on('tool_error', onToolError);
189
+ this.daemonListeners.push({ event: 'tool_error', handler: onToolError });
190
+
191
+ // Listen for follow-up message completion
192
+ // Track if any assistant messages were sent during follow-up
193
+ const followUpMessagesSent = new Map<string, boolean>();
194
+ const originalOnAssistantMessage = onAssistantMessage;
195
+ // Override to track follow-up messages
196
+ agentDaemon.off('assistant_message', onAssistantMessage);
197
+ const trackingAssistantMessage = (data: { taskId: string; message?: string }) => {
198
+ followUpMessagesSent.set(data.taskId, true);
199
+ originalOnAssistantMessage(data);
200
+ };
201
+ agentDaemon.on('assistant_message', trackingAssistantMessage);
202
+ // Update the stored handler
203
+ const assistantIdx = this.daemonListeners.findIndex(l => l.event === 'assistant_message');
204
+ if (assistantIdx >= 0) {
205
+ this.daemonListeners[assistantIdx] = { event: 'assistant_message', handler: trackingAssistantMessage };
206
+ }
207
+
208
+ const onFollowUpCompleted = async (data: { taskId: string }) => {
209
+ // If no assistant messages were sent during the follow-up, send a confirmation
210
+ if (!followUpMessagesSent.get(data.taskId)) {
211
+ const message = getChannelMessage('followUpProcessed', this.getMessageContext());
212
+ this.router.sendTaskUpdate(data.taskId, message);
213
+ }
214
+ followUpMessagesSent.delete(data.taskId);
215
+
216
+ // Send any artifacts (images, screenshots) created during the follow-up
217
+ await this.router.sendArtifacts(data.taskId);
218
+ };
219
+ agentDaemon.on('follow_up_completed', onFollowUpCompleted);
220
+ this.daemonListeners.push({ event: 'follow_up_completed', handler: onFollowUpCompleted });
221
+
222
+ // Listen for follow-up failures
223
+ const onFollowUpFailed = (data: { taskId: string; error?: string }) => {
224
+ const errorMsg = data.error || 'Unknown error';
225
+ const message = getChannelMessage('followUpFailed', this.getMessageContext(), { error: errorMsg });
226
+ this.router.sendTaskUpdate(data.taskId, message);
227
+ followUpMessagesSent.delete(data.taskId);
228
+ };
229
+ agentDaemon.on('follow_up_failed', onFollowUpFailed);
230
+ this.daemonListeners.push({ event: 'follow_up_failed', handler: onFollowUpFailed });
231
+
232
+ // Listen for approval requests - forward to Discord/Telegram
233
+ const onApprovalRequested = (data: { taskId: string; approval: any }) => {
234
+ this.router.sendApprovalRequest(data.taskId, data.approval);
235
+ };
236
+ agentDaemon.on('approval_requested', onApprovalRequested);
237
+ this.daemonListeners.push({ event: 'approval_requested', handler: onApprovalRequested });
238
+ }
239
+
240
+ /**
241
+ * Initialize the gateway
242
+ */
243
+ async initialize(mainWindow?: BrowserWindow): Promise<void> {
244
+ if (this.initialized) return;
245
+
246
+ if (mainWindow) {
247
+ this.router.setMainWindow(mainWindow);
248
+ }
249
+
250
+ // Load and register enabled channels
251
+ await this.loadChannels();
252
+
253
+ // Auto-connect if configured
254
+ if (this.config.autoConnect) {
255
+ await this.router.connectAll();
256
+ }
257
+
258
+ this.initialized = true;
259
+ console.log('Channel Gateway initialized');
260
+ }
261
+
262
+ /**
263
+ * Set the main window for IPC communication
264
+ */
265
+ setMainWindow(window: BrowserWindow): void {
266
+ this.router.setMainWindow(window);
267
+ }
268
+
269
+ /**
270
+ * Shutdown the gateway
271
+ */
272
+ async shutdown(): Promise<void> {
273
+ // Clean up daemon event listeners
274
+ if (this.agentDaemon) {
275
+ for (const { event, handler } of this.daemonListeners) {
276
+ this.agentDaemon.off(event, handler);
277
+ }
278
+ this.daemonListeners = [];
279
+ }
280
+
281
+ await this.router.disconnectAll();
282
+ this.initialized = false;
283
+ console.log('Channel Gateway shutdown');
284
+ }
285
+
286
+ // Channel Management
287
+
288
+ /**
289
+ * Add a new Telegram channel
290
+ */
291
+ async addTelegramChannel(
292
+ name: string,
293
+ botToken: string,
294
+ securityMode: 'open' | 'allowlist' | 'pairing' = 'pairing'
295
+ ): Promise<Channel> {
296
+ // Check if Telegram channel already exists
297
+ const existing = this.channelRepo.findByType('telegram');
298
+ if (existing) {
299
+ throw new Error('Telegram channel already configured. Update or remove it first.');
300
+ }
301
+
302
+ // Create channel record
303
+ const channel = this.channelRepo.create({
304
+ type: 'telegram',
305
+ name,
306
+ enabled: false, // Don't enable until tested
307
+ config: { botToken },
308
+ securityConfig: {
309
+ mode: securityMode,
310
+ pairingCodeTTL: 300, // 5 minutes
311
+ maxPairingAttempts: 5,
312
+ rateLimitPerMinute: 30,
313
+ },
314
+ status: 'disconnected',
315
+ });
316
+
317
+ return channel;
318
+ }
319
+
320
+ /**
321
+ * Add a new Discord channel
322
+ */
323
+ async addDiscordChannel(
324
+ name: string,
325
+ botToken: string,
326
+ applicationId: string,
327
+ guildIds?: string[],
328
+ securityMode: 'open' | 'allowlist' | 'pairing' = 'pairing'
329
+ ): Promise<Channel> {
330
+ // Check if Discord channel already exists
331
+ const existing = this.channelRepo.findByType('discord');
332
+ if (existing) {
333
+ throw new Error('Discord channel already configured. Update or remove it first.');
334
+ }
335
+
336
+ // Create channel record
337
+ const channel = this.channelRepo.create({
338
+ type: 'discord',
339
+ name,
340
+ enabled: false, // Don't enable until tested
341
+ config: { botToken, applicationId, guildIds },
342
+ securityConfig: {
343
+ mode: securityMode,
344
+ pairingCodeTTL: 300, // 5 minutes
345
+ maxPairingAttempts: 5,
346
+ rateLimitPerMinute: 30,
347
+ },
348
+ status: 'disconnected',
349
+ });
350
+
351
+ return channel;
352
+ }
353
+
354
+ /**
355
+ * Add a new Slack channel
356
+ */
357
+ async addSlackChannel(
358
+ name: string,
359
+ botToken: string,
360
+ appToken: string,
361
+ signingSecret?: string,
362
+ securityMode: 'open' | 'allowlist' | 'pairing' = 'pairing'
363
+ ): Promise<Channel> {
364
+ // Check if Slack channel already exists
365
+ const existing = this.channelRepo.findByType('slack');
366
+ if (existing) {
367
+ throw new Error('Slack channel already configured. Update or remove it first.');
368
+ }
369
+
370
+ // Create channel record
371
+ const channel = this.channelRepo.create({
372
+ type: 'slack',
373
+ name,
374
+ enabled: false, // Don't enable until tested
375
+ config: { botToken, appToken, signingSecret },
376
+ securityConfig: {
377
+ mode: securityMode,
378
+ pairingCodeTTL: 300, // 5 minutes
379
+ maxPairingAttempts: 5,
380
+ rateLimitPerMinute: 30,
381
+ },
382
+ status: 'disconnected',
383
+ });
384
+
385
+ return channel;
386
+ }
387
+
388
+ /**
389
+ * Add a new WhatsApp channel
390
+ */
391
+ async addWhatsAppChannel(
392
+ name: string,
393
+ allowedNumbers?: string[],
394
+ securityMode: 'open' | 'allowlist' | 'pairing' = 'pairing',
395
+ selfChatMode: boolean = true,
396
+ responsePrefix: string = '🤖'
397
+ ): Promise<Channel> {
398
+ // Check if WhatsApp channel already exists
399
+ const existing = this.channelRepo.findByType('whatsapp');
400
+ if (existing) {
401
+ throw new Error('WhatsApp channel already configured. Update or remove it first.');
402
+ }
403
+
404
+ // Create channel record
405
+ const channel = this.channelRepo.create({
406
+ type: 'whatsapp',
407
+ name,
408
+ enabled: false, // Don't enable until QR code is scanned
409
+ config: {
410
+ allowedNumbers,
411
+ selfChatMode,
412
+ responsePrefix,
413
+ },
414
+ securityConfig: {
415
+ mode: securityMode,
416
+ allowedUsers: allowedNumbers,
417
+ pairingCodeTTL: 300, // 5 minutes
418
+ maxPairingAttempts: 5,
419
+ rateLimitPerMinute: 30,
420
+ },
421
+ status: 'disconnected',
422
+ });
423
+
424
+ return channel;
425
+ }
426
+
427
+ /**
428
+ * Add a new iMessage channel
429
+ */
430
+ async addImessageChannel(
431
+ name: string,
432
+ cliPath?: string,
433
+ dbPath?: string,
434
+ allowedContacts?: string[],
435
+ securityMode: 'open' | 'allowlist' | 'pairing' = 'pairing',
436
+ dmPolicy: 'open' | 'allowlist' | 'pairing' | 'disabled' = 'pairing',
437
+ groupPolicy: 'open' | 'allowlist' | 'disabled' = 'allowlist'
438
+ ): Promise<Channel> {
439
+ // Check if iMessage channel already exists
440
+ const existing = this.channelRepo.findByType('imessage');
441
+ if (existing) {
442
+ throw new Error('iMessage channel already configured. Update or remove it first.');
443
+ }
444
+
445
+ // Create channel record
446
+ const channel = this.channelRepo.create({
447
+ type: 'imessage',
448
+ name,
449
+ enabled: false, // Don't enable until connected
450
+ config: {
451
+ cliPath,
452
+ dbPath,
453
+ allowedContacts,
454
+ dmPolicy,
455
+ groupPolicy,
456
+ },
457
+ securityConfig: {
458
+ mode: securityMode,
459
+ allowedUsers: allowedContacts,
460
+ pairingCodeTTL: 300, // 5 minutes
461
+ maxPairingAttempts: 5,
462
+ rateLimitPerMinute: 30,
463
+ },
464
+ status: 'disconnected',
465
+ });
466
+
467
+ return channel;
468
+ }
469
+
470
+ /**
471
+ * Add a new Signal channel
472
+ */
473
+ async addSignalChannel(
474
+ name: string,
475
+ phoneNumber: string,
476
+ dataDir?: string,
477
+ securityMode: 'open' | 'allowlist' | 'pairing' = 'pairing',
478
+ mode: 'native' | 'daemon' = 'native',
479
+ trustMode: 'tofu' | 'always' | 'manual' = 'tofu',
480
+ dmPolicy: 'open' | 'allowlist' | 'pairing' | 'disabled' = 'pairing',
481
+ groupPolicy: 'open' | 'allowlist' | 'disabled' = 'allowlist',
482
+ sendReadReceipts: boolean = true,
483
+ sendTypingIndicators: boolean = true
484
+ ): Promise<Channel> {
485
+ // Check if Signal channel already exists
486
+ const existing = this.channelRepo.findByType('signal');
487
+ if (existing) {
488
+ throw new Error('Signal channel already configured. Update or remove it first.');
489
+ }
490
+
491
+ // Create channel record
492
+ const channel = this.channelRepo.create({
493
+ type: 'signal',
494
+ name,
495
+ enabled: false, // Don't enable until connected
496
+ config: {
497
+ phoneNumber,
498
+ dataDir,
499
+ mode,
500
+ trustMode,
501
+ dmPolicy,
502
+ groupPolicy,
503
+ sendReadReceipts,
504
+ sendTypingIndicators,
505
+ },
506
+ securityConfig: {
507
+ mode: securityMode,
508
+ allowedUsers: [],
509
+ pairingCodeTTL: 300, // 5 minutes
510
+ maxPairingAttempts: 5,
511
+ rateLimitPerMinute: 30,
512
+ },
513
+ status: 'disconnected',
514
+ });
515
+
516
+ return channel;
517
+ }
518
+
519
+ /**
520
+ * Add a new Mattermost channel
521
+ */
522
+ async addMattermostChannel(
523
+ name: string,
524
+ serverUrl: string,
525
+ token: string,
526
+ teamId?: string,
527
+ securityMode: 'open' | 'allowlist' | 'pairing' = 'pairing'
528
+ ): Promise<Channel> {
529
+ // Check if Mattermost channel already exists
530
+ const existing = this.channelRepo.findByType('mattermost');
531
+ if (existing) {
532
+ throw new Error('Mattermost channel already configured. Update or remove it first.');
533
+ }
534
+
535
+ // Create channel record
536
+ const channel = this.channelRepo.create({
537
+ type: 'mattermost',
538
+ name,
539
+ enabled: false, // Don't enable until connected
540
+ config: {
541
+ serverUrl,
542
+ token,
543
+ teamId,
544
+ },
545
+ securityConfig: {
546
+ mode: securityMode,
547
+ allowedUsers: [],
548
+ pairingCodeTTL: 300, // 5 minutes
549
+ maxPairingAttempts: 5,
550
+ rateLimitPerMinute: 30,
551
+ },
552
+ status: 'disconnected',
553
+ });
554
+
555
+ return channel;
556
+ }
557
+
558
+ /**
559
+ * Add a new Matrix channel
560
+ */
561
+ async addMatrixChannel(
562
+ name: string,
563
+ homeserver: string,
564
+ userId: string,
565
+ accessToken: string,
566
+ deviceId?: string,
567
+ roomIds?: string[],
568
+ securityMode: 'open' | 'allowlist' | 'pairing' = 'pairing'
569
+ ): Promise<Channel> {
570
+ // Check if Matrix channel already exists
571
+ const existing = this.channelRepo.findByType('matrix');
572
+ if (existing) {
573
+ throw new Error('Matrix channel already configured. Update or remove it first.');
574
+ }
575
+
576
+ // Create channel record
577
+ const channel = this.channelRepo.create({
578
+ type: 'matrix',
579
+ name,
580
+ enabled: false, // Don't enable until connected
581
+ config: {
582
+ homeserver,
583
+ userId,
584
+ accessToken,
585
+ deviceId,
586
+ roomIds,
587
+ },
588
+ securityConfig: {
589
+ mode: securityMode,
590
+ allowedUsers: [],
591
+ pairingCodeTTL: 300, // 5 minutes
592
+ maxPairingAttempts: 5,
593
+ rateLimitPerMinute: 30,
594
+ },
595
+ status: 'disconnected',
596
+ });
597
+
598
+ return channel;
599
+ }
600
+
601
+ /**
602
+ * Add a new Twitch channel
603
+ */
604
+ async addTwitchChannel(
605
+ name: string,
606
+ username: string,
607
+ oauthToken: string,
608
+ channels: string[],
609
+ allowWhispers: boolean = false,
610
+ securityMode: 'open' | 'allowlist' | 'pairing' = 'pairing'
611
+ ): Promise<Channel> {
612
+ // Check if Twitch channel already exists
613
+ const existing = this.channelRepo.findByType('twitch');
614
+ if (existing) {
615
+ throw new Error('Twitch channel already configured. Update or remove it first.');
616
+ }
617
+
618
+ // Create channel record
619
+ const channel = this.channelRepo.create({
620
+ type: 'twitch',
621
+ name,
622
+ enabled: false, // Don't enable until connected
623
+ config: {
624
+ username,
625
+ oauthToken,
626
+ channels,
627
+ allowWhispers,
628
+ },
629
+ securityConfig: {
630
+ mode: securityMode,
631
+ allowedUsers: [],
632
+ pairingCodeTTL: 300, // 5 minutes
633
+ maxPairingAttempts: 5,
634
+ rateLimitPerMinute: 30,
635
+ },
636
+ status: 'disconnected',
637
+ });
638
+
639
+ return channel;
640
+ }
641
+
642
+ /**
643
+ * Add a new LINE channel
644
+ */
645
+ async addLineChannel(
646
+ name: string,
647
+ channelAccessToken: string,
648
+ channelSecret: string,
649
+ webhookPort: number = 3100,
650
+ securityMode: 'open' | 'allowlist' | 'pairing' = 'pairing'
651
+ ): Promise<Channel> {
652
+ // Check if LINE channel already exists
653
+ const existing = this.channelRepo.findByType('line');
654
+ if (existing) {
655
+ throw new Error('LINE channel already configured. Update or remove it first.');
656
+ }
657
+
658
+ // Create channel record
659
+ const channel = this.channelRepo.create({
660
+ type: 'line',
661
+ name,
662
+ enabled: false, // Don't enable until connected
663
+ config: {
664
+ channelAccessToken,
665
+ channelSecret,
666
+ webhookPort,
667
+ },
668
+ securityConfig: {
669
+ mode: securityMode,
670
+ allowedUsers: [],
671
+ pairingCodeTTL: 300,
672
+ maxPairingAttempts: 5,
673
+ rateLimitPerMinute: 30,
674
+ },
675
+ status: 'disconnected',
676
+ });
677
+
678
+ return channel;
679
+ }
680
+
681
+ /**
682
+ * Add a new BlueBubbles channel
683
+ */
684
+ async addBlueBubblesChannel(
685
+ name: string,
686
+ serverUrl: string,
687
+ password: string,
688
+ webhookPort: number = 3101,
689
+ allowedContacts?: string[],
690
+ securityMode: 'open' | 'allowlist' | 'pairing' = 'pairing'
691
+ ): Promise<Channel> {
692
+ // Check if BlueBubbles channel already exists
693
+ const existing = this.channelRepo.findByType('bluebubbles');
694
+ if (existing) {
695
+ throw new Error('BlueBubbles channel already configured. Update or remove it first.');
696
+ }
697
+
698
+ // Create channel record
699
+ const channel = this.channelRepo.create({
700
+ type: 'bluebubbles',
701
+ name,
702
+ enabled: false, // Don't enable until connected
703
+ config: {
704
+ serverUrl,
705
+ password,
706
+ webhookPort,
707
+ allowedContacts,
708
+ },
709
+ securityConfig: {
710
+ mode: securityMode,
711
+ allowedUsers: allowedContacts || [],
712
+ pairingCodeTTL: 300,
713
+ maxPairingAttempts: 5,
714
+ rateLimitPerMinute: 30,
715
+ },
716
+ status: 'disconnected',
717
+ });
718
+
719
+ return channel;
720
+ }
721
+
722
+ /**
723
+ * Add a new Email channel
724
+ */
725
+ async addEmailChannel(
726
+ name: string,
727
+ email: string,
728
+ password: string,
729
+ imapHost: string,
730
+ smtpHost: string,
731
+ displayName?: string,
732
+ allowedSenders?: string[],
733
+ subjectFilter?: string,
734
+ securityMode: 'open' | 'allowlist' | 'pairing' = 'pairing'
735
+ ): Promise<Channel> {
736
+ // Check if Email channel already exists
737
+ const existing = this.channelRepo.findByType('email');
738
+ if (existing) {
739
+ throw new Error('Email channel already configured. Update or remove it first.');
740
+ }
741
+
742
+ // Create channel record
743
+ const channel = this.channelRepo.create({
744
+ type: 'email',
745
+ name,
746
+ enabled: false, // Don't enable until connected
747
+ config: {
748
+ email,
749
+ password,
750
+ imapHost,
751
+ smtpHost,
752
+ displayName,
753
+ allowedSenders,
754
+ subjectFilter,
755
+ },
756
+ securityConfig: {
757
+ mode: securityMode,
758
+ allowedUsers: allowedSenders || [],
759
+ pairingCodeTTL: 300,
760
+ maxPairingAttempts: 5,
761
+ rateLimitPerMinute: 30,
762
+ },
763
+ status: 'disconnected',
764
+ });
765
+
766
+ return channel;
767
+ }
768
+
769
+ /**
770
+ * Update a channel configuration
771
+ */
772
+ updateChannel(channelId: string, updates: Partial<Channel>): void {
773
+ this.channelRepo.update(channelId, updates);
774
+ }
775
+
776
+ /**
777
+ * Enable a channel and connect
778
+ */
779
+ async enableChannel(channelId: string): Promise<void> {
780
+ const channel = this.channelRepo.findById(channelId);
781
+ if (!channel) {
782
+ throw new Error('Channel not found');
783
+ }
784
+
785
+ // Create and register adapter if not already done
786
+ let adapter = this.router.getAdapter(channel.type as ChannelType);
787
+ if (!adapter) {
788
+ adapter = this.createAdapterForChannel(channel);
789
+ this.router.registerAdapter(adapter);
790
+ }
791
+
792
+ // Update channel state
793
+ this.channelRepo.update(channelId, { enabled: true });
794
+
795
+ // Connect
796
+ await adapter.connect();
797
+ }
798
+
799
+ /**
800
+ * Disable a channel and disconnect
801
+ */
802
+ async disableChannel(channelId: string): Promise<void> {
803
+ const channel = this.channelRepo.findById(channelId);
804
+ if (!channel) {
805
+ throw new Error('Channel not found');
806
+ }
807
+
808
+ const adapter = this.router.getAdapter(channel.type as ChannelType);
809
+ if (adapter) {
810
+ await adapter.disconnect();
811
+ }
812
+
813
+ this.channelRepo.update(channelId, { enabled: false, status: 'disconnected' });
814
+ }
815
+
816
+ /**
817
+ * Enable WhatsApp channel and set up QR code forwarding
818
+ * This method connects the WhatsApp adapter and forwards QR codes to the renderer
819
+ */
820
+ async enableWhatsAppWithQRForwarding(channelId: string): Promise<void> {
821
+ const channel = this.channelRepo.findById(channelId);
822
+ if (!channel || channel.type !== 'whatsapp') {
823
+ throw new Error('WhatsApp channel not found');
824
+ }
825
+
826
+ // Create and register adapter if not already done
827
+ let adapter = this.router.getAdapter('whatsapp') as WhatsAppAdapter | undefined;
828
+ if (!adapter) {
829
+ adapter = this.createAdapterForChannel(channel) as WhatsAppAdapter;
830
+ this.router.registerAdapter(adapter);
831
+ }
832
+
833
+ // Set up QR code forwarding to renderer
834
+ const mainWindow = this.router.getMainWindow();
835
+ if (mainWindow && !mainWindow.isDestroyed()) {
836
+ adapter.onQrCode((qr: string) => {
837
+ console.log('WhatsApp QR code received, forwarding to renderer');
838
+ if (!mainWindow.isDestroyed()) {
839
+ mainWindow.webContents.send('whatsapp:qr-code', qr);
840
+ }
841
+ });
842
+
843
+ adapter.onStatusChange((status, error) => {
844
+ console.log(`WhatsApp status changed to: ${status}`);
845
+ if (!mainWindow.isDestroyed()) {
846
+ mainWindow.webContents.send('whatsapp:status', { status, error: error?.message });
847
+ if (status === 'connected') {
848
+ mainWindow.webContents.send('whatsapp:connected');
849
+ // Update channel status in database
850
+ this.channelRepo.update(channelId, {
851
+ enabled: true,
852
+ status: 'connected',
853
+ botUsername: adapter?.botUsername,
854
+ });
855
+ } else if (status === 'error') {
856
+ this.channelRepo.update(channelId, { status: 'error' });
857
+ } else if (status === 'disconnected') {
858
+ this.channelRepo.update(channelId, { status: 'disconnected' });
859
+ }
860
+ }
861
+ });
862
+ }
863
+
864
+ // Update channel state to connecting
865
+ this.channelRepo.update(channelId, { enabled: true, status: 'connecting' });
866
+
867
+ // Connect (this will trigger QR code generation)
868
+ await adapter.connect();
869
+ }
870
+
871
+ /**
872
+ * Get WhatsApp channel info including QR code
873
+ */
874
+ async getWhatsAppInfo(): Promise<{ qrCode?: string; phoneNumber?: string; status?: string }> {
875
+ const channel = this.channelRepo.findByType('whatsapp');
876
+ if (!channel) {
877
+ return {};
878
+ }
879
+
880
+ const adapter = this.router.getAdapter('whatsapp') as WhatsAppAdapter | undefined;
881
+ if (!adapter) {
882
+ return { status: channel.status };
883
+ }
884
+
885
+ return {
886
+ qrCode: adapter.qrCode,
887
+ phoneNumber: adapter.botUsername,
888
+ status: adapter.status,
889
+ };
890
+ }
891
+
892
+ /**
893
+ * Logout from WhatsApp and clear credentials
894
+ */
895
+ async whatsAppLogout(): Promise<void> {
896
+ const adapter = this.router.getAdapter('whatsapp') as WhatsAppAdapter | undefined;
897
+ if (adapter) {
898
+ await adapter.logout();
899
+ }
900
+
901
+ const channel = this.channelRepo.findByType('whatsapp');
902
+ if (channel) {
903
+ this.channelRepo.update(channel.id, { enabled: false, status: 'disconnected', botUsername: undefined });
904
+ }
905
+ }
906
+
907
+ /**
908
+ * Remove a channel
909
+ */
910
+ async removeChannel(channelId: string): Promise<void> {
911
+ await this.disableChannel(channelId);
912
+
913
+ // Delete associated data first (to avoid foreign key constraint errors)
914
+ this.messageRepo.deleteByChannelId(channelId);
915
+ this.sessionRepo.deleteByChannelId(channelId);
916
+ this.userRepo.deleteByChannelId(channelId);
917
+
918
+ // Now delete the channel
919
+ this.channelRepo.delete(channelId);
920
+ }
921
+
922
+ /**
923
+ * Test a channel connection without enabling it
924
+ */
925
+ async testChannel(channelId: string): Promise<{ success: boolean; error?: string; botUsername?: string }> {
926
+ const channel = this.channelRepo.findById(channelId);
927
+ if (!channel) {
928
+ return { success: false, error: 'Channel not found' };
929
+ }
930
+
931
+ try {
932
+ const adapter = this.createAdapterForChannel(channel);
933
+ await adapter.connect();
934
+ const info = await adapter.getInfo();
935
+ await adapter.disconnect();
936
+
937
+ return {
938
+ success: true,
939
+ botUsername: info.botUsername,
940
+ };
941
+ } catch (error) {
942
+ return {
943
+ success: false,
944
+ error: error instanceof Error ? error.message : String(error),
945
+ };
946
+ }
947
+ }
948
+
949
+ /**
950
+ * Get all channels
951
+ */
952
+ getChannels(): Channel[] {
953
+ return this.channelRepo.findAll();
954
+ }
955
+
956
+ /**
957
+ * Get a channel by ID
958
+ */
959
+ getChannel(channelId: string): Channel | undefined {
960
+ return this.channelRepo.findById(channelId);
961
+ }
962
+
963
+ /**
964
+ * Get channel by type
965
+ */
966
+ getChannelByType(type: string): Channel | undefined {
967
+ return this.channelRepo.findByType(type);
968
+ }
969
+
970
+ // User Management
971
+
972
+ /**
973
+ * Generate a pairing code for a user
974
+ */
975
+ generatePairingCode(channelId: string, userId?: string, displayName?: string): string {
976
+ const channel = this.channelRepo.findById(channelId);
977
+ if (!channel) {
978
+ throw new Error('Channel not found');
979
+ }
980
+ return this.securityManager.generatePairingCode(channel, userId, displayName);
981
+ }
982
+
983
+ /**
984
+ * Grant access to a user
985
+ */
986
+ grantUserAccess(channelId: string, userId: string, displayName?: string): void {
987
+ this.securityManager.grantAccess(channelId, userId, displayName);
988
+ }
989
+
990
+ /**
991
+ * Revoke user access
992
+ */
993
+ revokeUserAccess(channelId: string, userId: string): void {
994
+ this.securityManager.revokeAccess(channelId, userId);
995
+ }
996
+
997
+ /**
998
+ * Get users for a channel
999
+ * Automatically cleans up expired pending pairing entries
1000
+ */
1001
+ getChannelUsers(channelId: string): ReturnType<typeof this.userRepo.findByChannelId> {
1002
+ // Use securityManager to trigger cleanup of expired pending entries
1003
+ return this.securityManager.getChannelUsers(channelId);
1004
+ }
1005
+
1006
+ // Messaging
1007
+
1008
+ /**
1009
+ * Send a message to a channel chat
1010
+ */
1011
+ async sendMessage(
1012
+ channelType: ChannelType,
1013
+ chatId: string,
1014
+ text: string,
1015
+ options?: { replyTo?: string; parseMode?: 'text' | 'markdown' | 'html' }
1016
+ ): Promise<string> {
1017
+ return this.router.sendMessage(channelType, {
1018
+ chatId,
1019
+ text,
1020
+ replyTo: options?.replyTo,
1021
+ parseMode: options?.parseMode,
1022
+ });
1023
+ }
1024
+
1025
+ /**
1026
+ * Send a message to a session's chat
1027
+ */
1028
+ async sendMessageToSession(
1029
+ sessionId: string,
1030
+ text: string,
1031
+ options?: { replyTo?: string; parseMode?: 'text' | 'markdown' | 'html' }
1032
+ ): Promise<string | null> {
1033
+ const session = this.sessionManager.getSession(sessionId);
1034
+ if (!session) {
1035
+ console.error('Session not found:', sessionId);
1036
+ return null;
1037
+ }
1038
+
1039
+ const channel = this.channelRepo.findById(session.channelId);
1040
+ if (!channel) {
1041
+ console.error('Channel not found:', session.channelId);
1042
+ return null;
1043
+ }
1044
+
1045
+ return this.router.sendMessage(channel.type as ChannelType, {
1046
+ chatId: session.chatId,
1047
+ text,
1048
+ replyTo: options?.replyTo,
1049
+ parseMode: options?.parseMode,
1050
+ });
1051
+ }
1052
+
1053
+ // Events
1054
+
1055
+ /**
1056
+ * Register an event handler
1057
+ */
1058
+ onEvent(handler: GatewayEventHandler): void {
1059
+ this.router.onEvent(handler);
1060
+ }
1061
+
1062
+ // Task response methods
1063
+
1064
+ /**
1065
+ * Send a task update to the channel
1066
+ */
1067
+ async sendTaskUpdate(taskId: string, text: string): Promise<void> {
1068
+ return this.router.sendTaskUpdate(taskId, text);
1069
+ }
1070
+
1071
+ /**
1072
+ * Handle task completion
1073
+ */
1074
+ async handleTaskCompletion(taskId: string, result?: string): Promise<void> {
1075
+ return this.router.handleTaskCompletion(taskId, result);
1076
+ }
1077
+
1078
+ /**
1079
+ * Handle task failure
1080
+ */
1081
+ async handleTaskFailure(taskId: string, error: string): Promise<void> {
1082
+ return this.router.handleTaskFailure(taskId, error);
1083
+ }
1084
+
1085
+ // Private methods
1086
+
1087
+ /**
1088
+ * Load and register channel adapters
1089
+ */
1090
+ private async loadChannels(): Promise<void> {
1091
+ const channels = this.channelRepo.findAll();
1092
+
1093
+ for (const channel of channels) {
1094
+ try {
1095
+ const adapter = this.createAdapterForChannel(channel);
1096
+ this.router.registerAdapter(adapter);
1097
+ } catch (error) {
1098
+ console.error(`Failed to create adapter for channel ${channel.type}:`, error);
1099
+ }
1100
+ }
1101
+ }
1102
+
1103
+ /**
1104
+ * Create an adapter for a channel
1105
+ */
1106
+ private createAdapterForChannel(channel: Channel): ChannelAdapter {
1107
+ switch (channel.type) {
1108
+ case 'telegram':
1109
+ return createTelegramAdapter({
1110
+ enabled: channel.enabled,
1111
+ botToken: channel.config.botToken as string,
1112
+ webhookUrl: channel.config.webhookUrl as string | undefined,
1113
+ });
1114
+
1115
+ case 'discord':
1116
+ return createDiscordAdapter({
1117
+ enabled: channel.enabled,
1118
+ botToken: channel.config.botToken as string,
1119
+ applicationId: channel.config.applicationId as string,
1120
+ guildIds: channel.config.guildIds as string[] | undefined,
1121
+ });
1122
+
1123
+ case 'slack':
1124
+ return createSlackAdapter({
1125
+ enabled: channel.enabled,
1126
+ botToken: channel.config.botToken as string,
1127
+ appToken: channel.config.appToken as string,
1128
+ signingSecret: channel.config.signingSecret as string | undefined,
1129
+ });
1130
+
1131
+ case 'whatsapp':
1132
+ return createWhatsAppAdapter({
1133
+ enabled: channel.enabled,
1134
+ allowedNumbers: channel.config.allowedNumbers as string[] | undefined,
1135
+ printQrToTerminal: true, // For debugging
1136
+ selfChatMode: channel.config.selfChatMode as boolean | undefined ?? true,
1137
+ responsePrefix: channel.config.responsePrefix as string | undefined ?? '🤖',
1138
+ });
1139
+
1140
+ case 'imessage':
1141
+ return createImessageAdapter({
1142
+ enabled: channel.enabled,
1143
+ cliPath: channel.config.cliPath as string | undefined,
1144
+ dbPath: channel.config.dbPath as string | undefined,
1145
+ dmPolicy: channel.config.dmPolicy as 'open' | 'allowlist' | 'pairing' | 'disabled' | undefined,
1146
+ groupPolicy: channel.config.groupPolicy as 'open' | 'allowlist' | 'disabled' | undefined,
1147
+ allowedContacts: channel.config.allowedContacts as string[] | undefined,
1148
+ responsePrefix: channel.config.responsePrefix as string | undefined,
1149
+ });
1150
+
1151
+ case 'signal':
1152
+ return createSignalAdapter({
1153
+ enabled: channel.enabled,
1154
+ phoneNumber: channel.config.phoneNumber as string,
1155
+ cliPath: channel.config.cliPath as string | undefined,
1156
+ dataDir: channel.config.dataDir as string | undefined,
1157
+ mode: channel.config.mode as 'native' | 'daemon' | undefined,
1158
+ socketPath: channel.config.socketPath as string | undefined,
1159
+ trustMode: channel.config.trustMode as 'tofu' | 'always' | 'manual' | undefined,
1160
+ dmPolicy: channel.config.dmPolicy as 'open' | 'allowlist' | 'pairing' | 'disabled' | undefined,
1161
+ groupPolicy: channel.config.groupPolicy as 'open' | 'allowlist' | 'disabled' | undefined,
1162
+ allowedNumbers: channel.config.allowedNumbers as string[] | undefined,
1163
+ sendReadReceipts: channel.config.sendReadReceipts as boolean | undefined,
1164
+ sendTypingIndicators: channel.config.sendTypingIndicators as boolean | undefined,
1165
+ responsePrefix: channel.config.responsePrefix as string | undefined,
1166
+ });
1167
+
1168
+ case 'mattermost':
1169
+ return createMattermostAdapter({
1170
+ enabled: channel.enabled,
1171
+ serverUrl: channel.config.serverUrl as string,
1172
+ token: channel.config.token as string,
1173
+ teamId: channel.config.teamId as string | undefined,
1174
+ responsePrefix: channel.config.responsePrefix as string | undefined,
1175
+ });
1176
+
1177
+ case 'matrix':
1178
+ return createMatrixAdapter({
1179
+ enabled: channel.enabled,
1180
+ homeserver: channel.config.homeserver as string,
1181
+ userId: channel.config.userId as string,
1182
+ accessToken: channel.config.accessToken as string,
1183
+ deviceId: channel.config.deviceId as string | undefined,
1184
+ roomIds: channel.config.roomIds as string[] | undefined,
1185
+ sendTypingIndicators: channel.config.sendTypingIndicators as boolean | undefined,
1186
+ sendReadReceipts: channel.config.sendReadReceipts as boolean | undefined,
1187
+ responsePrefix: channel.config.responsePrefix as string | undefined,
1188
+ });
1189
+
1190
+ case 'twitch':
1191
+ return createTwitchAdapter({
1192
+ enabled: channel.enabled,
1193
+ username: channel.config.username as string,
1194
+ oauthToken: channel.config.oauthToken as string,
1195
+ channels: channel.config.channels as string[],
1196
+ allowWhispers: channel.config.allowWhispers as boolean | undefined,
1197
+ responsePrefix: channel.config.responsePrefix as string | undefined,
1198
+ });
1199
+
1200
+ case 'line':
1201
+ return createLineAdapter({
1202
+ enabled: channel.enabled,
1203
+ channelAccessToken: channel.config.channelAccessToken as string,
1204
+ channelSecret: channel.config.channelSecret as string,
1205
+ webhookPort: channel.config.webhookPort as number | undefined,
1206
+ webhookPath: channel.config.webhookPath as string | undefined,
1207
+ responsePrefix: channel.config.responsePrefix as string | undefined,
1208
+ });
1209
+
1210
+ case 'bluebubbles':
1211
+ return createBlueBubblesAdapter({
1212
+ enabled: channel.enabled,
1213
+ serverUrl: channel.config.serverUrl as string,
1214
+ password: channel.config.password as string,
1215
+ webhookPort: channel.config.webhookPort as number | undefined,
1216
+ webhookPath: channel.config.webhookPath as string | undefined,
1217
+ pollInterval: channel.config.pollInterval as number | undefined,
1218
+ allowedContacts: channel.config.allowedContacts as string[] | undefined,
1219
+ responsePrefix: channel.config.responsePrefix as string | undefined,
1220
+ });
1221
+
1222
+ case 'email':
1223
+ return createEmailAdapter({
1224
+ enabled: channel.enabled,
1225
+ imapHost: channel.config.imapHost as string,
1226
+ imapPort: channel.config.imapPort as number | undefined,
1227
+ imapSecure: channel.config.imapSecure as boolean | undefined,
1228
+ smtpHost: channel.config.smtpHost as string,
1229
+ smtpPort: channel.config.smtpPort as number | undefined,
1230
+ smtpSecure: channel.config.smtpSecure as boolean | undefined,
1231
+ email: channel.config.email as string,
1232
+ password: channel.config.password as string,
1233
+ displayName: channel.config.displayName as string | undefined,
1234
+ mailbox: channel.config.mailbox as string | undefined,
1235
+ pollInterval: channel.config.pollInterval as number | undefined,
1236
+ markAsRead: channel.config.markAsRead as boolean | undefined,
1237
+ allowedSenders: channel.config.allowedSenders as string[] | undefined,
1238
+ subjectFilter: channel.config.subjectFilter as string | undefined,
1239
+ responsePrefix: channel.config.responsePrefix as string | undefined,
1240
+ });
1241
+
1242
+ default:
1243
+ throw new Error(`Unsupported channel type: ${channel.type}`);
1244
+ }
1245
+ }
1246
+ }
1247
+
1248
+ // Re-export types and components
1249
+ export * from './channels/types';
1250
+ export * from './router';
1251
+ export * from './session';
1252
+ export * from './security';
1253
+ export * from './channel-registry';
1254
+ export { TelegramAdapter, createTelegramAdapter } from './channels/telegram';
1255
+ export { DiscordAdapter, createDiscordAdapter } from './channels/discord';
1256
+ export { SlackAdapter, createSlackAdapter } from './channels/slack';
1257
+ export { WhatsAppAdapter, createWhatsAppAdapter } from './channels/whatsapp';
1258
+ export { ImessageAdapter, createImessageAdapter } from './channels/imessage';
1259
+ export { SignalAdapter, createSignalAdapter } from './channels/signal';
1260
+ export { SignalClient } from './channels/signal-client';
1261
+ export { MattermostAdapter, createMattermostAdapter } from './channels/mattermost';
1262
+ export { MattermostClient } from './channels/mattermost-client';
1263
+ export { MatrixAdapter, createMatrixAdapter } from './channels/matrix';
1264
+ export { MatrixClient } from './channels/matrix-client';
1265
+ export { TwitchAdapter, createTwitchAdapter } from './channels/twitch';
1266
+ export { TwitchClient } from './channels/twitch-client';
1267
+ export { LineAdapter, createLineAdapter } from './channels/line';
1268
+ export { LineClient } from './channels/line-client';
1269
+ export { BlueBubblesAdapter, createBlueBubblesAdapter } from './channels/bluebubbles';
1270
+ export { BlueBubblesClient } from './channels/bluebubbles-client';
1271
+ export { EmailAdapter, createEmailAdapter } from './channels/email';
1272
+ export { EmailClient } from './channels/email-client';
1273
+ export { TunnelManager, getAvailableTunnelProviders, createAutoTunnel } from './tunnel';
1274
+ export type { TunnelProvider, TunnelStatus, TunnelConfig, TunnelInfo } from './tunnel';