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,1150 @@
1
+ /**
2
+ * Discord Channel Adapter
3
+ *
4
+ * Implements the ChannelAdapter interface using discord.js for Discord Bot API.
5
+ * Supports slash commands, direct messages, button components, embeds, and threads.
6
+ */
7
+
8
+ import {
9
+ Client,
10
+ GatewayIntentBits,
11
+ Partials,
12
+ Events,
13
+ Message,
14
+ REST,
15
+ Routes,
16
+ SlashCommandBuilder,
17
+ AttachmentBuilder,
18
+ ChatInputCommandInteraction,
19
+ TextChannel,
20
+ DMChannel,
21
+ ThreadChannel,
22
+ ChannelType as DiscordChannelType,
23
+ ActionRowBuilder,
24
+ ButtonBuilder,
25
+ ButtonStyle,
26
+ ButtonInteraction,
27
+ EmbedBuilder,
28
+ ColorResolvable,
29
+ StringSelectMenuBuilder,
30
+ StringSelectMenuInteraction,
31
+ } from 'discord.js';
32
+ import * as fs from 'fs';
33
+ import * as path from 'path';
34
+ import {
35
+ ChannelAdapter,
36
+ ChannelStatus,
37
+ IncomingMessage,
38
+ OutgoingMessage,
39
+ MessageHandler,
40
+ ErrorHandler,
41
+ StatusHandler,
42
+ ChannelInfo,
43
+ DiscordConfig,
44
+ CallbackQuery,
45
+ CallbackQueryHandler,
46
+ InlineKeyboardButton,
47
+ Poll,
48
+ SelectMenu,
49
+ SelectMenuHandler,
50
+ } from './types';
51
+
52
+ /**
53
+ * Embed color constants for different message types
54
+ */
55
+ const EMBED_COLORS = {
56
+ pending: 0xffa500 as ColorResolvable, // Orange
57
+ success: 0x57f287 as ColorResolvable, // Green
58
+ error: 0xed4245 as ColorResolvable, // Red
59
+ info: 0x5865f2 as ColorResolvable, // Blue (Discord blurple)
60
+ warning: 0xfee75c as ColorResolvable, // Yellow
61
+ neutral: 0x99aab5 as ColorResolvable, // Gray
62
+ } as const;
63
+
64
+ export class DiscordAdapter implements ChannelAdapter {
65
+ readonly type = 'discord' as const;
66
+
67
+ private client: Client | null = null;
68
+ private _status: ChannelStatus = 'disconnected';
69
+ private _botUsername?: string;
70
+ private _botId?: string;
71
+ private messageHandlers: MessageHandler[] = [];
72
+ private errorHandlers: ErrorHandler[] = [];
73
+ private statusHandlers: StatusHandler[] = [];
74
+ private callbackQueryHandlers: CallbackQueryHandler[] = [];
75
+ private selectMenuHandlers: SelectMenuHandler[] = [];
76
+ private config: DiscordConfig;
77
+
78
+ // Track pending interactions that need reply (chatId -> interaction)
79
+ private pendingInteractions: Map<string, ChatInputCommandInteraction> = new Map();
80
+
81
+ // Track thread starters for context (threadId -> starter info)
82
+ private threadStarterCache: Map<string, { authorId: string; authorName: string; content: string }> = new Map();
83
+
84
+ constructor(config: DiscordConfig) {
85
+ this.config = config;
86
+ }
87
+
88
+ get status(): ChannelStatus {
89
+ return this._status;
90
+ }
91
+
92
+ get botUsername(): string | undefined {
93
+ return this._botUsername;
94
+ }
95
+
96
+ /**
97
+ * Connect to Discord
98
+ */
99
+ async connect(): Promise<void> {
100
+ if (this._status === 'connected' || this._status === 'connecting') {
101
+ return;
102
+ }
103
+
104
+ this.setStatus('connecting');
105
+
106
+ try {
107
+ // Create client instance with required intents and partials
108
+ // Partials.Channel is required to receive DM messages
109
+ this.client = new Client({
110
+ intents: [
111
+ GatewayIntentBits.Guilds,
112
+ GatewayIntentBits.GuildMessages,
113
+ GatewayIntentBits.DirectMessages,
114
+ GatewayIntentBits.MessageContent,
115
+ ],
116
+ partials: [
117
+ Partials.Channel, // Required to receive DMs
118
+ Partials.Message, // Required for uncached message events
119
+ ],
120
+ });
121
+
122
+ // Set up event handlers
123
+ this.client.once(Events.ClientReady, async (client) => {
124
+ this._botUsername = client.user.username;
125
+ this._botId = client.user.id;
126
+ console.log(`Discord bot @${this._botUsername} is ready`);
127
+
128
+ // Register slash commands
129
+ await this.registerSlashCommands();
130
+
131
+ this.setStatus('connected');
132
+ });
133
+
134
+ // Handle regular messages (for conversations)
135
+ this.client.on(Events.MessageCreate, async (message) => {
136
+ // Ignore bot messages
137
+ if (message.author.bot) return;
138
+
139
+ // Handle DMs and mentions in guilds
140
+ const isDM = message.channel.type === DiscordChannelType.DM;
141
+ const isMentioned = message.mentions.has(this.client!.user!);
142
+ const isThread = message.channel.isThread();
143
+
144
+ console.log(`Discord message received: isDM=${isDM}, isMentioned=${isMentioned}, isThread=${isThread}, content="${message.content.slice(0, 50)}"`);
145
+
146
+ if (isDM || isMentioned) {
147
+ const incomingMessage = this.mapMessageToIncoming(message);
148
+ console.log(`Processing Discord message from ${message.author.username}: ${incomingMessage.text.slice(0, 50)}`);
149
+ await this.handleIncomingMessage(incomingMessage);
150
+ }
151
+ });
152
+
153
+ // Handle slash command, button, and select menu interactions
154
+ this.client.on(Events.InteractionCreate, async (interaction) => {
155
+ // Handle button interactions
156
+ if (interaction.isButton()) {
157
+ await this.handleButtonInteraction(interaction);
158
+ return;
159
+ }
160
+
161
+ // Handle select menu interactions
162
+ if (interaction.isStringSelectMenu()) {
163
+ await this.handleSelectMenuInteraction(interaction);
164
+ return;
165
+ }
166
+
167
+ if (!interaction.isChatInputCommand()) return;
168
+
169
+ // Defer the reply FIRST to avoid interaction timeout (Discord requires response within 3 seconds)
170
+ try {
171
+ await interaction.deferReply();
172
+ } catch (error) {
173
+ console.error('Failed to defer reply:', error);
174
+ return;
175
+ }
176
+
177
+ // Store the interaction so sendMessage can use editReply for the first response
178
+ if (interaction.channelId) {
179
+ this.pendingInteractions.set(interaction.channelId, interaction);
180
+
181
+ // Auto-clear after 14 minutes (interactions expire after 15 minutes)
182
+ setTimeout(() => {
183
+ this.pendingInteractions.delete(interaction.channelId!);
184
+ }, 14 * 60 * 1000);
185
+ }
186
+
187
+ // Convert slash command to message format
188
+ const incomingMessage = this.mapInteractionToIncoming(interaction);
189
+ await this.handleIncomingMessage(incomingMessage);
190
+ });
191
+
192
+ // Handle errors
193
+ this.client.on(Events.Error, (error) => {
194
+ console.error('Discord client error:', error);
195
+ this.handleError(error, 'client.error');
196
+ });
197
+
198
+ // Login
199
+ await this.client.login(this.config.botToken);
200
+ } catch (error) {
201
+ const err = error instanceof Error ? error : new Error(String(error));
202
+ this.setStatus('error', err);
203
+ throw err;
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Handle button interaction (callback query equivalent)
209
+ */
210
+ private async handleButtonInteraction(interaction: ButtonInteraction): Promise<void> {
211
+ const customId = interaction.customId;
212
+
213
+ // Create callback query object matching our interface
214
+ const callbackQuery: CallbackQuery = {
215
+ id: interaction.id,
216
+ userId: interaction.user.id,
217
+ userName: interaction.user.displayName || interaction.user.username,
218
+ chatId: interaction.channelId!,
219
+ messageId: interaction.message.id,
220
+ data: customId,
221
+ threadId: interaction.channel?.isThread() ? interaction.channelId! : undefined,
222
+ raw: interaction,
223
+ };
224
+
225
+ // Notify all registered handlers
226
+ for (const handler of this.callbackQueryHandlers) {
227
+ try {
228
+ await handler(callbackQuery);
229
+ } catch (error) {
230
+ console.error('Error in callback query handler:', error);
231
+ this.handleError(
232
+ error instanceof Error ? error : new Error(String(error)),
233
+ 'callbackQueryHandler'
234
+ );
235
+ }
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Register slash commands with Discord
241
+ */
242
+ private async registerSlashCommands(): Promise<void> {
243
+ if (!this.client?.user) return;
244
+
245
+ const commands = [
246
+ new SlashCommandBuilder()
247
+ .setName('start')
248
+ .setDescription('Start the bot and get help'),
249
+ new SlashCommandBuilder()
250
+ .setName('help')
251
+ .setDescription('Show available commands'),
252
+ new SlashCommandBuilder()
253
+ .setName('workspaces')
254
+ .setDescription('List available workspaces'),
255
+ new SlashCommandBuilder()
256
+ .setName('workspace')
257
+ .setDescription('Select or show current workspace')
258
+ .addStringOption(option =>
259
+ option.setName('path')
260
+ .setDescription('Workspace path to select')
261
+ .setRequired(false)),
262
+ new SlashCommandBuilder()
263
+ .setName('addworkspace')
264
+ .setDescription('Add a new workspace by path')
265
+ .addStringOption(option =>
266
+ option.setName('path')
267
+ .setDescription('Path to the workspace folder')
268
+ .setRequired(true)),
269
+ new SlashCommandBuilder()
270
+ .setName('newtask')
271
+ .setDescription('Start a fresh task/conversation'),
272
+ new SlashCommandBuilder()
273
+ .setName('provider')
274
+ .setDescription('Change or show current LLM provider')
275
+ .addStringOption(option =>
276
+ option.setName('name')
277
+ .setDescription('Provider name (anthropic, gemini, openrouter, bedrock, ollama)')
278
+ .setRequired(false)),
279
+ new SlashCommandBuilder()
280
+ .setName('providers')
281
+ .setDescription('List all available providers'),
282
+ new SlashCommandBuilder()
283
+ .setName('models')
284
+ .setDescription('List available AI models'),
285
+ new SlashCommandBuilder()
286
+ .setName('model')
287
+ .setDescription('Change or show current model')
288
+ .addStringOption(option =>
289
+ option.setName('name')
290
+ .setDescription('Model name to use')
291
+ .setRequired(false)),
292
+ new SlashCommandBuilder()
293
+ .setName('status')
294
+ .setDescription('Check bot status'),
295
+ new SlashCommandBuilder()
296
+ .setName('cancel')
297
+ .setDescription('Cancel current task'),
298
+ new SlashCommandBuilder()
299
+ .setName('task')
300
+ .setDescription('Run a task')
301
+ .addStringOption(option =>
302
+ option.setName('prompt')
303
+ .setDescription('Task description')
304
+ .setRequired(true)),
305
+ new SlashCommandBuilder()
306
+ .setName('pair')
307
+ .setDescription('Pair with a pairing code to gain access')
308
+ .addStringOption(option =>
309
+ option.setName('code')
310
+ .setDescription('The pairing code from CoWork OS app')
311
+ .setRequired(true)),
312
+ new SlashCommandBuilder()
313
+ .setName('approve')
314
+ .setDescription('Approve the pending action'),
315
+ new SlashCommandBuilder()
316
+ .setName('deny')
317
+ .setDescription('Deny the pending action'),
318
+ ];
319
+
320
+ const rest = new REST().setToken(this.config.botToken);
321
+
322
+ try {
323
+ console.log('Registering Discord slash commands...');
324
+
325
+ // Register commands globally or to specific guilds
326
+ if (this.config.guildIds && this.config.guildIds.length > 0) {
327
+ // Register to specific guilds (faster for development)
328
+ for (const guildId of this.config.guildIds) {
329
+ await rest.put(
330
+ Routes.applicationGuildCommands(this.config.applicationId, guildId),
331
+ { body: commands.map(c => c.toJSON()) }
332
+ );
333
+ }
334
+ } else {
335
+ // Register globally (takes up to 1 hour to propagate)
336
+ await rest.put(
337
+ Routes.applicationCommands(this.config.applicationId),
338
+ { body: commands.map(c => c.toJSON()) }
339
+ );
340
+ }
341
+
342
+ console.log('Discord slash commands registered');
343
+ } catch (error) {
344
+ console.error('Failed to register Discord slash commands:', error);
345
+ }
346
+ }
347
+
348
+ /**
349
+ * Disconnect from Discord
350
+ */
351
+ async disconnect(): Promise<void> {
352
+ if (this.client) {
353
+ this.client.destroy();
354
+ this.client = null;
355
+ }
356
+ this._botUsername = undefined;
357
+ this._botId = undefined;
358
+ this.setStatus('disconnected');
359
+ }
360
+
361
+ /**
362
+ * Send a message to a Discord channel
363
+ */
364
+ async sendMessage(message: OutgoingMessage): Promise<string> {
365
+ if (!this.client || this._status !== 'connected') {
366
+ throw new Error('Discord bot is not connected');
367
+ }
368
+
369
+ // Process text for Discord compatibility
370
+ let processedText = message.text;
371
+ if (message.parseMode === 'markdown') {
372
+ processedText = this.convertMarkdownForDiscord(message.text);
373
+ }
374
+
375
+ // Build button components if inline keyboard is provided
376
+ const components = message.inlineKeyboard && message.inlineKeyboard.length > 0
377
+ ? this.buildButtonComponents(message.inlineKeyboard)
378
+ : [];
379
+
380
+ // Use smart chunking that preserves code fences
381
+ const chunks = this.splitMessageSmart(processedText, 2000);
382
+ let lastMessageId = '';
383
+
384
+ // Check if there's a pending interaction for this chat that needs reply
385
+ const pendingInteraction = this.pendingInteractions.get(message.chatId);
386
+
387
+ // Determine target channel (could be a thread)
388
+ let targetChannelId = message.chatId;
389
+ if (message.threadId) {
390
+ targetChannelId = message.threadId;
391
+ }
392
+
393
+ try {
394
+ for (let i = 0; i < chunks.length; i++) {
395
+ const chunk = chunks[i];
396
+ const isLastChunk = i === chunks.length - 1;
397
+
398
+ // Only add buttons to the last chunk
399
+ const chunkComponents = isLastChunk ? components : [];
400
+
401
+ // First chunk: use interaction reply if available
402
+ if (i === 0 && pendingInteraction) {
403
+ try {
404
+ const reply = await pendingInteraction.editReply({
405
+ content: chunk,
406
+ components: chunkComponents,
407
+ });
408
+ lastMessageId = typeof reply === 'object' && 'id' in reply ? reply.id : pendingInteraction.id;
409
+ // Clear the pending interaction after first reply
410
+ this.pendingInteractions.delete(message.chatId);
411
+ continue;
412
+ } catch (interactionError) {
413
+ // Interaction may have expired, fall back to channel.send
414
+ console.warn('Interaction reply failed, falling back to channel.send:', interactionError);
415
+ this.pendingInteractions.delete(message.chatId);
416
+ }
417
+ }
418
+
419
+ // Regular channel message
420
+ const channel = await this.client.channels.fetch(targetChannelId);
421
+ if (!channel || !this.isTextBasedChannel(channel)) {
422
+ throw new Error('Invalid channel or channel is not text-based');
423
+ }
424
+
425
+ const sent = await (channel as TextChannel | DMChannel | ThreadChannel).send({
426
+ content: chunk,
427
+ components: chunkComponents,
428
+ reply: message.replyTo && i === 0 ? { messageReference: message.replyTo } : undefined,
429
+ });
430
+ lastMessageId = sent.id;
431
+ }
432
+ } catch (error: unknown) {
433
+ // If markdown parsing fails, retry without formatting
434
+ const errorMessage = error instanceof Error ? error.message : String(error);
435
+ if (errorMessage.includes('parse') || errorMessage.includes('format')) {
436
+ console.log('Markdown parsing failed, retrying without formatting');
437
+ return this.sendMessagePlain(targetChannelId, message.text, message.replyTo, components);
438
+ }
439
+ throw error;
440
+ }
441
+
442
+ return lastMessageId;
443
+ }
444
+
445
+ /**
446
+ * Send a message with an embed (rich format)
447
+ */
448
+ async sendEmbed(
449
+ chatId: string,
450
+ options: {
451
+ title?: string;
452
+ description?: string;
453
+ color?: keyof typeof EMBED_COLORS;
454
+ fields?: Array<{ name: string; value: string; inline?: boolean }>;
455
+ footer?: string;
456
+ timestamp?: boolean;
457
+ },
458
+ buttons?: InlineKeyboardButton[][]
459
+ ): Promise<string> {
460
+ if (!this.client || this._status !== 'connected') {
461
+ throw new Error('Discord bot is not connected');
462
+ }
463
+
464
+ const embed = new EmbedBuilder();
465
+
466
+ if (options.title) embed.setTitle(options.title);
467
+ if (options.description) embed.setDescription(options.description);
468
+ if (options.color) embed.setColor(EMBED_COLORS[options.color]);
469
+ if (options.fields) {
470
+ for (const field of options.fields) {
471
+ embed.addFields({ name: field.name, value: field.value, inline: field.inline });
472
+ }
473
+ }
474
+ if (options.footer) embed.setFooter({ text: options.footer });
475
+ if (options.timestamp) embed.setTimestamp();
476
+
477
+ const components = buttons ? this.buildButtonComponents(buttons) : [];
478
+
479
+ const channel = await this.client.channels.fetch(chatId);
480
+ if (!channel || !this.isTextBasedChannel(channel)) {
481
+ throw new Error('Invalid channel');
482
+ }
483
+
484
+ const sent = await (channel as TextChannel | DMChannel | ThreadChannel).send({
485
+ embeds: [embed],
486
+ components,
487
+ });
488
+
489
+ return sent.id;
490
+ }
491
+
492
+ /**
493
+ * Build Discord button components from our button format
494
+ */
495
+ private buildButtonComponents(buttons: InlineKeyboardButton[][]): ActionRowBuilder<ButtonBuilder>[] {
496
+ const rows: ActionRowBuilder<ButtonBuilder>[] = [];
497
+
498
+ for (const rowButtons of buttons) {
499
+ if (rowButtons.length === 0) continue;
500
+
501
+ const row = new ActionRowBuilder<ButtonBuilder>();
502
+ let buttonCount = 0;
503
+
504
+ for (const button of rowButtons) {
505
+ if (buttonCount >= 5) break; // Discord max 5 buttons per row
506
+
507
+ const discordButton = new ButtonBuilder()
508
+ .setLabel(button.text.substring(0, 80)); // Discord max 80 chars
509
+
510
+ if (button.url) {
511
+ discordButton.setStyle(ButtonStyle.Link);
512
+ discordButton.setURL(button.url);
513
+ } else if (button.callbackData) {
514
+ // Determine button style based on callback data
515
+ if (button.callbackData.startsWith('approve')) {
516
+ discordButton.setStyle(ButtonStyle.Success);
517
+ } else if (button.callbackData.startsWith('deny')) {
518
+ discordButton.setStyle(ButtonStyle.Danger);
519
+ } else {
520
+ discordButton.setStyle(ButtonStyle.Primary);
521
+ }
522
+ discordButton.setCustomId(button.callbackData.substring(0, 100)); // Discord max 100 chars
523
+ } else {
524
+ continue; // Skip buttons without action
525
+ }
526
+
527
+ row.addComponents(discordButton);
528
+ buttonCount++;
529
+ }
530
+
531
+ if (buttonCount > 0) {
532
+ rows.push(row);
533
+ }
534
+
535
+ if (rows.length >= 5) break; // Discord max 5 rows
536
+ }
537
+
538
+ return rows;
539
+ }
540
+
541
+ /**
542
+ * Send a plain text message without formatting
543
+ */
544
+ private async sendMessagePlain(
545
+ chatId: string,
546
+ text: string,
547
+ replyTo?: string,
548
+ components: ActionRowBuilder<ButtonBuilder>[] = []
549
+ ): Promise<string> {
550
+ const channel = await this.client!.channels.fetch(chatId);
551
+ if (!channel || !this.isTextBasedChannel(channel)) {
552
+ throw new Error('Invalid channel');
553
+ }
554
+
555
+ const chunks = this.splitMessageSmart(text, 2000);
556
+ let lastMessageId = '';
557
+
558
+ for (let i = 0; i < chunks.length; i++) {
559
+ const isLastChunk = i === chunks.length - 1;
560
+ const sent = await (channel as TextChannel | DMChannel | ThreadChannel).send({
561
+ content: chunks[i],
562
+ components: isLastChunk ? components : [],
563
+ reply: replyTo && i === 0 ? { messageReference: replyTo } : undefined,
564
+ });
565
+ lastMessageId = sent.id;
566
+ }
567
+
568
+ return lastMessageId;
569
+ }
570
+
571
+ /**
572
+ * Convert GitHub-flavored markdown to Discord-compatible format
573
+ */
574
+ private convertMarkdownForDiscord(text: string): string {
575
+ let result = text;
576
+
577
+ // Convert markdown headers (## Header) to bold (**Header**)
578
+ result = result.replace(/^#{1,6}\s+(.+)$/gm, '**$1**');
579
+
580
+ // Convert horizontal rules (---, ***) to a line
581
+ result = result.replace(/^[-*]{3,}$/gm, '───────────────────');
582
+
583
+ return result;
584
+ }
585
+
586
+ /**
587
+ * Smart message splitting that preserves code fences
588
+ */
589
+ private splitMessageSmart(text: string, maxLength: number): string[] {
590
+ if (text.length <= maxLength) {
591
+ return [text];
592
+ }
593
+
594
+ const chunks: string[] = [];
595
+ let remaining = text;
596
+ let inCodeBlock = false;
597
+ let codeBlockLang = '';
598
+
599
+ while (remaining.length > 0) {
600
+ if (remaining.length <= maxLength) {
601
+ // Close any open code block at the end
602
+ if (inCodeBlock) {
603
+ chunks.push(remaining);
604
+ } else {
605
+ chunks.push(remaining);
606
+ }
607
+ break;
608
+ }
609
+
610
+ // Find the best breaking point
611
+ let breakIndex = this.findBreakPoint(remaining, maxLength, inCodeBlock);
612
+ let chunk = remaining.substring(0, breakIndex);
613
+
614
+ // Check if we're entering or leaving a code block
615
+ const codeBlockMatches = chunk.match(/```(\w*)/g) || [];
616
+ for (const match of codeBlockMatches) {
617
+ if (inCodeBlock) {
618
+ inCodeBlock = false;
619
+ codeBlockLang = '';
620
+ } else {
621
+ inCodeBlock = true;
622
+ codeBlockLang = match.replace('```', '');
623
+ }
624
+ }
625
+
626
+ // If we're in a code block and the chunk doesn't close it, close it manually
627
+ if (inCodeBlock && !chunk.endsWith('```')) {
628
+ chunk += '\n```';
629
+ }
630
+
631
+ chunks.push(chunk);
632
+ remaining = remaining.substring(breakIndex).trimStart();
633
+
634
+ // If we closed a code block, reopen it in the next chunk
635
+ if (inCodeBlock && remaining.length > 0) {
636
+ remaining = '```' + codeBlockLang + '\n' + remaining;
637
+ }
638
+ }
639
+
640
+ return chunks;
641
+ }
642
+
643
+ /**
644
+ * Find the best break point for message splitting
645
+ */
646
+ private findBreakPoint(text: string, maxLength: number, inCodeBlock: boolean): number {
647
+ // Reserve space for potential code fence closure
648
+ const reservedSpace = inCodeBlock ? 4 : 0;
649
+ const effectiveMax = maxLength - reservedSpace;
650
+
651
+ // Try to break at a newline
652
+ let breakIndex = text.lastIndexOf('\n', effectiveMax);
653
+ if (breakIndex > effectiveMax / 2) {
654
+ return breakIndex + 1;
655
+ }
656
+
657
+ // Try to break at a space
658
+ breakIndex = text.lastIndexOf(' ', effectiveMax);
659
+ if (breakIndex > effectiveMax / 2) {
660
+ return breakIndex + 1;
661
+ }
662
+
663
+ // Force break at max length
664
+ return effectiveMax;
665
+ }
666
+
667
+ /**
668
+ * Legacy split method for compatibility
669
+ */
670
+ private splitMessage(text: string, maxLength: number): string[] {
671
+ return this.splitMessageSmart(text, maxLength);
672
+ }
673
+
674
+ /**
675
+ * Edit an existing message
676
+ */
677
+ async editMessage(chatId: string, messageId: string, text: string): Promise<void> {
678
+ if (!this.client || this._status !== 'connected') {
679
+ throw new Error('Discord bot is not connected');
680
+ }
681
+
682
+ const channel = await this.client.channels.fetch(chatId);
683
+ if (!channel || !this.isTextBasedChannel(channel)) {
684
+ throw new Error('Invalid channel');
685
+ }
686
+
687
+ const message = await (channel as TextChannel | DMChannel | ThreadChannel).messages.fetch(messageId);
688
+ await message.edit(text);
689
+ }
690
+
691
+ /**
692
+ * Delete a message
693
+ */
694
+ async deleteMessage(chatId: string, messageId: string): Promise<void> {
695
+ if (!this.client || this._status !== 'connected') {
696
+ throw new Error('Discord bot is not connected');
697
+ }
698
+
699
+ const channel = await this.client.channels.fetch(chatId);
700
+ if (!channel || !this.isTextBasedChannel(channel)) {
701
+ throw new Error('Invalid channel');
702
+ }
703
+
704
+ const message = await (channel as TextChannel | DMChannel | ThreadChannel).messages.fetch(messageId);
705
+ await message.delete();
706
+ }
707
+
708
+ /**
709
+ * Send a document/file to a channel
710
+ */
711
+ async sendDocument(chatId: string, filePath: string, caption?: string): Promise<string> {
712
+ if (!this.client || this._status !== 'connected') {
713
+ throw new Error('Discord bot is not connected');
714
+ }
715
+
716
+ // Check if file exists
717
+ if (!fs.existsSync(filePath)) {
718
+ throw new Error(`File not found: ${filePath}`);
719
+ }
720
+
721
+ const channel = await this.client.channels.fetch(chatId);
722
+ if (!channel || !this.isTextBasedChannel(channel)) {
723
+ throw new Error('Invalid channel');
724
+ }
725
+
726
+ const fileName = path.basename(filePath);
727
+ const attachment = new AttachmentBuilder(filePath, { name: fileName });
728
+
729
+ const sent = await (channel as TextChannel | DMChannel | ThreadChannel).send({
730
+ content: caption,
731
+ files: [attachment],
732
+ });
733
+
734
+ return sent.id;
735
+ }
736
+
737
+ /**
738
+ * Register a message handler
739
+ */
740
+ onMessage(handler: MessageHandler): void {
741
+ this.messageHandlers.push(handler);
742
+ }
743
+
744
+ /**
745
+ * Register a callback query handler (for button interactions)
746
+ */
747
+ onCallbackQuery(handler: CallbackQueryHandler): void {
748
+ this.callbackQueryHandlers.push(handler);
749
+ }
750
+
751
+ /**
752
+ * Answer a callback query (acknowledge button press)
753
+ * For Discord, this updates the message or sends an ephemeral response
754
+ */
755
+ async answerCallbackQuery(queryId: string, text?: string, showAlert?: boolean): Promise<void> {
756
+ // In Discord, we need to use the interaction object stored in the raw field
757
+ // The queryId is the interaction ID, but we need the actual interaction object
758
+ // This is typically handled directly in handleButtonInteraction
759
+ // This method provides API compatibility with Telegram
760
+ console.log(`answerCallbackQuery called: ${queryId}, text: ${text}, showAlert: ${showAlert}`);
761
+ }
762
+
763
+ /**
764
+ * Edit a message with a new inline keyboard
765
+ */
766
+ async editMessageWithKeyboard(
767
+ chatId: string,
768
+ messageId: string,
769
+ text?: string,
770
+ inlineKeyboard?: InlineKeyboardButton[][]
771
+ ): Promise<void> {
772
+ if (!this.client || this._status !== 'connected') {
773
+ throw new Error('Discord bot is not connected');
774
+ }
775
+
776
+ const channel = await this.client.channels.fetch(chatId);
777
+ if (!channel || !this.isTextBasedChannel(channel)) {
778
+ throw new Error('Invalid channel');
779
+ }
780
+
781
+ const message = await (channel as TextChannel | DMChannel | ThreadChannel).messages.fetch(messageId);
782
+ const components = inlineKeyboard ? this.buildButtonComponents(inlineKeyboard) : [];
783
+
784
+ await message.edit({
785
+ content: text || message.content,
786
+ components,
787
+ });
788
+ }
789
+
790
+ // ============================================================================
791
+ // Extended Features
792
+ // ============================================================================
793
+
794
+ /**
795
+ * Send typing indicator
796
+ */
797
+ async sendTyping(chatId: string): Promise<void> {
798
+ if (!this.client || this._status !== 'connected') {
799
+ throw new Error('Discord bot is not connected');
800
+ }
801
+
802
+ const channel = await this.client.channels.fetch(chatId);
803
+ if (!channel || !this.isTextBasedChannel(channel)) {
804
+ throw new Error('Invalid channel');
805
+ }
806
+
807
+ await (channel as TextChannel | DMChannel | ThreadChannel).sendTyping();
808
+ }
809
+
810
+ /**
811
+ * Add reaction to a message
812
+ */
813
+ async addReaction(chatId: string, messageId: string, emoji: string): Promise<void> {
814
+ if (!this.client || this._status !== 'connected') {
815
+ throw new Error('Discord bot is not connected');
816
+ }
817
+
818
+ const channel = await this.client.channels.fetch(chatId);
819
+ if (!channel || !this.isTextBasedChannel(channel)) {
820
+ throw new Error('Invalid channel');
821
+ }
822
+
823
+ const message = await (channel as TextChannel | DMChannel | ThreadChannel).messages.fetch(messageId);
824
+ await message.react(emoji);
825
+ }
826
+
827
+ /**
828
+ * Remove reaction from a message
829
+ */
830
+ async removeReaction(chatId: string, messageId: string, emoji: string): Promise<void> {
831
+ if (!this.client || this._status !== 'connected') {
832
+ throw new Error('Discord bot is not connected');
833
+ }
834
+
835
+ const channel = await this.client.channels.fetch(chatId);
836
+ if (!channel || !this.isTextBasedChannel(channel)) {
837
+ throw new Error('Invalid channel');
838
+ }
839
+
840
+ const message = await (channel as TextChannel | DMChannel | ThreadChannel).messages.fetch(messageId);
841
+ const reaction = message.reactions.cache.get(emoji);
842
+ if (reaction && this._botId) {
843
+ await reaction.users.remove(this._botId);
844
+ }
845
+ }
846
+
847
+ /**
848
+ * Send a poll (Discord native polls)
849
+ */
850
+ async sendPoll(chatId: string, poll: Poll): Promise<string> {
851
+ if (!this.client || this._status !== 'connected') {
852
+ throw new Error('Discord bot is not connected');
853
+ }
854
+
855
+ const channel = await this.client.channels.fetch(chatId);
856
+ if (!channel || !this.isTextBasedChannel(channel)) {
857
+ throw new Error('Invalid channel');
858
+ }
859
+
860
+ // Discord polls require specific formatting
861
+ const pollData = {
862
+ question: { text: poll.question },
863
+ answers: poll.options.map(opt => ({ text: opt.text })),
864
+ duration: poll.openPeriod ? Math.ceil(poll.openPeriod / 3600) : 24, // Convert seconds to hours
865
+ allow_multiselect: poll.allowsMultipleAnswers ?? false,
866
+ };
867
+
868
+ const sent = await (channel as TextChannel | DMChannel | ThreadChannel).send({
869
+ poll: pollData as any,
870
+ });
871
+
872
+ return sent.id;
873
+ }
874
+
875
+ /**
876
+ * Send a message with a select menu (dropdown)
877
+ */
878
+ async sendWithSelectMenu(chatId: string, text: string, menu: SelectMenu): Promise<string> {
879
+ if (!this.client || this._status !== 'connected') {
880
+ throw new Error('Discord bot is not connected');
881
+ }
882
+
883
+ const channel = await this.client.channels.fetch(chatId);
884
+ if (!channel || !this.isTextBasedChannel(channel)) {
885
+ throw new Error('Invalid channel');
886
+ }
887
+
888
+ const selectMenu = new StringSelectMenuBuilder()
889
+ .setCustomId(menu.customId)
890
+ .setPlaceholder(menu.placeholder || 'Select an option')
891
+ .setMinValues(menu.minValues ?? 1)
892
+ .setMaxValues(menu.maxValues ?? 1)
893
+ .addOptions(
894
+ menu.options.map(opt => ({
895
+ label: opt.label,
896
+ value: opt.value,
897
+ description: opt.description,
898
+ emoji: opt.emoji ? { name: opt.emoji } : undefined,
899
+ default: opt.default,
900
+ }))
901
+ );
902
+
903
+ if (menu.disabled) {
904
+ selectMenu.setDisabled(true);
905
+ }
906
+
907
+ const row = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(selectMenu);
908
+
909
+ const sent = await (channel as TextChannel | DMChannel | ThreadChannel).send({
910
+ content: text,
911
+ components: [row],
912
+ });
913
+
914
+ return sent.id;
915
+ }
916
+
917
+ /**
918
+ * Register a select menu handler
919
+ */
920
+ onSelectMenu(handler: SelectMenuHandler): void {
921
+ this.selectMenuHandlers.push(handler);
922
+ }
923
+
924
+ /**
925
+ * Handle select menu interaction
926
+ */
927
+ private async handleSelectMenuInteraction(interaction: StringSelectMenuInteraction): Promise<void> {
928
+ const customId = interaction.customId;
929
+ const values = interaction.values;
930
+
931
+ // Acknowledge the interaction
932
+ try {
933
+ await interaction.deferUpdate();
934
+ } catch (error) {
935
+ console.error('Failed to defer select menu update:', error);
936
+ }
937
+
938
+ // Notify all registered handlers
939
+ for (const handler of this.selectMenuHandlers) {
940
+ try {
941
+ await handler(
942
+ customId,
943
+ values,
944
+ interaction.user.id,
945
+ interaction.channelId!,
946
+ interaction.message.id,
947
+ interaction
948
+ );
949
+ } catch (error) {
950
+ console.error('Error in select menu handler:', error);
951
+ this.handleError(
952
+ error instanceof Error ? error : new Error(String(error)),
953
+ 'selectMenuHandler'
954
+ );
955
+ }
956
+ }
957
+ }
958
+
959
+ // ============================================================================
960
+ // Handler Registration
961
+ // ============================================================================
962
+
963
+ /**
964
+ * Register an error handler
965
+ */
966
+ onError(handler: ErrorHandler): void {
967
+ this.errorHandlers.push(handler);
968
+ }
969
+
970
+ /**
971
+ * Register a status change handler
972
+ */
973
+ onStatusChange(handler: StatusHandler): void {
974
+ this.statusHandlers.push(handler);
975
+ }
976
+
977
+ /**
978
+ * Get channel info
979
+ */
980
+ async getInfo(): Promise<ChannelInfo> {
981
+ return {
982
+ type: 'discord',
983
+ status: this._status,
984
+ botId: this._botId,
985
+ botUsername: this._botUsername,
986
+ botDisplayName: this._botUsername,
987
+ extra: {
988
+ applicationId: this.config.applicationId,
989
+ guildIds: this.config.guildIds,
990
+ },
991
+ };
992
+ }
993
+
994
+ // Private methods
995
+
996
+ private isTextBasedChannel(channel: unknown): channel is TextChannel | DMChannel | ThreadChannel {
997
+ const ch = channel as { type?: DiscordChannelType };
998
+ return ch.type === DiscordChannelType.GuildText ||
999
+ ch.type === DiscordChannelType.DM ||
1000
+ ch.type === DiscordChannelType.PublicThread ||
1001
+ ch.type === DiscordChannelType.PrivateThread;
1002
+ }
1003
+
1004
+ private mapMessageToIncoming(message: Message): IncomingMessage {
1005
+ // Remove bot mention from the text if present
1006
+ let text = message.content;
1007
+ if (this._botId) {
1008
+ text = text.replace(new RegExp(`<@!?${this._botId}>\\s*`, 'g'), '').trim();
1009
+ }
1010
+
1011
+ // Map Discord message to command format if it looks like a command
1012
+ const commandText = this.parseCommand(text);
1013
+
1014
+ // Check for thread context
1015
+ const isThread = message.channel.isThread();
1016
+ const threadId = isThread ? message.channelId : undefined;
1017
+
1018
+ return {
1019
+ messageId: message.id,
1020
+ channel: 'discord',
1021
+ userId: message.author.id,
1022
+ userName: message.author.displayName || message.author.username,
1023
+ chatId: isThread ? (message.channel as ThreadChannel).parentId! : message.channelId,
1024
+ text: commandText || text,
1025
+ timestamp: message.createdAt,
1026
+ replyTo: message.reference?.messageId,
1027
+ threadId,
1028
+ isForumTopic: isThread,
1029
+ raw: message,
1030
+ };
1031
+ }
1032
+
1033
+ private mapInteractionToIncoming(interaction: ChatInputCommandInteraction): IncomingMessage {
1034
+ const commandName = interaction.commandName;
1035
+ let text = `/${commandName}`;
1036
+
1037
+ // Add options to the command text
1038
+ const options = interaction.options;
1039
+
1040
+ // Handle specific commands with their options
1041
+ switch (commandName) {
1042
+ case 'workspace': {
1043
+ const wsPath = options.getString('path');
1044
+ if (wsPath) text += ` ${wsPath}`;
1045
+ break;
1046
+ }
1047
+ case 'addworkspace': {
1048
+ const addPath = options.getString('path');
1049
+ if (addPath) text += ` ${addPath}`;
1050
+ break;
1051
+ }
1052
+ case 'provider': {
1053
+ const provider = options.getString('name');
1054
+ if (provider) text += ` ${provider}`;
1055
+ break;
1056
+ }
1057
+ case 'model': {
1058
+ const model = options.getString('name');
1059
+ if (model) text += ` ${model}`;
1060
+ break;
1061
+ }
1062
+ case 'task': {
1063
+ const prompt = options.getString('prompt');
1064
+ if (prompt) text = prompt; // Task prompt becomes the text directly
1065
+ break;
1066
+ }
1067
+ case 'pair': {
1068
+ const code = options.getString('code');
1069
+ if (code) text += ` ${code}`;
1070
+ break;
1071
+ }
1072
+ }
1073
+
1074
+ // Check for thread context
1075
+ const isThread = interaction.channel?.isThread() ?? false;
1076
+
1077
+ return {
1078
+ messageId: interaction.id,
1079
+ channel: 'discord',
1080
+ userId: interaction.user.id,
1081
+ userName: interaction.user.displayName || interaction.user.username,
1082
+ chatId: interaction.channelId!,
1083
+ text,
1084
+ timestamp: new Date(interaction.createdTimestamp),
1085
+ threadId: isThread ? interaction.channelId! : undefined,
1086
+ isForumTopic: isThread,
1087
+ raw: interaction,
1088
+ };
1089
+ }
1090
+
1091
+ /**
1092
+ * Parse text to see if it's a command (starts with /)
1093
+ */
1094
+ private parseCommand(text: string): string | null {
1095
+ // Check if text starts with a command
1096
+ const commandMatch = text.match(/^\/(\w+)(?:\s+(.*))?$/);
1097
+ if (commandMatch) {
1098
+ return text; // Already in command format
1099
+ }
1100
+ return null;
1101
+ }
1102
+
1103
+ private async handleIncomingMessage(message: IncomingMessage): Promise<void> {
1104
+ for (const handler of this.messageHandlers) {
1105
+ try {
1106
+ await handler(message);
1107
+ } catch (error) {
1108
+ console.error('Error in message handler:', error);
1109
+ this.handleError(
1110
+ error instanceof Error ? error : new Error(String(error)),
1111
+ 'messageHandler'
1112
+ );
1113
+ }
1114
+ }
1115
+ }
1116
+
1117
+ private handleError(error: Error, context?: string): void {
1118
+ for (const handler of this.errorHandlers) {
1119
+ try {
1120
+ handler(error, context);
1121
+ } catch (e) {
1122
+ console.error('Error in error handler:', e);
1123
+ }
1124
+ }
1125
+ }
1126
+
1127
+ private setStatus(status: ChannelStatus, error?: Error): void {
1128
+ this._status = status;
1129
+ for (const handler of this.statusHandlers) {
1130
+ try {
1131
+ handler(status, error);
1132
+ } catch (e) {
1133
+ console.error('Error in status handler:', e);
1134
+ }
1135
+ }
1136
+ }
1137
+ }
1138
+
1139
+ /**
1140
+ * Create a Discord adapter from configuration
1141
+ */
1142
+ export function createDiscordAdapter(config: DiscordConfig): DiscordAdapter {
1143
+ if (!config.botToken) {
1144
+ throw new Error('Discord bot token is required');
1145
+ }
1146
+ if (!config.applicationId) {
1147
+ throw new Error('Discord application ID is required');
1148
+ }
1149
+ return new DiscordAdapter(config);
1150
+ }