@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.
Files changed (112) hide show
  1. package/.claude/agents/agent-giuseppe-app-builder.md +7 -6
  2. package/.claude/agents/agent-lars-code-inspector.md +26 -14
  3. package/dist/agents/bot-manager.d.ts +48 -0
  4. package/dist/agents/bot-manager.js +254 -0
  5. package/dist/agents/factory.d.ts +150 -0
  6. package/dist/agents/factory.js +650 -0
  7. package/dist/agents/giuseppe/ai.d.ts +83 -0
  8. package/dist/agents/giuseppe/ai.js +466 -0
  9. package/dist/agents/giuseppe/bot.d.ts +110 -0
  10. package/dist/agents/giuseppe/bot.js +780 -0
  11. package/dist/agents/giuseppe/config.d.ts +25 -0
  12. package/dist/agents/giuseppe/config.js +227 -0
  13. package/dist/agents/giuseppe/files.d.ts +52 -0
  14. package/dist/agents/giuseppe/files.js +338 -0
  15. package/dist/agents/giuseppe/git.d.ts +48 -0
  16. package/dist/agents/giuseppe/git.js +298 -0
  17. package/dist/agents/giuseppe/index.d.ts +97 -0
  18. package/dist/agents/giuseppe/index.js +258 -0
  19. package/dist/agents/giuseppe/lsp.d.ts +113 -0
  20. package/dist/agents/giuseppe/lsp.js +485 -0
  21. package/dist/agents/giuseppe/monitor.d.ts +118 -0
  22. package/dist/agents/giuseppe/monitor.js +621 -0
  23. package/dist/agents/giuseppe/prompt.d.ts +5 -0
  24. package/dist/agents/giuseppe/prompt.js +94 -0
  25. package/dist/agents/giuseppe/registries/pending-classification.d.ts +28 -0
  26. package/dist/agents/giuseppe/registries/pending-classification.js +50 -0
  27. package/dist/agents/giuseppe/registries/pending-fix.d.ts +30 -0
  28. package/dist/agents/giuseppe/registries/pending-fix.js +42 -0
  29. package/dist/agents/giuseppe/registries/pending.d.ts +27 -0
  30. package/dist/agents/giuseppe/registries/pending.js +49 -0
  31. package/dist/agents/giuseppe/specialist.d.ts +47 -0
  32. package/dist/agents/giuseppe/specialist.js +237 -0
  33. package/dist/agents/giuseppe/types.d.ts +123 -0
  34. package/dist/agents/giuseppe/types.js +9 -0
  35. package/dist/agents/hailer-expert/index.d.ts +8 -0
  36. package/dist/agents/hailer-expert/index.js +14 -0
  37. package/dist/agents/hal/daemon.d.ts +142 -0
  38. package/dist/agents/hal/daemon.js +1103 -0
  39. package/dist/agents/hal/definitions.d.ts +55 -0
  40. package/dist/agents/hal/definitions.js +263 -0
  41. package/dist/agents/hal/index.d.ts +3 -0
  42. package/dist/agents/hal/index.js +8 -0
  43. package/dist/agents/index.d.ts +18 -0
  44. package/dist/agents/index.js +48 -0
  45. package/dist/agents/shared/base.d.ts +216 -0
  46. package/dist/agents/shared/base.js +846 -0
  47. package/dist/agents/shared/services/agent-registry.d.ts +107 -0
  48. package/dist/agents/shared/services/agent-registry.js +629 -0
  49. package/dist/agents/shared/services/conversation-manager.d.ts +50 -0
  50. package/dist/agents/shared/services/conversation-manager.js +136 -0
  51. package/dist/agents/shared/services/mcp-client.d.ts +56 -0
  52. package/dist/agents/shared/services/mcp-client.js +124 -0
  53. package/dist/agents/shared/services/message-classifier.d.ts +37 -0
  54. package/dist/agents/shared/services/message-classifier.js +187 -0
  55. package/dist/agents/shared/services/message-formatter.d.ts +89 -0
  56. package/dist/agents/shared/services/message-formatter.js +371 -0
  57. package/dist/agents/shared/services/session-logger.d.ts +106 -0
  58. package/dist/agents/shared/services/session-logger.js +446 -0
  59. package/dist/agents/shared/services/tool-executor.d.ts +41 -0
  60. package/dist/agents/shared/services/tool-executor.js +169 -0
  61. package/dist/agents/shared/services/workspace-schema-cache.d.ts +125 -0
  62. package/dist/agents/shared/services/workspace-schema-cache.js +578 -0
  63. package/dist/agents/shared/specialist.d.ts +91 -0
  64. package/dist/agents/shared/specialist.js +399 -0
  65. package/dist/agents/shared/tool-schema-loader.d.ts +62 -0
  66. package/dist/agents/shared/tool-schema-loader.js +232 -0
  67. package/dist/agents/shared/types.d.ts +327 -0
  68. package/dist/agents/shared/types.js +121 -0
  69. package/dist/app.js +21 -4
  70. package/dist/cli.js +0 -0
  71. package/dist/client/agents/orchestrator.d.ts +1 -0
  72. package/dist/client/agents/orchestrator.js +12 -1
  73. package/dist/commands/seed-config.d.ts +9 -0
  74. package/dist/commands/seed-config.js +372 -0
  75. package/dist/config.d.ts +10 -0
  76. package/dist/config.js +61 -1
  77. package/dist/core.d.ts +8 -0
  78. package/dist/core.js +137 -6
  79. package/dist/lib/discussion-lock.d.ts +42 -0
  80. package/dist/lib/discussion-lock.js +110 -0
  81. package/dist/mcp/UserContextCache.js +2 -2
  82. package/dist/mcp/hailer-clients.d.ts +15 -0
  83. package/dist/mcp/hailer-clients.js +100 -6
  84. package/dist/mcp/signal-handler.d.ts +16 -5
  85. package/dist/mcp/signal-handler.js +173 -122
  86. package/dist/mcp/tools/activity.js +9 -1
  87. package/dist/mcp/tools/bot-config.d.ts +184 -9
  88. package/dist/mcp/tools/bot-config.js +2177 -163
  89. package/dist/mcp/tools/giuseppe-tools.d.ts +21 -0
  90. package/dist/mcp/tools/giuseppe-tools.js +525 -0
  91. package/dist/mcp/utils/hailer-api-client.d.ts +42 -1
  92. package/dist/mcp/utils/hailer-api-client.js +128 -2
  93. package/dist/mcp/webhook-handler.d.ts +87 -0
  94. package/dist/mcp/webhook-handler.js +343 -0
  95. package/dist/mcp/workspace-cache.d.ts +5 -0
  96. package/dist/mcp/workspace-cache.js +11 -0
  97. package/dist/mcp-server.js +55 -5
  98. package/dist/modules/bug-reports/giuseppe-agent.d.ts +58 -0
  99. package/dist/modules/bug-reports/giuseppe-agent.js +467 -0
  100. package/dist/modules/bug-reports/giuseppe-ai.d.ts +25 -1
  101. package/dist/modules/bug-reports/giuseppe-ai.js +133 -2
  102. package/dist/modules/bug-reports/giuseppe-bot.d.ts +2 -2
  103. package/dist/modules/bug-reports/giuseppe-bot.js +66 -42
  104. package/dist/modules/bug-reports/giuseppe-daemon.d.ts +80 -0
  105. package/dist/modules/bug-reports/giuseppe-daemon.js +617 -0
  106. package/dist/modules/bug-reports/giuseppe-files.d.ts +12 -0
  107. package/dist/modules/bug-reports/giuseppe-files.js +37 -0
  108. package/dist/modules/bug-reports/giuseppe-lsp.d.ts +84 -13
  109. package/dist/modules/bug-reports/giuseppe-lsp.js +403 -61
  110. package/dist/modules/bug-reports/index.d.ts +1 -0
  111. package/dist/modules/bug-reports/index.js +31 -29
  112. package/package.json +3 -2
