@hailer/mcp 1.0.29 → 1.1.2

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 (233) hide show
  1. package/.claude/.session-checked +1 -0
  2. package/.claude/agents/agent-ada-skill-builder.md +10 -2
  3. package/.claude/agents/agent-alejandro-function-fields.md +104 -37
  4. package/.claude/agents/agent-bjorn-config-audit.md +41 -21
  5. package/.claude/agents/agent-builder-agent-creator.md +13 -3
  6. package/.claude/agents/agent-code-simplifier.md +53 -0
  7. package/.claude/agents/agent-dmitri-activity-crud.md +126 -11
  8. package/.claude/agents/agent-giuseppe-app-builder.md +212 -22
  9. package/.claude/agents/agent-gunther-mcp-tools.md +7 -36
  10. package/.claude/agents/agent-helga-workflow-config.md +75 -10
  11. package/.claude/agents/agent-igor-activity-mover-automation.md +125 -0
  12. package/.claude/agents/agent-ingrid-doc-templates.md +164 -36
  13. package/.claude/agents/agent-ivan-monolith.md +154 -0
  14. package/.claude/agents/agent-kenji-data-reader.md +15 -8
  15. package/.claude/agents/agent-lars-code-inspector.md +56 -8
  16. package/.claude/agents/agent-marco-mockup-builder.md +110 -0
  17. package/.claude/agents/agent-marcus-api-documenter.md +323 -0
  18. package/.claude/agents/agent-marketplace-publisher.md +232 -72
  19. package/.claude/agents/agent-marketplace-reviewer.md +255 -79
  20. package/.claude/agents/agent-permissions-handler.md +208 -0
  21. package/.claude/agents/agent-simple-writer.md +48 -0
  22. package/.claude/agents/agent-svetlana-code-review.md +127 -14
  23. package/.claude/agents/agent-tanya-test-runner.md +333 -0
  24. package/.claude/agents/agent-ui-designer.md +100 -0
  25. package/.claude/agents/agent-viktor-sql-insights.md +19 -6
  26. package/.claude/agents/agent-web-search.md +55 -0
  27. package/.claude/agents/agent-yevgeni-discussions.md +7 -1
  28. package/.claude/agents/agent-zara-zapier.md +159 -0
  29. package/.claude/commands/app-squad.md +135 -0
  30. package/.claude/commands/audit-squad.md +158 -0
  31. package/.claude/commands/autoplan.md +563 -0
  32. package/.claude/commands/cleanup-squad.md +98 -0
  33. package/.claude/commands/config-squad.md +106 -0
  34. package/.claude/commands/crud-squad.md +87 -0
  35. package/.claude/commands/data-squad.md +97 -0
  36. package/.claude/commands/debug-squad.md +303 -0
  37. package/.claude/commands/doc-squad.md +65 -0
  38. package/.claude/commands/handoff.md +137 -0
  39. package/.claude/commands/health.md +49 -0
  40. package/.claude/commands/help.md +2 -1
  41. package/.claude/commands/help:agents.md +96 -16
  42. package/.claude/commands/help:commands.md +55 -11
  43. package/.claude/commands/help:faq.md +16 -1
  44. package/.claude/commands/help:skills.md +93 -0
  45. package/.claude/commands/hotfix-squad.md +112 -0
  46. package/.claude/commands/integration-squad.md +82 -0
  47. package/.claude/commands/janitor-squad.md +167 -0
  48. package/.claude/commands/learn-auto.md +120 -0
  49. package/.claude/commands/learn.md +120 -0
  50. package/.claude/commands/mcp-list.md +27 -0
  51. package/.claude/commands/onboard-squad.md +140 -0
  52. package/.claude/commands/plan-workspace.md +732 -0
  53. package/.claude/commands/prd.md +131 -0
  54. package/.claude/commands/project-status.md +82 -0
  55. package/.claude/commands/publish.md +138 -0
  56. package/.claude/commands/recap.md +69 -0
  57. package/.claude/commands/restore.md +64 -0
  58. package/.claude/commands/review-squad.md +152 -0
  59. package/.claude/commands/save.md +24 -0
  60. package/.claude/commands/stats.md +19 -0
  61. package/.claude/commands/swarm.md +210 -0
  62. package/.claude/commands/tool-builder.md +3 -1
  63. package/.claude/commands/ws-pull.md +1 -1
  64. package/.claude/commands/yolo-off.md +17 -0
  65. package/.claude/commands/yolo.md +82 -0
  66. package/.claude/hooks/_shared-memory.cjs +305 -0
  67. package/.claude/hooks/_utils.cjs +134 -0
  68. package/.claude/hooks/agent-failure-detector.cjs +164 -79
  69. package/.claude/hooks/agent-usage-logger.cjs +204 -0
  70. package/.claude/hooks/app-edit-guard.cjs +20 -4
  71. package/.claude/hooks/auto-learn.cjs +316 -0
  72. package/.claude/hooks/bash-guard.cjs +282 -0
  73. package/.claude/hooks/builder-mode-manager.cjs +183 -54
  74. package/.claude/hooks/bulk-activity-guard.cjs +283 -0
  75. package/.claude/hooks/context-watchdog.cjs +292 -0
  76. package/.claude/hooks/delegation-reminder.cjs +478 -0
  77. package/.claude/hooks/design-system-lint.cjs +283 -0
  78. package/.claude/hooks/post-scaffold-hook.cjs +16 -3
  79. package/.claude/hooks/prompt-guard.cjs +366 -0
  80. package/.claude/hooks/publish-template-guard.cjs +16 -0
  81. package/.claude/hooks/session-start.cjs +35 -0
  82. package/.claude/hooks/shared-memory-writer.cjs +147 -0
  83. package/.claude/hooks/skill-injector.cjs +140 -0
  84. package/.claude/hooks/skill-usage-logger.cjs +258 -0
  85. package/.claude/hooks/src-edit-guard.cjs +16 -1
  86. package/.claude/hooks/sync-marketplace-agents.cjs +53 -8
  87. package/.claude/scripts/yolo-toggle.cjs +142 -0
  88. package/.claude/settings.json +141 -14
  89. package/.claude/skills/SDK-activity-patterns/SKILL.md +428 -0
  90. package/.claude/skills/SDK-document-templates/SKILL.md +1033 -0
  91. package/.claude/skills/SDK-function-fields/SKILL.md +542 -0
  92. package/.claude/skills/SDK-generate-skill/SKILL.md +92 -0
  93. package/.claude/skills/SDK-init-skill/SKILL.md +127 -0
  94. package/.claude/skills/SDK-insight-queries/SKILL.md +787 -0
  95. package/.claude/skills/SDK-ws-config-skill/SKILL.md +1139 -0
  96. package/.claude/skills/agent-structure/SKILL.md +98 -0
  97. package/.claude/skills/api-documentation-patterns/SKILL.md +474 -0
  98. package/.claude/skills/chrome-mcp-reference/SKILL.md +370 -0
  99. package/.claude/skills/delegation-routing/SKILL.md +202 -0
  100. package/.claude/skills/frontend-design/SKILL.md +254 -0
  101. package/.claude/skills/hailer-activity-mover/SKILL.md +213 -0
  102. package/.claude/skills/hailer-api-client/SKILL.md +518 -0
  103. package/.claude/skills/hailer-app-builder/SKILL.md +939 -11
  104. package/.claude/skills/hailer-apps-pictures/SKILL.md +269 -0
  105. package/.claude/skills/hailer-design-system/SKILL.md +235 -0
  106. package/.claude/skills/hailer-monolith-automations/SKILL.md +686 -0
  107. package/.claude/skills/hailer-permissions-system/SKILL.md +121 -0
  108. package/.claude/skills/hailer-project-protocol/SKILL.md +488 -0
  109. package/.claude/skills/hailer-rest-api/SKILL.md +61 -0
  110. package/.claude/skills/hailer-rest-api/hailer-activities.md +184 -0
  111. package/.claude/skills/hailer-rest-api/hailer-admin.md +473 -0
  112. package/.claude/skills/hailer-rest-api/hailer-calendar.md +256 -0
  113. package/.claude/skills/hailer-rest-api/hailer-feed.md +249 -0
  114. package/.claude/skills/hailer-rest-api/hailer-insights.md +195 -0
  115. package/.claude/skills/hailer-rest-api/hailer-messaging.md +276 -0
  116. package/.claude/skills/hailer-rest-api/hailer-workflows.md +283 -0
  117. package/.claude/skills/insight-join-patterns/SKILL.md +3 -0
  118. package/.claude/skills/integration-patterns/SKILL.md +421 -0
  119. package/.claude/skills/json-only-output/SKILL.md +52 -12
  120. package/.claude/skills/lsp-setup/SKILL.md +160 -0
  121. package/.claude/skills/mcp-direct-tools/SKILL.md +153 -0
  122. package/.claude/skills/optional-parameters/SKILL.md +32 -23
  123. package/.claude/skills/publish-hailer-app/SKILL.md +76 -12
  124. package/.claude/skills/testing-patterns/SKILL.md +630 -0
  125. package/.claude/skills/tool-builder/SKILL.md +250 -0
  126. package/.claude/skills/tool-parameter-usage/SKILL.md +59 -45
  127. package/.claude/skills/tool-response-verification/SKILL.md +82 -48
  128. package/.claude/skills/zapier-hailer-patterns/SKILL.md +581 -0
  129. package/.env.example +26 -7
  130. package/CLAUDE.md +290 -224
  131. package/dist/CLAUDE.md +370 -0
  132. package/dist/app.d.ts +1 -1
  133. package/dist/app.js +101 -101
  134. package/dist/bot/bot-config.d.ts +26 -0
  135. package/dist/bot/bot-config.js +135 -0
  136. package/dist/bot/bot-manager.d.ts +40 -0
  137. package/dist/bot/bot-manager.js +137 -0
  138. package/dist/bot/bot.d.ts +127 -0
  139. package/dist/bot/bot.js +1328 -0
  140. package/dist/bot/operation-logger.d.ts +28 -0
  141. package/dist/bot/operation-logger.js +132 -0
  142. package/dist/bot/services/conversation-manager.d.ts +60 -0
  143. package/dist/bot/services/conversation-manager.js +246 -0
  144. package/dist/bot/services/index.d.ts +9 -0
  145. package/dist/bot/services/index.js +18 -0
  146. package/dist/bot/services/message-classifier.d.ts +42 -0
  147. package/dist/bot/services/message-classifier.js +228 -0
  148. package/dist/bot/services/message-formatter.d.ts +88 -0
  149. package/dist/bot/services/message-formatter.js +411 -0
  150. package/dist/bot/services/session-logger.d.ts +162 -0
  151. package/dist/bot/services/session-logger.js +724 -0
  152. package/dist/bot/services/token-billing.d.ts +78 -0
  153. package/dist/bot/services/token-billing.js +233 -0
  154. package/dist/bot/services/types.d.ts +169 -0
  155. package/dist/bot/services/types.js +12 -0
  156. package/dist/bot/services/typing-indicator.d.ts +23 -0
  157. package/dist/bot/services/typing-indicator.js +60 -0
  158. package/dist/bot/services/workspace-schema-cache.d.ts +122 -0
  159. package/dist/bot/services/workspace-schema-cache.js +506 -0
  160. package/dist/bot/tool-executor.d.ts +28 -0
  161. package/dist/bot/tool-executor.js +48 -0
  162. package/dist/bot/workspace-overview.d.ts +12 -0
  163. package/dist/bot/workspace-overview.js +94 -0
  164. package/dist/cli.d.ts +1 -8
  165. package/dist/cli.js +1 -253
  166. package/dist/config.d.ts +96 -3
  167. package/dist/config.js +148 -37
  168. package/dist/core.d.ts +5 -0
  169. package/dist/core.js +61 -8
  170. package/dist/lib/discussion-lock.d.ts +42 -0
  171. package/dist/lib/discussion-lock.js +110 -0
  172. package/dist/lib/logger.d.ts +0 -1
  173. package/dist/lib/logger.js +39 -23
  174. package/dist/lib/request-logger.d.ts +77 -0
  175. package/dist/lib/request-logger.js +147 -0
  176. package/dist/mcp/UserContextCache.js +16 -13
  177. package/dist/mcp/hailer-clients.js +18 -17
  178. package/dist/mcp/signal-handler.js +29 -13
  179. package/dist/mcp/tool-registry.d.ts +4 -15
  180. package/dist/mcp/tool-registry.js +94 -32
  181. package/dist/mcp/tools/activity.js +28 -69
  182. package/dist/mcp/tools/app-core.js +9 -4
  183. package/dist/mcp/tools/app-marketplace.js +22 -12
  184. package/dist/mcp/tools/app-member.js +5 -2
  185. package/dist/mcp/tools/app-scaffold.js +32 -18
  186. package/dist/mcp/tools/bot-config/constants.d.ts +23 -0
  187. package/dist/mcp/tools/bot-config/constants.js +94 -0
  188. package/dist/mcp/tools/bot-config/core.d.ts +253 -0
  189. package/dist/mcp/tools/bot-config/core.js +2456 -0
  190. package/dist/mcp/tools/bot-config/index.d.ts +10 -0
  191. package/dist/mcp/tools/bot-config/index.js +59 -0
  192. package/dist/mcp/tools/bot-config/tools.d.ts +7 -0
  193. package/dist/mcp/tools/bot-config/tools.js +15 -0
  194. package/dist/mcp/tools/bot-config/types.d.ts +50 -0
  195. package/dist/mcp/tools/bot-config/types.js +6 -0
  196. package/dist/mcp/tools/discussion.js +107 -77
  197. package/dist/mcp/tools/document.d.ts +11 -0
  198. package/dist/mcp/tools/document.js +741 -0
  199. package/dist/mcp/tools/file.js +5 -2
  200. package/dist/mcp/tools/insight.js +36 -12
  201. package/dist/mcp/tools/investigate.d.ts +9 -0
  202. package/dist/mcp/tools/investigate.js +254 -0
  203. package/dist/mcp/tools/user.d.ts +2 -4
  204. package/dist/mcp/tools/user.js +9 -50
  205. package/dist/mcp/tools/workflow.d.ts +1 -0
  206. package/dist/mcp/tools/workflow.js +164 -52
  207. package/dist/mcp/utils/hailer-api-client.js +26 -17
  208. package/dist/mcp/webhook-handler.d.ts +64 -3
  209. package/dist/mcp/webhook-handler.js +219 -9
  210. package/dist/mcp-server.d.ts +4 -0
  211. package/dist/mcp-server.js +237 -25
  212. package/dist/plugins/bug-fixer/index.d.ts +2 -0
  213. package/dist/plugins/bug-fixer/index.js +18 -0
  214. package/dist/plugins/bug-fixer/tools.d.ts +45 -0
  215. package/dist/plugins/bug-fixer/tools.js +1096 -0
  216. package/package.json +10 -10
  217. package/scripts/test-hal-tools.ts +154 -0
  218. package/.claude/agents/agent-nora-name-functions.md +0 -123
  219. package/.claude/assistant-knowledge.md +0 -23
  220. package/.claude/commands/install-plugin.md +0 -261
  221. package/.claude/commands/list-plugins.md +0 -42
  222. package/.claude/commands/marketplace-setup.md +0 -33
  223. package/.claude/commands/publish-plugin.md +0 -55
  224. package/.claude/commands/uninstall-plugin.md +0 -87
  225. package/.claude/hooks/interactive-mode.cjs +0 -87
  226. package/.claude/hooks/mcp-server-guard.cjs +0 -108
  227. package/.claude/skills/marketplace-publishing.md +0 -155
  228. package/dist/bot/chat-bot.d.ts +0 -31
  229. package/dist/bot/chat-bot.js +0 -357
  230. package/dist/mcp/tools/metrics.d.ts +0 -13
  231. package/dist/mcp/tools/metrics.js +0 -546
  232. package/dist/stdio-server.d.ts +0 -14
  233. package/dist/stdio-server.js +0 -114
