@hailer/mcp 1.1.17-beta.3 → 1.2.0

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 (82) hide show
  1. package/.claude/CLAUDE.md +94 -135
  2. package/.claude/skills/create-and-publish-app/SKILL.md +127 -0
  3. package/.claude/skills/hailer-app-builder/SKILL.md +47 -0
  4. package/.claude/skills/publish-hailer-app/SKILL.md +35 -4
  5. package/.claude/skills/sdk-function-fields/SKILL.md +431 -287
  6. package/dist/bot/bot-manager.d.ts.map +1 -1
  7. package/dist/bot/bot-manager.js +2 -0
  8. package/dist/bot/bot-manager.js.map +1 -1
  9. package/dist/bot/bot.d.ts +2 -1
  10. package/dist/bot/bot.d.ts.map +1 -1
  11. package/dist/bot/bot.js +109 -41
  12. package/dist/bot/bot.js.map +1 -1
  13. package/dist/bot/services/message-classifier.d.ts.map +1 -1
  14. package/dist/bot/services/message-classifier.js +6 -0
  15. package/dist/bot/services/message-classifier.js.map +1 -1
  16. package/dist/bot/services/signal-router.d.ts.map +1 -1
  17. package/dist/bot/services/signal-router.js +1 -0
  18. package/dist/bot/services/signal-router.js.map +1 -1
  19. package/dist/bot/services/system-prompt.d.ts +4 -0
  20. package/dist/bot/services/system-prompt.d.ts.map +1 -1
  21. package/dist/bot/services/system-prompt.js +41 -12
  22. package/dist/bot/services/system-prompt.js.map +1 -1
  23. package/dist/bot/services/types.d.ts +7 -31
  24. package/dist/bot/services/types.d.ts.map +1 -1
  25. package/dist/bot/services/workspace-refresh.js.map +1 -1
  26. package/dist/bot/workspace-overview.d.ts.map +1 -1
  27. package/dist/bot/workspace-overview.js +4 -1
  28. package/dist/bot/workspace-overview.js.map +1 -1
  29. package/dist/bot-config/context.js.map +1 -1
  30. package/dist/bot-config/loader.d.ts.map +1 -1
  31. package/dist/bot-config/loader.js +1 -0
  32. package/dist/bot-config/loader.js.map +1 -1
  33. package/dist/bot-config/types.d.ts +2 -0
  34. package/dist/bot-config/types.d.ts.map +1 -1
  35. package/dist/mcp/UserContextCache.d.ts.map +1 -1
  36. package/dist/mcp/UserContextCache.js +8 -16
  37. package/dist/mcp/UserContextCache.js.map +1 -1
  38. package/dist/mcp/tool-registry.d.ts +3 -2
  39. package/dist/mcp/tool-registry.d.ts.map +1 -1
  40. package/dist/mcp/tool-registry.js +14 -9
  41. package/dist/mcp/tool-registry.js.map +1 -1
  42. package/dist/mcp/tools/activity.d.ts.map +1 -1
  43. package/dist/mcp/tools/activity.js +39 -94
  44. package/dist/mcp/tools/activity.js.map +1 -1
  45. package/dist/mcp/tools/app-scaffold.d.ts.map +1 -1
  46. package/dist/mcp/tools/app-scaffold.js +300 -575
  47. package/dist/mcp/tools/app-scaffold.js.map +1 -1
  48. package/dist/mcp/tools/date.d.ts +5 -0
  49. package/dist/mcp/tools/date.d.ts.map +1 -0
  50. package/dist/mcp/tools/date.js +23 -0
  51. package/dist/mcp/tools/date.js.map +1 -0
  52. package/dist/mcp/tools/discussion.d.ts.map +1 -1
  53. package/dist/mcp/tools/discussion.js +17 -9
  54. package/dist/mcp/tools/discussion.js.map +1 -1
  55. package/dist/mcp/tools/index.d.ts.map +1 -1
  56. package/dist/mcp/tools/index.js +2 -0
  57. package/dist/mcp/tools/index.js.map +1 -1
  58. package/dist/mcp/tools/insight.d.ts.map +1 -1
  59. package/dist/mcp/tools/insight.js +13 -19
  60. package/dist/mcp/tools/insight.js.map +1 -1
  61. package/dist/mcp/tools/workflow.d.ts +1 -0
  62. package/dist/mcp/tools/workflow.d.ts.map +1 -1
  63. package/dist/mcp/tools/workflow.js +293 -46
  64. package/dist/mcp/tools/workflow.js.map +1 -1
  65. package/dist/mcp/utils/data-transformers.d.ts +47 -10
  66. package/dist/mcp/utils/data-transformers.d.ts.map +1 -1
  67. package/dist/mcp/utils/data-transformers.js +12 -9
  68. package/dist/mcp/utils/data-transformers.js.map +1 -1
  69. package/dist/mcp/utils/types.d.ts +2 -0
  70. package/dist/mcp/utils/types.d.ts.map +1 -1
  71. package/dist/mcp/utils/types.js.map +1 -1
  72. package/dist/mcp/webhook-handler.d.ts.map +1 -1
  73. package/dist/mcp/webhook-handler.js +4 -1
  74. package/dist/mcp/webhook-handler.js.map +1 -1
  75. package/dist/mcp/workspace-cache.d.ts +8 -2
  76. package/dist/mcp/workspace-cache.d.ts.map +1 -1
  77. package/dist/mcp/workspace-cache.js +12 -8
  78. package/dist/mcp/workspace-cache.js.map +1 -1
  79. package/dist/plugins/vipunen/tools.d.ts +1 -0
  80. package/dist/plugins/vipunen/tools.d.ts.map +1 -1
  81. package/dist/plugins/vipunen/tools.js.map +1 -1
  82. package/package.json +1 -1
