@hailer/mcp 1.1.12 → 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 (271) hide show
  1. package/CHANGELOG.md +0 -7
  2. package/{.claude → dist}/CLAUDE.md +2 -2
  3. package/dist/app.js +18 -5
  4. package/dist/bot/bot-config.d.ts +10 -1
  5. package/dist/bot/bot-config.js +64 -3
  6. package/dist/bot/bot-manager.d.ts +2 -0
  7. package/dist/bot/bot-manager.js +9 -2
  8. package/dist/bot/bot.d.ts +33 -0
  9. package/dist/bot/bot.js +461 -160
  10. package/dist/bot/services/message-classifier.js +17 -0
  11. package/dist/bot/services/permission-guard.d.ts +52 -0
  12. package/dist/bot/services/permission-guard.js +149 -0
  13. package/dist/bot/services/types.d.ts +5 -0
  14. package/dist/bot/services/typing-indicator.d.ts +6 -1
  15. package/dist/bot/services/typing-indicator.js +19 -3
  16. package/dist/cli.js +0 -0
  17. package/dist/config.d.ts +6 -1
  18. package/dist/config.js +43 -0
  19. package/dist/core.js +3 -6
  20. package/dist/lib/discussion-lock.d.ts +42 -0
  21. package/dist/lib/discussion-lock.js +110 -0
  22. package/dist/mcp/UserContextCache.d.ts +5 -0
  23. package/dist/mcp/UserContextCache.js +51 -19
  24. package/dist/mcp/hailer-clients.d.ts +19 -1
  25. package/dist/mcp/hailer-clients.js +158 -24
  26. package/dist/mcp/session-store.d.ts +68 -0
  27. package/dist/mcp/session-store.js +169 -0
  28. package/dist/mcp/signal-handler.js +2 -0
  29. package/dist/mcp/tool-registry.d.ts +17 -4
  30. package/dist/mcp/tool-registry.js +37 -7
  31. package/dist/mcp/tools/activity.js +99 -7
  32. package/dist/mcp/tools/app-scaffold.js +304 -336
  33. package/dist/mcp/tools/bot-config/constants.d.ts +23 -0
  34. package/dist/mcp/tools/bot-config/constants.js +94 -0
  35. package/dist/mcp/tools/bot-config/core.d.ts +253 -0
  36. package/dist/mcp/tools/bot-config/core.js +2456 -0
  37. package/dist/mcp/tools/bot-config/index.d.ts +10 -0
  38. package/dist/mcp/tools/bot-config/index.js +59 -0
  39. package/dist/mcp/tools/bot-config/tools.d.ts +7 -0
  40. package/dist/mcp/tools/bot-config/tools.js +15 -0
  41. package/dist/mcp/tools/bot-config/types.d.ts +50 -0
  42. package/dist/mcp/tools/bot-config/types.js +6 -0
  43. package/dist/mcp/tools/bug-fixer-tools.d.ts +45 -0
  44. package/dist/mcp/tools/bug-fixer-tools.js +1096 -0
  45. package/dist/mcp/tools/company.d.ts +9 -0
  46. package/dist/mcp/tools/company.js +88 -0
  47. package/dist/mcp/tools/discussion.js +68 -0
  48. package/dist/mcp/tools/document.d.ts +11 -0
  49. package/dist/mcp/tools/document.js +741 -0
  50. package/dist/mcp/tools/investigate.d.ts +9 -0
  51. package/dist/mcp/tools/investigate.js +254 -0
  52. package/dist/mcp/tools/workflow-permissions.d.ts +15 -0
  53. package/dist/mcp/tools/workflow-permissions.js +204 -0
  54. package/dist/mcp/tools/workflow.js +57 -18
  55. package/dist/mcp/utils/index.d.ts +2 -0
  56. package/dist/mcp/utils/index.js +12 -1
  57. package/dist/mcp/utils/role-utils.d.ts +74 -0
  58. package/dist/mcp/utils/role-utils.js +151 -0
  59. package/dist/mcp/utils/types.d.ts +43 -1
  60. package/dist/mcp/utils/types.js +14 -0
  61. package/dist/mcp/webhook-handler.d.ts +4 -0
  62. package/dist/mcp/webhook-handler.js +8 -0
  63. package/dist/mcp-server.d.ts +23 -2
  64. package/dist/mcp-server.js +639 -127
  65. package/dist/plugins/vipunen/client.d.ts +150 -0
  66. package/dist/plugins/vipunen/client.js +535 -0
  67. package/dist/plugins/vipunen/config/schema-config.json +19 -0
  68. package/dist/plugins/vipunen/config/schema-doc.json +22 -0
  69. package/dist/plugins/vipunen/index.d.ts +41 -0
  70. package/dist/plugins/vipunen/index.js +88 -0
  71. package/dist/plugins/vipunen/tools.d.ts +26 -0
  72. package/dist/plugins/vipunen/tools.js +501 -0
  73. package/dist/stdio-server.d.ts +14 -0
  74. package/dist/stdio-server.js +101 -0
  75. package/package.json +2 -1
  76. package/.claude/agents/agent-ada-skill-builder.md +0 -94
  77. package/.claude/agents/agent-alejandro-function-fields.md +0 -342
  78. package/.claude/agents/agent-bjorn-config-audit.md +0 -103
  79. package/.claude/agents/agent-builder-agent-creator.md +0 -130
  80. package/.claude/agents/agent-code-simplifier.md +0 -53
  81. package/.claude/agents/agent-dmitri-activity-crud.md +0 -159
  82. package/.claude/agents/agent-giuseppe-app-builder.md +0 -247
  83. package/.claude/agents/agent-gunther-mcp-tools.md +0 -39
  84. package/.claude/agents/agent-helga-workflow-config.md +0 -204
  85. package/.claude/agents/agent-igor-activity-mover-automation.md +0 -125
  86. package/.claude/agents/agent-ingrid-doc-templates.md +0 -261
  87. package/.claude/agents/agent-ivan-monolith.md +0 -154
  88. package/.claude/agents/agent-kenji-data-reader.md +0 -86
  89. package/.claude/agents/agent-lars-code-inspector.md +0 -102
  90. package/.claude/agents/agent-marco-mockup-builder.md +0 -110
  91. package/.claude/agents/agent-marcus-api-documenter.md +0 -323
  92. package/.claude/agents/agent-marketplace-publisher.md +0 -280
  93. package/.claude/agents/agent-marketplace-reviewer.md +0 -309
  94. package/.claude/agents/agent-permissions-handler.md +0 -208
  95. package/.claude/agents/agent-simple-writer.md +0 -48
  96. package/.claude/agents/agent-svetlana-code-review.md +0 -171
  97. package/.claude/agents/agent-tanya-test-runner.md +0 -333
  98. package/.claude/agents/agent-ui-designer.md +0 -100
  99. package/.claude/agents/agent-viktor-sql-insights.md +0 -212
  100. package/.claude/agents/agent-web-search.md +0 -55
  101. package/.claude/agents/agent-yevgeni-discussions.md +0 -45
  102. package/.claude/agents/agent-zara-zapier.md +0 -159
  103. package/.claude/commands/app-squad.md +0 -135
  104. package/.claude/commands/audit-squad.md +0 -158
  105. package/.claude/commands/autoplan.md +0 -563
  106. package/.claude/commands/cleanup-squad.md +0 -98
  107. package/.claude/commands/config-squad.md +0 -106
  108. package/.claude/commands/crud-squad.md +0 -87
  109. package/.claude/commands/data-squad.md +0 -97
  110. package/.claude/commands/debug-squad.md +0 -303
  111. package/.claude/commands/doc-squad.md +0 -65
  112. package/.claude/commands/handoff.md +0 -137
  113. package/.claude/commands/health.md +0 -49
  114. package/.claude/commands/help.md +0 -29
  115. package/.claude/commands/help:agents.md +0 -151
  116. package/.claude/commands/help:commands.md +0 -78
  117. package/.claude/commands/help:faq.md +0 -79
  118. package/.claude/commands/help:plugins.md +0 -50
  119. package/.claude/commands/help:skills.md +0 -93
  120. package/.claude/commands/help:tools.md +0 -75
  121. package/.claude/commands/hotfix-squad.md +0 -112
  122. package/.claude/commands/integration-squad.md +0 -82
  123. package/.claude/commands/janitor-squad.md +0 -167
  124. package/.claude/commands/learn-auto.md +0 -120
  125. package/.claude/commands/learn.md +0 -120
  126. package/.claude/commands/mcp-list.md +0 -27
  127. package/.claude/commands/onboard-squad.md +0 -140
  128. package/.claude/commands/plan-workspace.md +0 -732
  129. package/.claude/commands/prd.md +0 -130
  130. package/.claude/commands/project-status.md +0 -82
  131. package/.claude/commands/publish.md +0 -138
  132. package/.claude/commands/recap.md +0 -69
  133. package/.claude/commands/restore.md +0 -64
  134. package/.claude/commands/review-squad.md +0 -152
  135. package/.claude/commands/save.md +0 -24
  136. package/.claude/commands/stats.md +0 -19
  137. package/.claude/commands/swarm.md +0 -210
  138. package/.claude/commands/tool-builder.md +0 -39
  139. package/.claude/commands/ws-pull.md +0 -44
  140. package/.claude/hooks/_shared-memory.cjs +0 -305
  141. package/.claude/hooks/_utils.cjs +0 -108
  142. package/.claude/hooks/agent-failure-detector.cjs +0 -383
  143. package/.claude/hooks/agent-usage-logger.cjs +0 -204
  144. package/.claude/hooks/app-edit-guard.cjs +0 -494
  145. package/.claude/hooks/auto-learn.cjs +0 -304
  146. package/.claude/hooks/bash-guard.cjs +0 -272
  147. package/.claude/hooks/builder-mode-manager.cjs +0 -354
  148. package/.claude/hooks/bulk-activity-guard.cjs +0 -271
  149. package/.claude/hooks/context-watchdog.cjs +0 -230
  150. package/.claude/hooks/delegation-reminder.cjs +0 -465
  151. package/.claude/hooks/design-system-lint.cjs +0 -271
  152. package/.claude/hooks/post-scaffold-hook.cjs +0 -181
  153. package/.claude/hooks/prompt-guard.cjs +0 -354
  154. package/.claude/hooks/publish-template-guard.cjs +0 -147
  155. package/.claude/hooks/session-start.cjs +0 -35
  156. package/.claude/hooks/shared-memory-writer.cjs +0 -147
  157. package/.claude/hooks/skill-injector.cjs +0 -140
  158. package/.claude/hooks/skill-usage-logger.cjs +0 -258
  159. package/.claude/hooks/src-edit-guard.cjs +0 -240
  160. package/.claude/hooks/sync-marketplace-agents.cjs +0 -346
  161. package/.claude/settings.json +0 -257
  162. package/.claude/skills/SDK-activity-patterns/SKILL.md +0 -428
  163. package/.claude/skills/SDK-document-templates/SKILL.md +0 -1033
  164. package/.claude/skills/SDK-function-fields/SKILL.md +0 -542
  165. package/.claude/skills/SDK-generate-skill/SKILL.md +0 -92
  166. package/.claude/skills/SDK-init-skill/SKILL.md +0 -127
  167. package/.claude/skills/SDK-insight-queries/SKILL.md +0 -787
  168. package/.claude/skills/SDK-ws-config-skill/SKILL.md +0 -1139
  169. package/.claude/skills/agent-structure/SKILL.md +0 -98
  170. package/.claude/skills/api-documentation-patterns/SKILL.md +0 -474
  171. package/.claude/skills/chrome-mcp-reference/SKILL.md +0 -370
  172. package/.claude/skills/delegation-routing/SKILL.md +0 -202
  173. package/.claude/skills/frontend-design/SKILL.md +0 -254
  174. package/.claude/skills/hailer-activity-mover/SKILL.md +0 -213
  175. package/.claude/skills/hailer-api-client/SKILL.md +0 -518
  176. package/.claude/skills/hailer-app-builder/SKILL.md +0 -1434
  177. package/.claude/skills/hailer-apps-pictures/SKILL.md +0 -269
  178. package/.claude/skills/hailer-design-system/SKILL.md +0 -235
  179. package/.claude/skills/hailer-monolith-automations/SKILL.md +0 -686
  180. package/.claude/skills/hailer-permissions-system/SKILL.md +0 -121
  181. package/.claude/skills/hailer-project-protocol/SKILL.md +0 -488
  182. package/.claude/skills/hailer-rest-api/SKILL.md +0 -61
  183. package/.claude/skills/hailer-rest-api/hailer-activities.md +0 -184
  184. package/.claude/skills/hailer-rest-api/hailer-admin.md +0 -473
  185. package/.claude/skills/hailer-rest-api/hailer-calendar.md +0 -256
  186. package/.claude/skills/hailer-rest-api/hailer-feed.md +0 -249
  187. package/.claude/skills/hailer-rest-api/hailer-insights.md +0 -195
  188. package/.claude/skills/hailer-rest-api/hailer-messaging.md +0 -276
  189. package/.claude/skills/hailer-rest-api/hailer-workflows.md +0 -283
  190. package/.claude/skills/insight-join-patterns/SKILL.md +0 -174
  191. package/.claude/skills/integration-patterns/SKILL.md +0 -421
  192. package/.claude/skills/json-only-output/SKILL.md +0 -72
  193. package/.claude/skills/lsp-setup/SKILL.md +0 -160
  194. package/.claude/skills/mcp-direct-tools/SKILL.md +0 -153
  195. package/.claude/skills/optional-parameters/SKILL.md +0 -72
  196. package/.claude/skills/publish-hailer-app/SKILL.md +0 -244
  197. package/.claude/skills/testing-patterns/SKILL.md +0 -630
  198. package/.claude/skills/tool-builder/SKILL.md +0 -250
  199. package/.claude/skills/tool-parameter-usage/SKILL.md +0 -126
  200. package/.claude/skills/tool-response-verification/SKILL.md +0 -92
  201. package/.claude/skills/zapier-hailer-patterns/SKILL.md +0 -581
  202. package/.mcp.json +0 -13
  203. package/.opencode/agent/agent-ada-skill-builder.md +0 -35
  204. package/.opencode/agent/agent-alejandro-function-fields.md +0 -39
  205. package/.opencode/agent/agent-bjorn-config-audit.md +0 -36
  206. package/.opencode/agent/agent-builder-agent-creator.md +0 -39
  207. package/.opencode/agent/agent-code-simplifier.md +0 -31
  208. package/.opencode/agent/agent-dmitri-activity-crud.md +0 -40
  209. package/.opencode/agent/agent-giuseppe-app-builder.md +0 -37
  210. package/.opencode/agent/agent-gunther-mcp-tools.md +0 -39
  211. package/.opencode/agent/agent-helga-workflow-config.md +0 -203
  212. package/.opencode/agent/agent-igor-activity-mover-automation.md +0 -46
  213. package/.opencode/agent/agent-ingrid-doc-templates.md +0 -39
  214. package/.opencode/agent/agent-ivan-monolith.md +0 -46
  215. package/.opencode/agent/agent-kenji-data-reader.md +0 -53
  216. package/.opencode/agent/agent-lars-code-inspector.md +0 -28
  217. package/.opencode/agent/agent-marco-mockup-builder.md +0 -42
  218. package/.opencode/agent/agent-marcus-api-documenter.md +0 -53
  219. package/.opencode/agent/agent-marketplace-publisher.md +0 -44
  220. package/.opencode/agent/agent-marketplace-reviewer.md +0 -42
  221. package/.opencode/agent/agent-permissions-handler.md +0 -50
  222. package/.opencode/agent/agent-simple-writer.md +0 -45
  223. package/.opencode/agent/agent-svetlana-code-review.md +0 -39
  224. package/.opencode/agent/agent-tanya-test-runner.md +0 -57
  225. package/.opencode/agent/agent-ui-designer.md +0 -56
  226. package/.opencode/agent/agent-viktor-sql-insights.md +0 -34
  227. package/.opencode/agent/agent-web-search.md +0 -42
  228. package/.opencode/agent/agent-yevgeni-discussions.md +0 -37
  229. package/.opencode/agent/agent-zara-zapier.md +0 -53
  230. package/.opencode/commands/app-squad.md +0 -135
  231. package/.opencode/commands/audit-squad.md +0 -158
  232. package/.opencode/commands/autoplan.md +0 -563
  233. package/.opencode/commands/cleanup-squad.md +0 -98
  234. package/.opencode/commands/config-squad.md +0 -106
  235. package/.opencode/commands/crud-squad.md +0 -87
  236. package/.opencode/commands/data-squad.md +0 -97
  237. package/.opencode/commands/debug-squad.md +0 -303
  238. package/.opencode/commands/doc-squad.md +0 -65
  239. package/.opencode/commands/handoff.md +0 -137
  240. package/.opencode/commands/health.md +0 -49
  241. package/.opencode/commands/help-agents.md +0 -151
  242. package/.opencode/commands/help-commands.md +0 -32
  243. package/.opencode/commands/help-faq.md +0 -29
  244. package/.opencode/commands/help-plugins.md +0 -28
  245. package/.opencode/commands/help-skills.md +0 -7
  246. package/.opencode/commands/help-tools.md +0 -40
  247. package/.opencode/commands/help.md +0 -28
  248. package/.opencode/commands/hotfix-squad.md +0 -112
  249. package/.opencode/commands/integration-squad.md +0 -82
  250. package/.opencode/commands/janitor-squad.md +0 -167
  251. package/.opencode/commands/learn-auto.md +0 -120
  252. package/.opencode/commands/learn.md +0 -120
  253. package/.opencode/commands/mcp-list.md +0 -27
  254. package/.opencode/commands/onboard-squad.md +0 -140
  255. package/.opencode/commands/plan-workspace.md +0 -732
  256. package/.opencode/commands/prd.md +0 -131
  257. package/.opencode/commands/project-status.md +0 -82
  258. package/.opencode/commands/publish.md +0 -138
  259. package/.opencode/commands/recap.md +0 -69
  260. package/.opencode/commands/restore.md +0 -64
  261. package/.opencode/commands/review-squad.md +0 -152
  262. package/.opencode/commands/save.md +0 -24
  263. package/.opencode/commands/stats.md +0 -19
  264. package/.opencode/commands/swarm.md +0 -210
  265. package/.opencode/commands/tool-builder.md +0 -39
  266. package/.opencode/commands/ws-pull.md +0 -44
  267. package/.opencode/opencode.json +0 -28
  268. package/SESSION-HANDOFF.md +0 -68
  269. package/inbox/2026-03-04-bot-config-patterns.md +0 -24
  270. package/scripts/postinstall.cjs +0 -64
  271. package/scripts/test-hal-tools.ts +0 -154
