@ema.co/mcp-toolkit 2026.1.26 → 2026.1.27-1

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.

Files changed (44) hide show
  1. package/dist/mcp/handlers/action/index.js +17 -20
  2. package/dist/mcp/handlers/data/index.js +72 -6
  3. package/dist/mcp/handlers/deprecation.js +50 -0
  4. package/dist/mcp/handlers/env/index.js +3 -3
  5. package/dist/mcp/handlers/knowledge/index.js +44 -237
  6. package/dist/mcp/handlers/persona/create.js +47 -18
  7. package/dist/mcp/handlers/persona/index.js +9 -10
  8. package/dist/mcp/handlers/persona/update.js +4 -2
  9. package/dist/mcp/handlers/reference/index.js +15 -2
  10. package/dist/mcp/handlers/sync/index.js +3 -18
  11. package/dist/mcp/handlers/workflow/analyze.js +53 -105
  12. package/dist/mcp/handlers/workflow/deploy.js +129 -0
  13. package/dist/mcp/handlers/workflow/generate.js +8 -28
  14. package/dist/mcp/handlers/workflow/index.js +258 -85
  15. package/dist/mcp/handlers/workflow/modify.js +9 -29
  16. package/dist/mcp/handlers/workflow/optimize.js +22 -108
  17. package/dist/mcp/handlers/workflow/utils.js +0 -102
  18. package/dist/mcp/handlers-consolidated.js +15 -38
  19. package/dist/mcp/prompts.js +82 -44
  20. package/dist/mcp/resources.js +335 -3
  21. package/dist/mcp/server.js +242 -457
  22. package/dist/mcp/tools.js +44 -61
  23. package/dist/sdk/action-schema-parser.js +11 -5
  24. package/dist/sdk/client.js +46 -17
  25. package/dist/sdk/ema-client.js +11 -0
  26. package/dist/sdk/generated/deprecated-actions.js +171 -0
  27. package/dist/sdk/guidance.js +58 -35
  28. package/dist/sdk/index.js +8 -7
  29. package/dist/sdk/knowledge.js +216 -1932
  30. package/dist/sdk/quality-gates.js +60 -336
  31. package/dist/sdk/validation-rules.js +33 -0
  32. package/dist/sdk/workflow-fixer.js +29 -360
  33. package/dist/sdk/workflow-intent.js +43 -3
  34. package/dist/sdk/workflow-transformer.js +0 -342
  35. package/docs/dashboard-operations.md +35 -0
  36. package/docs/ema-user-guide.md +66 -0
  37. package/docs/mcp-tools-guide.md +74 -45
  38. package/package.json +2 -2
  39. package/dist/mcp/handlers/persona/analyze.js +0 -275
  40. package/dist/mcp/handlers/persona/compare.js +0 -32
  41. package/dist/mcp/handlers/workflow/compile.js +0 -39
  42. package/docs/DEBUG-ANALYSIS-unused-category-type-mismatch.md +0 -481
  43. package/docs/TODO-fix-analyzer-and-modify.md +0 -182
  44. package/resources/action-schema.json +0 -5678
