@hailer/mcp 0.1.16 → 0.2.1

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 (202) hide show
  1. package/dist/app.js +24 -20
  2. package/dist/core.d.ts +33 -9
  3. package/dist/core.js +279 -147
  4. package/dist/mcp/UserContextCache.js +18 -0
  5. package/dist/mcp/hailer-clients.d.ts +9 -1
  6. package/dist/mcp/hailer-clients.js +13 -3
  7. package/dist/mcp/signal-handler.js +1 -1
  8. package/dist/mcp/tool-registry.d.ts +3 -1
  9. package/dist/mcp/tool-registry.js +4 -1
  10. package/dist/mcp/tools/activity.js +43 -34
  11. package/dist/mcp/tools/bot-config/constants.d.ts +23 -0
  12. package/dist/mcp/tools/bot-config/constants.js +94 -0
  13. package/dist/mcp/tools/{bot-config.d.ts → bot-config/core.d.ts} +6 -6
  14. package/dist/mcp/tools/{bot-config.js → bot-config/core.js} +15 -15
  15. package/dist/mcp/tools/bot-config/index.d.ts +10 -0
  16. package/dist/mcp/tools/bot-config/index.js +59 -0
  17. package/dist/mcp/tools/bot-config/tools.d.ts +7 -0
  18. package/dist/mcp/tools/bot-config/tools.js +15 -0
  19. package/dist/mcp/tools/bot-config/types.d.ts +50 -0
  20. package/dist/mcp/tools/bot-config/types.js +6 -0
  21. package/dist/mcp/tools/bug-fixer-tools.d.ts +21 -0
  22. package/dist/mcp/tools/{giuseppe-tools.js → bug-fixer-tools.js} +61 -61
  23. package/dist/mcp/tools/user.js +10 -29
  24. package/dist/mcp/tools/workflow.js +36 -2
  25. package/dist/mcp/utils/data-transformers.d.ts +0 -8
  26. package/dist/mcp/utils/data-transformers.js +0 -28
  27. package/dist/mcp/utils/index.d.ts +4 -1
  28. package/dist/mcp/utils/index.js +17 -3
  29. package/dist/mcp/utils/pagination.d.ts +40 -0
  30. package/dist/mcp/utils/pagination.js +55 -0
  31. package/dist/mcp/utils/response-builder.d.ts +53 -0
  32. package/dist/mcp/utils/response-builder.js +110 -0
  33. package/dist/mcp/utils/tool-helpers.d.ts +0 -8
  34. package/dist/mcp/utils/tool-helpers.js +0 -24
  35. package/dist/mcp/utils/types.d.ts +1 -33
  36. package/dist/mcp/webhook-handler.d.ts +2 -2
  37. package/dist/mcp/webhook-handler.js +5 -3
  38. package/dist/mcp-server.d.ts +2 -2
  39. package/dist/mcp-server.js +167 -140
  40. package/package.json +1 -1
  41. package/REFACTOR_STATUS.md +0 -127
  42. package/dist/agents/bot-manager.d.ts +0 -48
  43. package/dist/agents/bot-manager.js +0 -254
  44. package/dist/agents/factory.d.ts +0 -150
  45. package/dist/agents/factory.js +0 -650
  46. package/dist/agents/giuseppe/ai.d.ts +0 -83
  47. package/dist/agents/giuseppe/ai.js +0 -466
  48. package/dist/agents/giuseppe/bot.d.ts +0 -110
  49. package/dist/agents/giuseppe/bot.js +0 -780
  50. package/dist/agents/giuseppe/config.d.ts +0 -25
  51. package/dist/agents/giuseppe/config.js +0 -227
  52. package/dist/agents/giuseppe/files.d.ts +0 -52
  53. package/dist/agents/giuseppe/files.js +0 -338
  54. package/dist/agents/giuseppe/git.d.ts +0 -48
  55. package/dist/agents/giuseppe/git.js +0 -298
  56. package/dist/agents/giuseppe/index.d.ts +0 -97
  57. package/dist/agents/giuseppe/index.js +0 -258
  58. package/dist/agents/giuseppe/lsp.d.ts +0 -113
  59. package/dist/agents/giuseppe/lsp.js +0 -485
  60. package/dist/agents/giuseppe/monitor.d.ts +0 -118
  61. package/dist/agents/giuseppe/monitor.js +0 -621
  62. package/dist/agents/giuseppe/prompt.d.ts +0 -5
  63. package/dist/agents/giuseppe/prompt.js +0 -94
  64. package/dist/agents/giuseppe/registries/pending-classification.d.ts +0 -28
  65. package/dist/agents/giuseppe/registries/pending-classification.js +0 -50
  66. package/dist/agents/giuseppe/registries/pending-fix.d.ts +0 -30
  67. package/dist/agents/giuseppe/registries/pending-fix.js +0 -42
  68. package/dist/agents/giuseppe/registries/pending.d.ts +0 -27
  69. package/dist/agents/giuseppe/registries/pending.js +0 -49
  70. package/dist/agents/giuseppe/specialist.d.ts +0 -47
  71. package/dist/agents/giuseppe/specialist.js +0 -237
  72. package/dist/agents/giuseppe/types.d.ts +0 -123
  73. package/dist/agents/giuseppe/types.js +0 -9
  74. package/dist/agents/hailer-expert/index.d.ts +0 -8
  75. package/dist/agents/hailer-expert/index.js +0 -14
  76. package/dist/agents/hal/daemon.d.ts +0 -142
  77. package/dist/agents/hal/daemon.js +0 -1103
  78. package/dist/agents/hal/definitions.d.ts +0 -55
  79. package/dist/agents/hal/definitions.js +0 -263
  80. package/dist/agents/hal/index.d.ts +0 -3
  81. package/dist/agents/hal/index.js +0 -8
  82. package/dist/agents/index.d.ts +0 -18
  83. package/dist/agents/index.js +0 -48
  84. package/dist/agents/shared/base.d.ts +0 -216
  85. package/dist/agents/shared/base.js +0 -846
  86. package/dist/agents/shared/services/agent-registry.d.ts +0 -107
  87. package/dist/agents/shared/services/agent-registry.js +0 -629
  88. package/dist/agents/shared/services/conversation-manager.d.ts +0 -50
  89. package/dist/agents/shared/services/conversation-manager.js +0 -136
  90. package/dist/agents/shared/services/mcp-client.d.ts +0 -56
  91. package/dist/agents/shared/services/mcp-client.js +0 -124
  92. package/dist/agents/shared/services/message-classifier.d.ts +0 -37
  93. package/dist/agents/shared/services/message-classifier.js +0 -187
  94. package/dist/agents/shared/services/message-formatter.d.ts +0 -89
  95. package/dist/agents/shared/services/message-formatter.js +0 -371
  96. package/dist/agents/shared/services/session-logger.d.ts +0 -106
  97. package/dist/agents/shared/services/session-logger.js +0 -446
  98. package/dist/agents/shared/services/tool-executor.d.ts +0 -41
  99. package/dist/agents/shared/services/tool-executor.js +0 -169
  100. package/dist/agents/shared/services/workspace-schema-cache.d.ts +0 -125
  101. package/dist/agents/shared/services/workspace-schema-cache.js +0 -578
  102. package/dist/agents/shared/specialist.d.ts +0 -91
  103. package/dist/agents/shared/specialist.js +0 -399
  104. package/dist/agents/shared/tool-schema-loader.d.ts +0 -62
  105. package/dist/agents/shared/tool-schema-loader.js +0 -232
  106. package/dist/agents/shared/types.d.ts +0 -327
  107. package/dist/agents/shared/types.js +0 -121
  108. package/dist/client/agents/base.d.ts +0 -207
  109. package/dist/client/agents/base.js +0 -744
  110. package/dist/client/agents/definitions.d.ts +0 -53
  111. package/dist/client/agents/definitions.js +0 -263
  112. package/dist/client/agents/orchestrator.d.ts +0 -141
  113. package/dist/client/agents/orchestrator.js +0 -1062
  114. package/dist/client/agents/specialist.d.ts +0 -86
  115. package/dist/client/agents/specialist.js +0 -340
  116. package/dist/client/bot-entrypoint.d.ts +0 -7
  117. package/dist/client/bot-entrypoint.js +0 -103
  118. package/dist/client/bot-manager.d.ts +0 -44
  119. package/dist/client/bot-manager.js +0 -173
  120. package/dist/client/bot-runner.d.ts +0 -35
  121. package/dist/client/bot-runner.js +0 -188
  122. package/dist/client/chat-agent-daemon.d.ts +0 -464
  123. package/dist/client/chat-agent-daemon.js +0 -1774
  124. package/dist/client/daemon-factory.d.ts +0 -106
  125. package/dist/client/daemon-factory.js +0 -301
  126. package/dist/client/factory.d.ts +0 -111
  127. package/dist/client/factory.js +0 -314
  128. package/dist/client/index.d.ts +0 -17
  129. package/dist/client/index.js +0 -38
  130. package/dist/client/multi-bot-manager.d.ts +0 -42
  131. package/dist/client/multi-bot-manager.js +0 -161
  132. package/dist/client/orchestrator-daemon.d.ts +0 -87
  133. package/dist/client/orchestrator-daemon.js +0 -444
  134. package/dist/client/server.d.ts +0 -8
  135. package/dist/client/server.js +0 -251
  136. package/dist/client/services/agent-registry.d.ts +0 -108
  137. package/dist/client/services/agent-registry.js +0 -630
  138. package/dist/client/services/conversation-manager.d.ts +0 -50
  139. package/dist/client/services/conversation-manager.js +0 -136
  140. package/dist/client/services/mcp-client.d.ts +0 -48
  141. package/dist/client/services/mcp-client.js +0 -105
  142. package/dist/client/services/message-classifier.d.ts +0 -37
  143. package/dist/client/services/message-classifier.js +0 -187
  144. package/dist/client/services/message-formatter.d.ts +0 -84
  145. package/dist/client/services/message-formatter.js +0 -353
  146. package/dist/client/services/session-logger.d.ts +0 -106
  147. package/dist/client/services/session-logger.js +0 -446
  148. package/dist/client/services/tool-executor.d.ts +0 -41
  149. package/dist/client/services/tool-executor.js +0 -169
  150. package/dist/client/services/workspace-schema-cache.d.ts +0 -149
  151. package/dist/client/services/workspace-schema-cache.js +0 -732
  152. package/dist/client/specialist-daemon.d.ts +0 -77
  153. package/dist/client/specialist-daemon.js +0 -197
  154. package/dist/client/specialists.d.ts +0 -53
  155. package/dist/client/specialists.js +0 -178
  156. package/dist/client/tool-schema-loader.d.ts +0 -62
  157. package/dist/client/tool-schema-loader.js +0 -232
  158. package/dist/client/types.d.ts +0 -327
  159. package/dist/client/types.js +0 -121
  160. package/dist/commands/seed-config.d.ts +0 -9
  161. package/dist/commands/seed-config.js +0 -372
  162. package/dist/lib/context-manager.d.ts +0 -111
  163. package/dist/lib/context-manager.js +0 -431
  164. package/dist/lib/prompt-length-manager.d.ts +0 -81
  165. package/dist/lib/prompt-length-manager.js +0 -457
  166. package/dist/mcp/tools/giuseppe-tools.d.ts +0 -21
  167. package/dist/modules/bug-reports/bug-config.d.ts +0 -25
  168. package/dist/modules/bug-reports/bug-config.js +0 -187
  169. package/dist/modules/bug-reports/bug-monitor.d.ts +0 -108
  170. package/dist/modules/bug-reports/bug-monitor.js +0 -510
  171. package/dist/modules/bug-reports/giuseppe-agent.d.ts +0 -58
  172. package/dist/modules/bug-reports/giuseppe-agent.js +0 -467
  173. package/dist/modules/bug-reports/giuseppe-ai.d.ts +0 -83
  174. package/dist/modules/bug-reports/giuseppe-ai.js +0 -466
  175. package/dist/modules/bug-reports/giuseppe-bot.d.ts +0 -110
  176. package/dist/modules/bug-reports/giuseppe-bot.js +0 -804
  177. package/dist/modules/bug-reports/giuseppe-daemon.d.ts +0 -80
  178. package/dist/modules/bug-reports/giuseppe-daemon.js +0 -617
  179. package/dist/modules/bug-reports/giuseppe-files.d.ts +0 -64
  180. package/dist/modules/bug-reports/giuseppe-files.js +0 -375
  181. package/dist/modules/bug-reports/giuseppe-git.d.ts +0 -48
  182. package/dist/modules/bug-reports/giuseppe-git.js +0 -298
  183. package/dist/modules/bug-reports/giuseppe-lsp.d.ts +0 -113
  184. package/dist/modules/bug-reports/giuseppe-lsp.js +0 -485
  185. package/dist/modules/bug-reports/giuseppe-prompt.d.ts +0 -5
  186. package/dist/modules/bug-reports/giuseppe-prompt.js +0 -94
  187. package/dist/modules/bug-reports/index.d.ts +0 -77
  188. package/dist/modules/bug-reports/index.js +0 -215
  189. package/dist/modules/bug-reports/pending-classification-registry.d.ts +0 -28
  190. package/dist/modules/bug-reports/pending-classification-registry.js +0 -50
  191. package/dist/modules/bug-reports/pending-fix-registry.d.ts +0 -30
  192. package/dist/modules/bug-reports/pending-fix-registry.js +0 -42
  193. package/dist/modules/bug-reports/pending-registry.d.ts +0 -27
  194. package/dist/modules/bug-reports/pending-registry.js +0 -49
  195. package/dist/modules/bug-reports/types.d.ts +0 -123
  196. package/dist/modules/bug-reports/types.js +0 -9
  197. package/dist/routes/agents.d.ts +0 -44
  198. package/dist/routes/agents.js +0 -311
  199. package/dist/services/agent-credential-store.d.ts +0 -73
  200. package/dist/services/agent-credential-store.js +0 -212
  201. package/dist/services/bug-monitor.d.ts +0 -23
  202. package/dist/services/bug-monitor.js +0 -275