@@ -6,13 +6,48 @@
6
6
  * These are PLAYGROUND tools requiring workspace administrator permissions.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.workflowTools = exports.updateWorkflowPhaseTool = exports.coreInitTool = exports.countActivitiesTool = exports.listWorkflowsMinimalTool = exports.testFunctionFieldTool = exports.updateWorkflowFieldTool = exports.removeWorkflowTool = exports.installWorkflowTool = exports.listWorkflowsTool = exports.listWorkflowPhasesTool = exports.getWorkflowSchemaTool = void 0;
9
+ exports.workflowTools = exports.autoSetKeysTool = exports.updateWorkflowPhaseTool = exports.coreInitTool = exports.countActivitiesTool = exports.listWorkflowsMinimalTool = exports.testFunctionFieldTool = exports.updateWorkflowFieldTool = exports.removeWorkflowTool = exports.installWorkflowTool = exports.listWorkflowsTool = exports.listWorkflowPhasesTool = exports.getWorkflowSchemaTool = void 0;
10
10
  const zod_1 = require("zod");
11
11
  const tool_registry_1 = require("../tool-registry");
12
12
  const logger_1 = require("../../lib/logger");
13
13
  const request_logger_1 = require("../../lib/request-logger");
14
14
  const workspace_overview_1 = require("../../bot/workspace-overview");
15
+ const config_1 = require("../../config");
15
16
  const logger = (0, logger_1.createLogger)({ component: 'workflow-tools' });
