@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.
- package/.claude/CLAUDE.md +94 -135
- package/.claude/skills/create-and-publish-app/SKILL.md +127 -0
- package/.claude/skills/hailer-app-builder/SKILL.md +47 -0
- package/.claude/skills/publish-hailer-app/SKILL.md +35 -4
- package/.claude/skills/sdk-function-fields/SKILL.md +431 -287
- package/dist/bot/bot-manager.d.ts.map +1 -1
- package/dist/bot/bot-manager.js +2 -0
- package/dist/bot/bot-manager.js.map +1 -1
- package/dist/bot/bot.d.ts +2 -1
- package/dist/bot/bot.d.ts.map +1 -1
- package/dist/bot/bot.js +109 -41
- package/dist/bot/bot.js.map +1 -1
- package/dist/bot/services/message-classifier.d.ts.map +1 -1
- package/dist/bot/services/message-classifier.js +6 -0
- package/dist/bot/services/message-classifier.js.map +1 -1
- package/dist/bot/services/signal-router.d.ts.map +1 -1
- package/dist/bot/services/signal-router.js +1 -0
- package/dist/bot/services/signal-router.js.map +1 -1
- package/dist/bot/services/system-prompt.d.ts +4 -0
- package/dist/bot/services/system-prompt.d.ts.map +1 -1
- package/dist/bot/services/system-prompt.js +41 -12
- package/dist/bot/services/system-prompt.js.map +1 -1
- package/dist/bot/services/types.d.ts +7 -31
- package/dist/bot/services/types.d.ts.map +1 -1
- package/dist/bot/services/workspace-refresh.js.map +1 -1
- package/dist/bot/workspace-overview.d.ts.map +1 -1
- package/dist/bot/workspace-overview.js +4 -1
- package/dist/bot/workspace-overview.js.map +1 -1
- package/dist/bot-config/context.js.map +1 -1
- package/dist/bot-config/loader.d.ts.map +1 -1
- package/dist/bot-config/loader.js +1 -0
- package/dist/bot-config/loader.js.map +1 -1
- package/dist/bot-config/types.d.ts +2 -0
- package/dist/bot-config/types.d.ts.map +1 -1
- package/dist/mcp/UserContextCache.d.ts.map +1 -1
- package/dist/mcp/UserContextCache.js +8 -16
- package/dist/mcp/UserContextCache.js.map +1 -1
- package/dist/mcp/tool-registry.d.ts +3 -2
- package/dist/mcp/tool-registry.d.ts.map +1 -1
- package/dist/mcp/tool-registry.js +14 -9
- package/dist/mcp/tool-registry.js.map +1 -1
- package/dist/mcp/tools/activity.d.ts.map +1 -1
- package/dist/mcp/tools/activity.js +39 -94
- package/dist/mcp/tools/activity.js.map +1 -1
- package/dist/mcp/tools/app-scaffold.d.ts.map +1 -1
- package/dist/mcp/tools/app-scaffold.js +300 -575
- package/dist/mcp/tools/app-scaffold.js.map +1 -1
- package/dist/mcp/tools/date.d.ts +5 -0
- package/dist/mcp/tools/date.d.ts.map +1 -0
- package/dist/mcp/tools/date.js +23 -0
- package/dist/mcp/tools/date.js.map +1 -0
- package/dist/mcp/tools/discussion.d.ts.map +1 -1
- package/dist/mcp/tools/discussion.js +17 -9
- package/dist/mcp/tools/discussion.js.map +1 -1
- package/dist/mcp/tools/index.d.ts.map +1 -1
- package/dist/mcp/tools/index.js +2 -0
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/mcp/tools/insight.d.ts.map +1 -1
- package/dist/mcp/tools/insight.js +13 -19
- package/dist/mcp/tools/insight.js.map +1 -1
- package/dist/mcp/tools/workflow.d.ts +1 -0
- package/dist/mcp/tools/workflow.d.ts.map +1 -1
- package/dist/mcp/tools/workflow.js +293 -46
- package/dist/mcp/tools/workflow.js.map +1 -1
- package/dist/mcp/utils/data-transformers.d.ts +47 -10
- package/dist/mcp/utils/data-transformers.d.ts.map +1 -1
- package/dist/mcp/utils/data-transformers.js +12 -9
- package/dist/mcp/utils/data-transformers.js.map +1 -1
- package/dist/mcp/utils/types.d.ts +2 -0
- package/dist/mcp/utils/types.d.ts.map +1 -1
- package/dist/mcp/utils/types.js.map +1 -1
- package/dist/mcp/webhook-handler.d.ts.map +1 -1
- package/dist/mcp/webhook-handler.js +4 -1
- package/dist/mcp/webhook-handler.js.map +1 -1
- package/dist/mcp/workspace-cache.d.ts +8 -2
- package/dist/mcp/workspace-cache.d.ts.map +1 -1
- package/dist/mcp/workspace-cache.js +12 -8
- package/dist/mcp/workspace-cache.js.map +1 -1
- package/dist/plugins/vipunen/tools.d.ts +1 -0
- package/dist/plugins/vipunen/tools.d.ts.map +1 -1
- package/dist/plugins/vipunen/tools.js.map +1 -1
- 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
|
|
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
|
|
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
|
-
//
|
|
590
|
-
|
|
591
|
-
|
|
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": "${
|
|
771
|
+
responseText += ` "workflowId": "${exampleWorkflow}",\n`;
|
|
596
772
|
responseText += ` "name": "New Activity",\n`;
|
|
597
|
-
if (
|
|
598
|
-
responseText += ` "phaseId": "${
|
|
599
|
-
if (
|
|
600
|
-
responseText += ` "fields": { "${
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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:
|
|
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
|