whale-code 6.4.0

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 (319) hide show
  1. package/README.md +95 -0
  2. package/bin/swag-agent.js +9 -0
  3. package/bin/swagmanager-mcp.js +321 -0
  4. package/dist/cli/app.d.ts +26 -0
  5. package/dist/cli/app.js +64 -0
  6. package/dist/cli/chat/AgentSelector.d.ts +14 -0
  7. package/dist/cli/chat/AgentSelector.js +14 -0
  8. package/dist/cli/chat/ChatApp.d.ts +9 -0
  9. package/dist/cli/chat/ChatApp.js +267 -0
  10. package/dist/cli/chat/ChatInput.d.ts +39 -0
  11. package/dist/cli/chat/ChatInput.js +509 -0
  12. package/dist/cli/chat/MarkdownText.d.ts +10 -0
  13. package/dist/cli/chat/MarkdownText.js +20 -0
  14. package/dist/cli/chat/MessageList.d.ts +37 -0
  15. package/dist/cli/chat/MessageList.js +80 -0
  16. package/dist/cli/chat/ModelSelector.d.ts +20 -0
  17. package/dist/cli/chat/ModelSelector.js +73 -0
  18. package/dist/cli/chat/RewindViewer.d.ts +26 -0
  19. package/dist/cli/chat/RewindViewer.js +185 -0
  20. package/dist/cli/chat/StoreSelector.d.ts +14 -0
  21. package/dist/cli/chat/StoreSelector.js +24 -0
  22. package/dist/cli/chat/StreamingText.d.ts +12 -0
  23. package/dist/cli/chat/StreamingText.js +12 -0
  24. package/dist/cli/chat/SubagentPanel.d.ts +45 -0
  25. package/dist/cli/chat/SubagentPanel.js +110 -0
  26. package/dist/cli/chat/TeamPanel.d.ts +21 -0
  27. package/dist/cli/chat/TeamPanel.js +42 -0
  28. package/dist/cli/chat/ToolIndicator.d.ts +25 -0
  29. package/dist/cli/chat/ToolIndicator.js +436 -0
  30. package/dist/cli/chat/hooks/useAgentLoop.d.ts +39 -0
  31. package/dist/cli/chat/hooks/useAgentLoop.js +382 -0
  32. package/dist/cli/chat/hooks/useSlashCommands.d.ts +37 -0
  33. package/dist/cli/chat/hooks/useSlashCommands.js +387 -0
  34. package/dist/cli/commands/config-cmd.d.ts +10 -0
  35. package/dist/cli/commands/config-cmd.js +99 -0
  36. package/dist/cli/commands/doctor.d.ts +14 -0
  37. package/dist/cli/commands/doctor.js +172 -0
  38. package/dist/cli/commands/init.d.ts +16 -0
  39. package/dist/cli/commands/init.js +278 -0
  40. package/dist/cli/commands/mcp.d.ts +12 -0
  41. package/dist/cli/commands/mcp.js +162 -0
  42. package/dist/cli/login/LoginApp.d.ts +7 -0
  43. package/dist/cli/login/LoginApp.js +157 -0
  44. package/dist/cli/print-mode.d.ts +31 -0
  45. package/dist/cli/print-mode.js +202 -0
  46. package/dist/cli/serve-mode.d.ts +37 -0
  47. package/dist/cli/serve-mode.js +636 -0
  48. package/dist/cli/services/agent-definitions.d.ts +25 -0
  49. package/dist/cli/services/agent-definitions.js +91 -0
  50. package/dist/cli/services/agent-events.d.ts +178 -0
  51. package/dist/cli/services/agent-events.js +175 -0
  52. package/dist/cli/services/agent-loop.d.ts +90 -0
  53. package/dist/cli/services/agent-loop.js +762 -0
  54. package/dist/cli/services/agent-worker-base.d.ts +97 -0
  55. package/dist/cli/services/agent-worker-base.js +220 -0
  56. package/dist/cli/services/auth-service.d.ts +30 -0
  57. package/dist/cli/services/auth-service.js +160 -0
  58. package/dist/cli/services/background-processes.d.ts +126 -0
  59. package/dist/cli/services/background-processes.js +318 -0
  60. package/dist/cli/services/browser-auth.d.ts +24 -0
  61. package/dist/cli/services/browser-auth.js +180 -0
  62. package/dist/cli/services/claude-md-loader.d.ts +16 -0
  63. package/dist/cli/services/claude-md-loader.js +58 -0
  64. package/dist/cli/services/config-store.d.ts +47 -0
  65. package/dist/cli/services/config-store.js +79 -0
  66. package/dist/cli/services/debug-log.d.ts +10 -0
  67. package/dist/cli/services/debug-log.js +52 -0
  68. package/dist/cli/services/error-logger.d.ts +58 -0
  69. package/dist/cli/services/error-logger.js +269 -0
  70. package/dist/cli/services/file-history.d.ts +21 -0
  71. package/dist/cli/services/file-history.js +83 -0
  72. package/dist/cli/services/format-server-response.d.ts +16 -0
  73. package/dist/cli/services/format-server-response.js +440 -0
  74. package/dist/cli/services/git-context.d.ts +11 -0
  75. package/dist/cli/services/git-context.js +66 -0
  76. package/dist/cli/services/hooks.d.ts +85 -0
  77. package/dist/cli/services/hooks.js +258 -0
  78. package/dist/cli/services/interactive-tools.d.ts +125 -0
  79. package/dist/cli/services/interactive-tools.js +260 -0
  80. package/dist/cli/services/keybinding-manager.d.ts +52 -0
  81. package/dist/cli/services/keybinding-manager.js +115 -0
  82. package/dist/cli/services/local-tools.d.ts +22 -0
  83. package/dist/cli/services/local-tools.js +697 -0
  84. package/dist/cli/services/lsp-manager.d.ts +18 -0
  85. package/dist/cli/services/lsp-manager.js +717 -0
  86. package/dist/cli/services/mcp-client.d.ts +48 -0
  87. package/dist/cli/services/mcp-client.js +157 -0
  88. package/dist/cli/services/memory-manager.d.ts +16 -0
  89. package/dist/cli/services/memory-manager.js +57 -0
  90. package/dist/cli/services/model-manager.d.ts +18 -0
  91. package/dist/cli/services/model-manager.js +71 -0
  92. package/dist/cli/services/model-router.d.ts +26 -0
  93. package/dist/cli/services/model-router.js +149 -0
  94. package/dist/cli/services/permission-modes.d.ts +13 -0
  95. package/dist/cli/services/permission-modes.js +43 -0
  96. package/dist/cli/services/rewind.d.ts +84 -0
  97. package/dist/cli/services/rewind.js +194 -0
  98. package/dist/cli/services/ripgrep.d.ts +28 -0
  99. package/dist/cli/services/ripgrep.js +138 -0
  100. package/dist/cli/services/sandbox.d.ts +29 -0
  101. package/dist/cli/services/sandbox.js +97 -0
  102. package/dist/cli/services/server-tools.d.ts +61 -0
  103. package/dist/cli/services/server-tools.js +543 -0
  104. package/dist/cli/services/session-persistence.d.ts +23 -0
  105. package/dist/cli/services/session-persistence.js +99 -0
  106. package/dist/cli/services/subagent-worker.d.ts +19 -0
  107. package/dist/cli/services/subagent-worker.js +41 -0
  108. package/dist/cli/services/subagent.d.ts +47 -0
  109. package/dist/cli/services/subagent.js +647 -0
  110. package/dist/cli/services/system-prompt.d.ts +7 -0
  111. package/dist/cli/services/system-prompt.js +198 -0
  112. package/dist/cli/services/team-lead.d.ts +73 -0
  113. package/dist/cli/services/team-lead.js +512 -0
  114. package/dist/cli/services/team-state.d.ts +77 -0
  115. package/dist/cli/services/team-state.js +398 -0
  116. package/dist/cli/services/teammate.d.ts +31 -0
  117. package/dist/cli/services/teammate.js +689 -0
  118. package/dist/cli/services/telemetry.d.ts +61 -0
  119. package/dist/cli/services/telemetry.js +209 -0
  120. package/dist/cli/services/tools/agent-tools.d.ts +14 -0
  121. package/dist/cli/services/tools/agent-tools.js +347 -0
  122. package/dist/cli/services/tools/file-ops.d.ts +15 -0
  123. package/dist/cli/services/tools/file-ops.js +487 -0
  124. package/dist/cli/services/tools/search-tools.d.ts +8 -0
  125. package/dist/cli/services/tools/search-tools.js +186 -0
  126. package/dist/cli/services/tools/shell-exec.d.ts +10 -0
  127. package/dist/cli/services/tools/shell-exec.js +168 -0
  128. package/dist/cli/services/tools/task-manager.d.ts +28 -0
  129. package/dist/cli/services/tools/task-manager.js +209 -0
  130. package/dist/cli/services/tools/web-tools.d.ts +11 -0
  131. package/dist/cli/services/tools/web-tools.js +395 -0
  132. package/dist/cli/setup/SetupApp.d.ts +9 -0
  133. package/dist/cli/setup/SetupApp.js +191 -0
  134. package/dist/cli/shared/MatrixIntro.d.ts +4 -0
  135. package/dist/cli/shared/MatrixIntro.js +83 -0
  136. package/dist/cli/shared/Theme.d.ts +74 -0
  137. package/dist/cli/shared/Theme.js +127 -0
  138. package/dist/cli/shared/WhaleBanner.d.ts +10 -0
  139. package/dist/cli/shared/WhaleBanner.js +12 -0
  140. package/dist/cli/shared/markdown.d.ts +21 -0
  141. package/dist/cli/shared/markdown.js +756 -0
  142. package/dist/cli/status/StatusApp.d.ts +4 -0
  143. package/dist/cli/status/StatusApp.js +105 -0
  144. package/dist/cli/stores/StoreApp.d.ts +7 -0
  145. package/dist/cli/stores/StoreApp.js +81 -0
  146. package/dist/index.d.ts +15 -0
  147. package/dist/index.js +538 -0
  148. package/dist/local-agent/connection.d.ts +48 -0
  149. package/dist/local-agent/connection.js +332 -0
  150. package/dist/local-agent/discovery.d.ts +18 -0
  151. package/dist/local-agent/discovery.js +146 -0
  152. package/dist/local-agent/executor.d.ts +34 -0
  153. package/dist/local-agent/executor.js +241 -0
  154. package/dist/local-agent/index.d.ts +14 -0
  155. package/dist/local-agent/index.js +198 -0
  156. package/dist/node/adapters/base.d.ts +35 -0
  157. package/dist/node/adapters/base.js +10 -0
  158. package/dist/node/adapters/discord.d.ts +29 -0
  159. package/dist/node/adapters/discord.js +299 -0
  160. package/dist/node/adapters/email.d.ts +23 -0
  161. package/dist/node/adapters/email.js +218 -0
  162. package/dist/node/adapters/imessage.d.ts +17 -0
  163. package/dist/node/adapters/imessage.js +118 -0
  164. package/dist/node/adapters/slack.d.ts +26 -0
  165. package/dist/node/adapters/slack.js +259 -0
  166. package/dist/node/adapters/sms.d.ts +23 -0
  167. package/dist/node/adapters/sms.js +161 -0
  168. package/dist/node/adapters/telegram.d.ts +17 -0
  169. package/dist/node/adapters/telegram.js +101 -0
  170. package/dist/node/adapters/webchat.d.ts +27 -0
  171. package/dist/node/adapters/webchat.js +160 -0
  172. package/dist/node/adapters/whatsapp.d.ts +28 -0
  173. package/dist/node/adapters/whatsapp.js +230 -0
  174. package/dist/node/cli.d.ts +2 -0
  175. package/dist/node/cli.js +325 -0
  176. package/dist/node/config.d.ts +17 -0
  177. package/dist/node/config.js +31 -0
  178. package/dist/node/runtime.d.ts +50 -0
  179. package/dist/node/runtime.js +351 -0
  180. package/dist/server/handlers/__test-utils__/mock-supabase.d.ts +11 -0
  181. package/dist/server/handlers/__test-utils__/mock-supabase.js +393 -0
  182. package/dist/server/handlers/analytics.d.ts +17 -0
  183. package/dist/server/handlers/analytics.js +266 -0
  184. package/dist/server/handlers/api-keys.d.ts +6 -0
  185. package/dist/server/handlers/api-keys.js +221 -0
  186. package/dist/server/handlers/billing.d.ts +33 -0
  187. package/dist/server/handlers/billing.js +272 -0
  188. package/dist/server/handlers/browser.d.ts +10 -0
  189. package/dist/server/handlers/browser.js +517 -0
  190. package/dist/server/handlers/catalog.d.ts +99 -0
  191. package/dist/server/handlers/catalog.js +976 -0
  192. package/dist/server/handlers/comms.d.ts +254 -0
  193. package/dist/server/handlers/comms.js +588 -0
  194. package/dist/server/handlers/creations.d.ts +6 -0
  195. package/dist/server/handlers/creations.js +479 -0
  196. package/dist/server/handlers/crm.d.ts +89 -0
  197. package/dist/server/handlers/crm.js +538 -0
  198. package/dist/server/handlers/discovery.d.ts +6 -0
  199. package/dist/server/handlers/discovery.js +288 -0
  200. package/dist/server/handlers/embeddings.d.ts +92 -0
  201. package/dist/server/handlers/embeddings.js +197 -0
  202. package/dist/server/handlers/enrichment.d.ts +8 -0
  203. package/dist/server/handlers/enrichment.js +768 -0
  204. package/dist/server/handlers/image-gen.d.ts +6 -0
  205. package/dist/server/handlers/image-gen.js +409 -0
  206. package/dist/server/handlers/inventory.d.ts +319 -0
  207. package/dist/server/handlers/inventory.js +447 -0
  208. package/dist/server/handlers/kali.d.ts +10 -0
  209. package/dist/server/handlers/kali.js +210 -0
  210. package/dist/server/handlers/llm-providers.d.ts +6 -0
  211. package/dist/server/handlers/llm-providers.js +673 -0
  212. package/dist/server/handlers/local-agent.d.ts +6 -0
  213. package/dist/server/handlers/local-agent.js +118 -0
  214. package/dist/server/handlers/meta-ads.d.ts +111 -0
  215. package/dist/server/handlers/meta-ads.js +2279 -0
  216. package/dist/server/handlers/nodes.d.ts +33 -0
  217. package/dist/server/handlers/nodes.js +699 -0
  218. package/dist/server/handlers/operations.d.ts +138 -0
  219. package/dist/server/handlers/operations.js +131 -0
  220. package/dist/server/handlers/platform.d.ts +23 -0
  221. package/dist/server/handlers/platform.js +227 -0
  222. package/dist/server/handlers/supply-chain.d.ts +19 -0
  223. package/dist/server/handlers/supply-chain.js +327 -0
  224. package/dist/server/handlers/transcription.d.ts +17 -0
  225. package/dist/server/handlers/transcription.js +121 -0
  226. package/dist/server/handlers/video-gen.d.ts +6 -0
  227. package/dist/server/handlers/video-gen.js +466 -0
  228. package/dist/server/handlers/voice.d.ts +8 -0
  229. package/dist/server/handlers/voice.js +1146 -0
  230. package/dist/server/handlers/workflow-steps.d.ts +86 -0
  231. package/dist/server/handlers/workflow-steps.js +2349 -0
  232. package/dist/server/handlers/workflows.d.ts +7 -0
  233. package/dist/server/handlers/workflows.js +989 -0
  234. package/dist/server/index.d.ts +1 -0
  235. package/dist/server/index.js +2427 -0
  236. package/dist/server/lib/batch-client.d.ts +80 -0
  237. package/dist/server/lib/batch-client.js +467 -0
  238. package/dist/server/lib/code-worker-pool.d.ts +31 -0
  239. package/dist/server/lib/code-worker-pool.js +224 -0
  240. package/dist/server/lib/code-worker.d.ts +1 -0
  241. package/dist/server/lib/code-worker.js +188 -0
  242. package/dist/server/lib/compaction-service.d.ts +32 -0
  243. package/dist/server/lib/compaction-service.js +162 -0
  244. package/dist/server/lib/logger.d.ts +19 -0
  245. package/dist/server/lib/logger.js +46 -0
  246. package/dist/server/lib/otel.d.ts +38 -0
  247. package/dist/server/lib/otel.js +126 -0
  248. package/dist/server/lib/pg-rate-limiter.d.ts +21 -0
  249. package/dist/server/lib/pg-rate-limiter.js +86 -0
  250. package/dist/server/lib/prompt-sanitizer.d.ts +37 -0
  251. package/dist/server/lib/prompt-sanitizer.js +177 -0
  252. package/dist/server/lib/provider-capabilities.d.ts +85 -0
  253. package/dist/server/lib/provider-capabilities.js +190 -0
  254. package/dist/server/lib/provider-failover.d.ts +74 -0
  255. package/dist/server/lib/provider-failover.js +210 -0
  256. package/dist/server/lib/rate-limiter.d.ts +39 -0
  257. package/dist/server/lib/rate-limiter.js +147 -0
  258. package/dist/server/lib/server-agent-loop.d.ts +107 -0
  259. package/dist/server/lib/server-agent-loop.js +667 -0
  260. package/dist/server/lib/server-subagent.d.ts +78 -0
  261. package/dist/server/lib/server-subagent.js +203 -0
  262. package/dist/server/lib/session-checkpoint.d.ts +51 -0
  263. package/dist/server/lib/session-checkpoint.js +145 -0
  264. package/dist/server/lib/ssrf-guard.d.ts +13 -0
  265. package/dist/server/lib/ssrf-guard.js +240 -0
  266. package/dist/server/lib/supabase-client.d.ts +7 -0
  267. package/dist/server/lib/supabase-client.js +78 -0
  268. package/dist/server/lib/template-resolver.d.ts +31 -0
  269. package/dist/server/lib/template-resolver.js +215 -0
  270. package/dist/server/lib/utils.d.ts +16 -0
  271. package/dist/server/lib/utils.js +147 -0
  272. package/dist/server/local-agent-gateway.d.ts +82 -0
  273. package/dist/server/local-agent-gateway.js +426 -0
  274. package/dist/server/providers/anthropic.d.ts +20 -0
  275. package/dist/server/providers/anthropic.js +199 -0
  276. package/dist/server/providers/bedrock.d.ts +20 -0
  277. package/dist/server/providers/bedrock.js +194 -0
  278. package/dist/server/providers/gemini.d.ts +24 -0
  279. package/dist/server/providers/gemini.js +486 -0
  280. package/dist/server/providers/openai.d.ts +24 -0
  281. package/dist/server/providers/openai.js +522 -0
  282. package/dist/server/providers/registry.d.ts +32 -0
  283. package/dist/server/providers/registry.js +58 -0
  284. package/dist/server/providers/shared.d.ts +32 -0
  285. package/dist/server/providers/shared.js +124 -0
  286. package/dist/server/providers/types.d.ts +92 -0
  287. package/dist/server/providers/types.js +12 -0
  288. package/dist/server/proxy-handlers.d.ts +6 -0
  289. package/dist/server/proxy-handlers.js +89 -0
  290. package/dist/server/tool-router.d.ts +149 -0
  291. package/dist/server/tool-router.js +803 -0
  292. package/dist/server/validation.d.ts +24 -0
  293. package/dist/server/validation.js +301 -0
  294. package/dist/server/worker.d.ts +19 -0
  295. package/dist/server/worker.js +201 -0
  296. package/dist/setup.d.ts +8 -0
  297. package/dist/setup.js +181 -0
  298. package/dist/shared/agent-core.d.ts +157 -0
  299. package/dist/shared/agent-core.js +534 -0
  300. package/dist/shared/anthropic-types.d.ts +105 -0
  301. package/dist/shared/anthropic-types.js +7 -0
  302. package/dist/shared/api-client.d.ts +90 -0
  303. package/dist/shared/api-client.js +379 -0
  304. package/dist/shared/constants.d.ts +33 -0
  305. package/dist/shared/constants.js +80 -0
  306. package/dist/shared/sse-parser.d.ts +26 -0
  307. package/dist/shared/sse-parser.js +259 -0
  308. package/dist/shared/tool-dispatch.d.ts +52 -0
  309. package/dist/shared/tool-dispatch.js +191 -0
  310. package/dist/shared/types.d.ts +72 -0
  311. package/dist/shared/types.js +7 -0
  312. package/dist/updater.d.ts +25 -0
  313. package/dist/updater.js +140 -0
  314. package/dist/webchat/widget.d.ts +0 -0
  315. package/dist/webchat/widget.js +397 -0
  316. package/package.json +95 -0
  317. package/src/cli/services/builtin-skills/commit.md +19 -0
  318. package/src/cli/services/builtin-skills/review-pr.md +21 -0
  319. package/src/cli/services/builtin-skills/review.md +18 -0
