@hailer/mcp 0.1.8 → 0.1.9

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 (135) hide show
  1. package/.claude/agents/agent-dmitri-activity-crud.md +3 -1
  2. package/.claude/agents/agent-giuseppe-app-builder.md +11 -12
  3. package/.claude/agents/agent-kenji-data-reader.md +5 -3
  4. package/.claude/skills/hailer-app-builder/SKILL.md +506 -0
  5. package/.claude/skills/publish-hailer-app/SKILL.md +169 -0
  6. package/.claude/skills/tool-parameter-usage/SKILL.md +112 -0
  7. package/CLAUDE.md +6 -2
  8. package/REFACTOR_STATUS.md +127 -0
  9. package/dist/cli.js +0 -0
  10. package/dist/client/agents/base.d.ts +202 -0
  11. package/dist/client/agents/base.js +737 -0
  12. package/dist/client/agents/definitions.d.ts +53 -0
  13. package/dist/client/agents/definitions.js +178 -0
  14. package/dist/client/agents/orchestrator.d.ts +119 -0
  15. package/dist/client/agents/orchestrator.js +760 -0
  16. package/dist/client/agents/specialist.d.ts +86 -0
  17. package/dist/client/agents/specialist.js +340 -0
  18. package/dist/client/bot-manager.d.ts +44 -0
  19. package/dist/client/bot-manager.js +173 -0
  20. package/dist/client/chat-agent-daemon.d.ts +464 -0
  21. package/dist/client/chat-agent-daemon.js +1774 -0
  22. package/dist/client/daemon-factory.d.ts +106 -0
  23. package/dist/client/daemon-factory.js +301 -0
  24. package/dist/client/factory.d.ts +107 -0
  25. package/dist/client/factory.js +304 -0
  26. package/dist/client/index.d.ts +17 -0
  27. package/dist/client/index.js +38 -0
  28. package/dist/client/multi-bot-manager.d.ts +18 -0
  29. package/dist/client/multi-bot-manager.js +88 -1
  30. package/dist/client/orchestrator-daemon.d.ts +87 -0
  31. package/dist/client/orchestrator-daemon.js +444 -0
  32. package/dist/client/services/agent-registry.d.ts +108 -0
  33. package/dist/client/services/agent-registry.js +630 -0
  34. package/dist/client/services/conversation-manager.d.ts +50 -0
  35. package/dist/client/services/conversation-manager.js +136 -0
  36. package/dist/client/services/mcp-client.d.ts +48 -0
  37. package/dist/client/services/mcp-client.js +105 -0
  38. package/dist/client/services/message-classifier.d.ts +37 -0
  39. package/dist/client/services/message-classifier.js +187 -0
  40. package/dist/client/services/message-formatter.d.ts +84 -0
  41. package/dist/client/services/message-formatter.js +353 -0
  42. package/dist/client/services/session-logger.d.ts +106 -0
  43. package/dist/client/services/session-logger.js +446 -0
  44. package/dist/client/services/tool-executor.d.ts +41 -0
  45. package/dist/client/services/tool-executor.js +169 -0
  46. package/dist/client/services/workspace-schema-cache.d.ts +149 -0
  47. package/dist/client/services/workspace-schema-cache.js +732 -0
  48. package/dist/client/specialist-daemon.d.ts +77 -0
  49. package/dist/client/specialist-daemon.js +197 -0
  50. package/dist/client/specialists.d.ts +53 -0
  51. package/dist/client/specialists.js +178 -0
  52. package/dist/client/tool-schema-loader.d.ts +4 -3
  53. package/dist/client/tool-schema-loader.js +54 -8
  54. package/dist/client/types.d.ts +283 -55
  55. package/dist/client/types.js +113 -2
  56. package/dist/config.d.ts +1 -1
  57. package/dist/config.js +1 -1
  58. package/dist/core.d.ts +10 -2
  59. package/dist/core.js +43 -27
  60. package/dist/lib/logger.js +15 -3
  61. package/dist/mcp/UserContextCache.js +2 -2
  62. package/dist/mcp/hailer-clients.js +5 -5
  63. package/dist/mcp/signal-handler.js +27 -5
  64. package/dist/mcp/tools/activity.js +137 -65
  65. package/dist/mcp/tools/app-core.js +4 -140
  66. package/dist/mcp/tools/app-marketplace.js +15 -260
  67. package/dist/mcp/tools/app-member.js +2 -73
  68. package/dist/mcp/tools/app-scaffold.js +146 -87
  69. package/dist/mcp/tools/discussion.js +348 -73
  70. package/dist/mcp/tools/insight.js +74 -190
  71. package/dist/mcp/tools/workflow.js +20 -94
  72. package/dist/mcp/utils/hailer-api-client.d.ts +4 -2
  73. package/dist/mcp/utils/hailer-api-client.js +24 -10
  74. package/dist/mcp-server.d.ts +4 -0
  75. package/dist/mcp-server.js +24 -4
  76. package/dist/routes/agents.d.ts +44 -0
  77. package/dist/routes/agents.js +311 -0
  78. package/dist/services/agent-credential-store.d.ts +73 -0
  79. package/dist/services/agent-credential-store.js +212 -0
  80. package/lineup-manager/dist/assets/index-8ce6041d.css +1 -0
  81. package/lineup-manager/dist/assets/index-e168f265.js +600 -0
  82. package/lineup-manager/dist/index.html +15 -0
  83. package/lineup-manager/dist/manifest.json +17 -0
  84. package/lineup-manager/dist/vite.svg +1 -0
  85. package/package.json +1 -1
  86. package/dist/client/adaptive-documentation-bot.d.ts +0 -106
  87. package/dist/client/adaptive-documentation-bot.js +0 -464
  88. package/dist/client/adaptive-documentation-types.d.ts +0 -66
  89. package/dist/client/adaptive-documentation-types.js +0 -9
  90. package/dist/client/agent-activity-bot.d.ts +0 -51
  91. package/dist/client/agent-activity-bot.js +0 -166
  92. package/dist/client/agent-tracker.d.ts +0 -499
  93. package/dist/client/agent-tracker.js +0 -659
  94. package/dist/client/description-updater.d.ts +0 -56
  95. package/dist/client/description-updater.js +0 -259
  96. package/dist/client/log-parser.d.ts +0 -72
  97. package/dist/client/log-parser.js +0 -387
  98. package/dist/client/mcp-assistant.d.ts +0 -21
  99. package/dist/client/mcp-assistant.js +0 -58
  100. package/dist/client/mcp-client.d.ts +0 -50
  101. package/dist/client/mcp-client.js +0 -538
  102. package/dist/client/message-processor.d.ts +0 -35
  103. package/dist/client/message-processor.js +0 -357
  104. package/dist/client/providers/anthropic-provider.d.ts +0 -19
  105. package/dist/client/providers/anthropic-provider.js +0 -645
  106. package/dist/client/providers/assistant-provider.d.ts +0 -17
  107. package/dist/client/providers/assistant-provider.js +0 -51
  108. package/dist/client/providers/llm-provider.d.ts +0 -47
  109. package/dist/client/providers/llm-provider.js +0 -367
  110. package/dist/client/providers/openai-provider.d.ts +0 -23
  111. package/dist/client/providers/openai-provider.js +0 -630
  112. package/dist/client/simple-llm-caller.d.ts +0 -19
  113. package/dist/client/simple-llm-caller.js +0 -100
  114. package/dist/client/skill-generator.d.ts +0 -81
  115. package/dist/client/skill-generator.js +0 -386
  116. package/dist/client/test-adaptive-bot.d.ts +0 -9
  117. package/dist/client/test-adaptive-bot.js +0 -82
  118. package/dist/client/token-pricing.d.ts +0 -38
  119. package/dist/client/token-pricing.js +0 -127
  120. package/dist/client/token-tracker.d.ts +0 -232
  121. package/dist/client/token-tracker.js +0 -457
  122. package/dist/client/token-usage-bot.d.ts +0 -53
  123. package/dist/client/token-usage-bot.js +0 -153
  124. package/dist/client/tool-executor.d.ts +0 -69
  125. package/dist/client/tool-executor.js +0 -159
  126. package/dist/lib/materialize.d.ts +0 -3
  127. package/dist/lib/materialize.js +0 -101
  128. package/dist/lib/normalizedName.d.ts +0 -7
  129. package/dist/lib/normalizedName.js +0 -48
  130. package/dist/lib/terminal-prompt.d.ts +0 -9
  131. package/dist/lib/terminal-prompt.js +0 -108
  132. package/dist/mcp/tools/skill.d.ts +0 -10
  133. package/dist/mcp/tools/skill.js +0 -279
  134. package/dist/mcp/tools/workflow-template.d.ts +0 -19
  135. package/dist/mcp/tools/workflow-template.js +0 -822
