@hailer/mcp 0.2.6 → 1.0.20

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 (40) hide show
  1. package/.claude/skills/client-bot-architecture/skill.md +340 -0
  2. package/.claude/skills/publish-hailer-app/SKILL.md +11 -0
  3. package/.opencode/opencode.json +6 -4
  4. package/dist/app.d.ts +1 -1
  5. package/dist/app.js +116 -84
  6. package/dist/bot/chat-bot.d.ts +31 -0
  7. package/dist/bot/chat-bot.js +356 -0
  8. package/dist/cli.js +0 -0
  9. package/dist/config.d.ts +15 -2
  10. package/dist/config.js +53 -3
  11. package/dist/lib/logger.js +11 -11
  12. package/dist/mcp/hailer-clients.js +12 -11
  13. package/dist/mcp/tool-registry.d.ts +4 -0
  14. package/dist/mcp/tool-registry.js +78 -1
  15. package/dist/mcp/tools/activity.js +47 -0
  16. package/dist/mcp/tools/discussion.js +44 -1
  17. package/dist/mcp/tools/metrics.d.ts +13 -0
  18. package/dist/mcp/tools/metrics.js +546 -0
  19. package/dist/mcp/tools/user.d.ts +1 -0
  20. package/dist/mcp/tools/user.js +94 -1
  21. package/dist/mcp/tools/workflow.js +109 -40
  22. package/dist/mcp/webhook-handler.js +7 -4
  23. package/dist/mcp-server.js +22 -6
  24. package/dist/stdio-server.d.ts +14 -0
  25. package/dist/stdio-server.js +101 -0
  26. package/package.json +6 -6
  27. package/scripts/test-hal-tools.ts +154 -0
  28. package/test-billing-server.js +136 -0
  29. package/dist/lib/discussion-lock.d.ts +0 -42
  30. package/dist/lib/discussion-lock.js +0 -110
  31. package/dist/mcp/tools/bot-config/constants.d.ts +0 -23
  32. package/dist/mcp/tools/bot-config/constants.js +0 -94
  33. package/dist/mcp/tools/bot-config/core.d.ts +0 -253
  34. package/dist/mcp/tools/bot-config/core.js +0 -2456
  35. package/dist/mcp/tools/bot-config/index.d.ts +0 -10
  36. package/dist/mcp/tools/bot-config/index.js +0 -59
  37. package/dist/mcp/tools/bot-config/tools.d.ts +0 -7
  38. package/dist/mcp/tools/bot-config/tools.js +0 -15
  39. package/dist/mcp/tools/bot-config/types.d.ts +0 -50
  40. package/dist/mcp/tools/bot-config/types.js +0 -6
@@ -277,9 +277,14 @@ class ToolRegistry {
277
277
  name,
278
278
  apiKey: context.apiKey.substring(0, 8) + '...'
279
279
  });
280
+ // Pre-transform for specific tools (fix common LLM mistakes before validation)
281
+ let transformedArgs = args;
282
+ if (name === 'install_workflow' && args.workflowTemplates) {
283
+ transformedArgs = this.preTransformInstallWorkflow(args);
284
+ }
280
285
  // Zod validation
281
286
  try {
282
- const validated = tool.schema.parse(args);
287
+ const validated = tool.schema.parse(transformedArgs);
283
288
  return await tool.execute(validated, context);
284
289
  }