@@ -0,0 +1,299 @@
1
+ import { BaseAdapter } from "./base.js";
2
+ const DISCORD_API = "https://discord.com/api/v10";
3
+ const GATEWAY_URL = "wss://gateway.discord.gg/?v=10&encoding=json";
4
+ const MAX_MESSAGE_LENGTH = 2000;
5
+ // Discord Gateway opcodes
6
+ const OP = {
7
+ DISPATCH: 0,
8
+ HEARTBEAT: 1,
9
+ IDENTIFY: 2,
10
+ RESUME: 6,
11
+ RECONNECT: 7,
12
+ INVALID_SESSION: 9,
13
+ HELLO: 10,
14
+ HEARTBEAT_ACK: 11,
15
+ };
16
+ // Discord Gateway intents
17
+ const INTENTS = {
18
+ GUILDS: 1 << 0,
19
+ GUILD_MESSAGES: 1 << 9,
20
+ MESSAGE_CONTENT: 1 << 15,
21
+ DIRECT_MESSAGES: 1 << 12,
22
+ };
23
+ export class DiscordAdapter extends BaseAdapter {
24
+ type = "discord";
25
+ name;
26
+ config;
27
+ ws = null;
28
+ heartbeatTimer = null;
29
+ sequence = null;
30
+ sessionId = null;
31
+ resumeUrl = null;
32
+ botUserId = null;
33
+ reconnectAttempts = 0;
34
+ constructor(name, config) {
35
+ super();
36
+ this.name = name;
37
+ this.config = config;
38
+ }
39
+ async start() {
40
+ if (this.running)
41
+ return;
42
+ this.running = true;
43
+ this.reconnectAttempts = 0;
44
+ console.log(`[discord] Connecting to gateway...`);
45
+ this.connectGateway(GATEWAY_URL);
46
+ }
47
+ async stop() {
48
+ this.running = false;
49
+ if (this.heartbeatTimer) {
50
+ clearInterval(this.heartbeatTimer);
51
+ this.heartbeatTimer = null;
52
+ }
53
+ if (this.ws) {
54
+ this.ws.close(1000, "Shutting down");
55
+ this.ws = null;
56
+ }
57
+ }
58
+ async sendMessage(msg) {
59
+ try {
60
+ const channelId = msg.metadata?.channel_id || msg.conversation_id;
61
+ if (!channelId) {
62
+ console.error("[discord] No channel_id to send to");
63
+ return false;
64
+ }
65
+ const maxLen = this.config.max_message_length || MAX_MESSAGE_LENGTH;
66
+ let content = msg.content;
67
+ // Split long messages into chunks (Discord 2000 char limit)
68
+ const chunks = [];
69
+ while (content.length > 0) {
70
+ chunks.push(content.slice(0, maxLen));
71
+ content = content.slice(maxLen);
72
+ }
73
+ for (const chunk of chunks) {
74
+ const res = await fetch(`${DISCORD_API}/channels/${channelId}/messages`, {
75
+ method: "POST",
76
+ headers: {
77
+ "Content-Type": "application/json",
78
+ Authorization: `Bot ${this.config.bot_token}`,
79
+ },
80
+ body: JSON.stringify({
81
+ content: chunk,
82
+ // Reply to the original message if we have a message_id
83
+ ...(msg.metadata?.message_id ? {
84
+ message_reference: { message_id: msg.metadata.message_id },
85
+ } : {}),
86
+ }),
87
+ });
88
+ if (!res.ok) {
89
+ const err = await res.text();
90
+ console.error(`[discord] Send failed (${res.status}):`, err);
91
+ this.stats.errors++;
92
+ return false;
93
+ }
94
+ }
95
+ this.stats.messages_out++;
96
+ this.stats.last_message_at = new Date().toISOString();
97
+ return true;
98
+ }
99
+ catch (err) {
100
+ console.error(`[discord] Send error:`, err.message);
101
+ this.stats.errors++;
102
+ return false;
103
+ }
104
+ }
105
+ connectGateway(url) {
106
+ if (!this.running)
107
+ return;
108
+ try {
109
+ this.ws = new WebSocket(url);
110
+ }
111
+ catch (err) {
112
+ console.error(`[discord] WebSocket creation failed:`, err.message);
113
+ this.scheduleReconnect();
114
+ return;
115
+ }
116
+ this.ws.onopen = () => {
117
+ console.log(`[discord] Gateway connected`);
118
+ this.reconnectAttempts = 0;
119
+ };
120
+ this.ws.onmessage = (event) => {
121
+ try {
122
+ const data = JSON.parse(String(event.data));
123
+ this.handleGatewayMessage(data);
124
+ }
125
+ catch (err) {
126
+ console.error(`[discord] Failed to parse gateway message:`, err.message);
127
+ }
128
+ };
129
+ this.ws.onclose = (event) => {
130
+ console.log(`[discord] Gateway closed: ${event.code} ${event.reason}`);
131
+ if (this.heartbeatTimer) {
132
+ clearInterval(this.heartbeatTimer);
133
+ this.heartbeatTimer = null;
134
+ }
135
+ // Non-resumable close codes
136
+ const fatalCodes = [4004, 4010, 4011, 4012, 4013, 4014];
137
+ if (fatalCodes.includes(event.code)) {
138
+ console.error(`[discord] Fatal close code ${event.code}, not reconnecting`);
139
+ this.running = false;
140
+ return;
141
+ }
142
+ if (this.running)
143
+ this.scheduleReconnect();
144
+ };
145
+ this.ws.onerror = () => {
146
+ console.error(`[discord] Gateway error`);
147
+ };
148
+ }
149
+ handleGatewayMessage(data) {
150
+ const { op, d, s, t } = data;
151
+ // Track sequence for resume
152
+ if (s !== null && s !== undefined)
153
+ this.sequence = s;
154
+ switch (op) {
155
+ case OP.HELLO: {
156
+ // Start heartbeating
157
+ const interval = d.heartbeat_interval;
158
+ this.heartbeatTimer = setInterval(() => {
159
+ this.ws?.send(JSON.stringify({ op: OP.HEARTBEAT, d: this.sequence }));
160
+ }, interval);
161
+ // Send initial heartbeat after random jitter
162
+ setTimeout(() => {
163
+ this.ws?.send(JSON.stringify({ op: OP.HEARTBEAT, d: this.sequence }));
164
+ }, Math.random() * interval);
165
+ // Identify or resume
166
+ if (this.sessionId && this.sequence !== null) {
167
+ this.ws?.send(JSON.stringify({
168
+ op: OP.RESUME,
169
+ d: {
170
+ token: this.config.bot_token,
171
+ session_id: this.sessionId,
172
+ seq: this.sequence,
173
+ },
174
+ }));
175
+ }
176
+ else {
177
+ this.ws?.send(JSON.stringify({
178
+ op: OP.IDENTIFY,
179
+ d: {
180
+ token: this.config.bot_token,
181
+ intents: INTENTS.GUILDS | INTENTS.GUILD_MESSAGES | INTENTS.MESSAGE_CONTENT | INTENTS.DIRECT_MESSAGES,
182
+ properties: {
183
+ os: process.platform,
184
+ browser: "whalenode",
185
+ device: "whalenode",
186
+ },
187
+ },
188
+ }));
189
+ }
190
+ break;
191
+ }
192
+ case OP.HEARTBEAT_ACK:
193
+ // Server acknowledged heartbeat — connection is healthy
194
+ break;
195
+ case OP.HEARTBEAT:
196
+ // Server requesting immediate heartbeat
197
+ this.ws?.send(JSON.stringify({ op: OP.HEARTBEAT, d: this.sequence }));
198
+ break;
199
+ case OP.RECONNECT:
200
+ console.log(`[discord] Server requested reconnect`);
201
+ this.ws?.close(4000, "Reconnecting");
202
+ break;
203
+ case OP.INVALID_SESSION:
204
+ console.log(`[discord] Invalid session, re-identifying...`);
205
+ this.sessionId = null;
206
+ this.sequence = null;
207
+ // Wait 1-5s before re-identifying (per Discord docs)
208
+ setTimeout(() => {
209
+ this.ws?.close(4000, "Re-identifying");
210
+ }, 1000 + Math.random() * 4000);
211
+ break;
212
+ case OP.DISPATCH:
213
+ this.handleDispatch(t, d);
214
+ break;
215
+ }
216
+ }
217
+ handleDispatch(eventName, data) {
218
+ switch (eventName) {
219
+ case "READY":
220
+ this.sessionId = data.session_id;
221
+ this.resumeUrl = data.resume_gateway_url;
222
+ this.botUserId = data.user?.id;
223
+ console.log(`[discord] Ready as ${data.user?.username}#${data.user?.discriminator} (${data.guilds?.length || 0} guilds)`);
224
+ break;
225
+ case "RESUMED":
226
+ console.log(`[discord] Session resumed`);
227
+ break;
228
+ case "MESSAGE_CREATE":
229
+ this.handleMessage(data).catch(err => {
230
+ console.error(`[discord] Error handling message:`, err.message);
231
+ this.stats.errors++;
232
+ });
233
+ break;
234
+ }
235
+ }
236
+ async handleMessage(data) {
237
+ // Ignore bot's own messages
238
+ if (data.author?.id === this.botUserId)
239
+ return;
240
+ // Ignore other bots
241
+ if (data.author?.bot)
242
+ return;
243
+ const channelId = data.channel_id;
244
+ const guildId = data.guild_id;
245
+ // Filter by allowed channels
246
+ if (this.config.allowed_channels?.length) {
247
+ if (!this.config.allowed_channels.includes(channelId))
248
+ return;
249
+ }
250
+ // Filter by allowed guilds
251
+ if (this.config.allowed_guilds?.length && guildId) {
252
+ if (!this.config.allowed_guilds.includes(guildId))
253
+ return;
254
+ }
255
+ // Check mention requirement
256
+ if (this.config.mention_required && this.botUserId) {
257
+ const mentioned = data.mentions?.some((m) => m.id === this.botUserId);
258
+ if (!mentioned)
259
+ return;
260
+ }
261
+ // Strip bot mention from content
262
+ let content = data.content || "";
263
+ if (this.botUserId) {
264
+ content = content.replace(new RegExp(`<@!?${this.botUserId}>`, "g"), "").trim();
265
+ }
266
+ if (!content)
267
+ return;
268
+ const payload = {
269
+ sender_id: data.author?.id || "unknown",
270
+ sender_name: data.member?.nick || data.author?.global_name || data.author?.username,
271
+ content,
272
+ metadata: {
273
+ channel_id: channelId,
274
+ guild_id: guildId,
275
+ message_id: data.id,
276
+ is_dm: !guildId,
277
+ },
278
+ };
279
+ this.stats.messages_in++;
280
+ this.stats.last_message_at = new Date().toISOString();
281
+ if (this.onInboundMessage) {
282
+ await this.onInboundMessage(payload);
283
+ }
284
+ }
285
+ scheduleReconnect() {
286
+ if (!this.running)
287
+ return;
288
+ this.reconnectAttempts++;
289
+ const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts - 1), 60_000);
290
+ console.log(`[discord] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})...`);
291
+ setTimeout(() => {
292
+ if (!this.running)
293
+ return;
294
+ // Use resume URL if available, otherwise default gateway
295
+ const url = (this.sessionId && this.resumeUrl) ? this.resumeUrl : GATEWAY_URL;
296
+ this.connectGateway(url);
297
+ }, delay);
298
+ }
299
+ }
@@ -0,0 +1,23 @@
1
+ import { BaseAdapter, type OutboundMessage } from "./base.js";
2
+ export interface EmailConfig {
3
+ api_key: string;
4
+ from_address: string;
5
+ webhook_port?: number;
6
+ webhook_secret?: string;
7
+ }
8
+ export declare class EmailAdapter extends BaseAdapter {
9
+ readonly type = "email";
10
+ readonly name: string;
11
+ private config;
12
+ private server;
13
+ private reconnectAttempts;
14
+ constructor(name: string, config: EmailConfig);
15
+ start(): Promise<void>;
16
+ stop(): Promise<void>;
17
+ sendMessage(msg: OutboundMessage): Promise<boolean>;
18
+ private startWebhookServer;
19
+ /** Handle inbound Resend webhook event (POST with JSON body) */
20
+ private handleIncoming;
21
+ private processWebhookPayload;
22
+ private scheduleRestart;
23
+ }
@@ -0,0 +1,218 @@
1
+ import { createServer } from "node:http";
2
+ import { createHmac } from "node:crypto";
3
+ import { BaseAdapter } from "./base.js";
4
+ const RESEND_API = "https://api.resend.com";
5
+ const DEFAULT_WEBHOOK_PORT = 3102;
6
+ export class EmailAdapter extends BaseAdapter {
7
+ type = "email";
8
+ name;
9
+ config;
10
+ server = null;
11
+ reconnectAttempts = 0;
12
+ constructor(name, config) {
13
+ super();
14
+ this.name = name;
15
+ this.config = config;
16
+ }
17
+ async start() {
18
+ if (this.running)
19
+ return;
20
+ this.running = true;
21
+ this.reconnectAttempts = 0;
22
+ console.log(`[email] Starting Resend webhook server...`);
23
+ this.startWebhookServer();
24
+ }
25
+ async stop() {
26
+ this.running = false;
27
+ if (this.server) {
28
+ await new Promise((resolve) => {
29
+ this.server.close(() => resolve());
30
+ });
31
+ this.server = null;
32
+ }
33
+ console.log(`[email] Webhook server stopped`);
34
+ }
35
+ async sendMessage(msg) {
36
+ try {
37
+ const toAddress = msg.metadata?.from_address || msg.sender_id;
38
+ if (!toAddress) {
39
+ console.error("[email] No recipient email address");
40
+ return false;
41
+ }
42
+ const subject = msg.metadata?.subject || "Re: Your message";
43
+ const res = await fetch(`${RESEND_API}/emails`, {
44
+ method: "POST",
45
+ headers: {
46
+ "Content-Type": "application/json",
47
+ Authorization: `Bearer ${this.config.api_key}`,
48
+ },
49
+ body: JSON.stringify({
50
+ from: this.config.from_address,
51
+ to: [toAddress],
52
+ subject,
53
+ text: msg.content,
54
+ }),
55
+ });
56
+ if (res.ok) {
57
+ this.stats.messages_out++;
58
+ this.stats.last_message_at = new Date().toISOString();
59
+ return true;
60
+ }
61
+ const err = await res.text();
62
+ console.error(`[email] Send failed (${res.status}):`, err);
63
+ this.stats.errors++;
64
+ return false;
65
+ }
66
+ catch (err) {
67
+ console.error(`[email] Send error:`, err.message);
68
+ this.stats.errors++;
69
+ return false;
70
+ }
71
+ }
72
+ startWebhookServer() {
73
+ const port = this.config.webhook_port || DEFAULT_WEBHOOK_PORT;
74
+ this.server = createServer((req, res) => {
75
+ if (req.method === "POST") {
76
+ this.handleIncoming(req, res);
77
+ }
78
+ else {
79
+ // Health check endpoint
80
+ res.writeHead(200, { "Content-Type": "text/plain" });
81
+ res.end("WhaleNode Email adapter running");
82
+ }
83
+ });
84
+ this.server.on("error", (err) => {
85
+ console.error(`[email] Server error:`, err.message);
86
+ this.stats.errors++;
87
+ if (this.running)
88
+ this.scheduleRestart();
89
+ });
90
+ this.server.listen(port, () => {
91
+ console.log(`[email] Resend webhook server listening on port ${port}`);
92
+ });
93
+ }
94
+ /** Handle inbound Resend webhook event (POST with JSON body) */
95
+ handleIncoming(req, res) {
96
+ let body = "";
97
+ req.on("data", (chunk) => {
98
+ body += chunk.toString();
99
+ });
100
+ req.on("end", () => {
101
+ // Verify webhook signature if secret is configured
102
+ if (this.config.webhook_secret) {
103
+ const signature = req.headers["svix-signature"];
104
+ const messageId = req.headers["svix-id"];
105
+ const timestamp = req.headers["svix-timestamp"];
106
+ if (!signature || !messageId || !timestamp) {
107
+ res.writeHead(401);
108
+ res.end("Missing signature headers");
109
+ return;
110
+ }
111
+ const signedContent = `${messageId}.${timestamp}.${body}`;
112
+ const secretBytes = Buffer.from(this.config.webhook_secret.replace("whsec_", ""), "base64");
113
+ const expectedSignature = createHmac("sha256", secretBytes)
114
+ .update(signedContent)
115
+ .digest("base64");
116
+ // Svix sends multiple signatures separated by spaces, each prefixed with "v1,"
117
+ const signatures = signature.split(" ").map((s) => s.replace("v1,", ""));
118
+ const valid = signatures.some((s) => s === expectedSignature);
119
+ if (!valid) {
120
+ console.warn(`[email] Invalid webhook signature`);
121
+ res.writeHead(401);
122
+ res.end("Invalid signature");
123
+ return;
124
+ }
125
+ }
126
+ res.writeHead(200, { "Content-Type": "application/json" });
127
+ res.end(JSON.stringify({ status: "ok" }));
128
+ this.processWebhookPayload(body).catch((err) => {
129
+ console.error(`[email] Error processing webhook:`, err.message);
130
+ this.stats.errors++;
131
+ });
132
+ });
133
+ }
134
+ async processWebhookPayload(raw) {
135
+ let data;
136
+ try {
137
+ data = JSON.parse(raw);
138
+ }
139
+ catch {
140
+ console.error(`[email] Invalid JSON in webhook payload`);
141
+ return;
142
+ }
143
+ // Resend inbound email webhook structure
144
+ // The event type for inbound emails is "email.received"
145
+ const eventType = data.type;
146
+ if (eventType !== "email.received") {
147
+ // Ignore non-inbound events (delivery status, bounces, etc.)
148
+ return;
149
+ }
150
+ const emailData = data.data;
151
+ if (!emailData)
152
+ return;
153
+ const fromAddress = emailData.from;
154
+ const toAddress = emailData.to;
155
+ const subject = emailData.subject || "";
156
+ // Extract text content from the email body
157
+ // Resend provides text and/or html fields
158
+ let content = emailData.text || "";
159
+ if (!content && emailData.html) {
160
+ // Basic HTML-to-text: strip tags and decode common entities
161
+ content = emailData.html
162
+ .replace(/<br\s*\/?>/gi, "\n")
163
+ .replace(/<\/p>/gi, "\n\n")
164
+ .replace(/<[^>]+>/g, "")
165
+ .replace(/&amp;/g, "&")
166
+ .replace(/&lt;/g, "<")
167
+ .replace(/&gt;/g, ">")
168
+ .replace(/&quot;/g, "\"")
169
+ .replace(/&#39;/g, "'")
170
+ .replace(/&nbsp;/g, " ")
171
+ .trim();
172
+ }
173
+ if (!content && subject) {
174
+ content = subject;
175
+ }
176
+ if (!fromAddress || !content)
177
+ return;
178
+ const payload = {
179
+ sender_id: fromAddress,
180
+ content,
181
+ metadata: {
182
+ from_address: fromAddress,
183
+ to_address: toAddress,
184
+ subject,
185
+ email_id: emailData.id,
186
+ created_at: emailData.created_at,
187
+ attachments: emailData.attachments?.length || 0,
188
+ },
189
+ };
190
+ this.stats.messages_in++;
191
+ this.stats.last_message_at = new Date().toISOString();
192
+ if (this.onInboundMessage) {
193
+ await this.onInboundMessage(payload).catch((err) => {
194
+ console.error(`[email] Error handling message:`, err.message);
195
+ this.stats.errors++;
196
+ });
197
+ }
198
+ }
199
+ scheduleRestart() {
200
+ if (!this.running)
201
+ return;
202
+ this.reconnectAttempts++;
203
+ const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts - 1), 60_000);
204
+ console.log(`[email] Restarting webhook server in ${delay}ms (attempt ${this.reconnectAttempts})...`);
205
+ setTimeout(() => {
206
+ if (!this.running)
207
+ return;
208
+ if (this.server) {
209
+ this.server.close(() => {
210
+ this.startWebhookServer();
211
+ });
212
+ }
213
+ else {
214
+ this.startWebhookServer();
215
+ }
216
+ }, delay);
217
+ }
218
+ }
@@ -0,0 +1,17 @@
1
+ import { BaseAdapter, type OutboundMessage } from "./base.js";
2
+ export interface IMessageConfig {
3
+ watch_groups?: number[];
4
+ mention_required?: string;
5
+ max_message_length?: number;
6
+ }
7
+ export declare class IMessageAdapter extends BaseAdapter {
8
+ readonly type = "imessage";
9
+ readonly name: string;
10
+ private process;
11
+ private config;
12
+ constructor(name: string, config?: IMessageConfig);
13
+ start(): Promise<void>;
14
+ stop(): Promise<void>;
15
+ sendMessage(msg: OutboundMessage): Promise<boolean>;
16
+ private handleLine;
17
+ }
@@ -0,0 +1,118 @@
1
+ import { spawn } from "node:child_process";
2
+ import { BaseAdapter } from "./base.js";
3
+ export class IMessageAdapter extends BaseAdapter {
4
+ type = "imessage";
5
+ name;
6
+ process = null;
7
+ config;
8
+ constructor(name, config = {}) {
9
+ super();
10
+ this.name = name;
11
+ this.config = config;
12
+ }
13
+ async start() {
14
+ if (this.running)
15
+ return;
16
+ this.running = true;
17
+ // Spawn imsg watch as a child process
18
+ const args = ["watch", "--json"];
19
+ this.process = spawn("imsg", args, { stdio: ["ignore", "pipe", "pipe"] });
20
+ let buffer = "";
21
+ this.process.stdout?.on("data", (chunk) => {
22
+ buffer += chunk.toString();
23
+ const lines = buffer.split("\n");
24
+ buffer = lines.pop() || "";
25
+ for (const line of lines) {
26
+ if (!line.trim())
27
+ continue;
28
+ this.handleLine(line).catch(err => {
29
+ console.error(`[imessage] Error handling message:`, err.message);
30
+ this.stats.errors++;
31
+ });
32
+ }
33
+ });
34
+ this.process.stderr?.on("data", (chunk) => {
35
+ console.error(`[imessage] stderr:`, chunk.toString().trim());
36
+ });
37
+ this.process.on("exit", (code) => {
38
+ console.log(`[imessage] Process exited with code ${code}`);
39
+ const wasRunning = this.running;
40
+ this.running = false;
41
+ // Auto-restart after 5s if unexpected exit while we were supposed to be running
42
+ if (code !== 0 && wasRunning) {
43
+ console.log(`[imessage] Restarting in 5s...`);
44
+ setTimeout(() => this.start(), 5000);
45
+ }
46
+ });
47
+ console.log(`[imessage] Watching for messages...`);
48
+ }
49
+ async stop() {
50
+ this.running = false;
51
+ if (this.process) {
52
+ this.process.kill("SIGTERM");
53
+ this.process = null;
54
+ }
55
+ }
56
+ async sendMessage(msg) {
57
+ try {
58
+ const text = msg.content.substring(0, this.config.max_message_length || 4000);
59
+ const chatId = msg.metadata?.chat_id || msg.conversation_id;
60
+ if (!chatId) {
61
+ console.error("[imessage] No chat_id to send to");
62
+ return false;
63
+ }
64
+ const proc = spawn("imsg", ["send", "--chat", String(chatId), text]);
65
+ return new Promise((resolve) => {
66
+ proc.on("exit", (code) => {
67
+ if (code === 0) {
68
+ this.stats.messages_out++;
69
+ this.stats.last_message_at = new Date().toISOString();
70
+ resolve(true);
71
+ }
72
+ else {
73
+ this.stats.errors++;
74
+ resolve(false);
75
+ }
76
+ });
77
+ });
78
+ }
79
+ catch (err) {
80
+ this.stats.errors++;
81
+ return false;
82
+ }
83
+ }
84
+ async handleLine(line) {
85
+ let data;
86
+ try {
87
+ data = JSON.parse(line);
88
+ }
89
+ catch {
90
+ return; // Not JSON
91
+ }
92
+ // Filter by watched groups
93
+ if (this.config.watch_groups?.length) {
94
+ if (!this.config.watch_groups.includes(data.chat_id))
95
+ return;
96
+ }
97
+ // Check mention requirement
98
+ if (this.config.mention_required) {
99
+ if (!data.text?.includes(this.config.mention_required))
100
+ return;
101
+ }
102
+ const payload = {
103
+ sender_id: data.sender || data.handle || "unknown",
104
+ sender_name: data.sender_name,
105
+ content: data.text || "",
106
+ metadata: {
107
+ chat_id: data.chat_id,
108
+ group: data.group,
109
+ attachments: data.attachments,
110
+ },
111
+ };
112
+ this.stats.messages_in++;
113
+ this.stats.last_message_at = new Date().toISOString();
114
+ if (this.onInboundMessage) {
115
+ await this.onInboundMessage(payload);
116
+ }
117
+ }
118
+ }