@hailer/mcp 1.1.11 → 1.1.13

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 (252) hide show
  1. package/dist/app.js +18 -5
  2. package/dist/bot/bot-config.d.ts +12 -1
  3. package/dist/bot/bot-config.js +98 -14
  4. package/dist/bot/bot-manager.d.ts +13 -3
  5. package/dist/bot/bot-manager.js +80 -25
  6. package/dist/bot/bot.d.ts +46 -0
  7. package/dist/bot/bot.js +542 -166
  8. package/dist/bot/services/message-classifier.js +17 -0
  9. package/dist/bot/services/permission-guard.d.ts +52 -0
  10. package/dist/bot/services/permission-guard.js +149 -0
  11. package/dist/bot/services/types.d.ts +5 -0
  12. package/dist/bot/services/typing-indicator.d.ts +6 -1
  13. package/dist/bot/services/typing-indicator.js +19 -3
  14. package/dist/config.d.ts +6 -1
  15. package/dist/config.js +43 -0
  16. package/dist/core.js +3 -6
  17. package/dist/mcp/UserContextCache.d.ts +5 -0
  18. package/dist/mcp/UserContextCache.js +51 -19
  19. package/dist/mcp/hailer-clients.d.ts +19 -1
  20. package/dist/mcp/hailer-clients.js +157 -20
  21. package/dist/mcp/session-store.d.ts +68 -0
  22. package/dist/mcp/session-store.js +169 -0
  23. package/dist/mcp/signal-handler.js +12 -12
  24. package/dist/mcp/tool-registry.d.ts +17 -4
  25. package/dist/mcp/tool-registry.js +37 -7
  26. package/dist/mcp/tools/activity.js +99 -7
  27. package/dist/mcp/tools/app-scaffold.js +304 -336
  28. package/dist/mcp/tools/company.d.ts +9 -0
  29. package/dist/mcp/tools/company.js +88 -0
  30. package/dist/mcp/tools/discussion.js +68 -0
  31. package/dist/mcp/tools/workflow-permissions.d.ts +15 -0
  32. package/dist/mcp/tools/workflow-permissions.js +204 -0
  33. package/dist/mcp/tools/workflow.js +57 -18
  34. package/dist/mcp/utils/index.d.ts +2 -0
  35. package/dist/mcp/utils/index.js +12 -1
  36. package/dist/mcp/utils/role-utils.d.ts +74 -0
  37. package/dist/mcp/utils/role-utils.js +151 -0
  38. package/dist/mcp/utils/types.d.ts +43 -1
  39. package/dist/mcp/utils/types.js +14 -0
  40. package/dist/mcp/webhook-handler.d.ts +6 -0
  41. package/dist/mcp/webhook-handler.js +11 -0
  42. package/dist/mcp-server.d.ts +23 -2
  43. package/dist/mcp-server.js +639 -111
  44. package/dist/plugins/vipunen/client.d.ts +150 -0
  45. package/dist/plugins/vipunen/client.js +535 -0
  46. package/dist/plugins/vipunen/config/schema-config.json +19 -0
  47. package/dist/plugins/vipunen/config/schema-doc.json +22 -0
  48. package/dist/plugins/vipunen/index.d.ts +41 -0
  49. package/dist/plugins/vipunen/index.js +88 -0
  50. package/dist/plugins/vipunen/tools.d.ts +26 -0
  51. package/dist/plugins/vipunen/tools.js +501 -0
  52. package/package.json +2 -1
  53. package/.claude/.context-watchdog.json +0 -1
  54. package/.claude/.session-checked +0 -1
  55. package/.claude/CLAUDE.md +0 -370
  56. package/.claude/agents/agent-ada-skill-builder.md +0 -94
  57. package/.claude/agents/agent-alejandro-function-fields.md +0 -342
  58. package/.claude/agents/agent-bjorn-config-audit.md +0 -103
  59. package/.claude/agents/agent-builder-agent-creator.md +0 -130
  60. package/.claude/agents/agent-code-simplifier.md +0 -53
  61. package/.claude/agents/agent-dmitri-activity-crud.md +0 -159
  62. package/.claude/agents/agent-giuseppe-app-builder.md +0 -247
  63. package/.claude/agents/agent-gunther-mcp-tools.md +0 -39
  64. package/.claude/agents/agent-helga-workflow-config.md +0 -204
  65. package/.claude/agents/agent-igor-activity-mover-automation.md +0 -125
  66. package/.claude/agents/agent-ingrid-doc-templates.md +0 -261
  67. package/.claude/agents/agent-ivan-monolith.md +0 -154
  68. package/.claude/agents/agent-kenji-data-reader.md +0 -86
  69. package/.claude/agents/agent-lars-code-inspector.md +0 -102
  70. package/.claude/agents/agent-marco-mockup-builder.md +0 -110
  71. package/.claude/agents/agent-marcus-api-documenter.md +0 -323
  72. package/.claude/agents/agent-marketplace-publisher.md +0 -280
  73. package/.claude/agents/agent-marketplace-reviewer.md +0 -309
  74. package/.claude/agents/agent-permissions-handler.md +0 -208
  75. package/.claude/agents/agent-simple-writer.md +0 -48
  76. package/.claude/agents/agent-svetlana-code-review.md +0 -171
  77. package/.claude/agents/agent-tanya-test-runner.md +0 -333
  78. package/.claude/agents/agent-ui-designer.md +0 -100
  79. package/.claude/agents/agent-viktor-sql-insights.md +0 -212
  80. package/.claude/agents/agent-web-search.md +0 -55
  81. package/.claude/agents/agent-yevgeni-discussions.md +0 -45
  82. package/.claude/agents/agent-zara-zapier.md +0 -159
  83. package/.claude/commands/app-squad.md +0 -135
  84. package/.claude/commands/audit-squad.md +0 -158
  85. package/.claude/commands/autoplan.md +0 -563
  86. package/.claude/commands/cleanup-squad.md +0 -98
  87. package/.claude/commands/config-squad.md +0 -106
  88. package/.claude/commands/crud-squad.md +0 -87
  89. package/.claude/commands/data-squad.md +0 -97
  90. package/.claude/commands/debug-squad.md +0 -303
  91. package/.claude/commands/doc-squad.md +0 -65
  92. package/.claude/commands/handoff.md +0 -137
  93. package/.claude/commands/health.md +0 -49
  94. package/.claude/commands/help.md +0 -29
  95. package/.claude/commands/help:agents.md +0 -151
  96. package/.claude/commands/help:commands.md +0 -78
  97. package/.claude/commands/help:faq.md +0 -79
  98. package/.claude/commands/help:plugins.md +0 -50
  99. package/.claude/commands/help:skills.md +0 -93
  100. package/.claude/commands/help:tools.md +0 -75
  101. package/.claude/commands/hotfix-squad.md +0 -112
  102. package/.claude/commands/integration-squad.md +0 -82
  103. package/.claude/commands/janitor-squad.md +0 -167
  104. package/.claude/commands/learn-auto.md +0 -120
  105. package/.claude/commands/learn.md +0 -120
  106. package/.claude/commands/mcp-list.md +0 -27
  107. package/.claude/commands/onboard-squad.md +0 -140
  108. package/.claude/commands/plan-workspace.md +0 -732
  109. package/.claude/commands/prd.md +0 -130
  110. package/.claude/commands/project-status.md +0 -82
  111. package/.claude/commands/publish.md +0 -138
  112. package/.claude/commands/recap.md +0 -69
  113. package/.claude/commands/restore.md +0 -64
  114. package/.claude/commands/review-squad.md +0 -152
  115. package/.claude/commands/save.md +0 -24
  116. package/.claude/commands/stats.md +0 -19
  117. package/.claude/commands/swarm.md +0 -210
  118. package/.claude/commands/tool-builder.md +0 -39
  119. package/.claude/commands/ws-pull.md +0 -44
  120. package/.claude/hooks/_shared-memory.cjs +0 -305
  121. package/.claude/hooks/_utils.cjs +0 -108
  122. package/.claude/hooks/agent-failure-detector.cjs +0 -383
  123. package/.claude/hooks/agent-usage-logger.cjs +0 -204
  124. package/.claude/hooks/app-edit-guard.cjs +0 -494
  125. package/.claude/hooks/auto-learn.cjs +0 -304
  126. package/.claude/hooks/bash-guard.cjs +0 -272
  127. package/.claude/hooks/builder-mode-manager.cjs +0 -354
  128. package/.claude/hooks/bulk-activity-guard.cjs +0 -271
  129. package/.claude/hooks/context-watchdog.cjs +0 -230
  130. package/.claude/hooks/delegation-reminder.cjs +0 -465
  131. package/.claude/hooks/design-system-lint.cjs +0 -271
  132. package/.claude/hooks/post-scaffold-hook.cjs +0 -181
  133. package/.claude/hooks/prompt-guard.cjs +0 -354
  134. package/.claude/hooks/publish-template-guard.cjs +0 -147
  135. package/.claude/hooks/session-start.cjs +0 -35
  136. package/.claude/hooks/shared-memory-writer.cjs +0 -147
  137. package/.claude/hooks/skill-injector.cjs +0 -140
  138. package/.claude/hooks/skill-usage-logger.cjs +0 -258
  139. package/.claude/hooks/src-edit-guard.cjs +0 -240
  140. package/.claude/hooks/sync-marketplace-agents.cjs +0 -346
  141. package/.claude/settings.json +0 -257
  142. package/.claude/skills/SDK-activity-patterns/SKILL.md +0 -428
  143. package/.claude/skills/SDK-document-templates/SKILL.md +0 -1033
  144. package/.claude/skills/SDK-function-fields/SKILL.md +0 -542
  145. package/.claude/skills/SDK-generate-skill/SKILL.md +0 -92
  146. package/.claude/skills/SDK-init-skill/SKILL.md +0 -127
  147. package/.claude/skills/SDK-insight-queries/SKILL.md +0 -787
  148. package/.claude/skills/SDK-ws-config-skill/SKILL.md +0 -1139
  149. package/.claude/skills/agent-structure/SKILL.md +0 -98
  150. package/.claude/skills/api-documentation-patterns/SKILL.md +0 -474
  151. package/.claude/skills/chrome-mcp-reference/SKILL.md +0 -370
  152. package/.claude/skills/delegation-routing/SKILL.md +0 -202
  153. package/.claude/skills/frontend-design/SKILL.md +0 -254
  154. package/.claude/skills/hailer-activity-mover/SKILL.md +0 -213
  155. package/.claude/skills/hailer-api-client/SKILL.md +0 -518
  156. package/.claude/skills/hailer-app-builder/SKILL.md +0 -1434
  157. package/.claude/skills/hailer-apps-pictures/SKILL.md +0 -269
  158. package/.claude/skills/hailer-design-system/SKILL.md +0 -235
  159. package/.claude/skills/hailer-monolith-automations/SKILL.md +0 -686
  160. package/.claude/skills/hailer-permissions-system/SKILL.md +0 -121
  161. package/.claude/skills/hailer-project-protocol/SKILL.md +0 -488
  162. package/.claude/skills/hailer-rest-api/SKILL.md +0 -61
  163. package/.claude/skills/hailer-rest-api/hailer-activities.md +0 -184
  164. package/.claude/skills/hailer-rest-api/hailer-admin.md +0 -473
  165. package/.claude/skills/hailer-rest-api/hailer-calendar.md +0 -256
  166. package/.claude/skills/hailer-rest-api/hailer-feed.md +0 -249
  167. package/.claude/skills/hailer-rest-api/hailer-insights.md +0 -195
  168. package/.claude/skills/hailer-rest-api/hailer-messaging.md +0 -276
  169. package/.claude/skills/hailer-rest-api/hailer-workflows.md +0 -283
  170. package/.claude/skills/insight-join-patterns/SKILL.md +0 -174
  171. package/.claude/skills/integration-patterns/SKILL.md +0 -421
  172. package/.claude/skills/json-only-output/SKILL.md +0 -72
  173. package/.claude/skills/lsp-setup/SKILL.md +0 -160
  174. package/.claude/skills/mcp-direct-tools/SKILL.md +0 -153
  175. package/.claude/skills/optional-parameters/SKILL.md +0 -72
  176. package/.claude/skills/publish-hailer-app/SKILL.md +0 -244
  177. package/.claude/skills/testing-patterns/SKILL.md +0 -630
  178. package/.claude/skills/tool-builder/SKILL.md +0 -250
  179. package/.claude/skills/tool-parameter-usage/SKILL.md +0 -126
  180. package/.claude/skills/tool-response-verification/SKILL.md +0 -92
  181. package/.claude/skills/zapier-hailer-patterns/SKILL.md +0 -581
  182. package/.hailer-mcp-port +0 -1
  183. package/.mcp.json +0 -13
  184. package/.opencode/agent/agent-ada-skill-builder.md +0 -35
  185. package/.opencode/agent/agent-alejandro-function-fields.md +0 -39
  186. package/.opencode/agent/agent-bjorn-config-audit.md +0 -36
  187. package/.opencode/agent/agent-builder-agent-creator.md +0 -39
  188. package/.opencode/agent/agent-code-simplifier.md +0 -31
  189. package/.opencode/agent/agent-dmitri-activity-crud.md +0 -40
  190. package/.opencode/agent/agent-giuseppe-app-builder.md +0 -37
  191. package/.opencode/agent/agent-gunther-mcp-tools.md +0 -39
  192. package/.opencode/agent/agent-helga-workflow-config.md +0 -204
  193. package/.opencode/agent/agent-igor-activity-mover-automation.md +0 -46
  194. package/.opencode/agent/agent-ingrid-doc-templates.md +0 -39
  195. package/.opencode/agent/agent-ivan-monolith.md +0 -46
  196. package/.opencode/agent/agent-kenji-data-reader.md +0 -53
  197. package/.opencode/agent/agent-lars-code-inspector.md +0 -28
  198. package/.opencode/agent/agent-marco-mockup-builder.md +0 -42
  199. package/.opencode/agent/agent-marcus-api-documenter.md +0 -53
  200. package/.opencode/agent/agent-marketplace-publisher.md +0 -44
  201. package/.opencode/agent/agent-marketplace-reviewer.md +0 -42
  202. package/.opencode/agent/agent-permissions-handler.md +0 -50
  203. package/.opencode/agent/agent-simple-writer.md +0 -45
  204. package/.opencode/agent/agent-svetlana-code-review.md +0 -39
  205. package/.opencode/agent/agent-tanya-test-runner.md +0 -57
  206. package/.opencode/agent/agent-ui-designer.md +0 -56
  207. package/.opencode/agent/agent-viktor-sql-insights.md +0 -34
  208. package/.opencode/agent/agent-web-search.md +0 -42
  209. package/.opencode/agent/agent-yevgeni-discussions.md +0 -37
  210. package/.opencode/agent/agent-zara-zapier.md +0 -53
  211. package/.opencode/commands/app-squad.md +0 -135
  212. package/.opencode/commands/audit-squad.md +0 -158
  213. package/.opencode/commands/autoplan.md +0 -563
  214. package/.opencode/commands/cleanup-squad.md +0 -98
  215. package/.opencode/commands/config-squad.md +0 -106
  216. package/.opencode/commands/crud-squad.md +0 -87
  217. package/.opencode/commands/data-squad.md +0 -97
  218. package/.opencode/commands/debug-squad.md +0 -303
  219. package/.opencode/commands/doc-squad.md +0 -65
  220. package/.opencode/commands/handoff.md +0 -137
  221. package/.opencode/commands/health.md +0 -49
  222. package/.opencode/commands/help-agents.md +0 -151
  223. package/.opencode/commands/help-commands.md +0 -32
  224. package/.opencode/commands/help-faq.md +0 -29
  225. package/.opencode/commands/help-plugins.md +0 -28
  226. package/.opencode/commands/help-skills.md +0 -7
  227. package/.opencode/commands/help-tools.md +0 -40
  228. package/.opencode/commands/help.md +0 -28
  229. package/.opencode/commands/hotfix-squad.md +0 -112
  230. package/.opencode/commands/integration-squad.md +0 -82
  231. package/.opencode/commands/janitor-squad.md +0 -167
  232. package/.opencode/commands/learn-auto.md +0 -120
  233. package/.opencode/commands/learn.md +0 -120
  234. package/.opencode/commands/mcp-list.md +0 -27
  235. package/.opencode/commands/onboard-squad.md +0 -140
  236. package/.opencode/commands/plan-workspace.md +0 -732
  237. package/.opencode/commands/prd.md +0 -131
  238. package/.opencode/commands/project-status.md +0 -82
  239. package/.opencode/commands/publish.md +0 -138
  240. package/.opencode/commands/recap.md +0 -69
  241. package/.opencode/commands/restore.md +0 -64
  242. package/.opencode/commands/review-squad.md +0 -152
  243. package/.opencode/commands/save.md +0 -24
  244. package/.opencode/commands/stats.md +0 -19
  245. package/.opencode/commands/swarm.md +0 -210
  246. package/.opencode/commands/tool-builder.md +0 -39
  247. package/.opencode/commands/ws-pull.md +0 -44
  248. package/.opencode/opencode.json +0 -21
  249. package/inbox/failures.log +0 -1
  250. package/inbox/usage.jsonl +0 -4
  251. package/scripts/postinstall.cjs +0 -64
  252. package/scripts/test-hal-tools.ts +0 -154
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.subscribeToSignal = exports.clearAllConnections = exports.disconnectHailerClientByApiKey = exports.createHailerClientByApiKey = exports.HailerClientManager = void 0;
3
+ exports.subscribeToSignal = exports.clearAllConnections = exports.disconnectHailerClientByApiKey = exports.createHailerClientByApiKey = exports.HailerClientManager = exports.RestOnlySocket = void 0;
4
4
  exports.getCurrentUserId = getCurrentUserId;