@@ -6,6 +6,7 @@ const workspace_cache_1 = require("./workspace-cache");
6
6
  const config_1 = require("../config");
7
7
  const logger_1 = require("../lib/logger");
8
8
  const index_1 = require("./utils/index");
9
+ const index_2 = require("./utils/index");
9
10
  const logger = (0, logger_1.createLogger)({ component: 'user-context-cache' });
10
11
  /**
11
12
  * Cache for user-specific data (client connections, init data, workspace cache)
@@ -57,7 +58,7 @@ class UserContextCache {
57
58
  else {
58
59
  // Cache expired, remove stale entry
59
60
  this.cache.delete(apiKey);
60
- logger.debug('Cache expired, refreshing user context', {
61
+ logger.info('Cache expired, refreshing user context', {
61
62
  apiKey: apiKey.substring(0, 8) + '...',
62
63
  ageMinutes: Math.round(age / (1000 * 60))
63
64
  });
@@ -66,7 +67,7 @@ class UserContextCache {
66
67
  else if (forceRefresh && this.cache.has(apiKey)) {
67
68
  // Force refresh requested, clear existing cache
68
69
  this.cache.delete(apiKey);
69
- logger.debug('Force refresh requested, clearing cached user context', {
70
+ logger.info('Force refresh requested, clearing cached user context', {
70
71
  apiKey: apiKey.substring(0, 8) + '...'
71
72
  });
72
73
  }
@@ -105,29 +106,57 @@ class UserContextCache {
105
106
  const init = await client.socket.request('v2.core.init', [
106
107
  ['processes', 'users', 'network', 'networks', 'teams']
107
108
  ]);
108
- // Validate single workspace access - MCP requires bot credentials with access to one workspace only
109
- const workspaceCount = Object.keys(init.networks || {}).length;
109
+ (0, index_1.normalizeInitProcesses)(init);
110
+ // Multi-workspace support: log available workspaces, use first one as default
111
+ // User can switch workspaces using list_my_workspaces tool and specifying workspaceId
112
+ const networks = (init.networks || {});
113
+ const workspaceCount = Object.keys(networks).length;
110
114
  if (workspaceCount > 1) {
111
- const networks = (init.networks || {});
112
- const workspaceNames = Object.values(networks)
113
- .map((ws) => ws.name)
115
+ const workspaceList = Object.entries(networks)
116
+ .map(([id, ws]) => `${ws.name} (${id})`)
114
117
  .join(', ');
115
- logger.error('Multi-workspace credentials detected', {
118
+ logger.info('Multi-workspace user detected', {
116
119
  workspaceCount,
117
- workspaces: workspaceNames,
118
- apiKey: apiKey.substring(0, 8) + '...'
120
+ workspaces: workspaceList,
121
+ apiKey: apiKey.substring(0, 8) + '...',
122
+ hint: 'Use list_my_workspaces tool to see all workspaces and switch between them'
119
123
  });
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
124
  }
126
125
  // Create workspace cache from init data
127
126
  const appConfig = (0, config_1.createApplicationConfig)();
128
127
  const workspaceCache = (0, workspace_cache_1.createWorkspaceCache)(init, appConfig.mcpConfig);
128
+ // Extract user roles from ALL workspaces
129
+ const currentUserId = await (0, hailer_clients_1.getCurrentUserId)(client);
130
+ const workspaceRoles = (0, index_2.extractWorkspaceRoles)(networks, currentUserId);
131
+ // Get current workspace ID
132
+ const currentWorkspaceId = init.network?._id || Object.keys(networks)[0] || '';
133
+ // Get role for current workspace (for backward compatibility and initial tool filtering)
134
+ const userRole = workspaceRoles[currentWorkspaceId] || 'guest';
135
+ const allowedGroups = (0, index_2.getAllowedGroups)(userRole, config_1.environment.ENABLE_NUCLEAR_TOOLS);
136
+ logger.info('User roles extracted from all workspaces', {
137
+ apiKey: apiKey.substring(0, 8) + '...',
138
+ workspaceCount: Object.keys(workspaceRoles).length,
139
+ currentWorkspaceId,
140
+ currentRole: userRole,
141
+ allRoles: Object.entries(workspaceRoles).map(([id, role]) => `${id.slice(-6)}:${role}`).join(', ')
142
+ });
129
143
  // Get credentials from config (for tools like publish_hailer_app that need external auth)
130
- const accountConfig = appConfig.getClientConfig(apiKey);
144
+ // User API Keys (OAuth users) don't have bot credentials - use empty strings
145
+ const isUserApiKey = apiKey.startsWith('userapikey_');
146
+ let email = '';
147
+ let password = '';
148
+ if (!isUserApiKey) {
149
+ try {
150
+ const accountConfig = appConfig.getClientConfig(apiKey);
151
+ email = accountConfig.email;
152
+ password = accountConfig.password;
153
+ }
154
+ catch (error) {
155
+ logger.warn('No client config found for API key, using empty credentials', {
156
+ apiKey: apiKey.substring(0, 8) + '...'
157
+ });
158
+ }
159
+ }
131
160
  const context = {
132
161
  client,
133
162
  hailer,
@@ -135,8 +164,11 @@ class UserContextCache {
135
164
  workspaceCache,
136
165
  apiKey,
137
166
  createdAt: Date.now(),
138
- email: accountConfig.email,
139
- password: accountConfig.password,
167
+ email,
168
+ password,
169
+ workspaceRoles, // NEW: Map of workspaceId → role
170
+ currentWorkspaceId, // NEW: Current workspace ID
171
+ allowedGroups, // Keep for stdio-server compatibility
140
172
  };
141
173
  // Calculate and log cache sizes
142
174
  const rawInitSize = Buffer.byteLength(JSON.stringify(init), 'utf8');
@@ -176,7 +208,7 @@ class UserContextCache {
176
208
  static clearAll() {
177
209
  const size = this.cache.size;
178
210
  this.cache.clear();
179
- logger.debug('Cleared all user contexts from cache', { clearedCount: size });
211
+ logger.info('Cleared all user contexts from cache', { clearedCount: size });
180
212
  }
181
213
  /**
182
214
  * Get cache statistics (for monitoring and debugging)
@@ -5,10 +5,28 @@ export interface HailerRestClient {
5
5
  sessionKey: string;
6
6
  }
7
7
  export interface HailerClient {
8
- socket: Client;
8
+ socket: Client | RestOnlySocket;
9
9
  rest: HailerRestClient;
10
10
  sessionKey: string;
11
11
  }
12
+ /**
13
+ * REST-only socket mock for User API Key sessions
14
+ * User API Keys require IP for validation, which socket.resume() doesn't provide.
15
+ * This mock implements the socket.request() interface using REST API calls.
16
+ */
17
+ export declare class RestOnlySocket {
18
+ host: string;
19
+ private apiKey;
20
+ constructor(baseUrl: string, apiKey: string);
21
+ /**
22
+ * Make RPC request via REST API instead of socket
23
+ * Hailer's /api endpoint accepts the same RPC format as socket
24
+ */
25
+ request(method: string, args?: unknown[]): Promise<unknown>;
26
+ on(_event: string, _handler: (...args: any[]) => void): void;
27
+ off(_event: string, _handler: (...args: any[]) => void): void;
28
+ disconnect(): void;
29
+ }
12
30
  /**
13
31
  * Get the current user ID from the authenticated session
14
32
  * This is called after authentication to retrieve the user ID automatically
@@ -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 });
@@ -204,10 +267,7 @@ class HailerClientManager {
204
267
  if (!this.signalHandlers.has(eventType)) {
205
268
  this.signalHandlers.set(eventType, []);
206
269
  }
207
- const handlers = this.signalHandlers.get(eventType);
208
- if (!handlers.includes(handler)) {
209
- handlers.push(handler);
210
- }
270
+ this.signalHandlers.get(eventType).push(handler);
211
271
  }
212
272
  // Remove a signal handler
213
273
  offSignal(eventType, handler) {
@@ -247,12 +307,86 @@ class HailerClientManager {
247
307
  exports.HailerClientManager = HailerClientManager;
248
308
  // Connection pool for the MCP server
249
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
+ }
250
379
  /**
251
380
  * Create Hailer client by API key (O(1) lookup)
252
381
  * This is the new efficient method for MCP Server to get connections
253
382
  */