17
+ /**
18
+ * Detect if the SDK workspace/ directory exists (local SDK project mode).
19
+ * When true, config changes should go through workspace/ files + npm run push,
20
+ * not through MCP API calls.
21
+ */
22
+ function isSdkProject() {
23
+ try {
24
+ const fs = require('fs');
25
+ const path = require('path');
26
+ const workspacePath = config_1.environment.WORKSPACE_CONFIG_PATH || path.join(process.cwd(), 'workspace');
27
+ return fs.existsSync(workspacePath);
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ }
33
+ /** Return an SDK redirect message for config tools when SDK is available */
34
+ function sdkRedirect(tool, sdkCommand, sdkFile) {
35
+ return {
36
+ content: [{
37
+ type: "text",
38
+ text: `⚠️ **SDK project detected — use the SDK instead of \`${tool}\`**\n\n` +
39
+ `This project has a \`workspace/\` directory, which means workflow configuration should be managed through SDK files.\n\n` +
40
+ `**Instead, do this:**\n` +
41
+ `1. Edit \`${sdkFile}\`\n` +
42
+ `2. Run \`${sdkCommand}\`\n` +
43
+ `3. Run \`npm run pull\` to sync back\n\n` +
44
+ `This ensures your local files stay in sync with Hailer and changes are version-controlled.\n\n` +
45
+ `💡 If you really need to use the API directly (e.g., no filesystem access), add \`force: true\` to override.`,
46
+ }],
47
+ };
48
+ }
49
+ /** Hint prepended to discovery tool responses when SDK workspace/ exists */
50
+ const SDK_DISCOVERY_HINT = `💡 **Tip:** This info is available locally in \`workspace/\` files — read those instead of making API calls next time.\n\n`;
16
51
  // ============================================================================
17
52
  // HELPER FUNCTIONS
18
53
  // ============================================================================
@@ -51,11 +86,103 @@ function parseErrorType(errorMessage) {
51
86
  const match = errorMessage.match(/^(SyntaxError|ReferenceError|TypeError)/);
52
87
  return match ? match[1] : 'Error';
53
88
  }
89
+ /** Find workflow in cached data by _id or key */
90
+ function resolveWorkflow(processes, idOrKey) {
91
+ return processes.find((p) => p._id === idOrKey || p.key === idOrKey);
92
+ }
93
+ /** Generate a camelCase key from a name (e.g., "Customer Acquisition" → "customerAcquisition") */
94
+ function generateKey(name, existingKeys) {
95
+ const words = name
96
+ .replace(/[^a-zA-Z0-9\s-_]/g, '')
97
+ .split(/[\s\-_]+/)
98
+ .filter(w => w.length > 0);
99
+ if (words.length === 0)
100
+ return 'unnamed';
101
+ const base = words[0].toLowerCase() + words.slice(1).map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
102
+ const key = base.slice(0, 64);
103
+ if (!existingKeys || !existingKeys.has(key))
104
+ return key;
105
+ // Dedup: append incrementing suffix
106
+ for (let i = 2; i <= 99; i++) {
107
+ const candidate = `${key}${i}`.slice(0, 64);
108
+ if (!existingKeys.has(candidate))
109
+ return candidate;
110
+ }
111
+ return `${key}${Date.now()}`.slice(0, 64);
112
+ }
113
+ /**
114
+ * Auto-set keys on a workflow, its phases, and its fields.
115
+ * Skips any that already have keys. Returns a summary of what was set.
116
+ */
117
+ async function autoSetKeys(workflowId, context) {
118
+ const result = { workflow: undefined, phases: {}, fields: {}, errors: [] };
119
+ // Get workflow from cache
120
+ const workflow = resolveWorkflow(context.init.processes || [], workflowId);
121
+ if (!workflow) {
122
+ result.errors.push(`Workflow "${workflowId}" not found`);
123
+ return result;
124
+ }
125
+ // Collect existing keys to avoid duplicates
126
+ const existingPhaseKeys = new Set();
127
+ for (const phase of Object.values(workflow.phases || {})) {
128
+ if (phase.key)
129
+ existingPhaseKeys.add(phase.key);
130
+ }
131
+ const existingFieldKeys = new Set();
132
+ for (const field of Object.values(workflow.fields || {})) {
133
+ if (field.key)
134
+ existingFieldKeys.add(field.key);
135
+ }
136
+ // Set workflow key (unique per workspace — no local dedup needed, API rejects duplicates)
137
+ if (!workflow.key && workflow.name) {
138
+ const key = generateKey(workflow.name);
139
+ try {
140
+ await context.hailer.request('process.set_info', [workflow._id, { key }]);
141
+ result.workflow = key;
142
+ }
143
+ catch (e) {
144
+ result.errors.push(`Workflow key "${key}": ${e?.message || e}`);
145
+ }
146
+ }
147
+ // Set phase keys (unique per workflow)
148
+ for (const [phaseId, phase] of Object.entries(workflow.phases || {})) {
149
+ const p = phase;
150
+ if (!p.key && p.name) {
151
+ const key = generateKey(p.name, existingPhaseKeys);
152
+ existingPhaseKeys.add(key);
153
+ try {
154
+ await context.hailer.request('phase.update', [phaseId, { key }]);
155
+ result.phases[phaseId] = key;
156
+ }
157
+ catch (e) {
158
+ result.errors.push(`Phase "${p.name}" key "${key}": ${e?.message || e}`);
159
+ }
160
+ }
161
+ }
162
+ // Set field keys (unique per workflow)
163
+ for (const [fieldId, field] of Object.entries(workflow.fields || {})) {
164
+ const f = field;
165
+ if (!f.key && f.label) {
166
+ const key = generateKey(f.label, existingFieldKeys);
167
+ existingFieldKeys.add(key);
168
+ try {
169
+ await context.hailer.request('process.update_field', [workflow._id, fieldId, { key }]);
170
+ result.fields[fieldId] = key;
171
+ }
172
+ catch (e) {
173
+ result.errors.push(`Field "${f.label}" key "${key}": ${e?.message || e}`);
174
+ }
175
+ }
176
+ }
177
+ return result;
178
+ }
54
179
  // ============================================================================
55
180
  // READ TOOLS - Workflow Schema and Information
56
181
  // ============================================================================
57
182
  const getWorkflowSchemaDescription = `Get workflow field structure - CALL THIS BEFORE create_activity or update_activity.
58
183
 
184
+ **SDK project?** If workspace/ exists, read field definitions from workspace/<WorkflowName>_<id>/fields.ts instead of calling this tool.
185
+
59
186
  **Hailer Concept:** Shows all fields (columns) in a workflow with their IDs, types, and options. You MUST know field IDs to create or update activities.
60
187
 
61
188
  **Returns:** Field IDs, labels, types (text, numeric, date, dropdown, user, activitylink), required status, dropdown options.
@@ -66,23 +193,18 @@ exports.getWorkflowSchemaTool = {
66
193
  group: tool_registry_1.ToolGroup.READ,
67
194
  description: getWorkflowSchemaDescription,
68
195
  schema: zod_1.z.object({
69
- workflowId: zod_1.z.string().describe("Workflow ID to get schema from"),
70
- phaseId: zod_1.z.string().describe("Phase ID to get schema from (use list_workflow_phases to get available phases)"),
196
+ workflowId: zod_1.z.string().describe("Workflow ID or key to get schema from"),
197
+ phaseId: zod_1.z.string().describe("Phase ID or key to get schema from (use list_workflow_phases to get available phases)"),
71
198
  compact: zod_1.z.boolean().optional().describe("Return compact summary instead of full field details (default: false)"),
199
+ force: zod_1.z.boolean().optional().describe("Override SDK redirect — use API even when workspace/ exists"),
72
200
  }),
73
201
  async execute(args, context) {
202
+ if (isSdkProject() && !args.force) {
203
+ return sdkRedirect('get_workflow_schema', 'cat workspace/<WorkflowName>_<id>/fields.ts', 'workspace/<WorkflowName>_<id>/fields.ts');
204
+ }
74
205
  try {
75
- // Validate phaseId format early - must be 24-char MongoDB ID, not a simple number
76
- if (args.phaseId && args.phaseId.length !== 24) {
77
- return {
78
- content: [{
79
- type: "text",
80
- text: `Invalid phaseId "${args.phaseId}" - must be a 24-character ID.\n\n**How to get valid phase IDs:**\n1. Call \`list_workflow_phases\` with the workflowId first\n2. Use the returned phase IDs (24 characters like "691ffdf84217e9e8434e56a5")\n\n**Example:**\n\`\`\`\nlist_workflow_phases({ workflowId: "${args.workflowId}" })\n\`\`\``,
81
- }],
82
- };
83
- }
84
206
  // Use cached workflow data from context.init (already fetched during initialization)