5
5
  exports.registerBotCredentials = registerBotCredentials;
6
6
  exports.unregisterBotCredentials = unregisterBotCredentials;
@@ -9,6 +9,70 @@ const auth_1 = require("./auth");
9
9
  const logger_1 = require("../lib/logger");
10
10
  const config_1 = require("../config");
11
11
  const logger = (0, logger_1.createLogger)({ component: 'hailer-clients' });
12
+ // Default API base URL for OAuth sessions (User API Keys)
13
+ const DEFAULT_API_BASE_URL = process.env.BOT_API_BASE_URL || 'https://api.hailer.com';
14
+ /**
15
+ * REST-only socket mock for User API Key sessions
16
+ * User API Keys require IP for validation, which socket.resume() doesn't provide.
17
+ * This mock implements the socket.request() interface using REST API calls.
18
+ */
19
+ class RestOnlySocket {
20
+ host;
21
+ apiKey;
22
+ constructor(baseUrl, apiKey) {
23
+ this.host = baseUrl;
24
+ this.apiKey = apiKey;
25
+ }
26
+ /**
27
+ * Make RPC request via REST API instead of socket
28
+ * Hailer's /api endpoint accepts the same RPC format as socket
29
+ */
30
+ async request(method, args = []) {
31
+ const url = `${this.host}/api/${method.replace(/\./g, '/')}`;
32
+ logger.debug('RestOnlySocket making request', {
33
+ url,
34
+ method,
35
+ apiKeyPrefix: this.apiKey.substring(0, 20) + '...',
36
+ });
37
+ const response = await (0, auth_1.safeFetch)(url, {
38
+ method: 'POST',
39
+ headers: {
40
+ 'Content-Type': 'application/json',
41
+ 'hlrkey': this.apiKey,
42
+ },
43
+ body: JSON.stringify({ args }),
44
+ });
45
+ if (!response.ok) {
46
+ const errorText = await response.text();
47
+ logger.error('RestOnlySocket request failed', {
48
+ url,
49
+ method,
50
+ status: response.status,
51
+ errorText,
52
+ });
53
+ throw new Error(`REST API request failed: ${response.status} ${errorText}`);
54
+ }
55
+ const result = await response.json();
56
+ logger.debug('RestOnlySocket request succeeded', { method });
57
+ return result;
58
+ }
59
+ // Stub methods for socket interface compatibility
60
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
+ on(_event, _handler) {
62
+ // REST-only client doesn't support real-time events
63
+ logger.debug('RestOnlySocket.on() called - events not supported for User API Key sessions');
64
+ }
65
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
+ off(_event, _handler) {
67
+ // REST-only client doesn't support real-time events
68
+ logger.debug('RestOnlySocket.off() called - events not supported for User API Key sessions');
69
+ }
70
+ disconnect() {
71
+ // Nothing to disconnect for REST-only client
72
+ logger.debug('RestOnlySocket.disconnect() called - no-op for REST client');
73
+ }
74
+ }
75
+ exports.RestOnlySocket = RestOnlySocket;
12
76
  /**
13
77
  * Get the current user ID from the authenticated session
14
78
  * This is called after authentication to retrieve the user ID automatically
@@ -91,24 +155,23 @@ class HailerClientManager {
91
155
  password: this.password,
92
156
  ...(isLocalDev && { rejectUnauthorized: false }), // Add for local dev with self-signed certs
93
157
  };
94
- // Track timeout with single settled flag to prevent race conditions
158
+ // Track timeout for proper cleanup
95
159
  let timeoutId = null;
96
- let settled = false;
160
+ let timeoutCleared = false;
97
161
  try {
98
162
  // Create socket client using @hailer/cli with cancellable timeout
99
163
  this.socketClient = (await Promise.race([
100
164
  cli_1.Client.create(clientOptions).then(client => {
101
- if (!settled) {
102
- settled = true;
103
- if (timeoutId)
104
- clearTimeout(timeoutId);
165
+ // Clear timeout on success
166
+ if (timeoutId && !timeoutCleared) {
167
+ clearTimeout(timeoutId);
168
+ timeoutCleared = true;
105
169
  }
106
170
  return client;
107
171
  }),
108
172
  new Promise((_, reject) => {
109
173
  timeoutId = setTimeout(() => {
110
- if (!settled) {
111
- settled = true;
174
+ if (!timeoutCleared) {
112
175
  reject(new Error(`Timeout connecting to: ${this.host}`));
113
176
  }
114
177
  }, 30000);
@@ -116,10 +179,10 @@ class HailerClientManager {
116
179
  ]));
117
180
  }
118
181
  catch (error) {
119
- // Ensure timeout is cleared on error
120
- if (timeoutId && !settled) {
121
- settled = true;
182
+ // Ensure timeout is cleared on error too
183
+ if (timeoutId && !timeoutCleared) {
122
184
  clearTimeout(timeoutId);
185
+ timeoutCleared = true;
123
186
  }
124
187
  logger.error('Failed to create socket client', error, { username: this.username });
125
188
  throw error;
@@ -163,13 +226,13 @@ class HailerClientManager {
163
226
  logger.info('Socket reconnected and session resumed', { username: this.username });
164
227
  });
165
228
  this.socketClient.on("connect", () => {
166
- logger.debug('Socket connected', { username: this.username });
229
+ logger.info('Socket connected', { username: this.username });
167
230
  });
168
231
  this.socketClient.on("connect_error", (error) => {
169
232
  logger.error('Socket connection error', { error: error.message, username: this.username });
170
233
  });
171
234
  this.socketClient.on("reconnect_attempt", (attempt) => {
172
- logger.debug('Socket reconnection attempt', { attempt, username: this.username });
235
+ logger.info('Socket reconnection attempt', { attempt, username: this.username });
173
236
  });
174
237
  this.socketClient.on("reconnect_failed", () => {
175
238
  logger.error('Socket reconnection failed permanently', { username: this.username });
@@ -244,12 +307,86 @@ class HailerClientManager {
244
307
  exports.HailerClientManager = HailerClientManager;
245
308
  // Connection pool for the MCP server
246
309
  const connectionPool = new Map();
310
+ // Pool for User API Key socket clients
311
+ const userApiKeyClients = new Map();
312
+ /**
313
+ * Create a REST-only client for User API Keys
314
+ *
315
+ * User API Keys require IP address for validation (see validateApiKey in backend).
316
+ * Socket.IO resume() doesn't pass IP, so we use REST API with hlrkey header instead.
317
+ * The REST API passes req.ip to core.getSession() which then validates the key.
318
+ */
319
+ async function createUserApiKeyClient(userApiKey) {
320
+ // Check if we already have this client
321
+ const existing = userApiKeyClients.get(userApiKey);
322
+ if (existing) {
323
+ logger.debug('Reusing existing User API Key client', {
324
+ apiKey: userApiKey.substring(0, 20) + '...'
325
+ });
326
+ return existing;
327
+ }
328
+ logger.info('Creating REST-only client for User API Key', {
329
+ apiKey: userApiKey.substring(0, 20) + '...',
330
+ apiBaseUrl: DEFAULT_API_BASE_URL
331
+ });
332
+ try {
333
+ // Create REST-only socket mock that uses HTTP API instead of WebSocket
334
+ const restSocket = new RestOnlySocket(DEFAULT_API_BASE_URL, userApiKey);
335
+ // Verify the API key works by making a test request
336
+ logger.debug('Verifying User API Key via REST...');
337
+ const testResult = await restSocket.request('v2.core.init', [['user']]);
338
+ if (!testResult?.user?._id) {
339
+ throw new Error('User API Key validation failed - could not get user info');
340
+ }
341
+ logger.info('User API Key validated successfully via REST', {
342
+ apiKey: userApiKey.substring(0, 20) + '...',
343
+ userId: testResult.user._id
344
+ });
345
+ // Create REST client
346
+ const restClient = {
347
+ fetch: (url, options = {}) => {
348
+ const fullUrl = url.startsWith('http') ? url : `${DEFAULT_API_BASE_URL}${url}`;
349
+ const headers = {
350
+ ...options.headers,
351
+ hlrkey: userApiKey,
352
+ 'Content-Type': 'application/json',
353
+ };
354
+ return (0, auth_1.safeFetch)(fullUrl, { ...options, headers });
355
+ },
356
+ sessionKey: userApiKey,
357
+ };
358
+ const client = {
359
+ socket: restSocket,
360
+ rest: restClient,
361
+ sessionKey: userApiKey,
362
+ };
363
+ userApiKeyClients.set(userApiKey, client);
364
+ return client;
365
+ }
366
+ catch (error) {
367
+ const errorMsg = error instanceof Error
368
+ ? error.message
369
+ : (typeof error === 'object' && error !== null)
370
+ ? JSON.stringify(error)
371
+ : String(error);
372
+ logger.error('Failed to create User API Key client', {
373
+ error: errorMsg,
374
+ errorType: typeof error,
375
+ });
376
+ throw new Error(`Failed to create User API Key session: ${errorMsg}`);
377
+ }
378
+ }
247
379
  /**
248
380
  * Create Hailer client by API key (O(1) lookup)
249
381
  * This is the new efficient method for MCP Server to get connections
250
382
  */
