@hailer/mcp 0.0.1
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/commands/tool-builder.md +37 -0
- package/.claude/commands/ws-pull.md +44 -0
- package/.claude/settings.json +8 -0
- package/.claude/settings.local.json +49 -0
- package/.claude/skills/activity-api/SKILL.md +96 -0
- package/.claude/skills/activity-api/references/activity-endpoints.md +845 -0
- package/.claude/skills/add-app-member-skill/SKILL.md +977 -0
- package/.claude/skills/agent-building/SKILL.md +243 -0
- package/.claude/skills/agent-building/references/architecture-patterns.md +446 -0
- package/.claude/skills/agent-building/references/code-examples.md +587 -0
- package/.claude/skills/agent-building/references/implementation-guide.md +619 -0
- package/.claude/skills/app-api/SKILL.md +219 -0
- package/.claude/skills/app-api/references/app-endpoints.md +759 -0
- package/.claude/skills/building-hailer-apps-skill/SKILL.md +548 -0
- package/.claude/skills/create-app-skill/SKILL.md +1101 -0
- package/.claude/skills/create-insight-skill/SKILL.md +1317 -0
- package/.claude/skills/get-insight-data-skill/SKILL.md +1053 -0
- package/.claude/skills/hailer-api/SKILL.md +283 -0
- package/.claude/skills/hailer-api/references/activities.md +620 -0
- package/.claude/skills/hailer-api/references/authentication.md +216 -0
- package/.claude/skills/hailer-api/references/datasets.md +437 -0
- package/.claude/skills/hailer-api/references/files.md +301 -0
- package/.claude/skills/hailer-api/references/insights.md +469 -0
- package/.claude/skills/hailer-api/references/workflows.md +720 -0
- package/.claude/skills/hailer-api/references/workspaces-users.md +445 -0
- package/.claude/skills/insight-api/SKILL.md +185 -0
- package/.claude/skills/insight-api/references/insight-endpoints.md +514 -0
- package/.claude/skills/install-workflow-skill/SKILL.md +1056 -0
- package/.claude/skills/list-apps-skill/SKILL.md +1010 -0
- package/.claude/skills/list-workflows-minimal-skill/SKILL.md +992 -0
- package/.claude/skills/local-first-skill/SKILL.md +570 -0
- package/.claude/skills/mcp-tools/SKILL.md +419 -0
- package/.claude/skills/mcp-tools/references/api-endpoints.md +499 -0
- package/.claude/skills/mcp-tools/references/data-structures.md +554 -0
- package/.claude/skills/mcp-tools/references/implementation-patterns.md +717 -0
- package/.claude/skills/preview-insight-skill/SKILL.md +1290 -0
- package/.claude/skills/publish-hailer-app-skill/SKILL.md +453 -0
- package/.claude/skills/remove-app-member-skill/SKILL.md +671 -0
- package/.claude/skills/remove-app-skill/SKILL.md +985 -0
- package/.claude/skills/remove-insight-skill/SKILL.md +1011 -0
- package/.claude/skills/remove-workflow-skill/SKILL.md +920 -0
- package/.claude/skills/scaffold-hailer-app-skill/SKILL.md +1034 -0
- package/.claude/skills/skill-testing/README.md +137 -0
- package/.claude/skills/skill-testing/SKILL.md +348 -0
- package/.claude/skills/skill-testing/references/test-patterns.md +705 -0
- package/.claude/skills/skill-testing/references/testing-guide.md +603 -0
- package/.claude/skills/skill-testing/references/validation-checklist.md +537 -0
- package/.claude/skills/tool-builder/SKILL.md +328 -0
- package/.claude/skills/update-app-skill/SKILL.md +970 -0
- package/.claude/skills/update-workflow-field-skill/SKILL.md +1098 -0
- package/.env.example +81 -0
- package/.mcp.json +13 -0
- package/README.md +297 -0
- package/dist/app.d.ts +4 -0
- package/dist/app.js +74 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +5 -0
- package/dist/client/adaptive-documentation-bot.d.ts +108 -0
- package/dist/client/adaptive-documentation-bot.js +475 -0
- package/dist/client/adaptive-documentation-types.d.ts +66 -0
- package/dist/client/adaptive-documentation-types.js +9 -0
- package/dist/client/agent-activity-bot.d.ts +51 -0
- package/dist/client/agent-activity-bot.js +166 -0
- package/dist/client/agent-tracker.d.ts +499 -0
- package/dist/client/agent-tracker.js +659 -0
- package/dist/client/description-updater.d.ts +56 -0
- package/dist/client/description-updater.js +259 -0
- package/dist/client/log-parser.d.ts +72 -0
- package/dist/client/log-parser.js +387 -0
- package/dist/client/mcp-client.d.ts +50 -0
- package/dist/client/mcp-client.js +532 -0
- package/dist/client/message-processor.d.ts +35 -0
- package/dist/client/message-processor.js +352 -0
- package/dist/client/multi-bot-manager.d.ts +24 -0
- package/dist/client/multi-bot-manager.js +74 -0
- package/dist/client/providers/anthropic-provider.d.ts +19 -0
- package/dist/client/providers/anthropic-provider.js +631 -0
- package/dist/client/providers/llm-provider.d.ts +47 -0
- package/dist/client/providers/llm-provider.js +367 -0
- package/dist/client/providers/openai-provider.d.ts +23 -0
- package/dist/client/providers/openai-provider.js +621 -0
- package/dist/client/simple-llm-caller.d.ts +19 -0
- package/dist/client/simple-llm-caller.js +100 -0
- package/dist/client/skill-generator.d.ts +81 -0
- package/dist/client/skill-generator.js +386 -0
- package/dist/client/test-adaptive-bot.d.ts +9 -0
- package/dist/client/test-adaptive-bot.js +82 -0
- package/dist/client/token-pricing.d.ts +38 -0
- package/dist/client/token-pricing.js +127 -0
- package/dist/client/token-tracker.d.ts +232 -0
- package/dist/client/token-tracker.js +457 -0
- package/dist/client/token-usage-bot.d.ts +53 -0
- package/dist/client/token-usage-bot.js +153 -0
- package/dist/client/tool-executor.d.ts +69 -0
- package/dist/client/tool-executor.js +159 -0
- package/dist/client/tool-schema-loader.d.ts +60 -0
- package/dist/client/tool-schema-loader.js +178 -0
- package/dist/client/types.d.ts +69 -0
- package/dist/client/types.js +7 -0
- package/dist/config.d.ts +162 -0
- package/dist/config.js +296 -0
- package/dist/core.d.ts +26 -0
- package/dist/core.js +147 -0
- package/dist/lib/context-manager.d.ts +111 -0
- package/dist/lib/context-manager.js +431 -0
- package/dist/lib/logger.d.ts +74 -0
- package/dist/lib/logger.js +277 -0
- package/dist/lib/materialize.d.ts +3 -0
- package/dist/lib/materialize.js +101 -0
- package/dist/lib/normalizedName.d.ts +7 -0
- package/dist/lib/normalizedName.js +48 -0
- package/dist/lib/prompt-length-manager.d.ts +81 -0
- package/dist/lib/prompt-length-manager.js +457 -0
- package/dist/lib/terminal-prompt.d.ts +9 -0
- package/dist/lib/terminal-prompt.js +108 -0
- package/dist/mcp/UserContextCache.d.ts +56 -0
- package/dist/mcp/UserContextCache.js +163 -0
- package/dist/mcp/auth.d.ts +2 -0
- package/dist/mcp/auth.js +29 -0
- package/dist/mcp/hailer-clients.d.ts +42 -0
- package/dist/mcp/hailer-clients.js +246 -0
- package/dist/mcp/signal-handler.d.ts +45 -0
- package/dist/mcp/signal-handler.js +317 -0
- package/dist/mcp/tool-registry.d.ts +100 -0
- package/dist/mcp/tool-registry.js +306 -0
- package/dist/mcp/tools/activity.d.ts +15 -0
- package/dist/mcp/tools/activity.js +955 -0
- package/dist/mcp/tools/app.d.ts +20 -0
- package/dist/mcp/tools/app.js +1488 -0
- package/dist/mcp/tools/discussion.d.ts +19 -0
- package/dist/mcp/tools/discussion.js +950 -0
- package/dist/mcp/tools/file.d.ts +15 -0
- package/dist/mcp/tools/file.js +119 -0
- package/dist/mcp/tools/insight.d.ts +17 -0
- package/dist/mcp/tools/insight.js +806 -0
- package/dist/mcp/tools/skill.d.ts +10 -0
- package/dist/mcp/tools/skill.js +279 -0
- package/dist/mcp/tools/user.d.ts +10 -0
- package/dist/mcp/tools/user.js +108 -0
- package/dist/mcp/tools/workflow-template.d.ts +19 -0
- package/dist/mcp/tools/workflow-template.js +822 -0
- package/dist/mcp/tools/workflow.d.ts +18 -0
- package/dist/mcp/tools/workflow.js +1362 -0
- package/dist/mcp/utils/api-errors.d.ts +45 -0
- package/dist/mcp/utils/api-errors.js +160 -0
- package/dist/mcp/utils/data-transformers.d.ts +102 -0
- package/dist/mcp/utils/data-transformers.js +194 -0
- package/dist/mcp/utils/file-upload.d.ts +33 -0
- package/dist/mcp/utils/file-upload.js +148 -0
- package/dist/mcp/utils/hailer-api-client.d.ts +120 -0
- package/dist/mcp/utils/hailer-api-client.js +323 -0
- package/dist/mcp/utils/index.d.ts +13 -0
- package/dist/mcp/utils/index.js +39 -0
- package/dist/mcp/utils/logger.d.ts +42 -0
- package/dist/mcp/utils/logger.js +103 -0
- package/dist/mcp/utils/types.d.ts +286 -0
- package/dist/mcp/utils/types.js +7 -0
- package/dist/mcp/workspace-cache.d.ts +42 -0
- package/dist/mcp/workspace-cache.js +97 -0
- package/dist/mcp-server.d.ts +42 -0
- package/dist/mcp-server.js +280 -0
- package/package.json +56 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,950 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Discussion Tools - Clean Architecture Implementation
|
|
4
|
+
*
|
|
5
|
+
* Tools for managing Hailer discussions/chats:
|
|
6
|
+
* - List user's discussions (READ)
|
|
7
|
+
* - Fetch discussion messages with pagination (WRITE)
|
|
8
|
+
* - Join/leave discussions (WRITE)
|
|
9
|
+
* - Post messages to discussions (WRITE)
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.getActivityFromDiscussionTool = exports.inviteDiscussionMembersTool = exports.addDiscussionMessageTool = exports.leaveDiscussionTool = exports.joinDiscussionTool = exports.fetchPreviousDiscussionMessagesTool = exports.fetchDiscussionMessagesTool = exports.listMyDiscussionsTool = void 0;
|
|
13
|
+
const zod_1 = require("zod");
|
|
14
|
+
const tool_registry_1 = require("../tool-registry");
|
|
15
|
+
const index_1 = require("../utils/index");
|
|
16
|
+
const workspace_cache_1 = require("../workspace-cache");
|
|
17
|
+
const logger = (0, index_1.createLogger)({ component: 'discussion-tools' });
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// HELPER FUNCTIONS
|
|
20
|
+
// ============================================================================
|
|
21
|
+
/**
|
|
22
|
+
* Sanitize string to remove invalid Unicode characters
|
|
23
|
+
*/
|
|
24
|
+
function sanitizeString(str) {
|
|
25
|
+
if (!str)
|
|
26
|
+
return '';
|
|
27
|
+
return str.replace(/[\ud800-\udfff]/g, '\ufffd');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Format system/meta message content based on message type
|
|
31
|
+
*/
|
|
32
|
+
function formatSystemMessage(msg) {
|
|
33
|
+
const type = msg.type;
|
|
34
|
+
const meta = msg.meta;
|
|
35
|
+
switch (type) {
|
|
36
|
+
case 'user.join':
|
|
37
|
+
return '[User joined the discussion]';
|
|
38
|
+
case 'user.leave':
|
|
39
|
+
return '[User left the discussion]';
|
|
40
|
+
case 'activity.created':
|
|
41
|
+
return meta?.activityName
|
|
42
|
+
? `[Activity created: ${meta.activityName}]`
|
|
43
|
+
: '[Activity created]';
|
|
44
|
+
case 'activity.updated':
|
|
45
|
+
return meta?.activityName
|
|
46
|
+
? `[Activity updated: ${meta.activityName}]`
|
|
47
|
+
: '[Activity updated]';
|
|
48
|
+
case 'linked.activity.created':
|
|
49
|
+
return meta?.activityName
|
|
50
|
+
? `[Linked activity created: ${meta.activityName}]`
|
|
51
|
+
: '[Linked activity created]';
|
|
52
|
+
case 'linked.activity.moved':
|
|
53
|
+
return meta?.activityName
|
|
54
|
+
? `[Linked activity moved: ${meta.activityName}]`
|
|
55
|
+
: '[Linked activity moved]';
|
|
56
|
+
case 'discussion.invites':
|
|
57
|
+
return '[Users invited to discussion]';
|
|
58
|
+
case 'discussion.removesinvites':
|
|
59
|
+
return '[Users removed from discussion]';
|
|
60
|
+
case 'event.new':
|
|
61
|
+
return meta?.eventName
|
|
62
|
+
? `[Event created: ${meta.eventName}]`
|
|
63
|
+
: '[Event created]';
|
|
64
|
+
case 'event.updated':
|
|
65
|
+
return meta?.eventName
|
|
66
|
+
? `[Event updated: ${meta.eventName}]`
|
|
67
|
+
: '[Event updated]';
|
|
68
|
+
case 'event.attend_response':
|
|
69
|
+
return '[Event attendance response]';
|
|
70
|
+
default:
|
|
71
|
+
return `[System message: ${type}]`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// TOOL 1: LIST MY DISCUSSIONS
|
|
76
|
+
// ============================================================================
|
|
77
|
+
const listMyDiscussionsDescription = `š¬ List all discussions you are currently participating in - Shows discussion names, unread counts, starred status, and linked activities`;
|
|
78
|
+
exports.listMyDiscussionsTool = {
|
|
79
|
+
name: 'list_my_discussions',
|
|
80
|
+
group: tool_registry_1.ToolGroup.READ,
|
|
81
|
+
description: listMyDiscussionsDescription,
|
|
82
|
+
schema: zod_1.z.object({}),
|
|
83
|
+
async execute(args, context) {
|
|
84
|
+
try {
|
|
85
|
+
const syncResponse = await context.hailer.request('v2.discussion.sync', [{ timestamp: 0 }]);
|
|
86
|
+
logger.debug('Discussion sync response', {
|
|
87
|
+
discussionsCount: syncResponse.discussions?.length
|
|
88
|
+
});
|
|
89
|
+
const discussions = syncResponse.discussions || [];
|
|
90
|
+
const unreadCounts = syncResponse.unread_counts || {};
|
|
91
|
+
let responseText = `š¬ **MY DISCUSSIONS** (${discussions.length} total):\n\n`;
|
|
92
|
+
if (discussions.length === 0) {
|
|
93
|
+
responseText += `ā You are not currently participating in any discussions.\n\n`;
|
|
94
|
+
responseText += `š” **TIP**: Join activity discussions using the \`join_discussion\` tool.`;
|
|
95
|
+
return {
|
|
96
|
+
content: [{ type: "text", text: responseText }],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// Collect all linked activity IDs to fetch their names
|
|
100
|
+
const activityIds = discussions
|
|
101
|
+
.filter((d) => d.linked_activity)
|
|
102
|
+
.map((d) => d.linked_activity);
|
|
103
|
+
// Fetch activity names in parallel for better UX
|
|
104
|
+
const activityNames = {};
|
|
105
|
+
if (activityIds.length > 0) {
|
|
106
|
+
const activityPromises = activityIds.map(async (activityId) => {
|
|
107
|
+
try {
|
|
108
|
+
const activity = await context.hailer.fetchActivityById(activityId);
|
|
109
|
+
return { id: activityId, name: activity?.name || null };
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
logger.debug('Failed to fetch activity name', { activityId, error });
|
|
113
|
+
return { id: activityId, name: null };
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
const results = await Promise.all(activityPromises);
|
|
117
|
+
results.forEach(({ id, name }) => {
|
|
118
|
+
if (name)
|
|
119
|
+
activityNames[id] = name;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const discussionsByType = {
|
|
123
|
+
activity: [],
|
|
124
|
+
event: [],
|
|
125
|
+
group: [],
|
|
126
|
+
private: [],
|
|
127
|
+
other: []
|
|
128
|
+
};
|
|
129
|
+
discussions.forEach((discussion) => {
|
|
130
|
+
let type = 'other';
|
|
131
|
+
if (discussion.linked_activity) {
|
|
132
|
+
type = 'activity';
|
|
133
|
+
}
|
|
134
|
+
else if (discussion.linked_event) {
|
|
135
|
+
type = 'event';
|
|
136
|
+
}
|
|
137
|
+
else if (discussion.private === true) {
|
|
138
|
+
type = 'private';
|
|
139
|
+
}
|
|
140
|
+
else if (discussion.private === false) {
|
|
141
|
+
type = 'group';
|
|
142
|
+
}
|
|
143
|
+
discussionsByType[type].push(discussion);
|
|
144
|
+
});
|
|
145
|
+
const typeLabels = {
|
|
146
|
+
activity: 'š Activity Discussions',
|
|
147
|
+
event: 'š
Event Discussions',
|
|
148
|
+
group: 'š„ Group Discussions',
|
|
149
|
+
private: 'š¬ Private Discussions',
|
|
150
|
+
other: 'ā Other Discussions'
|
|
151
|
+
};
|
|
152
|
+
for (const [type, typeDiscussions] of Object.entries(discussionsByType)) {
|
|
153
|
+
if (typeDiscussions.length === 0)
|
|
154
|
+
continue;
|
|
155
|
+
responseText += `${typeLabels[type]} (${typeDiscussions.length}):\n\n`;
|
|
156
|
+
typeDiscussions.forEach((discussion, index) => {
|
|
157
|
+
const unreadCount = unreadCounts[discussion._id] || 0;
|
|
158
|
+
responseText += `${index + 1}. `;
|
|
159
|
+
if (unreadCount > 0)
|
|
160
|
+
responseText += `š“ `;
|
|
161
|
+
// For activity discussions, prefer the activity name over discussion.name
|
|
162
|
+
let displayName = sanitizeString(discussion.name) || 'Untitled';
|
|
163
|
+
if (discussion.linked_activity && activityNames[discussion.linked_activity]) {
|
|
164
|
+
displayName = activityNames[discussion.linked_activity];
|
|
165
|
+
}
|
|
166
|
+
responseText += `**${displayName}**\n`;
|
|
167
|
+
responseText += ` - Discussion ID: \`${sanitizeString(discussion._id)}\`\n`;
|
|
168
|
+
responseText += ` - Type: ${type}\n`;
|
|
169
|
+
if (discussion.linked_activity) {
|
|
170
|
+
responseText += ` - Activity ID: \`${sanitizeString(discussion.linked_activity)}\`\n`;
|
|
171
|
+
}
|
|
172
|
+
if (discussion.linked_event) {
|
|
173
|
+
responseText += ` - Event ID: \`${sanitizeString(discussion.linked_event)}\`\n`;
|
|
174
|
+
}
|
|
175
|
+
if (unreadCount > 0) {
|
|
176
|
+
responseText += ` - Unread: ${unreadCount} message(s)\n`;
|
|
177
|
+
}
|
|
178
|
+
if (discussion.hasMessages) {
|
|
179
|
+
responseText += ` - Has messages: Yes\n`;
|
|
180
|
+
}
|
|
181
|
+
responseText += `\n`;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
const totalUnread = Object.values(unreadCounts).reduce((sum, count) => sum + count, 0);
|
|
185
|
+
responseText += `\nš **SUMMARY:**\n`;
|
|
186
|
+
responseText += `- Total discussions: ${discussions.length}\n`;
|
|
187
|
+
for (const [type, typeDiscussions] of Object.entries(discussionsByType)) {
|
|
188
|
+
if (typeDiscussions.length > 0) {
|
|
189
|
+
const labelWithoutEmoji = typeLabels[type].substring(typeLabels[type].indexOf(' ') + 1);
|
|
190
|
+
responseText += `- ${labelWithoutEmoji}: ${typeDiscussions.length}\n`;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
responseText += `- Total unread: ${totalUnread}\n`;
|
|
194
|
+
responseText += `\nš” **USAGE:**\n`;
|
|
195
|
+
responseText += `- Use \`fetch_discussion_messages\` with discussion ID to read messages\n`;
|
|
196
|
+
responseText += `- Use \`add_discussion_message\` with discussion ID to post a message\n`;
|
|
197
|
+
responseText += `- Use \`leave_discussion\` with discussion ID to leave a discussion`;
|
|
198
|
+
return {
|
|
199
|
+
content: [
|
|
200
|
+
{
|
|
201
|
+
type: "text",
|
|
202
|
+
text: responseText,
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
logger.error("Failed to list discussions", error);
|
|
209
|
+
return {
|
|
210
|
+
content: [
|
|
211
|
+
{
|
|
212
|
+
type: "text",
|
|
213
|
+
text: `ā Failed to list discussions: ${error instanceof Error ? error.message : String(error)}`,
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
// ============================================================================
|
|
221
|
+
// TOOL 2: FETCH DISCUSSION MESSAGES
|
|
222
|
+
// ============================================================================
|
|
223
|
+
const fetchDiscussionMessagesDescription = `Read latest messages from any Hailer discussion/chat (up to 50 messages - API limit)`;
|
|
224
|
+
exports.fetchDiscussionMessagesTool = {
|
|
225
|
+
name: 'fetch_discussion_messages',
|
|
226
|
+
group: tool_registry_1.ToolGroup.READ,
|
|
227
|
+
description: fetchDiscussionMessagesDescription,
|
|
228
|
+
schema: zod_1.z.object({
|
|
229
|
+
discussionId: zod_1.z
|
|
230
|
+
.string()
|
|
231
|
+
.min(24, "Discussion ID must be at least 24 characters")
|
|
232
|
+
.describe("The discussion ID to read messages from. You can get this from activity results (look for 'discussion' field) or from URLs like https://app.hailer.com/#/discussions/DISCUSSION_ID"),
|
|
233
|
+
limit: zod_1.z
|
|
234
|
+
.number()
|
|
235
|
+
.int()
|
|
236
|
+
.min(1, "Limit must be at least 1")
|
|
237
|
+
.max(50, "Limit cannot exceed 50 messages (API maximum)")
|
|
238
|
+
.default(50)
|
|
239
|
+
.optional()
|
|
240
|
+
.describe("Number of messages to fetch (default: 50, max: 50 due to API limit). Use fetch_previous_discussion_messages for older messages")
|
|
241
|
+
}),
|
|
242
|
+
async execute(args, context) {
|
|
243
|
+
const limit = args.limit || 50;
|
|
244
|
+
logger.debug('Fetching discussion messages', {
|
|
245
|
+
discussionId: args.discussionId,
|
|
246
|
+
limit: limit,
|
|
247
|
+
apiKey: context.apiKey.substring(0, 8) + '...'
|
|
248
|
+
});
|
|
249
|
+
try {
|
|
250
|
+
const result = await context.hailer.fetchDiscussionMessages(args.discussionId, limit);
|
|
251
|
+
const messages = result.messages || [];
|
|
252
|
+
const messageCount = messages.length;
|
|
253
|
+
const userMessages = messages.filter((m) => m.type === "user");
|
|
254
|
+
const recentMessage = messages[0];
|
|
255
|
+
const optimizedMessages = messages.map((msg) => {
|
|
256
|
+
let cleanMsg = msg.msg || '';
|
|
257
|
+
if (cleanMsg) {
|
|
258
|
+
cleanMsg = cleanMsg.replace(/data:[^;]+;base64,[A-Za-z0-9+/=]+/g, '[FILE_REMOVED]');
|
|
259
|
+
cleanMsg = cleanMsg.replace(/!\[.*?\]\(data:[^)]+\)/g, '[IMAGE_REMOVED]');
|
|
260
|
+
}
|
|
261
|
+
const userInfo = (0, workspace_cache_1.getUserById)(context.workspaceCache, msg.uid);
|
|
262
|
+
let username = `User ${msg.uid}`;
|
|
263
|
+
if (userInfo) {
|
|
264
|
+
const fullName = `${userInfo.firstname || ''} ${userInfo.lastname || ''}`.trim();
|
|
265
|
+
username = fullName || userInfo.fullName || `User ${msg.uid}`;
|
|
266
|
+
}
|
|
267
|
+
const optimizedMsg = {
|
|
268
|
+
_id: msg._id,
|
|
269
|
+
username: username,
|
|
270
|
+
created: new Date(msg.created).toLocaleString(),
|
|
271
|
+
type: msg.type,
|
|
272
|
+
msg: cleanMsg
|
|
273
|
+
};
|
|
274
|
+
if (msg.type !== 'user' && !msg.msg) {
|
|
275
|
+
optimizedMsg.systemDescription = formatSystemMessage(msg);
|
|
276
|
+
if (msg.meta && Object.keys(msg.meta).length > 0) {
|
|
277
|
+
optimizedMsg.meta = msg.meta;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (msg.replyTo) {
|
|
281
|
+
optimizedMsg.replyTo = msg.replyTo;
|
|
282
|
+
}
|
|
283
|
+
return optimizedMsg;
|
|
284
|
+
});
|
|
285
|
+
const optimizedResult = {
|
|
286
|
+
oldestLoaded: result.oldestLoaded,
|
|
287
|
+
newestLoaded: result.newestLoaded,
|
|
288
|
+
messages: optimizedMessages
|
|
289
|
+
};
|
|
290
|
+
let responseText = `š¬ Successfully fetched ${messageCount} messages from discussion ${args.discussionId}`;
|
|
291
|
+
if (result.truncated) {
|
|
292
|
+
responseText += ` (limited from ${result.originalCount} available messages)`;
|
|
293
|
+
}
|
|
294
|
+
responseText += `\n\nš Summary:\n- ${userMessages.length} user messages\n- ${messages.length - userMessages.length} system messages (joins, activity creation, etc.)\n- Most recent: ${recentMessage ? `"${recentMessage.msg || "System event"}" at ${new Date(recentMessage.created).toLocaleString()}` : "No messages"}`;
|
|
295
|
+
responseText += `\n- Requested limit: ${limit} messages (API max: 50)`;
|
|
296
|
+
if (result.truncated) {
|
|
297
|
+
responseText += `\n- ā ļø More messages available: ${result.originalCount - limit} additional messages not shown`;
|
|
298
|
+
}
|
|
299
|
+
if (optimizedMessages.length > 0) {
|
|
300
|
+
const oldestMessage = optimizedMessages[optimizedMessages.length - 1];
|
|
301
|
+
responseText += `\n\nš **PAGINATION**: To get older messages, use fetch_previous_discussion_messages with:`;
|
|
302
|
+
responseText += `\n- oldestMessageId: "${oldestMessage._id}"`;
|
|
303
|
+
responseText += `\n- batches: 1-10 (each batch = ~50 messages)`;
|
|
304
|
+
}
|
|
305
|
+
responseText += `\n\nš” Use this to understand conversation context, get project updates, or analyze team communications!\n\nOptimized conversation data (essential fields only):\n${JSON.stringify(optimizedResult, null, 2)}`;
|
|
306
|
+
return {
|
|
307
|
+
content: [
|
|
308
|
+
{
|
|
309
|
+
type: "text",
|
|
310
|
+
text: responseText,
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
logger.error("Error fetching discussion messages", error);
|
|
317
|
+
const errorMessage = error instanceof Error
|
|
318
|
+
? error.message
|
|
319
|
+
: (typeof error === 'object' && error !== null)
|
|
320
|
+
? JSON.stringify(error, null, 2)
|
|
321
|
+
: String(error);
|
|
322
|
+
const isPermissionError = errorMessage.toLowerCase().includes('permission') ||
|
|
323
|
+
errorMessage.toLowerCase().includes('access') ||
|
|
324
|
+
errorMessage.toLowerCase().includes('forbidden') ||
|
|
325
|
+
errorMessage.toLowerCase().includes('unauthorized');
|
|
326
|
+
let helpText = `ā Error fetching discussion messages: ${errorMessage}\n\n`;
|
|
327
|
+
if (isPermissionError) {
|
|
328
|
+
helpText += `š **PERMISSION REQUIRED**: You need to join this discussion before you can read messages.\n\n` +
|
|
329
|
+
`š” **Solution**: Use the join_discussion tool first:\n` +
|
|
330
|
+
` join_discussion(discussionId: "${args.discussionId}")\n\n`;
|
|
331
|
+
}
|
|
332
|
+
helpText += `š” Tips:\n` +
|
|
333
|
+
`- Make sure the discussion ID is correct (24-character string)\n` +
|
|
334
|
+
`- You must be a member/follower of the discussion to read messages\n` +
|
|
335
|
+
`- Some discussions may be private or require specific permissions\n\n` +
|
|
336
|
+
`Example discussion ID: '683ef53087e3d8329abaa3ad'`;
|
|
337
|
+
return {
|
|
338
|
+
content: [
|
|
339
|
+
{
|
|
340
|
+
type: "text",
|
|
341
|
+
text: helpText,
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
};
|
|
348
|
+
// ============================================================================
|
|
349
|
+
// TOOL 3: FETCH PREVIOUS DISCUSSION MESSAGES
|
|
350
|
+
// ============================================================================
|
|
351
|
+
const fetchPreviousDiscussionMessagesDescription = `Fetch older messages from a discussion using pagination - requires a message ID from previous results to continue from`;
|
|
352
|
+
exports.fetchPreviousDiscussionMessagesTool = {
|
|
353
|
+
name: 'fetch_previous_discussion_messages',
|
|
354
|
+
group: tool_registry_1.ToolGroup.READ,
|
|
355
|
+
description: fetchPreviousDiscussionMessagesDescription,
|
|
356
|
+
schema: zod_1.z.object({
|
|
357
|
+
oldestMessageId: zod_1.z
|
|
358
|
+
.string()
|
|
359
|
+
.min(24, "Message ID must be at least 24 characters")
|
|
360
|
+
.describe("The ID of the oldest message from your previous fetch - use this to continue pagination. Get this from the '_id' field of the last message in previous results"),
|
|
361
|
+
batches: zod_1.z
|
|
362
|
+
.number()
|
|
363
|
+
.int()
|
|
364
|
+
.min(1, "Batches must be at least 1")
|
|
365
|
+
.max(10, "Cannot fetch more than 10 batches at once")
|
|
366
|
+
.default(1)
|
|
367
|
+
.optional()
|
|
368
|
+
.describe("Number of previous message batches to fetch (default: 1, max: 10). Each batch contains up to 50 messages. Use higher values to get more history in one call")
|
|
369
|
+
}),
|
|
370
|
+
async execute(args, context) {
|
|
371
|
+
const batches = args.batches || 1;
|
|
372
|
+
logger.debug('Fetching previous discussion messages', {
|
|
373
|
+
oldestMessageId: args.oldestMessageId,
|
|
374
|
+
batches: batches,
|
|
375
|
+
apiKey: context.apiKey.substring(0, 8) + '...'
|
|
376
|
+
});
|
|
377
|
+
try {
|
|
378
|
+
const result = await context.hailer.fetchPreviousDiscussionMessages(args.oldestMessageId, batches);
|
|
379
|
+
const messages = result.messages || [];
|
|
380
|
+
const messageCount = messages.length;
|
|
381
|
+
const userMessages = messages.filter((m) => m.type === "user");
|
|
382
|
+
const oldestMessage = messages.length > 0 ? messages[messages.length - 1] : null;
|
|
383
|
+
const optimizedMessages = messages.map((msg) => {
|
|
384
|
+
let cleanMsg = msg.msg || '';
|
|
385
|
+
if (cleanMsg) {
|
|
386
|
+
cleanMsg = cleanMsg.replace(/data:[^;]+;base64,[A-Za-z0-9+/=]+/g, '[FILE_REMOVED]');
|
|
387
|
+
cleanMsg = cleanMsg.replace(/!\[.*?\]\(data:[^)]+\)/g, '[IMAGE_REMOVED]');
|
|
388
|
+
}
|
|
389
|
+
const userInfo = (0, workspace_cache_1.getUserById)(context.workspaceCache, msg.uid);
|
|
390
|
+
let username = `User ${msg.uid}`;
|
|
391
|
+
if (userInfo) {
|
|
392
|
+
const fullName = `${userInfo.firstname || ''} ${userInfo.lastname || ''}`.trim();
|
|
393
|
+
username = fullName || userInfo.fullName || `User ${msg.uid}`;
|
|
394
|
+
}
|
|
395
|
+
const optimizedMsg = {
|
|
396
|
+
_id: msg._id,
|
|
397
|
+
username: username,
|
|
398
|
+
created: new Date(msg.created).toLocaleString(),
|
|
399
|
+
msg: cleanMsg
|
|
400
|
+
};
|
|
401
|
+
if (msg.replyTo) {
|
|
402
|
+
optimizedMsg.replyTo = msg.replyTo;
|
|
403
|
+
}
|
|
404
|
+
return optimizedMsg;
|
|
405
|
+
});
|
|
406
|
+
const optimizedResult = {
|
|
407
|
+
messages: optimizedMessages,
|
|
408
|
+
totalFetched: result.totalFetched,
|
|
409
|
+
batchesCompleted: result.batchesCompleted,
|
|
410
|
+
oldestMessageId: result.oldestMessageId,
|
|
411
|
+
hasMore: result.hasMore
|
|
412
|
+
};
|
|
413
|
+
let responseText = `š¬ Successfully fetched ${messageCount} previous messages`;
|
|
414
|
+
responseText += `\n\nš Summary:`;
|
|
415
|
+
responseText += `\n- ${userMessages.length} user messages`;
|
|
416
|
+
responseText += `\n- ${messages.length - userMessages.length} system messages`;
|
|
417
|
+
responseText += `\n- Batches completed: ${result.batchesCompleted}/${batches}`;
|
|
418
|
+
responseText += `\n- Total messages fetched: ${result.totalFetched}`;
|
|
419
|
+
if (oldestMessage) {
|
|
420
|
+
responseText += `\n- Oldest message: "${oldestMessage.msg || 'System event'}" at ${new Date(oldestMessage.created).toLocaleString()}`;
|
|
421
|
+
}
|
|
422
|
+
if (result.hasMore && optimizedMessages.length > 0) {
|
|
423
|
+
const oldestOptimizedMessage = optimizedMessages[optimizedMessages.length - 1];
|
|
424
|
+
responseText += `\n\nš **CONTINUE PAGINATION**: To get even older messages:`;
|
|
425
|
+
responseText += `\n- oldestMessageId: "${oldestOptimizedMessage._id}"`;
|
|
426
|
+
responseText += `\n- batches: 1-10 (for more history)`;
|
|
427
|
+
}
|
|
428
|
+
else if (!result.hasMore) {
|
|
429
|
+
responseText += `\n\nā
**END OF HISTORY**: No more messages available in this discussion.`;
|
|
430
|
+
}
|
|
431
|
+
responseText += `\n\nš” Use this to explore conversation history and understand context!\n\nOptimized conversation data (essential fields only):\n${JSON.stringify(optimizedResult, null, 2)}`;
|
|
432
|
+
return {
|
|
433
|
+
content: [
|
|
434
|
+
{
|
|
435
|
+
type: "text",
|
|
436
|
+
text: responseText,
|
|
437
|
+
},
|
|
438
|
+
],
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
catch (error) {
|
|
442
|
+
logger.error("Error fetching previous discussion messages", error);
|
|
443
|
+
return {
|
|
444
|
+
content: [
|
|
445
|
+
{
|
|
446
|
+
type: "text",
|
|
447
|
+
text: `ā Error fetching previous discussion messages: ${error instanceof Error ? error.message : String(error)}\n\nš” **IMPORTANT**: Make sure you're using a MESSAGE ID, not a DISCUSSION ID!\n\n**How to get the correct oldestMessageId:**\n1. First call fetch_discussion_messages with your discussionId\n2. Look at the last message in the results\n3. Use that message's '_id' field as oldestMessageId\n\n**Example:**\n- ā Wrong: "682dcfa9d36a4907a88bb279" (this is a discussion ID)\n- ā
Correct: "68e5c1903b104307efdebce9" (this is a message ID)\n\nMessage IDs are usually different from discussion IDs!`,
|
|
448
|
+
},
|
|
449
|
+
],
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
};
|
|
454
|
+
// ============================================================================
|
|
455
|
+
// TOOL 4: JOIN DISCUSSION
|
|
456
|
+
// ============================================================================
|
|
457
|
+
const joinDiscussionDescription = `Join a discussion to gain access to read and post messages - Use this for activity discussions (provide activityId) or general discussions (provide discussionId)`;
|
|
458
|
+
exports.joinDiscussionTool = {
|
|
459
|
+
name: 'join_discussion',
|
|
460
|
+
group: tool_registry_1.ToolGroup.WRITE,
|
|
461
|
+
description: joinDiscussionDescription,
|
|
462
|
+
schema: zod_1.z.object({
|
|
463
|
+
activityId: zod_1.z
|
|
464
|
+
.string()
|
|
465
|
+
.min(24, "Activity ID must be at least 24 characters")
|
|
466
|
+
.optional()
|
|
467
|
+
.describe("Activity ID to join its discussion (preferred method for activity discussions)"),
|
|
468
|
+
discussionId: zod_1.z
|
|
469
|
+
.string()
|
|
470
|
+
.min(24, "Discussion ID must be at least 24 characters")
|
|
471
|
+
.optional()
|
|
472
|
+
.describe("Discussion ID to join directly (use this for non-activity discussions)")
|
|
473
|
+
}).refine((data) => data.activityId || data.discussionId, {
|
|
474
|
+
message: "Either activityId or discussionId must be provided"
|
|
475
|
+
}),
|
|
476
|
+
async execute(args, context) {
|
|
477
|
+
logger.debug('Joining discussion', {
|
|
478
|
+
activityId: args.activityId,
|
|
479
|
+
discussionId: args.discussionId,
|
|
480
|
+
apiKey: context.apiKey.substring(0, 8) + '...'
|
|
481
|
+
});
|
|
482
|
+
try {
|
|
483
|
+
if (args.activityId) {
|
|
484
|
+
const result = await context.hailer.joinActivityDiscussion(args.activityId);
|
|
485
|
+
const activity = await context.hailer.fetchActivityById(args.activityId);
|
|
486
|
+
const discussionId = activity.discussion;
|
|
487
|
+
return {
|
|
488
|
+
content: [
|
|
489
|
+
{
|
|
490
|
+
type: "text",
|
|
491
|
+
text: `ā
Successfully joined activity discussion!\n\n` +
|
|
492
|
+
`š **Activity**: ${activity.name || args.activityId}\n` +
|
|
493
|
+
`š¬ **Discussion ID**: ${discussionId}\n` +
|
|
494
|
+
`š **Status**: ${result ? 'Now following this activity' : 'Already following'}\n\n` +
|
|
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
|
+
};
|
|
505
|
+
}
|
|
506
|
+
if (args.discussionId) {
|
|
507
|
+
const result = await context.hailer.joinDiscussion(args.discussionId);
|
|
508
|
+
return {
|
|
509
|
+
content: [
|
|
510
|
+
{
|
|
511
|
+
type: "text",
|
|
512
|
+
text: `ā
${result.message}\n\n` +
|
|
513
|
+
`š¬ **Discussion ID**: ${args.discussionId}\n` +
|
|
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
|
+
],
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
content: [
|
|
526
|
+
{
|
|
527
|
+
type: "text",
|
|
528
|
+
text: `ā Error: Either activityId or discussionId must be provided`,
|
|
529
|
+
},
|
|
530
|
+
],
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
catch (error) {
|
|
534
|
+
logger.error("Error joining discussion", error);
|
|
535
|
+
const errorMessage = error instanceof Error
|
|
536
|
+
? error.message
|
|
537
|
+
: (typeof error === 'object' && error !== null)
|
|
538
|
+
? JSON.stringify(error, null, 2)
|
|
539
|
+
: String(error);
|
|
540
|
+
return {
|
|
541
|
+
content: [
|
|
542
|
+
{
|
|
543
|
+
type: "text",
|
|
544
|
+
text: `ā Error joining discussion: ${errorMessage}\n\n` +
|
|
545
|
+
`š” Troubleshooting:\n` +
|
|
546
|
+
`- For activity discussions: Use join_discussion(activityId: "...")\n` +
|
|
547
|
+
`- The activity or discussion must exist and be accessible\n` +
|
|
548
|
+
`- You need permission to join this discussion\n` +
|
|
549
|
+
`- Some discussions may be private or require an invitation\n\n` +
|
|
550
|
+
`š Examples:\n` +
|
|
551
|
+
`- Activity discussion: join_discussion(activityId: "68446dc05b30685f67c6fcd4")\n` +
|
|
552
|
+
`- General discussion: join_discussion(discussionId: "683ef53087e3d8329abaa3ad")`,
|
|
553
|
+
},
|
|
554
|
+
],
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
},
|
|
558
|
+
};
|
|
559
|
+
// ============================================================================
|
|
560
|
+
// TOOL 5: LEAVE DISCUSSION
|
|
561
|
+
// ============================================================================
|
|
562
|
+
const leaveDiscussionDescription = `Leave a discussion - Works for activity discussions (provide activityId) or general discussions (provide discussionId)`;
|
|
563
|
+
exports.leaveDiscussionTool = {
|
|
564
|
+
name: 'leave_discussion',
|
|
565
|
+
group: tool_registry_1.ToolGroup.WRITE,
|
|
566
|
+
description: leaveDiscussionDescription,
|
|
567
|
+
schema: zod_1.z.object({
|
|
568
|
+
activityId: zod_1.z
|
|
569
|
+
.string()
|
|
570
|
+
.min(24, "Activity ID must be at least 24 characters")
|
|
571
|
+
.optional()
|
|
572
|
+
.describe("Activity ID to leave its discussion (preferred method for activity discussions)"),
|
|
573
|
+
discussionId: zod_1.z
|
|
574
|
+
.string()
|
|
575
|
+
.min(24, "Discussion ID must be at least 24 characters")
|
|
576
|
+
.optional()
|
|
577
|
+
.describe("Discussion ID to leave directly")
|
|
578
|
+
}).refine((data) => data.activityId || data.discussionId, {
|
|
579
|
+
message: "Either activityId or discussionId must be provided"
|
|
580
|
+
}),
|
|
581
|
+
async execute(args, context) {
|
|
582
|
+
logger.debug('Leaving discussion', {
|
|
583
|
+
activityId: args.activityId,
|
|
584
|
+
discussionId: args.discussionId,
|
|
585
|
+
apiKey: context.apiKey.substring(0, 8) + '...'
|
|
586
|
+
});
|
|
587
|
+
try {
|
|
588
|
+
if (args.activityId) {
|
|
589
|
+
const result = await context.hailer.leaveActivityDiscussion(args.activityId);
|
|
590
|
+
const activity = await context.hailer.fetchActivityById(args.activityId);
|
|
591
|
+
const discussionId = activity.discussion;
|
|
592
|
+
return {
|
|
593
|
+
content: [
|
|
594
|
+
{
|
|
595
|
+
type: "text",
|
|
596
|
+
text: `ā
Successfully left activity discussion!\n\n` +
|
|
597
|
+
`š **Activity**: ${activity.name || args.activityId}\n` +
|
|
598
|
+
`š¬ **Discussion ID**: ${discussionId}\n` +
|
|
599
|
+
`š **Status**: ${result ? 'Unfollowed activity' : 'Was not following'}\n\n` +
|
|
600
|
+
`š You will no longer receive notifications from this discussion.\n\n` +
|
|
601
|
+
`š” To rejoin: join_discussion(activityId: "${args.activityId}")\n\n` +
|
|
602
|
+
`š **Links:**\n` +
|
|
603
|
+
`- Activity: https://app.hailer.com/#/activities/${args.activityId}\n` +
|
|
604
|
+
`- Discussion: https://app.hailer.com/#/discussions/${discussionId}`,
|
|
605
|
+
},
|
|
606
|
+
],
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
if (args.discussionId) {
|
|
610
|
+
await context.hailer.leaveDiscussion(args.discussionId);
|
|
611
|
+
return {
|
|
612
|
+
content: [
|
|
613
|
+
{
|
|
614
|
+
type: "text",
|
|
615
|
+
text: `ā
Successfully left discussion!\n\n` +
|
|
616
|
+
`š¬ **Discussion ID**: ${args.discussionId}\n\n` +
|
|
617
|
+
`š You will no longer receive notifications from this discussion.\n\n` +
|
|
618
|
+
`š” To rejoin: join_discussion(discussionId: "${args.discussionId}")\n\n` +
|
|
619
|
+
`š **Direct Link:** https://app.hailer.com/#/discussions/${args.discussionId}`,
|
|
620
|
+
},
|
|
621
|
+
],
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
return {
|
|
625
|
+
content: [
|
|
626
|
+
{
|
|
627
|
+
type: "text",
|
|
628
|
+
text: `ā Error: Either activityId or discussionId must be provided`,
|
|
629
|
+
},
|
|
630
|
+
],
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
catch (error) {
|
|
634
|
+
logger.error("Error leaving discussion", error);
|
|
635
|
+
const errorMessage = error instanceof Error
|
|
636
|
+
? error.message
|
|
637
|
+
: (typeof error === 'object' && error !== null)
|
|
638
|
+
? JSON.stringify(error, null, 2)
|
|
639
|
+
: String(error);
|
|
640
|
+
return {
|
|
641
|
+
content: [
|
|
642
|
+
{
|
|
643
|
+
type: "text",
|
|
644
|
+
text: `ā Error leaving discussion: ${errorMessage}\n\n` +
|
|
645
|
+
`š” Troubleshooting:\n` +
|
|
646
|
+
`- For activity discussions: Use leave_discussion(activityId: "...")\n` +
|
|
647
|
+
`- For general discussions: Use leave_discussion(discussionId: "...")\n` +
|
|
648
|
+
`- The activity or discussion must exist and be accessible\n` +
|
|
649
|
+
`- You need permission to leave this discussion\n` +
|
|
650
|
+
`- Some discussions may have restrictions on leaving\n\n` +
|
|
651
|
+
`š Examples:\n` +
|
|
652
|
+
`- Activity discussion: leave_discussion(activityId: "68446dc05b30685f67c6fcd4")\n` +
|
|
653
|
+
`- General discussion: leave_discussion(discussionId: "683ef53087e3d8329abaa3ad")`,
|
|
654
|
+
},
|
|
655
|
+
],
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
},
|
|
659
|
+
};
|
|
660
|
+
// ============================================================================
|
|
661
|
+
// TOOL 6: ADD DISCUSSION MESSAGE
|
|
662
|
+
// ============================================================================
|
|
663
|
+
const addDiscussionMessageDescription = `Post a message to a Hailer discussion with optional file attachments - AI agents can participate in conversations`;
|
|
664
|
+
exports.addDiscussionMessageTool = {
|
|
665
|
+
name: 'add_discussion_message',
|
|
666
|
+
group: tool_registry_1.ToolGroup.WRITE,
|
|
667
|
+
description: addDiscussionMessageDescription,
|
|
668
|
+
schema: zod_1.z.object({
|
|
669
|
+
discussionId: zod_1.z.string().min(24, "Discussion ID must be at least 24 characters").describe("The discussion ID where to post the message"),
|
|
670
|
+
content: zod_1.z.string().min(1, "Message content cannot be empty").describe("The message text to post"),
|
|
671
|
+
fileIds: zod_1.z.array(zod_1.z.string()).optional().describe("Optional array of file IDs to attach (from upload_files tool)")
|
|
672
|
+
}),
|
|
673
|
+
async execute(args, context) {
|
|
674
|
+
try {
|
|
675
|
+
const result = await context.hailer.sendDiscussionMessage(args.discussionId, args.content, args.fileIds);
|
|
676
|
+
let responseText = `ā
Successfully posted message to discussion ${args.discussionId}!\n\nš Message: "${args.content}"`;
|
|
677
|
+
if (args.fileIds && args.fileIds.length > 0) {
|
|
678
|
+
responseText += `\nš Attachments: ${args.fileIds.length} file(s)`;
|
|
679
|
+
}
|
|
680
|
+
responseText += `\n\nš¬ Your message has been added to the conversation and team members will be notified.\n\nResponse data:\n${JSON.stringify(result, null, 2)}`;
|
|
681
|
+
return {
|
|
682
|
+
content: [
|
|
683
|
+
{
|
|
684
|
+
type: "text",
|
|
685
|
+
text: responseText,
|
|
686
|
+
},
|
|
687
|
+
],
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
catch (error) {
|
|
691
|
+
return {
|
|
692
|
+
content: [
|
|
693
|
+
{
|
|
694
|
+
type: "text",
|
|
695
|
+
text: `ā Error posting message: ${error instanceof Error ? error.message : String(error)}\n\nš” Tips:\n- Make sure the discussion ID is correct (24-character string)\n- Check that you have permission to post to this discussion\n- Verify the discussion exists and is accessible\n- If attaching files, ensure file IDs are valid (from upload_files tool)\n\nExample discussion ID: '683ef53087e3d8329abaa3ad'`,
|
|
696
|
+
},
|
|
697
|
+
],
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
},
|
|
701
|
+
};
|
|
702
|
+
// ============================================================================
|
|
703
|
+
// TOOL 7: INVITE DISCUSSION MEMBERS
|
|
704
|
+
// ============================================================================
|
|
705
|
+
const inviteDiscussionMembersDescription = `Invite users to a discussion - Manager bots can use this to bring specialized bots or users into conversations based on needs`;
|
|
706
|
+
exports.inviteDiscussionMembersTool = {
|
|
707
|
+
name: 'invite_discussion_members',
|
|
708
|
+
group: tool_registry_1.ToolGroup.WRITE,
|
|
709
|
+
description: inviteDiscussionMembersDescription,
|
|
710
|
+
schema: zod_1.z.object({
|
|
711
|
+
discussionId: zod_1.z
|
|
712
|
+
.string()
|
|
713
|
+
.min(24, "Discussion ID must be at least 24 characters")
|
|
714
|
+
.describe("The discussion ID where to invite members"),
|
|
715
|
+
userIds: zod_1.z
|
|
716
|
+
.array(zod_1.z.string().min(24, "User ID must be at least 24 characters"))
|
|
717
|
+
.min(1, "At least one user ID must be provided")
|
|
718
|
+
.describe("Array of user IDs to invite to the discussion. Use search_workspace_users to find user IDs")
|
|
719
|
+
}),
|
|
720
|
+
async execute(args, context) {
|
|
721
|
+
logger.debug('Inviting members to discussion', {
|
|
722
|
+
discussionId: args.discussionId,
|
|
723
|
+
userIds: args.userIds,
|
|
724
|
+
count: args.userIds.length,
|
|
725
|
+
apiKey: context.apiKey.substring(0, 8) + '...'
|
|
726
|
+
});
|
|
727
|
+
try {
|
|
728
|
+
// Use messenger.invite_users endpoint
|
|
729
|
+
const result = await context.hailer.request('messenger.invite_users', [
|
|
730
|
+
args.discussionId,
|
|
731
|
+
args.userIds
|
|
732
|
+
]);
|
|
733
|
+
logger.debug('Invite result', { result });
|
|
734
|
+
// Get usernames for better feedback
|
|
735
|
+
const invitedUsers = args.userIds.map((userId) => {
|
|
736
|
+
const userInfo = (0, workspace_cache_1.getUserById)(context.workspaceCache, userId);
|
|
737
|
+
const fullName = userInfo
|
|
738
|
+
? `${userInfo.firstname || ''} ${userInfo.lastname || ''}`.trim() || userInfo.fullName
|
|
739
|
+
: userId;
|
|
740
|
+
return `- ${fullName} (${userId})`;
|
|
741
|
+
}).join('\n');
|
|
742
|
+
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
|
+
],
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
catch (error) {
|
|
760
|
+
logger.error("Error inviting discussion members", error);
|
|
761
|
+
const errorMessage = error instanceof Error
|
|
762
|
+
? error.message
|
|
763
|
+
: (typeof error === 'object' && error !== null)
|
|
764
|
+
? JSON.stringify(error, null, 2)
|
|
765
|
+
: String(error);
|
|
766
|
+
return {
|
|
767
|
+
content: [
|
|
768
|
+
{
|
|
769
|
+
type: "text",
|
|
770
|
+
text: `ā Error inviting members to discussion: ${errorMessage}\n\n` +
|
|
771
|
+
`š” Troubleshooting:\n` +
|
|
772
|
+
`- Verify the discussion ID is correct (24-character string)\n` +
|
|
773
|
+
`- Ensure all user IDs are valid (use search_workspace_users tool)\n` +
|
|
774
|
+
`- You need permission to invite members to this discussion\n` +
|
|
775
|
+
`- Some discussions may have restrictions on adding members\n\n` +
|
|
776
|
+
`š Example:\n` +
|
|
777
|
+
`invite_discussion_members({\n` +
|
|
778
|
+
` discussionId: "683ef53087e3d8329abaa3ad",\n` +
|
|
779
|
+
` userIds: ["690aed2924d79161d7529fd7", "690aed2924d79161d7529fd8"]\n` +
|
|
780
|
+
`})`,
|
|
781
|
+
},
|
|
782
|
+
],
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
},
|
|
786
|
+
};
|
|
787
|
+
// Note: Hailer API does not have a "kick" or "remove members" feature.
|
|
788
|
+
// Bots can only remove themselves using the leave_discussion tool.
|
|
789
|
+
// ============================================================================
|
|
790
|
+
// TOOL 8: GET ACTIVITY FROM DISCUSSION
|
|
791
|
+
// ============================================================================
|
|
792
|
+
const getActivityFromDiscussionDescription = `š Reverse lookup: Get activity details from a discussion ID
|
|
793
|
+
|
|
794
|
+
**Purpose**: When you have a discussion ID but need to know what activity it belongs to.
|
|
795
|
+
|
|
796
|
+
**Example**:
|
|
797
|
+
\`\`\`javascript
|
|
798
|
+
get_activity_from_discussion({
|
|
799
|
+
discussionId: "691ffe874217e9e8434e57f5"
|
|
800
|
+
})
|
|
801
|
+
\`\`\`
|
|
802
|
+
|
|
803
|
+
**Returns**: Activity name, ID, workflow info, phase, and field values
|
|
804
|
+
|
|
805
|
+
**Use Cases**:
|
|
806
|
+
- Identify which activity a discussion belongs to
|
|
807
|
+
- Get activity context when processing discussion messages
|
|
808
|
+
- Navigate from chat to activity details`;
|
|
809
|
+
exports.getActivityFromDiscussionTool = {
|
|
810
|
+
name: 'get_activity_from_discussion',
|
|
811
|
+
group: tool_registry_1.ToolGroup.READ,
|
|
812
|
+
description: getActivityFromDiscussionDescription,
|
|
813
|
+
schema: zod_1.z.object({
|
|
814
|
+
discussionId: zod_1.z
|
|
815
|
+
.string()
|
|
816
|
+
.min(24, "Discussion ID must be at least 24 characters")
|
|
817
|
+
.describe("The discussion ID to look up the associated activity for")
|
|
818
|
+
}),
|
|
819
|
+
async execute(args, context) {
|
|
820
|
+
logger.debug('Getting activity from discussion', {
|
|
821
|
+
discussionId: args.discussionId,
|
|
822
|
+
apiKey: context.apiKey.substring(0, 8) + '...'
|
|
823
|
+
});
|
|
824
|
+
try {
|
|
825
|
+
// First, sync discussions to find the one with this ID
|
|
826
|
+
const syncResponse = await context.hailer.request('v2.discussion.sync', [{ timestamp: 0 }]);
|
|
827
|
+
const discussions = syncResponse.discussions || [];
|
|
828
|
+
const targetDiscussion = discussions.find((d) => d._id === args.discussionId);
|
|
829
|
+
if (!targetDiscussion) {
|
|
830
|
+
return {
|
|
831
|
+
content: [{
|
|
832
|
+
type: "text",
|
|
833
|
+
text: `ā Discussion not found: ${args.discussionId}\n\n` +
|
|
834
|
+
`š” **Possible reasons:**\n` +
|
|
835
|
+
`- You are not a member of this discussion\n` +
|
|
836
|
+
`- The discussion ID is incorrect\n` +
|
|
837
|
+
`- The discussion has been deleted\n\n` +
|
|
838
|
+
`**Tip**: Use \`list_my_discussions\` to see discussions you have access to.`
|
|
839
|
+
}]
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
// Check if this discussion is linked to an activity
|
|
843
|
+
if (!targetDiscussion.linked_activity) {
|
|
844
|
+
let discussionType = 'unknown';
|
|
845
|
+
if (targetDiscussion.linked_event) {
|
|
846
|
+
discussionType = 'event';
|
|
847
|
+
}
|
|
848
|
+
else if (targetDiscussion.private === true) {
|
|
849
|
+
discussionType = 'private';
|
|
850
|
+
}
|
|
851
|
+
else if (targetDiscussion.private === false) {
|
|
852
|
+
discussionType = 'group';
|
|
853
|
+
}
|
|
854
|
+
return {
|
|
855
|
+
content: [{
|
|
856
|
+
type: "text",
|
|
857
|
+
text: `ā ļø Discussion found but not linked to an activity\n\n` +
|
|
858
|
+
`š¬ **Discussion ID**: \`${args.discussionId}\`\n` +
|
|
859
|
+
`š **Type**: ${discussionType}\n` +
|
|
860
|
+
`š **Name**: ${sanitizeString(targetDiscussion.name) || 'Untitled'}\n` +
|
|
861
|
+
(targetDiscussion.linked_event ? `š
**Event ID**: \`${targetDiscussion.linked_event}\`\n` : '') +
|
|
862
|
+
`\nš” This discussion is not associated with any activity.`
|
|
863
|
+
}]
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
// Fetch the linked activity details
|
|
867
|
+
const activityId = targetDiscussion.linked_activity;
|
|
868
|
+
const activity = await context.hailer.fetchActivityById(activityId);
|
|
869
|
+
if (!activity) {
|
|
870
|
+
return {
|
|
871
|
+
content: [{
|
|
872
|
+
type: "text",
|
|
873
|
+
text: `ā ļø Discussion is linked to activity \`${activityId}\` but activity could not be loaded.\n\n` +
|
|
874
|
+
`š” **Possible reasons:**\n` +
|
|
875
|
+
`- The activity has been deleted\n` +
|
|
876
|
+
`- You don't have permission to view the activity\n` +
|
|
877
|
+
`- The activity is in a different workspace`
|
|
878
|
+
}]
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
// Build comprehensive response
|
|
882
|
+
let responseText = `ā
**Activity Found!**\n\n`;
|
|
883
|
+
responseText += `š **Activity Details:**\n`;
|
|
884
|
+
responseText += `- **Name**: ${activity.name || 'Untitled'}\n`;
|
|
885
|
+
responseText += `- **Activity ID**: \`${activity._id}\`\n`;
|
|
886
|
+
responseText += `- **Discussion ID**: \`${args.discussionId}\`\n`;
|
|
887
|
+
if (activity.workflowId) {
|
|
888
|
+
responseText += `- **Workflow ID**: \`${activity.workflowId}\`\n`;
|
|
889
|
+
}
|
|
890
|
+
if (activity.workflowName) {
|
|
891
|
+
responseText += `- **Workflow**: ${activity.workflowName}\n`;
|
|
892
|
+
}
|
|
893
|
+
if (activity.phaseName) {
|
|
894
|
+
responseText += `- **Phase**: ${activity.phaseName}\n`;
|
|
895
|
+
}
|
|
896
|
+
if (activity.phaseId) {
|
|
897
|
+
responseText += `- **Phase ID**: \`${activity.phaseId}\`\n`;
|
|
898
|
+
}
|
|
899
|
+
if (activity.created) {
|
|
900
|
+
responseText += `- **Created**: ${new Date(activity.created).toLocaleString()}\n`;
|
|
901
|
+
}
|
|
902
|
+
if (activity.updated) {
|
|
903
|
+
responseText += `- **Updated**: ${new Date(activity.updated).toLocaleString()}\n`;
|
|
904
|
+
}
|
|
905
|
+
// Add field values if present
|
|
906
|
+
if (activity.fields && Object.keys(activity.fields).length > 0) {
|
|
907
|
+
responseText += `\nš **Field Values:**\n`;
|
|
908
|
+
for (const [fieldId, value] of Object.entries(activity.fields)) {
|
|
909
|
+
if (value !== null && value !== undefined && value !== '') {
|
|
910
|
+
const displayValue = typeof value === 'object' ? JSON.stringify(value) : String(value);
|
|
911
|
+
responseText += `- \`${fieldId}\`: ${displayValue}\n`;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
responseText += `\nš **Links:**\n`;
|
|
916
|
+
responseText += `- Activity: https://app.hailer.com/#/activities/${activity._id}\n`;
|
|
917
|
+
responseText += `- Discussion: https://app.hailer.com/#/discussions/${args.discussionId}\n`;
|
|
918
|
+
responseText += `\nš” **Next Steps:**\n`;
|
|
919
|
+
responseText += `- View full activity: \`show_activity_by_id({ activityId: "${activity._id}" })\`\n`;
|
|
920
|
+
responseText += `- List workflow activities: \`list_activities({ workflowId: "${activity.workflowId}", phaseId: "${activity.phaseId}", fields: [...] })\``;
|
|
921
|
+
return {
|
|
922
|
+
content: [{
|
|
923
|
+
type: "text",
|
|
924
|
+
text: responseText
|
|
925
|
+
}]
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
catch (error) {
|
|
929
|
+
logger.error("Error getting activity from discussion", error);
|
|
930
|
+
const errorMessage = error instanceof Error
|
|
931
|
+
? error.message
|
|
932
|
+
: (typeof error === 'object' && error !== null)
|
|
933
|
+
? JSON.stringify(error, null, 2)
|
|
934
|
+
: String(error);
|
|
935
|
+
return {
|
|
936
|
+
content: [{
|
|
937
|
+
type: "text",
|
|
938
|
+
text: `ā Error getting activity from discussion: ${errorMessage}\n\n` +
|
|
939
|
+
`š” **Troubleshooting:**\n` +
|
|
940
|
+
`- Verify the discussion ID is correct (24-character string)\n` +
|
|
941
|
+
`- Ensure you have access to the discussion\n` +
|
|
942
|
+
`- Try \`list_my_discussions\` to see available discussions\n\n` +
|
|
943
|
+
`š **Example:**\n` +
|
|
944
|
+
`get_activity_from_discussion({ discussionId: "691ffe874217e9e8434e57f5" })`
|
|
945
|
+
}]
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
//# sourceMappingURL=discussion.js.map
|