@hailer/mcp 0.1.17 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/dist/app.js +24 -20
  2. package/dist/core.d.ts +33 -9
  3. package/dist/core.js +279 -147
  4. package/dist/mcp/UserContextCache.js +18 -0
  5. package/dist/mcp/hailer-clients.d.ts +9 -1
  6. package/dist/mcp/hailer-clients.js +13 -3
  7. package/dist/mcp/signal-handler.js +1 -1
  8. package/dist/mcp/tool-registry.d.ts +3 -1
  9. package/dist/mcp/tool-registry.js +4 -1
  10. package/dist/mcp/tools/activity.js +43 -34
  11. package/dist/mcp/tools/bot-config/constants.d.ts +23 -0
  12. package/dist/mcp/tools/bot-config/constants.js +94 -0
  13. package/dist/mcp/tools/{bot-config.d.ts → bot-config/core.d.ts} +6 -6
  14. package/dist/mcp/tools/{bot-config.js → bot-config/core.js} +15 -15
  15. package/dist/mcp/tools/bot-config/index.d.ts +10 -0
  16. package/dist/mcp/tools/bot-config/index.js +59 -0
  17. package/dist/mcp/tools/bot-config/tools.d.ts +7 -0
  18. package/dist/mcp/tools/bot-config/tools.js +15 -0
  19. package/dist/mcp/tools/bot-config/types.d.ts +50 -0
  20. package/dist/mcp/tools/bot-config/types.js +6 -0
  21. package/dist/mcp/tools/bug-fixer-tools.d.ts +21 -0
  22. package/dist/mcp/tools/{giuseppe-tools.js → bug-fixer-tools.js} +61 -61
  23. package/dist/mcp/tools/user.js +10 -29
  24. package/dist/mcp/tools/workflow.js +36 -2
  25. package/dist/mcp/utils/data-transformers.d.ts +0 -8
  26. package/dist/mcp/utils/data-transformers.js +0 -28
  27. package/dist/mcp/utils/index.d.ts +4 -1
  28. package/dist/mcp/utils/index.js +17 -3
  29. package/dist/mcp/utils/pagination.d.ts +40 -0
  30. package/dist/mcp/utils/pagination.js +55 -0
  31. package/dist/mcp/utils/response-builder.d.ts +53 -0
  32. package/dist/mcp/utils/response-builder.js +110 -0
  33. package/dist/mcp/utils/tool-helpers.d.ts +0 -8
  34. package/dist/mcp/utils/tool-helpers.js +0 -24
  35. package/dist/mcp/utils/types.d.ts +1 -33
  36. package/dist/mcp-server.d.ts +2 -2
  37. package/dist/mcp-server.js +161 -139
  38. package/package.json +1 -1
  39. package/REFACTOR_STATUS.md +0 -127
  40. package/dist/agents/bot-manager.d.ts +0 -48
  41. package/dist/agents/bot-manager.js +0 -254
  42. package/dist/agents/factory.d.ts +0 -150
  43. package/dist/agents/factory.js +0 -650
  44. package/dist/agents/giuseppe/ai.d.ts +0 -83
  45. package/dist/agents/giuseppe/ai.js +0 -466
  46. package/dist/agents/giuseppe/bot.d.ts +0 -110
  47. package/dist/agents/giuseppe/bot.js +0 -780
  48. package/dist/agents/giuseppe/config.d.ts +0 -25
  49. package/dist/agents/giuseppe/config.js +0 -227
  50. package/dist/agents/giuseppe/files.d.ts +0 -52
  51. package/dist/agents/giuseppe/files.js +0 -338
  52. package/dist/agents/giuseppe/git.d.ts +0 -48
  53. package/dist/agents/giuseppe/git.js +0 -298
  54. package/dist/agents/giuseppe/index.d.ts +0 -97
  55. package/dist/agents/giuseppe/index.js +0 -258
  56. package/dist/agents/giuseppe/lsp.d.ts +0 -113
  57. package/dist/agents/giuseppe/lsp.js +0 -485
  58. package/dist/agents/giuseppe/monitor.d.ts +0 -118
  59. package/dist/agents/giuseppe/monitor.js +0 -621
  60. package/dist/agents/giuseppe/prompt.d.ts +0 -5
  61. package/dist/agents/giuseppe/prompt.js +0 -94
  62. package/dist/agents/giuseppe/registries/pending-classification.d.ts +0 -28
  63. package/dist/agents/giuseppe/registries/pending-classification.js +0 -50
  64. package/dist/agents/giuseppe/registries/pending-fix.d.ts +0 -30
  65. package/dist/agents/giuseppe/registries/pending-fix.js +0 -42
  66. package/dist/agents/giuseppe/registries/pending.d.ts +0 -27
  67. package/dist/agents/giuseppe/registries/pending.js +0 -49
  68. package/dist/agents/giuseppe/specialist.d.ts +0 -47
  69. package/dist/agents/giuseppe/specialist.js +0 -237
  70. package/dist/agents/giuseppe/types.d.ts +0 -123
  71. package/dist/agents/giuseppe/types.js +0 -9
  72. package/dist/agents/hailer-expert/index.d.ts +0 -8
  73. package/dist/agents/hailer-expert/index.js +0 -14
  74. package/dist/agents/hal/daemon.d.ts +0 -142
  75. package/dist/agents/hal/daemon.js +0 -1103
  76. package/dist/agents/hal/definitions.d.ts +0 -55
  77. package/dist/agents/hal/definitions.js +0 -263
  78. package/dist/agents/hal/index.d.ts +0 -3
  79. package/dist/agents/hal/index.js +0 -8
  80. package/dist/agents/index.d.ts +0 -18
  81. package/dist/agents/index.js +0 -48
  82. package/dist/agents/shared/base.d.ts +0 -216
  83. package/dist/agents/shared/base.js +0 -846
  84. package/dist/agents/shared/services/agent-registry.d.ts +0 -107
  85. package/dist/agents/shared/services/agent-registry.js +0 -629
  86. package/dist/agents/shared/services/conversation-manager.d.ts +0 -50
  87. package/dist/agents/shared/services/conversation-manager.js +0 -136
  88. package/dist/agents/shared/services/mcp-client.d.ts +0 -56
  89. package/dist/agents/shared/services/mcp-client.js +0 -124
  90. package/dist/agents/shared/services/message-classifier.d.ts +0 -37
  91. package/dist/agents/shared/services/message-classifier.js +0 -187
  92. package/dist/agents/shared/services/message-formatter.d.ts +0 -89
  93. package/dist/agents/shared/services/message-formatter.js +0 -371
  94. package/dist/agents/shared/services/session-logger.d.ts +0 -106
  95. package/dist/agents/shared/services/session-logger.js +0 -446
  96. package/dist/agents/shared/services/tool-executor.d.ts +0 -41
  97. package/dist/agents/shared/services/tool-executor.js +0 -169
  98. package/dist/agents/shared/services/workspace-schema-cache.d.ts +0 -125
  99. package/dist/agents/shared/services/workspace-schema-cache.js +0 -578
  100. package/dist/agents/shared/specialist.d.ts +0 -91
  101. package/dist/agents/shared/specialist.js +0 -399
  102. package/dist/agents/shared/tool-schema-loader.d.ts +0 -62
  103. package/dist/agents/shared/tool-schema-loader.js +0 -232
  104. package/dist/agents/shared/types.d.ts +0 -327
  105. package/dist/agents/shared/types.js +0 -121
  106. package/dist/client/agents/base.d.ts +0 -207
  107. package/dist/client/agents/base.js +0 -744
  108. package/dist/client/agents/definitions.d.ts +0 -53
  109. package/dist/client/agents/definitions.js +0 -263
  110. package/dist/client/agents/orchestrator.d.ts +0 -141
  111. package/dist/client/agents/orchestrator.js +0 -1062
  112. package/dist/client/agents/specialist.d.ts +0 -86
  113. package/dist/client/agents/specialist.js +0 -340
  114. package/dist/client/bot-entrypoint.d.ts +0 -7
  115. package/dist/client/bot-entrypoint.js +0 -103
  116. package/dist/client/bot-manager.d.ts +0 -44
  117. package/dist/client/bot-manager.js +0 -173
  118. package/dist/client/bot-runner.d.ts +0 -35
  119. package/dist/client/bot-runner.js +0 -188
  120. package/dist/client/chat-agent-daemon.d.ts +0 -464
  121. package/dist/client/chat-agent-daemon.js +0 -1774
  122. package/dist/client/daemon-factory.d.ts +0 -106
  123. package/dist/client/daemon-factory.js +0 -301
  124. package/dist/client/factory.d.ts +0 -111
  125. package/dist/client/factory.js +0 -314
  126. package/dist/client/index.d.ts +0 -17
  127. package/dist/client/index.js +0 -38
  128. package/dist/client/multi-bot-manager.d.ts +0 -42
  129. package/dist/client/multi-bot-manager.js +0 -161
  130. package/dist/client/orchestrator-daemon.d.ts +0 -87
  131. package/dist/client/orchestrator-daemon.js +0 -444
  132. package/dist/client/server.d.ts +0 -8
  133. package/dist/client/server.js +0 -251
  134. package/dist/client/services/agent-registry.d.ts +0 -108
  135. package/dist/client/services/agent-registry.js +0 -630
  136. package/dist/client/services/conversation-manager.d.ts +0 -50
  137. package/dist/client/services/conversation-manager.js +0 -136
  138. package/dist/client/services/mcp-client.d.ts +0 -48
  139. package/dist/client/services/mcp-client.js +0 -105
  140. package/dist/client/services/message-classifier.d.ts +0 -37
  141. package/dist/client/services/message-classifier.js +0 -187
  142. package/dist/client/services/message-formatter.d.ts +0 -84
  143. package/dist/client/services/message-formatter.js +0 -353
  144. package/dist/client/services/session-logger.d.ts +0 -106
  145. package/dist/client/services/session-logger.js +0 -446
  146. package/dist/client/services/tool-executor.d.ts +0 -41
  147. package/dist/client/services/tool-executor.js +0 -169
  148. package/dist/client/services/workspace-schema-cache.d.ts +0 -149
  149. package/dist/client/services/workspace-schema-cache.js +0 -732
  150. package/dist/client/specialist-daemon.d.ts +0 -77
  151. package/dist/client/specialist-daemon.js +0 -197
  152. package/dist/client/specialists.d.ts +0 -53
  153. package/dist/client/specialists.js +0 -178
  154. package/dist/client/tool-schema-loader.d.ts +0 -62
  155. package/dist/client/tool-schema-loader.js +0 -232
  156. package/dist/client/types.d.ts +0 -327
  157. package/dist/client/types.js +0 -121
  158. package/dist/commands/seed-config.d.ts +0 -9
  159. package/dist/commands/seed-config.js +0 -372
  160. package/dist/lib/context-manager.d.ts +0 -111
  161. package/dist/lib/context-manager.js +0 -431
  162. package/dist/lib/prompt-length-manager.d.ts +0 -81
  163. package/dist/lib/prompt-length-manager.js +0 -457
  164. package/dist/mcp/tools/giuseppe-tools.d.ts +0 -21
  165. package/dist/modules/bug-reports/bug-config.d.ts +0 -25
  166. package/dist/modules/bug-reports/bug-config.js +0 -187
  167. package/dist/modules/bug-reports/bug-monitor.d.ts +0 -108
  168. package/dist/modules/bug-reports/bug-monitor.js +0 -510
  169. package/dist/modules/bug-reports/giuseppe-agent.d.ts +0 -58
  170. package/dist/modules/bug-reports/giuseppe-agent.js +0 -467
  171. package/dist/modules/bug-reports/giuseppe-ai.d.ts +0 -83
  172. package/dist/modules/bug-reports/giuseppe-ai.js +0 -466
  173. package/dist/modules/bug-reports/giuseppe-bot.d.ts +0 -110
  174. package/dist/modules/bug-reports/giuseppe-bot.js +0 -804
  175. package/dist/modules/bug-reports/giuseppe-daemon.d.ts +0 -80
  176. package/dist/modules/bug-reports/giuseppe-daemon.js +0 -617
  177. package/dist/modules/bug-reports/giuseppe-files.d.ts +0 -64
  178. package/dist/modules/bug-reports/giuseppe-files.js +0 -375
  179. package/dist/modules/bug-reports/giuseppe-git.d.ts +0 -48
  180. package/dist/modules/bug-reports/giuseppe-git.js +0 -298
  181. package/dist/modules/bug-reports/giuseppe-lsp.d.ts +0 -113
  182. package/dist/modules/bug-reports/giuseppe-lsp.js +0 -485
  183. package/dist/modules/bug-reports/giuseppe-prompt.d.ts +0 -5
  184. package/dist/modules/bug-reports/giuseppe-prompt.js +0 -94
  185. package/dist/modules/bug-reports/index.d.ts +0 -77
  186. package/dist/modules/bug-reports/index.js +0 -215
  187. package/dist/modules/bug-reports/pending-classification-registry.d.ts +0 -28
  188. package/dist/modules/bug-reports/pending-classification-registry.js +0 -50
  189. package/dist/modules/bug-reports/pending-fix-registry.d.ts +0 -30
  190. package/dist/modules/bug-reports/pending-fix-registry.js +0 -42
  191. package/dist/modules/bug-reports/pending-registry.d.ts +0 -27
  192. package/dist/modules/bug-reports/pending-registry.js +0 -49
  193. package/dist/modules/bug-reports/types.d.ts +0 -123
  194. package/dist/modules/bug-reports/types.js +0 -9
  195. package/dist/routes/agents.d.ts +0 -44
  196. package/dist/routes/agents.js +0 -311
  197. package/dist/services/agent-credential-store.d.ts +0 -73
  198. package/dist/services/agent-credential-store.js +0 -212
  199. package/dist/services/bug-monitor.d.ts +0 -23
  200. package/dist/services/bug-monitor.js +0 -275
