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