@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
@@ -59,6 +59,22 @@ class MessageClassifier {
59
59
  messages = retryMessages;
60
60
  }
61
61
  messageContent = targetMessage.msg || targetMessage.content || "";
62
+ // Extract forwarded message content — forwarded messages have empty msg
63
+ if (!messageContent && targetMessage.forwardMessage) {
64
+ let fwd = targetMessage.forwardMessage;
65
+ const fwdParts = [];
66
+ let depth = 0;
67
+ while (fwd && depth < 10) {
68
+ const fwdText = fwd.msg || fwd.content || '';
69
+ if (fwdText)
70
+ fwdParts.push(fwdText);
71
+ fwd = fwd.forwardMessage;
72
+ depth++;
73
+ }
74
+ if (fwdParts.length > 0) {
75
+ messageContent = `[Forwarded message] ${fwdParts.join(' → ')}`;
76
+ }
77
+ }
62
78
  // Extract file attachments if present
63
79
  if (targetMessage.files && Array.isArray(targetMessage.files) && targetMessage.files.length > 0) {
64
80
  fileAttachments = targetMessage.files.map((file) => ({
@@ -169,6 +185,7 @@ class MessageClassifier {
169
185
  isReplyToBot,
170
186
  isMention,
171
187
  isDirectMessage,
188
+ isPrivateDiscussion: !!(discData?.private),
172
189
  fileAttachments: fileAttachments.length > 0 ? fileAttachments : undefined,
173
190
  };
174
191
  }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Unified permission guard for bot tool execution.
3
+ *
4
+ * Enforces the workspace permission system: bot scope ceiling + per-user
5
+ * workflow access from the permission index built from core.init.
6
+ *
7
+ * Three levels of checking:
8
+ * 1. Pre-execution: extract workflow IDs from tool args, deny if user lacks access
9
+ * 2. Post-execution: extract workflow IDs from tool results (when not in args)
10
+ * 3. Post-filter: strip items from list results that the user can't see
11
+ */
12
+ export interface PermissionDenied {
13
+ workflowId: string;
14
+ reason: 'bot-scope' | string;
15
+ }
16
+ export interface PermissionContext {
17
+ /** Bot's allowed workflows (empty = all) */
18
+ allowedWorkflows: string[];
19
+ /** Workflow ID → Set of user IDs who can access it. Missing = open to all. */
20
+ permissionIndex: Map<string, Set<string>>;
21
+ /** Workspace admin user IDs */
22
+ adminUserIds: Set<string>;
23
+ /** Workspace owner user IDs */
24
+ ownerUserIds: Set<string>;
25
+ /** Insight ID → Set of source workflow IDs */
26
+ insightWorkflowCache: Map<string, Set<string>>;
27
+ }
28
+ /**
29
+ * Build the permission index from core.init data.
30
+ *
31
+ * Returns: { permissionIndex, adminUserIds, ownerUserIds, workspaceMemberIds }
32
+ *
33
+ * permissionIndex only contains RESTRICTED workflows (those with explicit member lists).
34
+ * If a workflow is not in the index, it's open to all workspace members.
35
+ */
36
+ export declare function buildPermissionIndex(init: any, workspaceId: string): {
37
+ permissionIndex: Map<string, Set<string>>;
38
+ adminUserIds: Set<string>;
39
+ ownerUserIds: Set<string>;
40
+ workspaceMemberIds: Set<string>;
41
+ };
42
+ /**
43
+ * Extract all workflow IDs from tool args, regardless of shape.
44
+ * Handles: top-level workflowId, sources[].workflowId, insightId → cached workflows.
45
+ */
46
+ export declare function extractWorkflowIdsFromArgs(args: Record<string, unknown>, insightWorkflowCache: Map<string, Set<string>>): string[];
47
+ /**
48
+ * Check if a user can access the given workflow IDs.
49
+ * Returns null if allowed, or { workflowId, reason } if denied.
50
+ */
51
+ export declare function checkWorkflowAccess(workflowIds: string[], senderId: string, ctx: PermissionContext): PermissionDenied | null;
52
+ //# sourceMappingURL=permission-guard.d.ts.map
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ /**
3
+ * Unified permission guard for bot tool execution.
4
+ *
5
+ * Enforces the workspace permission system: bot scope ceiling + per-user
6
+ * workflow access from the permission index built from core.init.
7
+ *
8
+ * Three levels of checking:
9
+ * 1. Pre-execution: extract workflow IDs from tool args, deny if user lacks access
10
+ * 2. Post-execution: extract workflow IDs from tool results (when not in args)
11
+ * 3. Post-filter: strip items from list results that the user can't see
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.buildPermissionIndex = buildPermissionIndex;
15
+ exports.extractWorkflowIdsFromArgs = extractWorkflowIdsFromArgs;
16
+ exports.checkWorkflowAccess = checkWorkflowAccess;
17
+ /**
18
+ * Build the permission index from core.init data.
19
+ *
20
+ * Returns: { permissionIndex, adminUserIds, ownerUserIds, workspaceMemberIds }
21
+ *
22
+ * permissionIndex only contains RESTRICTED workflows (those with explicit member lists).
23
+ * If a workflow is not in the index, it's open to all workspace members.
24
+ */
25
+ function buildPermissionIndex(init, workspaceId) {
26
+ const permissionIndex = new Map();
27
+ const adminUserIds = new Set();
28
+ const ownerUserIds = new Set();
29
+ const workspaceMemberIds = new Set();
30
+ if (!init?.processes || !workspaceId) {
31
+ return { permissionIndex, adminUserIds, ownerUserIds, workspaceMemberIds };
32
+ }
33
+ // Extract workspace members, admins, owners from network.members
34
+ const network = init.network;
35
+ if (network?.members) {
36
+ const members = Array.isArray(network.members) ? network.members : Object.values(network.members);
37
+ for (const member of members) {
38
+ if (member.uid)
39
+ workspaceMemberIds.add(member.uid);
40
+ if (member.admin === true)
41
+ adminUserIds.add(member.uid);
42
+ if (member.owner === true)
43
+ ownerUserIds.add(member.uid);
44
+ }
45
+ }
46
+ const teams = init.teams?.[workspaceId] || {};
47
+ const groups = init.groups?.[workspaceId] || {};
48
+ for (const proc of init.processes) {
49
+ const members = proc.members || [];
50
+ // network_ member = all workspace members have access → skip (open)
51
+ if (members.some((m) => m.id?.startsWith('network_')))
52
+ continue;
53
+ // Public team = all workspace members have access → skip (open)
54
+ let hasPublicTeam = false;
55
+ for (const m of members) {
56
+ if (!m.id?.startsWith('team_'))
57
+ continue;
58
+ const team = teams[m.id.slice(5)];
59
+ if (team?.public) {
60
+ hasPublicTeam = true;
61
+ break;
62
+ }
63
+ }
64
+ if (hasPublicTeam)
65
+ continue;
66
+ // Restricted workflow — build explicit user set
67
+ const userIds = new Set();
68
+ for (const member of members) {
69
+ const id = member.id;
70
+ if (!id)
71
+ continue;
72
+ if (id.startsWith('user_')) {
73
+ userIds.add(id.slice(5));
74
+ }
75
+ else if (id.startsWith('team_')) {
76
+ const team = teams[id.slice(5)];
77
+ if (team?.members) {
78
+ for (const uid of team.members) {
79
+ userIds.add(typeof uid === 'string' ? uid : uid._id || uid.id || uid.uid);
80
+ }
81
+ }
82
+ }
83
+ else if (id.startsWith('group_')) {
84
+ const group = groups[id.slice(6)];
85
+ if (group?.members) {
86
+ for (const uid of group.members) {
87
+ userIds.add(typeof uid === 'string' ? uid : uid._id || uid.id || uid.uid);
88
+ }
89
+ }
90
+ }
91
+ }
92
+ // Workflow creator always has access
93
+ if (proc.uid)
94
+ userIds.add(proc.uid);
95
+ permissionIndex.set(proc._id, userIds);
96
+ }
97
+ return { permissionIndex, adminUserIds, ownerUserIds, workspaceMemberIds };
98
+ }
99
+ /**
100
+ * Extract all workflow IDs from tool args, regardless of shape.
101
+ * Handles: top-level workflowId, sources[].workflowId, insightId → cached workflows.
102
+ */
103
+ function extractWorkflowIdsFromArgs(args, insightWorkflowCache) {
104
+ const ids = [];
105
+ // Direct workflowId arg
106
+ if (typeof args.workflowId === 'string') {
107
+ ids.push(args.workflowId);
108
+ }
109
+ // sources[].workflowId (insight tools)
110
+ if (Array.isArray(args.sources)) {
111
+ for (const src of args.sources) {
112
+ if (src && typeof src === 'object' && typeof src.workflowId === 'string') {
113
+ ids.push(src.workflowId);
114
+ }
115
+ }
116
+ }
117
+ // insightId → cached workflow mapping
118
+ if (typeof args.insightId === 'string') {
119
+ const cached = insightWorkflowCache.get(args.insightId);
120
+ if (cached)
121
+ ids.push(...cached);
122
+ }
123
+ return ids;
124
+ }
125
+ /**
126
+ * Check if a user can access the given workflow IDs.
127
+ * Returns null if allowed, or { workflowId, reason } if denied.
128
+ */
129
+ function checkWorkflowAccess(workflowIds, senderId, ctx) {
130
+ if (workflowIds.length === 0)
131
+ return null;
132
+ for (const wfId of workflowIds) {
133
+ // Bot scope ceiling
134
+ if (ctx.allowedWorkflows.length > 0 && !ctx.allowedWorkflows.includes(wfId)) {
135
+ return { workflowId: wfId, reason: 'bot-scope' };
136
+ }
137
+ // Admins and owners bypass user-level checks
138
+ if (ctx.adminUserIds.has(senderId) || ctx.ownerUserIds.has(senderId)) {
139
+ continue;
140
+ }
141
+ // User permission — restricted workflow, user not in member list
142
+ const allowed = ctx.permissionIndex.get(wfId);
143
+ if (allowed && !allowed.has(senderId)) {
144
+ return { workflowId: wfId, reason: senderId };
145
+ }
146
+ }
147
+ return null;
148
+ }
149
+ //# sourceMappingURL=permission-guard.js.map
@@ -62,6 +62,8 @@ export interface BotConnection {
62
62
  };
63
63
  }
64
64
  export type MessagePriority = "high" | "normal" | "low";
65
+ /** Controls when the bot processes a message */
66
+ export type ResponseMode = 'always' | 'mention_only' | 'reply_only' | 'mention_or_reply';
65
67
  /** File attachment in a Hailer message */
66
68
  export interface HailerFileAttachment {
67
69
  _id: string;
@@ -82,6 +84,8 @@ export interface HailerMessage {
82
84
  replyTo?: string;
83
85
  created?: number;
84
86
  files?: HailerFileAttachment[];
87
+ forwardMessageId?: string;
88
+ forwardMessage?: HailerMessage;
85
89
  }
86
90
  /** Hailer user from search/workspace cache */
87
91
  export interface HailerUser {
@@ -114,6 +118,7 @@ export interface IncomingMessage {
114
118
  isReplyToBot: boolean;
115
119
  isMention: boolean;
116
120
  isDirectMessage: boolean;
121
+ isPrivateDiscussion: boolean;
117
122
  fileAttachments?: IncomingFileAttachment[];
118
123
  }
119
124
  export interface DiscussionState {
@@ -9,11 +9,16 @@ export declare class TypingIndicatorService {
9
9
  private logger;
10
10
  private static TYPING_REFRESH_MS;
11
11
  private typingIntervals;
12
+ private currentStatus;
12
13
  constructor(botConnection: BotConnection, logger: Logger);
13
14
  /**
14
15
  * Start typing indicator with auto-refresh interval for a discussion
15
16
  */
16
- start(discussionId: string): void;
17
+ start(discussionId: string, statusText?: string): void;
18
+ /**
19
+ * Update the status text for an active typing indicator
20
+ */
21
+ updateStatus(discussionId: string, statusText: string): void;
17
22
  /**
18
23
  * Stop typing indicator for a specific discussion, or all discussions (shutdown)
19
24
  */
@@ -10,6 +10,7 @@ class TypingIndicatorService {
10
10
  logger;
11
11
  static TYPING_REFRESH_MS = 3000;
12
12
  typingIntervals = new Map();
13
+ currentStatus = new Map();
13
14
  constructor(botConnection, logger) {
14
15
  this.botConnection = botConnection;
15
16
  this.logger = logger;
@@ -17,7 +18,9 @@ class TypingIndicatorService {
17
18
  /**
18
19
  * Start typing indicator with auto-refresh interval for a discussion
19
20
  */
20
- start(discussionId) {
21
+ start(discussionId, statusText) {
22
+ if (statusText)
23
+ this.currentStatus.set(discussionId, statusText);
21
24
  if (this.typingIntervals.has(discussionId))
22
25
  return;
23
26
  this.sendSignal(discussionId, true);
@@ -26,6 +29,15 @@ class TypingIndicatorService {
26
29
  }, TypingIndicatorService.TYPING_REFRESH_MS);
27
30
  this.typingIntervals.set(discussionId, interval);
28
31
  }
32
+ /**
33
+ * Update the status text for an active typing indicator
34
+ */
35
+ updateStatus(discussionId, statusText) {
36
+ this.currentStatus.set(discussionId, statusText);
37
+ if (this.typingIntervals.has(discussionId)) {
38
+ this.sendSignal(discussionId, true);
39
+ }
40
+ }
29
41
  /**
30
42
  * Stop typing indicator for a specific discussion, or all discussions (shutdown)
31
43
  */
@@ -35,6 +47,7 @@ class TypingIndicatorService {
35
47
  if (interval) {
36
48
  clearInterval(interval);
37
49
  this.typingIntervals.delete(discussionId);
50
+ this.currentStatus.delete(discussionId);
38
51
  this.sendSignal(discussionId, false);
39
52
  }
40
53
  }
@@ -45,14 +58,17 @@ class TypingIndicatorService {
45
58
  this.sendSignal(discId, false);
46
59
  }
47
60
  this.typingIntervals.clear();
61
+ this.currentStatus.clear();
48
62
  }
49
63
  }
50
64
  sendSignal(discussionId, isTyping) {
65
+ const statusText = isTyping ? this.currentStatus.get(discussionId) : undefined;
51
66
  this.botConnection.client.socket.request("messenger.set_discussion_typing_state", [
52
67
  discussionId,
53
- isTyping
68
+ isTyping,
69
+ ...(statusText ? [statusText] : []),
54
70
  ]).catch((error) => {
55
- this.logger.debug("Typing indicator failed", { discussionId, isTyping, error: error?.message });
71
+ this.logger.debug("Typing indicator failed", { discussionId, isTyping, statusText, error: error?.message });
56
72
  });
57
73
  }
58
74
  }
package/dist/config.d.ts CHANGED
@@ -20,12 +20,13 @@ export declare const APP_VERSION: string;
20
20
  */
21
21
  export declare const environment: {
22
22
  NODE_ENV: "development" | "production" | "test";
23
- LOG_LEVEL: "debug" | "info" | "warn" | "error";
23
+ LOG_LEVEL: "error" | "debug" | "info" | "warn";
24
24
  DISABLE_MCP_SERVER: boolean;
25
25
  MCP_CLIENT_ENABLED: boolean;
26
26
  ENABLE_NUCLEAR_TOOLS: boolean;
27
27
  PORT: number;
28
28
  CORS_ORIGINS: string[];
29
+ HAILER_APP_URL: string;
29
30
  CLIENT_CONFIGS: Record<string, {
30
31
  email: string;
31
32
  password: string;
@@ -50,6 +51,8 @@ export declare const environment: {
50
51
  CONTEXT_ENABLE_AUTO_SUMMARIZATION: boolean;
51
52
  CONTEXT_MAX_SUMMARIZATION_CHUNKS: number;
52
53
  TOKEN_BILLING_ENABLED: boolean;
54
+ VIPUNEN_API_KEYS: Record<string, "admin" | "dev" | "readonly">;
55
+ MCP_PUBLIC_URL?: string | undefined;
53
56
  WORKSPACE_CONFIG_PATH?: string | undefined;
54
57
  DEV_APPS_PATH?: string | undefined;
55
58
  DEV_APPS_PATHS?: string | undefined;
@@ -57,6 +60,8 @@ export declare const environment: {
57
60
  OPENAI_API_BASE?: string | undefined;
58
61
  OPENAI_MODEL?: string | undefined;
59
62
  ANTHROPIC_API_KEY?: string | undefined;
63
+ WEAVIATE_HOST?: string | undefined;
64
+ WEAVIATE_API_KEY?: string | undefined;
60
65
  };
61
66
  /**
62
67
  * Mask sensitive data for safe logging
package/dist/config.js CHANGED
@@ -102,6 +102,10 @@ const environmentSchema = zod_1.z.object({
102
102
  // Server
103
103
  PORT: zod_1.z.string().transform(v => parseInt(v) || 3030).default('3030'),
104
104
  CORS_ORIGINS: jsonArraySchema(zod_1.z.string().url()).default('[]'),
105
+ // OAuth - public URL for redirects (e.g., https://mcp.hailer.com)
106
+ MCP_PUBLIC_URL: zod_1.z.string().url().optional(),
107
+ // Hailer frontend URL for OAuth login (e.g., https://app.hailer.com)
108
+ HAILER_APP_URL: zod_1.z.string().url().default('https://app.hailer.com'),
105
109
  // Hailer integration - now as efficient Map
106
110
  CLIENT_CONFIGS: clientConfigsSchema,
107
111
  // Workspace configuration path (for reading workspace configs from external projects)
@@ -142,6 +146,45 @@ const environmentSchema = zod_1.z.object({
142
146
  CONTEXT_MAX_SUMMARIZATION_CHUNKS: zod_1.z.string().transform(v => parseInt(v) || 10).default('10'),
143
147
  // Token billing (real-time usage billing per workspace via Hailer API)
144
148
  TOKEN_BILLING_ENABLED: zod_1.z.string().transform(v => v === 'true').default('false'),
149
+ // Vipunen (Weaviate RAG knowledge base)
150
+ WEAVIATE_HOST: zod_1.z.string().url().optional(),
151
+ WEAVIATE_API_KEY: zod_1.z.string().min(1).optional(),
152
+ // Vipunen client API keys — JSON map of key → group
153
+ // Format: { "my-api-key": "admin", "dev-key": "dev", "reader-key": "readonly" }
154
+ // Groups: admin (full access), dev (VipunenConfig read-only), readonly (read-only everywhere)
155
+ VIPUNEN_API_KEYS: zod_1.z.string()
156
+ .optional()
157
+ .default('{}')
158
+ .transform((value, ctx) => {
159
+ try {
160
+ const parsed = JSON.parse(value);
161
+ const validGroups = ['admin', 'dev', 'readonly'];
162
+ for (const [key, group] of Object.entries(parsed)) {
163
+ if (key.length < 32) {
164
+ ctx.addIssue({
165
+ code: zod_1.z.ZodIssueCode.custom,
166
+ message: `Vipunen API key too short (min 32 chars): "${key.substring(0, 4)}..."`,
167
+ });
168
+ return zod_1.z.NEVER;
169
+ }
170
+ if (!validGroups.includes(group)) {
171
+ ctx.addIssue({
172
+ code: zod_1.z.ZodIssueCode.custom,
173
+ message: `Invalid group "${group}" for key "${key.substring(0, 4)}...". Must be one of: ${validGroups.join(', ')}`,
174
+ });
175
+ return zod_1.z.NEVER;
176
+ }
177
+ }
178
+ return parsed;
179
+ }
180
+ catch (error) {
181
+ ctx.addIssue({
182
+ code: zod_1.z.ZodIssueCode.custom,
183
+ message: `Invalid VIPUNEN_API_KEYS JSON: ${error.message}`,
184
+ });
185
+ return zod_1.z.NEVER;
186
+ }
187
+ }),
145
188
  });
146
189
  /**
147
190
  * Get process environment
package/dist/core.js CHANGED
@@ -49,15 +49,13 @@ class Core {
49
49
  this.logger.debug('Starting Hailer MCP application');
50
50
  try {
51
51
  // Start MCP Server FIRST (daemon/client needs it for tool schemas)
52
+ // Server starts even without CLIENT_CONFIGS - OAuth sessions are dynamic
52
53
  if (this.appConfig.server.enableMcpServer) {
53
54
  const hasAccounts = Object.keys(this.appConfig.hailerAccounts).length > 0;
54
55
  if (!hasAccounts) {
55
- this.logger.warn('MCP Server enabled but no accounts configured in CLIENT_CONFIGS. Server will not start.');
56
- this.logger.warn('Add at least one account to CLIENT_CONFIGS in .env.local to use the MCP Server.');
57
- }
58
- else {
59
- await this.startMCPServer();
56
+ this.logger.info('No accounts in CLIENT_CONFIGS - server will accept OAuth sessions only');
60
57
  }
58
+ await this.startMCPServer();
61
59
  }
62
60
  // Start bot client ONLY if MCP_CLIENT_ENABLED=true
63
61
  if (this.appConfig.server.enableClient) {
@@ -78,7 +76,6 @@ class Core {
78
76
  port: this.appConfig.server.port,
79
77
  corsOrigins: this.appConfig.server.corsOrigins,
80
78
  toolRegistry: this.toolRegistry,
81
- getDaemonStatus: () => this.getDaemonStatus() // Pass callback for daemon monitoring
82
79
  });
83
80
  await this.mcpServer.start();
84
81
  this.logger.debug('MCP Server service started');
@@ -1,6 +1,8 @@
1
1
  import { HailerClient } from './hailer-clients';
2
2
  import { WorkspaceCache } from './workspace-cache';
3
3
  import { HailerV2CoreInitResponse, HailerApiClient } from './utils/index';
4
+ import { ToolGroup } from './tool-registry';
5
+ import { UserRole } from './utils/index';
4
6
  export interface UserContext {
5
7
  client: HailerClient;
6
8
  hailer: HailerApiClient;
@@ -10,6 +12,9 @@ export interface UserContext {
10
12
  createdAt: number;
11
13
  email: string;
12
14
  password: string;
15
+ workspaceRoles: Record<string, UserRole>;
16
+ currentWorkspaceId: string;
17
+ allowedGroups: ToolGroup[];
13
18
  }
14
19
  /**
15
20
  * Cache for user-specific data (client connections, init data, workspace cache)
@@ -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