251
383
  const createHailerClientByApiKey = async (apiKey) => {
252
- // O(1) lookup in Map-based CLIENT_CONFIGS
384
+ // Check if this is a User API Key (from OAuth flow)
385
+ // User API Keys start with "userapikey_" and can authenticate via socket resume()
386
+ if (apiKey.startsWith('userapikey_')) {
387
+ return await createUserApiKeyClient(apiKey);
388
+ }
389
+ // O(1) lookup in Map-based CLIENT_CONFIGS (for bot accounts)
253
390
  const account = config_1.environment.CLIENT_CONFIGS[apiKey];
254
391
  if (!account) {
255
392
  throw new Error(`No Hailer account found for API key: ${apiKey}`);
@@ -280,7 +417,7 @@ const createHailerClientByApiKey = async (apiKey) => {
280
417
  connectionPool.delete(connectionKey);
281
418
  }
282
419
  // Create new connection
283
- logger.debug('Creating new connection', {
420
+ logger.info('Creating new connection', {
284
421
  apiKey: apiKey.substring(0, 8) + '...',
285
422
  email: (0, config_1.maskEmail)(account.email),
286
423
  host: account.apiBaseUrl
@@ -312,7 +449,7 @@ const clearAllConnections = () => {
312
449
  clientManager.disconnect();
313
450
  }
314
451
  connectionPool.clear();
315
- logger.debug('Cleared Hailer connections', { count });
452
+ logger.info('Cleared Hailer connections', { count });
316
453
  };
317
454
  exports.clearAllConnections = clearAllConnections;
318
455
  /**
@@ -346,10 +483,10 @@ function registerBotCredentials(botId, email, password, options) {
346
483
  config_1.environment.CLIENT_CONFIGS[apiKey] = {
347
484
  email,
348
485
  password,
349
- apiBaseUrl: config_1.environment.BOT_API_BASE_URL || 'https://api.hailer.com',
486
+ apiBaseUrl: 'https://api.hailer.com',
350
487
  ...(options?.allowedGroups && { allowedGroups: options.allowedGroups }),
351
488
  };
352
- logger.debug('Bot credentials registered', {
489
+ logger.info('Bot credentials registered', {
353
490
  botId,
354
491
  hasAllowedGroups: !!options?.allowedGroups
355
492
  });
@@ -361,6 +498,6 @@ function registerBotCredentials(botId, email, password, options) {
361
498
  function unregisterBotCredentials(apiKey) {
362
499
  delete config_1.environment.CLIENT_CONFIGS[apiKey];
363
500
  (0, exports.disconnectHailerClientByApiKey)(apiKey);
364
- logger.debug('Bot credentials unregistered');
501
+ logger.info('Bot credentials unregistered');
365
502
  }
366
503
  //# sourceMappingURL=hailer-clients.js.map
@@ -0,0 +1,68 @@
1
+ /**
2
+ * SessionStore - Maps key_id to real api_key for Claude App authentication
3
+ *
4
+ * Purpose: Keep real Hailer API keys on server side, Claude only gets a reference (key_id).
5
+ * Multiple concurrent sessions supported - each user gets their own key_id.
6
+ *
7
+ * Flow:
8
+ * 1. User logs in via Hailer auth page
9
+ * 2. Frontend generates key_id, sends {key_id, api_key} to MCP /auth/register
10
+ * 3. Frontend redirects to Claude with key_id as "access_token"
11
+ * 4. Claude sends key_id in Authorization header
12
+ * 5. MCP looks up real api_key from this store
13
+ */
14
+ export interface Session {
15
+ apiKey: string;
16
+ workspaceId: string;
17
+ createdAt: number;
18
+ lastAccessedAt: number;
19
+ }
20
+ export declare class SessionStore {
21
+ private store;
22
+ private maxTtlMs;
23
+ private inactivityTtlMs;
24
+ private cleanupInterval;
25
+ /**
26
+ * @param maxTtlHours - Maximum session lifetime in hours (default 24)
27
+ * @param inactivityMinutes - Session expires after this many minutes of inactivity (default 30)
28
+ */
29
+ constructor(maxTtlHours?: number, inactivityMinutes?: number);
30
+ /**
31
+ * Register a new session (called by /auth/register endpoint)
32
+ */
33
+ set(keyId: string, apiKey: string, workspaceId: string): void;
34
+ /**
35
+ * Get session by key_id (returns null if expired or not found)
36
+ * Updates lastAccessedAt to extend inactivity timeout
37
+ */
38
+ get(keyId: string): Session | null;
39
+ /**
40
+ * Delete session (logout)
41
+ */
42
+ delete(keyId: string): boolean;
43
+ /**
44
+ * Remove all expired sessions (both max TTL and inactivity)
45
+ */
46
+ cleanup(): void;
47
+ /**
48
+ * Get store statistics
49
+ */
50
+ getStats(): {
51
+ size: number;
52
+ config: {
53
+ maxTtlHours: number;
54
+ inactivityMinutes: number;
55
+ };
56
+ sessions: Array<{
57
+ keyId: string;
58
+ ageMinutes: number;
59
+ inactivityMinutes: number;
60
+ }>;
61
+ };
62
+ /**
63
+ * Stop cleanup interval (for graceful shutdown)
64
+ */
65
+ destroy(): void;
66
+ }
67
+ export declare const sessionStore: SessionStore;
68
+ //# sourceMappingURL=session-store.d.ts.map
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ /**
3
+ * SessionStore - Maps key_id to real api_key for Claude App authentication
4
+ *
5
+ * Purpose: Keep real Hailer API keys on server side, Claude only gets a reference (key_id).
6
+ * Multiple concurrent sessions supported - each user gets their own key_id.
7
+ *
8
+ * Flow:
9
+ * 1. User logs in via Hailer auth page
10
+ * 2. Frontend generates key_id, sends {key_id, api_key} to MCP /auth/register
11
+ * 3. Frontend redirects to Claude with key_id as "access_token"
12
+ * 4. Claude sends key_id in Authorization header
13
+ * 5. MCP looks up real api_key from this store
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.sessionStore = exports.SessionStore = void 0;
17
+ const logger_1 = require("../lib/logger");
18
+ const logger = (0, logger_1.createLogger)({ component: 'session-store' });
19
+ class SessionStore {
20
+ store = new Map();
21
+ maxTtlMs; // Maximum session lifetime (absolute)
22
+ inactivityTtlMs; // Session expires after inactivity
23
+ cleanupInterval = null;
24
+ /**
25
+ * @param maxTtlHours - Maximum session lifetime in hours (default 24)
26
+ * @param inactivityMinutes - Session expires after this many minutes of inactivity (default 30)
27
+ */
28
+ constructor(maxTtlHours = 24, inactivityMinutes = 30) {
29
+ this.maxTtlMs = maxTtlHours * 60 * 60 * 1000;
30
+ this.inactivityTtlMs = inactivityMinutes * 60 * 1000;
31
+ // Cleanup expired sessions every 5 minutes (more frequent for inactivity-based expiry)
32
+ this.cleanupInterval = setInterval(() => this.cleanup(), 5 * 60 * 1000);
33
+ logger.info('SessionStore initialized', {
34
+ maxTtlHours,
35
+ inactivityMinutes,
36
+ maxTtlMs: this.maxTtlMs,
37
+ inactivityTtlMs: this.inactivityTtlMs
38
+ });
39
+ }
40
+ /**
41
+ * Register a new session (called by /auth/register endpoint)
42
+ */
43
+ set(keyId, apiKey, workspaceId) {
44
+ const now = Date.now();
45
+ const session = {
46
+ apiKey,
47
+ workspaceId,
48
+ createdAt: now,
49
+ lastAccessedAt: now,
50
+ };
51
+ this.store.set(keyId, session);
52
+ logger.info('Session registered', {
53
+ keyId: keyId.substring(0, 8) + '...',
54
+ workspaceId: workspaceId.substring(0, 8) + '...',
55
+ totalSessions: this.store.size,
56
+ });
57
+ }
58
+ /**
59
+ * Get session by key_id (returns null if expired or not found)
60
+ * Updates lastAccessedAt to extend inactivity timeout
61
+ */
62
+ get(keyId) {
63
+ const session = this.store.get(keyId);
64
+ if (!session) {
65
+ logger.debug('Session not found', { keyId: keyId.substring(0, 8) + '...' });
66
+ return null;
67
+ }
68
+ const now = Date.now();
69
+ const age = now - session.createdAt;
70
+ const inactivity = now - session.lastAccessedAt;
71
+ // Check if max lifetime exceeded (absolute expiry)
72
+ if (age > this.maxTtlMs) {
73
+ logger.info('Session expired (max TTL)', {
74
+ keyId: keyId.substring(0, 8) + '...',
75
+ ageHours: Math.round(age / (1000 * 60 * 60)),
76
+ });
77
+ this.store.delete(keyId);
78
+ return null;
79
+ }
80
+ // Check if inactivity timeout exceeded
81
+ if (inactivity > this.inactivityTtlMs) {
82
+ logger.info('Session expired (inactivity)', {
83
+ keyId: keyId.substring(0, 8) + '...',
84
+ inactivityMinutes: Math.round(inactivity / (1000 * 60)),
85
+ });
86
+ this.store.delete(keyId);
87
+ return null;
88
+ }
89
+ // Update lastAccessedAt to extend session
90
+ session.lastAccessedAt = now;
91
+ logger.debug('Session found', {
92
+ keyId: keyId.substring(0, 8) + '...',
93
+ ageMinutes: Math.round(age / (1000 * 60)),
94
+ inactivityMinutes: Math.round(inactivity / (1000 * 60)),
95
+ });
96
+ return session;
97
+ }
98
+ /**
99
+ * Delete session (logout)
100
+ */
101
+ delete(keyId) {
102
+ const existed = this.store.delete(keyId);
103
+ if (existed) {
104
+ logger.info('Session deleted', { keyId: keyId.substring(0, 8) + '...' });
105
+ }
106
+ return existed;
107
+ }
108
+ /**
109
+ * Remove all expired sessions (both max TTL and inactivity)
110
+ */
111
+ cleanup() {
112
+ const now = Date.now();
113
+ let removedMaxTtl = 0;
114
+ let removedInactivity = 0;
115
+ for (const [keyId, session] of this.store.entries()) {
116
+ const age = now - session.createdAt;
117
+ const inactivity = now - session.lastAccessedAt;
118
+ if (age > this.maxTtlMs) {
119
+ this.store.delete(keyId);
120
+ removedMaxTtl++;
121
+ }
122
+ else if (inactivity > this.inactivityTtlMs) {
123
+ this.store.delete(keyId);
124
+ removedInactivity++;
125
+ }
126
+ }
127
+ const totalRemoved = removedMaxTtl + removedInactivity;
128
+ if (totalRemoved > 0) {
129
+ logger.info('Cleanup completed', {
130
+ removedMaxTtl,
131
+ removedInactivity,
132
+ remaining: this.store.size
133
+ });
134
+ }
135
+ }
136
+ /**
137
+ * Get store statistics
138
+ */
139
+ getStats() {
140
+ const now = Date.now();
141
+ const sessions = Array.from(this.store.entries()).map(([keyId, session]) => ({
142
+ keyId: keyId.substring(0, 8) + '...',
143
+ ageMinutes: Math.round((now - session.createdAt) / (1000 * 60)),
144
+ inactivityMinutes: Math.round((now - session.lastAccessedAt) / (1000 * 60)),
145
+ }));
146
+ return {
147
+ size: this.store.size,
148
+ config: {
149
+ maxTtlHours: Math.round(this.maxTtlMs / (1000 * 60 * 60)),
150
+ inactivityMinutes: Math.round(this.inactivityTtlMs / (1000 * 60)),
151
+ },
152
+ sessions,
153
+ };
154
+ }
155
+ /**
156
+ * Stop cleanup interval (for graceful shutdown)
157
+ */
158
+ destroy() {
159
+ if (this.cleanupInterval) {
160
+ clearInterval(this.cleanupInterval);
161
+ this.cleanupInterval = null;
162
+ }
163
+ logger.info('SessionStore destroyed');
164
+ }
165
+ }
166
+ exports.SessionStore = SessionStore;
167
+ // Singleton instance
168
+ exports.sessionStore = new SessionStore();
169
+ //# sourceMappingURL=session-store.js.map
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SignalHandler = void 0;
4
4
  const logger_1 = require("../lib/logger");
5
+ const webhooks_1 = require("../bot-config/webhooks");
6
+ const types_1 = require("./utils/types");
5
7
  // Optional: bot-config only available in bot server mode, not MCP terminal
6
8
  let reloadConfigFromHailer = null;
7
9
  let setActiveWorkspace = null;
@@ -169,18 +171,15 @@ class SignalHandler {
169
171
  prevPhase: data.prevPhase || data.meta?.prevPhase
170
172
  });
171
173
  // Handle Agent Directory phase changes for bot enable/disable
172
- // DISABLED: Now using webhook instead of socket signals for Agent Directory updates
173
- // The webhook handler (src/mcp/webhook-handler.ts) handles bot config updates
174
- // Keeping this code for potential fallback if webhook is unavailable
175
- // if (processId && phase && activityIdRaw) {
176
- // const activityIds = Array.isArray(activityIdRaw)
177
- // ? activityIdRaw
178
- // : [activityIdRaw];
179
- //
180
- // handleActivityPhaseChange(processId, activityIds, phase).catch(err => {
181
- // logger.error('Failed to handle activity phase change', err);
182
- // });
183
- // }
174
+ // Socket signals as primary path, webhook as backup
175
+ if (processId && phase && activityIdRaw) {
176
+ const activityIds = Array.isArray(activityIdRaw)
177
+ ? activityIdRaw
178
+ : [activityIdRaw];
179
+ (0, webhooks_1.handleActivityPhaseChange)(processId, activityIds, phase).catch(err => {
180
+ logger.error('Failed to handle activity phase change', err);
181
+ });
182
+ }
184
183
  }
185
184
  handleActivityCreated(signal) {
186
185
  const data = signal.data;
@@ -241,6 +240,7 @@ class SignalHandler {
241
240
  // Refresh cache by fetching fresh data - API requires array of keys to fetch
242
241
  // IMPORTANT: Include 'processes' to get updated workflow list (e.g., after template install)
243
242
  const init = await this.client.socket.request('v2.core.init', [['users', 'network', 'networks', 'processes']]);
243
+ (0, types_1.normalizeInitProcesses)(init);
244
244
  // Update workspace cache with fresh data
245
245
  if (init.processes) {
246
246
  this.workspaceCache.rawInit.processes = init.processes;
@@ -10,7 +10,6 @@
10
10
  * - Enables Claude's strict mode validation with actual workspace values
11
11
  */
12
12
  import { z } from 'zod';
13
- import { UserContext } from './UserContextCache';
14
13
  import { McpResponse } from './utils/types';
15
14
  /**
16
15
  * Tool groups for access control
@@ -27,6 +26,12 @@ export declare enum ToolGroup {
27
26
  NUCLEAR = "nuclear",
28
27
  BOT_INTERNAL = "bot_internal"
29
28
  }
29
+ /**
30
+ * Context type for tool execution
31
+ * - 'hailer': Requires Hailer authentication via UserContextCache (default)
32
+ * - 'none': No authentication needed (e.g., Weaviate tools with their own auth)
33
+ */
34
+ export type ToolContextType = 'hailer' | 'none';
30
35
  /**
31
36
  * Tool definition interface
32
37
  */
@@ -35,7 +40,11 @@ export interface Tool<TSchema extends z.ZodType = z.ZodType> {
35
40
  group: ToolGroup;
36
41
  description: string;
37
42
  schema: TSchema;
38
- execute: (args: z.infer<TSchema>, context: UserContext) => Promise<McpResponse>;
43
+ /** What authentication context this tool needs. Defaults to 'hailer'. */
44
+ contextType?: ToolContextType;
45
+ /** Context is UserContext for Hailer tools, VipunenContext for contextType:'none' tools.
46
+ * Type safety is enforced at the mcp-server dispatch boundary, not here. */
47
+ execute: (args: z.infer<TSchema>, context: any) => Promise<McpResponse>;
39
48
  }
40
49
  /**
41
50
  * Tool definition for MCP protocol (JSON-RPC)
@@ -56,10 +65,10 @@ export interface ToolDefinition {
56
65
  */
57
66
  export declare class ToolRegistry {
58
67
  private tools;
68
+ private _lazyGuard?;
59
69
  private enableNuclearTools;
60
70
  private workspaceSchemas;
61
71
  private workspaceSchemasLoaded;
62
- private static readonly TOOL_TO_SCHEMA_MAP;
63
72
  constructor(options?: {
64
73
  enableNuclearTools?: boolean;
65
74
  });
@@ -80,6 +89,10 @@ export declare class ToolRegistry {
80
89
  * Add a tool to the registry
81
90
  */
82
91
  addTool(tool: Tool): void;
92
+ /**
93
+ * Get tool definitions filtered by contextType — single-pass, no secondary lookups
94
+ */
95
+ getToolDefinitionsByContextType(contextType: ToolContextType): ToolDefinition[];
83
96
  /**
84
97
  * Get tool definitions with optional filtering
85
98
  *
@@ -108,7 +121,7 @@ export declare class ToolRegistry {
108
121
  /**
109
122
  * Execute tool with validation
110
123
  */
111
- executeTool(name: string, args: any, context: UserContext): Promise<any>;
124
+ executeTool(name: string, args: any, context: any): Promise<any>;
112
125
  /**
113
126
  * Pre-transform install_workflow args to fix common LLM mistakes BEFORE validation
114
127
  */