@hailer/mcp 0.1.6 ā 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.
- package/.claude/agents/agent-dmitri-activity-crud.md +3 -1
- package/.claude/agents/agent-giuseppe-app-builder.md +11 -12
- package/.claude/agents/agent-kenji-data-reader.md +5 -3
- package/.claude/hooks/sync-marketplace-agents.cjs +117 -56
- package/.claude/skills/hailer-app-builder/SKILL.md +506 -0
- package/.claude/skills/publish-hailer-app/SKILL.md +169 -0
- package/.claude/skills/tool-parameter-usage/SKILL.md +112 -0
- package/CHANGELOG.md +20 -0
- package/CLAUDE.md +37 -16
- package/REFACTOR_STATUS.md +127 -0
- package/dist/cli.js +0 -0
- package/dist/client/agents/base.d.ts +202 -0
- package/dist/client/agents/base.js +737 -0
- package/dist/client/agents/definitions.d.ts +53 -0
- package/dist/client/agents/definitions.js +178 -0
- package/dist/client/agents/orchestrator.d.ts +119 -0
- package/dist/client/agents/orchestrator.js +760 -0
- package/dist/client/agents/specialist.d.ts +86 -0
- package/dist/client/agents/specialist.js +340 -0
- package/dist/client/bot-manager.d.ts +44 -0
- package/dist/client/bot-manager.js +173 -0
- package/dist/client/chat-agent-daemon.d.ts +464 -0
- package/dist/client/chat-agent-daemon.js +1774 -0
- package/dist/client/daemon-factory.d.ts +106 -0
- package/dist/client/daemon-factory.js +301 -0
- package/dist/client/factory.d.ts +107 -0
- package/dist/client/factory.js +304 -0
- package/dist/client/index.d.ts +17 -0
- package/dist/client/index.js +38 -0
- package/dist/client/multi-bot-manager.d.ts +18 -0
- package/dist/client/multi-bot-manager.js +88 -1
- package/dist/client/orchestrator-daemon.d.ts +87 -0
- package/dist/client/orchestrator-daemon.js +444 -0
- package/dist/client/services/agent-registry.d.ts +108 -0
- package/dist/client/services/agent-registry.js +630 -0
- package/dist/client/services/conversation-manager.d.ts +50 -0
- package/dist/client/services/conversation-manager.js +136 -0
- package/dist/client/services/mcp-client.d.ts +48 -0
- package/dist/client/services/mcp-client.js +105 -0
- package/dist/client/services/message-classifier.d.ts +37 -0
- package/dist/client/services/message-classifier.js +187 -0
- package/dist/client/services/message-formatter.d.ts +84 -0
- package/dist/client/services/message-formatter.js +353 -0
- package/dist/client/services/session-logger.d.ts +106 -0
- package/dist/client/services/session-logger.js +446 -0
- package/dist/client/services/tool-executor.d.ts +41 -0
- package/dist/client/services/tool-executor.js +169 -0
- package/dist/client/services/workspace-schema-cache.d.ts +149 -0
- package/dist/client/services/workspace-schema-cache.js +732 -0
- package/dist/client/specialist-daemon.d.ts +77 -0
- package/dist/client/specialist-daemon.js +197 -0
- package/dist/client/specialists.d.ts +53 -0
- package/dist/client/specialists.js +178 -0
- package/dist/client/tool-schema-loader.d.ts +4 -3
- package/dist/client/tool-schema-loader.js +54 -8
- package/dist/client/types.d.ts +283 -55
- package/dist/client/types.js +113 -2
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/core.d.ts +10 -2
- package/dist/core.js +43 -27
- package/dist/lib/logger.js +15 -3
- package/dist/mcp/UserContextCache.js +2 -2
- package/dist/mcp/hailer-clients.js +5 -5
- package/dist/mcp/signal-handler.js +27 -5
- package/dist/mcp/tools/activity.js +137 -65
- package/dist/mcp/tools/app-core.js +4 -140
- package/dist/mcp/tools/app-marketplace.js +15 -260
- package/dist/mcp/tools/app-member.js +2 -73
- package/dist/mcp/tools/app-scaffold.js +146 -87
- package/dist/mcp/tools/discussion.js +348 -73
- package/dist/mcp/tools/insight.js +74 -190
- package/dist/mcp/tools/workflow.js +20 -94
- package/dist/mcp/utils/hailer-api-client.d.ts +4 -2
- package/dist/mcp/utils/hailer-api-client.js +24 -10
- package/dist/mcp-server.d.ts +4 -0
- package/dist/mcp-server.js +24 -4
- package/dist/routes/agents.d.ts +44 -0
- package/dist/routes/agents.js +311 -0
- package/dist/services/agent-credential-store.d.ts +73 -0
- package/dist/services/agent-credential-store.js +212 -0
- package/lineup-manager/dist/assets/index-8ce6041d.css +1 -0
- package/lineup-manager/dist/assets/index-e168f265.js +600 -0
- package/lineup-manager/dist/index.html +15 -0
- package/lineup-manager/dist/manifest.json +17 -0
- package/lineup-manager/dist/vite.svg +1 -0
- package/package.json +1 -1
- package/dist/client/adaptive-documentation-bot.d.ts +0 -106
- package/dist/client/adaptive-documentation-bot.js +0 -464
- package/dist/client/adaptive-documentation-types.d.ts +0 -66
- package/dist/client/adaptive-documentation-types.js +0 -9
- package/dist/client/agent-activity-bot.d.ts +0 -51
- package/dist/client/agent-activity-bot.js +0 -166
- package/dist/client/agent-tracker.d.ts +0 -499
- package/dist/client/agent-tracker.js +0 -659
- package/dist/client/description-updater.d.ts +0 -56
- package/dist/client/description-updater.js +0 -259
- package/dist/client/log-parser.d.ts +0 -72
- package/dist/client/log-parser.js +0 -387
- package/dist/client/mcp-assistant.d.ts +0 -21
- package/dist/client/mcp-assistant.js +0 -58
- package/dist/client/mcp-client.d.ts +0 -50
- package/dist/client/mcp-client.js +0 -538
- package/dist/client/message-processor.d.ts +0 -35
- package/dist/client/message-processor.js +0 -357
- package/dist/client/providers/anthropic-provider.d.ts +0 -19
- package/dist/client/providers/anthropic-provider.js +0 -645
- package/dist/client/providers/assistant-provider.d.ts +0 -17
- package/dist/client/providers/assistant-provider.js +0 -51
- package/dist/client/providers/llm-provider.d.ts +0 -47
- package/dist/client/providers/llm-provider.js +0 -367
- package/dist/client/providers/openai-provider.d.ts +0 -23
- package/dist/client/providers/openai-provider.js +0 -630
- package/dist/client/simple-llm-caller.d.ts +0 -19
- package/dist/client/simple-llm-caller.js +0 -100
- package/dist/client/skill-generator.d.ts +0 -81
- package/dist/client/skill-generator.js +0 -386
- package/dist/client/test-adaptive-bot.d.ts +0 -9
- package/dist/client/test-adaptive-bot.js +0 -82
- package/dist/client/token-pricing.d.ts +0 -38
- package/dist/client/token-pricing.js +0 -127
- package/dist/client/token-tracker.d.ts +0 -232
- package/dist/client/token-tracker.js +0 -457
- package/dist/client/token-usage-bot.d.ts +0 -53
- package/dist/client/token-usage-bot.js +0 -153
- package/dist/client/tool-executor.d.ts +0 -69
- package/dist/client/tool-executor.js +0 -159
- package/dist/lib/materialize.d.ts +0 -3
- package/dist/lib/materialize.js +0 -101
- package/dist/lib/normalizedName.d.ts +0 -7
- package/dist/lib/normalizedName.js +0 -48
- package/dist/lib/terminal-prompt.d.ts +0 -9
- package/dist/lib/terminal-prompt.js +0 -108
- package/dist/mcp/tools/skill.d.ts +0 -10
- package/dist/mcp/tools/skill.js +0 -279
- package/dist/mcp/tools/workflow-template.d.ts +0 -19
- package/dist/mcp/tools/workflow-template.js +0 -822
|
@@ -102,21 +102,29 @@ exports.listMyDiscussionsTool = {
|
|
|
102
102
|
.map((d) => d.linked_activity);
|
|
103
103
|
// Fetch activity names in parallel for better UX
|
|
104
104
|
const activityNames = {};
|
|
105
|
+
const activityErrors = [];
|
|
105
106
|
if (activityIds.length > 0) {
|
|
106
107
|
const activityPromises = activityIds.map(async (activityId) => {
|
|
107
108
|
try {
|
|
108
109
|
const activity = await context.hailer.fetchActivityById(activityId);
|
|
109
|
-
return { id: activityId, name: activity?.name || null };
|
|
110
|
+
return { id: activityId, name: activity?.name || null, error: null };
|
|
110
111
|
}
|
|
111
112
|
catch (error) {
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
const errorMsg = error instanceof Error ? error.message :
|
|
114
|
+
(typeof error === 'object' && error !== null && 'msg' in error) ? error.msg :
|
|
115
|
+
String(error);
|
|
116
|
+
logger.debug('Failed to fetch activity name', { activityId, error: errorMsg });
|
|
117
|
+
return { id: activityId, name: null, error: errorMsg };
|
|
114
118
|
}
|
|
115
119
|
});
|
|
116
120
|
const results = await Promise.all(activityPromises);
|
|
117
|
-
results.forEach(({ id, name }) => {
|
|
118
|
-
if (name)
|
|
121
|
+
results.forEach(({ id, name, error }) => {
|
|
122
|
+
if (name) {
|
|
119
123
|
activityNames[id] = name;
|
|
124
|
+
}
|
|
125
|
+
else if (error) {
|
|
126
|
+
activityErrors.push({ id, error });
|
|
127
|
+
}
|
|
120
128
|
});
|
|
121
129
|
}
|
|
122
130
|
const discussionsByType = {
|
|
@@ -191,6 +199,14 @@ exports.listMyDiscussionsTool = {
|
|
|
191
199
|
}
|
|
192
200
|
}
|
|
193
201
|
responseText += `- Total unread: ${totalUnread}\n`;
|
|
202
|
+
// Add warnings section if there were errors loading activities
|
|
203
|
+
if (activityErrors.length > 0) {
|
|
204
|
+
responseText += `\nā ļø **WARNINGS** (${activityErrors.length} activity/activities failed to load):\n`;
|
|
205
|
+
activityErrors.forEach(({ id, error }) => {
|
|
206
|
+
responseText += `- Activity \`${id}\`: ${error}\n`;
|
|
207
|
+
});
|
|
208
|
+
responseText += `\nš” These activities may have broken function fields or permission issues.\n`;
|
|
209
|
+
}
|
|
194
210
|
responseText += `\nš” **USAGE:**\n`;
|
|
195
211
|
responseText += `- Use \`fetch_discussion_messages\` with discussion ID to read messages\n`;
|
|
196
212
|
responseText += `- Use \`add_discussion_message\` with discussion ID to post a message\n`;
|
|
@@ -241,13 +257,46 @@ exports.fetchDiscussionMessagesTool = {
|
|
|
241
257
|
}),
|
|
242
258
|
async execute(args, context) {
|
|
243
259
|
const limit = args.limit || 50;
|
|
260
|
+
let discussionId = args.discussionId;
|
|
244
261
|
logger.debug('Fetching discussion messages', {
|
|
245
|
-
discussionId:
|
|
262
|
+
discussionId: discussionId,
|
|
246
263
|
limit: limit,
|
|
247
264
|
apiKey: context.apiKey.substring(0, 8) + '...'
|
|
248
265
|
});
|
|
249
266
|
try {
|
|
250
|
-
|
|
267
|
+
// Auto-detect: if this is an activity ID, get its discussion ID
|
|
268
|
+
// Try fetching as discussion first, if it fails, check if it's an activity
|
|
269
|
+
let result;
|
|
270
|
+
try {
|
|
271
|
+
result = await context.hailer.fetchDiscussionMessages(discussionId, limit);
|
|
272
|
+
}
|
|
273
|
+
catch (firstError) {
|
|
274
|
+
// If first attempt fails, try loading as activity to get discussion ID
|
|
275
|
+
logger.debug('First fetch failed, checking if this is an activity ID', {
|
|
276
|
+
providedId: discussionId,
|
|
277
|
+
error: firstError?.msg || firstError?.message
|
|
278
|
+
});
|
|
279
|
+
try {
|
|
280
|
+
const activity = await context.hailer.fetchActivityById(discussionId);
|
|
281
|
+
if (activity?.discussion) {
|
|
282
|
+
logger.info('Auto-converted activity ID to discussion ID', {
|
|
283
|
+
activityId: discussionId,
|
|
284
|
+
discussionId: activity.discussion,
|
|
285
|
+
activityName: activity.name
|
|
286
|
+
});
|
|
287
|
+
discussionId = activity.discussion;
|
|
288
|
+
result = await context.hailer.fetchDiscussionMessages(discussionId, limit);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
// Not an activity or no discussion linked, rethrow original error
|
|
292
|
+
throw firstError;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
catch (activityError) {
|
|
296
|
+
// Activity lookup failed too, rethrow original error
|
|
297
|
+
throw firstError;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
251
300
|
const messages = result.messages || [];
|
|
252
301
|
const messageCount = messages.length;
|
|
253
302
|
const userMessages = messages.filter((m) => m.type === "user");
|
|
@@ -266,6 +315,7 @@ exports.fetchDiscussionMessagesTool = {
|
|
|
266
315
|
}
|
|
267
316
|
const optimizedMsg = {
|
|
268
317
|
_id: msg._id,
|
|
318
|
+
uid: msg.uid, // Include user ID for conversation history
|
|
269
319
|
username: username,
|
|
270
320
|
created: new Date(msg.created).toLocaleString(),
|
|
271
321
|
type: msg.type,
|
|
@@ -394,6 +444,7 @@ exports.fetchPreviousDiscussionMessagesTool = {
|
|
|
394
444
|
}
|
|
395
445
|
const optimizedMsg = {
|
|
396
446
|
_id: msg._id,
|
|
447
|
+
uid: msg.uid, // Include user ID for conversation history
|
|
397
448
|
username: username,
|
|
398
449
|
created: new Date(msg.created).toLocaleString(),
|
|
399
450
|
msg: cleanMsg
|
|
@@ -454,7 +505,18 @@ exports.fetchPreviousDiscussionMessagesTool = {
|
|
|
454
505
|
// ============================================================================
|
|
455
506
|
// TOOL 4: JOIN DISCUSSION
|
|
456
507
|
// ============================================================================
|
|
457
|
-
const joinDiscussionDescription =
|
|
508
|
+
const joinDiscussionDescription = `<purpose>Join a discussion (bot joins) and optionally invite a user</purpose>
|
|
509
|
+
<when-to-use>
|
|
510
|
+
<case>Activity discussions: provide activityId</case>
|
|
511
|
+
<case>General discussions: provide discussionId</case>
|
|
512
|
+
</when-to-use>
|
|
513
|
+
<critical>
|
|
514
|
+
When user says "invite me" or asks to be added to a discussion:
|
|
515
|
+
- Extract user_id from the incoming message ā inviteUserId
|
|
516
|
+
- Extract source activity from current discussion ā sourceActivityId (for wormhole link back)
|
|
517
|
+
- Provide context explaining WHY ā welcomeReason
|
|
518
|
+
- Bot joins FIRST, then invites user, then posts welcome message with wormhole
|
|
519
|
+
</critical>`;
|
|
458
520
|
exports.joinDiscussionTool = {
|
|
459
521
|
name: 'join_discussion',
|
|
460
522
|
group: tool_registry_1.ToolGroup.WRITE,
|
|
@@ -469,7 +531,21 @@ exports.joinDiscussionTool = {
|
|
|
469
531
|
.string()
|
|
470
532
|
.min(24, "Discussion ID must be at least 24 characters")
|
|
471
533
|
.optional()
|
|
472
|
-
.describe("Discussion ID to join directly (use this for non-activity discussions)")
|
|
534
|
+
.describe("Discussion ID to join directly (use this for non-activity discussions)"),
|
|
535
|
+
inviteUserId: zod_1.z
|
|
536
|
+
.string()
|
|
537
|
+
.min(24, "User ID must be at least 24 characters")
|
|
538
|
+
.optional()
|
|
539
|
+
.describe("User ID to invite. Get from incoming message user_id attribute when user asks to be invited."),
|
|
540
|
+
welcomeReason: zod_1.z
|
|
541
|
+
.string()
|
|
542
|
+
.optional()
|
|
543
|
+
.describe("Context/reason for the invite - posted as welcome message in the discussion."),
|
|
544
|
+
sourceActivityId: zod_1.z
|
|
545
|
+
.string()
|
|
546
|
+
.min(24)
|
|
547
|
+
.optional()
|
|
548
|
+
.describe("IMPORTANT: The activity_id from the CURRENT discussion where the request was made. This creates a 'came from' link in the welcome message. Get this from the incoming message's activity_id attribute.")
|
|
473
549
|
}).refine((data) => data.activityId || data.discussionId, {
|
|
474
550
|
message: "Either activityId or discussionId must be provided"
|
|
475
551
|
}),
|
|
@@ -477,57 +553,207 @@ exports.joinDiscussionTool = {
|
|
|
477
553
|
logger.debug('Joining discussion', {
|
|
478
554
|
activityId: args.activityId,
|
|
479
555
|
discussionId: args.discussionId,
|
|
556
|
+
inviteUserId: args.inviteUserId,
|
|
480
557
|
apiKey: context.apiKey.substring(0, 8) + '...'
|
|
481
558
|
});
|
|
482
559
|
try {
|
|
560
|
+
let discussionId;
|
|
561
|
+
let activityName;
|
|
562
|
+
let resolvedActivityId;
|
|
483
563
|
if (args.activityId) {
|
|
484
564
|
const result = await context.hailer.joinActivityDiscussion(args.activityId);
|
|
485
565
|
const activity = await context.hailer.fetchActivityById(args.activityId);
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
`š You can now:\n` +
|
|
496
|
-
`- Read messages with: fetch_discussion_messages(discussionId: "${discussionId}")\n` +
|
|
497
|
-
`- Post messages with: add_discussion_message(discussionId: "${discussionId}", content: "your message")\n` +
|
|
498
|
-
`- Leave with: leave_discussion(activityId: "${args.activityId}")\n\n` +
|
|
499
|
-
`š **Direct Links:**\n` +
|
|
500
|
-
`- Activity: https://app.hailer.com/#/activities/${args.activityId}\n` +
|
|
501
|
-
`- Discussion: https://app.hailer.com/#/discussions/${discussionId}`,
|
|
502
|
-
},
|
|
503
|
-
],
|
|
504
|
-
};
|
|
566
|
+
discussionId = activity.discussion;
|
|
567
|
+
activityName = activity.name;
|
|
568
|
+
resolvedActivityId = args.activityId;
|
|
569
|
+
logger.debug('Joined activity discussion', {
|
|
570
|
+
activityId: args.activityId,
|
|
571
|
+
discussionId,
|
|
572
|
+
activityName,
|
|
573
|
+
followResult: result
|
|
574
|
+
});
|
|
505
575
|
}
|
|
506
|
-
if (args.discussionId) {
|
|
507
|
-
|
|
576
|
+
else if (args.discussionId) {
|
|
577
|
+
// Try joining as discussion first
|
|
578
|
+
try {
|
|
579
|
+
await context.hailer.joinDiscussion(args.discussionId);
|
|
580
|
+
discussionId = args.discussionId;
|
|
581
|
+
logger.debug('Joined discussion directly', {
|
|
582
|
+
discussionId
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
catch (directJoinError) {
|
|
586
|
+
// If direct join fails, try treating the ID as an activity ID
|
|
587
|
+
logger.debug('Direct discussion join failed, trying as activity ID', {
|
|
588
|
+
providedId: args.discussionId,
|
|
589
|
+
error: directJoinError?.msg || directJoinError?.message
|
|
590
|
+
});
|
|
591
|
+
try {
|
|
592
|
+
const activity = await context.hailer.fetchActivityById(args.discussionId);
|
|
593
|
+
if (activity?.discussion) {
|
|
594
|
+
// It's an activity! Join its discussion
|
|
595
|
+
await context.hailer.joinActivityDiscussion(args.discussionId);
|
|
596
|
+
discussionId = activity.discussion;
|
|
597
|
+
activityName = activity.name;
|
|
598
|
+
resolvedActivityId = args.discussionId;
|
|
599
|
+
logger.info('Auto-resolved activity ID to discussion', {
|
|
600
|
+
activityId: args.discussionId,
|
|
601
|
+
discussionId,
|
|
602
|
+
activityName
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
// Not an activity either, rethrow original error
|
|
607
|
+
throw directJoinError;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
catch (activityError) {
|
|
611
|
+
// Activity lookup also failed, rethrow original error
|
|
612
|
+
logger.debug('Activity lookup also failed', {
|
|
613
|
+
error: activityError?.msg || activityError?.message
|
|
614
|
+
});
|
|
615
|
+
throw directJoinError;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
508
620
|
return {
|
|
509
|
-
content: [
|
|
510
|
-
{
|
|
621
|
+
content: [{
|
|
511
622
|
type: "text",
|
|
512
|
-
text:
|
|
513
|
-
|
|
514
|
-
`š **Method**: ${result.method}\n\n` +
|
|
515
|
-
`š You can now:\n` +
|
|
516
|
-
`- Read messages with: fetch_discussion_messages(discussionId: "${args.discussionId}")\n` +
|
|
517
|
-
`- Post messages with: add_discussion_message(discussionId: "${args.discussionId}", content: "your message")\n\n` +
|
|
518
|
-
`š” **Note**: For activity discussions, prefer using activityId for better integration.\n\n` +
|
|
519
|
-
`š **Direct Link:** https://app.hailer.com/#/discussions/${args.discussionId}`,
|
|
520
|
-
},
|
|
521
|
-
],
|
|
623
|
+
text: `ā Error: Either activityId or discussionId must be provided`,
|
|
624
|
+
}],
|
|
522
625
|
};
|
|
523
626
|
}
|
|
627
|
+
// Invite the requesting user if provided
|
|
628
|
+
let userInvited = false;
|
|
629
|
+
let userAlreadyMember = false;
|
|
630
|
+
let invitedUserName = '';
|
|
631
|
+
if (args.inviteUserId) {
|
|
632
|
+
try {
|
|
633
|
+
// Get user name first
|
|
634
|
+
const userInfo = (0, workspace_cache_1.getUserById)(context.workspaceCache, args.inviteUserId);
|
|
635
|
+
invitedUserName = userInfo
|
|
636
|
+
? `${userInfo.firstname || ''} ${userInfo.lastname || ''}`.trim() || userInfo.fullName || args.inviteUserId
|
|
637
|
+
: args.inviteUserId;
|
|
638
|
+
// Check if user is already a member
|
|
639
|
+
const syncResponse = await context.hailer.request('messenger.load_discussions', [
|
|
640
|
+
[discussionId]
|
|
641
|
+
]);
|
|
642
|
+
const discussion = syncResponse?.[discussionId];
|
|
643
|
+
const existingMembers = new Set(discussion?.participants || []);
|
|
644
|
+
if (existingMembers.has(args.inviteUserId)) {
|
|
645
|
+
userAlreadyMember = true;
|
|
646
|
+
logger.debug('User already a member, skipping invite', {
|
|
647
|
+
discussionId,
|
|
648
|
+
userId: args.inviteUserId,
|
|
649
|
+
userName: invitedUserName
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
logger.debug('Inviting user to discussion', {
|
|
654
|
+
discussionId,
|
|
655
|
+
userId: args.inviteUserId
|
|
656
|
+
});
|
|
657
|
+
await context.hailer.request('messenger.invite_users', [
|
|
658
|
+
discussionId,
|
|
659
|
+
[args.inviteUserId]
|
|
660
|
+
]);
|
|
661
|
+
userInvited = true;
|
|
662
|
+
logger.debug('User invited successfully', {
|
|
663
|
+
discussionId,
|
|
664
|
+
userId: args.inviteUserId,
|
|
665
|
+
userName: invitedUserName
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
// Post welcome message if reason provided
|
|
669
|
+
if (args.welcomeReason) {
|
|
670
|
+
try {
|
|
671
|
+
// Format user tag properly: [hailerTag|Name](userId) with ZWNBSP
|
|
672
|
+
const ZWNBSP = '\uFEFF';
|
|
673
|
+
const userTag = `${ZWNBSP}[hailerTag|${invitedUserName}](${args.inviteUserId})${ZWNBSP}`;
|
|
674
|
+
// Format source activity tag (where request came from)
|
|
675
|
+
// IMPORTANT: Ignore sourceActivityId if it equals the destination activityId
|
|
676
|
+
// (LLM sometimes confuses source/destination)
|
|
677
|
+
let sourceTag = '';
|
|
678
|
+
const validSourceId = args.sourceActivityId && args.sourceActivityId !== args.activityId
|
|
679
|
+
? args.sourceActivityId
|
|
680
|
+
: null;
|
|
681
|
+
if (validSourceId) {
|
|
682
|
+
try {
|
|
683
|
+
const sourceActivity = await context.hailer.fetchActivityById(validSourceId);
|
|
684
|
+
const sourceActivityName = sourceActivity?.name || 'Unknown';
|
|
685
|
+
sourceTag = `${ZWNBSP}[hailerTag|${sourceActivityName}](${validSourceId})${ZWNBSP}`;
|
|
686
|
+
}
|
|
687
|
+
catch {
|
|
688
|
+
sourceTag = `activity ${validSourceId}`;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
// Build welcome message - focus on WHERE they came from
|
|
692
|
+
let welcomeMsg;
|
|
693
|
+
if (sourceTag) {
|
|
694
|
+
welcomeMsg = `š ${userTag} joined from ${sourceTag}\n\n**Context:** ${args.welcomeReason}`;
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
welcomeMsg = `š ${userTag} was added to this discussion.\n\n**Context:** ${args.welcomeReason}`;
|
|
698
|
+
}
|
|
699
|
+
// Build links metadata for tags
|
|
700
|
+
const links = [
|
|
701
|
+
{ target: args.inviteUserId, targetType: 'user', type: 'linked-to' }
|
|
702
|
+
];
|
|
703
|
+
if (args.activityId) {
|
|
704
|
+
links.push({ target: args.activityId, targetType: 'activity', type: 'linked-to' });
|
|
705
|
+
}
|
|
706
|
+
if (validSourceId) {
|
|
707
|
+
links.push({ target: validSourceId, targetType: 'activity', type: 'linked-to' });
|
|
708
|
+
}
|
|
709
|
+
await context.hailer.request('messenger.send', [
|
|
710
|
+
{ msg: welcomeMsg, links },
|
|
711
|
+
discussionId
|
|
712
|
+
]);
|
|
713
|
+
logger.debug('Welcome message posted', { discussionId, hadTags: true, hasWormhole: !!validSourceId });
|
|
714
|
+
}
|
|
715
|
+
catch (welcomeError) {
|
|
716
|
+
logger.warn('Failed to post welcome message', {
|
|
717
|
+
discussionId,
|
|
718
|
+
error: welcomeError instanceof Error ? welcomeError.message : String(welcomeError)
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
catch (inviteError) {
|
|
724
|
+
logger.warn('Failed to invite user', {
|
|
725
|
+
discussionId,
|
|
726
|
+
userId: args.inviteUserId,
|
|
727
|
+
error: inviteError instanceof Error ? inviteError.message : String(inviteError)
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
// Build response
|
|
732
|
+
let responseText = `ā
Successfully joined discussion!\n\n`;
|
|
733
|
+
if (activityName) {
|
|
734
|
+
responseText += `š **Activity**: ${activityName}\n`;
|
|
735
|
+
}
|
|
736
|
+
responseText += `š¬ **Discussion ID**: ${discussionId}\n`;
|
|
737
|
+
if (userInvited) {
|
|
738
|
+
responseText += `š¤ **User Invited**: ${invitedUserName} ā
\n`;
|
|
739
|
+
}
|
|
740
|
+
else if (userAlreadyMember) {
|
|
741
|
+
responseText += `š¤ **User**: ${invitedUserName} (already a member)\n`;
|
|
742
|
+
}
|
|
743
|
+
responseText += `\nš You can now:\n`;
|
|
744
|
+
responseText += `- Read messages with: fetch_discussion_messages(discussionId: "${discussionId}")\n`;
|
|
745
|
+
responseText += `- Post messages with: add_discussion_message(discussionId: "${discussionId}", content: "your message")\n`;
|
|
746
|
+
if (resolvedActivityId) {
|
|
747
|
+
responseText += `- Leave with: leave_discussion(activityId: "${resolvedActivityId}")\n`;
|
|
748
|
+
responseText += `\nš **Direct Links:**\n`;
|
|
749
|
+
responseText += `- Activity: https://app.hailer.com/#/activities/${resolvedActivityId}\n`;
|
|
750
|
+
}
|
|
751
|
+
responseText += `- Discussion: https://app.hailer.com/#/discussions/${discussionId}`;
|
|
524
752
|
return {
|
|
525
|
-
content: [
|
|
526
|
-
{
|
|
753
|
+
content: [{
|
|
527
754
|
type: "text",
|
|
528
|
-
text:
|
|
529
|
-
},
|
|
530
|
-
],
|
|
755
|
+
text: responseText,
|
|
756
|
+
}],
|
|
531
757
|
};
|
|
532
758
|
}
|
|
533
759
|
catch (error) {
|
|
@@ -549,7 +775,8 @@ exports.joinDiscussionTool = {
|
|
|
549
775
|
`- Some discussions may be private or require an invitation\n\n` +
|
|
550
776
|
`š Examples:\n` +
|
|
551
777
|
`- Activity discussion: join_discussion(activityId: "68446dc05b30685f67c6fcd4")\n` +
|
|
552
|
-
`- General discussion: join_discussion(discussionId: "683ef53087e3d8329abaa3ad")
|
|
778
|
+
`- General discussion: join_discussion(discussionId: "683ef53087e3d8329abaa3ad")\n` +
|
|
779
|
+
`- With user invite: join_discussion(activityId: "...", inviteUserId: "user-id-from-context")`,
|
|
553
780
|
},
|
|
554
781
|
],
|
|
555
782
|
};
|
|
@@ -725,35 +952,83 @@ exports.inviteDiscussionMembersTool = {
|
|
|
725
952
|
apiKey: context.apiKey.substring(0, 8) + '...'
|
|
726
953
|
});
|
|
727
954
|
try {
|
|
728
|
-
//
|
|
729
|
-
const
|
|
730
|
-
args.discussionId
|
|
731
|
-
args.userIds
|
|
955
|
+
// First, check who is already a member
|
|
956
|
+
const syncResponse = await context.hailer.request('messenger.load_discussions', [
|
|
957
|
+
[args.discussionId]
|
|
732
958
|
]);
|
|
733
|
-
|
|
734
|
-
//
|
|
735
|
-
|
|
959
|
+
const discussion = syncResponse?.[args.discussionId];
|
|
960
|
+
// Block invites to private chats - this breaks Hailer
|
|
961
|
+
if (discussion?.private === true) {
|
|
962
|
+
return {
|
|
963
|
+
content: [{
|
|
964
|
+
type: "text",
|
|
965
|
+
text: `ā Cannot invite members to a private chat.\n\n` +
|
|
966
|
+
`Private chats are 1-on-1 conversations and cannot have additional members.\n` +
|
|
967
|
+
`š” To include more people, create a group discussion instead.`
|
|
968
|
+
}]
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
const existingMembers = new Set(discussion?.participants || []);
|
|
972
|
+
// Filter out users who are already members
|
|
973
|
+
const alreadyMembers = [];
|
|
974
|
+
const newUsers = [];
|
|
975
|
+
for (const userId of args.userIds) {
|
|
976
|
+
if (existingMembers.has(userId)) {
|
|
977
|
+
alreadyMembers.push(userId);
|
|
978
|
+
}
|
|
979
|
+
else {
|
|
980
|
+
newUsers.push(userId);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
logger.debug('Membership check', {
|
|
984
|
+
discussionId: args.discussionId,
|
|
985
|
+
existingMemberCount: existingMembers.size,
|
|
986
|
+
alreadyMembers: alreadyMembers.length,
|
|
987
|
+
newUsers: newUsers.length
|
|
988
|
+
});
|
|
989
|
+
// Helper to get user display name
|
|
990
|
+
const getUserName = (userId) => {
|
|
736
991
|
const userInfo = (0, workspace_cache_1.getUserById)(context.workspaceCache, userId);
|
|
737
|
-
|
|
738
|
-
? `${userInfo.firstname || ''} ${userInfo.lastname || ''}`.trim() || userInfo.fullName
|
|
992
|
+
return userInfo
|
|
993
|
+
? `${userInfo.firstname || ''} ${userInfo.lastname || ''}`.trim() || userInfo.fullName || userId
|
|
739
994
|
: userId;
|
|
740
|
-
|
|
741
|
-
|
|
995
|
+
};
|
|
996
|
+
// If all users are already members, return early
|
|
997
|
+
if (newUsers.length === 0) {
|
|
998
|
+
const alreadyMembersList = alreadyMembers.map((userId) => `- ${getUserName(userId)} (${userId})`).join('\n');
|
|
999
|
+
return {
|
|
1000
|
+
content: [{
|
|
1001
|
+
type: "text",
|
|
1002
|
+
text: `ā¹ļø All users are already members of this discussion!\n\n` +
|
|
1003
|
+
`š¬ **Discussion ID**: ${args.discussionId}\n` +
|
|
1004
|
+
`š„ **Already Members**:\n${alreadyMembersList}\n\n` +
|
|
1005
|
+
`š” No invites were sent - these users can already participate.`
|
|
1006
|
+
}]
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
// Invite only the new users
|
|
1010
|
+
const result = await context.hailer.request('messenger.invite_users', [
|
|
1011
|
+
args.discussionId,
|
|
1012
|
+
newUsers
|
|
1013
|
+
]);
|
|
1014
|
+
logger.debug('Invite result', { result, invitedCount: newUsers.length });
|
|
1015
|
+
// Build response
|
|
1016
|
+
const invitedUsersList = newUsers.map((userId) => `- ${getUserName(userId)} (${userId})`).join('\n');
|
|
1017
|
+
let responseText = `ā
Successfully invited ${newUsers.length} member(s) to discussion!\n\n` +
|
|
1018
|
+
`š¬ **Discussion ID**: ${args.discussionId}\n` +
|
|
1019
|
+
`š„ **Invited Members**:\n${invitedUsersList}\n`;
|
|
1020
|
+
// Note if some were already members
|
|
1021
|
+
if (alreadyMembers.length > 0) {
|
|
1022
|
+
const alreadyMembersList = alreadyMembers.map((userId) => `- ${getUserName(userId)}`).join('\n');
|
|
1023
|
+
responseText += `\nā¹ļø **Already Members** (skipped):\n${alreadyMembersList}\n`;
|
|
1024
|
+
}
|
|
1025
|
+
responseText += `\nš The invited members can now:\n` +
|
|
1026
|
+
`- See all messages in the discussion\n` +
|
|
1027
|
+
`- Post messages and participate\n` +
|
|
1028
|
+
`- Receive notifications for new activity\n\n` +
|
|
1029
|
+
`š **Discussion Link:** https://app.hailer.com/#/discussions/${args.discussionId}`;
|
|
742
1030
|
return {
|
|
743
|
-
content: [
|
|
744
|
-
{
|
|
745
|
-
type: "text",
|
|
746
|
-
text: `ā
Successfully invited ${args.userIds.length} member(s) to discussion!\n\n` +
|
|
747
|
-
`š¬ **Discussion ID**: ${args.discussionId}\n` +
|
|
748
|
-
`š„ **Invited Members**:\n${invitedUsers}\n\n` +
|
|
749
|
-
`š The invited members can now:\n` +
|
|
750
|
-
`- See all messages in the discussion\n` +
|
|
751
|
-
`- Post messages and participate\n` +
|
|
752
|
-
`- Receive notifications for new activity\n\n` +
|
|
753
|
-
`š” **Use Case**: Perfect for bringing specialized bots or team members into conversations!\n\n` +
|
|
754
|
-
`š **Discussion Link:** https://app.hailer.com/#/discussions/${args.discussionId}`,
|
|
755
|
-
},
|
|
756
|
-
],
|
|
1031
|
+
content: [{ type: "text", text: responseText }],
|
|
757
1032
|
};
|
|
758
1033
|
}
|
|
759
1034
|
catch (error) {
|