@hailer/mcp 0.1.17 → 0.2.2

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 (200) hide show
  1. package/dist/app.js +27 -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-server.d.ts +2 -2
  37. package/dist/mcp-server.js +161 -139
  38. package/package.json +1 -1
  39. package/REFACTOR_STATUS.md +0 -127
  40. package/dist/agents/bot-manager.d.ts +0 -48
  41. package/dist/agents/bot-manager.js +0 -254
  42. package/dist/agents/factory.d.ts +0 -150
  43. package/dist/agents/factory.js +0 -650
  44. package/dist/agents/giuseppe/ai.d.ts +0 -83
  45. package/dist/agents/giuseppe/ai.js +0 -466
  46. package/dist/agents/giuseppe/bot.d.ts +0 -110
  47. package/dist/agents/giuseppe/bot.js +0 -780
  48. package/dist/agents/giuseppe/config.d.ts +0 -25
  49. package/dist/agents/giuseppe/config.js +0 -227
  50. package/dist/agents/giuseppe/files.d.ts +0 -52
  51. package/dist/agents/giuseppe/files.js +0 -338
  52. package/dist/agents/giuseppe/git.d.ts +0 -48
  53. package/dist/agents/giuseppe/git.js +0 -298
  54. package/dist/agents/giuseppe/index.d.ts +0 -97
  55. package/dist/agents/giuseppe/index.js +0 -258
  56. package/dist/agents/giuseppe/lsp.d.ts +0 -113
  57. package/dist/agents/giuseppe/lsp.js +0 -485
  58. package/dist/agents/giuseppe/monitor.d.ts +0 -118
  59. package/dist/agents/giuseppe/monitor.js +0 -621
  60. package/dist/agents/giuseppe/prompt.d.ts +0 -5
  61. package/dist/agents/giuseppe/prompt.js +0 -94
  62. package/dist/agents/giuseppe/registries/pending-classification.d.ts +0 -28
  63. package/dist/agents/giuseppe/registries/pending-classification.js +0 -50
  64. package/dist/agents/giuseppe/registries/pending-fix.d.ts +0 -30
  65. package/dist/agents/giuseppe/registries/pending-fix.js +0 -42
  66. package/dist/agents/giuseppe/registries/pending.d.ts +0 -27
  67. package/dist/agents/giuseppe/registries/pending.js +0 -49
  68. package/dist/agents/giuseppe/specialist.d.ts +0 -47
  69. package/dist/agents/giuseppe/specialist.js +0 -237
  70. package/dist/agents/giuseppe/types.d.ts +0 -123
  71. package/dist/agents/giuseppe/types.js +0 -9
  72. package/dist/agents/hailer-expert/index.d.ts +0 -8
  73. package/dist/agents/hailer-expert/index.js +0 -14
  74. package/dist/agents/hal/daemon.d.ts +0 -142
  75. package/dist/agents/hal/daemon.js +0 -1103
  76. package/dist/agents/hal/definitions.d.ts +0 -55
  77. package/dist/agents/hal/definitions.js +0 -263
  78. package/dist/agents/hal/index.d.ts +0 -3
  79. package/dist/agents/hal/index.js +0 -8
  80. package/dist/agents/index.d.ts +0 -18
  81. package/dist/agents/index.js +0 -48
  82. package/dist/agents/shared/base.d.ts +0 -216
  83. package/dist/agents/shared/base.js +0 -846
  84. package/dist/agents/shared/services/agent-registry.d.ts +0 -107
  85. package/dist/agents/shared/services/agent-registry.js +0 -629
  86. package/dist/agents/shared/services/conversation-manager.d.ts +0 -50
  87. package/dist/agents/shared/services/conversation-manager.js +0 -136
  88. package/dist/agents/shared/services/mcp-client.d.ts +0 -56
  89. package/dist/agents/shared/services/mcp-client.js +0 -124
  90. package/dist/agents/shared/services/message-classifier.d.ts +0 -37
  91. package/dist/agents/shared/services/message-classifier.js +0 -187
  92. package/dist/agents/shared/services/message-formatter.d.ts +0 -89
  93. package/dist/agents/shared/services/message-formatter.js +0 -371
  94. package/dist/agents/shared/services/session-logger.d.ts +0 -106
  95. package/dist/agents/shared/services/session-logger.js +0 -446
  96. package/dist/agents/shared/services/tool-executor.d.ts +0 -41
  97. package/dist/agents/shared/services/tool-executor.js +0 -169
  98. package/dist/agents/shared/services/workspace-schema-cache.d.ts +0 -125
  99. package/dist/agents/shared/services/workspace-schema-cache.js +0 -578
  100. package/dist/agents/shared/specialist.d.ts +0 -91
  101. package/dist/agents/shared/specialist.js +0 -399
  102. package/dist/agents/shared/tool-schema-loader.d.ts +0 -62
  103. package/dist/agents/shared/tool-schema-loader.js +0 -232
  104. package/dist/agents/shared/types.d.ts +0 -327
  105. package/dist/agents/shared/types.js +0 -121
  106. package/dist/client/agents/base.d.ts +0 -207
  107. package/dist/client/agents/base.js +0 -744
  108. package/dist/client/agents/definitions.d.ts +0 -53
  109. package/dist/client/agents/definitions.js +0 -263
  110. package/dist/client/agents/orchestrator.d.ts +0 -141
  111. package/dist/client/agents/orchestrator.js +0 -1062
  112. package/dist/client/agents/specialist.d.ts +0 -86
  113. package/dist/client/agents/specialist.js +0 -340
  114. package/dist/client/bot-entrypoint.d.ts +0 -7
  115. package/dist/client/bot-entrypoint.js +0 -103
  116. package/dist/client/bot-manager.d.ts +0 -44
  117. package/dist/client/bot-manager.js +0 -173
  118. package/dist/client/bot-runner.d.ts +0 -35
  119. package/dist/client/bot-runner.js +0 -188
  120. package/dist/client/chat-agent-daemon.d.ts +0 -464
  121. package/dist/client/chat-agent-daemon.js +0 -1774
  122. package/dist/client/daemon-factory.d.ts +0 -106
  123. package/dist/client/daemon-factory.js +0 -301
  124. package/dist/client/factory.d.ts +0 -111
  125. package/dist/client/factory.js +0 -314
  126. package/dist/client/index.d.ts +0 -17
  127. package/dist/client/index.js +0 -38
  128. package/dist/client/multi-bot-manager.d.ts +0 -42
  129. package/dist/client/multi-bot-manager.js +0 -161
  130. package/dist/client/orchestrator-daemon.d.ts +0 -87
  131. package/dist/client/orchestrator-daemon.js +0 -444
  132. package/dist/client/server.d.ts +0 -8
  133. package/dist/client/server.js +0 -251
  134. package/dist/client/services/agent-registry.d.ts +0 -108
  135. package/dist/client/services/agent-registry.js +0 -630
  136. package/dist/client/services/conversation-manager.d.ts +0 -50
  137. package/dist/client/services/conversation-manager.js +0 -136
  138. package/dist/client/services/mcp-client.d.ts +0 -48
  139. package/dist/client/services/mcp-client.js +0 -105
  140. package/dist/client/services/message-classifier.d.ts +0 -37
  141. package/dist/client/services/message-classifier.js +0 -187
  142. package/dist/client/services/message-formatter.d.ts +0 -84
  143. package/dist/client/services/message-formatter.js +0 -353
  144. package/dist/client/services/session-logger.d.ts +0 -106
  145. package/dist/client/services/session-logger.js +0 -446
  146. package/dist/client/services/tool-executor.d.ts +0 -41
  147. package/dist/client/services/tool-executor.js +0 -169
  148. package/dist/client/services/workspace-schema-cache.d.ts +0 -149
  149. package/dist/client/services/workspace-schema-cache.js +0 -732
  150. package/dist/client/specialist-daemon.d.ts +0 -77
  151. package/dist/client/specialist-daemon.js +0 -197
  152. package/dist/client/specialists.d.ts +0 -53
  153. package/dist/client/specialists.js +0 -178
  154. package/dist/client/tool-schema-loader.d.ts +0 -62
  155. package/dist/client/tool-schema-loader.js +0 -232
  156. package/dist/client/types.d.ts +0 -327
  157. package/dist/client/types.js +0 -121
  158. package/dist/commands/seed-config.d.ts +0 -9
  159. package/dist/commands/seed-config.js +0 -372
  160. package/dist/lib/context-manager.d.ts +0 -111
  161. package/dist/lib/context-manager.js +0 -431
  162. package/dist/lib/prompt-length-manager.d.ts +0 -81
  163. package/dist/lib/prompt-length-manager.js +0 -457
  164. package/dist/mcp/tools/giuseppe-tools.d.ts +0 -21
  165. package/dist/modules/bug-reports/bug-config.d.ts +0 -25
  166. package/dist/modules/bug-reports/bug-config.js +0 -187
  167. package/dist/modules/bug-reports/bug-monitor.d.ts +0 -108
  168. package/dist/modules/bug-reports/bug-monitor.js +0 -510
  169. package/dist/modules/bug-reports/giuseppe-agent.d.ts +0 -58
  170. package/dist/modules/bug-reports/giuseppe-agent.js +0 -467
  171. package/dist/modules/bug-reports/giuseppe-ai.d.ts +0 -83
  172. package/dist/modules/bug-reports/giuseppe-ai.js +0 -466
  173. package/dist/modules/bug-reports/giuseppe-bot.d.ts +0 -110
  174. package/dist/modules/bug-reports/giuseppe-bot.js +0 -804
  175. package/dist/modules/bug-reports/giuseppe-daemon.d.ts +0 -80
  176. package/dist/modules/bug-reports/giuseppe-daemon.js +0 -617
  177. package/dist/modules/bug-reports/giuseppe-files.d.ts +0 -64
  178. package/dist/modules/bug-reports/giuseppe-files.js +0 -375
  179. package/dist/modules/bug-reports/giuseppe-git.d.ts +0 -48
  180. package/dist/modules/bug-reports/giuseppe-git.js +0 -298
  181. package/dist/modules/bug-reports/giuseppe-lsp.d.ts +0 -113
  182. package/dist/modules/bug-reports/giuseppe-lsp.js +0 -485
  183. package/dist/modules/bug-reports/giuseppe-prompt.d.ts +0 -5
  184. package/dist/modules/bug-reports/giuseppe-prompt.js +0 -94
  185. package/dist/modules/bug-reports/index.d.ts +0 -77
  186. package/dist/modules/bug-reports/index.js +0 -215
  187. package/dist/modules/bug-reports/pending-classification-registry.d.ts +0 -28
  188. package/dist/modules/bug-reports/pending-classification-registry.js +0 -50
  189. package/dist/modules/bug-reports/pending-fix-registry.d.ts +0 -30
  190. package/dist/modules/bug-reports/pending-fix-registry.js +0 -42
  191. package/dist/modules/bug-reports/pending-registry.d.ts +0 -27
  192. package/dist/modules/bug-reports/pending-registry.js +0 -49
  193. package/dist/modules/bug-reports/types.d.ts +0 -123
  194. package/dist/modules/bug-reports/types.js +0 -9
  195. package/dist/routes/agents.d.ts +0 -44
  196. package/dist/routes/agents.js +0 -311
  197. package/dist/services/agent-credential-store.d.ts +0 -73
  198. package/dist/services/agent-credential-store.js +0 -212
  199. package/dist/services/bug-monitor.d.ts +0 -23
  200. 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