@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
@@ -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/cli.js CHANGED
File without changes
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');
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Discussion Lock Registry
3
+ *
4
+ * Prevents multiple bots from responding to the same discussion.
5
+ * When a specialist bot (like Giuseppe) is handling a discussion,
6
+ * it acquires a lock. Other bots (like Orchestrator) check the lock
7
+ * before responding.
8
+ */
9
+ /**
10
+ * Acquire a lock on a discussion
11
+ * @param discussionId - The discussion to lock
12
+ * @param botName - Name of the bot acquiring the lock (for logging)
13
+ * @param ttlMs - Lock duration in milliseconds (default: 5 minutes)
14
+ * @returns true if lock acquired, false if already locked by another bot
15
+ */
16
+ export declare function acquireDiscussionLock(discussionId: string, botName: string, ttlMs?: number): boolean;
17
+ /**
18
+ * Release a lock on a discussion
19
+ * @param discussionId - The discussion to unlock
20
+ * @param botName - Name of the bot releasing (must match acquirer)
21
+ */
22
+ export declare function releaseDiscussionLock(discussionId: string, botName: string): void;
23
+ /**
24
+ * Check if a discussion is locked by another bot
25
+ * @param discussionId - The discussion to check
26
+ * @param myBotName - Name of the checking bot (own locks don't block)
27
+ * @returns true if locked by ANOTHER bot, false if free or own lock
28
+ */
29
+ export declare function isDiscussionLocked(discussionId: string, myBotName: string): boolean;
30
+ /**
31
+ * Clean up expired locks (call periodically)
32
+ */
33
+ export declare function cleanupExpiredLocks(): number;
34
+ /**
35
+ * Get current lock status (for debugging)
36
+ */
37
+ export declare function getLockStatus(): Map<string, {
38
+ botName: string;
39
+ acquiredAt: number;
40
+ expiresAt: number;
41
+ }>;
42
+ //# sourceMappingURL=discussion-lock.d.ts.map
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ /**
3
+ * Discussion Lock Registry
4
+ *
5
+ * Prevents multiple bots from responding to the same discussion.
6
+ * When a specialist bot (like Giuseppe) is handling a discussion,
7
+ * it acquires a lock. Other bots (like Orchestrator) check the lock
8
+ * before responding.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.acquireDiscussionLock = acquireDiscussionLock;
12
+ exports.releaseDiscussionLock = releaseDiscussionLock;
13
+ exports.isDiscussionLocked = isDiscussionLocked;
14
+ exports.cleanupExpiredLocks = cleanupExpiredLocks;
15
+ exports.getLockStatus = getLockStatus;
16
+ const logger_1 = require("./logger");
17
+ const logger = (0, logger_1.createLogger)({ component: 'discussion-lock' });
18
+ // Singleton map of discussionId -> { botName, acquiredAt, expiresAt }
19
+ const locks = new Map();
20
+ // Default lock TTL: 5 minutes (allows for LLM processing time)
21
+ const DEFAULT_LOCK_TTL_MS = 5 * 60 * 1000;
22
+ /**
23
+ * Acquire a lock on a discussion
24
+ * @param discussionId - The discussion to lock
25
+ * @param botName - Name of the bot acquiring the lock (for logging)
26
+ * @param ttlMs - Lock duration in milliseconds (default: 5 minutes)
27
+ * @returns true if lock acquired, false if already locked by another bot
28
+ */
29
+ function acquireDiscussionLock(discussionId, botName, ttlMs = DEFAULT_LOCK_TTL_MS) {
30
+ const now = Date.now();
31
+ const existing = locks.get(discussionId);
32
+ // Check if existing lock is still valid
33
+ if (existing && existing.expiresAt > now) {
34
+ if (existing.botName === botName) {
35
+ // Same bot - extend the lock
36
+ existing.expiresAt = now + ttlMs;
37
+ return true;
38
+ }
39
+ // Different bot has the lock
40
+ logger.debug('Discussion already locked', {
41
+ discussionId,
42
+ lockedBy: existing.botName,
43
+ requestedBy: botName
44
+ });
45
+ return false;
46
+ }
47
+ // Acquire lock
48
+ locks.set(discussionId, {
49
+ botName,
50
+ acquiredAt: now,
51
+ expiresAt: now + ttlMs
52
+ });
53
+ logger.debug('Discussion lock acquired', { discussionId, botName, ttlMs });
54
+ return true;
55
+ }
56
+ /**
57
+ * Release a lock on a discussion
58
+ * @param discussionId - The discussion to unlock
59
+ * @param botName - Name of the bot releasing (must match acquirer)
60
+ */
61
+ function releaseDiscussionLock(discussionId, botName) {
62
+ const existing = locks.get(discussionId);
63
+ if (existing && existing.botName === botName) {
64
+ locks.delete(discussionId);
65
+ logger.debug('Discussion lock released', { discussionId, botName });
66
+ }
67
+ }
68
+ /**
69
+ * Check if a discussion is locked by another bot
70
+ * @param discussionId - The discussion to check
71
+ * @param myBotName - Name of the checking bot (own locks don't block)
72
+ * @returns true if locked by ANOTHER bot, false if free or own lock
73
+ */
74
+ function isDiscussionLocked(discussionId, myBotName) {
75
+ const now = Date.now();
76
+ const existing = locks.get(discussionId);
77
+ // No lock or expired
78
+ if (!existing || existing.expiresAt <= now) {
79
+ return false;
80
+ }
81
+ // Own lock doesn't block
82
+ if (existing.botName === myBotName) {
83
+ return false;
84
+ }
85
+ return true;
86
+ }
87
+ /**
88
+ * Clean up expired locks (call periodically)
89
+ */
90
+ function cleanupExpiredLocks() {
91
+ const now = Date.now();
92
+ let cleaned = 0;
93
+ for (const [discussionId, lock] of locks.entries()) {
94
+ if (lock.expiresAt <= now) {
95
+ locks.delete(discussionId);
96
+ cleaned++;
97
+ }
98
+ }
99
+ if (cleaned > 0) {
100
+ logger.debug('Cleaned up expired locks', { count: cleaned });
101
+ }
102
+ return cleaned;
103
+ }
104
+ /**
105
+ * Get current lock status (for debugging)
106
+ */
107
+ function getLockStatus() {
108
+ return new Map(locks);
109
+ }
110
+ //# sourceMappingURL=discussion-lock.js.map
@@ -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)