@hailer/mcp 0.1.15 → 0.1.16
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/.claude/agents/agent-giuseppe-app-builder.md +7 -6
- package/.claude/agents/agent-lars-code-inspector.md +26 -14
- package/dist/agents/bot-manager.d.ts +48 -0
- package/dist/agents/bot-manager.js +254 -0
- package/dist/agents/factory.d.ts +150 -0
- package/dist/agents/factory.js +650 -0
- package/dist/agents/giuseppe/ai.d.ts +83 -0
- package/dist/agents/giuseppe/ai.js +466 -0
- package/dist/agents/giuseppe/bot.d.ts +110 -0
- package/dist/agents/giuseppe/bot.js +780 -0
- package/dist/agents/giuseppe/config.d.ts +25 -0
- package/dist/agents/giuseppe/config.js +227 -0
- package/dist/agents/giuseppe/files.d.ts +52 -0
- package/dist/agents/giuseppe/files.js +338 -0
- package/dist/agents/giuseppe/git.d.ts +48 -0
- package/dist/agents/giuseppe/git.js +298 -0
- package/dist/agents/giuseppe/index.d.ts +97 -0
- package/dist/agents/giuseppe/index.js +258 -0
- package/dist/agents/giuseppe/lsp.d.ts +113 -0
- package/dist/agents/giuseppe/lsp.js +485 -0
- package/dist/agents/giuseppe/monitor.d.ts +118 -0
- package/dist/agents/giuseppe/monitor.js +621 -0
- package/dist/agents/giuseppe/prompt.d.ts +5 -0
- package/dist/agents/giuseppe/prompt.js +94 -0
- package/dist/agents/giuseppe/registries/pending-classification.d.ts +28 -0
- package/dist/agents/giuseppe/registries/pending-classification.js +50 -0
- package/dist/agents/giuseppe/registries/pending-fix.d.ts +30 -0
- package/dist/agents/giuseppe/registries/pending-fix.js +42 -0
- package/dist/agents/giuseppe/registries/pending.d.ts +27 -0
- package/dist/agents/giuseppe/registries/pending.js +49 -0
- package/dist/agents/giuseppe/specialist.d.ts +47 -0
- package/dist/agents/giuseppe/specialist.js +237 -0
- package/dist/agents/giuseppe/types.d.ts +123 -0
- package/dist/agents/giuseppe/types.js +9 -0
- package/dist/agents/hailer-expert/index.d.ts +8 -0
- package/dist/agents/hailer-expert/index.js +14 -0
- package/dist/agents/hal/daemon.d.ts +142 -0
- package/dist/agents/hal/daemon.js +1103 -0
- package/dist/agents/hal/definitions.d.ts +55 -0
- package/dist/agents/hal/definitions.js +263 -0
- package/dist/agents/hal/index.d.ts +3 -0
- package/dist/agents/hal/index.js +8 -0
- package/dist/agents/index.d.ts +18 -0
- package/dist/agents/index.js +48 -0
- package/dist/agents/shared/base.d.ts +216 -0
- package/dist/agents/shared/base.js +846 -0
- package/dist/agents/shared/services/agent-registry.d.ts +107 -0
- package/dist/agents/shared/services/agent-registry.js +629 -0
- package/dist/agents/shared/services/conversation-manager.d.ts +50 -0
- package/dist/agents/shared/services/conversation-manager.js +136 -0
- package/dist/agents/shared/services/mcp-client.d.ts +56 -0
- package/dist/agents/shared/services/mcp-client.js +124 -0
- package/dist/agents/shared/services/message-classifier.d.ts +37 -0
- package/dist/agents/shared/services/message-classifier.js +187 -0
- package/dist/agents/shared/services/message-formatter.d.ts +89 -0
- package/dist/agents/shared/services/message-formatter.js +371 -0
- package/dist/agents/shared/services/session-logger.d.ts +106 -0
- package/dist/agents/shared/services/session-logger.js +446 -0
- package/dist/agents/shared/services/tool-executor.d.ts +41 -0
- package/dist/agents/shared/services/tool-executor.js +169 -0
- package/dist/agents/shared/services/workspace-schema-cache.d.ts +125 -0
- package/dist/agents/shared/services/workspace-schema-cache.js +578 -0
- package/dist/agents/shared/specialist.d.ts +91 -0
- package/dist/agents/shared/specialist.js +399 -0
- package/dist/agents/shared/tool-schema-loader.d.ts +62 -0
- package/dist/agents/shared/tool-schema-loader.js +232 -0
- package/dist/agents/shared/types.d.ts +327 -0
- package/dist/agents/shared/types.js +121 -0
- package/dist/app.js +21 -4
- package/dist/cli.js +0 -0
- package/dist/client/agents/orchestrator.d.ts +1 -0
- package/dist/client/agents/orchestrator.js +12 -1
- package/dist/commands/seed-config.d.ts +9 -0
- package/dist/commands/seed-config.js +372 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.js +61 -1
- package/dist/core.d.ts +8 -0
- package/dist/core.js +137 -6
- package/dist/lib/discussion-lock.d.ts +42 -0
- package/dist/lib/discussion-lock.js +110 -0
- package/dist/mcp/UserContextCache.js +2 -2
- package/dist/mcp/hailer-clients.d.ts +15 -0
- package/dist/mcp/hailer-clients.js +100 -6
- package/dist/mcp/signal-handler.d.ts +16 -5
- package/dist/mcp/signal-handler.js +173 -122
- package/dist/mcp/tools/activity.js +9 -1
- package/dist/mcp/tools/bot-config.d.ts +184 -9
- package/dist/mcp/tools/bot-config.js +2177 -163
- package/dist/mcp/tools/giuseppe-tools.d.ts +21 -0
- package/dist/mcp/tools/giuseppe-tools.js +525 -0
- package/dist/mcp/utils/hailer-api-client.d.ts +42 -1
- package/dist/mcp/utils/hailer-api-client.js +128 -2
- package/dist/mcp/webhook-handler.d.ts +87 -0
- package/dist/mcp/webhook-handler.js +343 -0
- package/dist/mcp/workspace-cache.d.ts +5 -0
- package/dist/mcp/workspace-cache.js +11 -0
- package/dist/mcp-server.js +55 -5
- package/dist/modules/bug-reports/giuseppe-agent.d.ts +58 -0
- package/dist/modules/bug-reports/giuseppe-agent.js +467 -0
- package/dist/modules/bug-reports/giuseppe-ai.d.ts +25 -1
- package/dist/modules/bug-reports/giuseppe-ai.js +133 -2
- package/dist/modules/bug-reports/giuseppe-bot.d.ts +2 -2
- package/dist/modules/bug-reports/giuseppe-bot.js +66 -42
- package/dist/modules/bug-reports/giuseppe-daemon.d.ts +80 -0
- package/dist/modules/bug-reports/giuseppe-daemon.js +617 -0
- package/dist/modules/bug-reports/giuseppe-files.d.ts +12 -0
- package/dist/modules/bug-reports/giuseppe-files.js +37 -0
- package/dist/modules/bug-reports/giuseppe-lsp.d.ts +84 -13
- package/dist/modules/bug-reports/giuseppe-lsp.js +403 -61
- package/dist/modules/bug-reports/index.d.ts +1 -0
- package/dist/modules/bug-reports/index.js +31 -29
- package/package.json +3 -2
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Daemon Factory
|
|
4
|
+
*
|
|
5
|
+
* Creates and manages agent daemons in orchestrator mode:
|
|
6
|
+
* - One orchestrator (HAL) handles general conversation
|
|
7
|
+
* - Specialist daemons handle complex domain-specific tasks
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.DaemonManager = void 0;
|
|
44
|
+
exports.createDaemonManager = createDaemonManager;
|
|
45
|
+
exports.startDaemonMode = startDaemonMode;
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const daemon_1 = require("./hal/daemon");
|
|
49
|
+
const specialist_1 = require("./shared/specialist");
|
|
50
|
+
const definitions_1 = require("./hal/definitions");
|
|
51
|
+
const bot_manager_1 = require("./bot-manager");
|
|
52
|
+
const config_1 = require("../config");
|
|
53
|
+
const logger_1 = require("../lib/logger");
|
|
54
|
+
const bot_config_1 = require("../mcp/tools/bot-config");
|
|
55
|
+
const hailer_clients_1 = require("../mcp/hailer-clients");
|
|
56
|
+
const logger = (0, logger_1.createLogger)({ component: "DaemonFactory" });
|
|
57
|
+
/**
|
|
58
|
+
* Load workspace configurations from .bot-config/*.json files
|
|
59
|
+
*/
|
|
60
|
+
function loadWorkspaceConfigs() {
|
|
61
|
+
const configDir = path.join(process.cwd(), '.bot-config');
|
|
62
|
+
if (!fs.existsSync(configDir)) {
|
|
63
|
+
logger.warn('No .bot-config directory found');
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
const files = fs.readdirSync(configDir).filter(f => f.endsWith('.json'));
|
|
67
|
+
const configs = [];
|
|
68
|
+
for (const file of files) {
|
|
69
|
+
try {
|
|
70
|
+
const content = fs.readFileSync(path.join(configDir, file), 'utf-8');
|
|
71
|
+
configs.push(JSON.parse(content));
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
logger.warn('Failed to load workspace config', { file, error });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return configs;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Map botType from Agent Directory to specialist key
|
|
81
|
+
* botType values come from the Agent Directory workflow
|
|
82
|
+
*/
|
|
83
|
+
function mapBotTypeToSpecialistKey(botType) {
|
|
84
|
+
if (!botType)
|
|
85
|
+
return null;
|
|
86
|
+
const mapping = {
|
|
87
|
+
'workflow-expert': 'hailerExpert',
|
|
88
|
+
'bug-fixer': 'giuseppe',
|
|
89
|
+
// Add more mappings as new specialists are added
|
|
90
|
+
};
|
|
91
|
+
return mapping[botType] || null;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Manages orchestrator + specialist daemons
|
|
95
|
+
*/
|
|
96
|
+
class DaemonManager {
|
|
97
|
+
daemons = new Map();
|
|
98
|
+
orchestrator = null;
|
|
99
|
+
specialists = new Map();
|
|
100
|
+
botManager;
|
|
101
|
+
options;
|
|
102
|
+
botTypeMap;
|
|
103
|
+
displayNameMap;
|
|
104
|
+
constructor(botManager, options) {
|
|
105
|
+
this.botManager = botManager;
|
|
106
|
+
this.options = options;
|
|
107
|
+
this.botTypeMap = options.botTypeMap || new Map();
|
|
108
|
+
this.displayNameMap = options.displayNameMap || new Map();
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get botType for a userId from the workspace config
|
|
112
|
+
*/
|
|
113
|
+
getBotType(userId) {
|
|
114
|
+
return this.botTypeMap.get(userId) || null;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get displayName for a userId from the workspace config (Agent Directory activity name)
|
|
118
|
+
* Falls back to Hailer profile name if not set
|
|
119
|
+
*/
|
|
120
|
+
getDisplayName(userId) {
|
|
121
|
+
return this.displayNameMap.get(userId) || this.botManager.getUserName(userId);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Start all daemons (orchestrator + specialists)
|
|
125
|
+
*/
|
|
126
|
+
async startAll() {
|
|
127
|
+
await this.startOrchestratorMode();
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Orchestrator mode: one orchestrator (HAL) + specialist daemons
|
|
131
|
+
*/
|
|
132
|
+
async startOrchestratorMode() {
|
|
133
|
+
const botClients = this.botManager.getAllBotClients();
|
|
134
|
+
logger.info("Starting daemons in ORCHESTRATOR mode", {
|
|
135
|
+
botCount: botClients.length,
|
|
136
|
+
});
|
|
137
|
+
// Find orchestrator bot
|
|
138
|
+
let orchestratorClient;
|
|
139
|
+
const specialistClients = new Map();
|
|
140
|
+
for (const botClient of botClients) {
|
|
141
|
+
const email = botClient.config.email;
|
|
142
|
+
const botType = this.getBotType(botClient.userId);
|
|
143
|
+
// Check if this is the orchestrator (by email match, botType, or first bot fallback)
|
|
144
|
+
if (this.options.orchestratorEmail === email ||
|
|
145
|
+
botType === 'orchestrator' ||
|
|
146
|
+
(!this.options.orchestratorEmail && !orchestratorClient)) {
|
|
147
|
+
orchestratorClient = botClient;
|
|
148
|
+
logger.info("Orchestrator assigned", {
|
|
149
|
+
userId: botClient.userId,
|
|
150
|
+
email: (0, config_1.maskEmail)(email),
|
|
151
|
+
botType,
|
|
152
|
+
reason: this.options.orchestratorEmail === email ? 'email_match' :
|
|
153
|
+
botType === 'orchestrator' ? 'botType' : 'first_bot_fallback'
|
|
154
|
+
});
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
// Check if this matches a specialist
|
|
158
|
+
if (this.options.specialistEmails) {
|
|
159
|
+
for (const [specialistKey, specialistEmail] of Object.entries(this.options.specialistEmails)) {
|
|
160
|
+
if (email === specialistEmail) {
|
|
161
|
+
specialistClients.set(specialistKey, botClient);
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
// Auto-assign based on botType from workspace config
|
|
168
|
+
const specialistKey = mapBotTypeToSpecialistKey(botType);
|
|
169
|
+
if (specialistKey && definitions_1.SPECIALISTS[specialistKey]) {
|
|
170
|
+
specialistClients.set(specialistKey, botClient);
|
|
171
|
+
logger.info("Specialist assigned by botType", {
|
|
172
|
+
userId: botClient.userId,
|
|
173
|
+
botType,
|
|
174
|
+
specialistKey
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
// Fallback: assign to first available specialist
|
|
179
|
+
for (const key of Object.keys(definitions_1.SPECIALISTS)) {
|
|
180
|
+
if (!specialistClients.has(key)) {
|
|
181
|
+
specialistClients.set(key, botClient);
|
|
182
|
+
logger.info("Specialist assigned by fallback", {
|
|
183
|
+
userId: botClient.userId,
|
|
184
|
+
specialistKey: key
|
|
185
|
+
});
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (!orchestratorClient) {
|
|
193
|
+
throw new Error("No orchestrator bot found");
|
|
194
|
+
}
|
|
195
|
+
// Create specialist user ID map for orchestrator
|
|
196
|
+
const specialistUserIds = new Map();
|
|
197
|
+
for (const [key, client] of specialistClients) {
|
|
198
|
+
specialistUserIds.set(key, client.userId);
|
|
199
|
+
}
|
|
200
|
+
// Start orchestrator
|
|
201
|
+
const orchestratorConfig = {
|
|
202
|
+
botClient: orchestratorClient,
|
|
203
|
+
mcpServerUrl: this.options.mcpServerUrl,
|
|
204
|
+
anthropicApiKey: this.options.anthropicApiKey,
|
|
205
|
+
model: this.options.model,
|
|
206
|
+
specialistUserIds,
|
|
207
|
+
};
|
|
208
|
+
this.orchestrator = new daemon_1.OrchestratorDaemon(orchestratorConfig);
|
|
209
|
+
await this.orchestrator.initialize();
|
|
210
|
+
// Register specialist user IDs with orchestrator (with display names from workspace)
|
|
211
|
+
for (const [key, userId] of specialistUserIds) {
|
|
212
|
+
const displayName = this.getDisplayName(userId);
|
|
213
|
+
this.orchestrator.registerSpecialistUserId(key, userId, displayName);
|
|
214
|
+
}
|
|
215
|
+
this.daemons.set(orchestratorClient.userId, this.orchestrator);
|
|
216
|
+
logger.info("Orchestrator started", {
|
|
217
|
+
botId: orchestratorClient.userId,
|
|
218
|
+
email: (0, config_1.maskEmail)(orchestratorClient.config.email),
|
|
219
|
+
specialistCount: specialistClients.size,
|
|
220
|
+
});
|
|
221
|
+
// Subscribe to bot state changes to update specialist availability
|
|
222
|
+
(0, bot_config_1.onBotStateChange)((userId, enabled) => {
|
|
223
|
+
const botType = this.getBotType(userId);
|
|
224
|
+
// Skip orchestrator changes (handled by daemon restart)
|
|
225
|
+
if (botType === 'orchestrator')
|
|
226
|
+
return;
|
|
227
|
+
// Find the specialist key for this user
|
|
228
|
+
const specialistKey = mapBotTypeToSpecialistKey(botType);
|
|
229
|
+
if (!specialistKey) {
|
|
230
|
+
logger.debug('No specialist mapping for botType', { userId, botType });
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (enabled) {
|
|
234
|
+
// Re-register the specialist (with display name from workspace)
|
|
235
|
+
const displayName = this.getDisplayName(userId);
|
|
236
|
+
this.orchestrator?.registerSpecialistUserId(specialistKey, userId, displayName);
|
|
237
|
+
logger.info('Specialist re-enabled', { specialistKey, userId, displayName });
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
// Unregister the specialist
|
|
241
|
+
this.orchestrator?.unregisterSpecialist(specialistKey);
|
|
242
|
+
logger.info('Specialist disabled', { specialistKey, userId });
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
// Start specialist daemons
|
|
246
|
+
for (const [specialistKey, botClient] of specialistClients) {
|
|
247
|
+
const specialistDef = definitions_1.SPECIALISTS[specialistKey];
|
|
248
|
+
if (!specialistDef) {
|
|
249
|
+
logger.warn("Unknown specialist key", { key: specialistKey });
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
// Clone the specialist to avoid cross-workspace contamination
|
|
253
|
+
const specialist = { ...specialistDef, botUserId: botClient.userId };
|
|
254
|
+
const specialistConfig = {
|
|
255
|
+
botClient,
|
|
256
|
+
mcpServerUrl: this.options.mcpServerUrl,
|
|
257
|
+
anthropicApiKey: this.options.anthropicApiKey,
|
|
258
|
+
model: specialist.model || this.options.model,
|
|
259
|
+
specialist,
|
|
260
|
+
};
|
|
261
|
+
const specialistDaemon = new specialist_1.SpecialistDaemon(specialistConfig);
|
|
262
|
+
await specialistDaemon.initialize();
|
|
263
|
+
this.specialists.set(specialistKey, specialistDaemon);
|
|
264
|
+
this.daemons.set(botClient.userId, specialistDaemon);
|
|
265
|
+
logger.info("Specialist daemon started", {
|
|
266
|
+
key: specialistKey,
|
|
267
|
+
name: specialist.name,
|
|
268
|
+
botId: botClient.userId,
|
|
269
|
+
email: (0, config_1.maskEmail)(botClient.config.email),
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
logger.info("Orchestrator mode started", {
|
|
273
|
+
orchestratorId: orchestratorClient.userId,
|
|
274
|
+
specialistCount: this.specialists.size,
|
|
275
|
+
totalDaemons: this.daemons.size,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Stop all daemons
|
|
280
|
+
* Flushes session logs for each daemon before stopping
|
|
281
|
+
*/
|
|
282
|
+
async stopAll() {
|
|
283
|
+
const stopPromises = Array.from(this.daemons.entries()).map(async ([botId, daemon]) => {
|
|
284
|
+
await daemon.stop();
|
|
285
|
+
logger.info("Daemon stopped", { botId });
|
|
286
|
+
});
|
|
287
|
+
await Promise.all(stopPromises);
|
|
288
|
+
this.daemons.clear();
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Get daemon for a specific bot
|
|
292
|
+
*/
|
|
293
|
+
getDaemon(botId) {
|
|
294
|
+
return this.daemons.get(botId);
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Get status of all daemons
|
|
298
|
+
*/
|
|
299
|
+
getStatus() {
|
|
300
|
+
return Array.from(this.daemons.entries()).map(([botId, daemon]) => ({
|
|
301
|
+
botId,
|
|
302
|
+
state: daemon.getConversationState(),
|
|
303
|
+
}));
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Log current status to console (for monitoring)
|
|
307
|
+
*/
|
|
308
|
+
logStatus() {
|
|
309
|
+
const status = this.getStatus();
|
|
310
|
+
if (this.options.orchestratorMode) {
|
|
311
|
+
logger.info("=== ORCHESTRATOR MODE STATUS ===");
|
|
312
|
+
// Log orchestrator
|
|
313
|
+
if (this.orchestrator) {
|
|
314
|
+
const orchStatus = this.orchestrator.getOrchestratorStatus();
|
|
315
|
+
logger.info("ORCHESTRATOR (HAL):", {
|
|
316
|
+
discussions: orchStatus.conversationState.discussionCount,
|
|
317
|
+
messages: orchStatus.conversationState.currentMessageCount,
|
|
318
|
+
queue: orchStatus.conversationState.queueLength,
|
|
319
|
+
processing: orchStatus.conversationState.isProcessing,
|
|
320
|
+
specialists: orchStatus.specialists.filter(s => s.available).length,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
// Log specialists
|
|
324
|
+
for (const [key, specialist] of this.specialists) {
|
|
325
|
+
const specStatus = specialist.getSpecialistStatus();
|
|
326
|
+
logger.info(`SPECIALIST [${key}] ${specStatus.name}:`, {
|
|
327
|
+
discussions: specStatus.conversationState.discussionCount,
|
|
328
|
+
messages: specStatus.conversationState.currentMessageCount,
|
|
329
|
+
queue: specStatus.conversationState.queueLength,
|
|
330
|
+
processing: specStatus.conversationState.isProcessing,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
logger.info("=== DAEMON STATUS ===");
|
|
336
|
+
for (const { botId, state } of status) {
|
|
337
|
+
logger.info(`Bot ${botId.substring(0, 8)}...`, {
|
|
338
|
+
discussions: state.discussionCount,
|
|
339
|
+
messages: state.currentMessageCount,
|
|
340
|
+
queue: state.queueLength,
|
|
341
|
+
processing: state.isProcessing,
|
|
342
|
+
});
|
|
343
|
+
if (state.lastMessages.length > 0) {
|
|
344
|
+
logger.info("Last messages:");
|
|
345
|
+
for (const msg of state.lastMessages) {
|
|
346
|
+
logger.info(` [${msg.role}] ${msg.preview}`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
logger.info("=====================");
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Get orchestrator instance (only in orchestrator mode)
|
|
355
|
+
*/
|
|
356
|
+
getOrchestrator() {
|
|
357
|
+
return this.orchestrator;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Get specialist by key (only in orchestrator mode)
|
|
361
|
+
*/
|
|
362
|
+
getSpecialist(key) {
|
|
363
|
+
return this.specialists.get(key);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Check if running in orchestrator mode
|
|
367
|
+
*/
|
|
368
|
+
isOrchestratorMode() {
|
|
369
|
+
return !!this.options.orchestratorMode;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Trigger HAL to respond in a discussion with context
|
|
373
|
+
*/
|
|
374
|
+
async triggerHalResponse(discussionId, activityId, context) {
|
|
375
|
+
if (!this.orchestrator) {
|
|
376
|
+
logger.warn('Cannot trigger HAL response - orchestrator not running');
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
await this.orchestrator.respondWithContext(discussionId, activityId, context);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Hot-reload: Start a specialist daemon dynamically
|
|
383
|
+
* Preserves orchestrator conversation context
|
|
384
|
+
*/
|
|
385
|
+
async startSpecialist(email, password, botType, userId) {
|
|
386
|
+
const specialistKey = mapBotTypeToSpecialistKey(botType);
|
|
387
|
+
if (!specialistKey) {
|
|
388
|
+
logger.warn('Unknown botType for specialist', { botType });
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
// Check if already running
|
|
392
|
+
if (this.specialists.has(specialistKey)) {
|
|
393
|
+
logger.info('Specialist already running', { specialistKey });
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
const specialistDef = definitions_1.SPECIALISTS[specialistKey];
|
|
397
|
+
if (!specialistDef) {
|
|
398
|
+
logger.warn('Specialist definition not found', { specialistKey });
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
try {
|
|
402
|
+
// Initialize bot client
|
|
403
|
+
const actualUserId = userId || email;
|
|
404
|
+
const apiKey = (0, hailer_clients_1.registerBotCredentials)(actualUserId, email, password);
|
|
405
|
+
await this.botManager.initializeBotClient(email, password, apiKey);
|
|
406
|
+
const botClient = this.botManager.getBotClientByBotId(specialistKey) ||
|
|
407
|
+
Array.from(this.botManager.getAllBotClients())
|
|
408
|
+
.find(bc => bc.config.email === email);
|
|
409
|
+
if (!botClient) {
|
|
410
|
+
logger.error('Failed to get bot client after initialization', { email: (0, config_1.maskEmail)(email) });
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
// Update botType map
|
|
414
|
+
this.botTypeMap.set(botClient.userId, botType);
|
|
415
|
+
// Clone the specialist to avoid cross-workspace contamination
|
|
416
|
+
const specialist = { ...specialistDef, botUserId: botClient.userId };
|
|
417
|
+
// Create and start specialist daemon
|
|
418
|
+
const specialistConfig = {
|
|
419
|
+
botClient,
|
|
420
|
+
mcpServerUrl: this.options.mcpServerUrl,
|
|
421
|
+
anthropicApiKey: this.options.anthropicApiKey,
|
|
422
|
+
model: specialist.model || this.options.model,
|
|
423
|
+
specialist,
|
|
424
|
+
};
|
|
425
|
+
const specialistDaemon = new specialist_1.SpecialistDaemon(specialistConfig);
|
|
426
|
+
await specialistDaemon.initialize();
|
|
427
|
+
this.specialists.set(specialistKey, specialistDaemon);
|
|
428
|
+
this.daemons.set(botClient.userId, specialistDaemon);
|
|
429
|
+
// Register with orchestrator (with display name from workspace)
|
|
430
|
+
const displayName = this.getDisplayName(botClient.userId);
|
|
431
|
+
this.orchestrator?.registerSpecialistUserId(specialistKey, botClient.userId, displayName);
|
|
432
|
+
logger.info('Specialist hot-loaded successfully', {
|
|
433
|
+
specialistKey,
|
|
434
|
+
name: specialist.name,
|
|
435
|
+
botId: botClient.userId,
|
|
436
|
+
email,
|
|
437
|
+
});
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
catch (error) {
|
|
441
|
+
logger.error('Failed to hot-load specialist', {
|
|
442
|
+
specialistKey,
|
|
443
|
+
email,
|
|
444
|
+
error: error instanceof Error ? error.message : String(error),
|
|
445
|
+
});
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Hot-reload: Stop a specialist daemon dynamically
|
|
451
|
+
* Preserves orchestrator conversation context
|
|
452
|
+
*/
|
|
453
|
+
async stopSpecialist(botType) {
|
|
454
|
+
const specialistKey = mapBotTypeToSpecialistKey(botType);
|
|
455
|
+
if (!specialistKey) {
|
|
456
|
+
logger.warn('Unknown botType for specialist', { botType });
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
const specialistDaemon = this.specialists.get(specialistKey);
|
|
460
|
+
if (!specialistDaemon) {
|
|
461
|
+
logger.info('Specialist not running', { specialistKey });
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
try {
|
|
465
|
+
// Find botId by looking through daemons map
|
|
466
|
+
let botId;
|
|
467
|
+
for (const [id, daemon] of this.daemons) {
|
|
468
|
+
if (daemon === specialistDaemon) {
|
|
469
|
+
botId = id;
|
|
470
|
+
break;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
// Unregister from orchestrator first
|
|
474
|
+
this.orchestrator?.unregisterSpecialist(specialistKey);
|
|
475
|
+
// Stop the daemon
|
|
476
|
+
await specialistDaemon.stop();
|
|
477
|
+
// Remove from maps
|
|
478
|
+
this.specialists.delete(specialistKey);
|
|
479
|
+
if (botId) {
|
|
480
|
+
this.daemons.delete(botId);
|
|
481
|
+
this.botTypeMap.delete(botId);
|
|
482
|
+
// Remove bot client connection
|
|
483
|
+
await this.botManager.removeBotClient(botId);
|
|
484
|
+
}
|
|
485
|
+
logger.info('Specialist stopped successfully', {
|
|
486
|
+
specialistKey,
|
|
487
|
+
botId,
|
|
488
|
+
});
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
491
|
+
catch (error) {
|
|
492
|
+
logger.error('Failed to stop specialist', {
|
|
493
|
+
specialistKey,
|
|
494
|
+
error: error instanceof Error ? error.message : String(error),
|
|
495
|
+
});
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Hot-reload a specialist (stop if running, start if enabled)
|
|
501
|
+
*/
|
|
502
|
+
async hotReloadSpecialist(email, password, botType, enabled, userId) {
|
|
503
|
+
if (enabled) {
|
|
504
|
+
return this.startSpecialist(email, password, botType, userId);
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
return this.stopSpecialist(botType);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Start periodic status logging
|
|
512
|
+
*/
|
|
513
|
+
startStatusLogging(intervalMs = 30000) {
|
|
514
|
+
return setInterval(() => {
|
|
515
|
+
this.logStatus();
|
|
516
|
+
}, intervalMs);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
exports.DaemonManager = DaemonManager;
|
|
520
|
+
/**
|
|
521
|
+
* Create and start the daemon manager
|
|
522
|
+
* This is the main entry point for daemon mode
|
|
523
|
+
*
|
|
524
|
+
* @param options - Optional settings for orchestrator mode
|
|
525
|
+
*/
|
|
526
|
+
async function createDaemonManager(options) {
|
|
527
|
+
const appConfig = (0, config_1.createApplicationConfig)();
|
|
528
|
+
const mcpClientConfig = appConfig.mcpClient;
|
|
529
|
+
if (!mcpClientConfig) {
|
|
530
|
+
logger.error("MCP Client not configured - cannot start daemon mode");
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
// Find Anthropic API key from providers
|
|
534
|
+
const anthropicProvider = mcpClientConfig.providers.find(p => p.type === "anthropic");
|
|
535
|
+
if (!anthropicProvider) {
|
|
536
|
+
logger.error("Anthropic provider not configured - daemon mode requires Anthropic");
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
// Create bot manager
|
|
540
|
+
const botManager = new bot_manager_1.MultiBotManager([]);
|
|
541
|
+
// Load workspace configs from .bot-config/*.json
|
|
542
|
+
const workspaceConfigs = loadWorkspaceConfigs();
|
|
543
|
+
if (workspaceConfigs.length === 0) {
|
|
544
|
+
logger.warn('No workspace configs found in .bot-config/ - run npm run seed-config first');
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
// For now, use first workspace with an orchestrator
|
|
548
|
+
// TODO: Support multiple workspaces
|
|
549
|
+
const workspaceConfig = options?.workspaceId
|
|
550
|
+
? workspaceConfigs.find(c => c.workspaceId === options.workspaceId && c.orchestrator)
|
|
551
|
+
: workspaceConfigs.find(c => c.orchestrator);
|
|
552
|
+
if (!workspaceConfig) {
|
|
553
|
+
logger.error('No workspace with orchestrator found');
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
logger.info('Using workspace config', {
|
|
557
|
+
workspaceId: workspaceConfig.workspaceId,
|
|
558
|
+
workspaceName: workspaceConfig.workspaceName
|
|
559
|
+
});
|
|
560
|
+
// Build botTypeMap and displayNameMap for runtime lookups
|
|
561
|
+
const botTypeMap = new Map();
|
|
562
|
+
const displayNameMap = new Map();
|
|
563
|
+
// Initialize orchestrator
|
|
564
|
+
const orch = workspaceConfig.orchestrator;
|
|
565
|
+
const orchestratorEmail = orch.email;
|
|
566
|
+
try {
|
|
567
|
+
logger.info('Loading orchestrator', { email: (0, config_1.maskEmail)(orch.email), displayName: orch.displayName });
|
|
568
|
+
const orchestratorUserId = orch.userId || 'orchestrator';
|
|
569
|
+
const apiKey = (0, hailer_clients_1.registerBotCredentials)(orchestratorUserId, orch.email, orch.password);
|
|
570
|
+
await botManager.initializeBotClient(orch.email, orch.password, apiKey);
|
|
571
|
+
// Register orchestrator botType and displayName
|
|
572
|
+
botTypeMap.set(orchestratorUserId, 'orchestrator');
|
|
573
|
+
if (orch.displayName) {
|
|
574
|
+
displayNameMap.set(orchestratorUserId, orch.displayName);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
catch (error) {
|
|
578
|
+
logger.error('Failed to initialize orchestrator', { error });
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
// Initialize enabled specialists
|
|
582
|
+
for (const spec of workspaceConfig.specialists) {
|
|
583
|
+
if (!spec.enabled) {
|
|
584
|
+
logger.debug('Skipping disabled specialist', { email: (0, config_1.maskEmail)(spec.email), botType: spec.botType });
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
try {
|
|
588
|
+
logger.info('Loading specialist', { email: (0, config_1.maskEmail)(spec.email), botType: spec.botType, displayName: spec.displayName });
|
|
589
|
+
const specialistUserId = spec.userId || spec.email;
|
|
590
|
+
const apiKey = (0, hailer_clients_1.registerBotCredentials)(specialistUserId, spec.email, spec.password);
|
|
591
|
+
await botManager.initializeBotClient(spec.email, spec.password, apiKey);
|
|
592
|
+
// Register specialist botType and displayName
|
|
593
|
+
if (spec.botType) {
|
|
594
|
+
botTypeMap.set(specialistUserId, spec.botType);
|
|
595
|
+
}
|
|
596
|
+
if (spec.displayName) {
|
|
597
|
+
displayNameMap.set(specialistUserId, spec.displayName);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
catch (error) {
|
|
601
|
+
logger.warn('Failed to initialize specialist', {
|
|
602
|
+
email: spec.email,
|
|
603
|
+
error: error instanceof Error ? error.message : String(error)
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
// Check environment for orchestrator mode (defaults to TRUE)
|
|
608
|
+
const orchestratorMode = options?.orchestratorMode ??
|
|
609
|
+
process.env.DAEMON_ORCHESTRATOR_MODE !== "false";
|
|
610
|
+
// Create daemon manager
|
|
611
|
+
const daemonManager = new DaemonManager(botManager, {
|
|
612
|
+
mcpServerUrl: mcpClientConfig.mcpServerUrl,
|
|
613
|
+
anthropicApiKey: anthropicProvider.apiKey,
|
|
614
|
+
model: anthropicProvider.model,
|
|
615
|
+
orchestratorMode,
|
|
616
|
+
orchestratorEmail: orchestratorEmail || options?.orchestratorEmail,
|
|
617
|
+
specialistEmails: options?.specialistEmails,
|
|
618
|
+
botTypeMap,
|
|
619
|
+
displayNameMap,
|
|
620
|
+
});
|
|
621
|
+
// Start all daemons
|
|
622
|
+
await daemonManager.startAll();
|
|
623
|
+
return daemonManager;
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Quick start function for testing
|
|
627
|
+
*
|
|
628
|
+
* @param orchestratorMode - Enable orchestrator mode (default: true)
|
|
629
|
+
*/
|
|
630
|
+
async function startDaemonMode(orchestratorMode = true) {
|
|
631
|
+
logger.info("Starting Chat Agent Daemon Mode (ORCHESTRATOR)...");
|
|
632
|
+
const manager = await createDaemonManager({ orchestratorMode });
|
|
633
|
+
if (!manager) {
|
|
634
|
+
logger.error("Failed to create daemon manager");
|
|
635
|
+
process.exit(1);
|
|
636
|
+
}
|
|
637
|
+
// Handle shutdown - await async stopAll() to flush session logs
|
|
638
|
+
process.on("SIGINT", () => {
|
|
639
|
+
logger.info("Shutting down daemons...");
|
|
640
|
+
manager.stopAll().then(() => process.exit(0));
|
|
641
|
+
});
|
|
642
|
+
process.on("SIGTERM", () => {
|
|
643
|
+
logger.info("Shutting down daemons...");
|
|
644
|
+
manager.stopAll().then(() => process.exit(0));
|
|
645
|
+
});
|
|
646
|
+
logger.info("Chat Agent Daemon Mode (ORCHESTRATOR) running. Press Ctrl+C to stop.");
|
|
647
|
+
// Start periodic status logging
|
|
648
|
+
manager.startStatusLogging(60000);
|
|
649
|
+
}
|
|
650
|
+
//# sourceMappingURL=factory.js.map
|