@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.
Files changed (163) hide show
  1. package/.claude/commands/tool-builder.md +37 -0
  2. package/.claude/commands/ws-pull.md +44 -0
  3. package/.claude/settings.json +8 -0
  4. package/.claude/settings.local.json +49 -0
  5. package/.claude/skills/activity-api/SKILL.md +96 -0
  6. package/.claude/skills/activity-api/references/activity-endpoints.md +845 -0
  7. package/.claude/skills/add-app-member-skill/SKILL.md +977 -0
  8. package/.claude/skills/agent-building/SKILL.md +243 -0
  9. package/.claude/skills/agent-building/references/architecture-patterns.md +446 -0
  10. package/.claude/skills/agent-building/references/code-examples.md +587 -0
  11. package/.claude/skills/agent-building/references/implementation-guide.md +619 -0
  12. package/.claude/skills/app-api/SKILL.md +219 -0
  13. package/.claude/skills/app-api/references/app-endpoints.md +759 -0
  14. package/.claude/skills/building-hailer-apps-skill/SKILL.md +548 -0
  15. package/.claude/skills/create-app-skill/SKILL.md +1101 -0
  16. package/.claude/skills/create-insight-skill/SKILL.md +1317 -0
  17. package/.claude/skills/get-insight-data-skill/SKILL.md +1053 -0
  18. package/.claude/skills/hailer-api/SKILL.md +283 -0
  19. package/.claude/skills/hailer-api/references/activities.md +620 -0
  20. package/.claude/skills/hailer-api/references/authentication.md +216 -0
  21. package/.claude/skills/hailer-api/references/datasets.md +437 -0
  22. package/.claude/skills/hailer-api/references/files.md +301 -0
  23. package/.claude/skills/hailer-api/references/insights.md +469 -0
  24. package/.claude/skills/hailer-api/references/workflows.md +720 -0
  25. package/.claude/skills/hailer-api/references/workspaces-users.md +445 -0
  26. package/.claude/skills/insight-api/SKILL.md +185 -0
  27. package/.claude/skills/insight-api/references/insight-endpoints.md +514 -0
  28. package/.claude/skills/install-workflow-skill/SKILL.md +1056 -0
  29. package/.claude/skills/list-apps-skill/SKILL.md +1010 -0
  30. package/.claude/skills/list-workflows-minimal-skill/SKILL.md +992 -0
  31. package/.claude/skills/local-first-skill/SKILL.md +570 -0
  32. package/.claude/skills/mcp-tools/SKILL.md +419 -0
  33. package/.claude/skills/mcp-tools/references/api-endpoints.md +499 -0
  34. package/.claude/skills/mcp-tools/references/data-structures.md +554 -0
  35. package/.claude/skills/mcp-tools/references/implementation-patterns.md +717 -0
  36. package/.claude/skills/preview-insight-skill/SKILL.md +1290 -0
  37. package/.claude/skills/publish-hailer-app-skill/SKILL.md +453 -0
  38. package/.claude/skills/remove-app-member-skill/SKILL.md +671 -0
  39. package/.claude/skills/remove-app-skill/SKILL.md +985 -0
  40. package/.claude/skills/remove-insight-skill/SKILL.md +1011 -0
  41. package/.claude/skills/remove-workflow-skill/SKILL.md +920 -0
  42. package/.claude/skills/scaffold-hailer-app-skill/SKILL.md +1034 -0
  43. package/.claude/skills/skill-testing/README.md +137 -0
  44. package/.claude/skills/skill-testing/SKILL.md +348 -0
  45. package/.claude/skills/skill-testing/references/test-patterns.md +705 -0
  46. package/.claude/skills/skill-testing/references/testing-guide.md +603 -0
  47. package/.claude/skills/skill-testing/references/validation-checklist.md +537 -0
  48. package/.claude/skills/tool-builder/SKILL.md +328 -0
  49. package/.claude/skills/update-app-skill/SKILL.md +970 -0
  50. package/.claude/skills/update-workflow-field-skill/SKILL.md +1098 -0
  51. package/.env.example +81 -0
  52. package/.mcp.json +13 -0
  53. package/README.md +297 -0
  54. package/dist/app.d.ts +4 -0
  55. package/dist/app.js +74 -0
  56. package/dist/cli.d.ts +3 -0
  57. package/dist/cli.js +5 -0
  58. package/dist/client/adaptive-documentation-bot.d.ts +108 -0
  59. package/dist/client/adaptive-documentation-bot.js +475 -0
  60. package/dist/client/adaptive-documentation-types.d.ts +66 -0
  61. package/dist/client/adaptive-documentation-types.js +9 -0
  62. package/dist/client/agent-activity-bot.d.ts +51 -0
  63. package/dist/client/agent-activity-bot.js +166 -0
  64. package/dist/client/agent-tracker.d.ts +499 -0
  65. package/dist/client/agent-tracker.js +659 -0
  66. package/dist/client/description-updater.d.ts +56 -0
  67. package/dist/client/description-updater.js +259 -0
  68. package/dist/client/log-parser.d.ts +72 -0
  69. package/dist/client/log-parser.js +387 -0
  70. package/dist/client/mcp-client.d.ts +50 -0
  71. package/dist/client/mcp-client.js +532 -0
  72. package/dist/client/message-processor.d.ts +35 -0
  73. package/dist/client/message-processor.js +352 -0
  74. package/dist/client/multi-bot-manager.d.ts +24 -0
  75. package/dist/client/multi-bot-manager.js +74 -0
  76. package/dist/client/providers/anthropic-provider.d.ts +19 -0
  77. package/dist/client/providers/anthropic-provider.js +631 -0
  78. package/dist/client/providers/llm-provider.d.ts +47 -0
  79. package/dist/client/providers/llm-provider.js +367 -0
  80. package/dist/client/providers/openai-provider.d.ts +23 -0
  81. package/dist/client/providers/openai-provider.js +621 -0
  82. package/dist/client/simple-llm-caller.d.ts +19 -0
  83. package/dist/client/simple-llm-caller.js +100 -0
  84. package/dist/client/skill-generator.d.ts +81 -0
  85. package/dist/client/skill-generator.js +386 -0
  86. package/dist/client/test-adaptive-bot.d.ts +9 -0
  87. package/dist/client/test-adaptive-bot.js +82 -0
  88. package/dist/client/token-pricing.d.ts +38 -0
  89. package/dist/client/token-pricing.js +127 -0
  90. package/dist/client/token-tracker.d.ts +232 -0
  91. package/dist/client/token-tracker.js +457 -0
  92. package/dist/client/token-usage-bot.d.ts +53 -0
  93. package/dist/client/token-usage-bot.js +153 -0
  94. package/dist/client/tool-executor.d.ts +69 -0
  95. package/dist/client/tool-executor.js +159 -0
  96. package/dist/client/tool-schema-loader.d.ts +60 -0
  97. package/dist/client/tool-schema-loader.js +178 -0
  98. package/dist/client/types.d.ts +69 -0
  99. package/dist/client/types.js +7 -0
  100. package/dist/config.d.ts +162 -0
  101. package/dist/config.js +296 -0
  102. package/dist/core.d.ts +26 -0
  103. package/dist/core.js +147 -0
  104. package/dist/lib/context-manager.d.ts +111 -0
  105. package/dist/lib/context-manager.js +431 -0
  106. package/dist/lib/logger.d.ts +74 -0
  107. package/dist/lib/logger.js +277 -0
  108. package/dist/lib/materialize.d.ts +3 -0
  109. package/dist/lib/materialize.js +101 -0
  110. package/dist/lib/normalizedName.d.ts +7 -0
  111. package/dist/lib/normalizedName.js +48 -0
  112. package/dist/lib/prompt-length-manager.d.ts +81 -0
  113. package/dist/lib/prompt-length-manager.js +457 -0
  114. package/dist/lib/terminal-prompt.d.ts +9 -0
  115. package/dist/lib/terminal-prompt.js +108 -0
  116. package/dist/mcp/UserContextCache.d.ts +56 -0
  117. package/dist/mcp/UserContextCache.js +163 -0
  118. package/dist/mcp/auth.d.ts +2 -0
  119. package/dist/mcp/auth.js +29 -0
  120. package/dist/mcp/hailer-clients.d.ts +42 -0
  121. package/dist/mcp/hailer-clients.js +246 -0
  122. package/dist/mcp/signal-handler.d.ts +45 -0
  123. package/dist/mcp/signal-handler.js +317 -0
  124. package/dist/mcp/tool-registry.d.ts +100 -0
  125. package/dist/mcp/tool-registry.js +306 -0
  126. package/dist/mcp/tools/activity.d.ts +15 -0
  127. package/dist/mcp/tools/activity.js +955 -0
  128. package/dist/mcp/tools/app.d.ts +20 -0
  129. package/dist/mcp/tools/app.js +1488 -0
  130. package/dist/mcp/tools/discussion.d.ts +19 -0
  131. package/dist/mcp/tools/discussion.js +950 -0
  132. package/dist/mcp/tools/file.d.ts +15 -0
  133. package/dist/mcp/tools/file.js +119 -0
  134. package/dist/mcp/tools/insight.d.ts +17 -0
  135. package/dist/mcp/tools/insight.js +806 -0
  136. package/dist/mcp/tools/skill.d.ts +10 -0
  137. package/dist/mcp/tools/skill.js +279 -0
  138. package/dist/mcp/tools/user.d.ts +10 -0
  139. package/dist/mcp/tools/user.js +108 -0
  140. package/dist/mcp/tools/workflow-template.d.ts +19 -0
  141. package/dist/mcp/tools/workflow-template.js +822 -0
  142. package/dist/mcp/tools/workflow.d.ts +18 -0
  143. package/dist/mcp/tools/workflow.js +1362 -0
  144. package/dist/mcp/utils/api-errors.d.ts +45 -0
  145. package/dist/mcp/utils/api-errors.js +160 -0
  146. package/dist/mcp/utils/data-transformers.d.ts +102 -0
  147. package/dist/mcp/utils/data-transformers.js +194 -0
  148. package/dist/mcp/utils/file-upload.d.ts +33 -0
  149. package/dist/mcp/utils/file-upload.js +148 -0
  150. package/dist/mcp/utils/hailer-api-client.d.ts +120 -0
  151. package/dist/mcp/utils/hailer-api-client.js +323 -0
  152. package/dist/mcp/utils/index.d.ts +13 -0
  153. package/dist/mcp/utils/index.js +39 -0
  154. package/dist/mcp/utils/logger.d.ts +42 -0
  155. package/dist/mcp/utils/logger.js +103 -0
  156. package/dist/mcp/utils/types.d.ts +286 -0
  157. package/dist/mcp/utils/types.js +7 -0
  158. package/dist/mcp/workspace-cache.d.ts +42 -0
  159. package/dist/mcp/workspace-cache.js +97 -0
  160. package/dist/mcp-server.d.ts +42 -0
  161. package/dist/mcp-server.js +280 -0
  162. package/package.json +56 -0
  163. package/tsconfig.json +23 -0