@@ -0,0 +1,760 @@
1
+ "use strict";
2
+ /**
3
+ * Orchestrator Daemon (HAL)
4
+ *
5
+ * The main conversational bot that handles general chat and coordinates
6
+ * with specialist bots when tasks are too complex.
7
+ *
8
+ * HAL can:
9
+ * - Handle general conversation and simple queries
10
+ * - Detect when a task needs specialist help
11
+ * - Invite specialist bots to the discussion
12
+ * - Hand off context to specialists
13
+ * - Summarize specialist responses for users
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.OrchestratorDaemon = void 0;
17
+ const base_1 = require("./base");
18
+ const definitions_1 = require("./definitions");
19
+ const logger_1 = require("../../lib/logger");
20
+ class OrchestratorDaemon extends base_1.ChatAgentDaemon {
21
+ orchestratorLogger;
22
+ specialists = new Map();
23
+ activeSpecialistsInDiscussion = new Map(); // discussionId -> Set<specialistUserId>
24
+ specialistUserIds = new Map(); // specialistKey -> userId
25
+ toolsUsedInCurrentMessage = false; // Track if tools were used in current message processing
26
+ lastToolsUsed = []; // Track which tools were used (for silent success detection)
27
+ lastToolsFailed = false; // Track if any tool failed (to allow error reporting)
28
+ // Tools that should NOT trigger confirmation messages back to source chat (on SUCCESS only)
29
+ static SILENT_SUCCESS_TOOLS = new Set(['join_discussion']);
30
+ // Cross-discussion memory - remembers recent activity context
31
+ lastKnownActivityId = null;
32
+ lastKnownActivityName = null;
33
+ lastKnownActivityTime = 0;
34
+ static CONTEXT_MEMORY_TIMEOUT = 5 * 60 * 1000; // 5 minutes
35
+ constructor(config) {
36
+ super(config);
37
+ this.orchestratorLogger = (0, logger_1.createLogger)({
38
+ component: "OrchestratorDaemon",
39
+ botId: config.botClient.userId,
40
+ });
41
+ // Register specialists from config
42
+ for (const [key, specialist] of Object.entries(definitions_1.SPECIALISTS)) {
43
+ this.specialists.set(key, specialist);
44
+ }
45
+ // Set specialist user IDs if provided
46
+ if (config.specialistUserIds) {
47
+ this.specialistUserIds = config.specialistUserIds;
48
+ }
49
+ }
50
+ // ===== AGENT DIRECTORY OVERRIDES =====
51
+ /**
52
+ * Override agent name for Agent Directory
53
+ * Uses the actual Hailer user name from BotClient (set in workspace)
54
+ */
55
+ getAgentName() {
56
+ // Use parent implementation which gets name from BotClient
57
+ return super.getAgentName();
58
+ }
59
+ /**
60
+ * Override agent description for Agent Directory
61
+ */
62
+ getAgentDescription() {
63
+ return "HAL - the main Hailer Assistant orchestrator. Handles general conversation and coordinates specialist bots for complex tasks.";
64
+ }
65
+ /**
66
+ * Override Position details for Orchestrator
67
+ */
68
+ getPositionDetails() {
69
+ return {
70
+ name: "HAL Orchestrator",
71
+ purpose: "Main point of contact for users. Handles general conversation, triages requests, and coordinates specialist bots for complex tasks.",
72
+ personaTone: "Sharp, efficient, and helpful. Professional but approachable. Uses clear, concise language.",
73
+ coreCapabilities: "- Monitor all workspace discussions\n- Respond to general queries and greetings\n- Detect when specialist help is needed\n- Invite specialists to discussions\n- Coordinate multi-step workflows\n- Execute MCP tools for data operations",
74
+ boundaries: "- Never fabricate data - always use tools\n- Don't attempt complex technical tasks alone\n- Hand off to specialists for: bulk operations, report creation, workflow setup\n- Don't share credentials or sensitive config",
75
+ };
76
+ }
77
+ /**
78
+ * Orchestrator only needs basic tools - complex ops go to specialists
79
+ */
80
+ getToolWhitelist() {
81
+ return [
82
+ // Workflow discovery
83
+ "list_workflows",
84
+ "list_workflow_phases",
85
+ "get_workflow_schema",
86
+ "list_workflows_minimal",
87
+ // Activity operations
88
+ "list_activities",
89
+ "show_activity_by_id",
90
+ "count_activities",
91
+ "create_activity",
92
+ "update_activity",
93
+ // Discussion tools
94
+ "join_discussion",
95
+ "add_discussion_message",
96
+ "invite_discussion_members",
97
+ "fetch_discussion_messages",
98
+ "get_activity_from_discussion",
99
+ "list_my_discussions",
100
+ // User lookup
101
+ "search_workspace_users",
102
+ // App tools (read-only)
103
+ "list_apps",
104
+ ];
105
+ }
106
+ /**
107
+ * Preprocess tool input - inject context for certain tools
108
+ * Uses cross-discussion memory to maintain context awareness
109
+ */
110
+ preprocessToolInput(toolName, input) {
111
+ // Force compact output for schema tools (avoid verbose field dumps in discussions)
112
+ if (toolName === "get_workflow_schema") {
113
+ return { ...input, compact: true };
114
+ }
115
+ // Auto-inject sourceActivityId for join_discussion
116
+ if (toolName === "join_discussion") {
117
+ // Debug: Log current context state
118
+ this.orchestratorLogger.debug("join_discussion context check", {
119
+ currentLinkedActivityId: this.currentLinkedActivityId || "none",
120
+ lastKnownActivityId: this.lastKnownActivityId || "none",
121
+ inputSourceActivityId: input.sourceActivityId || "none",
122
+ currentDiscussionId: this.currentDiscussionId || "none",
123
+ });
124
+ if (!input.sourceActivityId) {
125
+ // First try current message's linked activity
126
+ const currentActivityId = this.currentLinkedActivityId;
127
+ // Fall back to recent memory if within timeout
128
+ const memoryStillValid = Date.now() - this.lastKnownActivityTime < OrchestratorDaemon.CONTEXT_MEMORY_TIMEOUT;
129
+ const sourceActivityId = currentActivityId || (memoryStillValid ? this.lastKnownActivityId : null);
130
+ if (sourceActivityId) {
131
+ this.orchestratorLogger.info("Auto-injected sourceActivityId", {
132
+ sourceActivityId,
133
+ fromCurrentMessage: !!currentActivityId,
134
+ fromMemory: !currentActivityId && !!sourceActivityId,
135
+ });
136
+ input = { ...input, sourceActivityId };
137
+ }
138
+ else {
139
+ this.orchestratorLogger.debug("No sourceActivityId available to inject", {
140
+ currentLinkedActivityId: this.currentLinkedActivityId,
141
+ lastKnownActivityId: this.lastKnownActivityId,
142
+ memoryStillValid,
143
+ });
144
+ }
145
+ }
146
+ // Always inject a default welcomeReason if user is being invited
147
+ // This ensures welcome messages are always posted
148
+ if (input.inviteUserId && !input.welcomeReason) {
149
+ const reason = this.lastKnownActivityName
150
+ ? `Added from ${this.lastKnownActivityName} discussion`
151
+ : "Added to this discussion";
152
+ this.orchestratorLogger.debug("Auto-injected welcomeReason", { reason });
153
+ input = { ...input, welcomeReason: reason };
154
+ }
155
+ }
156
+ return input;
157
+ }
158
+ /**
159
+ * Override to detect tool failures for silent success feature
160
+ */
161
+ async executeToolsAndContinue(toolUseBlocks, originalMessage) {
162
+ // Reset failure flag before executing tools
163
+ this.lastToolsFailed = false;
164
+ // Get current activity session
165
+ const sessionKey = this.currentLinkedActivityId || this.currentDiscussionId || "default";
166
+ const session = this.sessionLogger.getSession(sessionKey);
167
+ // Store the user's request that triggered these tool calls (for context)
168
+ if (session && !session.triggerRequest) {
169
+ session.triggerRequest = originalMessage.content.substring(0, 500);
170
+ session.requestedBy = originalMessage.senderName;
171
+ session.requestedById = originalMessage.senderId;
172
+ }
173
+ // Execute tools using the tool executor service
174
+ const toolResults = await this.toolExecutor.executeTools(toolUseBlocks, {
175
+ session,
176
+ preprocessToolInput: this.preprocessToolInput.bind(this),
177
+ });
178
+ // Check if any tool failed (detect various error patterns)
179
+ const anyToolFailed = toolResults.some(r => {
180
+ if (r.is_error)
181
+ return true;
182
+ // Check for error patterns in content (MCP tools return error text)
183
+ const content = r.content.toLowerCase();
184
+ return content.includes('error') || content.includes('failed') || content.includes('❌');
185
+ });
186
+ if (anyToolFailed) {
187
+ this.lastToolsFailed = true;
188
+ this.orchestratorLogger.debug("Tool failure detected", {
189
+ tools: this.lastToolsUsed,
190
+ failedCount: toolResults.filter(r => r.is_error).length,
191
+ });
192
+ }
193
+ // Get conversation for this discussion
194
+ const conversation = this.conversationManager.getConversation(originalMessage.discussionId);
195
+ // Add tool results to conversation
196
+ conversation.push({
197
+ role: "user",
198
+ content: toolResults,
199
+ });
200
+ // Continue with LLM
201
+ const response = await this.client.messages.create({
202
+ model: this.config.model || "claude-haiku-4-5-20251001",
203
+ max_tokens: 2000,
204
+ system: this.getSystemPrompt(),
205
+ messages: conversation,
206
+ tools: this.minimalTools,
207
+ });
208
+ // Track token usage in session
209
+ if (session && response.usage) {
210
+ session.metrics.inputTokens += response.usage.input_tokens;
211
+ session.metrics.outputTokens += response.usage.output_tokens;
212
+ session.lastActivityTime = Date.now();
213
+ }
214
+ // Recursively handle (might need more tools or finally respond)
215
+ await this.handleLlmResponse(response, originalMessage);
216
+ }
217
+ /**
218
+ * Update cross-discussion memory when processing messages
219
+ * Call this when entering an activity discussion to remember context
220
+ */
221
+ updateContextMemory(activityId, activityName) {
222
+ if (activityId) {
223
+ this.lastKnownActivityId = activityId;
224
+ this.lastKnownActivityName = activityName;
225
+ this.lastKnownActivityTime = Date.now();
226
+ this.orchestratorLogger.debug("Updated context memory", { activityId, activityName });
227
+ }
228
+ }
229
+ /**
230
+ * Override to update cross-discussion memory when entering activity discussions
231
+ */
232
+ async extractIncomingMessage(signal) {
233
+ const message = await super.extractIncomingMessage(signal);
234
+ // Update context memory if this message is from an activity discussion
235
+ if (message?.linkedActivityId) {
236
+ this.updateContextMemory(message.linkedActivityId, message.linkedActivityName || null);
237
+ }
238
+ return message;
239
+ }
240
+ /**
241
+ * Register a specialist's Hailer user ID
242
+ * Called during initialization when we know the specialist bot's user ID
243
+ */
244
+ registerSpecialistUserId(specialistKey, userId) {
245
+ this.specialistUserIds.set(specialistKey, userId);
246
+ const specialist = this.specialists.get(specialistKey);
247
+ if (specialist) {
248
+ specialist.botUserId = userId;
249
+ this.orchestratorLogger.info("Specialist registered", {
250
+ key: specialistKey,
251
+ name: specialist.name,
252
+ userId,
253
+ });
254
+ }
255
+ }
256
+ /**
257
+ * Check if a specialist is already active in a discussion
258
+ */
259
+ isSpecialistActiveInDiscussion(discussionId, specialistUserId) {
260
+ const active = this.activeSpecialistsInDiscussion.get(discussionId);
261
+ return active?.has(specialistUserId) ?? false;
262
+ }
263
+ /**
264
+ * Mark a specialist as active in a discussion
265
+ */
266
+ markSpecialistActive(discussionId, specialistUserId) {
267
+ if (!this.activeSpecialistsInDiscussion.has(discussionId)) {
268
+ this.activeSpecialistsInDiscussion.set(discussionId, new Set());
269
+ }
270
+ this.activeSpecialistsInDiscussion.get(discussionId).add(specialistUserId);
271
+ }
272
+ /**
273
+ * Invite a specialist to a discussion
274
+ */
275
+ async inviteSpecialist(specialist, discussionId, handoffContext) {
276
+ const specialistUserId = specialist.botUserId;
277
+ if (!specialistUserId) {
278
+ this.orchestratorLogger.warn("Specialist has no user ID", {
279
+ name: specialist.name,
280
+ });
281
+ return false;
282
+ }
283
+ // Check if already active
284
+ if (this.isSpecialistActiveInDiscussion(discussionId, specialistUserId)) {
285
+ this.orchestratorLogger.debug("Specialist already in discussion", {
286
+ name: specialist.name,
287
+ discussionId,
288
+ });
289
+ // Just tag them again
290
+ await this.postResponse(discussionId, `@"${specialist.name}" - ${handoffContext}`);
291
+ return true;
292
+ }
293
+ try {
294
+ this.orchestratorLogger.info("Inviting specialist to discussion", {
295
+ name: specialist.name,
296
+ userId: specialistUserId,
297
+ discussionId,
298
+ });
299
+ // Invite using MCP tool
300
+ await this.callMcpTool("invite_discussion_members", {
301
+ discussionId,
302
+ userIds: [specialistUserId],
303
+ });
304
+ this.markSpecialistActive(discussionId, specialistUserId);
305
+ // Post handoff message
306
+ await this.postResponse(discussionId, `@"${specialist.name}" - ${handoffContext}`);
307
+ this.orchestratorLogger.info("Specialist invited successfully", {
308
+ name: specialist.name,
309
+ discussionId,
310
+ });
311
+ return true;
312
+ }
313
+ catch (error) {
314
+ this.orchestratorLogger.error("Failed to invite specialist", {
315
+ name: specialist.name,
316
+ discussionId,
317
+ error: error instanceof Error ? error.message : String(error),
318
+ });
319
+ return false;
320
+ }
321
+ }
322
+ /**
323
+ * Override system prompt to include orchestrator capabilities
324
+ */
325
+ getSystemPrompt() {
326
+ const now = new Date();
327
+ const { firstName, lastName } = this.getAgentName();
328
+ const fullName = `${firstName} ${lastName}`.trim();
329
+ // Build specialist info for prompt
330
+ const specialistInfo = Array.from(this.specialists.entries())
331
+ .map(([key, spec]) => {
332
+ const hasUserId = !!spec.botUserId;
333
+ return `- **${spec.name}** ${hasUserId ? "(available)" : "(not configured)"}
334
+ Expertise: ${spec.expertise.join(", ")}
335
+ Triggers: ${spec.triggerKeywords.slice(0, 5).join(", ")}`;
336
+ })
337
+ .join("\n\n");
338
+ return `<identity>
339
+ You are ${fullName} - the Hailer Assistant. Sharp, efficient, and helpful.
340
+ Bot ID: ${this.botClient.userId}
341
+
342
+ You're the main point of contact for users. You handle general conversation
343
+ and simple tasks yourself, but can bring in specialist bots for complex work.
344
+ </identity>
345
+
346
+ <current_time>${now.toISOString()}</current_time>
347
+
348
+ <personality>
349
+ **BUSINESS MODE** (default):
350
+ - Professional, direct, competent
351
+ - Get things done efficiently
352
+ - Provide accurate information
353
+
354
+ **SARCASM MODE** (for ridiculous requests):
355
+ - Dry wit, not mean-spirited
356
+ - Still help after the gentle mockery
357
+ </personality>
358
+
359
+ <decision_framework>
360
+ For each message, decide:
361
+
362
+ 1. **HIGH PRIORITY** (priority="high") - ALWAYS RESPOND:
363
+ - Direct messages (1:1) -> respond helpfully
364
+ - @mentions -> respond (even if just a greeting - acknowledge and ask how you can help)
365
+ - Replies to your messages -> respond
366
+ - **NEVER use <ignore/> for HIGH priority. Always acknowledge the user.**
367
+
368
+ 2. **NORMAL PRIORITY** (general chat) - STRICT FILTERING:
369
+
370
+ **RESPOND ONLY IF the message:**
371
+ - Explicitly asks about Hailer (workflows, activities, insights, apps, discussions)
372
+ - Requests to find/list/create/update workspace data
373
+ - Discusses a specific activity, customer, project, or workflow by name
374
+ - You can genuinely help with workspace-related context
375
+ - Is a complex task needing specialist help
376
+
377
+ **IGNORE (output <ignore/>) for NORMAL priority if:**
378
+ - Random characters, gibberish, keyboard mashing (no real words, repeated patterns)
379
+ - General chit-chat unrelated to workspace ("how are you", jokes)
380
+ - Conversations between other users that don't need you
381
+ - Bare greetings without a question ("hi", "hey") - but NOT if HIGH priority!
382
+ - Off-topic discussions (sports, weather, personal chat)
383
+ - Anything you're uncertain about
384
+
385
+ **CRITICAL:** For NORMAL priority only - if no clear workspace-related question/task, output <ignore/>.
386
+ When in doubt about NORMAL priority, IGNORE. Your DEFAULT for normal priority is <ignore/>.
387
+
388
+ 3. **RESPOND FORMAT** (only when you have something helpful):
389
+ <respond discussion="DISCUSSION_ID">
390
+ Your response
391
+ </respond>
392
+
393
+ 4. **IGNORE FORMAT** (use liberally - this is your DEFAULT):
394
+ <ignore/>
395
+ </decision_framework>
396
+
397
+ <specialists>
398
+ You can invite specialist bots when tasks are too complex.
399
+
400
+ ${specialistInfo}
401
+
402
+ **When to invite a specialist:**
403
+ - Creating new workflows/pipelines
404
+ - Setting up reports/insights/dashboards
405
+ - Bulk operations (10+ items)
406
+ - Complex multi-step data tasks
407
+ - Workflow configuration changes
408
+
409
+ **When to handle yourself:**
410
+ - General chat, greetings
411
+ - Simple queries (list activities, show details)
412
+ - Single create/update operations
413
+ - Questions about the conversation
414
+ - Clarifying user requirements
415
+
416
+ <invite_syntax>
417
+ <invite specialist="hailerExpert">
418
+ Clear description of what you need done.
419
+ Include relevant context from the conversation.
420
+ </invite>
421
+ </invite_syntax>
422
+
423
+ <handoff_rules>
424
+ CRITICAL: When you've already gathered IDs (workflows, fields, phases), include them formatted in the handoff.
425
+
426
+ <bad_handoff>
427
+ "Create an insight for the Injuries workflow showing player data"
428
+ </bad_handoff>
429
+
430
+ <good_handoff>
431
+ "Create insight for Injuries workflow:
432
+ - workflowId: 691ffdf84217e9e8434e56ad
433
+ - Fields: { name: 'playerName', fieldId: '691ffdf84217e9e8434e56b1' }, { name: 'injuryType', fieldId: '691ffdf84217e9e8434e56b2' }
434
+ - Query: SELECT name as \"Activity\", playerName, injuryType FROM injuries ORDER BY name"
435
+ </good_handoff>
436
+
437
+ <insight_field_format>
438
+ Always format fields as: { name: 'Column Name', fieldId: 'FIELD_ID' }
439
+ Use 'fieldId' not 'id' for the field identifier.
440
+ </insight_field_format>
441
+ </handoff_rules>
442
+
443
+ <after_invite>
444
+ 1. I invite them to the discussion
445
+ 2. I post your handoff message mentioning them
446
+ 3. They see it and take action
447
+ </after_invite>
448
+ </specialists>
449
+
450
+ <your_tools>
451
+ You have access to basic MCP tools for simple operations:
452
+ - list_workflows, list_workflow_phases, get_workflow_schema
453
+ - list_activities, show_activity_by_id, count_activities
454
+ - create_activity, update_activity (single operations)
455
+ - search_workspace_users
456
+ - Discussion tools (join_discussion, add_discussion_message, invite_discussion_members)
457
+
458
+ For complex operations (workflow creation, insights, bulk ops), invite a specialist.
459
+
460
+ **CRITICAL for join_discussion when inviting users:**
461
+ ALWAYS pass these parameters from the incoming message:
462
+ - inviteUserId = user_id attribute
463
+ - sourceActivityId = activity_id attribute (creates "came from" link!)
464
+ - welcomeReason = why they're being invited
465
+
466
+ **join_discussion ID types:**
467
+ - HailerTags like [hailerTag|Name](ID) usually contain ACTIVITY IDs, not discussion IDs
468
+ - When user references an activity/player/customer by tag, use: activityId parameter
469
+ - Only use discussionId for direct discussion links (rare)
470
+ - The tool auto-detects: if discussionId fails, it tries as activityId
471
+
472
+ **SILENT SUCCESS for join_discussion:**
473
+ After successfully joining a discussion, do NOT post a confirmation message back to the source chat.
474
+ The action is self-evident (bot appears in target discussion). Only report ERRORS back to source chat.
475
+
476
+ **CRITICAL - Extract IDs from context:**
477
+ The <incoming> tag contains IDs. ALWAYS use them:
478
+ - activityId attribute → pass to show_activity_by_id, update_activity
479
+ - discussionId attribute → pass to get_activity_from_discussion, add_discussion_message
480
+
481
+ **NEVER call tools with empty parameters {}** - always extract from context first.
482
+
483
+ **update_activity formats:**
484
+ - Single mode: use \`activityId\` parameter
485
+ - Bulk mode (activities array): use \`_id\` inside each object, NOT \`activityId\`
486
+
487
+ **Field values must be plain:**
488
+ - numericunit: just the number → \`78\` (NOT \`{"type":"numericunit","value":78}\`)
489
+ - text: just the string → \`"hello"\`
490
+ - activitylink: just the ID → \`"abc123..."\`
491
+ </your_tools>
492
+
493
+ <tagging>
494
+ **Activity Tags:** #ACTIVITY_ID (24-char hex)
495
+ - Correct: "Check out #691ffe874217e9e8434e57fc"
496
+ - Wrong: "Check out #691ffe874217e9e8434e57fc (Name)" - name auto-displays
497
+
498
+ **User Mentions:** @"Full Name" or @userId
499
+ </tagging>
500
+
501
+ <agentic_loop>
502
+ **CRITICAL: Complete work before responding!**
503
+
504
+ You work in a single-turn loop. When you respond with <respond>, your turn ENDS.
505
+ There is NO "next turn" unless a new message arrives.
506
+
507
+ **WRONG - promising future work:**
508
+ <respond discussion="...">
509
+ I found 29 players. Let me check which ones are injured...
510
+ </respond>
511
+ → This posts the message but NEVER calls any tools!
512
+
513
+ **RIGHT - complete the work first:**
514
+ 1. Call list_activities with injury filter
515
+ 2. Get results
516
+ 3. THEN respond with the actual answer:
517
+ <respond discussion="...">
518
+ Found 3 injured players: #player1, #player2, #player3
519
+ </respond>
520
+
521
+ **RULE: Never say "let me check" or "give me a second" - just DO IT by calling tools.**
522
+ Only use <respond> when you have the FINAL answer to share.
523
+ </agentic_loop>
524
+
525
+ <rules>
526
+ - Be concise - snappy responses, not essays
527
+ - Use your memory - reference past conversations
528
+ - For HIGH priority: respond immediately
529
+ - For NORMAL priority: your DEFAULT is <ignore/>. Only break if you see a clear workspace question.
530
+ - **Gibberish test:** No recognizable words, no vowels, repeated patterns = <ignore/>
531
+ - **Relevance test:** Is this about workflows/activities/insights? If not = <ignore/>
532
+ - When inviting specialists, explain to the user what's happening
533
+ - After specialist responds, summarize for the user if needed
534
+ - **Complete work before responding** - Never say "I'm going to do X", just DO X with tool calls
535
+ </rules>`;
536
+ }
537
+ /**
538
+ * Override response handling to detect specialist invitations
539
+ */
540
+ async handleLlmResponse(response, originalMessage) {
541
+ // Get conversation for this discussion
542
+ const conversation = this.conversationManager.getConversation(originalMessage.discussionId);
543
+ // Add assistant response to conversation
544
+ // Cast response content to MessageParam content type (ContentBlock[] → ContentBlockParam[])
545
+ conversation.push({
546
+ role: "assistant",
547
+ content: response.content,
548
+ });
549
+ // Check for specialist invitation in text content
550
+ const textBlocks = response.content.filter((block) => block.type === "text");
551
+ const textContent = textBlocks.map((b) => b.text).join("\n");
552
+ // Look for <invite specialist="...">...</invite> pattern
553
+ const inviteMatch = textContent.match(/<invite specialist="(\w+)">([\s\S]*?)<\/invite>/);
554
+ if (inviteMatch) {
555
+ const [fullMatch, specialistKey, handoffContext] = inviteMatch;
556
+ const specialist = this.specialists.get(specialistKey);
557
+ if (specialist && specialist.botUserId) {
558
+ this.orchestratorLogger.info("LLM requested specialist invitation", {
559
+ specialist: specialistKey,
560
+ discussionId: originalMessage.discussionId,
561
+ });
562
+ // Extract any text before the invite tag to post as acknowledgment
563
+ const preInviteText = textContent
564
+ .substring(0, textContent.indexOf("<invite"))
565
+ .trim();
566
+ // Check for respond wrapper
567
+ const respondMatch = preInviteText.match(/<respond discussion="([^"]+)">([\s\S]*)/);
568
+ if (respondMatch) {
569
+ const [, discussionId, content] = respondMatch;
570
+ const cleanContent = content.replace(/<\/respond>.*$/s, "").trim();
571
+ if (cleanContent) {
572
+ await this.postResponse(discussionId, cleanContent);
573
+ }
574
+ }
575
+ else if (preInviteText) {
576
+ // No respond wrapper, but there's text - might be for high priority
577
+ if (originalMessage.priority === "high") {
578
+ await this.postResponse(originalMessage.discussionId, preInviteText);
579
+ }
580
+ }
581
+ // Invite the specialist
582
+ const invited = await this.inviteSpecialist(specialist, originalMessage.discussionId, handoffContext.trim());
583
+ if (!invited) {
584
+ // Failed to invite - let user know
585
+ await this.postResponse(originalMessage.discussionId, `I tried to bring in ${specialist.name} but couldn't reach them. Let me try handling this myself...`);
586
+ // Continue with normal handling
587
+ }
588
+ return; // Don't continue with normal response handling
589
+ }
590
+ else {
591
+ this.orchestratorLogger.warn("Specialist not available", {
592
+ key: specialistKey,
593
+ hasSpecialist: !!specialist,
594
+ hasUserId: !!specialist?.botUserId,
595
+ });
596
+ }
597
+ }
598
+ // Check for tool calls
599
+ const toolUseBlocks = response.content.filter((block) => block.type === "tool_use");
600
+ if (toolUseBlocks.length > 0) {
601
+ // Mark that tools were used - subsequent responses should be posted
602
+ this.toolsUsedInCurrentMessage = true;
603
+ // Track which tools were used (for silent success detection)
604
+ this.lastToolsUsed = toolUseBlocks.map(b => b.name);
605
+ // Execute tools and continue conversation
606
+ await this.executeToolsAndContinue(toolUseBlocks, originalMessage);
607
+ return;
608
+ }
609
+ // Check for regular response (no invite, no tools)
610
+ if (textBlocks.length > 0) {
611
+ const responseText = textContent.trim();
612
+ this.orchestratorLogger.info("LLM raw response", {
613
+ discussion: originalMessage.discussionId,
614
+ priority: originalMessage.priority,
615
+ responseLength: responseText.length,
616
+ fullResponse: responseText.substring(0, 500),
617
+ hasIgnoreTag: responseText.includes("<ignore"),
618
+ hasRespondTag: responseText.includes("<respond"),
619
+ });
620
+ // Check for IGNORE decision - remove from context to keep it clean
621
+ if (responseText.includes("<decision>IGNORE</decision>") ||
622
+ responseText.includes("<ignore")) {
623
+ this.orchestratorLogger.debug("LLM decided to ignore - removing from context", {
624
+ discussion: originalMessage.discussionId,
625
+ });
626
+ // Stop typing indicator
627
+ this.stopTypingIndicator();
628
+ // Remove both the assistant's response AND the incoming message from context
629
+ conversation.pop(); // Remove assistant response we just added
630
+ conversation.pop(); // Remove the incoming message
631
+ this.toolsUsedInCurrentMessage = false; // Reset for next message
632
+ return;
633
+ }
634
+ // Check for explicit response directive
635
+ const responseMatch = responseText.match(/<respond discussion="([^"]+)">([\s\S]*?)<\/respond>/);
636
+ if (responseMatch) {
637
+ const targetDiscussion = responseMatch[1];
638
+ const content = responseMatch[2].trim();
639
+ // SILENT SUCCESS: Skip confirmation if we just ran a silent tool successfully (not on error)
640
+ const usedSilentTool = this.lastToolsUsed.some(t => OrchestratorDaemon.SILENT_SUCCESS_TOOLS.has(t));
641
+ if (usedSilentTool && !this.lastToolsFailed) {
642
+ this.orchestratorLogger.debug("Suppressing confirmation for silent success tool", {
643
+ discussion: originalMessage.discussionId,
644
+ tools: this.lastToolsUsed,
645
+ });
646
+ // Stop typing indicator
647
+ this.stopTypingIndicator();
648
+ // NOTE: Don't pop from context - keep conversation valid for future messages
649
+ this.toolsUsedInCurrentMessage = false;
650
+ this.lastToolsUsed = [];
651
+ this.lastToolsFailed = false;
652
+ return;
653
+ }
654
+ await this.postResponse(targetDiscussion, content);
655
+ this.toolsUsedInCurrentMessage = false; // Reset for next message
656
+ this.lastToolsUsed = [];
657
+ this.lastToolsFailed = false;
658
+ return;
659
+ }
660
+ // For high priority messages, always send substantive responses
661
+ // (unless we just ran a silent success tool)
662
+ if (originalMessage.priority === "high" &&
663
+ responseText &&
664
+ !responseText.includes("<thinking>") &&
665
+ !responseText.startsWith("I'll") &&
666
+ responseText.length > 10) {
667
+ // SILENT SUCCESS: Skip confirmation if we just ran a silent tool successfully (not on error)
668
+ const usedSilentTool = this.lastToolsUsed.some(t => OrchestratorDaemon.SILENT_SUCCESS_TOOLS.has(t));
669
+ if (usedSilentTool && !this.lastToolsFailed) {
670
+ this.orchestratorLogger.debug("Suppressing high-priority confirmation for silent success tool", {
671
+ discussion: originalMessage.discussionId,
672
+ tools: this.lastToolsUsed,
673
+ });
674
+ // Stop typing indicator
675
+ this.stopTypingIndicator();
676
+ // NOTE: Don't pop from context - keep conversation valid for future messages
677
+ this.toolsUsedInCurrentMessage = false;
678
+ this.lastToolsUsed = [];
679
+ this.lastToolsFailed = false;
680
+ return;
681
+ }
682
+ await this.postResponse(originalMessage.discussionId, responseText);
683
+ this.toolsUsedInCurrentMessage = false; // Reset for next message
684
+ this.lastToolsUsed = [];
685
+ this.lastToolsFailed = false;
686
+ return;
687
+ }
688
+ // For normal priority:
689
+ // - If tools were used, post substantive responses (LLM was doing real work)
690
+ // - Otherwise, require explicit <respond> tag
691
+ if (originalMessage.priority === "normal") {
692
+ // SILENT SUCCESS: Skip confirmation if we just ran a silent tool successfully (not on error)
693
+ const usedSilentTool = this.lastToolsUsed.some(t => OrchestratorDaemon.SILENT_SUCCESS_TOOLS.has(t));
694
+ const shouldSuppressSilent = usedSilentTool && !this.lastToolsFailed;
695
+ if (this.toolsUsedInCurrentMessage && responseText.length > 50 && !responseText.includes("<ignore") && !shouldSuppressSilent) {
696
+ // Tools were used and we have a substantive response - post it
697
+ this.orchestratorLogger.info("Posting tool-assisted response", {
698
+ discussion: originalMessage.discussionId,
699
+ responseLength: responseText.length,
700
+ });
701
+ await this.postResponse(originalMessage.discussionId, responseText);
702
+ this.toolsUsedInCurrentMessage = false; // Reset for next message
703
+ this.lastToolsUsed = [];
704
+ this.lastToolsFailed = false;
705
+ return;
706
+ }
707
+ if (shouldSuppressSilent) {
708
+ this.orchestratorLogger.debug("Suppressing normal-priority confirmation for silent success tool", {
709
+ discussion: originalMessage.discussionId,
710
+ tools: this.lastToolsUsed,
711
+ });
712
+ // Stop typing indicator
713
+ this.stopTypingIndicator();
714
+ // NOTE: Don't pop from context when tools were used - keep conversation valid
715
+ this.toolsUsedInCurrentMessage = false;
716
+ this.lastToolsUsed = [];
717
+ this.lastToolsFailed = false;
718
+ return;
719
+ }
720
+ this.orchestratorLogger.debug("Normal priority without <respond> tag - removing from context", {
721
+ discussion: originalMessage.discussionId,
722
+ responsePreview: responseText.substring(0, 100),
723
+ toolsUsed: this.toolsUsedInCurrentMessage,
724
+ });
725
+ // Stop typing indicator
726
+ this.stopTypingIndicator();
727
+ // Remove from context - we didn't respond (only safe when no tools were used)
728
+ if (!this.toolsUsedInCurrentMessage) {
729
+ conversation.pop(); // Remove assistant response
730
+ conversation.pop(); // Remove incoming message
731
+ }
732
+ this.toolsUsedInCurrentMessage = false; // Reset for next message
733
+ this.lastToolsUsed = [];
734
+ this.lastToolsFailed = false;
735
+ }
736
+ }
737
+ }
738
+ /**
739
+ * Get orchestrator status including specialist info
740
+ */
741
+ getOrchestratorStatus() {
742
+ const specialists = Array.from(this.specialists.entries()).map(([key, spec]) => ({
743
+ key,
744
+ name: spec.name,
745
+ available: !!spec.botUserId,
746
+ userId: spec.botUserId,
747
+ }));
748
+ const activeInDiscussions = {};
749
+ for (const [discussionId, userIds] of this.activeSpecialistsInDiscussion) {
750
+ activeInDiscussions[discussionId] = Array.from(userIds);
751
+ }
752
+ return {
753
+ conversationState: this.getConversationState(),
754
+ specialists,
755
+ activeInDiscussions,
756
+ };
757
+ }
758
+ }
759
+ exports.OrchestratorDaemon = OrchestratorDaemon;
760
+ //# sourceMappingURL=orchestrator.js.map