85
- const workflow = context.init.processes?.find(p => p._id === args.workflowId);
207
+ const workflow = resolveWorkflow(context.init.processes || [], args.workflowId);
86
208
  if (!workflow) {
87
209
  return {
88
210
  content: [{
@@ -111,7 +233,7 @@ exports.getWorkflowSchemaTool = {
111
233
  }],
112
234
  };
113
235
  }
114
- let responseText = `📋 **WORKFLOW SCHEMA** for "${workflow.name}":\n\n`;
236
+ let responseText = (isSdkProject() ? SDK_DISCOVERY_HINT : '') + `📋 **WORKFLOW SCHEMA** for "${workflow.name}":\n\n`;
115
237
  if (schemaData.name) {
116
238
  responseText += `**Name Field:**\n`;
117
239
  responseText += `- Type: ${schemaData.name.type}\n`;
@@ -187,6 +309,8 @@ exports.getWorkflowSchemaTool = {
187
309
  };
188
310
  const listWorkflowPhasesDescription = `List phases (stages/statuses) in a workflow - REQUIRED to get phase IDs.
189
311
 
312
+ **SDK project?** If workspace/ exists, read phases from workspace/<WorkflowName>_<id>/phases.ts instead of calling this tool.
313
+
190
314
  **Hailer Concept:** Phases are stages like "New" → "In Progress" → "Done". Activities move through phases as work progresses.
191
315
 
192
316
  **When to use:** Before list_activities (need phaseId), before create_activity (need initial phase), before update_activity (to move to new phase).
@@ -197,12 +321,16 @@ exports.listWorkflowPhasesTool = {
197
321
  group: tool_registry_1.ToolGroup.READ,
198
322
  description: listWorkflowPhasesDescription,
199
323
  schema: zod_1.z.object({
200
- workflowId: zod_1.z.string().describe("Workflow ID to get phases from"),
324
+ workflowId: zod_1.z.string().describe("Workflow ID or key to get phases from"),
325
+ force: zod_1.z.boolean().optional().describe("Override SDK redirect — use API even when workspace/ exists"),
201
326
  }),
202
327
  async execute(args, context) {
328
+ if (isSdkProject() && !args.force) {
329
+ return sdkRedirect('list_workflow_phases', 'cat workspace/<WorkflowName>_<id>/phases.ts', 'workspace/<WorkflowName>_<id>/phases.ts');
330
+ }
203
331
  try {
204
332
  // Use cached workflow data from context.init (already fetched during initialization)
205
- const workflowData = context.init.processes?.find(p => p._id === args.workflowId);
333
+ const workflowData = resolveWorkflow(context.init.processes || [], args.workflowId);
206
334
  if (!workflowData) {
207
335
  return {
208
336
  content: [{
@@ -213,7 +341,7 @@ exports.listWorkflowPhasesTool = {
213
341
  }
214
342
  const phases = workflowData.phases || {};
215
343
  const phasesOrder = workflowData.phasesOrder || [];
216
- let responseText = `📊 **WORKFLOW PHASES** for "${workflowData.name}":\n\n`;
344
+ let responseText = (isSdkProject() ? SDK_DISCOVERY_HINT : '') + `📊 **WORKFLOW PHASES** for "${workflowData.name}":\n\n`;
217
345
  if (Object.keys(phases).length === 0) {
218
346
  responseText += `❌ No phases found in workflow "${workflowData.name}".`;
219
347
  return {
@@ -227,6 +355,9 @@ exports.listWorkflowPhasesTool = {
227
355
  if (phase) {
228
356
  responseText += `${index + 1}. **${phase.name}**\n`;
229
357
  responseText += ` - Phase ID: \`${phase._id}\`\n`;
358
+ if (phase.key) {
359
+ responseText += ` - Key: \`${phase.key}\`\n`;
360
+ }
230
361
  if (phase.description) {
231
362
  responseText += ` - Description: ${phase.description}\n`;
232
363
  }
@@ -441,6 +572,7 @@ const installWorkflowSchema = zod_1.z.object({
441
572
  .min(1, "At least one workflow template is required")
442
573
  .max(100, "Maximum 100 workflow templates per installation")
443
574
  .describe("Array of workflow template objects to install"),
575
+ force: zod_1.z.boolean().optional().describe("Override SDK redirect — use API directly even when workspace/ exists"),
444
576
  });
445
577
  exports.installWorkflowTool = {
446
578
  name: 'install_workflow',
@@ -448,6 +580,10 @@ exports.installWorkflowTool = {
448
580
  description: installWorkflowDescription,
449
581
  schema: installWorkflowSchema,
450
582
  async execute(args, context) {
583
+ // Guard: redirect to SDK when workspace/ exists
584
+ if (isSdkProject() && !args.force) {
585
+ return sdkRedirect('install_workflow', 'npm run workflows-sync:force && npm run push:force', 'workspace/workflows.ts');
586
+ }
451
587
  logger.debug('Installing workflow', {
452
588
  templateCount: args.workflowTemplates.length,
453
589
  apiKey: context.apiKey.substring(0, 8) + '...'
@@ -537,6 +673,29 @@ exports.installWorkflowTool = {
537
673
  logger.warn('Failed to refresh workflow cache after installation', { error: refreshError });
538
674
  // Non-fatal - the workflow was created, just cache is stale
539
675
  }
676
+ // Auto-set keys on newly created workflows
677
+ const keyResults = [];
678
+ // Extract workflow IDs from the result map first
679
+ const newWorkflowIds = Object.entries(result)
680
+ .filter(([tpl]) => tpl.startsWith('_0') || tpl.startsWith('_00'))
681
+ .map(([, id]) => id);
682
+ // Fallback: if no _0xxx pattern, take all values that look like workflow IDs
683
+ if (newWorkflowIds.length === 0) {
684
+ for (const [, id] of Object.entries(result)) {
685
+ if (id.length === 24)
686
+ newWorkflowIds.push(id);
687
+ break; // Just take the first one
688
+ }
689
+ }
690
+ for (const wfId of newWorkflowIds) {
691
+ try {
692
+ const keys = await autoSetKeys(wfId, context);
693
+ keyResults.push({ workflowId: wfId, keys });
694
+ }
695
+ catch {
696
+ logger.warn('Failed to auto-set keys for new workflow', { workflowId: wfId });
697
+ }
698
+ }
540
699
  // Build success response - clearly separate workflow/field/phase IDs
541
700
  const templates = transformedTemplates;
542
701
  // Extract workflow ID (the one that starts with workflow template ID or is for the workflow)
@@ -586,18 +745,35 @@ exports.installWorkflowTool = {
586
745
  }
587
746
  responseText += `\n`;
588
747
  }
589
- // Example
590
- const firstFieldId = Object.values(fieldIds)[0];
591
- const firstPhaseId = Object.values(phaseIds)[0];
748
+ // Show auto-generated keys
749
+ if (keyResults.length > 0) {
750
+ const kr = keyResults[0].keys;
751
+ responseText += `**🔑 Auto-generated keys:**\n`;
752
+ if (kr.workflow)
753
+ responseText += `- Workflow: \`${kr.workflow}\`\n`;
754
+ for (const [, key] of Object.entries(kr.phases))
755
+ responseText += `- Phase: \`${key}\`\n`;
756
+ for (const [, key] of Object.entries(kr.fields))
757
+ responseText += `- Field: \`${key}\`\n`;
758
+ if (kr.errors.length > 0) {
759
+ responseText += `- ⚠️ ${kr.errors.length} key(s) failed\n`;
760
+ }
761
+ responseText += `\n`;
762
+ }
763
+ // Example using keys (preferred) with ID fallback
764
+ const kr = keyResults[0]?.keys;
765
+ const exampleWorkflow = kr?.workflow || workflowIds[0] || 'WORKFLOW_ID';
766
+ const examplePhase = Object.values(kr?.phases || {})[0] || Object.values(phaseIds)[0];
767
+ const exampleField = Object.values(kr?.fields || {})[0] || Object.values(fieldIds)[0];
592
768
  responseText += `**Example create_activity:**\n`;
593
769
  responseText += `\`\`\`json\n`;
594
770
  responseText += `{\n`;
595
- responseText += ` "workflowId": "${workflowIds[0] || 'WORKFLOW_ID'}",\n`;
771
+ responseText += ` "workflowId": "${exampleWorkflow}",\n`;
596
772
  responseText += ` "name": "New Activity",\n`;
597
- if (firstPhaseId)
598
- responseText += ` "phaseId": "${firstPhaseId}",\n`;
599
- if (firstFieldId)
600
- responseText += ` "fields": { "${firstFieldId}": "value" }\n`;
773
+ if (examplePhase)
774
+ responseText += ` "phaseId": "${examplePhase}",\n`;
775
+ if (exampleField)
776
+ responseText += ` "fields": { "${exampleField}": "value" }\n`;
601
777
  responseText += `}\n`;
602
778
  responseText += `\`\`\``;
603
779
  return {
@@ -637,7 +813,7 @@ const removeWorkflowSchema = zod_1.z.object({
637
813
  workflowId: zod_1.z
638
814
  .string()
639
815
  .min(1, "Workflow ID is required")
640
- .describe("The workflow ID to remove (get from list_workflows)"),
816
+ .describe("The workflow ID or key to remove (get from list_workflows)"),
641
817
  workspaceId: zod_1.z
642
818
  .string()
643
819
  .optional()
@@ -665,7 +841,7 @@ exports.removeWorkflowTool = {
665
841
  });
666
842
  try {
667
843
  // Get workflow info from cached init data
668
- const workflow = context.init.processes?.find(p => p._id === args.workflowId);
844
+ const workflow = resolveWorkflow(context.init.processes || [], args.workflowId);
669
845
  if (!workflow) {
670
846
  return {
671
847
  content: [{
@@ -779,7 +955,7 @@ const updateWorkflowFieldSchema = zod_1.z.object({
779
955
  workflowId: zod_1.z
780
956
  .string()
781
957
  .min(1, "Workflow ID is required")
782
- .describe("The workflow ID containing the field (get from list_workflows)"),
958
+ .describe("The workflow ID or key containing the field (get from list_workflows)"),
783
959
  fieldId: zod_1.z
784
960
  .string()
785
961
  .min(1, "Field ID is required")
@@ -801,6 +977,7 @@ const updateWorkflowFieldSchema = zod_1.z.object({
801
977
  .default(true)
802
978
  .optional()
803
979
  .describe("Auto-generate camelCase key from label if key not provided (default: true). IMPORTANT: Set to false when updating ONLY the label to prevent key regeneration errors."),
980
+ force: zod_1.z.boolean().optional().describe("Override SDK redirect — use API directly even when workspace/ exists"),
804
981
  });
805
982
  exports.updateWorkflowFieldTool = {
806
983
  name: 'update_workflow_field',
@@ -808,6 +985,9 @@ exports.updateWorkflowFieldTool = {
808
985
  description: updateWorkflowFieldDescription,
809
986
  schema: updateWorkflowFieldSchema,
810
987
  async execute(args, context) {
988
+ if (isSdkProject() && !args.force) {
989
+ return sdkRedirect('update_workflow_field', 'npm run fields-push:force', 'workspace/<WorkflowName>_<id>/fields.ts');
990
+ }
811
991
  logger.debug('Updating workflow field', {
812
992
  workflowId: args.workflowId,
813
993
  fieldId: args.fieldId,
@@ -953,7 +1133,7 @@ exports.testFunctionFieldTool = {
953
1133
  };
954
1134
  }
955
1135
  // Get workflow field definition from cached data (may be stale for newly created fields)
956
- const workflow = context.init.processes?.find(p => p._id === activity.process);
1136
+ const workflow = resolveWorkflow(context.init.processes || [], activity.process);
957
1137
  const field = workflow?.fields?.[args.fieldId];
958
1138
  // Resolve function variables: caller-provided takes priority, then field definition
959
1139
  const functionVariables = args.functionVariables || field?.functionVariables;
@@ -1094,7 +1274,7 @@ const listWorkflowsMinimalSchema = zod_1.z.object({
1094
1274
  });
1095
1275
  exports.listWorkflowsMinimalTool = {
1096
1276
  name: 'list_workflows_minimal',
1097
- group: tool_registry_1.ToolGroup.PLAYGROUND,
1277
+ group: tool_registry_1.ToolGroup.READ,
1098
1278
  description: listWorkflowsMinimalDescription,
1099
1279
  schema: listWorkflowsMinimalSchema,
1100
1280
  async execute(args, context) {
@@ -1135,7 +1315,7 @@ exports.listWorkflowsMinimalTool = {
1135
1315
  activityCount: w.createdActivities || 0,
1136
1316
  isStarred: w.isStarred || false
1137
1317
  }));
1138
- let responseText = `📋 **Workflows Found**\n\n`;
1318
+ let responseText = (isSdkProject() ? SDK_DISCOVERY_HINT : '') + `📋 **Workflows Found**\n\n`;
1139
1319
  responseText += `Total: ${totalCount}\n`;
1140
1320
  responseText += `Showing: ${workflows.length}\n`;
1141
1321
  if (offset > 0)
@@ -1203,11 +1383,11 @@ count_activities({
1203
1383
  - This tool makes 1 API call per workflow
1204
1384
  - Use list_workflows to get workflow IDs`;
1205
1385
  const countActivitiesSchema = zod_1.z.object({
1206
- workflowId: zod_1.z.string().describe("Workflow ID to count activities from (24 characters)"),
1386
+ workflowId: zod_1.z.string().describe("Workflow ID or key"),
1207
1387
  });
1208
1388
  exports.countActivitiesTool = {
1209
1389
  name: 'count_activities',
1210
- group: tool_registry_1.ToolGroup.PLAYGROUND,
1390
+ group: tool_registry_1.ToolGroup.READ,
1211
1391
  description: countActivitiesDescription,
1212
1392
  schema: countActivitiesSchema,
1213
1393
  async execute(args, context) {
@@ -1217,7 +1397,7 @@ exports.countActivitiesTool = {
1217
1397
  });
1218
1398
  try {
1219
1399
  // Get workflow name from cached data
1220
- const workflow = context.init.processes?.find(p => p._id === args.workflowId);
1400
+ const workflow = resolveWorkflow(context.init.processes || [], args.workflowId);
1221
1401
  if (!workflow) {
1222
1402
  return {
1223
1403
  content: [{
@@ -1300,14 +1480,15 @@ update_workflow_phase({
1300
1480
  - User must be workspace administrator
1301
1481
  `;
1302
1482
  const updateWorkflowPhaseSchema = zod_1.z.object({
1303
- workflowId: zod_1.z.string().describe("The workflow ID containing the phase (24 characters)"),
1304
- phaseId: zod_1.z.string().describe("The phase ID to update"),
1483
+ workflowId: zod_1.z.string().describe("The workflow ID or key containing the phase"),
1484
+ phaseId: zod_1.z.string().describe("The phase ID or key to update"),
1305
1485
  phaseData: zod_1.z.object({
1306
1486
  name: zod_1.z.string().optional().describe("New phase name"),
1307
1487
  description: zod_1.z.string().optional().describe("New phase description"),
1308
1488
  color: zod_1.z.string().optional().describe("Phase color code"),
1309
1489
  }).passthrough().describe("Phase properties to update"),
1310
1490
  workspaceId: zod_1.z.string().optional().describe("Optional workspace ID - defaults to current workspace"),
1491
+ force: zod_1.z.boolean().optional().describe("Override SDK redirect — use API directly even when workspace/ exists"),
1311
1492
  });
1312
1493
  // ============================================================================
1313
1494
  // CORE INIT TOOL - Workspace overview for terminal Claude Code sessions
@@ -1348,10 +1529,21 @@ exports.coreInitTool = {
1348
1529
  async execute(_args, context) {
1349
1530
  try {
1350
1531
  const overview = (0, workspace_overview_1.generateWorkspaceOverview)(context.init);
1532
+ const sdkMode = isSdkProject();
1533
+ let prefix = `Use this workspace context for all subsequent tool calls. Do NOT call core_init again this session.\n\n`;
1534
+ if (sdkMode) {
1535
+ prefix += `⚡ **SDK project detected** — \`workspace/\` directory exists.\n\n` +
1536
+ `**IMPORTANT: Read local files for discovery, not API calls:**\n` +
1537
+ `- Workflow/field/phase IDs → read \`workspace/\` files (fields.ts, phases.ts, enums.ts)\n` +
1538
+ `- Field types, labels, options → read \`workspace/<WorkflowName>_<id>/fields.ts\`\n` +
1539
+ `- Phase names and order → read \`workspace/<WorkflowName>_<id>/phases.ts\`\n` +
1540
+ `- Workflow config changes → edit workspace/ files + \`npm run push:force\`\n\n` +
1541
+ `**Use MCP tools ONLY for runtime data:** create_activity, update_activity, list_activities, preview_insight, discussions.\n\n`;
1542
+ }
1351
1543
  return {
1352
1544
  content: [{
1353
1545
  type: "text",
1354
- text: `Use this workspace context for all subsequent tool calls. Do NOT call core_init again this session.\n\n${overview}`,
1546
+ text: prefix + overview,
1355
1547
  }],
1356
1548
  };
1357
1549
  }
@@ -1373,21 +1565,15 @@ exports.updateWorkflowPhaseTool = {
1373
1565
  description: updateWorkflowPhaseDescription,
1374
1566
  schema: updateWorkflowPhaseSchema,
1375
1567
  async execute(args, context) {
1568
+ if (isSdkProject() && !args.force) {
1569
+ return sdkRedirect('update_workflow_phase', 'npm run phases-push:force', 'workspace/<WorkflowName>_<id>/phases.ts');
1570
+ }
1376
1571
  logger.debug('Updating workflow phase', {
1377
1572
  workflowId: args.workflowId,
1378
1573
  phaseId: args.phaseId,
1379
1574
  apiKey: context.apiKey.substring(0, 8) + '...'
1380
1575
  });
1381
1576
  try {
1382
- // Validate phaseId format early - must be 24-char MongoDB ID, not a simple number
1383
- if (args.phaseId && args.phaseId.length !== 24) {
1384
- return {
1385
- content: [{
1386
- type: "text",
1387
- text: `Invalid phaseId "${args.phaseId}" - must be a 24-character ID.\n\n**How to get valid phase IDs:**\n1. Call \`list_workflow_phases\` with the workflowId first\n2. Use the returned phase IDs (24 characters like "691ffdf84217e9e8434e56a5")\n\n**Example:**\n\`\`\`\nlist_workflow_phases({ workflowId: "${args.workflowId}" })\n\`\`\``,
1388
- }],
1389
- };
1390
- }
1391
1577
  // Get current workspace from cached init data
1392
1578
  let workspaceId = args.workspaceId;
1393
1579
  if (!workspaceId) {
@@ -1468,6 +1654,66 @@ exports.updateWorkflowPhaseTool = {
1468
1654
  }
1469
1655
  }
1470
1656
  };
1657
+ // ============================================================================
1658
+ // AUTO SET KEYS TOOL
1659
+ // ============================================================================
1660
+ const autoSetKeysDescription = `Auto-generate human-readable keys for a workflow, its phases, and its fields. Keys are derived from names/labels (e.g., "Customer Acquisition" → "customer-acquisition"). Skips entities that already have keys set. After setting keys, you can use them instead of hex ObjectIds in all tools.`;
1661
+ exports.autoSetKeysTool = {
1662
+ name: 'auto_set_keys',
1663
+ group: tool_registry_1.ToolGroup.PLAYGROUND,
1664
+ description: autoSetKeysDescription,
1665
+ schema: zod_1.z.object({
1666
+ workflowId: zod_1.z.string().describe("Workflow ID or key to set keys on"),
1667
+ }),
1668
+ async execute(args, context) {
1669
+ try {
1670
+ const result = await autoSetKeys(args.workflowId, context);
1671
+ let responseText = `✅ **Keys Auto-Generated**\n\n`;
1672
+ if (result.workflow) {
1673
+ responseText += `**Workflow key:** \`${result.workflow}\`\n\n`;
1674
+ }
1675
+ else {
1676
+ const workflow = resolveWorkflow(context.init.processes || [], args.workflowId);
1677
+ if (workflow?.key) {
1678
+ responseText += `**Workflow key:** \`${workflow.key}\` (already set)\n\n`;
1679
+ }
1680
+ }
1681
+ const phaseEntries = Object.entries(result.phases);
1682
+ if (phaseEntries.length > 0) {
1683
+ responseText += `**Phase keys set (${phaseEntries.length}):**\n`;
1684
+ for (const [, key] of phaseEntries) {
1685
+ responseText += `- \`${key}\`\n`;
1686
+ }
1687
+ responseText += `\n`;
1688
+ }
1689
+ const fieldEntries = Object.entries(result.fields);
1690
+ if (fieldEntries.length > 0) {
1691
+ responseText += `**Field keys set (${fieldEntries.length}):**\n`;
1692
+ for (const [, key] of fieldEntries) {
1693
+ responseText += `- \`${key}\`\n`;
1694
+ }
1695
+ responseText += `\n`;
1696
+ }
1697
+ if (phaseEntries.length === 0 && fieldEntries.length === 0 && !result.workflow) {
1698
+ responseText += `All keys already set — nothing to do.\n`;
1699
+ }
1700
+ if (result.errors.length > 0) {
1701
+ responseText += `\n⚠️ **Errors (${result.errors.length}):**\n`;
1702
+ for (const err of result.errors) {
1703
+ responseText += `- ${err}\n`;
1704
+ }
1705
+ }
1706
+ responseText += `\n💡 You can now use these keys instead of hex IDs in all tools.`;
1707
+ return { content: [{ type: "text", text: responseText }] };
1708
+ }
1709
+ catch (error) {
1710
+ const errorMessage = error instanceof Error ? error.message : String(error);
1711
+ return {
1712
+ content: [{ type: "text", text: `❌ **Error setting keys:** ${errorMessage}` }],
1713
+ };
1714
+ }
1715
+ }
1716
+ };
1471
1717
  /** All workflow tools */
1472
1718
  exports.workflowTools = [
1473
1719
  exports.getWorkflowSchemaTool,
@@ -1481,5 +1727,6 @@ exports.workflowTools = [
1481
1727
  exports.listWorkflowsMinimalTool,
1482
1728
  exports.countActivitiesTool,
1483
1729
  exports.coreInitTool,
1730
+ exports.autoSetKeysTool,
1484
1731
  ];
1485
1732
  //# sourceMappingURL=workflow.js.map