@@ -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
- * All standard persona modes are now extracted:
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";
@@ -174,7 +174,8 @@ function applySpecToWorkflow(spec, existingWorkflow) {
174
174
  * Handle persona(mode="update") - update persona config or workflow
175
175
  *
176
176
  * @param args.id - Persona ID or name (required)
177
- * @param args.proto_config - Proto config changes (smart merged with widgets)
177
+ * @param args.config - Config changes with widgets (preferred, smart merged)
178
+ * @param args.proto_config - Alias for config (internal, for backwards compatibility)
178
179
  * @param args.workflow - Optional raw workflow_def to set
179
180
  * @param args.workflow_spec - Optional WorkflowSpec (agent-built) - will be compiled
180
181
  * @param args.preview - If true, return changes without deploying (default: false)
@@ -201,8 +202,9 @@ export async function handleUpdate(args, client) {
201
202
  const projectSettings = existingProtoConfig.projectSettings;
202
203
  const isVoice = projectSettings?.projectType === 5;
203
204
  // Build merged proto_config with smart widget-level merging
205
+ // Accept both 'config' (tool schema) and 'proto_config' (internal) for compatibility
204
206
  let mergedProtoConfig = { ...existingProtoConfig };
205
- const newProtoConfig = args.proto_config;
207
+ const newProtoConfig = (args.config ?? args.proto_config);
206
208
  const updatedWidgets = [];
207
209
  if (newProtoConfig) {
208
210
  // 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
- // Deprecated param mappings for backwards compatibility
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 and log warnings
25
- const deprecationWarnings = checkDeprecatedParams(args);
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
- * Analyzes workflow structure, detects issues, and provides metrics.
4
+ * Returns workflow DATA for LLM to analyze using rules from ema://rules/*.
5
5
  *
6
- * Features:
7
- * - Issue detection (critical, warning, info)
8
- * - Connection validation
9
- * - Metrics calculation
10
- * - Execution flow analysis (loops, dead code, multiple responders)
11
- * - Auto-fix support
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 { detectWorkflowIssues, suggestWorkflowFixes, validateWorkflowConnections } from "../../../sdk/knowledge.js";
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
- // Determine what to include
66
- const include = args.include || ["issues", "connections", "fixes", "metrics"];
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
- environment: "demo",
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
  }
@@ -2,8 +2,31 @@
2
2
  * Workflow Deploy Handler
3
3
  *
4
4
  * Deploys a workflow_def to an existing persona.
5
+ * Includes deprecation warnings (does not block, just warns).
6
+ *
7
+ * BLOCKING VALIDATION:
8
+ * - If workflow has knowledge search nodes, data sources MUST be attached and indexed.
5
9
  */
6
10
  import { sanitizeWorkflowForDeploy } from "./utils.js";
11
+ import { DEPRECATED_ACTIONS_FALLBACK, checkWorkflowDeprecations } from "./index.js";
12
+ /**
13
+ * Detect knowledge search nodes in a workflow.
14
+ * Returns true if workflow contains search/v2 or search_datastore nodes (NOT web_search).
15
+ */
16
+ function hasKnowledgeSearchNodes(workflowDef) {
17
+ const actions = (workflowDef.actions ?? []);
18
+ return actions.some(a => {
19
+ const actionType = a.action || a.actionType || "";
20
+ const name = a.name || "";
21
+ // Match: search, search/v2, search_datastore
22
+ // Exclude: web_search, live_web_search
23
+ const isSearchNode = actionType.includes("search") ||
24
+ name.toLowerCase().includes("search");
25
+ const isWebSearch = actionType.includes("web_search") ||
26
+ name.toLowerCase().includes("web_search");
27
+ return isSearchNode && !isWebSearch;
28
+ });
29
+ }
7
30
  /**
8
31
  * Handle workflow deploy mode
9
32
  */
@@ -21,8 +44,103 @@ export async function handleWorkflowDeploy(args, client) {
21
44
  if (!persona) {
22
45
  return { error: `Persona not found: ${personaId}` };
23
46
  }
47
+ // ── BLOCKING VALIDATION: Bidirectional search/data source validation ──
48
+ const hasSearchNodes = hasKnowledgeSearchNodes(workflowDef);
49
+ let dataSourceStats = null;
50
+ // Try to get data source stats (used for both validation directions)
51
+ try {
52
+ dataSourceStats = await client.getDataSourceAggregates(personaId);
53
+ }
54
+ catch (statsError) {
55
+ // If we can't check stats, log warning but don't block
56
+ console.error(`[deploy] Could not verify data sources: ${statsError}`);
57
+ }
58
+ // VALIDATION 1: If workflow has search nodes, data sources MUST exist and be indexed
59
+ if (hasSearchNodes && dataSourceStats) {
60
+ if (dataSourceStats.total === 0) {
61
+ return {
62
+ error: "DEPLOYMENT BLOCKED: Workflow contains knowledge search nodes but persona has no data sources attached.",
63
+ mode: "deploy",
64
+ persona_id: personaId,
65
+ persona_name: persona.name,
66
+ validation_failed: "search_nodes_require_data_sources",
67
+ current_state: {
68
+ search_nodes_detected: true,
69
+ data_sources_total: 0,
70
+ data_sources_indexed: 0,
71
+ },
72
+ _fix: [
73
+ "1. Upload documents: persona(id='...',data={method:'upload',path:'/path/to/doc.pdf'})",
74
+ "2. Check status: persona(id='...',data={method:'stats'}) → wait for 'success' > 0",
75
+ "3. Then retry: workflow(mode='deploy', persona_id='...', workflow_def={...})",
76
+ ],
77
+ };
78
+ }
79
+ if (dataSourceStats.success === 0) {
80
+ return {
81
+ error: "DEPLOYMENT BLOCKED: Workflow contains knowledge search nodes but no data sources have been successfully indexed.",
82
+ mode: "deploy",
83
+ persona_id: personaId,
84
+ persona_name: persona.name,
85
+ validation_failed: "search_nodes_require_indexed_data",
86
+ current_state: {
87
+ search_nodes_detected: true,
88
+ data_sources_total: dataSourceStats.total,
89
+ data_sources_pending: dataSourceStats.pending,
90
+ data_sources_success: dataSourceStats.success,
91
+ data_sources_failed: dataSourceStats.failed,
92
+ },
93
+ _fix: dataSourceStats.pending > 0
94
+ ? [
95
+ "Data sources are still indexing. Wait for indexing to complete:",
96
+ "1. Check status: persona(id='...',data={method:'stats'})",
97
+ "2. Retry when 'success' > 0",
98
+ ]
99
+ : [
100
+ "All data sources failed indexing. Check file formats and re-upload:",
101
+ "1. List files: persona(id='...',data={method:'list'})",
102
+ "2. Delete failed: persona(id='...',data={method:'delete',file_id:'...'})",
103
+ "3. Re-upload: persona(id='...',data={method:'upload',path:'...'})",
104
+ ],
105
+ };
106
+ }
107
+ }
108
+ // VALIDATION 2: If persona has indexed data sources but workflow has NO search nodes → BLOCK
109
+ // This catches the case where user uploads documents but deploys a workflow that won't use them
110
+ if (!hasSearchNodes && dataSourceStats && dataSourceStats.success > 0) {
111
+ return {
112
+ error: "DEPLOYMENT BLOCKED: Persona has indexed knowledge files but workflow has no search nodes. The uploaded documents will NEVER be used.",
113
+ mode: "deploy",
114
+ persona_id: personaId,
115
+ persona_name: persona.name,
116
+ validation_failed: "data_sources_require_search_nodes",
117
+ current_state: {
118
+ search_nodes_detected: false,
119
+ data_sources_total: dataSourceStats.total,
120
+ data_sources_indexed: dataSourceStats.success,
121
+ },
122
+ _fix: [
123
+ "Your workflow needs a search node to query the uploaded documents.",
124
+ "1. Get workflow schema: workflow(mode='get', persona_id='...')",
125
+ "2. Add search node: Update workflow_def to include a search/v2 node that queries the knowledge base",
126
+ "3. Wire it up: Connect chat_trigger → summarizer → search → respond_for_external_actions",
127
+ "Example pattern: trigger → conversation_summarizer → search/v2 → respond_for_external_actions → WORKFLOW_OUTPUT",
128
+ ],
129
+ _example_workflow_pattern: {
130
+ nodes: [
131
+ "chat_trigger (trigger)",
132
+ "conversation_summarizer (extract query from conversation)",
133
+ "search/v2 (query knowledge base with datastore_configs)",
134
+ "respond_for_external_actions (generate response with sources)",
135
+ ],
136
+ flow: "trigger → summarizer → search → respond → output",
137
+ },
138
+ };
139
+ }
24
140
  // Sanitize workflow before deployment
25
141
  const sanitizedWorkflow = sanitizeWorkflowForDeploy(workflowDef);
142
+ // Check for deprecated actions (warn, don't block)
143
+ const deprecationWarnings = checkWorkflowDeprecations(workflowDef, DEPRECATED_ACTIONS_FALLBACK);
26
144
  // Determine proto_config to use: provided > existing
27
145
  const existingProtoConfig = persona.proto_config;
28
146
  const providedProtoConfig = args.proto_config;
@@ -31,6 +149,11 @@ export async function handleWorkflowDeploy(args, client) {
31
149
  const projectSettings = protoConfigToUse.projectSettings;
32
150
  const isVoice = projectSettings?.projectType === 5;
33
151
  const warnings = [];
152
+ // Add deprecation warning message if deprecated actions found
153
+ if (deprecationWarnings.length > 0) {
154
+ warnings.push(`Workflow uses ${deprecationWarnings.length} deprecated action(s). ` +
155
+ "Consider updating to prevent future breakage when deprecated actions are removed.");
156
+ }
34
157
  if (isVoice && !providedProtoConfig) {
35
158
  // Check if existing proto_config has conversationSettings populated
36
159
  const widgets = (protoConfigToUse.widgets ?? []);
@@ -58,6 +181,12 @@ export async function handleWorkflowDeploy(args, client) {
58
181
  if (warnings.length > 0) {
59
182
  result.warnings = warnings;
60
183
  }
184
+ // Include detailed deprecation info if any deprecated actions found
185
+ if (deprecationWarnings.length > 0) {
186
+ result.deprecation_warnings = deprecationWarnings;
187
+ result._warning = "Workflow deployed but contains deprecated actions. Update before platform removes them.";
188
+ result._next_step = "Use workflow(mode='get') to see current schema, then deploy updated workflow_def with non-deprecated actions.";
189
+ }
61
190
  return result;
62
191
  }
63
192
  catch (err) {
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import { parseInput, intentToSpec, generateWorkflow } from "../../../sdk/workflow-intent.js";
12
12
  import { compileWorkflow } from "../../../sdk/workflow-generator.js";
13
- import { detectWorkflowIssues } from "../../../sdk/knowledge.js";
13
+ // Detection removed - LLM analyzes with rules from ema://rules/*
14
14
  import { runIntentArchitect } from "../../../sdk/intent-architect.js";
15
15
  import { ensureActionRegistry } from "../../../sdk/action-registry.js";
16
16
  import { ensureSchemaRegistry, validateWorkflowSpec, generateActionCatalogForLLM } from "../../../sdk/workflow-validator.js";
@@ -142,30 +142,13 @@ async function deployToNewPersona(args, client, compiled, actionRegistry, getTem
142
142
  widgets: Array.from(widgetMap.values()),
143
143
  };
144
144
  // Step 4: Deploy workflow
145
- const existingWorkflow = newPersona.workflow_def;
146
- const existingWfName = existingWorkflow?.workflowName;
147
- const workflowForDeploy = JSON.parse(JSON.stringify(compiled.workflow_def));
148
- const wfName = workflowForDeploy.workflowName;
149
- if (wfName?.name && existingWfName?.name) {
150
- wfName.name.namespaces = existingWfName.name.namespaces;
151
- wfName.name.name = existingWfName.name.name;
152
- }
153
- // Fix results format
154
- const compiledResults = workflowForDeploy.results;
155
- if (compiledResults) {
156
- const newResults = {};
157
- for (const [, value] of Object.entries(compiledResults)) {
158
- if (value.actionName && value.outputName) {
159
- const key = `${value.actionName}.${value.outputName}`;
160
- newResults[key] = value;
161
- }
162
- }
163
- workflowForDeploy.results = newResults;
164
- }
145
+ // NOTE: The SDK's updateAiEmployee() handles workflowName namespace automatically.
146
+ // It will copy from existing workflow if present, or generate a valid namespace if not.
147
+ // It also fixes the results format. No need to manually manipulate these here.
165
148
  try {
166
149
  await client.updateAiEmployee({
167
150
  persona_id: newPersonaId,
168
- workflow: workflowForDeploy,
151
+ workflow: compiled.workflow_def,
169
152
  proto_config: mergedProtoConfig,
170
153
  }, { verbose: true });
171
154
  return { success: true, personaId: newPersonaId, personaName };
@@ -182,7 +165,7 @@ async function deployToNewPersona(args, client, compiled, actionRegistry, getTem
182
165
  personaId: newPersonaId,
183
166
  personaName,
184
167
  workflowDeployError: errMsg,
185
- workflowAttempted: workflowForDeploy,
168
+ workflowAttempted: compiled.workflow_def,
186
169
  };
187
170
  }
188
171
  }
@@ -288,14 +271,14 @@ export async function handleWorkflowGenerate(args, client, getTemplateId) {
288
271
  }
289
272
  }
290
273
  const compiled = compileWorkflow(spec, { registry: actionRegistry });
291
- // Validate generated workflow
292
- const issues = detectWorkflowIssues(compiled.workflow_def);
293
274
  const result = {
294
275
  mode: "generate",
295
276
  status: preview ? "preview" : "deployed",
296
277
  workflow_def: compiled.workflow_def,
297
278
  proto_config: compiled.proto_config,
298
279
  validation: parseResult.validation,
280
+ // LLM should analyze with ema://rules/anti-patterns
281
+ _analysis_hint: "Use ema://rules/anti-patterns to check for issues",
299
282
  };
300
283
  if (specValidation) {
301
284
  result.api_validation = {
@@ -304,9 +287,6 @@ export async function handleWorkflowGenerate(args, client, getTemplateId) {
304
287
  action_coverage: specValidation.action_coverage,
305
288
  };
306
289
  }
307
- if (issues.length > 0) {
308
- result.issues = issues;
309
- }
310
290
  // Deploy if not preview
311
291
  if (!preview && personaId) {
312
292
  const persona = await client.getPersonaById(personaId);