package/dist/core.js CHANGED
@@ -8,23 +8,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.Core = void 0;
9
9
  const logger_1 = require("./lib/logger");
10
10
  const config_1 = require("./config");
11
- const factory_1 = require("./agents/factory");
12
11
  const mcp_server_1 = require("./mcp-server");
13
12
  const tool_registry_1 = require("./mcp/tool-registry");
14
- const giuseppe_1 = require("./agents/giuseppe");
15
13
  const UserContextCache_1 = require("./mcp/UserContextCache");
16
- const bot_config_1 = require("./mcp/tools/bot-config");
17
14
  const webhook_handler_1 = require("./mcp/webhook-handler");
18
15
  class Core {
19
16
  logger;
20
17
  appConfig;
21
18
  toolRegistry;
22
19
  mcpServer;
23
- daemonManager = null;
24
- statusLogInterval;
25
- bugReportsModule;
26
- daemonInitInProgress = false;
27
- initialDaemonReady = false; // Prevents restart attempts before first daemon init
20
+ daemonManagers = new Map(); // workspace ID -> DaemonManager
21
+ statusLogIntervals = new Map(); // workspace ID -> interval
22
+ bugReportsModules = new Map(); // workspace ID -> BugReportsModule
23
+ daemonInitInProgress = new Set(); // workspace IDs currently initializing
24
+ initialDaemonReady = new Set(); // workspace IDs that have completed first init
28
25
  constructor() {
29
26
  // Initialize logger first
30
27
  this.logger = (0, logger_1.createLogger)({
@@ -65,14 +62,15 @@ class Core {
65
62
  await this.startMCPServer();
66
63
  }
67
64
  }
68
- // Initialize bot config persistence (reads config from Hailer Agent Directory)
69
- await this.initBotConfig();
70
- // Start client/daemon AFTER MCP server is ready
65
+ // Start client/daemon services ONLY if MCP_CLIENT_ENABLED=true
71
66
  if (this.appConfig.server.enableClient) {
67
+ // Initialize bot config persistence (reads config from Hailer Agent Directory)
68
+ await this.initBotConfig();
69
+ // Start daemons for all workspaces with orchestrator
72
70
  await this.startMCPClient();
71
+ // Start Bug Monitor service (reads config from Hailer)
72
+ await this.startBugMonitor();
73
73
  }
74
- // Start Bug Monitor service (reads config from Hailer)
75
- await this.startBugMonitor();
76
74
  this.setupGracefulShutdown();
77
75
  this.logger.info('All configured services started successfully');
78
76
  }
@@ -93,61 +91,117 @@ class Core {
93
91
  await this.mcpServer.start();
94
92
  this.logger.info('MCP Server service started');
95
93
  }
94
+ /**
95
+ * Start MCP Client (daemons) for all workspaces with enabled orchestrators
96
+ */
96
97
  async startMCPClient() {
97
- if (this.daemonInitInProgress) {
98
- this.logger.debug('Daemon init already in progress, skipping startMCPClient');
98
+ this.logger.info('Starting Chat Agent Daemons for all enabled workspaces');
99
+ // Load all workspace configs
100
+ const { loadBotConfigs } = require('./bot-config');
101
+ const botContexts = loadBotConfigs();
102
+ // Find all workspaces with enabled orchestrators
103
+ const workspacesWithOrchestrator = botContexts.filter((ctx) => ctx.getOrchestratorCredentials());
104
+ if (workspacesWithOrchestrator.length === 0) {
105
+ this.logger.info('No workspaces with orchestrator found - skipping daemon start');
99
106
  return;
100
107
  }
101
- this.logger.info('Starting Chat Agent Daemon (persistent conversation mode)');
102
- this.daemonInitInProgress = true;
103
- try {
104
- await this.initializeDaemonMode();
105
- this.initialDaemonReady = true; // Mark daemon as ready for restart signals
106
- this.logger.info('Chat Agent Daemon started');
107
- }
108
- catch (error) {
109
- this.logger.warn('Daemon mode failed to start - server will continue without client capabilities', {
110
- error: error instanceof Error ? error.message : String(error)
111
- });
112
- }
113
- finally {
114
- this.daemonInitInProgress = false;
115
- }
108
+ this.logger.info('Found workspaces with orchestrator', {
109
+ count: workspacesWithOrchestrator.length,
110
+ workspaces: workspacesWithOrchestrator.map((ctx) => ({
111
+ id: ctx.workspaceId,
112
+ name: ctx.workspaceName
113
+ }))
114
+ });
115
+ // Start daemon for each workspace
116
+ const startPromises = workspacesWithOrchestrator.map(async (ctx) => {
117
+ const workspaceId = ctx.workspaceId;
118
+ if (this.daemonInitInProgress.has(workspaceId)) {
119
+ this.logger.debug('Daemon init already in progress, skipping', { workspaceId });
120
+ return;
121
+ }
122
+ this.daemonInitInProgress.add(workspaceId);
123
+ try {
124
+ await this.initializeDaemonMode(workspaceId);
125
+ this.initialDaemonReady.add(workspaceId);
126
+ this.logger.info('Chat Agent Daemon started', { workspaceId });
127
+ }
128
+ catch (error) {
129
+ this.logger.warn('Daemon mode failed to start for workspace - continuing with other workspaces', {
130
+ workspaceId,
131
+ error: error instanceof Error ? error.message : String(error)
132
+ });
133
+ }
134
+ finally {
135
+ this.daemonInitInProgress.delete(workspaceId);
136
+ }
137
+ });
138
+ // Wait for all daemons to start (or fail)
139
+ await Promise.allSettled(startPromises);
140
+ this.logger.info('Chat Agent Daemons initialization complete', {
141
+ successCount: this.daemonManagers.size,
142
+ totalAttempted: workspacesWithOrchestrator.length
143
+ });
116
144
  }
145
+ /**
146
+ * Start Bug Monitor for all workspaces with running daemons
147
+ * Each workspace gets its own bug monitor instance
148
+ */
117
149
  async startBugMonitor() {
118
150
  try {
119
- // Get first configured account for Bug Reports Module
151
+ // Get all configured accounts
120
152
  const accounts = Object.entries(this.appConfig.hailerAccounts);
121
153
  if (accounts.length === 0) {
122
154
  this.logger.info('No Hailer accounts configured - Bug Reports Module disabled');
123
155
  return;
124
156
  }
125
- const [apiKey] = accounts[0];
126
- const userContext = await UserContextCache_1.UserContextCache.getContext(apiKey);
127
- this.bugReportsModule = new giuseppe_1.BugReportsModule(userContext);
128
- // Register bot user IDs to ignore (so bug monitor only processes human messages)
129
- if (this.daemonManager) {
130
- const daemonStatus = this.daemonManager.getStatus();
131
- for (const daemon of daemonStatus) {
132
- this.bugReportsModule.registerBotUser(daemon.botId);
133
- this.logger.debug('Registered bot user for bug monitor to ignore', { botId: daemon.botId });
157
+ // Start bug monitor for each workspace that has a running daemon
158
+ for (const [workspaceId, daemonManager] of this.daemonManagers) {
159
+ try {
160
+ // Find the appropriate API key for this workspace
161
+ // For now, use the first account (could be enhanced to match workspace-specific accounts)
162
+ const [apiKey] = accounts[0];
163
+ const userContext = await UserContextCache_1.UserContextCache.getContext(apiKey);
164
+ // Dynamic import - agents only needed when client is enabled
165
+ const { BugReportsModule } = require('./agents/bug-fixer');
166
+ const bugReportsModule = new BugReportsModule(userContext);
167
+ // Register bot user IDs to ignore (so bug monitor only processes human messages)
168
+ const daemonStatus = daemonManager.getStatus();
169
+ for (const daemon of daemonStatus) {
170
+ bugReportsModule.registerBotUser(daemon.botId);
171
+ this.logger.debug('Registered bot user for bug monitor to ignore', {
172
+ workspaceId,
173
+ botId: daemon.botId
174
+ });
175
+ }
176
+ // Register bug fixer disabled handler to trigger HAL response (BEFORE start!)
177
+ bugReportsModule.onBugFixerDisabled(async (bug) => {
178
+ if (bug.discussionId) {
179
+ const context = `[System notification: A new bug report "${bug.name}" was detected, but Bug Fixer (the auto-fix bot) is currently disabled. The user may ask how to enable Bug Fixer or want to know more about this bug. Be helpful and explain they can enable Bug Fixer in the AI Hub app.]`;
180
+ await daemonManager.triggerHalResponse(bug.discussionId, bug.id, context);
181
+ }
182
+ });
183
+ await bugReportsModule.start();
184
+ if (bugReportsModule.isRunning()) {
185
+ this.bugReportsModules.set(workspaceId, bugReportsModule);
186
+ const config = bugReportsModule.getConfig();
187
+ this.logger.info('Bug Reports Module started', {
188
+ workspaceId,
189
+ autoFix: config.autoFix,
190
+ interval: `${config.intervalMs / 1000}s`
191
+ });
192
+ }
134
193
  }
135
- }
136
- // Register giuseppe disabled handler to trigger HAL response (BEFORE start!)
137
- this.bugReportsModule.onGiuseppeDisabled(async (bug) => {
138
- if (this.daemonManager && bug.discussionId) {
139
- const context = `[System notification: A new bug report "${bug.name}" was detected, but Giuseppe (the auto-fix bot) is currently disabled. The user may ask how to enable Giuseppe or want to know more about this bug. Be helpful and explain they can enable Giuseppe in the AI Hub app.]`;
140
- await this.daemonManager.triggerHalResponse(bug.discussionId, bug.id, context);
194
+ catch (error) {
195
+ this.logger.warn('Bug Reports Module failed to start for workspace - continuing with other workspaces', {
196
+ workspaceId,
197
+ error: error instanceof Error ? error.message : String(error)
198
+ });
141
199
  }
142
- });
143
- await this.bugReportsModule.start();
144
- if (this.bugReportsModule.isRunning()) {
145
- const config = this.bugReportsModule.getConfig();
146
- this.logger.info('Bug Reports Module started', {
147
- autoFix: config.autoFix,
148
- interval: `${config.intervalMs / 1000}s`
149
- });
150
200
  }
201
+ this.logger.info('Bug Reports Module initialization complete', {
202
+ successCount: this.bugReportsModules.size,
203
+ totalAttempted: this.daemonManagers.size
204
+ });
151
205
  }
152
206
  catch (error) {
153
207
  this.logger.warn('Bug Reports Module failed to start - server will continue without bug monitoring', {
@@ -165,11 +219,28 @@ class Core {
165
219
  }
166
220
  const [apiKey] = accounts[0];
167
221
  const userContext = await UserContextCache_1.UserContextCache.getContext(apiKey);
168
- await (0, bot_config_1.initBotConfigPersistence)(userContext.hailer);
222
+ // Dynamic import - bot-config only needed when client is enabled
223
+ const { initBotConfigPersistence, onDaemonRestartNeeded } = require('./bot-config');
224
+ await initBotConfigPersistence(userContext.hailer);
169
225
  this.logger.info('Bot config persistence initialized');
226
+ // Register webhook handler early so it can trigger daemon start
227
+ this.registerWebhookHandler();
170
228
  // Register callback for daemon restart when new orchestrator is discovered
171
- (0, bot_config_1.onDaemonRestartNeeded)(() => {
172
- this.attemptDaemonRestart();
229
+ // This is called when orchestrator config changes in any workspace
230
+ onDaemonRestartNeeded(() => {
231
+ // Restart all workspaces with orchestrator enabled
232
+ const { loadBotConfigs } = require('./bot-config');
233
+ const configs = loadBotConfigs();
234
+ for (const ctx of configs) {
235
+ if (ctx.getOrchestratorCredentials()) {
236
+ this.attemptDaemonRestart(0, ctx.workspaceId).catch(err => {
237
+ this.logger.error('Failed to restart daemon after config change', {
238
+ workspaceId: ctx.workspaceId,
239
+ error: err
240
+ });
241
+ });
242
+ }
243
+ }
173
244
  });
174
245
  }
175
246
  catch (error) {
@@ -178,49 +249,125 @@ class Core {
178
249
  });
179
250
  }
180
251
  }
252
+ /**
253
+ * Register webhook handler for bot config updates
254
+ * Called early so webhooks can trigger daemon start even if initial start failed
255
+ */
256
+ registerWebhookHandler() {
257
+ (0, webhook_handler_1.onBotUpdate)((workspaceId, bot, action) => {
258
+ this.logger.info('Webhook bot update received', { workspaceId, botType: bot.botType, action, enabled: bot.enabled });
259
+ if (bot.botType === 'orchestrator') {
260
+ if (action === 'remove') {
261
+ // Orchestrator disabled - stop daemon for this workspace only
262
+ this.logger.info('Orchestrator disabled via webhook, stopping daemon', { workspaceId });
263
+ const manager = this.daemonManagers.get(workspaceId);
264
+ if (manager) {
265
+ manager.stopAll().catch((err) => {
266
+ this.logger.error('Failed to stop daemons after orchestrator removal', { workspaceId, err });
267
+ }).then(() => {
268
+ // Clean up after stopping
269
+ this.daemonManagers.delete(workspaceId);
270
+ const interval = this.statusLogIntervals.get(workspaceId);
271
+ if (interval) {
272
+ clearInterval(interval);
273
+ this.statusLogIntervals.delete(workspaceId);
274
+ }
275
+ this.initialDaemonReady.delete(workspaceId);
276
+ });
277
+ }
278
+ }
279
+ else {
280
+ // Orchestrator added/updated - start or restart daemon for this workspace
281
+ this.logger.info('Orchestrator config changed via webhook, triggering daemon start/restart', { workspaceId });
282
+ this.attemptDaemonRestart(0, workspaceId).catch(err => {
283
+ this.logger.error('Failed to restart daemon after orchestrator webhook update', { workspaceId, err });
284
+ });
285
+ }
286
+ }
287
+ else {
288
+ // Specialist changed - hot-reload without losing orchestrator context
289
+ this.logger.info('Specialist config changed via webhook, hot-reloading', {
290
+ workspaceId,
291
+ botType: bot.botType,
292
+ enabled: bot.enabled
293
+ });
294
+ const manager = this.daemonManagers.get(workspaceId);
295
+ if (manager && bot.botType) {
296
+ manager.hotReloadSpecialist(bot.email, bot.password, bot.botType, bot.enabled, bot.userId || undefined).then((success) => {
297
+ if (success) {
298
+ this.logger.info('Specialist hot-reloaded successfully', {
299
+ workspaceId,
300
+ botType: bot.botType,
301
+ enabled: bot.enabled
302
+ });
303
+ }
304
+ }).catch((err) => {
305
+ this.logger.error('Failed to hot-reload specialist', {
306
+ workspaceId,
307
+ botType: bot.botType,
308
+ error: err
309
+ });
310
+ });
311
+ }
312
+ }
313
+ });
314
+ this.logger.debug('Webhook handler registered');
315
+ }
181
316
  /**
182
317
  * Attempt to restart daemon mode when bot state changes
183
- * Stops existing daemons and starts fresh with updated configuration
318
+ * Stops existing daemon for a specific workspace and starts fresh with updated configuration
184
319
  * Includes retry logic for transient connection failures
320
+ *
321
+ * @param retryCount - Number of retry attempts made so far
322
+ * @param workspaceId - REQUIRED workspace ID to restart daemon for
185
323
  */
186
324
  async attemptDaemonRestart(retryCount = 0, workspaceId) {
187
325
  const MAX_RETRIES = 3;
188
326
  const RETRY_DELAY_MS = 5000;
189
- // Skip restart if initial daemon hasn't been created yet
190
- // (signals can fire before first daemon init completes)
191
- if (!this.initialDaemonReady) {
192
- this.logger.debug('Skipping daemon restart - initial daemon not yet ready');
193
- return;
327
+ // If initial daemon for this workspace never started, check if we now have config to start it
328
+ if (!this.initialDaemonReady.has(workspaceId)) {
329
+ // Check if config now exists for this workspace
330
+ const { loadBotConfigs } = require('./bot-config');
331
+ const configs = loadBotConfigs();
332
+ const workspaceConfig = configs.find((ctx) => ctx.workspaceId === workspaceId && ctx.getOrchestratorCredentials());
333
+ if (!workspaceConfig) {
334
+ this.logger.debug('Skipping daemon start - no orchestrator config yet', { workspaceId });
335
+ return;
336
+ }
337
+ this.logger.info('Config now available, attempting first daemon start', { workspaceId });
338
+ // Continue to start daemon for first time
194
339
  }
195
- if (this.daemonInitInProgress) {
196
- this.logger.debug('Daemon init already in progress, skipping');
340
+ if (this.daemonInitInProgress.has(workspaceId)) {
341
+ this.logger.debug('Daemon init already in progress, skipping', { workspaceId });
197
342
  return;
198
343
  }
199
- this.logger.info('Restarting daemons after bot state change', { retryCount });
200
- this.daemonInitInProgress = true;
344
+ this.logger.info('Restarting daemon after bot state change', { workspaceId, retryCount });
345
+ this.daemonInitInProgress.add(workspaceId);
201
346
  try {
202
- // Stop existing daemons if running
203
- if (this.daemonManager) {
204
- this.logger.info('Stopping existing daemons for restart');
205
- if (this.statusLogInterval) {
206
- clearInterval(this.statusLogInterval);
207
- this.statusLogInterval = undefined;
347
+ // Stop existing daemon for this workspace if running
348
+ const existingManager = this.daemonManagers.get(workspaceId);
349
+ if (existingManager) {
350
+ this.logger.info('Stopping existing daemon for restart', { workspaceId });
351
+ const interval = this.statusLogIntervals.get(workspaceId);
352
+ if (interval) {
353
+ clearInterval(interval);
354
+ this.statusLogIntervals.delete(workspaceId);
208
355
  }
209
- await this.daemonManager.stopAll();
210
- this.daemonManager = null;
356
+ await existingManager.stopAll();
357
+ this.daemonManagers.delete(workspaceId);
211
358
  }
212
- // Start fresh with updated bot state
359
+ // Start fresh with updated bot state for this workspace
213
360
  await this.initializeDaemonMode(workspaceId);
214
- // Verify orchestrator was created successfully (daemonManager is set by initializeDaemonMode)
215
- // Use explicit re-read to help TypeScript understand the state change
216
- const newManager = this.daemonManager;
361
+ // Verify daemon was created successfully
362
+ const newManager = this.daemonManagers.get(workspaceId);
217
363
  if (!newManager) {
218
364
  throw new Error('Daemon manager was not created');
219
365
  }
220
366
  if (!newManager.getOrchestrator()) {
221
367
  throw new Error('Daemon manager created but no orchestrator available');
222
368
  }
223
- this.logger.info('Daemons successfully restarted after state change');
369
+ this.logger.info('Daemon successfully restarted after state change', { workspaceId });
370
+ this.initialDaemonReady.add(workspaceId);
224
371
  }
225
372
  catch (error) {
226
373
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -229,110 +376,95 @@ class Core {
229
376
  errorMessage.includes('No orchestrator');
230
377
  if (isTransientError && retryCount < MAX_RETRIES) {
231
378
  this.logger.warn('Daemon restart failed with transient error, scheduling retry', {
379
+ workspaceId,
232
380
  error: errorMessage,
233
381
  retryCount: retryCount + 1,
234
382
  retryDelayMs: RETRY_DELAY_MS
235
383
  });
236
384
  // Reset flag before scheduling retry
237
- this.daemonInitInProgress = false;
385
+ this.daemonInitInProgress.delete(workspaceId);
238
386
  setTimeout(() => {
239
387
  this.attemptDaemonRestart(retryCount + 1, workspaceId);
240
388
  }, RETRY_DELAY_MS);
241
389
  return;
242
390
  }
243
391
  this.logger.warn('Daemon restart failed', {
392
+ workspaceId,
244
393
  error: errorMessage,
245
394
  retriesExhausted: retryCount >= MAX_RETRIES
246
395
  });
247
396
  }
248
397
  finally {
249
- this.daemonInitInProgress = false;
398
+ this.daemonInitInProgress.delete(workspaceId);
250
399
  }
251
400
  }
401
+ /**
402
+ * Initialize daemon for a specific workspace
403
+ *
404
+ * @param workspaceId - REQUIRED workspace ID to initialize daemon for
405
+ */
252
406
  async initializeDaemonMode(workspaceId) {
253
407
  this.logger.info('Initializing Chat Agent Daemon', { workspaceId });
254
408
  // Check for orchestrator mode via environment variable
255
- const orchestratorMode = process.env.DAEMON_ORCHESTRATOR_MODE === 'true';
256
- this.logger.info(`Daemon mode: ${orchestratorMode ? 'ORCHESTRATOR' : 'STANDARD'}`);
257
- this.daemonManager = await (0, factory_1.createDaemonManager)({ orchestratorMode, workspaceId });
258
- if (!this.daemonManager) {
259
- throw new Error('Failed to create daemon manager');
409
+ const orchestratorMode = process.env.DAEMON_ORCHESTRATOR_MODE !== 'false';
410
+ this.logger.info(`Daemon mode: ${orchestratorMode ? 'ORCHESTRATOR' : 'STANDARD'}`, { workspaceId });
411
+ // Dynamic import - agents only needed when client is enabled
412
+ const { createDaemonManager } = require('./agents/factory');
413
+ const daemonManager = await createDaemonManager({ orchestratorMode, workspaceId });
414
+ if (!daemonManager) {
415
+ throw new Error(`Failed to create daemon manager for workspace ${workspaceId}`);
260
416
  }
261
- const status = this.daemonManager.getStatus();
417
+ // Store the manager for this workspace
418
+ this.daemonManagers.set(workspaceId, daemonManager);
419
+ const status = daemonManager.getStatus();
262
420
  this.logger.info('Chat Agent Daemon initialized and ready', {
421
+ workspaceId,
263
422
  daemonCount: status.length,
264
- bots: status.map(s => s.botId),
423
+ bots: status.map((s) => s.botId),
265
424
  orchestratorMode
266
425
  });
267
- // Wire webhook updates to daemon hot-reload
268
- (0, webhook_handler_1.onBotUpdate)((workspaceId, bot, action) => {
269
- this.logger.info('Webhook bot update received', { workspaceId, botType: bot.botType, action, enabled: bot.enabled });
270
- if (bot.botType === 'orchestrator') {
271
- if (action === 'remove') {
272
- // Orchestrator disabled - stop daemons, don't restart
273
- this.logger.info('Orchestrator disabled via webhook, stopping daemons');
274
- this.daemonManager?.stopAll().catch(err => {
275
- this.logger.error('Failed to stop daemons after orchestrator removal', err);
276
- });
277
- }
278
- else {
279
- // Orchestrator added/updated - restart daemons
280
- this.logger.info('Orchestrator config changed via webhook, triggering daemon restart');
281
- this.attemptDaemonRestart(0, workspaceId).catch(err => {
282
- this.logger.error('Failed to restart daemon after orchestrator webhook update', err);
283
- });
284
- }
285
- }
286
- else {
287
- // Specialist changed - hot-reload without losing orchestrator context
288
- this.logger.info('Specialist config changed via webhook, hot-reloading', {
289
- botType: bot.botType,
290
- enabled: bot.enabled
291
- });
292
- if (this.daemonManager && bot.botType) {
293
- this.daemonManager.hotReloadSpecialist(bot.email, bot.password, bot.botType, bot.enabled, bot.userId || undefined).then(success => {
294
- if (success) {
295
- this.logger.info('Specialist hot-reloaded successfully', {
296
- botType: bot.botType,
297
- enabled: bot.enabled
298
- });
299
- }
300
- else {
301
- this.logger.warn('Specialist hot-reload failed', { botType: bot.botType });
302
- }
303
- }).catch(err => {
304
- this.logger.error('Error during specialist hot-reload', err);
305
- });
306
- }
307
- }
308
- });
309
- // Start periodic status logging (every 60s)
310
- this.statusLogInterval = this.daemonManager.startStatusLogging(60000);
311
- this.logger.info('Daemon status logging started (every 60s)');
426
+ // Start periodic status logging (every 60s) for this workspace
427
+ const interval = daemonManager.startStatusLogging(60000);
428
+ this.statusLogIntervals.set(workspaceId, interval);
429
+ this.logger.info('Daemon status logging started (every 60s)', { workspaceId });
312
430
  }
313
431
  /**
314
- * Get daemon status (for HTTP endpoint)
432
+ * Get daemon status for all workspaces (for HTTP endpoint)
315
433
  */
316
434
  getDaemonStatus() {
317
- return this.daemonManager?.getStatus() || null;
435
+ const allStatus = {};
436
+ for (const [workspaceId, manager] of this.daemonManagers) {
437
+ allStatus[workspaceId] = manager.getStatus();
438
+ }
439
+ return allStatus;
318
440
  }
319
441
  async stop() {
320
442
  this.logger.info('Stopping Hailer MCP application');
321
443
  try {
322
- // Stop status logging
323
- if (this.statusLogInterval) {
324
- clearInterval(this.statusLogInterval);
444
+ // Stop all status logging intervals
445
+ for (const [workspaceId, interval] of this.statusLogIntervals) {
446
+ clearInterval(interval);
447
+ this.logger.debug('Stopped status logging', { workspaceId });
325
448
  }
326
- // Stop Bug Reports Module
327
- if (this.bugReportsModule) {
328
- await this.bugReportsModule.stop();
449
+ this.statusLogIntervals.clear();
450
+ // Stop all Bug Reports Modules
451
+ for (const [workspaceId, module] of this.bugReportsModules) {
452
+ await module.stop();
453
+ this.logger.debug('Stopped Bug Reports Module', { workspaceId });
329
454
  }
330
- // Cleanup bot config (clear timers and callbacks)
331
- (0, bot_config_1.cleanupBotConfig)();
332
- if (this.daemonManager) {
333
- this.daemonManager.stopAll();
334
- this.logger.info('Chat Agent Daemon stopped');
455
+ this.bugReportsModules.clear();
456
+ // Cleanup bot config (clear timers and callbacks) - only if client was enabled
457
+ if (this.appConfig.server.enableClient) {
458
+ const { cleanupBotConfig } = require('./bot-config');
459
+ cleanupBotConfig();
335
460
  }
461
+ // Stop all daemon managers
462
+ const stopPromises = Array.from(this.daemonManagers.entries()).map(async ([workspaceId, manager]) => {
463
+ await manager.stopAll();
464
+ this.logger.info('Chat Agent Daemon stopped', { workspaceId });
465
+ });
466
+ await Promise.all(stopPromises);
467
+ this.daemonManagers.clear();
336
468
  if (this.mcpServer) {
337
469
  await this.mcpServer.stop();
338
470
  this.logger.info('MCP Server stopped');
@@ -105,6 +105,24 @@ class UserContextCache {
105
105
  const init = await client.socket.request('v2.core.init', [
106
106
  ['processes', 'users', 'network', 'networks', 'teams']
107
107
  ]);
108
+ // Validate single workspace access - MCP requires bot credentials with access to one workspace only
109
+ const workspaceCount = Object.keys(init.networks || {}).length;
110
+ if (workspaceCount > 1) {
111
+ const networks = (init.networks || {});
112
+ const workspaceNames = Object.values(networks)
113
+ .map((ws) => ws.name)
114
+ .join(', ');
115
+ logger.error('Multi-workspace credentials detected', {
116
+ workspaceCount,
117
+ workspaces: workspaceNames,
118
+ apiKey: apiKey.substring(0, 8) + '...'
119
+ });
120
+ // Clean up the connection before throwing - prevents dangling socket
121
+ (0, hailer_clients_1.disconnectHailerClientByApiKey)(apiKey);
122
+ throw new Error(`Multi-workspace credentials detected (${workspaceCount} workspaces: ${workspaceNames}). ` +
123
+ `MCP requires bot credentials with access to a single workspace. ` +
124
+ `Please use the bot account created during 'hailer-sdk init'.`);
125
+ }
108
126
  // Create workspace cache from init data
109
127
  const appConfig = (0, config_1.createApplicationConfig)();
110
128
  const workspaceCache = (0, workspace_cache_1.createWorkspaceCache)(init, appConfig.mcpConfig);
@@ -55,8 +55,16 @@ export declare const subscribeToSignal: (apiKey: string, eventType: SignalType |
55
55
  /**
56
56
  * Register new bot credentials for dynamic bot creation
57
57
  * Returns an API key that can be used to create a connection
58
+ *
59
+ * @param botId - Unique bot identifier
60
+ * @param email - Bot's Hailer email
61
+ * @param password - Bot's Hailer password
62
+ * @param options - Optional configuration
63
+ * @param options.allowedGroups - Tool groups this bot can access (e.g., ['read', 'write', 'bot_internal'])
58
64
  */
59
- export declare function registerBotCredentials(botId: string, email: string, password: string): string;
65
+ export declare function registerBotCredentials(botId: string, email: string, password: string, options?: {
66
+ allowedGroups?: string[];
67
+ }): string;
60
68
  /**
61
69
  * Unregister bot credentials and disconnect
62
70
  */
@@ -332,16 +332,26 @@ exports.subscribeToSignal = subscribeToSignal;
332
332
  /**
333
333
  * Register new bot credentials for dynamic bot creation
334
334
  * Returns an API key that can be used to create a connection
335
+ *
336
+ * @param botId - Unique bot identifier
337
+ * @param email - Bot's Hailer email
338
+ * @param password - Bot's Hailer password
339
+ * @param options - Optional configuration
340
+ * @param options.allowedGroups - Tool groups this bot can access (e.g., ['read', 'write', 'bot_internal'])
335
341
  */
336
- function registerBotCredentials(botId, email, password) {
342
+ function registerBotCredentials(botId, email, password, options) {
337
343
  const apiKey = `bot-${botId}-${Date.now()}`;
338
- // Add to environment CLIENT_CONFIGS
344
+ // Add to environment CLIENT_CONFIGS with optional tool access config
339
345
  config_1.environment.CLIENT_CONFIGS[apiKey] = {
340
346
  email,
341
347
  password,
342
348
  apiBaseUrl: 'https://api.hailer.com',
349
+ ...(options?.allowedGroups && { allowedGroups: options.allowedGroups }),
343
350
  };
344
- logger.info('Bot credentials registered', { botId });
351
+ logger.info('Bot credentials registered', {
352
+ botId,
353
+ hasAllowedGroups: !!options?.allowedGroups
354
+ });
345
355
  return apiKey;
346
356
  }
347
357
  /**
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SignalHandler = void 0;
4
4
  const logger_1 = require("../lib/logger");
5
- const bot_config_1 = require("./tools/bot-config");
5
+ const bot_config_1 = require("../bot-config");
6
6
  const logger = (0, logger_1.createLogger)({ component: 'signal-handler' });
7
7
  class SignalHandler {
8
8
  client;