@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
|
@@ -128,13 +128,48 @@ export async function handleCreate(args, client, getTemplateId) {
|
|
|
128
128
|
}
|
|
129
129
|
// For persona cloning, default include_data to true
|
|
130
130
|
const effectiveIncludeData = sourcePersonaId ? (includeData ?? true) : false;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
131
|
+
let result;
|
|
132
|
+
try {
|
|
133
|
+
result = await client.createAiEmployee({
|
|
134
|
+
name,
|
|
135
|
+
description: args.description,
|
|
136
|
+
template_id: templateId,
|
|
137
|
+
source_persona_id: sourcePersonaId,
|
|
138
|
+
clone_data: effectiveIncludeData,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
143
|
+
const personaType = args.type;
|
|
144
|
+
// Provide recovery guidance based on persona type
|
|
145
|
+
const recovery = {};
|
|
146
|
+
if (personaType === "voice" || templateId?.includes("voice") || templateId?.includes("001e")) {
|
|
147
|
+
recovery.option_1 = "Fetch gold template: ema://templates/voice-ai/workflow-def";
|
|
148
|
+
recovery.option_2 = "Fetch config template: ema://templates/voice-ai/proto-config";
|
|
149
|
+
recovery.option_3 = "See complete guide: ema://templates/voice-ai/complete";
|
|
150
|
+
}
|
|
151
|
+
else if (personaType === "chat") {
|
|
152
|
+
recovery.option_1 = "Use catalog(type='templates') to list available templates";
|
|
153
|
+
recovery.option_2 = "Try template(config='chat') for config structure";
|
|
154
|
+
}
|
|
155
|
+
else if (personaType === "dashboard") {
|
|
156
|
+
recovery.option_1 = "Use catalog(type='templates') to list available templates";
|
|
157
|
+
recovery.option_2 = "Try template(config='dashboard') for config structure";
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
recovery.option_1 = "Use catalog(type='templates') to list available templates";
|
|
161
|
+
recovery.option_2 = "Specify explicit template ID with from='<template-id>'";
|
|
162
|
+
}
|
|
163
|
+
recovery.fallback = "Fetch ema://templates/voice-ai/complete for step-by-step recovery guide";
|
|
164
|
+
return {
|
|
165
|
+
error: `create_ai_employee failed`,
|
|
166
|
+
details: errorMsg,
|
|
167
|
+
environment: client.env?.name || "unknown",
|
|
168
|
+
recovery,
|
|
169
|
+
_tip: "If template creation fails, use gold templates from ema://templates/{type}/workflow-def as fallback.",
|
|
170
|
+
_warning: "NEVER clone a random existing persona. Use gold templates instead.",
|
|
171
|
+
};
|
|
172
|
+
}
|
|
138
173
|
const newPersonaId = result.persona_id ?? result.id;
|
|
139
174
|
if (!newPersonaId) {
|
|
140
175
|
return { error: "Failed to create persona: no ID returned from API" };
|
|
@@ -145,21 +180,15 @@ export async function handleCreate(args, client, getTemplateId) {
|
|
|
145
180
|
let workflowError;
|
|
146
181
|
if (workflowDef && newPersonaId) {
|
|
147
182
|
try {
|
|
148
|
-
// Get the newly created persona to get its proto_config
|
|
183
|
+
// Get the newly created persona to get its proto_config
|
|
149
184
|
const newPersona = await client.getPersonaById(newPersonaId);
|
|
150
185
|
const protoConfig = (newPersona?.proto_config ?? {});
|
|
151
|
-
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
const existingWfName = existingWorkflow?.workflowName;
|
|
155
|
-
const workflowForDeploy = JSON.parse(JSON.stringify(workflowDef));
|
|
156
|
-
if (existingWfName?.name) {
|
|
157
|
-
// Set/overwrite the workflowName to match the existing one
|
|
158
|
-
workflowForDeploy.workflowName = existingWfName;
|
|
159
|
-
}
|
|
186
|
+
// NOTE: The SDK's updateAiEmployee() handles workflowName namespace automatically.
|
|
187
|
+
// It will copy from existing workflow if present, or generate a valid namespace if not.
|
|
188
|
+
// No need to manually manipulate workflowName here.
|
|
160
189
|
await client.updateAiEmployee({
|
|
161
190
|
persona_id: newPersonaId,
|
|
162
|
-
workflow:
|
|
191
|
+
workflow: workflowDef,
|
|
163
192
|
proto_config: protoConfig, // Required: API needs proto_config alongside workflow
|
|
164
193
|
});
|
|
165
194
|
workflowApplied = true;
|
|
@@ -192,6 +221,7 @@ export async function handleCreate(args, client, getTemplateId) {
|
|
|
192
221
|
sourcePersonaType,
|
|
193
222
|
dashboardCloneResult,
|
|
194
223
|
actionsError: validation.errors.join("; "),
|
|
224
|
+
createdFromTemplate: fromType === "template",
|
|
195
225
|
});
|
|
196
226
|
}
|
|
197
227
|
// Build execution context
|
|
@@ -214,6 +244,7 @@ export async function handleCreate(args, client, getTemplateId) {
|
|
|
214
244
|
sourcePersonaType,
|
|
215
245
|
// Don't include dashboardCloneResult - actions handle data operations
|
|
216
246
|
actionsResult,
|
|
247
|
+
createdFromTemplate: fromType === "template",
|
|
217
248
|
});
|
|
218
249
|
}
|
|
219
250
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -235,6 +266,7 @@ export async function handleCreate(args, client, getTemplateId) {
|
|
|
235
266
|
workflowError,
|
|
236
267
|
sourcePersonaType,
|
|
237
268
|
dashboardCloneResult,
|
|
269
|
+
createdFromTemplate: fromType === "template",
|
|
238
270
|
});
|
|
239
271
|
}
|
|
240
272
|
return buildCreateResult({
|
|
@@ -245,6 +277,7 @@ export async function handleCreate(args, client, getTemplateId) {
|
|
|
245
277
|
workflowError,
|
|
246
278
|
sourcePersonaType,
|
|
247
279
|
dashboardCloneResult,
|
|
280
|
+
createdFromTemplate: fromType === "template",
|
|
248
281
|
});
|
|
249
282
|
}
|
|
250
283
|
/**
|
|
@@ -270,6 +303,18 @@ function buildCreateResult(opts) {
|
|
|
270
303
|
if (opts.sourcePersonaType) {
|
|
271
304
|
result.source_persona_type = opts.sourcePersonaType;
|
|
272
305
|
}
|
|
306
|
+
// ── CRITICAL GUIDANCE: Template workflows are minimal starters ──
|
|
307
|
+
// This is where LLMs often go wrong - they create from template and think they're done
|
|
308
|
+
if (opts.createdFromTemplate && !opts.workflowApplied) {
|
|
309
|
+
result._warning = "PERSONA CREATED BUT WORKFLOW IS INCOMPLETE. Template workflows are minimal starters (just trigger→respond).";
|
|
310
|
+
result._required_next_steps = [
|
|
311
|
+
"1. BUILD WORKFLOW: Add intent categorization, search nodes, response handling",
|
|
312
|
+
`2. If uploading docs: Workflow MUST have search/v2 node or documents will NOT be used`,
|
|
313
|
+
`3. Get current workflow: workflow(mode='get', persona_id='${opts.newPersonaId}')`,
|
|
314
|
+
`4. Deploy complete workflow: workflow(mode='deploy', persona_id='${opts.newPersonaId}', workflow_def={...})`,
|
|
315
|
+
];
|
|
316
|
+
result._common_mistake = "Creating from template, uploading docs, and declaring 'done' WITHOUT building the workflow. The deploy will now BLOCK this pattern.";
|
|
317
|
+
}
|
|
273
318
|
if (opts.dashboardCloneResult) {
|
|
274
319
|
result.dashboard_data_clone = opts.dashboardCloneResult;
|
|
275
320
|
}
|
|
@@ -5,18 +5,20 @@
|
|
|
5
5
|
* instead of a giant switch statement. Each mode is in its own file
|
|
6
6
|
* for better testability and maintainability.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* - get: Fetch single persona
|
|
8
|
+
* Persona modes:
|
|
9
|
+
* - get: Fetch single persona (LLM then analyzes the data)
|
|
10
10
|
* - list: List all personas
|
|
11
11
|
* - templates: List available templates
|
|
12
|
-
* - compare: Compare two personas
|
|
13
12
|
* - sanitize: Sanitize persona data
|
|
14
13
|
* - update: Update persona config
|
|
15
14
|
* - delete: Delete persona (with confirmation)
|
|
16
15
|
* - create/clone: Create new persona from template or clone existing
|
|
17
|
-
* - analyze: Analyze persona for issues
|
|
18
16
|
* - intent: Direct Intent Architect invocation for qualification
|
|
19
17
|
*
|
|
18
|
+
* REMOVED (LLM does these):
|
|
19
|
+
* - analyze: LLM analyzes data from 'get'
|
|
20
|
+
* - compare: LLM compares data from two 'get' calls
|
|
21
|
+
*
|
|
20
22
|
* Version management modes extracted to version.ts:
|
|
21
23
|
* - snapshot/version_create, history/version_list, version_get
|
|
22
24
|
* - version_compare, restore/version_restore, version_policy
|
|
@@ -25,29 +27,27 @@
|
|
|
25
27
|
import { handleGet } from "./get.js";
|
|
26
28
|
import { handleList } from "./list.js";
|
|
27
29
|
import { handleTemplates } from "./templates.js";
|
|
28
|
-
import { handleCompare } from "./compare.js";
|
|
29
30
|
import { handleSanitize } from "./sanitize.js";
|
|
30
31
|
import { handleUpdate } from "./update.js";
|
|
31
32
|
import { handleDelete } from "./delete.js";
|
|
32
33
|
import { handleCreate } from "./create.js";
|
|
33
|
-
import { handleAnalyze } from "./analyze.js";
|
|
34
34
|
import { handleIntent } from "./intent.js";
|
|
35
35
|
/**
|
|
36
36
|
* Dispatch table for persona modes
|
|
37
37
|
*
|
|
38
38
|
* Note: create/clone handlers require getTemplateId callback as extra param
|
|
39
|
+
*
|
|
40
|
+
* REMOVED analyze/compare - LLM does analysis/comparison
|
|
39
41
|
*/
|
|
40
42
|
export const PERSONA_MODE_HANDLERS = {
|
|
41
43
|
get: handleGet,
|
|
42
44
|
list: handleList,
|
|
43
45
|
templates: handleTemplates,
|
|
44
|
-
compare: handleCompare,
|
|
45
46
|
sanitize: handleSanitize,
|
|
46
47
|
update: handleUpdate,
|
|
47
48
|
delete: handleDelete,
|
|
48
49
|
create: handleCreate,
|
|
49
50
|
clone: handleCreate, // Clone uses same handler as create
|
|
50
|
-
analyze: handleAnalyze,
|
|
51
51
|
intent: handleIntent,
|
|
52
52
|
};
|
|
53
53
|
/**
|
|
@@ -66,12 +66,11 @@ export function getPersonaModeHandler(mode) {
|
|
|
66
66
|
export { handleGet } from "./get.js";
|
|
67
67
|
export { handleList } from "./list.js";
|
|
68
68
|
export { handleTemplates } from "./templates.js";
|
|
69
|
-
export { handleCompare } from "./compare.js";
|
|
70
69
|
export { handleSanitize } from "./sanitize.js";
|
|
71
70
|
export { handleUpdate } from "./update.js";
|
|
72
71
|
export { handleDelete } from "./delete.js";
|
|
73
72
|
export { handleCreate } from "./create.js";
|
|
74
|
-
export { handleAnalyze } from "./analyze.js";
|
|
75
73
|
export { handleIntent } from "./intent.js";
|
|
74
|
+
// REMOVED: handleCompare, handleAnalyze - LLM does analysis/comparison
|
|
76
75
|
// Version management
|
|
77
76
|
export { handleVersion, isVersionMode } from "./version.js";
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* routes to handleWorkflow BEFORE reaching this handler. See handlePersona()
|
|
13
13
|
* in handlers-consolidated.ts.
|
|
14
14
|
*/
|
|
15
|
+
import { PROJECT_TYPES } from "../../../sdk/knowledge.js";
|
|
15
16
|
import { resolvePersona, validateWidgetsForApi } from "../utils.js";
|
|
16
17
|
/**
|
|
17
18
|
* Apply WorkflowSpec changes to an existing workflow_def.
|
|
@@ -174,7 +175,8 @@ function applySpecToWorkflow(spec, existingWorkflow) {
|
|
|
174
175
|
* Handle persona(mode="update") - update persona config or workflow
|
|
175
176
|
*
|
|
176
177
|
* @param args.id - Persona ID or name (required)
|
|
177
|
-
* @param args.
|
|
178
|
+
* @param args.config - Config changes with widgets (preferred, smart merged)
|
|
179
|
+
* @param args.proto_config - Alias for config (internal, for backwards compatibility)
|
|
178
180
|
* @param args.workflow - Optional raw workflow_def to set
|
|
179
181
|
* @param args.workflow_spec - Optional WorkflowSpec (agent-built) - will be compiled
|
|
180
182
|
* @param args.preview - If true, return changes without deploying (default: false)
|
|
@@ -199,10 +201,11 @@ export async function handleUpdate(args, client) {
|
|
|
199
201
|
: undefined;
|
|
200
202
|
// Determine persona type from projectSettings.projectType
|
|
201
203
|
const projectSettings = existingProtoConfig.projectSettings;
|
|
202
|
-
const isVoice = projectSettings?.projectType ===
|
|
204
|
+
const isVoice = projectSettings?.projectType === PROJECT_TYPES.voice;
|
|
203
205
|
// Build merged proto_config with smart widget-level merging
|
|
206
|
+
// Accept both 'config' (tool schema) and 'proto_config' (internal) for compatibility
|
|
204
207
|
let mergedProtoConfig = { ...existingProtoConfig };
|
|
205
|
-
const newProtoConfig = args.proto_config;
|
|
208
|
+
const newProtoConfig = (args.config ?? args.proto_config);
|
|
206
209
|
const updatedWidgets = [];
|
|
207
210
|
if (newProtoConfig) {
|
|
208
211
|
// If proto_config has widgets, do smart widget-level merge
|
|
@@ -34,10 +34,22 @@ export async function handleReference(args, context) {
|
|
|
34
34
|
if (type === "actions") {
|
|
35
35
|
const client = context?.client;
|
|
36
36
|
const id = args.id;
|
|
37
|
-
// Categories list
|
|
37
|
+
// Categories list - API-first with catalog fallback
|
|
38
38
|
if (args.categories) {
|
|
39
|
+
if (client) {
|
|
40
|
+
try {
|
|
41
|
+
const actions = await client.listActions();
|
|
42
|
+
const apiCategories = [...new Set(actions.map(a => a.category).filter(Boolean))];
|
|
43
|
+
if (apiCategories.length > 0) {
|
|
44
|
+
return { categories: apiCategories, count: apiCategories.length, source: "api" };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Fallback to catalog
|
|
49
|
+
}
|
|
50
|
+
}
|
|
39
51
|
const categories = [...new Set(Object.values(AGENT_CATALOG).map(a => a.category))];
|
|
40
|
-
return { categories, count: categories.length };
|
|
52
|
+
return { categories, count: categories.length, source: "catalog" };
|
|
41
53
|
}
|
|
42
54
|
// Suggest for use case
|
|
43
55
|
if (args.suggest) {
|
|
@@ -114,6 +126,7 @@ export async function handleReference(args, context) {
|
|
|
114
126
|
category: a.category,
|
|
115
127
|
enabled: a.enabled,
|
|
116
128
|
})),
|
|
129
|
+
source: "api",
|
|
117
130
|
};
|
|
118
131
|
}
|
|
119
132
|
catch {
|
|
@@ -4,28 +4,13 @@
|
|
|
4
4
|
* Handles cross-environment synchronization of personas.
|
|
5
5
|
*/
|
|
6
6
|
import { resolvePersona } from "../utils.js";
|
|
7
|
-
|
|
8
|
-
const DEPRECATED_PARAMS = {
|
|
9
|
-
identifier: { newName: "id", message: "'identifier' is deprecated, use 'id' instead (will be removed in v2.0.0)" },
|
|
10
|
-
};
|
|
11
|
-
function checkDeprecatedParams(args) {
|
|
12
|
-
const warnings = [];
|
|
13
|
-
for (const [oldName, info] of Object.entries(DEPRECATED_PARAMS)) {
|
|
14
|
-
if (args[oldName] !== undefined) {
|
|
15
|
-
warnings.push(info.message);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
return warnings;
|
|
19
|
-
}
|
|
7
|
+
import { handleDeprecatedParams } from "../deprecation.js";
|
|
20
8
|
/**
|
|
21
9
|
* Handle sync tool requests - sync personas across environments
|
|
22
10
|
*/
|
|
23
11
|
export async function handleSync(args, createClient, getSyncOptions) {
|
|
24
|
-
// Check for deprecated params
|
|
25
|
-
|
|
26
|
-
for (const warning of deprecationWarnings) {
|
|
27
|
-
console.warn(`[sync] Deprecation: ${warning}`);
|
|
28
|
-
}
|
|
12
|
+
// Check for deprecated params
|
|
13
|
+
handleDeprecatedParams(args, "sync");
|
|
29
14
|
const mode = args.mode || "run";
|
|
30
15
|
const id = args.id;
|
|
31
16
|
const identifier = args.identifier; // deprecated alias for 'id'
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Workflow Analyze Handler
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Returns workflow DATA for LLM to analyze using rules from ema://rules/*.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* -
|
|
6
|
+
* IMPORTANT: This handler does NOT pre-compute issues or fixes.
|
|
7
|
+
* The LLM applies ANTI_PATTERNS, INPUT_SOURCE_RULES, and OPTIMIZATION_RULES
|
|
8
|
+
* to reason about the workflow.
|
|
9
|
+
*
|
|
10
|
+
* Returns:
|
|
11
|
+
* - workflow_def: The raw workflow structure
|
|
12
|
+
* - connections: Type information for each edge
|
|
13
|
+
* - metrics: Basic counts (nodes, edges, etc.)
|
|
14
|
+
* - rules_hint: Pointer to ema://rules/* for analysis guidance
|
|
12
15
|
*/
|
|
13
|
-
import {
|
|
14
|
-
import { autoFixWorkflow } from "../../../sdk/workflow-fixer.js";
|
|
15
|
-
import { analyzeExecutionFlow, generateASCIIFlow } from "../../../sdk/workflow-execution-analyzer.js";
|
|
16
|
+
import { validateWorkflowConnections, parseWorkflowDef } from "../../../sdk/knowledge.js";
|
|
16
17
|
/**
|
|
17
|
-
* Calculate workflow metrics
|
|
18
|
+
* Calculate workflow metrics (pure data, no analysis)
|
|
18
19
|
*/
|
|
19
20
|
function calculateMetrics(workflow) {
|
|
20
21
|
const actions = workflow.actions;
|
|
@@ -36,15 +37,24 @@ function calculateMetrics(workflow) {
|
|
|
36
37
|
// Get trigger type
|
|
37
38
|
const trigger = workflow.trigger;
|
|
38
39
|
const triggerType = trigger?.trigger_type;
|
|
40
|
+
// Check for categorizer
|
|
41
|
+
const hasCategorizer = actions.some(a => {
|
|
42
|
+
const name = a.name?.toLowerCase() ?? "";
|
|
43
|
+
const action = a.action?.name?.name;
|
|
44
|
+
return name.includes("categorizer") || action?.includes("categorizer");
|
|
45
|
+
});
|
|
39
46
|
return {
|
|
40
47
|
node_count: actions.length,
|
|
41
48
|
connection_count: connectionCount,
|
|
42
49
|
has_hitl: hasHitl,
|
|
50
|
+
has_categorizer: hasCategorizer,
|
|
43
51
|
trigger_type: triggerType,
|
|
44
52
|
};
|
|
45
53
|
}
|
|
46
54
|
/**
|
|
47
55
|
* Handle workflow analyze mode
|
|
56
|
+
*
|
|
57
|
+
* Returns DATA for LLM to analyze - does NOT pre-compute issues.
|
|
48
58
|
*/
|
|
49
59
|
export async function handleWorkflowAnalyze(args, client) {
|
|
50
60
|
const personaId = args.persona_id;
|
|
@@ -62,110 +72,48 @@ export async function handleWorkflowAnalyze(args, client) {
|
|
|
62
72
|
if (!workflow) {
|
|
63
73
|
return { error: "No workflow to analyze. Provide workflow_def or persona_id." };
|
|
64
74
|
}
|
|
65
|
-
//
|
|
66
|
-
const
|
|
75
|
+
// Parse workflow to simpler structure
|
|
76
|
+
const nodes = parseWorkflowDef(workflow);
|
|
77
|
+
// Get connection type information
|
|
78
|
+
const connections = validateWorkflowConnections(workflow);
|
|
79
|
+
// Calculate metrics
|
|
80
|
+
const metrics = calculateMetrics(workflow);
|
|
67
81
|
const result = {
|
|
68
82
|
mode: "analyze",
|
|
69
83
|
persona_id: personaId,
|
|
70
84
|
persona_name: persona?.name,
|
|
71
|
-
|
|
85
|
+
// RAW DATA for LLM to analyze
|
|
86
|
+
workflow_def: workflow,
|
|
87
|
+
node_count: nodes.length,
|
|
88
|
+
nodes: nodes.map(n => ({
|
|
89
|
+
id: n.id,
|
|
90
|
+
action: n.action_name,
|
|
91
|
+
display_name: n.display_name,
|
|
92
|
+
inputs: n.incoming_edges?.map(e => `${e.source_node_id}.${e.source_output}`),
|
|
93
|
+
})),
|
|
94
|
+
// Type information for connections
|
|
95
|
+
connections: connections.map(c => ({
|
|
96
|
+
edge: c.edge_id,
|
|
97
|
+
source_type: c.source_type,
|
|
98
|
+
target_type: c.target_type,
|
|
99
|
+
compatible: c.compatible,
|
|
100
|
+
note: c.note,
|
|
101
|
+
})),
|
|
102
|
+
// Basic metrics
|
|
103
|
+
metrics,
|
|
104
|
+
// Tell LLM where to find analysis rules
|
|
105
|
+
_next_steps: [
|
|
106
|
+
"1. Fetch ema://rules/anti-patterns",
|
|
107
|
+
"2. Fetch ema://rules/structural-invariants",
|
|
108
|
+
"3. Check each node against the rules",
|
|
109
|
+
"4. Report issues YOU find (MCP does not pre-compute)",
|
|
110
|
+
],
|
|
72
111
|
};
|
|
73
|
-
// Issues and fixes
|
|
74
|
-
if (include.includes("issues") || include.includes("fixes")) {
|
|
75
|
-
const issues = detectWorkflowIssues(workflow);
|
|
76
|
-
if (include.includes("issues")) {
|
|
77
|
-
result.issues = issues;
|
|
78
|
-
}
|
|
79
|
-
if (include.includes("fixes")) {
|
|
80
|
-
result.fixes = suggestWorkflowFixes(issues);
|
|
81
|
-
}
|
|
82
|
-
result.issue_summary = {
|
|
83
|
-
total: issues.length,
|
|
84
|
-
critical: issues.filter((i) => i.severity === "critical").length,
|
|
85
|
-
warning: issues.filter((i) => i.severity === "warning").length,
|
|
86
|
-
info: issues.filter((i) => i.severity === "info").length,
|
|
87
|
-
};
|
|
88
|
-
result.validation_passed = issues.filter((i) => i.severity === "critical").length === 0;
|
|
89
|
-
}
|
|
90
|
-
// Connection validation
|
|
91
|
-
if (include.includes("connections")) {
|
|
92
|
-
result.connections = validateWorkflowConnections(workflow);
|
|
93
|
-
}
|
|
94
|
-
// Metrics
|
|
95
|
-
if (include.includes("metrics")) {
|
|
96
|
-
result.metrics = calculateMetrics(workflow);
|
|
97
|
-
}
|
|
98
|
-
// Execution flow analysis
|
|
99
|
-
if (include.includes("execution_flow")) {
|
|
100
|
-
const execAnalysis = analyzeExecutionFlow(workflow);
|
|
101
|
-
result.execution_flow = {
|
|
102
|
-
summary: execAnalysis.summary,
|
|
103
|
-
loops: execAnalysis.loops,
|
|
104
|
-
multiple_responder_issues: execAnalysis.multipleResponderIssues,
|
|
105
|
-
redundant_classifiers: execAnalysis.redundantClassifiers,
|
|
106
|
-
data_flow_issues: execAnalysis.dataFlowIssues,
|
|
107
|
-
dead_code_paths: execAnalysis.deadCodePaths,
|
|
108
|
-
};
|
|
109
|
-
// Include ASCII visualization if requested
|
|
110
|
-
if (args.visualize) {
|
|
111
|
-
result.execution_flow_ascii = generateASCIIFlow(execAnalysis);
|
|
112
|
-
}
|
|
113
|
-
// Add specific warnings for triple response risk
|
|
114
|
-
if (execAnalysis.summary.mayRepeatResponses) {
|
|
115
|
-
result.triple_response_warning = "⚠️ This workflow may cause duplicate/triple responses due to ungated parallel responders";
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
112
|
if (persona) {
|
|
119
113
|
result.persona = {
|
|
120
114
|
id: persona.id,
|
|
121
115
|
name: persona.name
|
|
122
116
|
};
|
|
123
117
|
}
|
|
124
|
-
// Calculate optimization guidance
|
|
125
|
-
const issues = result.issues;
|
|
126
|
-
const criticalCount = issues?.filter(i => i.severity === "critical").length ?? 0;
|
|
127
|
-
const warningCount = issues?.filter(i => i.severity === "warning").length ?? 0;
|
|
128
|
-
// AUTO-FIX: If fix=true, apply fixes and deploy
|
|
129
|
-
if (args.fix && personaId && persona && workflow) {
|
|
130
|
-
const fixResult = autoFixWorkflow(workflow);
|
|
131
|
-
result.fixes_applied = fixResult.fixesApplied;
|
|
132
|
-
result.fixes_warnings = fixResult.warnings;
|
|
133
|
-
if (fixResult.fixesApplied.length > 0 && fixResult.success) {
|
|
134
|
-
// Deploy the fixed workflow
|
|
135
|
-
try {
|
|
136
|
-
const fixedWorkflow = fixResult.workflowDef;
|
|
137
|
-
await client.updateAiEmployee({
|
|
138
|
-
persona_id: personaId,
|
|
139
|
-
workflow: fixedWorkflow,
|
|
140
|
-
proto_config: persona.proto_config,
|
|
141
|
-
});
|
|
142
|
-
result.fix_status = "deployed";
|
|
143
|
-
result.fixes_deployed = fixResult.fixesApplied.length;
|
|
144
|
-
result.status = `✅ Applied ${fixResult.fixesApplied.length} fixes and deployed`;
|
|
145
|
-
}
|
|
146
|
-
catch (deployErr) {
|
|
147
|
-
result.fix_status = "failed";
|
|
148
|
-
result.fix_error = deployErr instanceof Error ? deployErr.message : String(deployErr);
|
|
149
|
-
result.status = `⚠️ Fixes applied but deploy failed: ${result.fix_error}`;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
else if (fixResult.fixesApplied.length === 0) {
|
|
153
|
-
result.fix_status = "no_fixes_needed";
|
|
154
|
-
result.status = "✅ No auto-fixable issues found";
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
result.fix_status = "failed";
|
|
158
|
-
result.status = "⚠️ Fix generation failed";
|
|
159
|
-
}
|
|
160
|
-
return result;
|
|
161
|
-
}
|
|
162
|
-
// Provide optimization suggestion
|
|
163
|
-
if (criticalCount > 0 || warningCount > 0) {
|
|
164
|
-
result.optimization_suggestion = `This workflow has ${criticalCount} critical and ${warningCount} warning issues. ` +
|
|
165
|
-
`Use workflow(persona_id="${personaId ?? 'ID'}", optimize=true) to auto-fix.`;
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
result.status = "✅ Workflow is healthy - no issues detected";
|
|
169
|
-
}
|
|
170
118
|
return result;
|
|
171
119
|
}
|