@fortressllm/sybil 0.0.3

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 (288) hide show
  1. package/.env copy +91 -0
  2. package/.env.example +139 -0
  3. package/BROWSER_CONTROL.md +354 -0
  4. package/CLI_CHAT_FEATURE.md +224 -0
  5. package/CLI_GUIDE.md +359 -0
  6. package/DYNAMIC_SKILLS.md +345 -0
  7. package/DockerFile.sandbox +14 -0
  8. package/PROGRESS.md +249 -0
  9. package/README.md +281 -0
  10. package/RENAME_LOG.md +62 -0
  11. package/SIMPLIFIED_TELEGRAM_UX.md +273 -0
  12. package/SYBIL_SUMMARY.md +360 -0
  13. package/TASK11_NETWORK.md +202 -0
  14. package/TASK14_CLI.md +432 -0
  15. package/TASK8_SAFETY.md +317 -0
  16. package/TASK9_COMPLETION.md +186 -0
  17. package/TASK9_SUMMARY.md +201 -0
  18. package/TELEGRAM_OTP_AUTH.md +359 -0
  19. package/VECTOR_MEMORY.md +163 -0
  20. package/assets/logo.png +0 -0
  21. package/cypfq_code_search.md +287 -0
  22. package/cypfq_driver_search.md +297 -0
  23. package/cypfq_github_search.md +297 -0
  24. package/cypfq_repo_search.md +370 -0
  25. package/dist/agents/autonomous-agent.d.ts +61 -0
  26. package/dist/agents/autonomous-agent.d.ts.map +1 -0
  27. package/dist/agents/autonomous-agent.js +536 -0
  28. package/dist/agents/autonomous-agent.js.map +1 -0
  29. package/dist/agents/network.d.ts +1006 -0
  30. package/dist/agents/network.d.ts.map +1 -0
  31. package/dist/agents/network.js +1266 -0
  32. package/dist/agents/network.js.map +1 -0
  33. package/dist/cli/commands/backup.d.ts +3 -0
  34. package/dist/cli/commands/backup.d.ts.map +1 -0
  35. package/dist/cli/commands/backup.js +63 -0
  36. package/dist/cli/commands/backup.js.map +1 -0
  37. package/dist/cli/commands/config.d.ts +3 -0
  38. package/dist/cli/commands/config.d.ts.map +1 -0
  39. package/dist/cli/commands/config.js +163 -0
  40. package/dist/cli/commands/config.js.map +1 -0
  41. package/dist/cli/commands/doctor.d.ts +3 -0
  42. package/dist/cli/commands/doctor.d.ts.map +1 -0
  43. package/dist/cli/commands/doctor.js +107 -0
  44. package/dist/cli/commands/doctor.js.map +1 -0
  45. package/dist/cli/commands/init.d.ts +3 -0
  46. package/dist/cli/commands/init.d.ts.map +1 -0
  47. package/dist/cli/commands/init.js +138 -0
  48. package/dist/cli/commands/init.js.map +1 -0
  49. package/dist/cli/commands/logs.d.ts +3 -0
  50. package/dist/cli/commands/logs.d.ts.map +1 -0
  51. package/dist/cli/commands/logs.js +81 -0
  52. package/dist/cli/commands/logs.js.map +1 -0
  53. package/dist/cli/commands/otp.d.ts +3 -0
  54. package/dist/cli/commands/otp.d.ts.map +1 -0
  55. package/dist/cli/commands/otp.js +142 -0
  56. package/dist/cli/commands/otp.js.map +1 -0
  57. package/dist/cli/commands/restore.d.ts +3 -0
  58. package/dist/cli/commands/restore.d.ts.map +1 -0
  59. package/dist/cli/commands/restore.js +99 -0
  60. package/dist/cli/commands/restore.js.map +1 -0
  61. package/dist/cli/commands/start.d.ts +3 -0
  62. package/dist/cli/commands/start.d.ts.map +1 -0
  63. package/dist/cli/commands/start.js +65 -0
  64. package/dist/cli/commands/start.js.map +1 -0
  65. package/dist/cli/commands/status.d.ts +3 -0
  66. package/dist/cli/commands/status.d.ts.map +1 -0
  67. package/dist/cli/commands/status.js +68 -0
  68. package/dist/cli/commands/status.js.map +1 -0
  69. package/dist/cli/commands/stop.d.ts +3 -0
  70. package/dist/cli/commands/stop.d.ts.map +1 -0
  71. package/dist/cli/commands/stop.js +62 -0
  72. package/dist/cli/commands/stop.js.map +1 -0
  73. package/dist/cli/commands/update.d.ts +3 -0
  74. package/dist/cli/commands/update.d.ts.map +1 -0
  75. package/dist/cli/commands/update.js +49 -0
  76. package/dist/cli/commands/update.js.map +1 -0
  77. package/dist/cli/commands/whatsapp.d.ts +3 -0
  78. package/dist/cli/commands/whatsapp.d.ts.map +1 -0
  79. package/dist/cli/commands/whatsapp.js +281 -0
  80. package/dist/cli/commands/whatsapp.js.map +1 -0
  81. package/dist/cli/index.d.ts +7 -0
  82. package/dist/cli/index.d.ts.map +1 -0
  83. package/dist/cli/index.js +58 -0
  84. package/dist/cli/index.js.map +1 -0
  85. package/dist/cli.d.ts +9 -0
  86. package/dist/cli.d.ts.map +1 -0
  87. package/dist/cli.js +750 -0
  88. package/dist/cli.js.map +1 -0
  89. package/dist/index.d.ts +2 -0
  90. package/dist/index.d.ts.map +1 -0
  91. package/dist/index.js +109 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/mastra/index.d.ts +4 -0
  94. package/dist/mastra/index.d.ts.map +1 -0
  95. package/dist/mastra/index.js +37 -0
  96. package/dist/mastra/index.js.map +1 -0
  97. package/dist/mastra/memory.d.ts +9 -0
  98. package/dist/mastra/memory.d.ts.map +1 -0
  99. package/dist/mastra/memory.js +92 -0
  100. package/dist/mastra/memory.js.map +1 -0
  101. package/dist/processors/index.d.ts +74 -0
  102. package/dist/processors/index.d.ts.map +1 -0
  103. package/dist/processors/index.js +153 -0
  104. package/dist/processors/index.js.map +1 -0
  105. package/dist/processors/semantic-recall.d.ts +63 -0
  106. package/dist/processors/semantic-recall.d.ts.map +1 -0
  107. package/dist/processors/semantic-recall.js +216 -0
  108. package/dist/processors/semantic-recall.js.map +1 -0
  109. package/dist/processors/tool-search.d.ts +26 -0
  110. package/dist/processors/tool-search.d.ts.map +1 -0
  111. package/dist/processors/tool-search.js +41 -0
  112. package/dist/processors/tool-search.js.map +1 -0
  113. package/dist/skills/dynamic/skill-generator.d.ts +169 -0
  114. package/dist/skills/dynamic/skill-generator.d.ts.map +1 -0
  115. package/dist/skills/dynamic/skill-generator.js +488 -0
  116. package/dist/skills/dynamic/skill-generator.js.map +1 -0
  117. package/dist/tools/agent-delegation-tools.d.ts +142 -0
  118. package/dist/tools/agent-delegation-tools.d.ts.map +1 -0
  119. package/dist/tools/agent-delegation-tools.js +263 -0
  120. package/dist/tools/agent-delegation-tools.js.map +1 -0
  121. package/dist/tools/browser-tools.d.ts +374 -0
  122. package/dist/tools/browser-tools.d.ts.map +1 -0
  123. package/dist/tools/browser-tools.js +752 -0
  124. package/dist/tools/browser-tools.js.map +1 -0
  125. package/dist/tools/dynamic/registry.d.ts +61 -0
  126. package/dist/tools/dynamic/registry.d.ts.map +1 -0
  127. package/dist/tools/dynamic/registry.js +121 -0
  128. package/dist/tools/dynamic/registry.js.map +1 -0
  129. package/dist/tools/dynamic/tool-generator.d.ts +99 -0
  130. package/dist/tools/dynamic/tool-generator.d.ts.map +1 -0
  131. package/dist/tools/dynamic/tool-generator.js +367 -0
  132. package/dist/tools/dynamic/tool-generator.js.map +1 -0
  133. package/dist/tools/extended-tools.d.ts +176 -0
  134. package/dist/tools/extended-tools.d.ts.map +1 -0
  135. package/dist/tools/extended-tools.js +464 -0
  136. package/dist/tools/extended-tools.js.map +1 -0
  137. package/dist/tools/library/calendar/index.d.ts +134 -0
  138. package/dist/tools/library/calendar/index.d.ts.map +1 -0
  139. package/dist/tools/library/calendar/index.js +160 -0
  140. package/dist/tools/library/calendar/index.js.map +1 -0
  141. package/dist/tools/podman-workspace-mcp-cli.d.ts +3 -0
  142. package/dist/tools/podman-workspace-mcp-cli.d.ts.map +1 -0
  143. package/dist/tools/podman-workspace-mcp-cli.js +12 -0
  144. package/dist/tools/podman-workspace-mcp-cli.js.map +1 -0
  145. package/dist/tools/podman-workspace-mcp.d.ts +247 -0
  146. package/dist/tools/podman-workspace-mcp.d.ts.map +1 -0
  147. package/dist/tools/podman-workspace-mcp.js +1093 -0
  148. package/dist/tools/podman-workspace-mcp.js.map +1 -0
  149. package/dist/tools/podman-workspace.d.ts +148 -0
  150. package/dist/tools/podman-workspace.d.ts.map +1 -0
  151. package/dist/tools/podman-workspace.js +682 -0
  152. package/dist/tools/podman-workspace.js.map +1 -0
  153. package/dist/tools/telegram-file-tools.d.ts +78 -0
  154. package/dist/tools/telegram-file-tools.d.ts.map +1 -0
  155. package/dist/tools/telegram-file-tools.js +294 -0
  156. package/dist/tools/telegram-file-tools.js.map +1 -0
  157. package/dist/tools/tool-registry.d.ts +467 -0
  158. package/dist/tools/tool-registry.d.ts.map +1 -0
  159. package/dist/tools/tool-registry.js +156 -0
  160. package/dist/tools/tool-registry.js.map +1 -0
  161. package/dist/tools/web-tools.d.ts +77 -0
  162. package/dist/tools/web-tools.d.ts.map +1 -0
  163. package/dist/tools/web-tools.js +416 -0
  164. package/dist/tools/web-tools.js.map +1 -0
  165. package/dist/tools/whatsapp-autoreply-tools.d.ts +118 -0
  166. package/dist/tools/whatsapp-autoreply-tools.d.ts.map +1 -0
  167. package/dist/tools/whatsapp-autoreply-tools.js +503 -0
  168. package/dist/tools/whatsapp-autoreply-tools.js.map +1 -0
  169. package/dist/tools/whatsapp-tools.d.ts +175 -0
  170. package/dist/tools/whatsapp-tools.d.ts.map +1 -0
  171. package/dist/tools/whatsapp-tools.js +566 -0
  172. package/dist/tools/whatsapp-tools.js.map +1 -0
  173. package/dist/utils/logger.d.ts +65 -0
  174. package/dist/utils/logger.d.ts.map +1 -0
  175. package/dist/utils/logger.js +307 -0
  176. package/dist/utils/logger.js.map +1 -0
  177. package/dist/utils/model-config.d.ts +73 -0
  178. package/dist/utils/model-config.d.ts.map +1 -0
  179. package/dist/utils/model-config.js +366 -0
  180. package/dist/utils/model-config.js.map +1 -0
  181. package/dist/utils/semantic-memory.d.ts +82 -0
  182. package/dist/utils/semantic-memory.d.ts.map +1 -0
  183. package/dist/utils/semantic-memory.js +189 -0
  184. package/dist/utils/semantic-memory.js.map +1 -0
  185. package/dist/utils/system.d.ts +2 -0
  186. package/dist/utils/system.d.ts.map +1 -0
  187. package/dist/utils/system.js +24 -0
  188. package/dist/utils/system.js.map +1 -0
  189. package/dist/utils/telegram-auth.d.ts +54 -0
  190. package/dist/utils/telegram-auth.d.ts.map +1 -0
  191. package/dist/utils/telegram-auth.js +146 -0
  192. package/dist/utils/telegram-auth.js.map +1 -0
  193. package/dist/utils/telegram.d.ts +7 -0
  194. package/dist/utils/telegram.d.ts.map +1 -0
  195. package/dist/utils/telegram.js +1494 -0
  196. package/dist/utils/telegram.js.map +1 -0
  197. package/dist/utils/whatsapp-client.d.ts +166 -0
  198. package/dist/utils/whatsapp-client.d.ts.map +1 -0
  199. package/dist/utils/whatsapp-client.js +722 -0
  200. package/dist/utils/whatsapp-client.js.map +1 -0
  201. package/dist/workflows/planner-workflow.d.ts +39 -0
  202. package/dist/workflows/planner-workflow.d.ts.map +1 -0
  203. package/dist/workflows/planner-workflow.js +165 -0
  204. package/dist/workflows/planner-workflow.js.map +1 -0
  205. package/dist/workflows/skill-builder-workflow.d.ts +16 -0
  206. package/dist/workflows/skill-builder-workflow.d.ts.map +1 -0
  207. package/dist/workflows/skill-builder-workflow.js +157 -0
  208. package/dist/workflows/skill-builder-workflow.js.map +1 -0
  209. package/dist/workspace/index.d.ts +23 -0
  210. package/dist/workspace/index.d.ts.map +1 -0
  211. package/dist/workspace/index.js +64 -0
  212. package/dist/workspace/index.js.map +1 -0
  213. package/docs/README.md +140 -0
  214. package/docs/api/agents.md +481 -0
  215. package/docs/api/browser-tools.md +469 -0
  216. package/docs/api/memory.md +629 -0
  217. package/docs/architecture/agent-networks.md +586 -0
  218. package/docs/architecture/memory.md +579 -0
  219. package/docs/architecture/overview.md +436 -0
  220. package/docs/architecture/tools.md +637 -0
  221. package/docs/cli-tui.md +367 -0
  222. package/docs/guides/environment-variables.md +502 -0
  223. package/docs/guides/troubleshooting.md +882 -0
  224. package/docs/tutorials/agent-networks.md +432 -0
  225. package/docs/tutorials/dynamic-tools.md +469 -0
  226. package/docs/tutorials/getting-started.md +263 -0
  227. package/docs/tutorials/skills.md +561 -0
  228. package/docs/tutorials/web-browsing.md +329 -0
  229. package/mastra.db-shm +0 -0
  230. package/mastra.db-wal +0 -0
  231. package/package.json +71 -0
  232. package/plan.md +601 -0
  233. package/skills/code-review/SKILL.md +48 -0
  234. package/skills/task-planning/SKILL.md +55 -0
  235. package/skills/web-research/SKILL.md +79 -0
  236. package/skills/whatsapp-management/SKILL.md +78 -0
  237. package/src/agents/autonomous-agent.ts +626 -0
  238. package/src/agents/network.ts +1307 -0
  239. package/src/cli/commands/backup.ts +78 -0
  240. package/src/cli/commands/config.ts +176 -0
  241. package/src/cli/commands/doctor.ts +111 -0
  242. package/src/cli/commands/init.ts +150 -0
  243. package/src/cli/commands/logs.ts +94 -0
  244. package/src/cli/commands/otp.ts +162 -0
  245. package/src/cli/commands/restore.ts +118 -0
  246. package/src/cli/commands/start.ts +76 -0
  247. package/src/cli/commands/status.ts +81 -0
  248. package/src/cli/commands/stop.ts +68 -0
  249. package/src/cli/commands/update.ts +61 -0
  250. package/src/cli/commands/whatsapp.ts +322 -0
  251. package/src/cli/index.ts +69 -0
  252. package/src/cli.ts +830 -0
  253. package/src/index.ts +124 -0
  254. package/src/mastra/index.ts +49 -0
  255. package/src/mastra/memory.ts +99 -0
  256. package/src/mastra/public/workspace/plan.md +115 -0
  257. package/src/mastra/public/workspace/research/react-tailwind/skill.md +47 -0
  258. package/src/processors/index.ts +170 -0
  259. package/src/processors/semantic-recall.ts +277 -0
  260. package/src/processors/tool-search.ts +46 -0
  261. package/src/skills/dynamic/skill-generator.ts +568 -0
  262. package/src/tools/agent-delegation-tools.ts +301 -0
  263. package/src/tools/browser-tools.ts +792 -0
  264. package/src/tools/dynamic/registry.ts +144 -0
  265. package/src/tools/dynamic/tool-generator.ts +406 -0
  266. package/src/tools/extended-tools.ts +498 -0
  267. package/src/tools/library/calendar/index.ts +172 -0
  268. package/src/tools/podman-workspace-mcp-cli.ts +14 -0
  269. package/src/tools/podman-workspace-mcp.ts +1290 -0
  270. package/src/tools/podman-workspace.ts +858 -0
  271. package/src/tools/telegram-file-tools.ts +320 -0
  272. package/src/tools/tool-registry.ts +233 -0
  273. package/src/tools/web-tools.ts +461 -0
  274. package/src/tools/whatsapp-autoreply-tools.ts +616 -0
  275. package/src/tools/whatsapp-tools.ts +602 -0
  276. package/src/utils/logger.ts +368 -0
  277. package/src/utils/model-config.ts +437 -0
  278. package/src/utils/semantic-memory.ts +230 -0
  279. package/src/utils/system.ts +25 -0
  280. package/src/utils/telegram-auth.ts +201 -0
  281. package/src/utils/telegram.ts +1847 -0
  282. package/src/utils/whatsapp-client.ts +808 -0
  283. package/src/workflows/planner-workflow.ts +178 -0
  284. package/src/workflows/skill-builder-workflow.ts +175 -0
  285. package/src/workspace/index.ts +69 -0
  286. package/tsconfig.json +22 -0
  287. package/view-logs.sh +116 -0
  288. package/whatsapp-session.sh +197 -0