285
290
  catch (error) {
@@ -318,6 +323,78 @@ Then retry \`${name}\` with the correct format from the skill examples.` : `šŸ’”
318
323
  throw error;
319
324
  }
320
325
  }
326
+ /**
327
+ * Pre-transform install_workflow args to fix common LLM mistakes BEFORE validation
328
+ */
329
+ preTransformInstallWorkflow(args) {
330
+ let templates = args.workflowTemplates;
331
+ // Fix: single object instead of array
332
+ if (!Array.isArray(templates)) {
333
+ templates = [templates];
334
+ }
335
+ // Transform each template
336
+ templates = templates.map((template, tIdx) => {
337
+ const result = { ...template };
338
+ // Fix: fields as array instead of object
339
+ if (Array.isArray(template.fields)) {
340
+ const fieldsObj = {};
341
+ template.fields.forEach((field, idx) => {
342
+ const fieldId = `_${1000 + idx}`;
343
+ const { name, ...rest } = field;
344
+ fieldsObj[fieldId] = { ...rest, label: rest.label || name };
345
+ });
346
+ result.fields = fieldsObj;
347
+ }
348
+ else if (template.fields) {
349
+ // Fix: field IDs not matching pattern
350
+ const fieldsObj = {};
351
+ let idx = 0;
352
+ for (const [key, field] of Object.entries(template.fields)) {
353
+ const fieldId = /^_\d{4}$/.test(key) ? key : `_${1000 + idx}`;
354
+ const f = field;
355
+ const { name, ...rest } = f;
356
+ fieldsObj[fieldId] = { ...rest, label: rest.label || name || key };
357
+ idx++;
358
+ }
359
+ result.fields = fieldsObj;
360
+ }
361
+ // Fix: phases as array instead of object
362
+ if (Array.isArray(template.phases)) {
363
+ const phasesObj = {};
364
+ template.phases.forEach((phase, idx) => {
365
+ const phaseId = `_${2000 + idx}`;
366
+ phasesObj[phaseId] = { name: phase.name };
367
+ });
368
+ result.phases = phasesObj;
369
+ }
370
+ else if (template.phases) {
371
+ // Fix: phase IDs not matching pattern
372
+ const phasesObj = {};
373
+ let idx = 0;
374
+ for (const [key, phase] of Object.entries(template.phases)) {
375
+ const phaseId = /^_\d{4}$/.test(key) ? key : `_${2000 + idx}`;
376
+ const p = phase;
377
+ phasesObj[phaseId] = { name: p.name || key };
378
+ idx++;
379
+ }
380
+ result.phases = phasesObj;
381
+ }
382
+ // Fix: wrong field types
383
+ if (result.fields) {
384
+ for (const fieldId of Object.keys(result.fields)) {
385
+ const f = result.fields[fieldId];
386
+ // select → textpredefinedoptions
387
+ if (f.type === 'select')
388
+ f.type = 'textpredefinedoptions';
389
+ // user → users
390
+ if (f.type === 'user')
391
+ f.type = 'users';
392
+ }
393
+ }
394
+ return result;
395
+ });
396
+ return { ...args, workflowTemplates: templates };
397
+ }
321
398
  /**
322
399
  * Get tool metadata (for access control checks)
323
400
  */
@@ -658,6 +658,16 @@ exports.createActivityTool = {
658
658
  }),
659
659
  async execute(args, context) {
660
660
  try {
661
+ // DEBUG: Log raw input to diagnose LLM tool calling issues
662
+ logger.info("create_activity called", {
663
+ hasName: !!args.name,
664
+ hasActivities: !!args.activities,
665
+ activitiesType: typeof args.activities,
666
+ activitiesIsArray: Array.isArray(args.activities),
667
+ activitiesLength: Array.isArray(args.activities) ? args.activities.length :
668
+ (typeof args.activities === 'string' ? args.activities.length : 0),
669
+ rawArgs: JSON.stringify(args, null, 2).slice(0, 500), // First 500 chars to avoid huge logs
670
+ });
661
671
  // Helper function to process a single activity object
662
672
  const processActivity = (activityData) => {
663
673
  const activity = {
@@ -797,6 +807,43 @@ exports.createActivityTool = {
797
807
  options.discussionId = args.discussionId;
798
808
  }
799
809
  }
810
+ // Pre-validate required fields before API call
811
+ const workflow = context.init.processes?.find((p) => p._id === args.workflowId);
812
+ if (workflow?.fields) {
813
+ const requiredFields = [];
814
+ for (const [fieldId, field] of Object.entries(workflow.fields)) {
815
+ const f = field;
816
+ if (f.required) {
817
+ requiredFields.push({
818
+ id: fieldId,
819
+ label: f.label || f.key || fieldId,
820
+ key: f.key || fieldId,
821
+ });
822
+ }
823
+ }
824
+ if (requiredFields.length > 0) {
825
+ const missingByActivity = [];
826
+ for (let i = 0; i < activitiesToCreate.length; i++) {
827
+ const activity = activitiesToCreate[i];
828
+ const activityFields = activity.fields || {};
829
+ const missing = requiredFields.filter(rf => activityFields[rf.id] === undefined &&
830
+ activityFields[rf.key] === undefined);
831
+ if (missing.length > 0) {
832
+ const activityName = activity.name || `Activity ${i + 1}`;
833
+ missingByActivity.push(`${activityName}: missing ${missing.map(m => `"${m.label}" (${m.key})`).join(', ')}`);
834
+ }
835
+ }
836
+ if (missingByActivity.length > 0) {
837
+ const fieldList = requiredFields.map(f => `- ${f.label} (key: ${f.key}, id: ${f.id})`).join('\n');
838
+ return {
839
+ content: [{
840
+ type: "text",
841
+ text: `āŒ Missing required fields - cannot create activities.\n\n**Required fields for this workflow:**\n${fieldList}\n\n**Activities with missing fields:**\n${missingByActivity.map(m => `- ${m}`).join('\n')}\n\nšŸ’” Add the missing fields to each activity's \`fields\` object using either the field key or ID.`,
842
+ }],
843
+ };
844
+ }
845
+ }
846
+ }
800
847
  // Call API