package/dist/core.js CHANGED
@@ -8,12 +8,13 @@ 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("./client/factory");
11
+ const factory_1 = require("./agents/factory");
12
12
  const mcp_server_1 = require("./mcp-server");
13
13
  const tool_registry_1 = require("./mcp/tool-registry");
14
- const bug_reports_1 = require("./modules/bug-reports");
14
+ const giuseppe_1 = require("./agents/giuseppe");
15
15
  const UserContextCache_1 = require("./mcp/UserContextCache");
16
16
  const bot_config_1 = require("./mcp/tools/bot-config");
17
+ const webhook_handler_1 = require("./mcp/webhook-handler");
17
18
  class Core {
18
19
  logger;
19
20
  appConfig;
@@ -22,6 +23,8 @@ class Core {
22
23
  daemonManager = null;
23
24
  statusLogInterval;
24
25
  bugReportsModule;
26
+ daemonInitInProgress = false;
27
+ initialDaemonReady = false; // Prevents restart attempts before first daemon init
25
28
  constructor() {
26
29
  // Initialize logger first
27
30
  this.logger = (0, logger_1.createLogger)({
@@ -91,9 +94,15 @@ class Core {
91
94
  this.logger.info('MCP Server service started');
92
95
  }
93
96
  async startMCPClient() {
97
+ if (this.daemonInitInProgress) {
98
+ this.logger.debug('Daemon init already in progress, skipping startMCPClient');
99
+ return;
100
+ }
94
101
  this.logger.info('Starting Chat Agent Daemon (persistent conversation mode)');
102
+ this.daemonInitInProgress = true;
95
103
  try {
96
104
  await this.initializeDaemonMode();
105
+ this.initialDaemonReady = true; // Mark daemon as ready for restart signals
97
106
  this.logger.info('Chat Agent Daemon started');
98
107
  }
99
108
  catch (error) {
@@ -101,6 +110,9 @@ class Core {
101
110
  error: error instanceof Error ? error.message : String(error)
102
111
  });
103
112
  }
113
+ finally {
114
+ this.daemonInitInProgress = false;
115
+ }
104
116
  }
105
117
  async startBugMonitor() {
106
118
  try {
@@ -112,7 +124,7 @@ class Core {
112
124
  }
113
125
  const [apiKey] = accounts[0];
114
126
  const userContext = await UserContextCache_1.UserContextCache.getContext(apiKey);
115
- this.bugReportsModule = new bug_reports_1.BugReportsModule(userContext);
127
+ this.bugReportsModule = new giuseppe_1.BugReportsModule(userContext);
116
128
  // Register bot user IDs to ignore (so bug monitor only processes human messages)
117
129
  if (this.daemonManager) {
118
130
  const daemonStatus = this.daemonManager.getStatus();
@@ -155,6 +167,10 @@ class Core {
155
167
  const userContext = await UserContextCache_1.UserContextCache.getContext(apiKey);
156
168
  await (0, bot_config_1.initBotConfigPersistence)(userContext.hailer);
157
169
  this.logger.info('Bot config persistence initialized');
170
+ // Register callback for daemon restart when new orchestrator is discovered
171
+ (0, bot_config_1.onDaemonRestartNeeded)(() => {
172
+ this.attemptDaemonRestart();
173
+ });
158
174
  }
159
175
  catch (error) {
160
176
  this.logger.warn('Bot config persistence failed to initialize - using in-memory defaults', {
@@ -162,12 +178,83 @@ class Core {
162
178
  });
163
179
  }
164
180
  }
165
- async initializeDaemonMode() {
166
- this.logger.info('Initializing Chat Agent Daemon');
181
+ /**
182
+ * Attempt to restart daemon mode when bot state changes
183
+ * Stops existing daemons and starts fresh with updated configuration
184
+ * Includes retry logic for transient connection failures
185
+ */
186
+ async attemptDaemonRestart(retryCount = 0, workspaceId) {
187
+ const MAX_RETRIES = 3;
188
+ 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;
194
+ }
195
+ if (this.daemonInitInProgress) {
196
+ this.logger.debug('Daemon init already in progress, skipping');
197
+ return;
198
+ }
199
+ this.logger.info('Restarting daemons after bot state change', { retryCount });
200
+ this.daemonInitInProgress = true;
201
+ 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;
208
+ }
209
+ await this.daemonManager.stopAll();
210
+ this.daemonManager = null;
211
+ }
212
+ // Start fresh with updated bot state
213
+ 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;
217
+ if (!newManager) {
218
+ throw new Error('Daemon manager was not created');
219
+ }
220
+ if (!newManager.getOrchestrator()) {
221
+ throw new Error('Daemon manager created but no orchestrator available');
222
+ }
223
+ this.logger.info('Daemons successfully restarted after state change');
224
+ }
225
+ catch (error) {
226
+ const errorMessage = error instanceof Error ? error.message : String(error);
227
+ const isTransientError = errorMessage.includes('Timeout') ||
228
+ errorMessage.includes('ECONNREFUSED') ||
229
+ errorMessage.includes('No orchestrator');
230
+ if (isTransientError && retryCount < MAX_RETRIES) {
231
+ this.logger.warn('Daemon restart failed with transient error, scheduling retry', {
232
+ error: errorMessage,
233
+ retryCount: retryCount + 1,
234
+ retryDelayMs: RETRY_DELAY_MS
235
+ });
236
+ // Reset flag before scheduling retry
237
+ this.daemonInitInProgress = false;
238
+ setTimeout(() => {
239
+ this.attemptDaemonRestart(retryCount + 1, workspaceId);
240
+ }, RETRY_DELAY_MS);
241
+ return;
242
+ }
243
+ this.logger.warn('Daemon restart failed', {
244
+ error: errorMessage,
245
+ retriesExhausted: retryCount >= MAX_RETRIES
246
+ });
247
+ }
248
+ finally {
249
+ this.daemonInitInProgress = false;
250
+ }
251
+ }
252
+ async initializeDaemonMode(workspaceId) {
253
+ this.logger.info('Initializing Chat Agent Daemon', { workspaceId });
167
254
  // Check for orchestrator mode via environment variable
168
255
  const orchestratorMode = process.env.DAEMON_ORCHESTRATOR_MODE === 'true';
169
256
  this.logger.info(`Daemon mode: ${orchestratorMode ? 'ORCHESTRATOR' : 'STANDARD'}`);
170
- this.daemonManager = await (0, factory_1.createDaemonManager)({ orchestratorMode });
257
+ this.daemonManager = await (0, factory_1.createDaemonManager)({ orchestratorMode, workspaceId });
171
258
  if (!this.daemonManager) {
172
259
  throw new Error('Failed to create daemon manager');
173
260
  }
@@ -177,6 +264,48 @@ class Core {
177
264
  bots: status.map(s => s.botId),
178
265
  orchestratorMode
179
266
  });
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
+ });
180
309
  // Start periodic status logging (every 60s)
181
310
  this.statusLogInterval = this.daemonManager.startStatusLogging(60000);
182
311
  this.logger.info('Daemon status logging started (every 60s)');
@@ -198,6 +327,8 @@ class Core {
198
327
  if (this.bugReportsModule) {
199
328
  await this.bugReportsModule.stop();
200
329
  }
330
+ // Cleanup bot config (clear timers and callbacks)
331
+ (0, bot_config_1.cleanupBotConfig)();
201
332
  if (this.daemonManager) {
202
333
  this.daemonManager.stopAll();
203
334
  this.logger.info('Chat Agent Daemon stopped');
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Discussion Lock Registry
3
+ *
4
+ * Prevents multiple bots from responding to the same discussion.
5
+ * When a specialist bot (like Giuseppe) is handling a discussion,
6
+ * it acquires a lock. Other bots (like Orchestrator) check the lock
7
+ * before responding.
8
+ */
9
+ /**
10
+ * Acquire a lock on a discussion
11
+ * @param discussionId - The discussion to lock
12
+ * @param botName - Name of the bot acquiring the lock (for logging)
13
+ * @param ttlMs - Lock duration in milliseconds (default: 5 minutes)
14
+ * @returns true if lock acquired, false if already locked by another bot
15
+ */
16
+ export declare function acquireDiscussionLock(discussionId: string, botName: string, ttlMs?: number): boolean;
17
+ /**
18
+ * Release a lock on a discussion
19
+ * @param discussionId - The discussion to unlock
20
+ * @param botName - Name of the bot releasing (must match acquirer)
21
+ */
22
+ export declare function releaseDiscussionLock(discussionId: string, botName: string): void;
23
+ /**
24
+ * Check if a discussion is locked by another bot
25
+ * @param discussionId - The discussion to check
26
+ * @param myBotName - Name of the checking bot (own locks don't block)
27
+ * @returns true if locked by ANOTHER bot, false if free or own lock
28
+ */
29
+ export declare function isDiscussionLocked(discussionId: string, myBotName: string): boolean;
30
+ /**
31
+ * Clean up expired locks (call periodically)
32
+ */
33
+ export declare function cleanupExpiredLocks(): number;
34
+ /**
35
+ * Get current lock status (for debugging)
36
+ */
37
+ export declare function getLockStatus(): Map<string, {
38
+ botName: string;
39
+ acquiredAt: number;
40
+ expiresAt: number;
41
+ }>;
42
+ //# sourceMappingURL=discussion-lock.d.ts.map
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ /**
3
+ * Discussion Lock Registry
4
+ *
5
+ * Prevents multiple bots from responding to the same discussion.
6
+ * When a specialist bot (like Giuseppe) is handling a discussion,
7
+ * it acquires a lock. Other bots (like Orchestrator) check the lock
8
+ * before responding.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.acquireDiscussionLock = acquireDiscussionLock;
12
+ exports.releaseDiscussionLock = releaseDiscussionLock;
13
+ exports.isDiscussionLocked = isDiscussionLocked;
14
+ exports.cleanupExpiredLocks = cleanupExpiredLocks;
15
+ exports.getLockStatus = getLockStatus;
16
+ const logger_1 = require("./logger");
17
+ const logger = (0, logger_1.createLogger)({ component: 'discussion-lock' });
18
+ // Singleton map of discussionId -> { botName, acquiredAt, expiresAt }
19
+ const locks = new Map();
20
+ // Default lock TTL: 5 minutes (allows for LLM processing time)
21
+ const DEFAULT_LOCK_TTL_MS = 5 * 60 * 1000;
22
+ /**
23
+ * Acquire a lock on a discussion
24
+ * @param discussionId - The discussion to lock
25
+ * @param botName - Name of the bot acquiring the lock (for logging)
26
+ * @param ttlMs - Lock duration in milliseconds (default: 5 minutes)
27
+ * @returns true if lock acquired, false if already locked by another bot
28
+ */
29
+ function acquireDiscussionLock(discussionId, botName, ttlMs = DEFAULT_LOCK_TTL_MS) {
30
+ const now = Date.now();
31
+ const existing = locks.get(discussionId);
32
+ // Check if existing lock is still valid
33
+ if (existing && existing.expiresAt > now) {
34
+ if (existing.botName === botName) {
35
+ // Same bot - extend the lock
36
+ existing.expiresAt = now + ttlMs;
37
+ return true;
38
+ }
39
+ // Different bot has the lock
40
+ logger.debug('Discussion already locked', {
41
+ discussionId,
42
+ lockedBy: existing.botName,
43
+ requestedBy: botName
44
+ });
45
+ return false;
46
+ }
47
+ // Acquire lock
48
+ locks.set(discussionId, {
49
+ botName,
50
+ acquiredAt: now,
51
+ expiresAt: now + ttlMs
52
+ });
53
+ logger.debug('Discussion lock acquired', { discussionId, botName, ttlMs });
54
+ return true;
55
+ }
56
+ /**
57
+ * Release a lock on a discussion
58
+ * @param discussionId - The discussion to unlock
59
+ * @param botName - Name of the bot releasing (must match acquirer)
60
+ */
61
+ function releaseDiscussionLock(discussionId, botName) {
62
+ const existing = locks.get(discussionId);
63
+ if (existing && existing.botName === botName) {
64
+ locks.delete(discussionId);
65
+ logger.debug('Discussion lock released', { discussionId, botName });
66
+ }
67
+ }
68
+ /**
69
+ * Check if a discussion is locked by another bot
70
+ * @param discussionId - The discussion to check
71
+ * @param myBotName - Name of the checking bot (own locks don't block)
72
+ * @returns true if locked by ANOTHER bot, false if free or own lock
73
+ */
74
+ function isDiscussionLocked(discussionId, myBotName) {
75
+ const now = Date.now();
76
+ const existing = locks.get(discussionId);
77
+ // No lock or expired
78
+ if (!existing || existing.expiresAt <= now) {
79
+ return false;
80
+ }
81
+ // Own lock doesn't block
82
+ if (existing.botName === myBotName) {
83
+ return false;
84
+ }
85
+ return true;
86
+ }
87
+ /**
88
+ * Clean up expired locks (call periodically)
89
+ */
90
+ function cleanupExpiredLocks() {
91
+ const now = Date.now();
92
+ let cleaned = 0;
93
+ for (const [discussionId, lock] of locks.entries()) {
94
+ if (lock.expiresAt <= now) {
95
+ locks.delete(discussionId);
96
+ cleaned++;
97
+ }
98
+ }
99
+ if (cleaned > 0) {
100
+ logger.debug('Cleaned up expired locks', { count: cleaned });
101
+ }
102
+ return cleaned;
103
+ }
104
+ /**
105
+ * Get current lock status (for debugging)
106
+ */
107
+ function getLockStatus() {
108
+ return new Map(locks);
109
+ }
110
+ //# sourceMappingURL=discussion-lock.js.map
@@ -101,9 +101,9 @@ class UserContextCache {
101
101
  const client = await (0, hailer_clients_1.createHailerClientByApiKey)(apiKey);
102
102
  // Create API client (host extracted from client automatically)
103
103
  const hailer = new index_1.HailerApiClient(client);
104
- // Fetch user's workspace initialization data
104
+ // Fetch user's workspace initialization data (including teams for activity creation)
105
105
  const init = await client.socket.request('v2.core.init', [
106
- ['processes', 'users', 'network', 'networks']
106
+ ['processes', 'users', 'network', 'networks', 'teams']
107
107
  ]);
108
108
  // Create workspace cache from init data
109
109
  const appConfig = (0, config_1.createApplicationConfig)();
@@ -24,6 +24,7 @@ export declare class HailerClientManager {
24
24
  private socketClient;
25
25
  private restClient;
26
26
  private signalHandlers;
27
+ private signalsInitialized;
27
28
  constructor(host: string, username: string, password: string);
28
29
  connect(): Promise<HailerClient>;
29
30
  private setupSignalHandling;
@@ -42,8 +43,22 @@ export declare const createHailerClientByApiKey: (apiKey: string) => Promise<Hai
42
43
  * Disconnect client by API key (new unified approach)
43
44
  */
44
45
  export declare const disconnectHailerClientByApiKey: (apiKey: string) => void;
46
+ /**
47
+ * Clear all connections - forces fresh reconnect on next request
48
+ * Use this when you need to refresh stale server-side data (e.g., after template install)
49
+ */
50
+ export declare const clearAllConnections: () => void;
45
51
  /**
46
52
  * Subscribe to signals for a client connection
47
53
  */
48
54
  export declare const subscribeToSignal: (apiKey: string, eventType: SignalType | string, handler: SignalHandler) => (() => void) | null;
55
+ /**
56
+ * Register new bot credentials for dynamic bot creation
57
+ * Returns an API key that can be used to create a connection
58
+ */
59
+ export declare function registerBotCredentials(botId: string, email: string, password: string): string;
60
+ /**
61
+ * Unregister bot credentials and disconnect
62
+ */
63
+ export declare function unregisterBotCredentials(apiKey: string): void;
49
64
  //# sourceMappingURL=hailer-clients.d.ts.map
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.subscribeToSignal = exports.disconnectHailerClientByApiKey = exports.createHailerClientByApiKey = exports.HailerClientManager = void 0;
3
+ exports.subscribeToSignal = exports.clearAllConnections = exports.disconnectHailerClientByApiKey = exports.createHailerClientByApiKey = exports.HailerClientManager = void 0;
4
4
  exports.getCurrentUserId = getCurrentUserId;
5
+ exports.registerBotCredentials = registerBotCredentials;
6
+ exports.unregisterBotCredentials = unregisterBotCredentials;
5
7
  const cli_1 = require("@hailer/cli");
6
8
  const auth_1 = require("./auth");
7
9
  const logger_1 = require("../lib/logger");
@@ -74,6 +76,7 @@ class HailerClientManager {
74
76
  socketClient = null;
75
77
  restClient = null;
76
78
  signalHandlers = new Map();
79
+ signalsInitialized = false;
77
80
  constructor(host, username, password) {
78
81
  this.host = host;
79
82
  this.username = username;
@@ -88,14 +91,35 @@ class HailerClientManager {
88
91
  password: this.password,
89
92
  ...(isLocalDev && { rejectUnauthorized: false }), // Add for local dev with self-signed certs
90
93
  };
94
+ // Track timeout for proper cleanup
95
+ let timeoutId = null;
96
+ let timeoutCleared = false;
91
97
  try {
92
- // Create socket client using @hailer/cli with timeout
98
+ // Create socket client using @hailer/cli with cancellable timeout
93
99
  this.socketClient = (await Promise.race([
94
- cli_1.Client.create(clientOptions),
95
- new Promise((_, reject) => setTimeout(() => reject(new Error("Socket connection timeout after 30s")), 30000)),
100
+ cli_1.Client.create(clientOptions).then(client => {
101
+ // Clear timeout on success
102
+ if (timeoutId && !timeoutCleared) {
103
+ clearTimeout(timeoutId);
104
+ timeoutCleared = true;
105
+ }
106
+ return client;
107
+ }),
108
+ new Promise((_, reject) => {
109
+ timeoutId = setTimeout(() => {
110
+ if (!timeoutCleared) {
111
+ reject(new Error(`Timeout connecting to: ${this.host}`));
112
+ }
113
+ }, 30000);
114
+ }),
96
115
  ]));
97
116
  }
98
117
  catch (error) {
118
+ // Ensure timeout is cleared on error too
119
+ if (timeoutId && !timeoutCleared) {
120
+ clearTimeout(timeoutId);
121
+ timeoutCleared = true;
122
+ }
99
123
  logger.error('Failed to create socket client', error, { username: this.username });
100
124
  throw error;
101
125
  }
@@ -123,10 +147,43 @@ class HailerClientManager {
123
147
  };
124
148
  }
125
149
  setupSignalHandling() {
126
- if (!this.socketClient)
150
+ if (!this.socketClient || this.signalsInitialized)
127
151
  return;
152
+ this.signalsInitialized = true;
153
+ // Connection lifecycle logging - helps diagnose bot death issues
154
+ this.socketClient.on("disconnect", (reason) => {
155
+ logger.warn('Socket disconnected', {
156
+ reason,
157
+ username: this.username,
158
+ willReconnect: reason !== 'io server disconnect' && reason !== 'io client disconnect'
159
+ });
160
+ });
161
+ this.socketClient.on("reconnect", () => {
162
+ logger.info('Socket reconnected and session resumed', { username: this.username });
163
+ });
164
+ this.socketClient.on("connect", () => {
165
+ logger.info('Socket connected', { username: this.username });
166
+ });
167
+ this.socketClient.on("connect_error", (error) => {
168
+ logger.error('Socket connection error', { error: error.message, username: this.username });
169
+ });
170
+ this.socketClient.on("reconnect_attempt", (attempt) => {
171
+ logger.info('Socket reconnection attempt', { attempt, username: this.username });
172
+ });
173
+ this.socketClient.on("reconnect_failed", () => {
174
+ logger.error('Socket reconnection failed permanently', { username: this.username });
175
+ });
128
176
  this.socketClient.on("signals", (signal) => {
129
177
  const [eventType, eventData] = signal;
178
+ // Debug: log all incoming signals to see what we're receiving
179
+ if (eventType === 'messenger.new') {
180
+ logger.debug('HailerClientManager received messenger.new signal', {
181
+ eventType,
182
+ discussion: eventData.discussion,
183
+ handlersRegistered: this.signalHandlers.has(eventType),
184
+ handlerCount: this.signalHandlers.get(eventType)?.length || 0,
185
+ });
186
+ }
130
187
  // Dispatch to registered handlers
131
188
  const handlers = this.signalHandlers.get(eventType);
132
189
  if (handlers) {
@@ -165,6 +222,7 @@ class HailerClientManager {
165
222
  }
166
223
  this.restClient = null;
167
224
  this.signalHandlers.clear();
225
+ this.signalsInitialized = false;
168
226
  }
169
227
  isConnected() {
170
228
  return this.socketClient !== null && this.restClient !== null;
@@ -223,7 +281,7 @@ const createHailerClientByApiKey = async (apiKey) => {
223
281
  // Create new connection
224
282
  logger.info('Creating new connection', {
225
283
  apiKey: apiKey.substring(0, 8) + '...',
226
- email: account.email,
284
+ email: (0, config_1.maskEmail)(account.email),
227
285
  host: account.apiBaseUrl
228
286
  });
229
287
  clientManager = new HailerClientManager(account.apiBaseUrl, account.email, account.password);
@@ -243,6 +301,19 @@ const disconnectHailerClientByApiKey = (apiKey) => {
243
301
  }
244
302
  };
245
303
  exports.disconnectHailerClientByApiKey = disconnectHailerClientByApiKey;
304
+ /**
305
+ * Clear all connections - forces fresh reconnect on next request
306
+ * Use this when you need to refresh stale server-side data (e.g., after template install)
307
+ */
308
+ const clearAllConnections = () => {
309
+ const count = connectionPool.size;
310
+ for (const [, clientManager] of connectionPool.entries()) {
311
+ clientManager.disconnect();
312
+ }
313
+ connectionPool.clear();
314
+ logger.info('Cleared Hailer connections', { count });
315
+ };
316
+ exports.clearAllConnections = clearAllConnections;
246
317
  /**
247
318
  * Subscribe to signals for a client connection
248
319
  */
@@ -258,4 +329,27 @@ const subscribeToSignal = (apiKey, eventType, handler) => {
258
329
  };
259
330
  };
260
331
  exports.subscribeToSignal = subscribeToSignal;
332
+ /**
333
+ * Register new bot credentials for dynamic bot creation
334
+ * Returns an API key that can be used to create a connection
335
+ */
336
+ function registerBotCredentials(botId, email, password) {
337
+ const apiKey = `bot-${botId}-${Date.now()}`;
338
+ // Add to environment CLIENT_CONFIGS
339
+ config_1.environment.CLIENT_CONFIGS[apiKey] = {
340
+ email,
341
+ password,
342
+ apiBaseUrl: 'https://api.hailer.com',
343
+ };
344
+ logger.info('Bot credentials registered', { botId });
345
+ return apiKey;
346
+ }
347
+ /**
348
+ * Unregister bot credentials and disconnect
349
+ */
350
+ function unregisterBotCredentials(apiKey) {
351
+ delete config_1.environment.CLIENT_CONFIGS[apiKey];
352
+ (0, exports.disconnectHailerClientByApiKey)(apiKey);
353
+ logger.info('Bot credentials unregistered');
354
+ }
261
355
  //# sourceMappingURL=hailer-clients.js.map
@@ -3,7 +3,7 @@ import { HailerClient } from './hailer-clients';
3
3
  /**
4
4
  * Signal types that Hailer emits via socket.io
5
5
  */
6
- export type HailerSignalType = 'activities.updated' | 'activities.created' | 'activities.deleted' | 'discussion.message' | 'messenger.new' | 'user.joined' | 'user.left' | 'workspace.updated' | 'process.updated' | 'cache.invalidate';
6
+ export type HailerSignalType = 'activities.updated' | 'activities.created' | 'activities.deleted' | 'discussion.message' | 'messenger.new' | 'user.joined' | 'user.left' | 'workspace.updated' | 'process.updated' | 'cache.invalidate' | 'company.new_invitation';
7
7
  /** Raw signal from Hailer socket - tuple of [eventType, eventData] */
8
8
  export type HailerSocketSignal = [string, Record<string, unknown>];
9
9
  /** Message object nested in signal data */
@@ -14,6 +14,13 @@ export interface HailerSignalMessage {
14
14
  user?: string;
15
15
  userName?: string;
16
16
  }
17
+ /** Metadata for activities.updated signals - contains phase change information */
18
+ export interface ActivityUpdatedMeta {
19
+ processId: string;
20
+ phase: string;
21
+ prevPhase?: string;
22
+ activity_id: string | string[];
23
+ }
17
24
  /** Signal data varies by type - common fields for type narrowing */
18
25
  export interface HailerSignalData {
19
26
  _id?: string;
@@ -26,6 +33,7 @@ export interface HailerSignalData {
26
33
  discussion?: string;
27
34
  uid?: string;
28
35
  message?: HailerSignalMessage;
36
+ meta?: ActivityUpdatedMeta;
29
37
  [key: string]: unknown;
30
38
  }
31
39
  export interface HailerSignal {
@@ -46,7 +54,13 @@ export declare class SignalHandler {
46
54
  private subscriptions;
47
55
  private signalHistory;
48
56
  private maxHistorySize;
57
+ private static handlersBySocketId;
49
58
  constructor(client: HailerClient, workspaceCache?: WorkspaceCache | undefined);
59
+ /**
60
+ * Get or create a SignalHandler for a client (prevents duplicate listeners)
61
+ */
62
+ static getOrCreate(client: HailerClient, workspaceCache?: WorkspaceCache): SignalHandler;
63
+ private signalListener;
50
64
  private initializeSignalHandling;
51
65
  private handleIncomingSignal;
52
66
  private addToHistory;
@@ -58,14 +72,11 @@ export declare class SignalHandler {
58
72
  private handleWorkspaceUpdated;
59
73
  private handleMessengerNew;
60
74
  private handleCacheInvalidate;
75
+ private handleNewInvitation;
61
76
  subscribe(id: string, types: HailerSignalType[], handler: (signal: HailerSignal) => void, workspaceId?: string): void;
62
77
  unsubscribe(id: string): boolean;
63
78
  getSignalHistory(types?: HailerSignalType[], limit?: number, workspaceId?: string): HailerSignal[];
64
79
  getActiveSubscriptions(): SignalSubscription[];
65
80
  clearHistory(): void;
66
- createMcpSignalTools(server: {
67
- tool: (...args: unknown[]) => void;
68
- }): void;
69
81
  }
70
- export declare const createSignalHandler: (clients: HailerClient, workspaceCache?: WorkspaceCache) => SignalHandler;
71
82
  //# sourceMappingURL=signal-handler.d.ts.map