@ema.co/mcp-toolkit 2026.1.26 → 2026.1.27-10
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.
Potentially problematic release.
This version of @ema.co/mcp-toolkit might be problematic. Click here for more details.
- package/dist/mcp/handlers/action/index.js +17 -20
- package/dist/mcp/handlers/data/index.js +75 -6
- package/dist/mcp/handlers/deprecation.js +50 -0
- package/dist/mcp/handlers/env/index.js +3 -3
- package/dist/mcp/handlers/knowledge/index.js +44 -237
- package/dist/mcp/handlers/persona/create.js +63 -18
- package/dist/mcp/handlers/persona/index.js +9 -10
- package/dist/mcp/handlers/persona/update.js +6 -3
- package/dist/mcp/handlers/reference/index.js +15 -2
- package/dist/mcp/handlers/sync/index.js +3 -18
- package/dist/mcp/handlers/workflow/analyze.js +53 -105
- package/dist/mcp/handlers/workflow/deploy.js +160 -1
- package/dist/mcp/handlers/workflow/generate.js +8 -28
- package/dist/mcp/handlers/workflow/index.js +258 -85
- package/dist/mcp/handlers/workflow/modify.js +69 -26
- package/dist/mcp/handlers/workflow/optimize.js +22 -108
- package/dist/mcp/handlers/workflow/utils.js +0 -102
- package/dist/mcp/handlers-consolidated.js +15 -38
- package/dist/mcp/prompts.js +82 -44
- package/dist/mcp/resources.js +335 -3
- package/dist/mcp/server.js +242 -457
- package/dist/mcp/tools.js +44 -61
- package/dist/sdk/action-schema-parser.js +11 -5
- package/dist/sdk/client.js +81 -25
- package/dist/sdk/ema-client.js +11 -0
- package/dist/sdk/generated/deprecated-actions.js +171 -0
- package/dist/sdk/guidance.js +58 -35
- package/dist/sdk/index.js +8 -7
- package/dist/sdk/knowledge.js +216 -1932
- package/dist/sdk/quality-gates.js +60 -336
- package/dist/sdk/validation-rules.js +33 -0
- package/dist/sdk/workflow-fixer.js +29 -360
- package/dist/sdk/workflow-generator.js +2 -1
- package/dist/sdk/workflow-intent.js +43 -3
- package/dist/sdk/workflow-transformer.js +0 -342
- package/docs/dashboard-operations.md +35 -0
- package/docs/ema-user-guide.md +66 -0
- package/docs/mcp-tools-guide.md +74 -45
- package/package.json +2 -2
- package/dist/mcp/handlers/persona/analyze.js +0 -275
- package/dist/mcp/handlers/persona/compare.js +0 -32
- package/dist/mcp/handlers/workflow/compile.js +0 -39
- package/docs/DEBUG-ANALYSIS-unused-category-type-mismatch.md +0 -481
- package/docs/TODO-fix-analyzer-and-modify.md +0 -182
- package/resources/action-schema.json +0 -5678
|
@@ -1,379 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Workflow Fixer (SDK)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Call autoFixWorkflow() with just a workflow_def for one-call fixing.
|
|
4
|
+
* DEPRECATED: This module used detectWorkflowIssues which has been removed.
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
* -
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
11
|
-
* - Multiple responder detection
|
|
12
|
-
* - Ungated responder detection
|
|
6
|
+
* The LLM should analyze workflows using rules from:
|
|
7
|
+
* - ema://rules/anti-patterns
|
|
8
|
+
* - ema://rules/input-sources
|
|
9
|
+
* - ema://rules/optimizations
|
|
13
10
|
*
|
|
14
|
-
*
|
|
11
|
+
* And then make structured modifications via:
|
|
12
|
+
* - workflow(mode="deploy", workflow_def={...})
|
|
13
|
+
* - persona(mode="update", operations=[...])
|
|
15
14
|
*
|
|
16
|
-
*
|
|
17
|
-
* context needed. Best for SDK consumers who want autonomous fixing.
|
|
18
|
-
*
|
|
19
|
-
* - applyWorkflowFixes() (MCP): Takes pre-detected issues AND persona for
|
|
20
|
-
* Voice AI specific fixes (results mapping, HITL paths). Used by MCP handlers.
|
|
21
|
-
*
|
|
22
|
-
* These are COMPLEMENTARY, not duplicates. Use autoFixWorkflow() for SDK usage,
|
|
23
|
-
* use applyWorkflowFixes() when you have persona context and pre-analyzed issues.
|
|
15
|
+
* This file is kept for backwards compatibility with minimal functionality.
|
|
24
16
|
*/
|
|
25
|
-
import { detectWorkflowIssues } from "./knowledge.js";
|
|
26
|
-
import { detectMultipleResponders } from "./workflow-execution-analyzer.js";
|
|
27
17
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
28
|
-
//
|
|
18
|
+
// DEPRECATED FUNCTIONS (no-ops)
|
|
29
19
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
30
20
|
/**
|
|
31
|
-
*
|
|
32
|
-
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
const orphanIssues = issues.filter(i => i.type === 'orphan');
|
|
36
|
-
if (orphanIssues.length === 0)
|
|
37
|
-
return { def: workflowDef, fixes };
|
|
38
|
-
// Get actions array (Ema format)
|
|
39
|
-
const actions = (workflowDef.actions || workflowDef.nodes || []);
|
|
40
|
-
const nodesToRemove = new Set();
|
|
41
|
-
for (const issue of orphanIssues) {
|
|
42
|
-
const nodeName = issue.node;
|
|
43
|
-
if (!nodeName)
|
|
44
|
-
continue;
|
|
45
|
-
if (protectedNodes.has(nodeName)) {
|
|
46
|
-
fixes.push({
|
|
47
|
-
applied: false,
|
|
48
|
-
issueType: 'orphan',
|
|
49
|
-
nodeName,
|
|
50
|
-
description: `Skipped - node used by another fix`,
|
|
51
|
-
});
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
nodesToRemove.add(nodeName);
|
|
55
|
-
fixes.push({
|
|
56
|
-
applied: true,
|
|
57
|
-
issueType: 'orphan',
|
|
58
|
-
nodeName,
|
|
59
|
-
description: `Removed orphan node "${nodeName}"`,
|
|
60
|
-
change: 'removed',
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
const filteredActions = actions.filter((a) => {
|
|
64
|
-
const action = a;
|
|
65
|
-
return !nodesToRemove.has(action.name || '');
|
|
66
|
-
});
|
|
67
|
-
const key = workflowDef.actions ? 'actions' : 'nodes';
|
|
68
|
-
return {
|
|
69
|
-
def: { ...workflowDef, [key]: filteredActions },
|
|
70
|
-
fixes,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Generate a unique node ID with random suffix
|
|
75
|
-
*/
|
|
76
|
-
function generateNodeId(prefix) {
|
|
77
|
-
const suffix = Math.random().toString(36).substring(2, 8);
|
|
78
|
-
return `${prefix}_${suffix}`;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Create a convert_to_text action node
|
|
82
|
-
* This bridges EXTRACTION_COLUMN → TEXT_WITH_SOURCES
|
|
83
|
-
*/
|
|
84
|
-
function createConvertToTextNode(nodeId, sourceActionName, sourceOutput, displayName) {
|
|
85
|
-
return {
|
|
86
|
-
name: nodeId,
|
|
87
|
-
action: {
|
|
88
|
-
name: { namespaces: ['actions', 'emainternal'], name: 'convert_to_text' },
|
|
89
|
-
version: 'v0',
|
|
90
|
-
},
|
|
91
|
-
inputs: {
|
|
92
|
-
named_inputs: {
|
|
93
|
-
inline: {
|
|
94
|
-
array: {
|
|
95
|
-
values: [{
|
|
96
|
-
named: {
|
|
97
|
-
name: 'email_address',
|
|
98
|
-
value: {
|
|
99
|
-
actionOutput: {
|
|
100
|
-
actionName: sourceActionName,
|
|
101
|
-
output: sourceOutput,
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
description: 'Email address from entity extraction',
|
|
105
|
-
},
|
|
106
|
-
}],
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
displaySettings: {
|
|
112
|
-
displayName,
|
|
113
|
-
description: 'Convert extraction output to text for email',
|
|
114
|
-
coordinates: { x: 600, y: 300 },
|
|
115
|
-
showConfig: 0,
|
|
116
|
-
},
|
|
117
|
-
typeArguments: {},
|
|
118
|
-
tools: [],
|
|
119
|
-
disableHumanInteraction: true,
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Find email column name from entity_extraction node configuration
|
|
124
|
-
*/
|
|
125
|
-
function findEmailColumnName(extractionNode) {
|
|
126
|
-
const inputs = extractionNode.inputs;
|
|
127
|
-
if (!inputs)
|
|
128
|
-
return undefined;
|
|
129
|
-
const extractionColumnsInput = inputs.extraction_columns;
|
|
130
|
-
const columns = extractionColumnsInput?.inline?.array?.values || [];
|
|
131
|
-
for (const col of columns) {
|
|
132
|
-
const name = col.wellKnown?.extractionColumn?.name?.toLowerCase() || '';
|
|
133
|
-
if (name.includes('email') || name.includes('mail')) {
|
|
134
|
-
return col.wellKnown?.extractionColumn?.name;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return undefined;
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Fix wrong email recipient sources by inserting convert_to_text nodes
|
|
21
|
+
* DEPRECATED: Auto-fix workflow issues.
|
|
22
|
+
*
|
|
23
|
+
* This function now returns the workflow unchanged with a deprecation warning.
|
|
24
|
+
* Use LLM analysis with ema://rules/* instead.
|
|
141
25
|
*/
|
|
142
|
-
function
|
|
143
|
-
const fixes = [];
|
|
144
|
-
const usedNodes = new Set();
|
|
145
|
-
const wrongInputIssues = issues.filter(i => i.type === 'wrong_input_source');
|
|
146
|
-
if (wrongInputIssues.length === 0)
|
|
147
|
-
return { def: workflowDef, fixes, usedNodes };
|
|
148
|
-
const actionsSource = workflowDef.actions || workflowDef.nodes || [];
|
|
149
|
-
const actions = (Array.isArray(actionsSource) ? [...actionsSource] : []);
|
|
150
|
-
const newNodes = [];
|
|
151
|
-
// Find entity extraction nodes
|
|
152
|
-
const entityExtractionNodes = actions.filter((a) => {
|
|
153
|
-
const action = a;
|
|
154
|
-
return action.action?.name?.name === 'entity_extraction' ||
|
|
155
|
-
action.action?.name?.name === 'entity_extraction_with_documents';
|
|
156
|
-
});
|
|
157
|
-
const entityExtractionNodeNames = entityExtractionNodes
|
|
158
|
-
.map((a) => a.name)
|
|
159
|
-
.filter(Boolean);
|
|
160
|
-
if (entityExtractionNodeNames.length === 0) {
|
|
161
|
-
fixes.push({
|
|
162
|
-
applied: false,
|
|
163
|
-
issueType: 'wrong_input_source',
|
|
164
|
-
nodeName: 'N/A',
|
|
165
|
-
description: `No entity_extraction node found to rewire email to`,
|
|
166
|
-
});
|
|
167
|
-
return { def: workflowDef, fixes, usedNodes };
|
|
168
|
-
}
|
|
169
|
-
// Track which convert_to_text nodes we've created (to reuse for same source)
|
|
170
|
-
const converterNodes = new Map(); // "source.output" -> converterId
|
|
171
|
-
const updatedActions = actions.map((a) => {
|
|
172
|
-
const action = a;
|
|
173
|
-
const nodeIssue = wrongInputIssues.find(i => i.node === action.name);
|
|
174
|
-
if (!nodeIssue)
|
|
175
|
-
return a;
|
|
176
|
-
const isEmailNode = ['send_email_agent', 'send_communications_handler'].includes(action.action?.name?.name || '');
|
|
177
|
-
if (!isEmailNode)
|
|
178
|
-
return a;
|
|
179
|
-
const inputs = { ...action.inputs };
|
|
180
|
-
let anyFixed = false;
|
|
181
|
-
for (const [inputName, binding] of Object.entries(inputs)) {
|
|
182
|
-
if (!['to_email', 'email_to', 'recipient'].includes(inputName.toLowerCase()))
|
|
183
|
-
continue;
|
|
184
|
-
const b = binding;
|
|
185
|
-
if (!b.actionOutput?.actionName)
|
|
186
|
-
continue;
|
|
187
|
-
const targetExtractionName = entityExtractionNodeNames[0];
|
|
188
|
-
const targetExtractionNode = entityExtractionNodes.find((n) => n.name === targetExtractionName);
|
|
189
|
-
// Find the email column name from the extraction node config
|
|
190
|
-
const emailColumnName = findEmailColumnName(targetExtractionNode) || 'Email';
|
|
191
|
-
// Create or reuse a convert_to_text node
|
|
192
|
-
const converterKey = `${targetExtractionName}.${emailColumnName}`;
|
|
193
|
-
let converterId = converterNodes.get(converterKey);
|
|
194
|
-
if (!converterId) {
|
|
195
|
-
converterId = generateNodeId('convert_email');
|
|
196
|
-
converterNodes.set(converterKey, converterId);
|
|
197
|
-
// Create the new convert_to_text node
|
|
198
|
-
const converterNode = createConvertToTextNode(converterId, targetExtractionName, emailColumnName, `Convert ${emailColumnName} to Text`);
|
|
199
|
-
newNodes.push(converterNode);
|
|
200
|
-
}
|
|
201
|
-
// Rewire the email input to use the converter's output
|
|
202
|
-
inputs[inputName] = {
|
|
203
|
-
actionOutput: {
|
|
204
|
-
actionName: converterId,
|
|
205
|
-
output: 'text_with_sources',
|
|
206
|
-
},
|
|
207
|
-
};
|
|
208
|
-
usedNodes.add(targetExtractionName);
|
|
209
|
-
anyFixed = true;
|
|
210
|
-
fixes.push({
|
|
211
|
-
applied: true,
|
|
212
|
-
issueType: 'wrong_input_source',
|
|
213
|
-
nodeName: action.name || '',
|
|
214
|
-
description: `Inserted convert_to_text node and rewired ${inputName}`,
|
|
215
|
-
change: `${targetExtractionName}.${emailColumnName} → ${converterId} → ${action.name}.${inputName}`,
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
if (!anyFixed && Object.keys(inputs).length > 0) {
|
|
219
|
-
// Only report if we couldn't find any email inputs to fix
|
|
220
|
-
const emailInputs = Object.keys(inputs).filter(k => ['to_email', 'email_to', 'recipient'].includes(k.toLowerCase()));
|
|
221
|
-
if (emailInputs.length === 0) {
|
|
222
|
-
fixes.push({
|
|
223
|
-
applied: false,
|
|
224
|
-
issueType: 'wrong_input_source',
|
|
225
|
-
nodeName: action.name || '',
|
|
226
|
-
description: `No standard email input field found to rewire`,
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
return { ...action, inputs };
|
|
231
|
-
});
|
|
232
|
-
// Add the new converter nodes to the workflow
|
|
233
|
-
const allActions = [...updatedActions, ...newNodes];
|
|
234
|
-
const key = workflowDef.actions ? 'actions' : 'nodes';
|
|
26
|
+
export function autoFixWorkflow(workflowDef) {
|
|
235
27
|
return {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
28
|
+
workflowDef: workflowDef,
|
|
29
|
+
fixesApplied: [],
|
|
30
|
+
success: true,
|
|
31
|
+
warnings: [
|
|
32
|
+
"DEPRECATED: autoFixWorkflow() no longer auto-fixes issues.",
|
|
33
|
+
"Use LLM analysis with ema://rules/anti-patterns instead.",
|
|
34
|
+
],
|
|
239
35
|
};
|
|
240
36
|
}
|
|
241
37
|
/**
|
|
242
|
-
*
|
|
243
|
-
|
|
244
|
-
function
|
|
245
|
-
|
|
246
|
-
const fallbackIssues = issues.filter(i => i.type === 'missing_fallback');
|
|
247
|
-
if (fallbackIssues.length === 0)
|
|
248
|
-
return { def: workflowDef, fixes };
|
|
249
|
-
const actions = (workflowDef.actions || workflowDef.nodes || []);
|
|
250
|
-
for (const issue of fallbackIssues) {
|
|
251
|
-
const categorizer = actions.find((a) => a.name === issue.node);
|
|
252
|
-
if (!categorizer?.action?.typeArguments?.categories)
|
|
253
|
-
continue;
|
|
254
|
-
const categories = categorizer.action.typeArguments.categories;
|
|
255
|
-
if (categories.some(c => c.toLowerCase() === 'fallback' || c.toLowerCase() === 'other')) {
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
categorizer.action.typeArguments.categories = [...categories, 'Fallback'];
|
|
259
|
-
fixes.push({
|
|
260
|
-
applied: true,
|
|
261
|
-
issueType: 'missing_fallback',
|
|
262
|
-
nodeName: issue.node || '',
|
|
263
|
-
description: `Added "Fallback" category`,
|
|
264
|
-
change: `categories += "Fallback"`,
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
return { def: workflowDef, fixes };
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* NEW: Fix ungated responders by adding runIf conditions
|
|
38
|
+
* DEPRECATED: Suggest fixes for workflow issues.
|
|
39
|
+
*
|
|
40
|
+
* This function now returns empty results with a deprecation warning.
|
|
41
|
+
* Use LLM analysis with ema://rules/* instead.
|
|
271
42
|
*/
|
|
272
|
-
function
|
|
273
|
-
const fixes = [];
|
|
274
|
-
const ungatedIssues = multiResponderIssues.filter(i => i.type === 'ungated_responder');
|
|
275
|
-
if (ungatedIssues.length === 0)
|
|
276
|
-
return { def: workflowDef, fixes };
|
|
277
|
-
const actions = (workflowDef.actions || workflowDef.nodes || []);
|
|
278
|
-
// Find categorizer to use for gating
|
|
279
|
-
const categorizer = actions.find((a) => {
|
|
280
|
-
const action = a;
|
|
281
|
-
return action.action?.name?.name === 'chat_categorizer';
|
|
282
|
-
});
|
|
283
|
-
if (!categorizer) {
|
|
284
|
-
fixes.push({
|
|
285
|
-
applied: false,
|
|
286
|
-
issueType: 'ungated_responder',
|
|
287
|
-
nodeName: 'N/A',
|
|
288
|
-
description: `No categorizer found to add gating condition from`,
|
|
289
|
-
});
|
|
290
|
-
return { def: workflowDef, fixes };
|
|
291
|
-
}
|
|
292
|
-
// For now, just report what SHOULD be done
|
|
293
|
-
for (const issue of ungatedIssues) {
|
|
294
|
-
for (const nodeId of issue.nodes) {
|
|
295
|
-
fixes.push({
|
|
296
|
-
applied: false, // Manual fix recommended
|
|
297
|
-
issueType: 'ungated_responder',
|
|
298
|
-
nodeName: nodeId,
|
|
299
|
-
description: `Should add runIf condition from "${categorizer.name}"`,
|
|
300
|
-
change: `Add: runIf: { enum: { enumType: "category", enumValue: "<specific_category>" } }`,
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
return { def: workflowDef, fixes };
|
|
305
|
-
}
|
|
306
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
307
|
-
// MAIN AUTO-FIX FUNCTION
|
|
308
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
309
|
-
export function autoFixWorkflow(workflowDef) {
|
|
310
|
-
const def = workflowDef;
|
|
311
|
-
const originalIssues = detectWorkflowIssues(workflowDef);
|
|
312
|
-
const multiResponderIssues = detectMultipleResponders(workflowDef);
|
|
313
|
-
const originalIssueCount = originalIssues.length + multiResponderIssues.length;
|
|
314
|
-
const allFixes = [];
|
|
315
|
-
const warnings = [];
|
|
316
|
-
let currentDef = { ...def };
|
|
317
|
-
// 1. Fix wrong input sources first (protects nodes)
|
|
318
|
-
const inputSourceResult = fixWrongInputSource(currentDef, originalIssues);
|
|
319
|
-
currentDef = inputSourceResult.def;
|
|
320
|
-
allFixes.push(...inputSourceResult.fixes);
|
|
321
|
-
const protectedNodes = inputSourceResult.usedNodes;
|
|
322
|
-
// 2. Fix missing fallback
|
|
323
|
-
const fallbackResult = fixMissingFallback(currentDef, originalIssues);
|
|
324
|
-
currentDef = fallbackResult.def;
|
|
325
|
-
allFixes.push(...fallbackResult.fixes);
|
|
326
|
-
// 3. Fix orphan nodes (respecting protected)
|
|
327
|
-
const orphanResult = fixOrphanNodes(currentDef, originalIssues, protectedNodes);
|
|
328
|
-
currentDef = orphanResult.def;
|
|
329
|
-
allFixes.push(...orphanResult.fixes);
|
|
330
|
-
// 4. NEW: Report ungated responder fixes needed
|
|
331
|
-
const ungatedResult = fixUngatedResponders(currentDef, multiResponderIssues);
|
|
332
|
-
const multipleResponderFixes = ungatedResult.fixes;
|
|
333
|
-
// Re-detect remaining issues
|
|
334
|
-
const remainingIssues = detectWorkflowIssues(currentDef);
|
|
335
|
-
const remainingMultiResponder = detectMultipleResponders(currentDef);
|
|
336
|
-
const remainingIssueCount = remainingIssues.length + remainingMultiResponder.length;
|
|
337
|
-
const appliedCount = allFixes.filter(f => f.applied).length;
|
|
338
|
-
if (multipleResponderFixes.length > 0) {
|
|
339
|
-
warnings.push(`${multipleResponderFixes.length} ungated responder(s) need manual gating to prevent duplicate responses`);
|
|
340
|
-
}
|
|
43
|
+
export function suggestFixes(_workflowDef) {
|
|
341
44
|
return {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
remainingIssueCount,
|
|
345
|
-
workflowDef: currentDef,
|
|
346
|
-
success: appliedCount > 0,
|
|
347
|
-
warnings,
|
|
348
|
-
multipleResponderFixes,
|
|
45
|
+
standardFixes: [],
|
|
46
|
+
multiResponderFixes: [],
|
|
349
47
|
};
|
|
350
48
|
}
|
|
351
|
-
/**
|
|
352
|
-
* Suggest fixes without applying (dry run)
|
|
353
|
-
*/
|
|
354
|
-
export function suggestFixes(workflowDef) {
|
|
355
|
-
const issues = detectWorkflowIssues(workflowDef);
|
|
356
|
-
const multiResponderIssues = detectMultipleResponders(workflowDef);
|
|
357
|
-
const standardFixes = [];
|
|
358
|
-
const multiResponderFixes = [];
|
|
359
|
-
for (const issue of issues) {
|
|
360
|
-
if (!issue.auto_fixable)
|
|
361
|
-
continue;
|
|
362
|
-
standardFixes.push({
|
|
363
|
-
applied: false,
|
|
364
|
-
issueType: issue.type,
|
|
365
|
-
nodeName: issue.node || 'N/A',
|
|
366
|
-
description: issue.reason || `Fix ${issue.type}`,
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
for (const issue of multiResponderIssues) {
|
|
370
|
-
multiResponderFixes.push({
|
|
371
|
-
applied: false,
|
|
372
|
-
issueType: issue.type,
|
|
373
|
-
nodeName: issue.nodes.join(', '),
|
|
374
|
-
description: issue.description,
|
|
375
|
-
change: issue.fixSuggestion,
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
return { standardFixes, multiResponderFixes };
|
|
379
|
-
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* 2. Construct nodes based on user requirements
|
|
10
10
|
* 3. Pass to compileWorkflow() to generate deployment-ready JSON
|
|
11
11
|
*/
|
|
12
|
+
import { PROJECT_TYPES } from "./knowledge.js";
|
|
12
13
|
/**
|
|
13
14
|
* FALLBACK VALUES - Used only when ActionRegistry is not available.
|
|
14
15
|
* These are based on API data as of 2026-01-16.
|
|
@@ -329,7 +330,7 @@ function buildInputBinding(binding) {
|
|
|
329
330
|
}
|
|
330
331
|
}
|
|
331
332
|
function buildProtoConfig(spec) {
|
|
332
|
-
const projectType = spec.personaType
|
|
333
|
+
const projectType = PROJECT_TYPES[spec.personaType] ?? PROJECT_TYPES.dashboard;
|
|
333
334
|
// Widget format expected by Ema API:
|
|
334
335
|
// { name: "widgetName", type: <type_id>, widgetName: { ...config... } }
|
|
335
336
|
// The config is stored under a key matching the widget's name
|
|
@@ -445,9 +445,14 @@ const ACTION_CHAIN_PATTERNS = [
|
|
|
445
445
|
},
|
|
446
446
|
];
|
|
447
447
|
/**
|
|
448
|
-
*
|
|
448
|
+
* Intent routing type compatibility - determines how outputs wire to inputs during intent processing.
|
|
449
|
+
*
|
|
450
|
+
* NOTE: This is SEPARATE from knowledge.ts TYPE_COMPATIBILITY which is user-facing documentation.
|
|
451
|
+
* This focuses on the "can_connect_to" and "use_named_inputs_for" routing logic.
|
|
452
|
+
*
|
|
453
|
+
* Canonical type documentation: src/sdk/knowledge.ts → TYPE_COMPATIBILITY
|
|
449
454
|
*/
|
|
450
|
-
export const
|
|
455
|
+
export const INTENT_TYPE_ROUTING = {
|
|
451
456
|
WELL_KNOWN_TYPE_CHAT_CONVERSATION: {
|
|
452
457
|
can_connect_to: ["conversation"],
|
|
453
458
|
use_named_inputs_for: [],
|
|
@@ -1312,6 +1317,37 @@ export function summarizeIntentConfidence(confidence) {
|
|
|
1312
1317
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1313
1318
|
// Intent to Spec Conversion
|
|
1314
1319
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1320
|
+
/**
|
|
1321
|
+
* Generate takeActionInstructions from tools for Voice AI.
|
|
1322
|
+
*
|
|
1323
|
+
* This populates the "Define what actions your AI Employee can perform" section
|
|
1324
|
+
* in the Voice persona config. Format follows the Ema Voice API spec.
|
|
1325
|
+
*/
|
|
1326
|
+
function generateTakeActionInstructions(tools) {
|
|
1327
|
+
if (!tools || tools.length === 0) {
|
|
1328
|
+
return "";
|
|
1329
|
+
}
|
|
1330
|
+
const cases = tools.map((tool, index) => {
|
|
1331
|
+
const caseNum = index + 1;
|
|
1332
|
+
const toolName = tool.display_name ?? tool.action ?? `Action ${caseNum}`;
|
|
1333
|
+
const triggerCondition = tool.trigger_condition ?? tool.description ?? "Perform this action when requested";
|
|
1334
|
+
// Extract required parameters from tool schema if available
|
|
1335
|
+
const requiredParams = tool.required_inputs ?? [];
|
|
1336
|
+
const paramsObj = requiredParams.length > 0
|
|
1337
|
+
? `{ ${requiredParams.map(p => `"${p}": ""`).join(", ")} }`
|
|
1338
|
+
: "{ }";
|
|
1339
|
+
return `</Case ${caseNum}>
|
|
1340
|
+
${toolName}
|
|
1341
|
+
|
|
1342
|
+
Trigger When: ${triggerCondition}
|
|
1343
|
+
|
|
1344
|
+
Intent for tool call: "User requests ${toolName.toLowerCase()}"
|
|
1345
|
+
|
|
1346
|
+
Required parameters: ${paramsObj}
|
|
1347
|
+
</Case ${caseNum}>`;
|
|
1348
|
+
});
|
|
1349
|
+
return cases.join("\n\n");
|
|
1350
|
+
}
|
|
1315
1351
|
export function intentToSpec(intent) {
|
|
1316
1352
|
const nodes = [];
|
|
1317
1353
|
const resultMappings = [];
|
|
@@ -1463,7 +1499,11 @@ export function intentToSpec(intent) {
|
|
|
1463
1499
|
welcomeMessage: intent.voice_config?.welcome_message,
|
|
1464
1500
|
identityAndPurpose: intent.voice_config?.identity ?? intent.description,
|
|
1465
1501
|
hangupInstructions: intent.voice_config?.hangup_instructions,
|
|
1466
|
-
|
|
1502
|
+
transferInstructions: intent.voice_config?.transfer_instructions,
|
|
1503
|
+
waitMessage: intent.voice_config?.wait_message,
|
|
1504
|
+
// Generate takeActionInstructions from tools if not explicitly provided
|
|
1505
|
+
takeActionInstructions: intent.voice_config?.take_action_instructions ??
|
|
1506
|
+
generateTakeActionInstructions(intent.tools),
|
|
1467
1507
|
} : undefined;
|
|
1468
1508
|
return {
|
|
1469
1509
|
name: intent.name,
|