@hailer/mcp 0.1.8 → 0.1.9

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 (135) hide show
  1. package/.claude/agents/agent-dmitri-activity-crud.md +3 -1
  2. package/.claude/agents/agent-giuseppe-app-builder.md +11 -12
  3. package/.claude/agents/agent-kenji-data-reader.md +5 -3
  4. package/.claude/skills/hailer-app-builder/SKILL.md +506 -0
  5. package/.claude/skills/publish-hailer-app/SKILL.md +169 -0
  6. package/.claude/skills/tool-parameter-usage/SKILL.md +112 -0
  7. package/CLAUDE.md +6 -2
  8. package/REFACTOR_STATUS.md +127 -0
  9. package/dist/cli.js +0 -0
  10. package/dist/client/agents/base.d.ts +202 -0
  11. package/dist/client/agents/base.js +737 -0
  12. package/dist/client/agents/definitions.d.ts +53 -0
  13. package/dist/client/agents/definitions.js +178 -0
  14. package/dist/client/agents/orchestrator.d.ts +119 -0
  15. package/dist/client/agents/orchestrator.js +760 -0
  16. package/dist/client/agents/specialist.d.ts +86 -0
  17. package/dist/client/agents/specialist.js +340 -0
  18. package/dist/client/bot-manager.d.ts +44 -0
  19. package/dist/client/bot-manager.js +173 -0
  20. package/dist/client/chat-agent-daemon.d.ts +464 -0
  21. package/dist/client/chat-agent-daemon.js +1774 -0
  22. package/dist/client/daemon-factory.d.ts +106 -0
  23. package/dist/client/daemon-factory.js +301 -0
  24. package/dist/client/factory.d.ts +107 -0
  25. package/dist/client/factory.js +304 -0
  26. package/dist/client/index.d.ts +17 -0
  27. package/dist/client/index.js +38 -0
  28. package/dist/client/multi-bot-manager.d.ts +18 -0
  29. package/dist/client/multi-bot-manager.js +88 -1
  30. package/dist/client/orchestrator-daemon.d.ts +87 -0
  31. package/dist/client/orchestrator-daemon.js +444 -0
  32. package/dist/client/services/agent-registry.d.ts +108 -0
  33. package/dist/client/services/agent-registry.js +630 -0
  34. package/dist/client/services/conversation-manager.d.ts +50 -0
  35. package/dist/client/services/conversation-manager.js +136 -0
  36. package/dist/client/services/mcp-client.d.ts +48 -0
  37. package/dist/client/services/mcp-client.js +105 -0
  38. package/dist/client/services/message-classifier.d.ts +37 -0
  39. package/dist/client/services/message-classifier.js +187 -0
  40. package/dist/client/services/message-formatter.d.ts +84 -0
  41. package/dist/client/services/message-formatter.js +353 -0
  42. package/dist/client/services/session-logger.d.ts +106 -0
  43. package/dist/client/services/session-logger.js +446 -0
  44. package/dist/client/services/tool-executor.d.ts +41 -0
  45. package/dist/client/services/tool-executor.js +169 -0
  46. package/dist/client/services/workspace-schema-cache.d.ts +149 -0
  47. package/dist/client/services/workspace-schema-cache.js +732 -0
  48. package/dist/client/specialist-daemon.d.ts +77 -0
  49. package/dist/client/specialist-daemon.js +197 -0
  50. package/dist/client/specialists.d.ts +53 -0
  51. package/dist/client/specialists.js +178 -0
  52. package/dist/client/tool-schema-loader.d.ts +4 -3
  53. package/dist/client/tool-schema-loader.js +54 -8
  54. package/dist/client/types.d.ts +283 -55
  55. package/dist/client/types.js +113 -2
  56. package/dist/config.d.ts +1 -1
  57. package/dist/config.js +1 -1
  58. package/dist/core.d.ts +10 -2
  59. package/dist/core.js +43 -27
  60. package/dist/lib/logger.js +15 -3
  61. package/dist/mcp/UserContextCache.js +2 -2
  62. package/dist/mcp/hailer-clients.js +5 -5
  63. package/dist/mcp/signal-handler.js +27 -5
  64. package/dist/mcp/tools/activity.js +137 -65
  65. package/dist/mcp/tools/app-core.js +4 -140
  66. package/dist/mcp/tools/app-marketplace.js +15 -260
  67. package/dist/mcp/tools/app-member.js +2 -73
  68. package/dist/mcp/tools/app-scaffold.js +146 -87
  69. package/dist/mcp/tools/discussion.js +348 -73
  70. package/dist/mcp/tools/insight.js +74 -190
  71. package/dist/mcp/tools/workflow.js +20 -94
  72. package/dist/mcp/utils/hailer-api-client.d.ts +4 -2
  73. package/dist/mcp/utils/hailer-api-client.js +24 -10
  74. package/dist/mcp-server.d.ts +4 -0
  75. package/dist/mcp-server.js +24 -4
  76. package/dist/routes/agents.d.ts +44 -0
  77. package/dist/routes/agents.js +311 -0
  78. package/dist/services/agent-credential-store.d.ts +73 -0
  79. package/dist/services/agent-credential-store.js +212 -0
  80. package/lineup-manager/dist/assets/index-8ce6041d.css +1 -0
  81. package/lineup-manager/dist/assets/index-e168f265.js +600 -0
  82. package/lineup-manager/dist/index.html +15 -0
  83. package/lineup-manager/dist/manifest.json +17 -0
  84. package/lineup-manager/dist/vite.svg +1 -0
  85. package/package.json +1 -1
  86. package/dist/client/adaptive-documentation-bot.d.ts +0 -106
  87. package/dist/client/adaptive-documentation-bot.js +0 -464
  88. package/dist/client/adaptive-documentation-types.d.ts +0 -66
  89. package/dist/client/adaptive-documentation-types.js +0 -9
  90. package/dist/client/agent-activity-bot.d.ts +0 -51
  91. package/dist/client/agent-activity-bot.js +0 -166
  92. package/dist/client/agent-tracker.d.ts +0 -499
  93. package/dist/client/agent-tracker.js +0 -659
  94. package/dist/client/description-updater.d.ts +0 -56
  95. package/dist/client/description-updater.js +0 -259
  96. package/dist/client/log-parser.d.ts +0 -72
  97. package/dist/client/log-parser.js +0 -387
  98. package/dist/client/mcp-assistant.d.ts +0 -21
  99. package/dist/client/mcp-assistant.js +0 -58
  100. package/dist/client/mcp-client.d.ts +0 -50
  101. package/dist/client/mcp-client.js +0 -538
  102. package/dist/client/message-processor.d.ts +0 -35
  103. package/dist/client/message-processor.js +0 -357
  104. package/dist/client/providers/anthropic-provider.d.ts +0 -19
  105. package/dist/client/providers/anthropic-provider.js +0 -645
  106. package/dist/client/providers/assistant-provider.d.ts +0 -17
  107. package/dist/client/providers/assistant-provider.js +0 -51
  108. package/dist/client/providers/llm-provider.d.ts +0 -47
  109. package/dist/client/providers/llm-provider.js +0 -367
  110. package/dist/client/providers/openai-provider.d.ts +0 -23
  111. package/dist/client/providers/openai-provider.js +0 -630
  112. package/dist/client/simple-llm-caller.d.ts +0 -19
  113. package/dist/client/simple-llm-caller.js +0 -100
  114. package/dist/client/skill-generator.d.ts +0 -81
  115. package/dist/client/skill-generator.js +0 -386
  116. package/dist/client/test-adaptive-bot.d.ts +0 -9
  117. package/dist/client/test-adaptive-bot.js +0 -82
  118. package/dist/client/token-pricing.d.ts +0 -38
  119. package/dist/client/token-pricing.js +0 -127
  120. package/dist/client/token-tracker.d.ts +0 -232
  121. package/dist/client/token-tracker.js +0 -457
  122. package/dist/client/token-usage-bot.d.ts +0 -53
  123. package/dist/client/token-usage-bot.js +0 -153
  124. package/dist/client/tool-executor.d.ts +0 -69
  125. package/dist/client/tool-executor.js +0 -159
  126. package/dist/lib/materialize.d.ts +0 -3
  127. package/dist/lib/materialize.js +0 -101
  128. package/dist/lib/normalizedName.d.ts +0 -7
  129. package/dist/lib/normalizedName.js +0 -48
  130. package/dist/lib/terminal-prompt.d.ts +0 -9
  131. package/dist/lib/terminal-prompt.js +0 -108
  132. package/dist/mcp/tools/skill.d.ts +0 -10
  133. package/dist/mcp/tools/skill.js +0 -279
  134. package/dist/mcp/tools/workflow-template.d.ts +0 -19
  135. package/dist/mcp/tools/workflow-template.js +0 -822
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Specialist Daemon
3
+ *
4
+ * A lightweight daemon for specialist bots that only responds when @mentioned.
5
+ * Specialists are invited to discussions by the orchestrator (HAL) to handle
6
+ * specific complex tasks.
7
+ *
8
+ * Key differences from ChatAgentDaemon:
9
+ * - Only responds when @mentioned (not all messages)
10
+ * - Has specialized system prompt from Specialist config
11
+ * - More tool-focused, less conversational
12
+ */
13
+ import { ChatAgentDaemon, ChatAgentDaemonConfig, IncomingMessage } from "./base";
14
+ import { Specialist } from "./definitions";
15
+ import { HailerSignal } from "../../mcp/signal-handler";
16
+ import { ToolInput } from "../types";
17
+ export interface SpecialistDaemonConfig extends ChatAgentDaemonConfig {
18
+ /** The specialist configuration */
19
+ specialist: Specialist;
20
+ }
21
+ export declare class SpecialistDaemon extends ChatAgentDaemon {
22
+ private specialistLogger;
23
+ private specialist;
24
+ private lastResponseTime;
25
+ private minResponseGap;
26
+ constructor(config: SpecialistDaemonConfig);
27
+ /**
28
+ * Override agent name for Agent Directory - uses specialist's configured name
29
+ */
30
+ protected getAgentName(): {
31
+ firstName: string;
32
+ lastName: string;
33
+ };
34
+ /**
35
+ * Override agent description for Agent Directory - uses specialist's expertise
36
+ */
37
+ protected getAgentDescription(): string;
38
+ /**
39
+ * Override Position details for Specialist - uses specialist's expertise
40
+ */
41
+ protected getPositionDetails(): {
42
+ name: string;
43
+ purpose: string;
44
+ personaTone: string;
45
+ coreCapabilities: string;
46
+ boundaries: string;
47
+ };
48
+ /**
49
+ * Specialist gets more tools for complex operations
50
+ */
51
+ protected getToolWhitelist(): string[];
52
+ /**
53
+ * Force compact output for schema tools (avoid verbose field dumps in discussions)
54
+ */
55
+ protected preprocessToolInput(toolName: string, input: ToolInput): ToolInput;
56
+ /**
57
+ * Override to only process messages where we're @mentioned
58
+ */
59
+ protected extractIncomingMessage(signal: HailerSignal): Promise<IncomingMessage | null>;
60
+ /**
61
+ * Check if this specialist is mentioned in the message
62
+ * Supports various mention formats:
63
+ * - @"Specialist Name"
64
+ * - @SpecialistName (no spaces)
65
+ * - Direct user tag [hailerTag|Name](userId)
66
+ */
67
+ private isMentionedInMessage;
68
+ /**
69
+ * Override system prompt with specialist-specific prompt
70
+ */
71
+ protected getSystemPrompt(): string;
72
+ /**
73
+ * Track response time for rate limiting
74
+ */
75
+ protected postResponse(discussionId: string, content: string): Promise<void>;
76
+ /**
77
+ * Get specialist status
78
+ */
79
+ getSpecialistStatus(): {
80
+ name: string;
81
+ expertise: string[];
82
+ conversationState: ReturnType<ChatAgentDaemon["getConversationState"]>;
83
+ lastResponseTime: number;
84
+ };
85
+ }
86
+ //# sourceMappingURL=specialist.d.ts.map
@@ -0,0 +1,340 @@
1
+ "use strict";
2
+ /**
3
+ * Specialist Daemon
4
+ *
5
+ * A lightweight daemon for specialist bots that only responds when @mentioned.
6
+ * Specialists are invited to discussions by the orchestrator (HAL) to handle
7
+ * specific complex tasks.
8
+ *
9
+ * Key differences from ChatAgentDaemon:
10
+ * - Only responds when @mentioned (not all messages)
11
+ * - Has specialized system prompt from Specialist config
12
+ * - More tool-focused, less conversational
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.SpecialistDaemon = void 0;
16
+ const base_1 = require("./base");
17
+ const logger_1 = require("../../lib/logger");
18
+ class SpecialistDaemon extends base_1.ChatAgentDaemon {
19
+ specialistLogger;
20
+ specialist;
21
+ lastResponseTime = 0;
22
+ minResponseGap = 2000; // 2 seconds minimum between responses
23
+ constructor(config) {
24
+ super(config);
25
+ this.specialist = config.specialist;
26
+ this.specialistLogger = (0, logger_1.createLogger)({
27
+ component: "SpecialistDaemon",
28
+ botId: config.botClient.userId,
29
+ specialistName: config.specialist.name,
30
+ });
31
+ }
32
+ // ===== AGENT DIRECTORY OVERRIDES =====
33
+ /**
34
+ * Override agent name for Agent Directory - uses specialist's configured name
35
+ */
36
+ getAgentName() {
37
+ const nameParts = this.specialist.name.split(" ");
38
+ const firstName = nameParts[0] || "Specialist";
39
+ const lastName = nameParts.slice(1).join(" ") || "Bot";
40
+ return { firstName, lastName };
41
+ }
42
+ /**
43
+ * Override agent description for Agent Directory - uses specialist's expertise
44
+ */
45
+ getAgentDescription() {
46
+ return `${this.specialist.name} - Specialist bot.\n\nExpertise: ${this.specialist.expertise.join(", ")}\n\n${this.specialist.systemPrompt.substring(0, 500)}...`;
47
+ }
48
+ /**
49
+ * Override Position details for Specialist - uses specialist's expertise
50
+ */
51
+ getPositionDetails() {
52
+ return {
53
+ name: `${this.specialist.name} Position`,
54
+ purpose: `Specialist bot for: ${this.specialist.expertise.join(", ")}`,
55
+ personaTone: "Technical, precise, and data-focused. Responds with structured results.",
56
+ coreCapabilities: this.specialist.expertise.map(e => `- ${e}`).join("\n"),
57
+ boundaries: "- Only respond when explicitly @mentioned\n- Never fabricate data - always use tools\n- Stay focused on specialty area\n- Hand back to orchestrator when done",
58
+ };
59
+ }
60
+ /**
61
+ * Specialist gets more tools for complex operations
62
+ */
63
+ getToolWhitelist() {
64
+ return [
65
+ // All orchestrator tools
66
+ "list_workflows",
67
+ "list_workflow_phases",
68
+ "get_workflow_schema",
69
+ "list_workflows_minimal",
70
+ "list_activities",
71
+ "show_activity_by_id",
72
+ "count_activities",
73
+ "create_activity",
74
+ "update_activity",
75
+ "join_discussion",
76
+ "add_discussion_message",
77
+ "invite_discussion_members",
78
+ "fetch_discussion_messages",
79
+ "get_activity_from_discussion",
80
+ "list_my_discussions",
81
+ "search_workspace_users",
82
+ // Insight tools
83
+ "list_insights",
84
+ "get_insight_data",
85
+ "preview_insight",
86
+ "create_insight",
87
+ "update_insight",
88
+ "remove_insight",
89
+ // Workflow config
90
+ "update_workflow_field",
91
+ "update_workflow_phase",
92
+ "install_workflow",
93
+ "remove_workflow",
94
+ // Template tools
95
+ "list_templates",
96
+ "get_template",
97
+ "create_template",
98
+ "install_template",
99
+ // Other
100
+ "upload_files",
101
+ "test_function_field",
102
+ ];
103
+ }
104
+ /**
105
+ * Force compact output for schema tools (avoid verbose field dumps in discussions)
106
+ */
107
+ preprocessToolInput(toolName, input) {
108
+ if (toolName === "get_workflow_schema") {
109
+ return { ...input, compact: true };
110
+ }
111
+ return input;
112
+ }
113
+ /**
114
+ * Override to only process messages where we're @mentioned
115
+ */
116
+ async extractIncomingMessage(signal) {
117
+ // Get base message extraction
118
+ const message = await super.extractIncomingMessage(signal);
119
+ if (!message)
120
+ return null;
121
+ // Specialists only respond when mentioned
122
+ if (!this.isMentionedInMessage(message)) {
123
+ this.specialistLogger.debug("Message doesn't mention specialist, ignoring", {
124
+ discussion: message.discussionId,
125
+ from: message.senderName,
126
+ });
127
+ return null;
128
+ }
129
+ // Rate limit responses
130
+ const now = Date.now();
131
+ if (now - this.lastResponseTime < this.minResponseGap) {
132
+ this.specialistLogger.debug("Rate limiting specialist response", {
133
+ timeSinceLastResponse: now - this.lastResponseTime,
134
+ });
135
+ return null;
136
+ }
137
+ // Mark as high priority since we're explicitly mentioned
138
+ message.priority = "high";
139
+ message.priorityReason = `Specialist ${this.specialist.name} was tagged`;
140
+ this.specialistLogger.info("Specialist mentioned, processing message", {
141
+ discussion: message.discussionId,
142
+ from: message.senderName,
143
+ preview: message.content.substring(0, 50),
144
+ });
145
+ return message;
146
+ }
147
+ /**
148
+ * Check if this specialist is mentioned in the message
149
+ * Supports various mention formats:
150
+ * - @"Specialist Name"
151
+ * - @SpecialistName (no spaces)
152
+ * - Direct user tag [hailerTag|Name](userId)
153
+ */
154
+ isMentionedInMessage(message) {
155
+ const content = message.content;
156
+ const name = this.specialist.name;
157
+ const userId = this.botClient.userId;
158
+ // Check for hailerTag mention (most reliable)
159
+ const hailerTagPattern = new RegExp(`\\[hailerTag\\|[^\\]]*\\]\\(${userId}\\)`, "i");
160
+ if (hailerTagPattern.test(content)) {
161
+ return true;
162
+ }
163
+ // Check for @"Name" mention
164
+ if (content.includes(`@"${name}"`)) {
165
+ return true;
166
+ }
167
+ // Check for @Name (no spaces version)
168
+ const nameNoSpaces = name.replace(/\s+/g, "");
169
+ if (content.includes(`@${nameNoSpaces}`)) {
170
+ return true;
171
+ }
172
+ // Check for @Name (first word)
173
+ const firstName = name.split(" ")[0];
174
+ if (content.includes(`@${firstName}`)) {
175
+ return true;
176
+ }
177
+ // Check message.isMention (set by parent class)
178
+ if (message.isMention) {
179
+ return true;
180
+ }
181
+ return false;
182
+ }
183
+ /**
184
+ * Override system prompt with specialist-specific prompt
185
+ */
186
+ getSystemPrompt() {
187
+ const now = new Date();
188
+ return `${this.specialist.systemPrompt}
189
+
190
+ <current_time>${now.toISOString()}</current_time>
191
+
192
+ <bot_info>
193
+ Bot ID: ${this.botClient.userId}
194
+ Name: ${this.specialist.name}
195
+ </bot_info>
196
+
197
+ <response_rules>
198
+ - You were invited to this discussion to help with a specific task
199
+ - Read HAL's handoff message to understand what's needed
200
+ - Use MCP tools to complete the task - NEVER fabricate data
201
+ - Respond with clear, structured results
202
+ - Include activity links using #activityId format
203
+ - Be concise and technical - data first, explanation second
204
+ </response_rules>
205
+
206
+ <tool_parameters>
207
+ <context_ids>
208
+ Extract IDs from the incoming tag and HAL's handoff:
209
+ - activityId attribute → show_activity_by_id, update_activity
210
+ - discussionId attribute → get_activity_from_discussion
211
+ - workflowId, fieldId → insight tools
212
+ </context_ids>
213
+
214
+ <never_do>
215
+ - Call tools with empty parameters {}
216
+ - Use field LABELS as fieldIds (fieldId must be 24-char hex)
217
+ - Guess IDs - always extract from context
218
+ </never_do>
219
+
220
+ <update_activity_formats>
221
+ - Single mode: use activityId parameter
222
+ - Bulk mode: use _id inside activities array (NOT activityId)
223
+ </update_activity_formats>
224
+
225
+ <field_values>
226
+ - numericunit: 78 (NOT {"type":"numericunit","value":78})
227
+ - activitylink: "abc123..." (NOT {"_id":"abc123","name":"..."})
228
+ </field_values>
229
+ </tool_parameters>
230
+
231
+ <insight_tools>
232
+ <critical>
233
+ - fieldId MUST be 24-character hex ID, NOT the field label!
234
+ - Field names must be SQL-safe: no spaces (use camelCase)
235
+ </critical>
236
+
237
+ <create_insight_format>
238
+ sources: [{
239
+ name: "tableAlias",
240
+ workflowId: "691ffdf84217e9e8434e56ad",
241
+ fields: [
242
+ { name: "playerName", fieldId: "691ffdf84217e9e8434e56b1" },
243
+ { name: "injuryType", fieldId: "691ffdf84217e9e8434e56b2" }
244
+ ]
245
+ }]
246
+ </create_insight_format>
247
+
248
+ <hailer_sql_syntax>
249
+ Hailer insights: fields are mapped in sources.fields, then referenced by ALIAS in query.
250
+
251
+ HOW IT WORKS:
252
+ 1. Define fields in sources: { name: "playerName", fieldId: "691fff..." }
253
+ 2. Use the NAME (alias) in SQL: SELECT playerName FROM tableName
254
+
255
+ SELECT columns - use the field ALIAS names you defined:
256
+ - playerName, injuryType ← custom fields (by alias)
257
+ - name ← built-in activity name
258
+
259
+ Column aliases with spaces need quotes:
260
+ - playerName as "Player Name"
261
+ - injuryType as "Injury Type"
262
+
263
+ LIMITATIONS - DO NOT USE:
264
+ - Phase filtering (no _phase column)
265
+ - Date functions like NOW(), date('now'), CURRENT_DATE
266
+ - Complex WHERE clauses with dates
267
+
268
+ Keep queries SIMPLE: SELECT, FROM, basic WHERE (string comparisons), ORDER BY.
269
+ Users can filter via UI "Show Filters" button.
270
+ </hailer_sql_syntax>
271
+
272
+ <correct_query_example>
273
+ sources: [{
274
+ name: "injuries",
275
+ workflowId: "691ffdf84217e9e8434e56ad",
276
+ fields: [
277
+ { name: "playerName", fieldId: "691ffdf84217e9e8434e5694" },
278
+ { name: "injuryType", fieldId: "691ffdf84217e9e8434e5695" }
279
+ ]
280
+ }]
281
+
282
+ query: "SELECT playerName as \\"Player\\", injuryType as \\"Injury Type\\" FROM injuries ORDER BY playerName ASC"
283
+
284
+ WRONG (will fail):
285
+ - WHERE expectedRecovery > date('now')
286
+ - WHERE _phase = 'phaseId'
287
+ - SELECT FIELD(fieldId) as column
288
+ </correct_query_example>
289
+
290
+ <where_to_get_ids>
291
+ HAL's handoff contains workflowId, fieldIds, and phaseIds. Use them exactly.
292
+ If not provided, call get_workflow_schema AND list_workflow_phases.
293
+ </where_to_get_ids>
294
+ </insight_tools>
295
+
296
+ <response_format>
297
+ When you complete a task, respond with:
298
+ <respond discussion="DISCUSSION_ID">
299
+ [Status emoji] [Brief summary]
300
+
301
+ **What I did:**
302
+ - Step 1: ...
303
+ - Step 2: ...
304
+
305
+ **Results:**
306
+ [Data, links, or confirmation]
307
+
308
+ **Next steps (if applicable):**
309
+ [Suggestions for follow-up]
310
+ </respond>
311
+
312
+ If you need clarification:
313
+ <respond discussion="DISCUSSION_ID">
314
+ I need more information to complete this task:
315
+ - Question 1?
316
+ - Question 2?
317
+ </respond>
318
+ </response_format>`;
319
+ }
320
+ /**
321
+ * Track response time for rate limiting
322
+ */
323
+ async postResponse(discussionId, content) {
324
+ await super.postResponse(discussionId, content);
325
+ this.lastResponseTime = Date.now();
326
+ }
327
+ /**
328
+ * Get specialist status
329
+ */
330
+ getSpecialistStatus() {
331
+ return {
332
+ name: this.specialist.name,
333
+ expertise: this.specialist.expertise,
334
+ conversationState: this.getConversationState(),
335
+ lastResponseTime: this.lastResponseTime,
336
+ };
337
+ }
338
+ }
339
+ exports.SpecialistDaemon = SpecialistDaemon;
340
+ //# sourceMappingURL=specialist.js.map
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Multi-Bot Client Manager
3
+ * Manages multiple bot clients, each with their own Hailer connection
4
+ */
5
+ import { HailerClient } from "../mcp/hailer-clients";
6
+ import { SignalHandler } from "../mcp/signal-handler";
7
+ import { BotClientConfig } from "./types";
8
+ import { WorkspaceCache, UserInfo } from "../mcp/workspace-cache";
9
+ export interface BotClient {
10
+ userId: string;
11
+ firstName: string;
12
+ lastName: string;
13
+ config: BotClientConfig;
14
+ client: HailerClient;
15
+ signalHandler: SignalHandler;
16
+ workspaceCache?: WorkspaceCache;
17
+ }
18
+ export declare class MultiBotManager {
19
+ private botConfigs;
20
+ private botClients;
21
+ constructor(botConfigs: BotClientConfig[]);
22
+ initializeAllHailerClientsFromConfig(): Promise<void>;
23
+ getBotClient(userId: string): BotClient | undefined;
24
+ getAllBotClients(): BotClient[];
25
+ getBotIds(): string[];
26
+ private userNameCache;
27
+ private static USER_CACHE_TTL;
28
+ /**
29
+ * Look up a user's full name by their user ID
30
+ * Uses workspace cache first, then falls back to socket API call
31
+ */
32
+ getUserName(userId: string): string | undefined;
33
+ /**
34
+ * Look up a user's full name by their user ID (async version)
35
+ * Falls back to socket API if not in cache
36
+ */
37
+ getUserNameAsync(userId: string): Promise<string | undefined>;
38
+ /**
39
+ * Get user info by ID (returns full UserInfo object)
40
+ */
41
+ getUserInfo(userId: string): UserInfo | undefined;
42
+ shutdown(): Promise<void>;
43
+ }
44
+ //# sourceMappingURL=bot-manager.d.ts.map
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ /**
3
+ * Multi-Bot Client Manager
4
+ * Manages multiple bot clients, each with their own Hailer connection
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.MultiBotManager = void 0;
8
+ const hailer_clients_1 = require("../mcp/hailer-clients");
9
+ const signal_handler_1 = require("../mcp/signal-handler");
10
+ const workspace_cache_1 = require("../mcp/workspace-cache");
11
+ const config_1 = require("../config");
12
+ class MultiBotManager {
13
+ botConfigs;
14
+ botClients = new Map();
15
+ constructor(botConfigs) {
16
+ this.botConfigs = botConfigs;
17
+ }
18
+ // for client we may put initializeAllConfigClients method to do it at once
19
+ // in case we only use MCP Server:
20
+ // for MCP Server it may just get a specific client and this class initialize it specifically and even kill it later if not needed
21
+ async initializeAllHailerClientsFromConfig() {
22
+ console.log(`🤖 Initializing ${this.botConfigs.length} bot clients...`);
23
+ const appConfig = (0, config_1.createApplicationConfig)();
24
+ for (const config of this.botConfigs) {
25
+ try {
26
+ // Use shared connection pool via API key lookup
27
+ const client = await (0, hailer_clients_1.createHailerClientByApiKey)(config.mcpServerApiKey);
28
+ // Get user ID automatically from authenticated session
29
+ const userId = await (0, hailer_clients_1.getCurrentUserId)(client);
30
+ // Fetch init data to get workspace cache with user names and teams
31
+ let workspaceCache;
32
+ try {
33
+ const init = await client.socket.request('v2.core.init', [['users', 'network', 'networks', 'teams']]);
34
+ workspaceCache = (0, workspace_cache_1.createWorkspaceCache)(init, appConfig.mcpConfig);
35
+ const teamCount = Object.keys(init.teams || {}).length;
36
+ console.log(`📋 Loaded workspace cache with ${workspaceCache.users.length} users, ${teamCount} teams`);
37
+ }
38
+ catch (initError) {
39
+ console.warn(`⚠️ Could not load workspace cache for ${config.email}:`, initError);
40
+ }
41
+ const signalHandler = new signal_handler_1.SignalHandler(client, workspaceCache);
42
+ // Look up user's name from workspace cache
43
+ let firstName = 'Bot';
44
+ let lastName = '';
45
+ if (workspaceCache) {
46
+ const userInfo = workspaceCache.usersById[userId];
47
+ if (userInfo) {
48
+ firstName = userInfo.firstname || 'Bot';
49
+ lastName = userInfo.lastname || '';
50
+ }
51
+ }
52
+ const botClient = {
53
+ userId,
54
+ firstName,
55
+ lastName,
56
+ config,
57
+ client,
58
+ signalHandler,
59
+ workspaceCache,
60
+ };
61
+ this.botClients.set(userId, botClient);
62
+ console.log(`✅ Bot client initialized for ${config.email} (${userId}) via API key ${config.mcpServerApiKey.substring(0, 8)}...`);
63
+ }
64
+ catch (error) {
65
+ console.error(`❌ Failed to initialize bot client for ${config.email}:`, error);
66
+ // Continue with other bots even if one fails
67
+ }
68
+ }
69
+ console.log(`✅ Initialized ${this.botClients.size} bot clients successfully`);
70
+ }
71
+ getBotClient(userId) {
72
+ const botClient = this.botClients.get(userId);
73
+ if (!botClient) {
74
+ console.warn(`❌ No bot client found for userId: ${userId}`);
75
+ }
76
+ return botClient;
77
+ }
78
+ getAllBotClients() {
79
+ return Array.from(this.botClients.values());
80
+ }
81
+ getBotIds() {
82
+ return Array.from(this.botClients.keys());
83
+ }
84
+ // Simple in-memory cache for user lookups (refreshed on demand)
85
+ userNameCache = new Map();
86
+ static USER_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
87
+ /**
88
+ * Look up a user's full name by their user ID
89
+ * Uses workspace cache first, then falls back to socket API call
90
+ */
91
+ getUserName(userId) {
92
+ // Check in-memory cache first (for API-fetched users)
93
+ const cached = this.userNameCache.get(userId);
94
+ if (cached && Date.now() - cached.fetchedAt < MultiBotManager.USER_CACHE_TTL) {
95
+ return cached.name;
96
+ }
97
+ // Try each bot client's workspace cache until we find the user
98
+ for (const botClient of this.botClients.values()) {
99
+ if (botClient.workspaceCache) {
100
+ const user = (0, workspace_cache_1.getUserById)(botClient.workspaceCache, userId);
101
+ if (user) {
102
+ const fullName = `${user.firstname || ''} ${user.lastname || ''}`.trim();
103
+ return fullName || user.fullName || undefined;
104
+ }
105
+ }
106
+ }
107
+ return undefined;
108
+ }
109
+ /**
110
+ * Look up a user's full name by their user ID (async version)
111
+ * Falls back to socket API if not in cache
112
+ */
113
+ async getUserNameAsync(userId) {
114
+ // Try sync lookup first
115
+ const syncResult = this.getUserName(userId);
116
+ if (syncResult) {
117
+ return syncResult;
118
+ }
119
+ // Fall back to socket API call
120
+ const botClient = this.botClients.values().next().value;
121
+ if (!botClient) {
122
+ return undefined;
123
+ }
124
+ try {
125
+ // Fetch fresh init data with users
126
+ const init = await botClient.client.socket.request('v2.core.init', [['users']]);
127
+ if (init.users && init.users[userId]) {
128
+ const user = init.users[userId];
129
+ const fullName = `${user.firstname || ''} ${user.lastname || ''}`.trim();
130
+ // Cache the result
131
+ if (fullName) {
132
+ this.userNameCache.set(userId, { name: fullName, fetchedAt: Date.now() });
133
+ return fullName;
134
+ }
135
+ }
136
+ }
137
+ catch (error) {
138
+ console.warn(`⚠️ Could not fetch user info for ${userId}:`, error);
139
+ }
140
+ return undefined;
141
+ }
142
+ /**
143
+ * Get user info by ID (returns full UserInfo object)
144
+ */
145
+ getUserInfo(userId) {
146
+ for (const botClient of this.botClients.values()) {
147
+ if (botClient.workspaceCache) {
148
+ const user = (0, workspace_cache_1.getUserById)(botClient.workspaceCache, userId);
149
+ if (user) {
150
+ return user;
151
+ }
152
+ }
153
+ }
154
+ return undefined;
155
+ }
156
+ async shutdown() {
157
+ console.log("🤖 Shutting down all bot clients...");
158
+ for (const [userId, botClient] of this.botClients) {
159
+ try {
160
+ // Properly disconnect from shared connection pool
161
+ (0, hailer_clients_1.disconnectHailerClientByApiKey)(botClient.config.mcpServerApiKey);
162
+ console.log(`✅ Bot client ${userId} shut down (API key: ${botClient.config.mcpServerApiKey.substring(0, 8)}...)`);
163
+ }
164
+ catch (error) {
165
+ console.error(`❌ Error shutting down bot client ${userId}:`, error);
166
+ }
167
+ }
168
+ this.botClients.clear();
169
+ console.log("✅ All bot clients shut down");
170
+ }
171
+ }
172
+ exports.MultiBotManager = MultiBotManager;
173
+ //# sourceMappingURL=bot-manager.js.map