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,160 @@
1
+ import http from "node:http";
2
+ import { BaseAdapter } from "./base.js";
3
+ const DEFAULT_PORT = 3100;
4
+ const MAX_MESSAGE_LENGTH = 8000;
5
+ /**
6
+ * WebChat adapter — runs a local HTTP server that serves a chat widget API.
7
+ * Customers connect via embedded JS widget on any website.
8
+ *
9
+ * Endpoints:
10
+ * POST /message — send a message (returns agent response)
11
+ * GET /health — health check
12
+ * OPTIONS * — CORS preflight
13
+ */
14
+ export class WebchatAdapter extends BaseAdapter {
15
+ type = "webchat";
16
+ name;
17
+ config;
18
+ server = null;
19
+ port;
20
+ // Queue for outbound messages (agent responses pushed from server polling)
21
+ outboundQueue = new Map();
22
+ constructor(name, config = {}) {
23
+ super();
24
+ this.name = name;
25
+ this.config = config;
26
+ this.port = config.port || DEFAULT_PORT;
27
+ }
28
+ async start() {
29
+ if (this.running)
30
+ return;
31
+ this.running = true;
32
+ const origins = this.config.allowed_origins || ["*"];
33
+ this.server = http.createServer(async (req, res) => {
34
+ // CORS headers
35
+ const origin = req.headers.origin || "*";
36
+ const allowedOrigin = origins.includes("*") ? "*" : (origins.includes(origin) ? origin : "");
37
+ res.setHeader("Access-Control-Allow-Origin", allowedOrigin);
38
+ res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
39
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Session-Id");
40
+ if (req.method === "OPTIONS") {
41
+ res.writeHead(204);
42
+ res.end();
43
+ return;
44
+ }
45
+ if (req.method === "GET" && req.url === "/health") {
46
+ res.writeHead(200, { "Content-Type": "application/json" });
47
+ res.end(JSON.stringify({ status: "ok", adapter: this.name, stats: this.getStats() }));
48
+ return;
49
+ }
50
+ // GET /messages?session_id=xxx — poll for queued outbound messages
51
+ if (req.method === "GET" && req.url?.startsWith("/messages")) {
52
+ const url = new URL(req.url, `http://localhost:${this.port}`);
53
+ const sessionId = url.searchParams.get("session_id");
54
+ if (!sessionId) {
55
+ res.writeHead(400, { "Content-Type": "application/json" });
56
+ res.end(JSON.stringify({ error: "session_id required" }));
57
+ return;
58
+ }
59
+ const messages = this.outboundQueue.get(sessionId) || [];
60
+ this.outboundQueue.delete(sessionId);
61
+ res.writeHead(200, { "Content-Type": "application/json" });
62
+ res.end(JSON.stringify({ messages }));
63
+ return;
64
+ }
65
+ if (req.method === "POST" && req.url === "/message") {
66
+ try {
67
+ const body = await readBody(req);
68
+ const data = JSON.parse(body);
69
+ if (!data.content || !data.session_id) {
70
+ res.writeHead(400, { "Content-Type": "application/json" });
71
+ res.end(JSON.stringify({ error: "content and session_id required" }));
72
+ return;
73
+ }
74
+ const maxLen = this.config.max_message_length || MAX_MESSAGE_LENGTH;
75
+ const content = data.content.substring(0, maxLen);
76
+ const payload = {
77
+ sender_id: data.session_id,
78
+ sender_name: data.sender_name || "Visitor",
79
+ content,
80
+ metadata: {
81
+ session_id: data.session_id,
82
+ page_url: data.page_url,
83
+ user_agent: req.headers["user-agent"],
84
+ },
85
+ };
86
+ this.stats.messages_in++;
87
+ this.stats.last_message_at = new Date().toISOString();
88
+ if (this.onInboundMessage) {
89
+ await this.onInboundMessage(payload);
90
+ }
91
+ res.writeHead(200, { "Content-Type": "application/json" });
92
+ res.end(JSON.stringify({ success: true }));
93
+ }
94
+ catch (err) {
95
+ this.stats.errors++;
96
+ res.writeHead(500, { "Content-Type": "application/json" });
97
+ res.end(JSON.stringify({ error: "Internal error" }));
98
+ }
99
+ return;
100
+ }
101
+ res.writeHead(404);
102
+ res.end("Not found");
103
+ });
104
+ this.server.listen(this.port, () => {
105
+ console.log(`[webchat] Server listening on port ${this.port}`);
106
+ });
107
+ this.server.on("error", (err) => {
108
+ console.error(`[webchat] Server error:`, err.message);
109
+ this.stats.errors++;
110
+ });
111
+ }
112
+ async stop() {
113
+ this.running = false;
114
+ if (this.server) {
115
+ this.server.close();
116
+ this.server = null;
117
+ }
118
+ }
119
+ async sendMessage(msg) {
120
+ try {
121
+ // Queue message for polling by the web client
122
+ const sessionId = msg.metadata?.session_id || msg.conversation_id || "default";
123
+ if (!this.outboundQueue.has(sessionId)) {
124
+ this.outboundQueue.set(sessionId, []);
125
+ }
126
+ this.outboundQueue.get(sessionId).push(msg);
127
+ this.stats.messages_out++;
128
+ this.stats.last_message_at = new Date().toISOString();
129
+ // Auto-cleanup old queued messages (> 5 min old)
130
+ // Simple TTL: clear queue entries older than 5 minutes
131
+ if (this.outboundQueue.size > 1000) {
132
+ const oldest = Array.from(this.outboundQueue.keys()).slice(0, 500);
133
+ for (const key of oldest)
134
+ this.outboundQueue.delete(key);
135
+ }
136
+ return true;
137
+ }
138
+ catch {
139
+ this.stats.errors++;
140
+ return false;
141
+ }
142
+ }
143
+ }
144
+ function readBody(req) {
145
+ return new Promise((resolve, reject) => {
146
+ const chunks = [];
147
+ let size = 0;
148
+ req.on("data", (chunk) => {
149
+ size += chunk.length;
150
+ if (size > 1_048_576) { // 1MB limit
151
+ req.destroy();
152
+ reject(new Error("Body too large"));
153
+ return;
154
+ }
155
+ chunks.push(chunk);
156
+ });
157
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
158
+ req.on("error", reject);
159
+ });
160
+ }
@@ -0,0 +1,28 @@
1
+ import { BaseAdapter, type OutboundMessage } from "./base.js";
2
+ export interface WhatsAppConfig {
3
+ phone_number_id: string;
4
+ access_token: string;
5
+ verify_token: string;
6
+ webhook_port?: number;
7
+ allowed_numbers?: string[];
8
+ }
9
+ export declare class WhatsAppAdapter extends BaseAdapter {
10
+ readonly type = "whatsapp";
11
+ readonly name: string;
12
+ private config;
13
+ private server;
14
+ private reconnectAttempts;
15
+ constructor(name: string, config: WhatsAppConfig);
16
+ start(): Promise<void>;
17
+ stop(): Promise<void>;
18
+ sendMessage(msg: OutboundMessage): Promise<boolean>;
19
+ private startWebhookServer;
20
+ /** Handle WhatsApp webhook verification (GET request) */
21
+ private handleVerification;
22
+ /** Handle inbound WhatsApp webhook events (POST request) */
23
+ private handleIncoming;
24
+ private processWebhookPayload;
25
+ /** Mark a WhatsApp message as read */
26
+ private markAsRead;
27
+ private scheduleRestart;
28
+ }
@@ -0,0 +1,230 @@
1
+ import { createServer } from "node:http";
2
+ import { BaseAdapter } from "./base.js";
3
+ const GRAPH_API = "https://graph.facebook.com/v21.0";
4
+ const DEFAULT_WEBHOOK_PORT = 3100;
5
+ export class WhatsAppAdapter extends BaseAdapter {
6
+ type = "whatsapp";
7
+ name;
8
+ config;
9
+ server = null;
10
+ reconnectAttempts = 0;
11
+ constructor(name, config) {
12
+ super();
13
+ this.name = name;
14
+ this.config = config;
15
+ }
16
+ async start() {
17
+ if (this.running)
18
+ return;
19
+ this.running = true;
20
+ this.reconnectAttempts = 0;
21
+ console.log(`[whatsapp] Starting webhook server...`);
22
+ this.startWebhookServer();
23
+ }
24
+ async stop() {
25
+ this.running = false;
26
+ if (this.server) {
27
+ await new Promise((resolve) => {
28
+ this.server.close(() => resolve());
29
+ });
30
+ this.server = null;
31
+ }
32
+ console.log(`[whatsapp] Webhook server stopped`);
33
+ }
34
+ async sendMessage(msg) {
35
+ try {
36
+ const recipientPhone = msg.metadata?.from_number || msg.sender_id;
37
+ if (!recipientPhone) {
38
+ console.error("[whatsapp] No recipient phone number");
39
+ return false;
40
+ }
41
+ const res = await fetch(`${GRAPH_API}/${this.config.phone_number_id}/messages`, {
42
+ method: "POST",
43
+ headers: {
44
+ "Content-Type": "application/json",
45
+ Authorization: `Bearer ${this.config.access_token}`,
46
+ },
47
+ body: JSON.stringify({
48
+ messaging_product: "whatsapp",
49
+ recipient_type: "individual",
50
+ to: recipientPhone,
51
+ type: "text",
52
+ text: { body: msg.content },
53
+ }),
54
+ });
55
+ if (res.ok) {
56
+ this.stats.messages_out++;
57
+ this.stats.last_message_at = new Date().toISOString();
58
+ return true;
59
+ }
60
+ const err = await res.text();
61
+ console.error(`[whatsapp] Send failed (${res.status}):`, err);
62
+ this.stats.errors++;
63
+ return false;
64
+ }
65
+ catch (err) {
66
+ console.error(`[whatsapp] Send error:`, err.message);
67
+ this.stats.errors++;
68
+ return false;
69
+ }
70
+ }
71
+ startWebhookServer() {
72
+ const port = this.config.webhook_port || DEFAULT_WEBHOOK_PORT;
73
+ this.server = createServer((req, res) => {
74
+ if (req.method === "GET") {
75
+ this.handleVerification(req, res);
76
+ }
77
+ else if (req.method === "POST") {
78
+ this.handleIncoming(req, res);
79
+ }
80
+ else {
81
+ res.writeHead(405);
82
+ res.end();
83
+ }
84
+ });
85
+ this.server.on("error", (err) => {
86
+ console.error(`[whatsapp] Server error:`, err.message);
87
+ this.stats.errors++;
88
+ if (this.running)
89
+ this.scheduleRestart();
90
+ });
91
+ this.server.listen(port, () => {
92
+ console.log(`[whatsapp] Webhook server listening on port ${port}`);
93
+ });
94
+ }
95
+ /** Handle WhatsApp webhook verification (GET request) */
96
+ handleVerification(req, res) {
97
+ const url = new URL(req.url || "/", `http://localhost`);
98
+ const mode = url.searchParams.get("hub.mode");
99
+ const token = url.searchParams.get("hub.verify_token");
100
+ const challenge = url.searchParams.get("hub.challenge");
101
+ if (mode === "subscribe" && token === this.config.verify_token) {
102
+ console.log(`[whatsapp] Webhook verified`);
103
+ res.writeHead(200, { "Content-Type": "text/plain" });
104
+ res.end(challenge || "");
105
+ }
106
+ else {
107
+ console.warn(`[whatsapp] Webhook verification failed`);
108
+ res.writeHead(403);
109
+ res.end("Forbidden");
110
+ }
111
+ }
112
+ /** Handle inbound WhatsApp webhook events (POST request) */
113
+ handleIncoming(req, res) {
114
+ let body = "";
115
+ req.on("data", (chunk) => {
116
+ body += chunk.toString();
117
+ });
118
+ req.on("end", () => {
119
+ // Respond immediately to avoid WhatsApp retries
120
+ res.writeHead(200, { "Content-Type": "application/json" });
121
+ res.end(JSON.stringify({ status: "ok" }));
122
+ this.processWebhookPayload(body).catch((err) => {
123
+ console.error(`[whatsapp] Error processing webhook:`, err.message);
124
+ this.stats.errors++;
125
+ });
126
+ });
127
+ }
128
+ async processWebhookPayload(raw) {
129
+ let data;
130
+ try {
131
+ data = JSON.parse(raw);
132
+ }
133
+ catch {
134
+ console.error(`[whatsapp] Invalid JSON in webhook payload`);
135
+ return;
136
+ }
137
+ // WhatsApp Cloud API webhook structure
138
+ const entries = data.entry;
139
+ if (!Array.isArray(entries))
140
+ return;
141
+ for (const entry of entries) {
142
+ const changes = entry.changes;
143
+ if (!Array.isArray(changes))
144
+ continue;
145
+ for (const change of changes) {
146
+ if (change.field !== "messages")
147
+ continue;
148
+ const value = change.value;
149
+ if (!value?.messages)
150
+ continue;
151
+ for (const message of value.messages) {
152
+ // Only handle text messages
153
+ if (message.type !== "text")
154
+ continue;
155
+ const fromNumber = message.from;
156
+ // Filter by allowed numbers if configured
157
+ if (this.config.allowed_numbers?.length) {
158
+ if (!this.config.allowed_numbers.includes(fromNumber))
159
+ continue;
160
+ }
161
+ // Extract sender name from contacts
162
+ const contact = value.contacts?.find((c) => c.wa_id === fromNumber);
163
+ const senderName = contact?.profile?.name;
164
+ const payload = {
165
+ sender_id: fromNumber,
166
+ sender_name: senderName,
167
+ content: message.text?.body || "",
168
+ metadata: {
169
+ from_number: fromNumber,
170
+ message_id: message.id,
171
+ timestamp: message.timestamp,
172
+ phone_number_id: value.metadata?.phone_number_id,
173
+ },
174
+ };
175
+ this.stats.messages_in++;
176
+ this.stats.last_message_at = new Date().toISOString();
177
+ // Mark message as read
178
+ this.markAsRead(message.id).catch(() => {
179
+ // Best effort — non-critical
180
+ });
181
+ if (this.onInboundMessage) {
182
+ await this.onInboundMessage(payload).catch((err) => {
183
+ console.error(`[whatsapp] Error handling message:`, err.message);
184
+ this.stats.errors++;
185
+ });
186
+ }
187
+ }
188
+ }
189
+ }
190
+ }
191
+ /** Mark a WhatsApp message as read */
192
+ async markAsRead(messageId) {
193
+ try {
194
+ await fetch(`${GRAPH_API}/${this.config.phone_number_id}/messages`, {
195
+ method: "POST",
196
+ headers: {
197
+ "Content-Type": "application/json",
198
+ Authorization: `Bearer ${this.config.access_token}`,
199
+ },
200
+ body: JSON.stringify({
201
+ messaging_product: "whatsapp",
202
+ status: "read",
203
+ message_id: messageId,
204
+ }),
205
+ });
206
+ }
207
+ catch {
208
+ // Silently ignore read receipt failures
209
+ }
210
+ }
211
+ scheduleRestart() {
212
+ if (!this.running)
213
+ return;
214
+ this.reconnectAttempts++;
215
+ const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts - 1), 60_000);
216
+ console.log(`[whatsapp] Restarting webhook server in ${delay}ms (attempt ${this.reconnectAttempts})...`);
217
+ setTimeout(() => {
218
+ if (!this.running)
219
+ return;
220
+ if (this.server) {
221
+ this.server.close(() => {
222
+ this.startWebhookServer();
223
+ });
224
+ }
225
+ else {
226
+ this.startWebhookServer();
227
+ }
228
+ }, delay);
229
+ }
230
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};