801
848
  const result = await context.hailer.createActivities(args.workflowId, activitiesToCreate, options);
802
849
  // Handle response
@@ -14,6 +14,7 @@ const zod_1 = require("zod");
14
14
  const tool_registry_1 = require("../tool-registry");
15
15
  const index_1 = require("../utils/index");
16
16
  const workspace_cache_1 = require("../workspace-cache");
17
+ const hailer_clients_1 = require("../hailer-clients");
17
18
  const logger = (0, index_1.createLogger)({ component: 'discussion-tools' });
18
19
  // ============================================================================
19
20
  // HELPER FUNCTIONS
@@ -786,7 +787,17 @@ exports.joinDiscussionTool = {
786
787
  // ============================================================================
787
788
  // TOOL 5: LEAVE DISCUSSION
788
789
  // ============================================================================
789
- const leaveDiscussionDescription = `Leave a discussion - Works for activity discussions (provide activityId) or general discussions (provide discussionId)`;
790
+ const leaveDiscussionDescription = `Leave a discussion - Bot removes itself from a discussion.
791
+
792
+ **Parameters (at least one required):**
793
+ - \`discussionId\`: The discussion ID to leave (preferred for DMs and general discussions)
794
+ - \`activityId\`: The activity ID whose discussion to leave (for activity-linked discussions)
795
+
796
+ **Examples:**
797
+ - Leave by discussion ID: \`leave_discussion({ discussionId: "69525ce11d392644b7323050" })\`
798
+ - Leave by activity ID: \`leave_discussion({ activityId: "69525ce11d392644b732304f" })\`
799
+
800
+ **Note:** You MUST provide at least one parameter. The parameter cannot be inferred automatically.`;
790
801
  exports.leaveDiscussionTool = {
791
802
  name: 'leave_discussion',
792
803
  group: tool_registry_1.ToolGroup.WRITE,
@@ -806,6 +817,14 @@ exports.leaveDiscussionTool = {
806
817
  message: "Either activityId or discussionId must be provided"
807
818
  }),
808
819
  async execute(args, context) {
820
+ // DEBUG: Log raw input to diagnose LLM tool calling issues
821
+ logger.info("leave_discussion called", {
822
+ hasActivityId: !!args.activityId,
823
+ hasDiscussionId: !!args.discussionId,
824
+ activityId: args.activityId || '(not provided)',
825
+ discussionId: args.discussionId || '(not provided)',
826
+ rawArgs: JSON.stringify(args, null, 2),
827
+ });
809
828
  logger.debug('Leaving discussion', {
810
829
  activityId: args.activityId,
811
830
  discussionId: args.discussionId,
@@ -969,6 +988,30 @@ exports.inviteDiscussionMembersTool = {
969
988
  };
970
989
  }
971
990
  const existingMembers = new Set(discussion?.participants || []);
991
+ // Ensure the bot is a member before inviting others
992
+ // This allows the bot to post messages after inviting users
993
+ try {
994
+ const botUserId = await (0, hailer_clients_1.getCurrentUserId)(context.client);
995
+ if (botUserId && !existingMembers.has(botUserId)) {
996
+ try {
997
+ await context.hailer.joinDiscussion(args.discussionId);
998
+ logger.debug('Bot joined discussion before inviting', { discussionId: args.discussionId });
999
+ }
1000
+ catch (joinError) {
1001
+ // Non-fatal: bot might not be able to join some discussion types
1002
+ logger.debug('Bot join before invite failed (non-fatal)', {
1003
+ discussionId: args.discussionId,
1004
+ error: joinError instanceof Error ? joinError.message : String(joinError)
1005
+ });
1006
+ }
1007
+ }
1008
+ }
1009
+ catch (userIdError) {
1010
+ // Non-fatal: if we can't get the bot's user ID, continue with the invite
1011
+ logger.debug('Could not get bot user ID (non-fatal)', {
1012
+ error: userIdError instanceof Error ? userIdError.message : String(userIdError)
1013
+ });
1014
+ }
972
1015
  // Filter out users who are already members
973
1016
  const alreadyMembers = [];
974
1017
  const newUsers = [];
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Victoria Metrics Tools - Query and List Metrics
3
+ *
4
+ * Tools for querying Hailer metrics from Victoria Metrics:
5
+ * - Query metrics with PromQL (READ)
6
+ * - List available metrics (READ)
7
+ */
8
+ import { Tool } from '../tool-registry';
9
+ export declare const queryMetricTool: Tool;
10
+ export declare const listMetricsTool: Tool;
11
+ export declare const searchWorkspaceForMetricsTool: Tool;
12
+ export declare const searchUserForMetricsTool: Tool;
13
+ //# sourceMappingURL=metrics.d.ts.map