@@ -0,0 +1,616 @@
1
+ import { createTool } from "@mastra/core/tools";
2
+ import { z } from "zod";
3
+ import "dotenv/config";
4
+
5
+ // NOTE: whatsappManager and mastra are imported lazily (via dynamic import)
6
+ // inside the functions that need them to avoid circular dependencies.
7
+ // whatsapp-client.ts imports from this file, and mastra/index.ts imports
8
+ // autonomous-agent.ts which transitively imports from this file.
9
+
10
+ /**
11
+ * Interface for queued messages
12
+ */
13
+ interface QueuedMessage {
14
+ id: string;
15
+ body: string;
16
+ timestamp: number;
17
+ from: string;
18
+ senderName: string;
19
+ }
20
+
21
+ /**
22
+ * Interface for message queue per contact
23
+ */
24
+ interface MessageQueue {
25
+ messages: QueuedMessage[];
26
+ timeoutId: NodeJS.Timeout | null;
27
+ lastActivity: number;
28
+ isProcessing: boolean;
29
+ }
30
+
31
+ /**
32
+ * Storage for auto-reply configuration
33
+ */
34
+ interface AutoReplyConfig {
35
+ enabled: boolean;
36
+ mode: "manual" | "auto" | "smart";
37
+ whitelist: string[];
38
+ blacklist: string[];
39
+ maxRepliesPerHour: number;
40
+ replyDelayMs: number;
41
+ debounceMs: number; // Time to wait for inactivity (default: 2 minutes = 120000ms)
42
+ userContext: string;
43
+ customInstructions: string;
44
+ lastReplies: Map<string, number>;
45
+ }
46
+
47
+ const defaultConfig: AutoReplyConfig = {
48
+ enabled: false,
49
+ mode: "manual",
50
+ whitelist: [],
51
+ blacklist: [],
52
+ maxRepliesPerHour: 10,
53
+ replyDelayMs: 3000,
54
+ debounceMs: 120000, // 2 minutes
55
+ userContext: "",
56
+ customInstructions: "",
57
+ lastReplies: new Map(),
58
+ };
59
+
60
+ // In-memory config storage
61
+ let autoReplyConfig: AutoReplyConfig = { ...defaultConfig };
62
+
63
+ // Message queues per contact (phone number -> queue)
64
+ const messageQueues = new Map<string, MessageQueue>();
65
+
66
+ /**
67
+ * Add a message to the queue for debouncing
68
+ */
69
+ export function queueMessage(
70
+ from: string,
71
+ message: QueuedMessage,
72
+ onDebounceComplete: (phoneNumber: string, messages: QueuedMessage[]) => void
73
+ ): void {
74
+ const phoneNumber = from.replace(/\D/g, "").replace(/@c\.us$/, "");
75
+
76
+ // Get or create queue for this contact
77
+ let queue = messageQueues.get(phoneNumber);
78
+ if (!queue) {
79
+ queue = {
80
+ messages: [],
81
+ timeoutId: null,
82
+ lastActivity: Date.now(),
83
+ isProcessing: false,
84
+ };
85
+ messageQueues.set(phoneNumber, queue);
86
+ }
87
+
88
+ // Add message to queue
89
+ queue.messages.push(message);
90
+ queue.lastActivity = Date.now();
91
+
92
+ console.log(`[WhatsApp Debouncer] Queued message from ${message.senderName}. Total: ${queue.messages.length} messages`);
93
+
94
+ // Clear existing timeout
95
+ if (queue.timeoutId) {
96
+ clearTimeout(queue.timeoutId);
97
+ queue.timeoutId = null;
98
+ }
99
+
100
+ // Set new timeout for debouncing
101
+ queue.timeoutId = setTimeout(() => {
102
+ console.log(`[WhatsApp Debouncer] Debounce complete for ${message.senderName}. Processing ${queue!.messages.length} messages...`);
103
+
104
+ // Only process if not already processing and auto-reply is enabled
105
+ if (!queue!.isProcessing && autoReplyConfig.enabled) {
106
+ queue!.isProcessing = true;
107
+ onDebounceComplete(phoneNumber, [...queue!.messages]);
108
+
109
+ // Clear the queue after processing
110
+ queue!.messages = [];
111
+ queue!.isProcessing = false;
112
+ }
113
+
114
+ queue!.timeoutId = null;
115
+ }, autoReplyConfig.debounceMs);
116
+ }
117
+
118
+ /**
119
+ * Cancel pending messages for a contact
120
+ */
121
+ export function cancelPendingMessages(phoneNumber: string): void {
122
+ const queue = messageQueues.get(phoneNumber);
123
+ if (queue && queue.timeoutId) {
124
+ clearTimeout(queue.timeoutId);
125
+ queue.timeoutId = null;
126
+ queue.messages = [];
127
+ console.log(`[WhatsApp Debouncer] Cancelled pending messages for ${phoneNumber}`);
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Get pending messages for a contact
133
+ */
134
+ export function getPendingMessages(phoneNumber: string): QueuedMessage[] {
135
+ const queue = messageQueues.get(phoneNumber);
136
+ return queue ? [...queue.messages] : [];
137
+ }
138
+
139
+ /**
140
+ * Check if we should auto-reply to a contact
141
+ */
142
+ export function shouldAutoReply(from: string): { shouldReply: boolean; reason?: string } {
143
+ if (!autoReplyConfig.enabled) {
144
+ return { shouldReply: false, reason: "Auto-reply is disabled" };
145
+ }
146
+
147
+ const phoneNumber = from.replace(/\D/g, "").replace(/@c\.us$/, "");
148
+
149
+ // Check blacklist
150
+ if (autoReplyConfig.blacklist.includes(phoneNumber)) {
151
+ return { shouldReply: false, reason: "Number is blacklisted" };
152
+ }
153
+
154
+ // Check whitelist (if whitelist is not empty, only reply to whitelisted numbers)
155
+ if (autoReplyConfig.whitelist.length > 0 && !autoReplyConfig.whitelist.includes(phoneNumber)) {
156
+ return { shouldReply: false, reason: "Number not in whitelist" };
157
+ }
158
+
159
+ // Check rate limit
160
+ const lastReply = autoReplyConfig.lastReplies.get(phoneNumber);
161
+ if (lastReply) {
162
+ const hoursSinceLastReply = (Date.now() - lastReply) / (1000 * 60 * 60);
163
+ if (hoursSinceLastReply < 1) {
164
+ const repliesThisHour = Array.from(autoReplyConfig.lastReplies.entries())
165
+ .filter(([num, time]) => num === phoneNumber && (Date.now() - time) < 3600000)
166
+ .length;
167
+
168
+ if (repliesThisHour >= autoReplyConfig.maxRepliesPerHour) {
169
+ return { shouldReply: false, reason: "Rate limit exceeded" };
170
+ }
171
+ }
172
+ }
173
+
174
+ return { shouldReply: true };
175
+ }
176
+
177
+ /**
178
+ * Generate a smart reply using the agent based on batched messages
179
+ */
180
+ export async function generateSmartReplyForBatchedMessages(
181
+ chatId: string,
182
+ messages: QueuedMessage[],
183
+ chatHistory: Array<{ body: string; fromMe: boolean }>
184
+ ): Promise<{ reply: string; confidence: number; shouldSend: boolean; summary: string }> {
185
+ try {
186
+ // Lazy import to avoid circular dependency: this file → mastra/index → autonomous-agent → ... → this file
187
+ const { mastra } = await import("../mastra/index.js");
188
+ const agent = mastra.getAgent("autonomousAgent");
189
+
190
+ // Combine all batched messages into a single context
191
+ const batchedMessagesText = messages.map((msg, index) =>
192
+ `[${new Date(msg.timestamp).toLocaleTimeString()}] ${msg.senderName}: ${msg.body}`
193
+ ).join("\n");
194
+
195
+ // Get the latest sender name
196
+ const senderName = messages[messages.length - 1]?.senderName || "Unknown";
197
+
198
+ // Build context from chat history
199
+ const recentHistory = chatHistory.slice(-10);
200
+ const conversationContext = recentHistory
201
+ .map((msg) => `${msg.fromMe ? "Me" : senderName}: ${msg.body}`)
202
+ .join("\n");
203
+
204
+ const prompt = `Generate a reply to these WhatsApp messages on my behalf. The sender may have sent multiple messages in sequence.
205
+
206
+ My Context:
207
+ ${autoReplyConfig.userContext || "I'm a busy professional who values concise communication."}
208
+
209
+ Custom Instructions:
210
+ ${autoReplyConfig.customInstructions || "Reply naturally and helpfully. Keep it brief unless detailed explanation is needed."}
211
+
212
+ Previous Conversation History:
213
+ ${conversationContext}
214
+
215
+ Incoming Messages from ${senderName} (${messages.length} message${messages.length > 1 ? 's' : ''}):
216
+ ${batchedMessagesText}
217
+
218
+ Generate a single comprehensive reply that addresses all the messages appropriately. The reply should:
219
+ 1. Sound like me (based on my communication style)
220
+ 2. Address all the key points from the messages
221
+ 3. Be concise but helpful
222
+ 4. Match the tone of the conversation
223
+
224
+ Provide your response in this format:
225
+ SUMMARY: [brief summary of what the sender is asking/saying]
226
+ REPLY: [your suggested reply]
227
+ CONFIDENCE: [0-100, how confident you are this is appropriate]
228
+ SHOULD_SEND: [yes/no, whether this should be sent without human review]`;
229
+
230
+ const response = await agent.generate(prompt);
231
+
232
+ // Parse the response
233
+ const summaryMatch = response.text.match(/SUMMARY:\s*(.+?)(?=\nREPLY:|$)/s);
234
+ const replyMatch = response.text.match(/REPLY:\s*(.+?)(?=\nCONFIDENCE:|$)/s);
235
+ const confidenceMatch = response.text.match(/CONFIDENCE:\s*(\d+)/);
236
+ const shouldSendMatch = response.text.match(/SHOULD_SEND:\s*(yes|no)/i);
237
+
238
+ const summary = summaryMatch ? summaryMatch[1].trim() : "New message received";
239
+ const reply = replyMatch ? replyMatch[1].trim() : "Thanks for your message! I'll get back to you soon.";
240
+ const confidence = confidenceMatch ? parseInt(confidenceMatch[1]) : 50;
241
+ const shouldSend = shouldSendMatch ? shouldSendMatch[1].toLowerCase() === "yes" : false;
242
+
243
+ return { reply, confidence, shouldSend, summary };
244
+ } catch (error) {
245
+ console.error("Error generating smart reply for batched messages:", error);
246
+ return {
247
+ reply: "Thanks for your message! I'll get back to you soon.",
248
+ confidence: 0,
249
+ shouldSend: false,
250
+ summary: "Error processing messages",
251
+ };
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Record that we replied to someone
257
+ */
258
+ export function recordReply(phoneNumber: string): void {
259
+ autoReplyConfig.lastReplies.set(phoneNumber.replace(/\D/g, "").replace(/@c\.us$/, ""), Date.now());
260
+ }
261
+
262
+ /**
263
+ * Tool: Configure Auto-Reply Settings
264
+ */
265
+ export const configureAutoReplyTool = createTool({
266
+ id: "configure-auto-reply",
267
+ description: `
268
+ Configure WhatsApp auto-reply settings.
269
+ Use this to enable/disable auto-replies, set mode (manual/auto/smart),
270
+ manage whitelist/blacklist, set custom instructions, and configure debounce time.
271
+ Messages are batched and only processed after 2 minutes of inactivity from the sender.
272
+ `,
273
+ inputSchema: z.object({
274
+ action: z.enum(["enable", "disable", "set-mode", "set-debounce", "add-to-whitelist", "remove-from-whitelist", "add-to-blacklist", "remove-from-blacklist", "set-context", "set-instructions", "get-status", "clear-queue"]),
275
+ value: z.string().optional().describe("Value for the action (e.g., phone number, mode, instructions, debounce time in minutes)"),
276
+ }),
277
+ outputSchema: z.object({
278
+ success: z.boolean(),
279
+ message: z.string(),
280
+ currentConfig: z.object({
281
+ enabled: z.boolean(),
282
+ mode: z.string(),
283
+ debounceMinutes: z.number(),
284
+ whitelistCount: z.number(),
285
+ blacklistCount: z.number(),
286
+ maxRepliesPerHour: z.number(),
287
+ pendingQueues: z.number(),
288
+ }),
289
+ }),
290
+ execute: async (inputData) => {
291
+ const { action, value } = inputData;
292
+
293
+ switch (action) {
294
+ case "enable":
295
+ autoReplyConfig.enabled = true;
296
+ return {
297
+ success: true,
298
+ message: "✅ Auto-reply enabled. Messages will be batched and processed after 2 minutes of inactivity.",
299
+ currentConfig: getConfigSummary(),
300
+ };
301
+
302
+ case "disable":
303
+ autoReplyConfig.enabled = false;
304
+ return {
305
+ success: true,
306
+ message: "✅ Auto-reply disabled",
307
+ currentConfig: getConfigSummary(),
308
+ };
309
+
310
+ case "set-mode":
311
+ if (value && ["manual", "auto", "smart"].includes(value)) {
312
+ autoReplyConfig.mode = value as "manual" | "auto" | "smart";
313
+ return {
314
+ success: true,
315
+ message: `✅ Auto-reply mode set to: ${value}`,
316
+ currentConfig: getConfigSummary(),
317
+ };
318
+ }
319
+ return {
320
+ success: false,
321
+ message: "❌ Invalid mode. Use: manual, auto, or smart",
322
+ currentConfig: getConfigSummary(),
323
+ };
324
+
325
+ case "set-debounce":
326
+ if (value) {
327
+ const minutes = parseInt(value);
328
+ if (!isNaN(minutes) && minutes >= 1 && minutes <= 30) {
329
+ autoReplyConfig.debounceMs = minutes * 60 * 1000;
330
+ return {
331
+ success: true,
332
+ message: `✅ Debounce time set to ${minutes} minutes. Messages will be processed after ${minutes} minutes of inactivity.`,
333
+ currentConfig: getConfigSummary(),
334
+ };
335
+ }
336
+ }
337
+ return {
338
+ success: false,
339
+ message: "❌ Invalid debounce time. Please provide a number between 1 and 30 minutes.",
340
+ currentConfig: getConfigSummary(),
341
+ };
342
+
343
+ case "add-to-whitelist":
344
+ if (value) {
345
+ const cleanNumber = value.replace(/\D/g, "");
346
+ if (!autoReplyConfig.whitelist.includes(cleanNumber)) {
347
+ autoReplyConfig.whitelist.push(cleanNumber);
348
+ }
349
+ return {
350
+ success: true,
351
+ message: `✅ Added ${cleanNumber} to whitelist`,
352
+ currentConfig: getConfigSummary(),
353
+ };
354
+ }
355
+ return {
356
+ success: false,
357
+ message: "❌ Please provide a phone number",
358
+ currentConfig: getConfigSummary(),
359
+ };
360
+
361
+ case "remove-from-whitelist":
362
+ if (value) {
363
+ const cleanNumber = value.replace(/\D/g, "");
364
+ autoReplyConfig.whitelist = autoReplyConfig.whitelist.filter(n => n !== cleanNumber);
365
+ return {
366
+ success: true,
367
+ message: `✅ Removed ${cleanNumber} from whitelist`,
368
+ currentConfig: getConfigSummary(),
369
+ };
370
+ }
371
+ return {
372
+ success: false,
373
+ message: "❌ Please provide a phone number",
374
+ currentConfig: getConfigSummary(),
375
+ };
376
+
377
+ case "add-to-blacklist":
378
+ if (value) {
379
+ const cleanNumber = value.replace(/\D/g, "");
380
+ if (!autoReplyConfig.blacklist.includes(cleanNumber)) {
381
+ autoReplyConfig.blacklist.push(cleanNumber);
382
+ }
383
+ return {
384
+ success: true,
385
+ message: `✅ Added ${cleanNumber} to blacklist`,
386
+ currentConfig: getConfigSummary(),
387
+ };
388
+ }
389
+ return {
390
+ success: false,
391
+ message: "❌ Please provide a phone number",
392
+ currentConfig: getConfigSummary(),
393
+ };
394
+
395
+ case "remove-from-blacklist":
396
+ if (value) {
397
+ const cleanNumber = value.replace(/\D/g, "");
398
+ autoReplyConfig.blacklist = autoReplyConfig.blacklist.filter(n => n !== cleanNumber);
399
+ return {
400
+ success: true,
401
+ message: `✅ Removed ${cleanNumber} from blacklist`,
402
+ currentConfig: getConfigSummary(),
403
+ };
404
+ }
405
+ return {
406
+ success: false,
407
+ message: "❌ Please provide a phone number",
408
+ currentConfig: getConfigSummary(),
409
+ };
410
+
411
+ case "set-context":
412
+ if (value) {
413
+ autoReplyConfig.userContext = value;
414
+ return {
415
+ success: true,
416
+ message: "✅ User context updated",
417
+ currentConfig: getConfigSummary(),
418
+ };
419
+ }
420
+ return {
421
+ success: false,
422
+ message: "❌ Please provide context text",
423
+ currentConfig: getConfigSummary(),
424
+ };
425
+
426
+ case "set-instructions":
427
+ if (value) {
428
+ autoReplyConfig.customInstructions = value;
429
+ return {
430
+ success: true,
431
+ message: "✅ Custom instructions updated",
432
+ currentConfig: getConfigSummary(),
433
+ };
434
+ }
435
+ return {
436
+ success: false,
437
+ message: "❌ Please provide instructions",
438
+ currentConfig: getConfigSummary(),
439
+ };
440
+
441
+ case "get-status":
442
+ return {
443
+ success: true,
444
+ message: `📊 Auto-reply is ${autoReplyConfig.enabled ? 'enabled' : 'disabled'}\n` +
445
+ `⏱️ Debounce time: ${autoReplyConfig.debounceMs / 60000} minutes\n` +
446
+ `📝 Messages are batched and processed after the sender stops typing for ${autoReplyConfig.debounceMs / 60000} minutes.`,
447
+ currentConfig: getConfigSummary(),
448
+ };
449
+
450
+ case "clear-queue":
451
+ if (value) {
452
+ const cleanNumber = value.replace(/\D/g, "");
453
+ cancelPendingMessages(cleanNumber);
454
+ return {
455
+ success: true,
456
+ message: `✅ Cleared pending message queue for ${cleanNumber}`,
457
+ currentConfig: getConfigSummary(),
458
+ };
459
+ }
460
+ // Clear all queues
461
+ messageQueues.clear();
462
+ return {
463
+ success: true,
464
+ message: "✅ Cleared all pending message queues",
465
+ currentConfig: getConfigSummary(),
466
+ };
467
+
468
+ default:
469
+ return {
470
+ success: false,
471
+ message: "❌ Unknown action",
472
+ currentConfig: getConfigSummary(),
473
+ };
474
+ }
475
+ },
476
+ });
477
+
478
+ /**
479
+ * Tool: Approve and Send Pending Reply
480
+ */
481
+ export const approvePendingReplyTool = createTool({
482
+ id: "approve-pending-reply",
483
+ description: `
484
+ Approve a pending auto-reply message and send it.
485
+ Use this in manual mode when you want to send a suggested reply.
486
+ `,
487
+ inputSchema: z.object({
488
+ phoneNumber: z.string().describe("Phone number to send reply to"),
489
+ reply: z.string().describe("The reply message to send"),
490
+ }),
491
+ outputSchema: z.object({
492
+ success: z.boolean(),
493
+ message: z.string(),
494
+ messageId: z.string().optional(),
495
+ }),
496
+ execute: async (inputData) => {
497
+ const { phoneNumber, reply } = inputData;
498
+
499
+ // Lazy import to avoid circular dependency: this file ↔ whatsapp-client.ts
500
+ const { whatsappManager } = await import("../utils/whatsapp-client.js");
501
+
502
+ if (!whatsappManager.getReadyState()) {
503
+ return {
504
+ success: false,
505
+ message: "WhatsApp not connected",
506
+ };
507
+ }
508
+
509
+ const result = await whatsappManager.sendMessage(phoneNumber, reply);
510
+
511
+ if (result.success) {
512
+ recordReply(phoneNumber);
513
+ }
514
+
515
+ return {
516
+ success: result.success,
517
+ message: result.success ? "✅ Reply sent successfully" : `❌ Failed: ${result.error}`,
518
+ messageId: result.messageId,
519
+ };
520
+ },
521
+ });
522
+
523
+ /**
524
+ * Get a summary of current config
525
+ */
526
+ function getConfigSummary() {
527
+ return {
528
+ enabled: autoReplyConfig.enabled,
529
+ mode: autoReplyConfig.mode,
530
+ debounceMinutes: autoReplyConfig.debounceMs / 60000,
531
+ whitelistCount: autoReplyConfig.whitelist.length,
532
+ blacklistCount: autoReplyConfig.blacklist.length,
533
+ maxRepliesPerHour: autoReplyConfig.maxRepliesPerHour,
534
+ pendingQueues: messageQueues.size,
535
+ };
536
+ }
537
+
538
+ /**
539
+ * Get full config (for internal use)
540
+ */
541
+ export function getAutoReplyConfig(): AutoReplyConfig {
542
+ return autoReplyConfig;
543
+ }
544
+
545
+ /**
546
+ * Reset config to defaults
547
+ */
548
+ export function resetAutoReplyConfig(): void {
549
+ autoReplyConfig = { ...defaultConfig };
550
+ messageQueues.clear();
551
+ }
552
+
553
+ /**
554
+ * Load auto-reply configuration from environment variables
555
+ * Env vars:
556
+ * WHATSAPP_AUTOREPLY_ENABLED=true/false
557
+ * WHATSAPP_AUTOREPLY_MODE=manual/auto/smart
558
+ * WHATSAPP_AUTOREPLY_DEBOUNCE_MINUTES=2
559
+ * WHATSAPP_AUTOREPLY_MAX_REPLIES_PER_HOUR=10
560
+ * WHATSAPP_AUTOREPLY_WHITELIST=1234567890,9876543210
561
+ * WHATSAPP_AUTOREPLY_BLACKLIST=1111111111,2222222222
562
+ * WHATSAPP_AUTOREPLY_USER_CONTEXT="Your context here"
563
+ * WHATSAPP_AUTOREPLY_CUSTOM_INSTRUCTIONS="Your instructions here"
564
+ */
565
+ export function loadAutoReplyConfigFromEnv(): void {
566
+ const envEnabled = process.env.WHATSAPP_AUTOREPLY_ENABLED?.toLowerCase();
567
+ const envMode = process.env.WHATSAPP_AUTOREPLY_MODE?.toLowerCase();
568
+ const envDebounce = process.env.WHATSAPP_AUTOREPLY_DEBOUNCE_MINUTES;
569
+ const envMaxReplies = process.env.WHATSAPP_AUTOREPLY_MAX_REPLIES_PER_HOUR;
570
+ const envWhitelist = process.env.WHATSAPP_AUTOREPLY_WHITELIST;
571
+ const envBlacklist = process.env.WHATSAPP_AUTOREPLY_BLACKLIST;
572
+ const envContext = process.env.WHATSAPP_AUTOREPLY_USER_CONTEXT;
573
+ const envInstructions = process.env.WHATSAPP_AUTOREPLY_CUSTOM_INSTRUCTIONS;
574
+
575
+ if (envEnabled === "true") {
576
+ autoReplyConfig.enabled = true;
577
+ } else if (envEnabled === "false") {
578
+ autoReplyConfig.enabled = false;
579
+ }
580
+
581
+ if (envMode && ["manual", "auto", "smart"].includes(envMode)) {
582
+ autoReplyConfig.mode = envMode as "manual" | "auto" | "smart";
583
+ }
584
+
585
+ if (envDebounce) {
586
+ const minutes = parseInt(envDebounce);
587
+ if (!isNaN(minutes) && minutes >= 1 && minutes <= 30) {
588
+ autoReplyConfig.debounceMs = minutes * 60 * 1000;
589
+ }
590
+ }
591
+
592
+ if (envMaxReplies) {
593
+ const max = parseInt(envMaxReplies);
594
+ if (!isNaN(max) && max >= 1) {
595
+ autoReplyConfig.maxRepliesPerHour = max;
596
+ }
597
+ }
598
+
599
+ if (envWhitelist) {
600
+ autoReplyConfig.whitelist = envWhitelist.split(",").map(n => n.replace(/\D/g, "")).filter(n => n.length > 0);
601
+ }
602
+
603
+ if (envBlacklist) {
604
+ autoReplyConfig.blacklist = envBlacklist.split(",").map(n => n.replace(/\D/g, "")).filter(n => n.length > 0);
605
+ }
606
+
607
+ if (envContext) {
608
+ autoReplyConfig.userContext = envContext;
609
+ }
610
+
611
+ if (envInstructions) {
612
+ autoReplyConfig.customInstructions = envInstructions;
613
+ }
614
+
615
+ console.log(`[AutoReply] Config loaded from env: enabled=${autoReplyConfig.enabled}, mode=${autoReplyConfig.mode}, debounce=${autoReplyConfig.debounceMs / 60000}min`);
616
+ }