@@ -0,0 +1,411 @@
1
+ "use strict";
2
+ /**
3
+ * Message Formatter Service
4
+ *
5
+ * Handles formatting of messages for Hailer:
6
+ * - Converting @mentions and #activity tags to Hailer tags
7
+ * - Resolving user/activity IDs via API
8
+ * - Extracting tag links for messenger.send
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.MessageFormatterService = void 0;
12
+ class MessageFormatterService {
13
+ botConnection;
14
+ logger;
15
+ callMcpTool;
16
+ constructor(botConnection, logger, callMcpTool) {
17
+ this.botConnection = botConnection;
18
+ this.logger = logger;
19
+ this.callMcpTool = callMcpTool;
20
+ }
21
+ /**
22
+ * Sanitize display name to prevent tag injection attacks
23
+ * Removes/escapes characters that could break the tag format
24
+ */
25
+ sanitizeDisplayName(name) {
26
+ if (!name || typeof name !== 'string') {
27
+ return 'Unknown';
28
+ }
29
+ // Remove or escape characters that could break the tag format:
30
+ // [ ] | ( ) could break markdown-style tag parsing
31
+ // Control characters and ZWNBSP could cause rendering issues
32
+ return name
33
+ .replace(/[\[\]|()]/g, '') // Remove tag-breaking characters
34
+ .replace(/[\u0000-\u001F\uFEFF]/g, '') // Remove control chars and ZWNBSP
35
+ .replace(/\s+/g, ' ') // Normalize whitespace
36
+ .trim()
37
+ .slice(0, 100) || 'Unknown'; // Limit length
38
+ }
39
+ /**
40
+ * Validate that targetId is a valid MongoDB ObjectId format
41
+ */
42
+ isValidTargetId(id) {
43
+ return /^[a-f0-9]{24}$/i.test(id);
44
+ }
45
+ /**
46
+ * Format a Hailer tag for mentioning a user or activity
47
+ * Hailer requires ZWNBSP (U+FEFF) around the tag
48
+ */
49
+ formatHailerTag(targetId, displayName) {
50
+ // Validate targetId format
51
+ if (!this.isValidTargetId(targetId)) {
52
+ this.logger.warn('Invalid targetId format for Hailer tag', { targetId });
53
+ return displayName; // Return plain text if ID is invalid
54
+ }
55
+ const sanitizedName = this.sanitizeDisplayName(displayName);
56
+ const ZWNBSP = '\uFEFF';
57
+ return `${ZWNBSP}[hailerTag|${sanitizedName}](${targetId})${ZWNBSP}`;
58
+ }
59
+ /**
60
+ * Look up user by ID and create a Hailer tag
61
+ */
62
+ createUserTagById(userId) {
63
+ const cache = this.botConnection.workspaceCache;
64
+ if (!cache)
65
+ return null;
66
+ const user = cache.usersById[userId];
67
+ if (!user)
68
+ return null;
69
+ const displayName = user.fullName || `${user.firstname} ${user.lastname}`.trim();
70
+ return this.formatHailerTag(userId, displayName);
71
+ }
72
+ /**
73
+ * Look up user by name and create a Hailer tag
74
+ */
75
+ createUserTagByName(name) {
76
+ const cache = this.botConnection.workspaceCache;
77
+ if (!cache)
78
+ return null;
79
+ const nameLower = name.toLowerCase();
80
+ const user = cache.users.find(u => {
81
+ const fullName = u.fullName || `${u.firstname} ${u.lastname}`.trim();
82
+ return fullName.toLowerCase() === nameLower ||
83
+ u.firstname?.toLowerCase() === nameLower ||
84
+ u.lastname?.toLowerCase() === nameLower;
85
+ });
86
+ if (!user)
87
+ return null;
88
+ const displayName = user.fullName || `${user.firstname} ${user.lastname}`.trim();
89
+ return this.formatHailerTag(user.id, displayName);
90
+ }
91
+ /**
92
+ * Convert @mentions and #activity tags in response to Hailer tags
93
+ * Supports:
94
+ * - @userId (24-char hex MongoDB ID) -> user tag
95
+ * - @"Full Name" (quoted name lookup) -> user tag
96
+ * - @FirstName (single word name lookup) -> user tag
97
+ * - #activityId (24-char hex) -> activity tag
98
+ * - #"Activity Name" (quoted) -> activity tag (requires lookup)
99
+ */
100
+ convertMentionsToTags(content) {
101
+ // User mentions (from cache)
102
+ const cache = this.botConnection.workspaceCache;
103
+ if (cache) {
104
+ // Pattern 1: @userId (24-char hex)
105
+ content = content.replace(/@([a-f0-9]{24})\b/gi, (match, userId) => {
106
+ const tag = this.createUserTagById(userId);
107
+ return tag || match;
108
+ });
109
+ // Pattern 2: @"Full Name" (quoted)
110
+ content = content.replace(/@"([^"]+)"/g, (match, name) => {
111
+ const tag = this.createUserTagByName(name);
112
+ return tag || match;
113
+ });
114
+ // Pattern 3: @FirstName (single word, capitalized)
115
+ content = content.replace(/@([A-Z][a-z]+)\b/g, (match, name) => {
116
+ const tag = this.createUserTagByName(name);
117
+ return tag || match;
118
+ });
119
+ }
120
+ // Activity tags (#activityId and #"Name") are handled by
121
+ // resolveActivityTags (async) which runs before this
122
+ return content;
123
+ }
124
+ /**
125
+ * Resolve user tags - look up names for IDs via API when not in cache
126
+ * Handles @userId (24-char hex) format
127
+ */
128
+ async resolveUserTags(content) {
129
+ const pattern = /@([a-f0-9]{24})\b/gi;
130
+ const matches = [...content.matchAll(pattern)];
131
+ for (const match of matches) {
132
+ const userId = match[1];
133
+ // Try cache first
134
+ const cachedTag = this.createUserTagById(userId);
135
+ if (cachedTag) {
136
+ content = content.replace(match[0], cachedTag);
137
+ this.logger.debug("User tag from cache", { userId });
138
+ continue;
139
+ }
140
+ // Fallback to API
141
+ try {
142
+ this.logger.debug("Resolving user tag via API", { userId });
143
+ const result = await this.callMcpTool("search_workspace_users", { query: userId });
144
+ const resultText = result?.content?.[0]?.text;
145
+ if (resultText) {
146
+ // Parse the JSON array from the response
147
+ // Response format: "Found X users:\n\n```json\n[...]\n```"
148
+ const jsonMatch = resultText.match(/```json\s*([\s\S]*?)\s*```/);
149
+ if (jsonMatch) {
150
+ const users = JSON.parse(jsonMatch[1]);
151
+ const user = users.find((u) => u._id === userId);
152
+ if (user) {
153
+ const displayName = `${user.firstname || ''} ${user.lastname || ''}`.trim() || user.fullName || userId;
154
+ const tag = this.formatHailerTag(userId, displayName);
155
+ content = content.replace(match[0], tag);
156
+ this.logger.debug("Created user tag from API", { userId, displayName });
157
+ }
158
+ }
159
+ }
160
+ }
161
+ catch (error) {
162
+ this.logger.warn("Failed to resolve user tag", { userId, error });
163
+ // Leave as @userId - won't be clickable but visible
164
+ }
165
+ }
166
+ return content;
167
+ }
168
+ /**
169
+ * Resolve activity tags - look up names for IDs and IDs for names
170
+ * Handles both #activityId and #"Activity Name" formats
171
+ */
172
+ async resolveActivityTags(content) {
173
+ // Pattern 1: #activityId (24-char hex) - need to look up the name
174
+ const activityIdPattern = /#([a-f0-9]{24})\b/gi;
175
+ const idMatches = [...content.matchAll(activityIdPattern)];
176
+ for (const match of idMatches) {
177
+ const targetId = match[1];
178
+ try {
179
+ this.logger.debug("Resolving tag by ID", { targetId });
180
+ // Try as activity first
181
+ const result = await this.callMcpTool("show_activity_by_id", {
182
+ activityId: targetId,
183
+ });
184
+ const resultText = result?.content?.[0]?.text;
185
+ if (resultText) {
186
+ const jsonMatch = resultText.match(/\{[\s\S]*\}/);
187
+ if (jsonMatch) {
188
+ const parsed = JSON.parse(jsonMatch[0]);
189
+ const activityName = parsed.name;
190
+ if (activityName) {
191
+ const tag = this.formatHailerTag(targetId, activityName);
192
+ content = content.replace(match[0], tag);
193
+ this.logger.debug("Created activity tag", { targetId, activityName });
194
+ continue;
195
+ }
196
+ }
197
+ }
198
+ // Fallback: try as discussion ID
199
+ this.logger.debug("Activity lookup failed, trying as discussion", { targetId });
200
+ const discResult = await this.callMcpTool("get_activity_from_discussion", {
201
+ discussionId: targetId,
202
+ });
203
+ const discText = discResult?.content?.[0]?.text;
204
+ if (discText) {
205
+ const discJson = discText.match(/\{[\s\S]*\}/);
206
+ if (discJson) {
207
+ const discParsed = JSON.parse(discJson[0]);
208
+ const discName = discParsed.name || discParsed.activityName;
209
+ if (discName) {
210
+ const tag = this.formatHailerTag(targetId, discName);
211
+ content = content.replace(match[0], tag);
212
+ this.logger.debug("Created discussion tag", { targetId, discName });
213
+ continue;
214
+ }
215
+ }
216
+ }
217
+ // Last resort: try loading discussion directly for its name
218
+ try {
219
+ const discLoad = await this.botConnection.client.socket.request("messenger.load_discussions", [[targetId]]);
220
+ if (discLoad?.[targetId]?.name) {
221
+ const tag = this.formatHailerTag(targetId, discLoad[targetId].name);
222
+ content = content.replace(match[0], tag);
223
+ this.logger.debug("Created discussion tag from load", { targetId, name: discLoad[targetId].name });
224
+ }
225
+ }
226
+ catch {
227
+ this.logger.debug("Discussion load also failed", { targetId });
228
+ }
229
+ }
230
+ catch (error) {
231
+ this.logger.warn("Failed to resolve ID tag", { targetId, error });
232
+ }
233
+ }
234
+ // Pattern 2: #"Activity Name" - need to look up the ID
235
+ // This is harder because list_activities requires workflowId/phaseId
236
+ // For now, search in each workflow until we find a match
237
+ const activityNamePattern = /#"([^"]+)"/g;
238
+ const nameMatches = [...content.matchAll(activityNamePattern)];
239
+ for (const match of nameMatches) {
240
+ const activityName = match[1];
241
+ try {
242
+ // Get list of workflows first
243
+ const workflowsResult = await this.callMcpTool("list_workflows_minimal", {});
244
+ const workflowsText = workflowsResult?.content?.[0]?.text;
245
+ if (workflowsText) {
246
+ // Extract workflow IDs from the response (format varies)
247
+ const workflowIdMatches = workflowsText.matchAll(/"_id":\s*"([a-f0-9]{24})"/gi);
248
+ const workflowIds = [...workflowIdMatches].map(m => m[1]);
249
+ // Search each workflow for the activity
250
+ for (const workflowId of workflowIds.slice(0, 5)) { // Limit to first 5 workflows
251
+ try {
252
+ const phasesResult = await this.callMcpTool("list_workflow_phases", { workflowId });
253
+ const phasesText = phasesResult?.content?.[0]?.text;
254
+ if (phasesText) {
255
+ const phaseIdMatches = phasesText.matchAll(/"_id":\s*"([a-f0-9]{24})"/gi);
256
+ const phaseIds = [...phaseIdMatches].map(m => m[1]);
257
+ for (const phaseId of phaseIds.slice(0, 3)) { // Limit to first 3 phases
258
+ const activitiesResult = await this.callMcpTool("list_activities", {
259
+ workflowId,
260
+ phaseId,
261
+ search: activityName,
262
+ limit: 1,
263
+ });
264
+ const activitiesText = activitiesResult?.content?.[0]?.text;
265
+ if (activitiesText) {
266
+ // Look for activity ID and name in the response
267
+ const activityIdMatch = activitiesText.match(/"_id":\s*"([a-f0-9]{24})"/i);
268
+ if (activityIdMatch) {
269
+ const foundId = activityIdMatch[1];
270
+ const tag = this.formatHailerTag(foundId, activityName);
271
+ content = content.replace(match[0], tag);
272
+ break; // Found it, stop searching
273
+ }
274
+ }
275
+ }
276
+ }
277
+ }
278
+ catch {
279
+ // Continue to next workflow
280
+ }
281
+ }
282
+ }
283
+ }
284
+ catch (error) {
285
+ this.logger.warn("Failed to resolve activity name tag", { activityName, error });
286
+ // Leave the original #"Name" in place
287
+ }
288
+ }
289
+ return content;
290
+ }
291
+ /**
292
+ * Convert Hailer URLs to Hailer tags
293
+ * Handles:
294
+ * - https://app.hailer.com/#/activities/{activityId}
295
+ * - https://app.hailer.com/#/discussions/{discussionId}
296
+ */
297
+ async resolveHailerUrls(content) {
298
+ // Pattern for activity URLs
299
+ const activityUrlPattern = /https?:\/\/app\.hailer\.com\/#\/activities\/([a-f0-9]{24})/gi;
300
+ const activityMatches = [...content.matchAll(activityUrlPattern)];
301
+ for (const match of activityMatches) {
302
+ const activityId = match[1];
303
+ try {
304
+ const result = await this.callMcpTool("show_activity_by_id", { activityId });
305
+ const resultText = result?.content?.[0]?.text;
306
+ if (resultText) {
307
+ const jsonMatch = resultText.match(/\{[\s\S]*\}/);
308
+ if (jsonMatch) {
309
+ const parsed = JSON.parse(jsonMatch[0]);
310
+ const activityName = parsed.name;
311
+ if (activityName) {
312
+ const tag = this.formatHailerTag(activityId, activityName);
313
+ content = content.replace(match[0], tag);
314
+ this.logger.debug("Converted activity URL to tag", { activityId, activityName });
315
+ }
316
+ }
317
+ }
318
+ }
319
+ catch (error) {
320
+ this.logger.debug("Failed to resolve activity URL", { activityId, error });
321
+ // Leave URL as-is
322
+ }
323
+ }
324
+ // Pattern for discussion URLs - need to look up the linked activity
325
+ const discussionUrlPattern = /https?:\/\/app\.hailer\.com\/#\/discussions\/([a-f0-9]{24})/gi;
326
+ const discussionMatches = [...content.matchAll(discussionUrlPattern)];
327
+ for (const match of discussionMatches) {
328
+ const discussionId = match[1];
329
+ try {
330
+ // Load discussion to get linked activity
331
+ const discussionResponse = await this.botConnection.client.socket.request("messenger.load_discussions", [[discussionId]]);
332
+ if (discussionResponse && discussionResponse[discussionId]) {
333
+ const discData = discussionResponse[discussionId];
334
+ const linkedActivityId = discData.linked_activity;
335
+ const discussionName = discData.name;
336
+ if (linkedActivityId) {
337
+ // It's an activity discussion - use the activity ID for the tag
338
+ const tag = this.formatHailerTag(linkedActivityId, discussionName || "Activity");
339
+ content = content.replace(match[0], tag);
340
+ this.logger.debug("Converted discussion URL to activity tag", { discussionId, linkedActivityId, discussionName });
341
+ }
342
+ else if (discussionName) {
343
+ // It's a standalone discussion - use discussion name but discussion ID
344
+ const tag = this.formatHailerTag(discussionId, discussionName);
345
+ content = content.replace(match[0], tag);
346
+ this.logger.debug("Converted discussion URL to discussion tag", { discussionId, discussionName });
347
+ }
348
+ }
349
+ }
350
+ catch (error) {
351
+ this.logger.debug("Failed to resolve discussion URL", { discussionId, error });
352
+ // Leave URL as-is
353
+ }
354
+ }
355
+ // Pattern for process/workflow URLs
356
+ const processUrlPattern = /https?:\/\/app\.hailer\.com\/#\/processes\/([a-f0-9]{24})/gi;
357
+ const processMatches = [...content.matchAll(processUrlPattern)];
358
+ for (const match of processMatches) {
359
+ const workflowId = match[1];
360
+ try {
361
+ // Look up workflow name from cache (processes = workflows in Hailer)
362
+ const workflow = this.botConnection.workspaceCache?.rawInit?.processes?.find((w) => w._id === workflowId);
363
+ if (workflow?.name) {
364
+ const tag = this.formatHailerTag(workflowId, workflow.name);
365
+ content = content.replace(match[0], tag);
366
+ this.logger.debug("Converted process URL to tag", { workflowId, workflowName: workflow.name });
367
+ }
368
+ }
369
+ catch (error) {
370
+ this.logger.debug("Failed to resolve process URL", { workflowId, error });
371
+ // Leave URL as-is
372
+ }
373
+ }
374
+ return content;
375
+ }
376
+ /**
377
+ * Extract link metadata from hailerTag formatted content
378
+ * Returns array of links for the messenger.send API
379
+ */
380
+ extractTagLinks(content) {
381
+ const links = [];
382
+ // Match all hailerTag patterns: [hailerTag|Name](id)
383
+ const tagPattern = /\[hailerTag\|[^\]]+\]\(([a-f0-9]{24})\)/gi;
384
+ const matches = content.matchAll(tagPattern);
385
+ for (const match of matches) {
386
+ const targetId = match[1];
387
+ // Determine if it's a user or activity based on context
388
+ // For now, check if it's in the user cache
389
+ const isUser = this.botConnection.workspaceCache?.usersById[targetId];
390
+ links.push({
391
+ target: targetId,
392
+ targetType: isUser ? 'user' : 'activity',
393
+ type: 'linked-to'
394
+ });
395
+ }
396
+ return links;
397
+ }
398
+ /**
399
+ * Format incoming message for LLM consumption
400
+ */
401
+ formatIncomingMessage(message) {
402
+ const priorityTag = message.priority === "high" ? " priority=\"high\"" : "";
403
+ const reasonAttr = message.priority === "high" ? ` reason="${message.priorityReason}"` : "";
404
+ const activityAttr = message.linkedActivityId ? ` activity_id="${message.linkedActivityId}"` : "";
405
+ return `<incoming discussion="${message.discussionId}"${activityAttr} from="${message.senderName}" user_id="${message.senderId}" timestamp="${new Date(message.timestamp).toISOString()}"${priorityTag}${reasonAttr}>
406
+ ${message.content}
407
+ </incoming>`;
408
+ }
409
+ }
410
+ exports.MessageFormatterService = MessageFormatterService;
411
+ //# sourceMappingURL=message-formatter.js.map
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Session Logger Service
3
+ *
4
+ * Manages per-activity session tracking and logging to Hailer workflows:
5
+ * - Tracks metrics (tokens, tool calls, messages)
6
+ * - Logs completed sessions to SESSION_LOG workflow
7
+ * - Manages idle session detection and flushing
8
+ */
9
+ import Anthropic from "@anthropic-ai/sdk";
10
+ import { Logger } from "../../lib/logger";
11
+ import { ActivitySession, McpToolCallback } from "./types";
12
+ import { WorkspaceSchemaCacheService } from "./workspace-schema-cache";
13
+ /** Memory entry loaded from SESSION_LOG */
14
+ export interface MemoryEntry {
15
+ sessionId: string;
16
+ timestamp: number;
17
+ summary: string;
18
+ linkedActivityId?: string;
19
+ }
20
+ /** Memory metadata generated from conversation for smart retrieval */
21
+ export interface SessionMemoryMetadata {
22
+ headline: string;
23
+ topics: string;
24
+ entities: string;
25
+ outcome: string;
26
+ summary: string;
27
+ }
28
+ /** Memory headline for quick context injection */
29
+ export interface MemoryHeadline {
30
+ sessionId: string;
31
+ timestamp: number;
32
+ headline: string;
33
+ topics: string;
34
+ outcome: string;
35
+ }
36
+ export declare class SessionLoggerService {
37
+ private agentDirectoryId;
38
+ private logger;
39
+ private callMcpTool;
40
+ private getDefaultTeamId;
41
+ private activitySessions;
42
+ private lastGlobalLogId;
43
+ private idleCheckTimer;
44
+ private anthropicClient;
45
+ private schemaCache;
46
+ private _currentWorkspaceId;
47
+ constructor(agentDirectoryId: string | null, logger: Logger, callMcpTool: McpToolCallback, getDefaultTeamId: () => string | undefined);
48
+ /**
49
+ * Set the workspace schema cache for dynamic ID lookup
50
+ */
51
+ setSchemaCache(cache: WorkspaceSchemaCacheService, workspaceId: string): void;
52
+ /**
53
+ * Get Session Log schema from cache for a specific workspace
54
+ */
55
+ private getSessionLogSchema;
56
+ /**
57
+ * Set Anthropic client for generating summaries
58
+ */
59
+ setAnthropicClient(client: Anthropic): void;
60
+ /**
61
+ * Get or create an activity session for tracking
62
+ * Multi-tenant: workspaceId is required for proper schema lookup
63
+ */
64
+ getOrCreateActivitySession(message: {
65
+ linkedActivityId?: string;
66
+ linkedActivityName?: string;
67
+ discussionId: string;
68
+ workspaceId: string;
69
+ }): ActivitySession;
70
+ /**
71
+ * Get session by key
72
+ */
73
+ getSession(key: string): ActivitySession | undefined;
74
+ /**
75
+ * Get all active sessions
76
+ */
77
+ getActiveSessions(): Map<string, ActivitySession>;
78
+ /**
79
+ * Check for idle sessions and flush them
80
+ * Called periodically by timer
81
+ */
82
+ checkAndFlushIdleSessions(): Promise<void>;
83
+ /**
84
+ * Flush a single activity session to the session log
85
+ * Set DISABLE_SESSION_LOG=true to skip persistence (saves 20+ tokens per session)
86
+ */
87
+ flushActivitySession(_key: string, session: ActivitySession): Promise<void>;
88
+ /**
89
+ * Flush all active sessions (called on shutdown)
90
+ */
91
+ flushAllSessions(): Promise<void>;
92
+ /**
93
+ * Build summary for an activity session
94
+ * Uses pre-generated metadata if available
95
+ */
96
+ private buildActivitySessionSummary;
97
+ /**
98
+ * Calculate cost in USD based on Haiku 4.5 pricing
99
+ * - Input (uncached): $0.80 / 1M tokens
100
+ * - Output: $4.00 / 1M tokens
101
+ * - Cache creation: $1.00 / 1M tokens (125% of input)
102
+ * - Cache read: $0.08 / 1M tokens (10% of input)
103
+ */
104
+ private calculateCost;
105
+ /**
106
+ * Start periodic idle session checking
107
+ */
108
+ startIdleCheckTimer(intervalMs?: number): void;
109
+ /**
110
+ * Stop idle check timer
111
+ */
112
+ stopIdleCheckTimer(): void;
113
+ /**
114
+ * Sanitize string to remove invalid Unicode surrogate pairs
115
+ * These can cause JSON parsing errors in the Anthropic API
116
+ */
117
+ private sanitizeString;
118
+ /**
119
+ * Generate conversation summary AND metadata for memory retrieval
120
+ * Returns structured data for smart session recall
121
+ */
122
+ private generateSessionMetadata;
123
+ /**
124
+ * Generate a conversation summary using LLM (legacy, uses new method internally)
125
+ */
126
+ private generateConversationSummary;
127
+ /**
128
+ * Load memory entries for an activity from SESSION_LOG
129
+ * Returns past conversation summaries for context injection
130
+ *
131
+ * NOTE: ActivityLink fields store objects {_id, name}, so we fetch recent
132
+ * session logs and filter client-side by checking the nested _id.
133
+ */
134
+ loadMemoryForActivity(activityId: string, workspaceId: string, limit?: number): Promise<MemoryEntry[]>;
135
+ /**
136
+ * Format memory entries for injection into conversation context
137
+ */
138
+ formatMemoryForContext(entries: MemoryEntry[]): string;
139
+ /**
140
+ * Load memory headlines for a discussion (for smart context injection)
141
+ * Returns lightweight headlines instead of full summaries
142
+ *
143
+ * @param discussionId - Discussion to load memory for
144
+ * @param workspaceId - Workspace context
145
+ * @param options - Search options
146
+ */
147
+ loadMemoryForDiscussion(discussionId: string, workspaceId: string, options?: {
148
+ limit?: number;
149
+ keywords?: string;
150
+ maxAgeDays?: number;
151
+ }): Promise<MemoryHeadline[]>;
152
+ /**
153
+ * Format memory headlines for context injection (lightweight)
154
+ * Returns ~50 tokens for 3 entries
155
+ */
156
+ formatHeadlinesForContext(entries: MemoryHeadline[]): string;
157
+ /**
158
+ * Format timestamp as relative time (e.g., "2h ago", "yesterday")
159
+ */
160
+ private formatTimeAgo;
161
+ }
162
+ //# sourceMappingURL=session-logger.d.ts.map