@@ -0,0 +1,955 @@
1
+ "use strict";
2
+ /**
3
+ * Activity Tools - Clean Architecture Implementation
4
+ *
5
+ * Tools for managing Hailer activities:
6
+ * - List activities with filtering and pagination (READ)
7
+ * - Show activity details (READ)
8
+ * - Create new activities (WRITE)
9
+ * - Update existing activities (WRITE)
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.updateActivityTool = exports.createActivityTool = exports.showActivityByIdTool = exports.listActivitiesTool = 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: 'activity-tools' });
18
+ /**
19
+ * Extract error message from various error types
20
+ */
21
+ function extractErrorMessage(error) {
22
+ if (error instanceof Error) {
23
+ return error.message;
24
+ }
25
+ else if (error && typeof error === 'object') {
26
+ if ('message' in error) {
27
+ return String(error.message);
28
+ }
29
+ else if ('error' in error) {
30
+ return String(error.error);
31
+ }
32
+ else if ('statusText' in error) {
33
+ return String(error.statusText);
34
+ }
35
+ else {
36
+ try {
37
+ return JSON.stringify(error, null, 2);
38
+ }
39
+ catch {
40
+ return 'Complex error object (cannot serialize)';
41
+ }
42
+ }
43
+ }
44
+ else {
45
+ return String(error);
46
+ }
47
+ }
48
+ /**
49
+ * Validate and get workflow from init data
50
+ */
51
+ function validateAndGetWorkflow(workflowId, init) {
52
+ const workflow = init.processes.find((p) => p._id === workflowId);
53
+ if (!workflow) {
54
+ throw new Error(`Workflow "${workflowId}" not found. Use get_workflow_info to find available workflows.`);
55
+ }
56
+ return (0, index_1.transformWorkflow)(workflow);
57
+ }
58
+ /**
59
+ * Resolve phase for listing activities
60
+ */
61
+ async function resolvePhaseForListing(workflow, args, init) {
62
+ if (!args.phaseId) {
63
+ throw new Error(`Phase ID is required. Use the list_workflow_phases tool to get available phase IDs for workflow "${workflow.name}".`);
64
+ }
65
+ return args.phaseId;
66
+ }
67
+ /**
68
+ * Filter fieldsAndValues to only include requested fields
69
+ */
70
+ function filterFieldsAndValues(activities, requestedFieldIds, workflowFields) {
71
+ logger.debug('šŸ” Filtering fieldsAndValues', {
72
+ activityCount: activities.length,
73
+ requestedFieldCount: requestedFieldIds.length,
74
+ requestedFieldIds,
75
+ });
76
+ const fieldNameToId = new Map();
77
+ const fieldIdSet = new Set(requestedFieldIds);
78
+ for (const [fieldId, fieldInfo] of Object.entries(workflowFields)) {
79
+ if (fieldIdSet.has(fieldId) && fieldInfo.label) {
80
+ fieldNameToId.set(fieldInfo.label, fieldId);
81
+ }
82
+ }
83
+ logger.debug('šŸ—ŗļø Field mapping', {
84
+ fieldNameToIdSize: fieldNameToId.size,
85
+ fieldMap: Array.from(fieldNameToId.entries()),
86
+ });
87
+ return activities.map((activity) => {
88
+ if (!activity.fieldsAndValues || !Array.isArray(activity.fieldsAndValues)) {
89
+ logger.debug('āš ļø Activity has no fieldsAndValues', {
90
+ activityId: activity._id,
91
+ hasFieldsAndValues: !!activity.fieldsAndValues,
92
+ isArray: Array.isArray(activity.fieldsAndValues),
93
+ });
94
+ return activity;
95
+ }
96
+ const originalFieldCount = activity.fieldsAndValues.length;
97
+ const filteredFieldsAndValues = activity.fieldsAndValues.filter((field) => {
98
+ const fieldId = fieldNameToId.get(field.fieldName);
99
+ const isIncluded = !!fieldId;
100
+ if (originalFieldCount <= 3) {
101
+ logger.debug('šŸ” Field filter check', {
102
+ fieldName: field.fieldName,
103
+ mappedFieldId: fieldId,
104
+ isIncluded,
105
+ hasValue: field.value !== undefined && field.value !== null,
106
+ });
107
+ }
108
+ return isIncluded;
109
+ });
110
+ logger.debug('āœ‚ļø Filtered activity fields', {
111
+ activityId: activity._id,
112
+ originalFieldCount,
113
+ filteredFieldCount: filteredFieldsAndValues.length,
114
+ });
115
+ return {
116
+ ...activity,
117
+ fieldsAndValues: filteredFieldsAndValues,
118
+ };
119
+ });
120
+ }
121
+ /**
122
+ * Convert activity filters from MCP tool format to API format
123
+ */
124
+ function convertActivityFilters(mcpToolActivityListFilters) {
125
+ const apiActivityFilters = { and: [] };
126
+ let malformatFilterErrorMessage = undefined;
127
+ for (const [fieldId, filterConfig] of Object.entries(mcpToolActivityListFilters)) {
128
+ switch (filterConfig.operator) {
129
+ case "text_search":
130
+ if (filterConfig.value !== undefined) {
131
+ apiActivityFilters.and.push({ [fieldId]: { textSearch: filterConfig.value } });
132
+ }
133
+ else {
134
+ malformatFilterErrorMessage = 'text_search filter requires a value';
135
+ }
136
+ break;
137
+ case "equals":
138
+ if (filterConfig.value !== undefined) {
139
+ apiActivityFilters.and.push({ [fieldId]: { equalTo: filterConfig.value } });
140
+ }
141
+ else {
142
+ malformatFilterErrorMessage = 'equals filter requires a value';
143
+ }
144
+ break;
145
+ case "not_equals":
146
+ if (filterConfig.value !== undefined) {
147
+ apiActivityFilters.and.push({ [fieldId]: { notEqualTo: filterConfig.value } });
148
+ }
149
+ else {
150
+ malformatFilterErrorMessage = 'not_equals filter requires a value';
151
+ }
152
+ break;
153
+ case "contains":
154
+ if (filterConfig.value !== undefined) {
155
+ apiActivityFilters.and.push({ [fieldId]: { contains: filterConfig.value } });
156
+ }
157
+ else {
158
+ malformatFilterErrorMessage = 'contains filter requires a value';
159
+ }
160
+ break;
161
+ case "greater_than":
162
+ if (filterConfig.value !== undefined && typeof filterConfig.value === 'number') {
163
+ apiActivityFilters.and.push({ [fieldId]: { greaterThan: filterConfig.value } });
164
+ }
165
+ else {
166
+ malformatFilterErrorMessage = 'greater_than filter requires a numeric value';
167
+ }
168
+ break;
169
+ case "greater_than_or_equal":
170
+ if (filterConfig.value !== undefined && typeof filterConfig.value === 'number') {
171
+ apiActivityFilters.and.push({ [fieldId]: { greaterThanOrEqual: filterConfig.value } });
172
+ }
173
+ else {
174
+ malformatFilterErrorMessage = 'greater_than_or_equal filter requires a numeric value';
175
+ }
176
+ break;
177
+ case "less_than":
178
+ if (filterConfig.value !== undefined && typeof filterConfig.value === 'number') {
179
+ apiActivityFilters.and.push({ [fieldId]: { lessThan: filterConfig.value } });
180
+ }
181
+ else {
182
+ malformatFilterErrorMessage = 'less_than filter requires a numeric value';
183
+ }
184
+ break;
185
+ case "less_than_or_equal":
186
+ if (filterConfig.value !== undefined && typeof filterConfig.value === 'number') {
187
+ apiActivityFilters.and.push({ [fieldId]: { lessThanOrEqual: filterConfig.value } });
188
+ }
189
+ else {
190
+ malformatFilterErrorMessage = 'less_than_or_equal filter requires a numeric value';
191
+ }
192
+ break;
193
+ case "range":
194
+ if (!filterConfig.start || !filterConfig.end) {
195
+ malformatFilterErrorMessage = 'Incorrect filter format, either start or end not provided for range';
196
+ break;
197
+ }
198
+ apiActivityFilters.and.push({ [fieldId]: { between: [String(filterConfig.start), String(filterConfig.end)] } });
199
+ break;
200
+ default:
201
+ malformatFilterErrorMessage = 'Unknown filter provided';
202
+ break;
203
+ }
204
+ }
205
+ return { errorMessage: malformatFilterErrorMessage, V3ActivityListFilters: apiActivityFilters };
206
+ }
207
+ /**
208
+ * Format activity list response with pagination
209
+ */
210
+ function formatActivityListResponseWithPagination(activityData, workflow, init, phaseName, args) {
211
+ const activities = Array.isArray(activityData) ? activityData : (activityData.activities || activityData || []);
212
+ const metadata = activityData.metadata || {};
213
+ const cleanedActivities = (0, index_1.transformActivities)(activities, workflow, init.users);
214
+ const filteredActivities = args?.fields
215
+ ? filterFieldsAndValues(cleanedActivities, args.fields, workflow.fields)
216
+ : cleanedActivities;
217
+ const totalCount = metadata.totalCount || activities.length;
218
+ const currentPage = args?.page || 0;
219
+ const limit = args?.limit || 50;
220
+ const hasMorePages = activities.length === limit;
221
+ const totalPages = Math.ceil(totalCount / limit);
222
+ let responseText = `šŸ“Š **PAGINATION INFO:**\n`;
223
+ responseText += `- Current page: ${currentPage + 1}\n`;
224
+ responseText += `- Activities on this page: ${activities.length}\n`;
225
+ responseText += `- Limit per page: ${limit}\n`;
226
+ if (hasMorePages) {
227
+ responseText += `- āš ļø **MORE DATA AVAILABLE**: Use page=${currentPage + 1} to get next ${limit} activities\n`;
228
+ }
229
+ if (activities.length === limit && currentPage === 0) {
230
+ responseText += `\nšŸ’” **TIP**: This shows the first ${limit} activities.\n`;
231
+ responseText += ` - Use 'page' parameter to paginate through all results\n`;
232
+ responseText += ` - Use 'search' parameter to filter by name, description, or field content\n`;
233
+ responseText += ` - Use different 'sortBy' and 'sortOrder' for different ordering\n`;
234
+ }
235
+ responseText += `\n` + (0, index_1.formatActivityListResponse)(filteredActivities, workflow, phaseName);
236
+ return {
237
+ content: [
238
+ {
239
+ type: "text",
240
+ text: responseText,
241
+ },
242
+ ],
243
+ };
244
+ }
245
+ /**
246
+ * Format filtered activity list response
247
+ */
248
+ function formatFilteredActivityListResponse(activityData, workflow, init, args) {
249
+ const activities = Array.isArray(activityData) ? activityData : (activityData.activities || activityData || []);
250
+ const metadata = activityData.metadata || {};
251
+ const cleanedActivities = (0, index_1.transformActivities)(activities, workflow, init.users);
252
+ const filteredActivities = args.fields
253
+ ? filterFieldsAndValues(cleanedActivities, args.fields, workflow.fields)
254
+ : cleanedActivities;
255
+ const totalCount = metadata.totalCount || activities.length;
256
+ const currentPage = args?.page || 0;
257
+ const limit = args?.limit || 50;
258
+ const hasMorePages = activities.length === limit;
259
+ const totalPages = Math.ceil(totalCount / limit);
260
+ let responseText = `šŸŽÆ **FILTERED ACTIVITIES** in "${workflow.name}":\n\n`;
261
+ const filterCount = Object.keys(args.filters || {}).length;
262
+ responseText += `**Applied Filters:** ${filterCount} field filter(s)\n`;
263
+ for (const [fieldId, filter] of Object.entries(args.filters || {})) {
264
+ const filterConfig = filter;
265
+ responseText += `- Field \`${fieldId}\`: ${filterConfig.operator}`;
266
+ if (filterConfig.value !== undefined)
267
+ responseText += ` = "${filterConfig.value}"`;
268
+ if (filterConfig.start !== undefined)
269
+ responseText += ` from ${filterConfig.start} to ${filterConfig.end}`;
270
+ responseText += "\n";
271
+ }
272
+ if (args.search) {
273
+ responseText += `- Text search: "${args.search}"\n`;
274
+ }
275
+ responseText += `\n`;
276
+ responseText += `šŸ“Š **RESULTS:**\n`;
277
+ responseText += `- Total matching activities: ${totalCount}\n`;
278
+ responseText += `- Showing: ${activities.length} activities (page ${currentPage + 1} of ${totalPages})\n`;
279
+ responseText += `- Limit per page: ${limit}\n`;
280
+ if (hasMorePages) {
281
+ responseText += `- āš ļø **MORE DATA AVAILABLE**: Use page=${currentPage + 1} to get next ${limit} activities\n`;
282
+ }
283
+ if (activities.length === 0) {
284
+ responseText += `\nāŒ No activities match your filters.\n\n`;
285
+ responseText += `šŸ’” **TROUBLESHOOTING:**\n`;
286
+ responseText += `- Try broader filter criteria\n`;
287
+ responseText += `- Remove some filters to see if data exists\n`;
288
+ responseText += `- Call get_workflow_schema to verify field IDs are correct\n`;
289
+ responseText += `- Use list_activities without filters to see all activities`;
290
+ return {
291
+ content: [{ type: "text", text: responseText }],
292
+ };
293
+ }
294
+ responseText += `\n` + (0, index_1.formatActivityListResponse)(filteredActivities, workflow, undefined);
295
+ responseText += `\n\nšŸ’” **FILTER TIPS:**\n`;
296
+ responseText += `- Use 'equals' operator for exact matches (company IDs, relationship fields)\n`;
297
+ responseText += `- Use 'range' operator for date/number ranges (created/updated timestamps)\n`;
298
+ responseText += `- Use 'contains' or 'text_search' for partial text matching\n`;
299
+ responseText += `- Combine multiple filters for precise results\n`;
300
+ responseText += `- Call get_workflow_schema to discover available fields and their types`;
301
+ return {
302
+ content: [
303
+ {
304
+ type: "text",
305
+ text: responseText,
306
+ },
307
+ ],
308
+ };
309
+ }
310
+ /** Build activity update object */
311
+ function buildActivityUpdate(args, context) {
312
+ let parsedFields = args.fields;
313
+ if (typeof args.fields === "string") {
314
+ try {
315
+ parsedFields = JSON.parse(args.fields);
316
+ }
317
+ catch (e) {
318
+ throw new Error(`Invalid fields JSON: ${args.fields}`);
319
+ }
320
+ }
321
+ if (parsedFields &&
322
+ typeof parsedFields === "object" &&
323
+ context.workspaceCache) {
324
+ const invalidUsers = [];
325
+ for (const [fieldId, fieldValue] of Object.entries(parsedFields)) {
326
+ if (typeof fieldValue === "string" &&
327
+ /^[a-f0-9]{24}$/i.test(fieldValue)) {
328
+ const user = (0, workspace_cache_1.getUserById)(context.workspaceCache, fieldValue);
329
+ if (!user) {
330
+ invalidUsers.push(`${fieldId}: ${fieldValue}`);
331
+ }
332
+ }
333
+ }
334
+ if (invalidUsers.length > 0) {
335
+ throw new Error(`Invalid user IDs in fields: ${invalidUsers.join(", ")}. Use search_workspace_users to find valid user IDs.`);
336
+ }
337
+ }
338
+ return {
339
+ _id: args.activityId,
340
+ ...(args.name && { name: args.name }),
341
+ ...(parsedFields &&
342
+ typeof parsedFields === "object" && {
343
+ fields: parsedFields,
344
+ }),
345
+ ...(args.phaseId && { phaseId: args.phaseId }),
346
+ };
347
+ }
348
+ /**
349
+ * Format update activity response
350
+ */
351
+ function formatUpdateActivityResponse(args, result) {
352
+ const changesText = [
353
+ args.name ? `- Name: "${args.name}"` : "",
354
+ args.fields ? `- Fields: ${JSON.stringify(args.fields, null, 2)}` : "",
355
+ args.phaseId ? `- Moved to phase: ${args.phaseId}` : "",
356
+ ]
357
+ .filter(Boolean)
358
+ .join("\n");
359
+ const responseText = `āœ… Successfully updated activity ${args.activityId}!\n\nšŸ“ Changes applied:\n${changesText}\n\nšŸ’” The activity has been updated in your Hailer workspace.\n\nAPI Response:\n${JSON.stringify(result, null, 2)}`;
360
+ return {
361
+ content: [
362
+ {
363
+ type: "text",
364
+ text: responseText,
365
+ },
366
+ ],
367
+ };
368
+ }
369
+ exports.listActivitiesTool = {
370
+ name: 'list_activities',
371
+ group: tool_registry_1.ToolGroup.READ,
372
+ description: `šŸ“‹ Retrieves list of activities for a specific workflow phase. āš ļø CRITICAL: phaseId is MANDATORY string parameter, must match workflow's current phase. āœ… CORRECT: {"workflowId":"6756e099410306cab03a7dfb","phaseId":"specific_phase_identifier"} āŒ WRONG: {"workflowId":"6756e099410306cab03a7dfb"} (missing required phaseId)`,
373
+ schema: zod_1.z.object({
374
+ workflowId: zod_1.z.string().describe("Workflow ID to list activities from"),
375
+ phaseId: zod_1.z.string().describe("Phase ID to filter activities (required - use list_workflow_phases to get available phases)"),
376
+ fields: zod_1.z.preprocess((val) => {
377
+ if (typeof val === 'string') {
378
+ try {
379
+ return JSON.parse(val);
380
+ }
381
+ catch {
382
+ return val;
383
+ }
384
+ }
385
+ return val;
386
+ }, zod_1.z.array(zod_1.z.string()).optional()).describe("Array of field IDs to return (use get_workflow_schema to see available fields). Select the fields needed for the task - use fewer fields for listings (name, status, etc.) and more fields when detailed information is required. If not provided, you must call get_workflow_schema first."),
387
+ filters: zod_1.z.record(zod_1.z.object({
388
+ operator: zod_1.z.enum([
389
+ "text_search",
390
+ "equals",
391
+ "not_equals",
392
+ "contains",
393
+ "greater_than",
394
+ "greater_than_or_equal",
395
+ "less_than",
396
+ "less_than_or_equal",
397
+ "range",
398
+ ]).describe("Filter operator: text_search for partial text match in field (BEST for filtering by text in relationship fields when you only have a name), equals for exact match (requires activity ID), range for date/number ranges, contains for partial text, comparison operators for numbers/dates"),
399
+ value: zod_1.z.union([zod_1.z.string(), zod_1.z.number()]).optional().describe("Value to filter by - REQUIRED for text_search, equals, not_equals, contains, greater_than, less_than operators. NOT USED for range operator (use start/end instead). For text_search: use the text/name to search for. For equals: use the entity ID."),
400
+ start: zod_1.z.coerce.number().optional().describe("Start value - ONLY for range operator (NOT in 'value' field). Unix timestamp in milliseconds for date ranges. Example: for June 2025 date range, use start=1717200000000 directly here, NOT nested in 'value'."),
401
+ end: zod_1.z.coerce.number().optional().describe("End value - ONLY for range operator (NOT in 'value' field). Unix timestamp in milliseconds for date ranges. Example: for June 2025 date range, use end=1719791999000 directly here, NOT nested in 'value'."),
402
+ })).optional().describe("Field-based filters - PREFERRED over search parameter for reliable results. Key is the field ID from get_workflow_schema. CRITICAL: For range operator, use 'start' and 'end' as direct properties, NOT nested inside 'value'. Example: {fieldId: {operator: 'range', start: 1717200000000, end: 1719791999000}}. BEST use cases: 1) Date ranges: use range operator with created/updated field IDs, 2) Company/site by NAME: use text_search operator, 3) Exact ID matches: use equals operator."),
403
+ search: zod_1.z.string().optional().describe("Text search across activity content - USE FILTERS INSTEAD when possible for reliable results. Only use search for truly exploratory queries."),
404
+ limit: zod_1.z.coerce.number().optional().default(50).describe("Maximum number of activities to return (default: 50, max: 20000)"),
405
+ page: zod_1.z.coerce.number().optional().default(0).describe("Page number for pagination (0-based)"),
406
+ sortBy: zod_1.z.enum(["name", "created", "updated", "priority"]).optional().default("updated").describe("Field to sort by"),
407
+ sortOrder: zod_1.z.enum(["asc", "desc"]).optional().default("desc").describe("Sort direction"),
408
+ includeStats: zod_1.z.coerce.boolean().optional().default(true).describe("Include total count and pagination metadata"),
409
+ }),
410
+ async execute(args, context) {
411
+ try {
412
+ const workflow = validateAndGetWorkflow(args.workflowId, context.init);
413
+ const phaseId = await resolvePhaseForListing(workflow, args, context.init);
414
+ if (!args.fields || args.fields.length === 0) {
415
+ return {
416
+ content: [
417
+ {
418
+ type: "text",
419
+ text: `āŒ **Fields parameter required**: You must specify which fields to return to avoid huge responses.\n\n` +
420
+ `šŸ“‹ **Next steps:**\n` +
421
+ `1. Call \`get_workflow_schema\` with workflowId: "${args.workflowId}" and phaseId: "${phaseId}" to see available fields\n` +
422
+ `2. Then call \`list_activities\` again with 2-3 essential field IDs\n\n` +
423
+ `šŸ’” **Best practices:**\n` +
424
+ `- For product listings: ["name", "price", "stock"] (3 fields)\n` +
425
+ `- For quick view: ["name", "status"] (2 fields)\n` +
426
+ `- Only add description fields when user explicitly asks for details\n` +
427
+ `- More fields = exponentially higher token costs\n\n` +
428
+ `⚔ **Pro tip:** Use 'filters' parameter to narrow results instead of fetching everything\n\n` +
429
+ `This optimization prevents responses with hundreds of thousands of tokens.`,
430
+ },
431
+ ],
432
+ };
433
+ }
434
+ let apiFilters = undefined;
435
+ let filterErrorMessage = undefined;
436
+ if (args.filters && Object.keys(args.filters).length > 0) {
437
+ logger.debug('šŸŽÆ Converting filters for list_activities', {
438
+ filterCount: Object.keys(args.filters).length,
439
+ filters: args.filters
440
+ });
441
+ const convertedResult = convertActivityFilters(args.filters);
442
+ apiFilters = convertedResult.V3ActivityListFilters;
443
+ filterErrorMessage = convertedResult.errorMessage;
444
+ if (filterErrorMessage) {
445
+ return {
446
+ content: [
447
+ {
448
+ type: "text",
449
+ text: `āŒ **Filter Error:** ${filterErrorMessage}\n\n` +
450
+ `šŸ’” **Valid operators:** equals, not_equals, contains, text_search, greater_than, less_than, range\n` +
451
+ `šŸ“‹ Call \`get_workflow_schema\` to see available field IDs and types`,
452
+ }
453
+ ],
454
+ };
455
+ }
456
+ logger.debug('āœ… Filters converted successfully', {
457
+ apiFiltersAndCount: apiFilters.and.length,
458
+ apiFilters: JSON.stringify(apiFilters)
459
+ });
460
+ }
461
+ const options = {
462
+ page: args.page,
463
+ search: args.search,
464
+ sortBy: args.sortBy,
465
+ sortOrder: args.sortOrder,
466
+ filters: apiFilters,
467
+ includeStats: args.includeStats,
468
+ returnFlat: true,
469
+ };
470
+ logger.debug('šŸ“‹ Calling list_activities', {
471
+ workflowId: args.workflowId,
472
+ phaseId,
473
+ hasFilters: !!apiFilters,
474
+ filterCount: apiFilters?.and?.length || 0,
475
+ hasSearch: !!args.search,
476
+ });
477
+ const activityData = await context.hailer.fetchActivityList(args.workflowId, phaseId, args.limit, options);
478
+ let response;
479
+ if (args.filters && Object.keys(args.filters).length > 0) {
480
+ response = formatFilteredActivityListResponse(activityData, workflow, context.init, args);
481
+ }
482
+ else {
483
+ response = formatActivityListResponseWithPagination(activityData, workflow, context.init, undefined, args);
484
+ }
485
+ return response;
486
+ }
487
+ catch (error) {
488
+ logger.error("Failed to list activities", error);
489
+ return {
490
+ content: [
491
+ {
492
+ type: "text",
493
+ text: `āŒ Failed to list activities: ${extractErrorMessage(error)}`,
494
+ },
495
+ ],
496
+ };
497
+ }
498
+ },
499
+ };
500
+ exports.showActivityByIdTool = {
501
+ name: 'show_activity_by_id',
502
+ group: tool_registry_1.ToolGroup.READ,
503
+ description: `šŸ“„ Load specific activity by ID with full details`,
504
+ schema: zod_1.z.object({
505
+ activityId: zod_1.z.string().describe("Activity ID to load"),
506
+ }),
507
+ async execute(args, context) {
508
+ try {
509
+ const activity = await context.hailer.fetchActivityById(args.activityId);
510
+ let responseText = `āœ… Loaded activity with ID "${activity._id}":\n\n${JSON.stringify(activity, null, 2)}`;
511
+ return {
512
+ content: [
513
+ {
514
+ type: "text",
515
+ text: responseText,
516
+ },
517
+ ],
518
+ };
519
+ }
520
+ catch (error) {
521
+ logger.error("Failed to load activity", error, {
522
+ activityId: args.activityId,
523
+ });
524
+ return {
525
+ content: [
526
+ {
527
+ type: "text",
528
+ text: `āŒ Failed to load activity: ${extractErrorMessage(error)}`,
529
+ },
530
+ ],
531
+ };
532
+ }
533
+ },
534
+ };
535
+ // ============================================================================
536
+ // TOOL 3: CREATE ACTIVITY
537
+ // ============================================================================
538
+ const createActivityDescription = `Create one or multiple activities - Supports both single and bulk creation
539
+
540
+ **BULK CREATION (3+ activities):**
541
+ Use the 'activities' parameter to create multiple activities efficiently:
542
+ - activities: [{ name, fields, phaseId }, { name, fields, phaseId }, ...]
543
+ - All activities created in one API call
544
+ - Much faster for creating 10, 50, or 100+ activities
545
+ - Recommended for sample data generation and batch imports
546
+
547
+ **SINGLE CREATION:**
548
+ Use individual parameters (name, fields, phaseId) for creating one activity
549
+
550
+ **IMPORTANT - Field Type Rules:**
551
+ - textpredefinedoptions: MUST be a single STRING value (e.g., "Option1"), NOT an array
552
+ - activitylink: MUST be a STRING (activity ID), NOT an array
553
+ - users/teams: STRING user/team ID
554
+ - numeric/date: NUMBER (timestamp for dates)
555
+ - text/textarea: STRING
556
+
557
+ **Field Keys vs Field IDs:**
558
+ If workflow uses field 'key' (recommended), you can use readable names:
559
+ - With keys: { "vendor": "activity-id", "priority": "High" }
560
+ - Without keys: { "507f191e810c19729de860ea": "activity-id" }
561
+
562
+ **Examples:**
563
+ Single: { workflowId, name: "Customer Lead", fields: { customerName: "John" } }
564
+ Bulk: { workflowId, activities: [{ name: "Lead 1", fields: {...} }, { name: "Lead 2", fields: {...} }] }`;
565
+ exports.createActivityTool = {
566
+ name: 'create_activity',
567
+ group: tool_registry_1.ToolGroup.WRITE,
568
+ description: createActivityDescription,
569
+ schema: zod_1.z.object({
570
+ workflowId: zod_1.z
571
+ .string()
572
+ .min(24, "Workflow ID must be at least 24 characters")
573
+ .describe("The workflow/dataset ID where to create the activity/activities. Find this in any list_* tool results"),
574
+ // BULK CREATION (optional - takes precedence over single parameters)
575
+ activities: zod_1.z
576
+ .array(zod_1.z.object({
577
+ name: zod_1.z.string().min(1, "Activity name cannot be empty").describe("Activity name/title"),
578
+ fields: zod_1.z.union([zod_1.z.record(zod_1.z.any()), zod_1.z.string()]).optional().describe("Custom field values (field keys or IDs)"),
579
+ phaseId: zod_1.z.string().optional().describe("Phase ID for this activity"),
580
+ teamId: zod_1.z.string().optional().describe("Team ID for this activity"),
581
+ discussionId: zod_1.z.string().optional().describe("Link to existing discussion"),
582
+ followerIds: zod_1.z.union([zod_1.z.array(zod_1.z.string()), zod_1.z.string()]).optional().describe("User IDs to invite"),
583
+ fileIds: zod_1.z.union([zod_1.z.array(zod_1.z.string()), zod_1.z.string()]).optional().describe("File IDs to attach"),
584
+ }))
585
+ .optional()
586
+ .describe("BULK: Array of activities to create. Use this for creating 3+ activities efficiently. If provided, single activity parameters (name, fields) are ignored."),
587
+ // SINGLE CREATION (used when 'activities' is not provided)
588
+ name: zod_1.z
589
+ .string()
590
+ .min(1, "Activity name cannot be empty")
591
+ .optional()
592
+ .describe("SINGLE: The activity name/title (e.g. 'šŸ¤– AI Analysis Bot', 'Customer Research Task')"),
593
+ description: zod_1.z
594
+ .string()
595
+ .optional()
596
+ .describe("SINGLE: Optional detailed description"),
597
+ phaseId: zod_1.z
598
+ .string()
599
+ .optional()
600
+ .describe("SINGLE: Optional phase/category ID. If not specified, uses the default phase"),
601
+ teamId: zod_1.z
602
+ .string()
603
+ .optional()
604
+ .describe("SINGLE: Optional team ID for team-specific activities"),
605
+ fields: zod_1.z
606
+ .union([zod_1.z.record(zod_1.z.any()), zod_1.z.string()])
607
+ .optional()
608
+ .describe("SINGLE: Optional custom field values. IMPORTANT: activitylink values must be STRINGS (activity IDs), not arrays. Example: { 'vendor': 'activity-id-string' }"),
609
+ discussionId: zod_1.z
610
+ .string()
611
+ .optional()
612
+ .describe("SINGLE: Optional discussion ID to link to existing conversation"),
613
+ followerIds: zod_1.z
614
+ .union([zod_1.z.array(zod_1.z.string()), zod_1.z.string()])
615
+ .optional()
616
+ .describe("SINGLE: Optional array of user IDs to invite"),
617
+ fileIds: zod_1.z
618
+ .union([zod_1.z.array(zod_1.z.string()), zod_1.z.string()])
619
+ .optional()
620
+ .describe("SINGLE: Optional array of file IDs to attach"),
621
+ }),
622
+ async execute(args, context) {
623
+ try {
624
+ // Helper function to process a single activity object
625
+ const processActivity = (activityData) => {
626
+ const activity = {
627
+ name: activityData.name,
628
+ };
629
+ if (activityData.fields) {
630
+ let parsedFields = activityData.fields;
631
+ if (typeof activityData.fields === "string" && activityData.fields.trim() !== "") {
632
+ try {
633
+ parsedFields = JSON.parse(activityData.fields);
634
+ }
635
+ catch (e) {
636
+ throw new Error(`Invalid fields JSON: ${activityData.fields}`);
637
+ }
638
+ }
639
+ if (parsedFields &&
640
+ typeof parsedFields === "object" &&
641
+ Object.keys(parsedFields).length > 0) {
642
+ activity.fields = parsedFields;
643
+ }
644
+ }
645
+ if (activityData.phaseId && activityData.phaseId.trim() !== "") {
646
+ activity.phaseId = activityData.phaseId;
647
+ }
648
+ if (activityData.teamId && activityData.teamId.trim() !== "") {
649
+ activity.teamId = activityData.teamId;
650
+ }
651
+ if (activityData.followerIds) {
652
+ let parsedFollowerIds = activityData.followerIds;
653
+ if (typeof activityData.followerIds === "string" &&
654
+ activityData.followerIds.trim() !== "") {
655
+ try {
656
+ parsedFollowerIds = JSON.parse(activityData.followerIds);
657
+ }
658
+ catch (e) {
659
+ throw new Error(`Invalid followerIds JSON: ${activityData.followerIds}`);
660
+ }
661
+ }
662
+ if (Array.isArray(parsedFollowerIds) &&
663
+ parsedFollowerIds.length > 0) {
664
+ if (context.workspaceCache) {
665
+ const invalidUsers = [];
666
+ for (const userId of parsedFollowerIds) {
667
+ const user = (0, workspace_cache_1.getUserById)(context.workspaceCache, userId);
668
+ if (!user) {
669
+ invalidUsers.push(userId);
670
+ }
671
+ }
672
+ if (invalidUsers.length > 0) {
673
+ throw new Error(`Invalid user IDs in followerIds: ${invalidUsers.join(", ")}. Use search_workspace_users to find valid user IDs.`);
674
+ }
675
+ }
676
+ activity.followerIds = parsedFollowerIds;
677
+ }
678
+ }
679
+ if (activityData.fileIds) {
680
+ let parsedFileIds = activityData.fileIds;
681
+ if (typeof activityData.fileIds === "string" &&
682
+ activityData.fileIds.trim() !== "") {
683
+ try {
684
+ parsedFileIds = JSON.parse(activityData.fileIds);
685
+ }
686
+ catch (e) {
687
+ throw new Error(`Invalid fileIds JSON: ${activityData.fileIds}`);
688
+ }
689
+ }
690
+ if (Array.isArray(parsedFileIds) && parsedFileIds.length > 0) {
691
+ activity.fileIds = parsedFileIds;
692
+ }
693
+ }
694
+ return activity;
695
+ };
696
+ // Determine if this is bulk or single creation
697
+ const isBulk = args.activities && Array.isArray(args.activities) && args.activities.length > 0;
698
+ // Get workflow's default team (owner team) if available
699
+ const rawWorkflow = context.init.processes.find((p) => p._id === args.workflowId);
700
+ const defaultTeamId = rawWorkflow?.team;
701
+ let activitiesToCreate;
702
+ let options = {
703
+ returnDocument: true,
704
+ };
705
+ if (isBulk) {
706
+ // BULK CREATION
707
+ logger.info(`Creating ${args.activities.length} activities in bulk`, {
708
+ workflowId: args.workflowId,
709
+ count: args.activities.length,
710
+ defaultTeamId: defaultTeamId,
711
+ });
712
+ activitiesToCreate = args.activities.map((activityData) => {
713
+ const activity = processActivity(activityData);
714
+ // Use default team if no teamId provided
715
+ if (!activity.teamId && defaultTeamId) {
716
+ activity.teamId = defaultTeamId;
717
+ }
718
+ return activity;
719
+ });
720
+ // Check for common discussionId in bulk
721
+ if (args.discussionId && args.discussionId.trim() !== "") {
722
+ options.discussionId = args.discussionId;
723
+ }
724
+ }
725
+ else {
726
+ // SINGLE CREATION
727
+ if (!args.name) {
728
+ throw new Error("Either 'activities' array or 'name' parameter is required");
729
+ }
730
+ logger.info("Creating single activity", {
731
+ workflowId: args.workflowId,
732
+ name: args.name,
733
+ defaultTeamId: defaultTeamId,
734
+ });
735
+ const activity = processActivity(args);
736
+ // Use default team if no teamId provided
737
+ if (!activity.teamId && defaultTeamId) {
738
+ activity.teamId = defaultTeamId;
739
+ }
740
+ activitiesToCreate = [activity];
741
+ if (args.discussionId && args.discussionId.trim() !== "") {
742
+ options.discussionId = args.discussionId;
743
+ }
744
+ }
745
+ // Call API
746
+ const result = await context.hailer.createActivities(args.workflowId, activitiesToCreate, options);
747
+ // Handle response
748
+ const createdActivities = Array.isArray(result) ? result : [result];
749
+ if (isBulk) {
750
+ // BULK RESPONSE
751
+ const workflowUrl = `https://app.hailer.com/#/processes/${args.workflowId}`;
752
+ const responseText = `šŸŽ‰ Successfully created ${createdActivities.length} activities in workflow ${args.workflowId}!
753
+
754
+ šŸ“Š **Bulk Creation Summary:**
755
+ - **Total Created**: ${createdActivities.length} activities
756
+ - **Workflow**: ${args.workflowId}
757
+ - **Status**: All activities created successfully
758
+
759
+ šŸ”— **View Workflow**: ${workflowUrl}
760
+
761
+ šŸ“‹ **Created Activities:**
762
+ ${createdActivities.slice(0, 10).map((act, idx) => `${idx + 1}. "${act.name}" (ID: ${act._id}, Phase: ${act.currentPhase})`).join('\n')}${createdActivities.length > 10 ? `\n... and ${createdActivities.length - 10} more` : ''}
763
+
764
+ āœ… All activities are now active and ready for use!`;
765
+ return {
766
+ content: [
767
+ {
768
+ type: "text",
769
+ text: responseText,
770
+ },
771
+ ],
772
+ };
773
+ }
774
+ else {
775
+ // SINGLE RESPONSE
776
+ const createdActivity = createdActivities[0];
777
+ const activityId = createdActivity._id;
778
+ const discussionId = createdActivity.discussion;
779
+ const sequence = createdActivity.sequence;
780
+ const currentPhase = createdActivity.currentPhase;
781
+ const workspaceId = createdActivity.cid;
782
+ const activityUrl = `https://app.hailer.com/#/activities/${activityId}`;
783
+ const discussionUrl = `https://app.hailer.com/#/discussions/${discussionId}`;
784
+ const workflowUrl = `https://app.hailer.com/#/processes/${args.workflowId}`;
785
+ const responseText = `šŸŽ‰ Successfully created activity "${args.name}" in workflow ${args.workflowId}!
786
+
787
+ šŸ“‹ **Activity Details:**
788
+ - **ID**: ${activityId}
789
+ - **Discussion**: ${discussionId}
790
+ - **Sequence**: #${sequence}
791
+ - **Current Phase**: ${currentPhase}
792
+ - **Status**: Active and ready for use
793
+
794
+ šŸ”— **Direct Links:**
795
+ - **View Activity**: ${activityUrl}
796
+ - **Open Discussion**: ${discussionUrl}
797
+ - **View Workflow**: ${workflowUrl}
798
+
799
+ šŸ’¬ ${args.discussionId ? "Activity linked to existing discussion" : "New discussion created automatically"} for team collaboration.
800
+
801
+ šŸ”§ **Debug Info:**
802
+ - **Workspace ID**: ${workspaceId}
803
+ - **Final Phase**: ${currentPhase}`;
804
+ return {
805
+ content: [
806
+ {
807
+ type: "text",
808
+ text: responseText,
809
+ },
810
+ ],
811
+ };
812
+ }
813
+ }
814
+ catch (error) {
815
+ logger.error("Failed to create activity/activities", error, {
816
+ workflowId: args.workflowId,
817
+ isBulk: !!(args.activities && args.activities.length > 0),
818
+ });
819
+ const errorMessage = error instanceof Error
820
+ ? error.message
821
+ : (typeof error === 'object' && error !== null)
822
+ ? JSON.stringify(error, null, 2)
823
+ : String(error);
824
+ return {
825
+ content: [
826
+ {
827
+ type: "text",
828
+ text: `āŒ Error creating activity: ${errorMessage}\n\nšŸ’” Troubleshooting Tips:\n- Verify the workflowId exists and is accessible\n- Check that you have permission to create activities in this workflow\n- Ensure field IDs in the fields object match the workflow's field definitions\n- Use search_workspace_users to find valid user IDs for assignment\n- Verify user IDs in followerIds exist and are accessible`,
829
+ },
830
+ ],
831
+ };
832
+ }
833
+ },
834
+ };
835
+ // ============================================================================
836
+ // TOOL 4: UPDATE ACTIVITY
837
+ // ============================================================================
838
+ const updateActivityDescription = `Update one or multiple activities in any Hailer workflow
839
+
840
+ **BULK UPDATE (3+ activities):**
841
+ Use the 'activities' parameter to update multiple activities efficiently:
842
+ - activities: [{ _id, name?, fields? }, { _id, name?, fields? }, ...]
843
+ - All activities updated in one API call
844
+ - Much faster for updating 10, 50, or 100+ activities
845
+ - Recommended for batch operations and data migrations
846
+
847
+ **SINGLE UPDATE:**
848
+ Use individual parameters (_id or activityId, name, fields, phaseId) for updating one activity
849
+
850
+ **IMPORTANT - ActivityLink Field Values:**
851
+ When updating activitylink fields, use STRING values (the activity ID to link to), NOT arrays:
852
+ - āœ… Correct: { "vendorField": "507f1f77bcf86cd799439011" }
853
+ - āŒ Wrong: { "vendorField": ["507f1f77bcf86cd799439011"] }
854
+
855
+ **Field Keys vs Field IDs:**
856
+ If workflow uses field 'key' (recommended), you can use readable names:
857
+ - With keys: { "vendor": "new-activity-id", "priority": "High" }
858
+ - Without keys: { "507f191e810c19729de860ea": "new-activity-id" }
859
+
860
+ **Examples:**
861
+ Single: update_activity({ activityId: "xxx", fields: { status: "Active" } })
862
+ Bulk: update_activity({ activities: [{ _id: "xxx", fields: { status: "Active" } }, { _id: "yyy", fields: { status: "Inactive" } }] })`;
863
+ exports.updateActivityTool = {
864
+ name: 'update_activity',
865
+ group: tool_registry_1.ToolGroup.WRITE,
866
+ description: updateActivityDescription,
867
+ schema: zod_1.z.object({
868
+ // BULK: Array of activities to update
869
+ activities: zod_1.z
870
+ .preprocess((val) => (typeof val === "string" ? JSON.parse(val) : val), zod_1.z
871
+ .array(zod_1.z.object({
872
+ _id: zod_1.z.string().min(24, "Activity ID must be at least 24 characters"),
873
+ name: zod_1.z.string().optional(),
874
+ fields: zod_1.z.record(zod_1.z.any()).optional(),
875
+ phaseId: zod_1.z.string().optional(),
876
+ }))
877
+ .min(1))
878
+ .optional()
879
+ .describe("BULK: Array of activities to update. Use this for updating 3+ activities efficiently. If provided, single activity parameters (activityId, name, fields) are ignored."),
880
+ // SINGLE: Individual activity parameters
881
+ activityId: zod_1.z
882
+ .string()
883
+ .min(24, "Activity ID must be at least 24 characters")
884
+ .optional()
885
+ .describe("SINGLE: The unique ID of the activity to update (works for any workflow)"),
886
+ name: zod_1.z.string().optional().describe("SINGLE: New activity title/name"),
887
+ fields: zod_1.z
888
+ .union([zod_1.z.record(zod_1.z.any()), zod_1.z.string()])
889
+ .optional()
890
+ .describe("SINGLE: Updated field values as key-value pairs. IMPORTANT: activitylink values must be STRINGS (activity IDs), not arrays. Example: { 'vendor': 'activity-id-string', 'priority': 'High' }. Can be object or JSON string"),
891
+ phaseId: zod_1.z
892
+ .string()
893
+ .optional()
894
+ .describe("SINGLE: Optional phase ID to move the activity to"),
895
+ }),
896
+ async execute(args, context) {
897
+ try {
898
+ // BULK MODE: Update multiple activities
899
+ if (args.activities && Array.isArray(args.activities)) {
900
+ logger.debug("Bulk update mode", { activityCount: args.activities.length });
901
+ // Build updates for each activity
902
+ const updates = args.activities.map((activity) => {
903
+ const activityUpdate = { _id: activity._id };
904
+ if (activity.name)
905
+ activityUpdate.name = activity.name;
906
+ if (activity.fields) {
907
+ // Parse fields if string
908
+ const parsedFields = typeof activity.fields === 'string'
909
+ ? JSON.parse(activity.fields)
910
+ : activity.fields;
911
+ activityUpdate.fields = parsedFields;
912
+ }
913
+ return activityUpdate;
914
+ });
915
+ // Collect unique phase IDs (most activities will have the same phase)
916
+ const phaseIds = args.activities
917
+ .map((a) => a.phaseId)
918
+ .filter((p) => p);
919
+ const commonPhaseId = phaseIds.length > 0 ? phaseIds[0] : undefined;
920
+ // Call API with all updates
921
+ const options = commonPhaseId ? { phaseId: commonPhaseId } : undefined;
922
+ const result = await context.hailer.updateActivities(updates, options);
923
+ // Format bulk response
924
+ return {
925
+ content: [
926
+ {
927
+ type: "text",
928
+ text: `šŸŽ‰ Successfully updated ${args.activities.length} activities!\n\nšŸ“Š **Bulk Update Summary:**\n- **Total Updated**: ${args.activities.length} activities\n- **Status**: All activities updated successfully\n\nāœ… All activities have been updated in your Hailer workspace!`,
929
+ },
930
+ ],
931
+ };
932
+ }
933
+ // SINGLE MODE: Update one activity
934
+ const updates = buildActivityUpdate(args, context);
935
+ const options = args.phaseId ? { phaseId: args.phaseId } : undefined;
936
+ const result = await context.hailer.updateActivities([updates], options);
937
+ return formatUpdateActivityResponse(args, result);
938
+ }
939
+ catch (error) {
940
+ logger.error("Failed to update activity", error, {
941
+ activityId: args.activityId || 'bulk',
942
+ activityCount: args.activities?.length,
943
+ });
944
+ return {
945
+ content: [
946
+ {
947
+ type: "text",
948
+ text: `āŒ Error updating ${args.activities ? 'activities' : 'activity'}: ${error instanceof Error ? error.message : String(error)}`,
949
+ },
950
+ ],
951
+ };
952
+ }
953
+ },
954
+ };
955
+ //# sourceMappingURL=activity.js.map