@@ -1,744 +0,0 @@
1
- "use strict";
2
- /**
3
- * Chat Agent Daemon
4
- *
5
- * A persistent LLM conversation that monitors all workspace chats.
6
- * The LLM maintains context across messages and decides what to respond to.
7
- *
8
- * Architecture:
9
- * - One daemon per bot client
10
- * - Subscribes to ALL messenger.new signals (not filtered)
11
- * - LLM sees every message with priority markers
12
- * - LLM decides: RESPOND / IGNORE / ACTION
13
- */
14
- var __importDefault = (this && this.__importDefault) || function (mod) {
15
- return (mod && mod.__esModule) ? mod : { "default": mod };
16
- };
17
- Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.ChatAgentDaemon = void 0;
19
- const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
20
- const logger_1 = require("../../lib/logger");
21
- const tool_schema_loader_1 = require("../tool-schema-loader");
22
- const tool_registry_1 = require("../../mcp/tool-registry");
23
- const agent_registry_1 = require("../services/agent-registry");
24
- const conversation_manager_1 = require("../services/conversation-manager");
25
- const mcp_client_1 = require("../services/mcp-client");
26
- const message_formatter_1 = require("../services/message-formatter");
27
- const message_classifier_1 = require("../services/message-classifier");
28
- const session_logger_1 = require("../services/session-logger");
29
- const tool_executor_1 = require("../services/tool-executor");
30
- const workspace_schema_cache_1 = require("../services/workspace-schema-cache");
31
- class ChatAgentDaemon {
32
- logger;
33
- client;
34
- botClient;
35
- config;
36
- toolSchemaLoader = new tool_schema_loader_1.ToolSchemaLoader();
37
- // Processing state
38
- isProcessing = false;
39
- messageQueue = [];
40
- processedMessageIds = new Set();
41
- // Typing indicator state
42
- typingInterval = null;
43
- typingDiscussionId = null;
44
- static TYPING_REFRESH_MS = 3000; // Refresh typing every 3 seconds
45
- // Tool schemas (loaded once)
46
- toolIndex = [];
47
- minimalTools = [];
48
- // ===== SERVICES =====
49
- /** Message classifier - handles message extraction and priority classification */
50
- messageClassifier = null;
51
- /** Conversation manager - handles per-discussion context and LRU cache */
52
- conversationManager = null;
53
- /** MCP client service - handles tool schema loading and execution */
54
- mcpClient = null;
55
- /** Tool executor - handles tool execution and write tracking */
56
- toolExecutor = null;
57
- /** Agent registration service - handles Agent Directory, Position, Team, etc. */
58
- registryService = null;
59
- /** Message formatting service - handles tag resolution and formatting */
60
- messageFormatter = null;
61
- /** Session logging service - handles activity session tracking */
62
- sessionLogger = null;
63
- /** Workspace schema cache - dynamic workflow/field ID lookup */
64
- schemaCache = null;
65
- /** Current discussion context for tracking */
66
- currentDiscussionId = null;
67
- currentLinkedActivityId = null;
68
- constructor(config) {
69
- this.config = config;
70
- this.botClient = config.botClient;
71
- this.logger = (0, logger_1.createLogger)({
72
- component: "ChatAgentDaemon",
73
- botId: config.botClient.userId
74
- });
75
- this.client = new sdk_1.default({
76
- apiKey: config.anthropicApiKey,
77
- });
78
- }
79
- /**
80
- * Initialize the daemon - load tools and subscribe to signals
81
- */
82
- async initialize() {
83
- this.logger.info("Initializing Chat Agent Daemon", {
84
- botId: this.botClient.userId,
85
- email: this.botClient.config.email,
86
- });
87
- // Load tool index once
88
- const allowedGroups = [tool_registry_1.ToolGroup.READ, tool_registry_1.ToolGroup.WRITE];
89
- let tools = await this.toolSchemaLoader.loadToolIndex({
90
- mcpServerUrl: this.config.mcpServerUrl,
91
- mcpServerApiKey: this.botClient.config.mcpServerApiKey,
92
- allowedGroups,
93
- });
94
- // Filter by whitelist if agent specifies one
95
- const whitelist = this.getToolWhitelist();
96
- if (whitelist) {
97
- tools = tools.filter((t) => whitelist.includes(t.name));
98
- this.logger.debug("Tools filtered by whitelist", {
99
- total: tools.length,
100
- whitelist: whitelist.length
101
- });
102
- }
103
- this.toolIndex = tools;
104
- this.minimalTools = this.toolSchemaLoader.toMinimalToolDefinitions(this.toolIndex);
105
- this.logger.debug("Tools loaded", { toolCount: this.toolIndex.length });
106
- // Initialize MCP client service (must be first - other services depend on it)
107
- this.mcpClient = new mcp_client_1.McpClientService(this.config.mcpServerUrl, this.botClient.config.mcpServerApiKey, this.logger);
108
- // Get workspace ID early - needed for schema cache and registration
109
- const workspaceId = this.botClient.workspaceCache?.currentWorkspace?._id || "";
110
- // Initialize workspace schema cache FIRST (discovers workflows dynamically)
111
- // This MUST happen before agent registration so we have dynamic workflow IDs
112
- this.schemaCache = new workspace_schema_cache_1.WorkspaceSchemaCacheService(this.logger, this.mcpClient.callMcpTool.bind(this.mcpClient));
113
- // Initialize schema for current workspace (may install template if missing)
114
- if (workspaceId) {
115
- await this.schemaCache.initializeForWorkspace(workspaceId);
116
- }
117
- // Initialize conversation manager
118
- this.conversationManager = new conversation_manager_1.ConversationManager(100, // maxConversations
119
- this.config.maxContextMessages || 50, this.client, this.logger);
120
- // Initialize tool executor
121
- this.toolExecutor = new tool_executor_1.ToolExecutor(this.mcpClient, this.logger);
122
- // Initialize message classifier
123
- this.messageClassifier = new message_classifier_1.MessageClassifier(this.botClient.userId, this.botClient, this.logger);
124
- // Create agent registry service WITH schema cache for dynamic ID lookup
125
- this.registryService = new agent_registry_1.AgentRegistryService(this.schemaCache, this.logger, this.mcpClient.callMcpTool.bind(this.mcpClient), this.getDefaultTeamId.bind(this));
126
- // Initialize message formatter service
127
- this.messageFormatter = new message_formatter_1.MessageFormatterService(this.botClient, this.logger, this.mcpClient.callMcpTool.bind(this.mcpClient));
128
- // Build agent info from config and abstract methods
129
- const { firstName, lastName } = this.getAgentName();
130
- const agentInfo = {
131
- firstName,
132
- lastName,
133
- description: this.getAgentDescription(),
134
- email: this.botClient.config.email,
135
- userId: this.botClient.userId,
136
- };
137
- // Try to load from cache first (workspace-scoped, 1 API call to verify)
138
- const cacheLoaded = await this.registryService.loadFromCache(this.botClient.userId, workspaceId);
139
- if (!cacheLoaded) {
140
- // Cache miss or invalid - do full registration using dynamic schema lookup
141
- this.logger.info("Starting full agent registration", { workspaceId });
142
- await this.registryService.registerAllAgentData(agentInfo, this.getPositionDetails(), this.config.mcpServerUrl, this.toolIndex, workspaceId);
143
- }
144
- // Subscribe to ALL messenger.new signals (no filtering)
145
- this.botClient.signalHandler.subscribe(`daemon-${this.botClient.userId}`, ["messenger.new"], this.handleSignal.bind(this));
146
- // Initialize session logger service (after registration so we have agentDirectoryId)
147
- this.sessionLogger = new session_logger_1.SessionLoggerService(this.registryService?.getAgentDirectoryId() ?? null, this.logger, this.mcpClient.callMcpTool.bind(this.mcpClient), this.getDefaultTeamId.bind(this));
148
- // Pass Anthropic client for conversation summary generation
149
- this.sessionLogger.setAnthropicClient(this.client);
150
- // Pass schema cache for dynamic workflow ID lookup
151
- if (workspaceId && this.schemaCache) {
152
- this.sessionLogger.setSchemaCache(this.schemaCache, workspaceId);
153
- }
154
- // Start idle session check timer (every 30 seconds)
155
- this.sessionLogger.startIdleCheckTimer(30_000);
156
- this.logger.info("Chat Agent Daemon initialized and listening", {
157
- workspaceId,
158
- agentDirectoryId: this.registryService.getAgentDirectoryId(),
159
- positionId: this.registryService.getPositionId(),
160
- teamId: this.registryService.getTeamId(),
161
- });
162
- }
163
- /**
164
- * Extract and classify incoming message from signal
165
- * Can be overridden in subclasses to customize filtering
166
- */
167
- async extractIncomingMessage(signal) {
168
- return this.messageClassifier.extractIncomingMessage(signal);
169
- }
170
- /**
171
- * Handle incoming signal from Hailer
172
- */
173
- async handleSignal(signal) {
174
- try {
175
- const message = await this.extractIncomingMessage(signal);
176
- if (!message)
177
- return;
178
- // Dedupe
179
- if (this.processedMessageIds.has(message.id)) {
180
- return;
181
- }
182
- this.processedMessageIds.add(message.id);
183
- // Clean up old IDs (keep last 500)
184
- if (this.processedMessageIds.size > 500) {
185
- const ids = Array.from(this.processedMessageIds);
186
- this.processedMessageIds = new Set(ids.slice(-250));
187
- }
188
- this.logger.info("Incoming message", {
189
- from: message.senderName,
190
- discussion: message.discussionId,
191
- priority: message.priority,
192
- reason: message.priorityReason,
193
- preview: message.content.substring(0, 50),
194
- });
195
- // Queue the message
196
- this.messageQueue.push(message);
197
- // Process if not already processing
198
- if (!this.isProcessing) {
199
- await this.processQueue();
200
- }
201
- }
202
- catch (error) {
203
- this.logger.error("Failed to handle signal", error);
204
- }
205
- }
206
- /**
207
- * Process queued messages through the LLM
208
- * Note: Uses flag-first pattern to prevent race conditions
209
- */
210
- async processQueue() {
211
- // Set flag FIRST to prevent race condition (multiple async calls)
212
- if (this.isProcessing)
213
- return;
214
- this.isProcessing = true;
215
- // Now check queue (after claiming the lock)
216
- if (this.messageQueue.length === 0) {
217
- this.isProcessing = false;
218
- return;
219
- }
220
- try {
221
- // Sort by priority (high first) then by timestamp
222
- this.messageQueue.sort((a, b) => {
223
- const priorityOrder = { high: 0, normal: 1, low: 2 };
224
- if (priorityOrder[a.priority] !== priorityOrder[b.priority]) {
225
- return priorityOrder[a.priority] - priorityOrder[b.priority];
226
- }
227
- return a.timestamp - b.timestamp;
228
- });
229
- // Process one message at a time for now
230
- while (this.messageQueue.length > 0) {
231
- const message = this.messageQueue.shift();
232
- await this.processMessage(message);
233
- }
234
- }
235
- finally {
236
- this.isProcessing = false;
237
- }
238
- }
239
- /**
240
- * Process a single message through the persistent LLM conversation
241
- */
242
- async processMessage(message) {
243
- const startTime = Date.now();
244
- // Update current context for session tracking
245
- this.currentDiscussionId = message.discussionId;
246
- this.currentLinkedActivityId = message.linkedActivityId || null;
247
- // Multi-tenant: Initialize schema cache for this workspace on-demand
248
- // Each workspace has its own workflow IDs - must be cached before processing
249
- if (this.schemaCache && message.workspaceId) {
250
- const schemas = this.schemaCache.getSchemas(message.workspaceId);
251
- if (!schemas?.initialized) {
252
- this.logger.info("Initializing schema cache for workspace", { workspaceId: message.workspaceId });
253
- await this.schemaCache.initializeForWorkspace(message.workspaceId);
254
- // Update session logger with workspace-specific schema
255
- if (this.sessionLogger) {
256
- this.sessionLogger.setSchemaCache(this.schemaCache, message.workspaceId);
257
- }
258
- }
259
- }
260
- // Start typing indicator with auto-refresh
261
- this.startTypingIndicator(message.discussionId);
262
- // Get or create activity session
263
- const session = this.sessionLogger.getOrCreateActivitySession(message);
264
- session.lastActivityTime = Date.now();
265
- session.metrics.messagesProcessed++;
266
- // Add user message to conversation log (compact version)
267
- const userSnippet = message.content.length > 100
268
- ? message.content.substring(0, 100) + "..."
269
- : message.content;
270
- session.conversation.push(`${message.senderName}: ${userSnippet}`);
271
- // Format incoming message for LLM
272
- const incomingContent = this.formatIncomingMessage(message);
273
- // Get conversation for THIS discussion (isolated context)
274
- const conversation = this.conversationManager.getConversation(message.discussionId);
275
- // Load memory from Hailer if this is a new conversation for an activity
276
- if (conversation.length === 0 && message.linkedActivityId) {
277
- await this.injectMemoryForActivity(conversation, message.linkedActivityId, message.workspaceId);
278
- }
279
- // Append to conversation
280
- conversation.push({
281
- role: "user",
282
- content: incomingContent,
283
- });
284
- // Check context size and summarize if needed
285
- await this.conversationManager.manageContextSize(message.discussionId);
286
- try {
287
- // Call LLM with this discussion's conversation
288
- const response = await this.client.messages.create({
289
- model: this.config.model || "claude-haiku-4-5-20251001",
290
- max_tokens: 2000,
291
- system: this.getSystemPrompt(),
292
- messages: conversation,
293
- tools: this.getTools(),
294
- });
295
- // Track token usage in the activity session
296
- if (response.usage) {
297
- session.metrics.inputTokens += response.usage.input_tokens;
298
- session.metrics.outputTokens += response.usage.output_tokens;
299
- }
300
- // Handle response
301
- await this.handleLlmResponse(response, message);
302
- const duration = Date.now() - startTime;
303
- this.logger.info("Message processed", {
304
- discussion: message.discussionId,
305
- duration,
306
- stopReason: response.stop_reason,
307
- sessionActivity: session.activityName,
308
- });
309
- // Update last activity time (idle checker will flush when needed)
310
- session.lastActivityTime = Date.now();
311
- }
312
- catch (error) {
313
- this.logger.error("LLM processing failed", error);
314
- // Stop typing indicator on error
315
- this.stopTypingIndicator();
316
- // On error, remove the message from conversation to avoid poisoning context
317
- if (conversation.length > 0) {
318
- conversation.pop();
319
- }
320
- }
321
- }
322
- /**
323
- * Format incoming message for LLM consumption
324
- */
325
- formatIncomingMessage(message) {
326
- const priorityTag = message.priority === "high" ? " priority=\"high\"" : "";
327
- const reasonAttr = message.priority === "high" ? ` reason="${message.priorityReason}"` : "";
328
- const activityAttr = message.linkedActivityId ? ` activity_id="${message.linkedActivityId}"` : "";
329
- return `<incoming discussion="${message.discussionId}"${activityAttr} from="${message.senderName}" user_id="${message.senderId}" timestamp="${new Date(message.timestamp).toISOString()}"${priorityTag}${reasonAttr}>
330
- ${message.content}
331
- </incoming>`;
332
- }
333
- /**
334
- * Load and inject memory for an activity into conversation context
335
- * Called when entering a new discussion that's linked to an activity
336
- */
337
- async injectMemoryForActivity(conversation, activityId, workspaceId) {
338
- try {
339
- const memoryEntries = await this.sessionLogger.loadMemoryForActivity(activityId, workspaceId, 5);
340
- if (memoryEntries.length > 0) {
341
- const memoryContext = this.sessionLogger.formatMemoryForContext(memoryEntries);
342
- // Inject memory as a system-like user message at the start
343
- conversation.push({
344
- role: "user",
345
- content: memoryContext,
346
- });
347
- // Add acknowledgment to keep conversation valid (assistant must respond)
348
- conversation.push({
349
- role: "assistant",
350
- content: "I've loaded context from previous interactions with this activity. I'll use this to provide better continuity.",
351
- });
352
- this.logger.info("Injected memory into conversation", {
353
- activityId,
354
- memoryCount: memoryEntries.length,
355
- });
356
- }
357
- }
358
- catch (error) {
359
- this.logger.warn("Failed to inject memory", {
360
- activityId,
361
- error: error instanceof Error ? error.message : String(error),
362
- });
363
- // Continue without memory - not critical
364
- }
365
- }
366
- /**
367
- * Execute tool calls and continue the conversation
368
- * Simple passthrough - just execute tools and return results to LLM
369
- */
370
- async executeToolsAndContinue(toolUseBlocks, originalMessage) {
371
- // Get current activity session
372
- const sessionKey = this.currentLinkedActivityId || this.currentDiscussionId || "default";
373
- const session = this.sessionLogger.getSession(sessionKey);
374
- // Store the user's request that triggered these tool calls (for context)
375
- if (session && !session.triggerRequest) {
376
- session.triggerRequest = originalMessage.content.substring(0, 500);
377
- session.requestedBy = originalMessage.senderName;
378
- session.requestedById = originalMessage.senderId;
379
- }
380
- // Execute tools using the tool executor service
381
- const toolResults = await this.toolExecutor.executeTools(toolUseBlocks, {
382
- session,
383
- preprocessToolInput: this.preprocessToolInput.bind(this),
384
- });
385
- // Get conversation for this discussion
386
- const conversation = this.conversationManager.getConversation(originalMessage.discussionId);
387
- // Add tool results to conversation
388
- conversation.push({
389
- role: "user",
390
- content: toolResults,
391
- });
392
- // Continue with LLM
393
- const response = await this.client.messages.create({
394
- model: this.config.model || "claude-haiku-4-5-20251001",
395
- max_tokens: 2000,
396
- system: this.getSystemPrompt(),
397
- messages: conversation,
398
- tools: this.getTools(),
399
- });
400
- // Track token usage in session
401
- if (session && response.usage) {
402
- session.metrics.inputTokens += response.usage.input_tokens;
403
- session.metrics.outputTokens += response.usage.output_tokens;
404
- session.lastActivityTime = Date.now();
405
- }
406
- // Recursively handle (might need more tools or finally respond)
407
- await this.handleLlmResponse(response, originalMessage);
408
- }
409
- /**
410
- * Handle LLM response
411
- * Override in subclasses to customize response handling
412
- */
413
- async handleLlmResponse(response, originalMessage) {
414
- // Get conversation for this discussion
415
- const conversation = this.conversationManager.getConversation(originalMessage.discussionId);
416
- // Add assistant response to conversation
417
- // Cast response content to MessageParam content type (ContentBlock[] → ContentBlockParam[])
418
- conversation.push({
419
- role: "assistant",
420
- content: response.content,
421
- });
422
- // Check for tool calls
423
- const toolUseBlocks = response.content.filter((block) => block.type === "tool_use");
424
- if (toolUseBlocks.length > 0) {
425
- // Execute tools and continue conversation
426
- await this.executeToolsAndContinue(toolUseBlocks, originalMessage);
427
- return;
428
- }
429
- // Check for text response
430
- const textBlocks = response.content.filter((block) => block.type === "text");
431
- if (textBlocks.length > 0) {
432
- const responseText = textBlocks.map(b => b.text).join("\n").trim();
433
- // Log metadata only - avoid logging user content (PII risk)
434
- this.logger.info("LLM response received", {
435
- discussion: originalMessage.discussionId,
436
- priority: originalMessage.priority,
437
- responseLength: responseText.length,
438
- hasIgnoreTag: responseText.includes("<ignore"),
439
- hasRespondTag: responseText.includes("<respond"),
440
- });
441
- // Check for IGNORE decision
442
- if (responseText.includes("<decision>IGNORE</decision>") ||
443
- responseText.includes("<ignore")) {
444
- this.logger.debug("LLM decided to ignore message - removing from context", {
445
- discussion: originalMessage.discussionId,
446
- });
447
- // Stop typing indicator
448
- this.stopTypingIndicator();
449
- // Remove both the assistant's ignore response AND the original incoming message
450
- // to keep the context clean from irrelevant chatter
451
- conversation.pop(); // Remove assistant response we just added
452
- conversation.pop(); // Remove the incoming message
453
- return;
454
- }
455
- // Check for explicit response directive
456
- const responseMatch = responseText.match(/<respond discussion="([^"]+)">([\s\S]*?)<\/respond>/);
457
- if (responseMatch) {
458
- const targetDiscussion = responseMatch[1];
459
- const content = responseMatch[2].trim();
460
- await this.postResponse(targetDiscussion, content);
461
- return;
462
- }
463
- // For high priority messages, always send substantive responses
464
- if (originalMessage.priority === "high" && responseText &&
465
- !responseText.includes("<thinking>") &&
466
- !responseText.startsWith("I'll") &&
467
- responseText.length > 10) {
468
- await this.postResponse(originalMessage.discussionId, responseText);
469
- return;
470
- }
471
- // For normal priority, ONLY respond if LLM used explicit <respond> tag
472
- // This was already handled above - if we reach here, don't post
473
- // (LLM should use <respond> or <ignore> for normal priority messages)
474
- if (originalMessage.priority === "normal") {
475
- this.logger.debug("Normal priority without <respond> tag - removing from context", {
476
- discussion: originalMessage.discussionId,
477
- responsePreview: responseText.substring(0, 100),
478
- });
479
- // Stop typing indicator
480
- this.stopTypingIndicator();
481
- // Remove from context - we didn't respond so don't need this in history
482
- conversation.pop(); // Remove assistant response
483
- conversation.pop(); // Remove incoming message
484
- }
485
- }
486
- }
487
- /**
488
- * Start typing indicator with auto-refresh interval
489
- * Keeps refreshing every 3 seconds until stopTypingIndicator is called
490
- */
491
- startTypingIndicator(discussionId) {
492
- // Stop any existing typing interval
493
- this.stopTypingIndicator();
494
- this.typingDiscussionId = discussionId;
495
- // Send initial typing signal
496
- this.sendTypingSignal(discussionId, true);
497
- // Set up interval to refresh typing (prevents server-side timeout)
498
- this.typingInterval = setInterval(() => {
499
- if (this.typingDiscussionId) {
500
- this.sendTypingSignal(this.typingDiscussionId, true);
501
- }
502
- }, ChatAgentDaemon.TYPING_REFRESH_MS);
503
- }
504
- /**
505
- * Stop typing indicator and clear refresh interval
506
- */
507
- stopTypingIndicator() {
508
- if (this.typingInterval) {
509
- clearInterval(this.typingInterval);
510
- this.typingInterval = null;
511
- }
512
- if (this.typingDiscussionId) {
513
- this.sendTypingSignal(this.typingDiscussionId, false);
514
- this.typingDiscussionId = null;
515
- }
516
- }
517
- /**
518
- * Send typing signal to Hailer API
519
- * API: messenger.set_discussion_typing_state(discussionId, typingState)
520
- */
521
- sendTypingSignal(discussionId, isTyping) {
522
- this.botClient.client.socket.request("messenger.set_discussion_typing_state", [
523
- discussionId,
524
- isTyping
525
- ]).catch((error) => {
526
- // Silently ignore - typing indicator is not critical
527
- this.logger.debug("Typing indicator failed", { discussionId, isTyping, error: error?.message });
528
- });
529
- }
530
- /**
531
- * Post a response to a discussion
532
- * Automatically converts @mentions and #activity tags to Hailer tags
533
- * Includes links metadata required for tags to work
534
- */
535
- async postResponse(discussionId, content) {
536
- // Stop typing indicator before posting
537
- this.stopTypingIndicator();
538
- try {
539
- // Resolve tags that need API lookup
540
- let formattedContent = await this.messageFormatter.resolveUserTags(content); // @userId → [hailerTag|Name](id)
541
- formattedContent = await this.messageFormatter.resolveActivityTags(formattedContent); // #activityId → [hailerTag|Name](id)
542
- formattedContent = await this.messageFormatter.resolveHailerUrls(formattedContent); // URLs → [hailerTag|Name](id)
543
- // Then convert any remaining @mentions from cache (fallback)
544
- formattedContent = this.messageFormatter.convertMentionsToTags(formattedContent);
545
- // Remove redundant "(Name)" after tags - LLM sometimes adds these
546
- formattedContent = formattedContent.replace(/(\[hailerTag\|[^\]]+\]\([a-f0-9]{24}\)\uFEFF?)\s*\([^)]+\)/gi, '$1');
547
- // Extract link metadata for any tags in the content
548
- const links = this.messageFormatter.extractTagLinks(formattedContent);
549
- // Build message object with links if we have any
550
- const messageData = {
551
- msg: formattedContent
552
- };
553
- if (links.length > 0) {
554
- messageData.links = links;
555
- }
556
- await this.botClient.client.socket.request("messenger.send", [
557
- messageData,
558
- discussionId,
559
- ]);
560
- // Track response in activity session
561
- const sessionKey = this.currentLinkedActivityId || this.currentDiscussionId || "default";
562
- const session = this.sessionLogger.getSession(sessionKey);
563
- if (session) {
564
- session.metrics.responsesPosted++;
565
- session.actions.push(`Responded in discussion`);
566
- session.lastActivityTime = Date.now();
567
- // Add bot response to conversation log (compact version)
568
- const botSnippet = content.length > 100 ? content.substring(0, 100) + "..." : content;
569
- session.conversation.push(`Bot: ${botSnippet}`);
570
- }
571
- this.logger.info("Response posted", {
572
- discussion: discussionId,
573
- length: formattedContent.length,
574
- hadTags: formattedContent !== content,
575
- linkCount: links.length,
576
- });
577
- }
578
- catch (error) {
579
- this.logger.error("Failed to post response", error);
580
- }
581
- }
582
- /**
583
- * Get the system prompt for the daemon
584
- * MUST be overridden in subclasses
585
- */
586
- getSystemPrompt() {
587
- throw new Error("getSystemPrompt() must be implemented by subclass");
588
- }
589
- /**
590
- * Get tools for LLM calls
591
- * Override in subclass to add custom tools (like trigger_giuseppe_retry)
592
- */
593
- getTools() {
594
- return this.minimalTools;
595
- }
596
- /**
597
- * Get tool whitelist for this agent
598
- * Override in subclass to limit which tools are available
599
- * Return null for all tools, or array of tool names
600
- */
601
- getToolWhitelist() {
602
- return null; // Default: all tools
603
- }
604
- /**
605
- * Preprocess tool input before execution
606
- * Override in subclass to inject context (e.g., sourceActivityId)
607
- * @param toolName - Name of the tool being called
608
- * @param input - Original tool input from LLM
609
- * @returns Processed input (may be modified)
610
- */
611
- preprocessToolInput(toolName, input) {
612
- return input; // Default: no preprocessing
613
- }
614
- /**
615
- * Call MCP tool (delegates to McpClientService)
616
- * Protected for subclass access
617
- */
618
- async callMcpTool(name, args) {
619
- return this.mcpClient.callMcpTool(name, args);
620
- }
621
- /**
622
- * Stop the daemon
623
- * Flushes all pending activity sessions before stopping
624
- */
625
- async stop() {
626
- // Stop idle check timer and flush all pending sessions
627
- this.sessionLogger?.stopIdleCheckTimer();
628
- await this.sessionLogger?.flushAllSessions();
629
- this.botClient.signalHandler.unsubscribe(`daemon-${this.botClient.userId}`);
630
- this.logger.info("Chat Agent Daemon stopped", {
631
- agentDirectoryId: this.registryService?.getAgentDirectoryId(),
632
- activeSessions: this.sessionLogger?.getActiveSessions().size ?? 0,
633
- });
634
- }
635
- /**
636
- * Get current conversation state (for debugging)
637
- */
638
- getConversationState() {
639
- // Get current discussion's conversation
640
- const currentConversation = this.currentDiscussionId
641
- ? this.conversationManager.getConversation(this.currentDiscussionId)
642
- : [];
643
- // Get last 5 messages with preview
644
- const lastMessages = currentConversation.slice(-5).map(msg => {
645
- let preview = "";
646
- if (typeof msg.content === "string") {
647
- preview = msg.content.substring(0, 100);
648
- }
649
- else if (Array.isArray(msg.content)) {
650
- const textBlock = msg.content.find((b) => b.type === "text");
651
- preview = textBlock?.text?.substring(0, 100) || "[tool call/result]";
652
- }
653
- return {
654
- role: msg.role,
655
- preview: preview + (preview.length >= 100 ? "..." : ""),
656
- };
657
- });
658
- const state = this.conversationManager.getState(this.currentDiscussionId ?? undefined);
659
- return {
660
- discussionCount: state.discussionCount,
661
- currentDiscussion: this.currentDiscussionId,
662
- currentMessageCount: currentConversation.length,
663
- queueLength: this.messageQueue.length,
664
- lastMessages,
665
- isProcessing: this.isProcessing,
666
- };
667
- }
668
- /**
669
- * Get full conversation for a specific discussion (for debugging)
670
- */
671
- getFullConversation(discussionId) {
672
- const targetDiscussion = discussionId || this.currentDiscussionId;
673
- if (!targetDiscussion)
674
- return [];
675
- return [...this.conversationManager.getFullConversation(targetDiscussion)];
676
- }
677
- // ===== AGENT REGISTRY & SESSION LOGGING METHODS =====
678
- /**
679
- * Get agent's display name (override in subclass for custom names)
680
- * Default implementation uses the actual Hailer user name from BotClient
681
- */
682
- getAgentName() {
683
- // Use actual Hailer user name from BotClient (populated from workspace cache)
684
- return {
685
- firstName: this.botClient.firstName,
686
- lastName: this.botClient.lastName,
687
- };
688
- }
689
- /**
690
- * Get agent's description/system prompt (override in subclass)
691
- */
692
- getAgentDescription() {
693
- return "Chat Agent Daemon - handles general workspace conversations";
694
- }
695
- /**
696
- * Get default team ID from workspace cache
697
- * Returns the first available team, or undefined if no teams exist
698
- *
699
- * Teams structure in init is: { teams: { workspaceId: { teamId: teamData, ... } } }
700
- */
701
- getDefaultTeamId() {
702
- const rawInit = this.botClient.workspaceCache?.rawInit;
703
- if (!rawInit?.teams) {
704
- this.logger.debug("No teams in workspace cache");
705
- return undefined;
706
- }
707
- // Teams are nested under workspace ID
708
- // Structure: { workspaceId: { teamId1: {...}, teamId2: {...} } }
709
- const workspaceTeams = Object.values(rawInit.teams)[0];
710
- if (!workspaceTeams || typeof workspaceTeams !== 'object') {
711
- this.logger.debug("No workspace teams found");
712
- return undefined;
713
- }
714
- const teamIds = Object.keys(workspaceTeams);
715
- if (teamIds.length === 0) {
716
- this.logger.debug("No teams available in workspace");
717
- return undefined;
718
- }
719
- const defaultTeamId = teamIds[0];
720
- const teamName = workspaceTeams[defaultTeamId]?.name || 'unknown';
721
- this.logger.debug("Using default team", { teamId: defaultTeamId, teamName, teamCount: teamIds.length });
722
- return defaultTeamId;
723
- }
724
- /**
725
- * Get agent's Position details (override in subclass for custom positions)
726
- */
727
- getPositionDetails() {
728
- return {
729
- name: "Chat Agent",
730
- purpose: "General workspace assistant that monitors discussions and responds to user queries.",
731
- personaTone: "Professional, helpful, and concise. Responds directly without unnecessary filler.",
732
- coreCapabilities: "- Monitor workspace discussions\n- Answer questions about workflows and activities\n- Execute MCP tools to read/write data\n- Tag users and link activities",
733
- boundaries: "- Never fabricate data - always use tools\n- Don't respond to off-topic messages\n- Don't share sensitive credentials",
734
- };
735
- }
736
- /**
737
- * Get agent directory ID
738
- */
739
- getAgentDirectoryId() {
740
- return this.registryService?.getAgentDirectoryId() || null;
741
- }
742
- }
743
- exports.ChatAgentDaemon = ChatAgentDaemon;
744
- //# sourceMappingURL=base.js.map