254
383
  const createHailerClientByApiKey = async (apiKey) => {
255
- // 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)
256
390
  const account = config_1.environment.CLIENT_CONFIGS[apiKey];
257
391
  if (!account) {
258
392
  throw new Error(`No Hailer account found for API key: ${apiKey}`);
@@ -283,7 +417,7 @@ const createHailerClientByApiKey = async (apiKey) => {
283
417
  connectionPool.delete(connectionKey);
284
418
  }
285
419
  // Create new connection
286
- logger.debug('Creating new connection', {
420
+ logger.info('Creating new connection', {
287
421
  apiKey: apiKey.substring(0, 8) + '...',
288
422
  email: (0, config_1.maskEmail)(account.email),
289
423
  host: account.apiBaseUrl
@@ -315,7 +449,7 @@ const clearAllConnections = () => {
315
449
  clientManager.disconnect();
316
450
  }
317
451
  connectionPool.clear();
318
- logger.debug('Cleared Hailer connections', { count });
452
+ logger.info('Cleared Hailer connections', { count });
319
453
  };
320
454
  exports.clearAllConnections = clearAllConnections;
321
455
  /**
@@ -349,10 +483,10 @@ function registerBotCredentials(botId, email, password, options) {
349
483
  config_1.environment.CLIENT_CONFIGS[apiKey] = {
350
484
  email,
351
485
  password,
352
- apiBaseUrl: config_1.environment.BOT_API_BASE_URL || 'https://api.hailer.com',
486
+ apiBaseUrl: 'https://api.hailer.com',
353
487
  ...(options?.allowedGroups && { allowedGroups: options.allowedGroups }),
354
488
  };
355
- logger.debug('Bot credentials registered', {
489
+ logger.info('Bot credentials registered', {
356
490
  botId,
357
491
  hasAllowedGroups: !!options?.allowedGroups
358
492
  });
@@ -364,6 +498,6 @@ function registerBotCredentials(botId, email, password, options) {
364
498
  function unregisterBotCredentials(apiKey) {
365
499
  delete config_1.environment.CLIENT_CONFIGS[apiKey];
366
500
  (0, exports.disconnectHailerClientByApiKey)(apiKey);
367
- logger.debug('Bot credentials unregistered');
501
+ logger.info('Bot credentials unregistered');
368
502
  }
369
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
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SignalHandler = void 0;
4
4
  const logger_1 = require("../lib/logger");
5
5
  const webhooks_1 = require("../bot-config/webhooks");
6
+ const types_1 = require("./utils/types");
6
7
  // Optional: bot-config only available in bot server mode, not MCP terminal
7
8
  let reloadConfigFromHailer = null;
8
9
  let setActiveWorkspace = null;
@@ -239,6 +240,7 @@ class SignalHandler {
239
240
  // Refresh cache by fetching fresh data - API requires array of keys to fetch
240
241
  // IMPORTANT: Include 'processes' to get updated workflow list (e.g., after template install)
241
242
  const init = await this.client.socket.request('v2.core.init', [['users', 'network', 'networks', 'processes']]);
243
+ (0, types_1.normalizeInitProcesses)(init);
242
244
  // Update workspace cache with fresh data
243
245
  if (init.processes) {
244
246
  this.workspaceCache.rawInit.processes = init.processes;