@ema.co/mcp-toolkit 2026.1.25 → 2026.1.26-4

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 (87) hide show
  1. package/README.md +10 -2
  2. package/dist/mcp/handlers/action/index.js +3 -18
  3. package/dist/mcp/handlers/data/index.js +385 -41
  4. package/dist/mcp/handlers/data/templates.js +107 -0
  5. package/dist/mcp/handlers/deprecation.js +50 -0
  6. package/dist/mcp/handlers/env/index.js +8 -4
  7. package/dist/mcp/handlers/knowledge/index.js +44 -237
  8. package/dist/mcp/handlers/persona/create.js +47 -18
  9. package/dist/mcp/handlers/persona/index.js +14 -11
  10. package/dist/mcp/handlers/persona/update.js +4 -2
  11. package/dist/mcp/handlers/persona/version.js +234 -0
  12. package/dist/mcp/handlers/sync/index.js +3 -18
  13. package/dist/mcp/handlers/template/index.js +75 -10
  14. package/dist/mcp/handlers/workflow/analyze.js +171 -0
  15. package/dist/mcp/handlers/workflow/compare.js +70 -0
  16. package/dist/mcp/handlers/workflow/deploy.js +73 -0
  17. package/dist/mcp/handlers/workflow/generate.js +350 -0
  18. package/dist/mcp/handlers/workflow/index.js +294 -0
  19. package/dist/mcp/handlers/workflow/modify.js +456 -0
  20. package/dist/mcp/handlers/workflow/optimize.js +136 -0
  21. package/dist/mcp/handlers/workflow/types.js +4 -0
  22. package/dist/mcp/handlers/workflow/utils.js +30 -0
  23. package/dist/mcp/handlers-consolidated.js +73 -2696
  24. package/dist/mcp/prompts.js +83 -43
  25. package/dist/mcp/resources.js +382 -57
  26. package/dist/mcp/server.js +199 -391
  27. package/dist/mcp/{tools-v2.js → tools.js} +20 -54
  28. package/dist/mcp/workflow-operations.js +2 -2
  29. package/dist/sdk/client-adapter.js +267 -32
  30. package/dist/sdk/client.js +45 -16
  31. package/dist/sdk/ema-client.js +183 -0
  32. package/dist/sdk/generated/deprecated-actions.js +171 -0
  33. package/dist/sdk/generated/template-fallbacks.js +123 -0
  34. package/dist/sdk/guidance.js +65 -11
  35. package/dist/sdk/index.js +3 -1
  36. package/dist/sdk/knowledge.js +139 -86
  37. package/dist/sdk/workflow-intent.js +27 -0
  38. package/dist/sdk/workflow-transformer.js +0 -342
  39. package/docs/mcp-tools-guide.md +37 -45
  40. package/package.json +10 -4
  41. package/dist/mcp/handlers/persona/analyze.js +0 -275
  42. package/dist/mcp/handlers/persona/compare.js +0 -32
  43. package/dist/mcp/tools-consolidated.js +0 -875
  44. package/dist/mcp/tools-legacy.js +0 -736
  45. package/docs/CODEBASE-ANALYSIS-2026-01-23.md +0 -936
  46. package/docs/CODEBASE-ANALYSIS-PRIORITIZED.md +0 -774
  47. package/docs/api-contracts.md +0 -216
  48. package/docs/auto-builder-analysis.md +0 -271
  49. package/docs/blog/mcp-tool-design-lessons.md +0 -309
  50. package/docs/data-architecture.md +0 -166
  51. package/docs/demos/ap-invoice-generation.md +0 -347
  52. package/docs/demos/ap-invoice-processing.md +0 -271
  53. package/docs/ema-auto-builder-guide.html +0 -394
  54. package/docs/lessons-learned.md +0 -209
  55. package/docs/llm-native-workflow-design.md +0 -252
  56. package/docs/local-generation.md +0 -508
  57. package/docs/mcp-flow-diagram.md +0 -135
  58. package/docs/migration/action-composition-migration.md +0 -270
  59. package/docs/naming-conventions.md +0 -278
  60. package/docs/proposals/HANDOFF-tool-restructure.md +0 -526
  61. package/docs/proposals/action-composition.md +0 -490
  62. package/docs/proposals/explicit-method-restructure.md +0 -328
  63. package/docs/proposals/mcp-tool-restructure-2026-01.md +0 -366
  64. package/docs/proposals/self-contained-guidance.md +0 -427
  65. package/docs/proto-sdk-generation.md +0 -242
  66. package/docs/release-impact.md +0 -102
  67. package/docs/release-process.md +0 -157
  68. package/docs/staging.RULE.md +0 -142
  69. package/docs/test-persona-creation.md +0 -196
  70. package/docs/tool-consolidation-v2.md +0 -225
  71. package/docs/tool-response-standards.md +0 -256
  72. package/resources/demo-kits/README.md +0 -175
  73. package/resources/demo-kits/finance-ap/manifest.json +0 -150
  74. package/resources/demo-kits/tags.json +0 -91
  75. package/resources/docs/getting-started.md +0 -97
  76. package/resources/templates/auto-builder-rules.md +0 -224
  77. package/resources/templates/chat-ai/README.md +0 -119
  78. package/resources/templates/chat-ai/persona-config.json +0 -111
  79. package/resources/templates/dashboard-ai/README.md +0 -156
  80. package/resources/templates/dashboard-ai/persona-config.json +0 -180
  81. package/resources/templates/demo-scenarios/README.md +0 -63
  82. package/resources/templates/demo-scenarios/test-published-package.md +0 -116
  83. package/resources/templates/document-gen-ai/README.md +0 -132
  84. package/resources/templates/document-gen-ai/persona-config.json +0 -316
  85. package/resources/templates/voice-ai/README.md +0 -123
  86. package/resources/templates/voice-ai/persona-config.json +0 -74
  87. package/resources/templates/voice-ai/workflow-prompt.md +0 -121
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Version Management Handlers
3
+ *
4
+ * Provides local version tracking for personas (snapshot, history, restore).
5
+ * Versions are stored in the local workspace, not on the Ema platform.
6
+ */
7
+ import { createVersionStorage } from "../../../sdk/version-storage.js";
8
+ import { createVersionPolicyEngine } from "../../../sdk/version-policy.js";
9
+ /**
10
+ * Check if a mode is a version management mode.
11
+ */
12
+ export function isVersionMode(mode) {
13
+ return [
14
+ "snapshot",
15
+ "history",
16
+ "restore",
17
+ "version_create",
18
+ "version_list",
19
+ "version_get",
20
+ "version_compare",
21
+ "version_restore",
22
+ "version_policy",
23
+ ].includes(mode);
24
+ }
25
+ /**
26
+ * Handle version management operations.
27
+ *
28
+ * @param mode - The version mode (snapshot, history, restore, version_*)
29
+ * @param args - Handler arguments including id, version, message, etc.
30
+ * @param client - Ema client for fetching personas
31
+ * @param persona - Resolved persona (pre-fetched)
32
+ * @param versionContext - Workspace and environment context
33
+ */
34
+ export async function handleVersion(mode, args, client, persona, versionContext) {
35
+ const storage = createVersionStorage(versionContext.workspaceRoot);
36
+ const engine = createVersionPolicyEngine(storage);
37
+ switch (mode) {
38
+ // ─────────────────────────────────────────────────────────────────────────
39
+ // snapshot / version_create - Create a new version snapshot
40
+ // ─────────────────────────────────────────────────────────────────────────
41
+ case "snapshot":
42
+ case "version_create": {
43
+ // Fetch full persona with workflow
44
+ const fullPersona = await client.getPersonaById(persona.id);
45
+ if (!fullPersona) {
46
+ return { error: `Could not fetch full persona: ${persona.id}` };
47
+ }
48
+ const result = engine.forceCreateVersion(fullPersona, {
49
+ environment: versionContext.environment,
50
+ tenant_id: versionContext.tenant_id,
51
+ message: args.message,
52
+ created_by: "mcp-toolkit",
53
+ });
54
+ if (!result.created || !result.version) {
55
+ return { error: result.reason };
56
+ }
57
+ return {
58
+ success: true,
59
+ version: {
60
+ id: result.version.id,
61
+ version_number: result.version.version_number,
62
+ version_name: result.version.version_name,
63
+ content_hash: result.version.content_hash,
64
+ created_at: result.version.created_at,
65
+ message: result.version.message,
66
+ },
67
+ changes_from_parent: result.changes_from_parent,
68
+ versions_pruned: result.versions_pruned,
69
+ };
70
+ }
71
+ // ─────────────────────────────────────────────────────────────────────────
72
+ // history / version_list - List version history
73
+ // ─────────────────────────────────────────────────────────────────────────
74
+ case "history":
75
+ case "version_list": {
76
+ const versions = engine.listVersions(persona.id, {
77
+ limit: args.limit,
78
+ });
79
+ return {
80
+ persona_id: persona.id,
81
+ persona_name: persona.name,
82
+ versions,
83
+ count: versions.length,
84
+ };
85
+ }
86
+ // ─────────────────────────────────────────────────────────────────────────
87
+ // version_get - Get a specific version
88
+ // ─────────────────────────────────────────────────────────────────────────
89
+ case "version_get": {
90
+ const versionId = args.version ?? "latest";
91
+ const version = engine.getVersion(persona.id, versionId);
92
+ if (!version) {
93
+ return { error: `Version not found: ${versionId}` };
94
+ }
95
+ return {
96
+ persona_id: persona.id,
97
+ persona_name: persona.name,
98
+ version: {
99
+ id: version.id,
100
+ version_number: version.version_number,
101
+ version_name: version.version_name,
102
+ content_hash: version.content_hash,
103
+ created_at: version.created_at,
104
+ created_by: version.created_by,
105
+ trigger: version.trigger,
106
+ message: version.message,
107
+ changes_summary: version.changes_summary,
108
+ },
109
+ snapshot: {
110
+ display_name: version.snapshot.display_name,
111
+ description: version.snapshot.description,
112
+ workflow_id: version.snapshot.workflow_id,
113
+ trigger_type: version.snapshot.trigger_type,
114
+ embedding_enabled: version.snapshot.embedding_enabled,
115
+ has_workflow: !!version.snapshot.workflow_definition,
116
+ has_proto_config: !!version.snapshot.proto_config,
117
+ },
118
+ };
119
+ }
120
+ // ─────────────────────────────────────────────────────────────────────────
121
+ // version_compare - Compare two versions
122
+ // ─────────────────────────────────────────────────────────────────────────
123
+ case "version_compare": {
124
+ const v1 = args.v1;
125
+ const v2 = args.v2;
126
+ if (!v1 || !v2) {
127
+ return { error: "v1 and v2 required for version_compare mode" };
128
+ }
129
+ const result = engine.compareVersions(persona.id, v1, v2);
130
+ if (!result.success) {
131
+ return { error: result.error };
132
+ }
133
+ return {
134
+ persona_id: persona.id,
135
+ persona_name: persona.name,
136
+ comparison: {
137
+ v1: result.diff?.v1,
138
+ v2: result.diff?.v2,
139
+ identical: result.diff?.identical,
140
+ changed_fields: result.diff?.changed_fields,
141
+ workflow_diff: result.diff?.workflow_diff,
142
+ },
143
+ summary: result.summary,
144
+ };
145
+ }
146
+ // ─────────────────────────────────────────────────────────────────────────
147
+ // restore / version_restore - Restore to a previous version
148
+ // ─────────────────────────────────────────────────────────────────────────
149
+ case "restore":
150
+ case "version_restore": {
151
+ const versionId = args.version;
152
+ if (!versionId) {
153
+ return { error: "version required for restore mode" };
154
+ }
155
+ const restoreData = engine.getRestoreData(persona.id, versionId);
156
+ if (!restoreData.success || !restoreData.restore_payload) {
157
+ return { error: restoreData.error ?? "Failed to get restore data" };
158
+ }
159
+ // Create a version snapshot before restoring (audit trail)
160
+ const fullPersona = await client.getPersonaById(persona.id);
161
+ if (fullPersona) {
162
+ engine.forceCreateVersion(fullPersona, {
163
+ environment: versionContext.environment,
164
+ tenant_id: versionContext.tenant_id,
165
+ message: `Before restore to ${restoreData.version?.version_name}`,
166
+ created_by: "mcp-toolkit",
167
+ });
168
+ }
169
+ // Apply the restore
170
+ const payload = restoreData.restore_payload;
171
+ await client.updateAiEmployee({
172
+ persona_id: payload.persona_id,
173
+ name: payload.name,
174
+ description: payload.description,
175
+ proto_config: payload.proto_config,
176
+ workflow: payload.workflow ?? undefined,
177
+ welcome_messages: payload.welcome_messages ?? undefined,
178
+ embedding_enabled: payload.embedding_enabled ?? undefined,
179
+ });
180
+ // Create post-restore version
181
+ const restoredPersona = await client.getPersonaById(persona.id);
182
+ if (restoredPersona) {
183
+ engine.forceCreateVersion(restoredPersona, {
184
+ environment: versionContext.environment,
185
+ tenant_id: versionContext.tenant_id,
186
+ message: `Restored to ${restoreData.version?.version_name}`,
187
+ created_by: "mcp-toolkit",
188
+ });
189
+ }
190
+ return {
191
+ success: true,
192
+ persona_id: persona.id,
193
+ restored_to: {
194
+ version_id: restoreData.version?.id,
195
+ version_name: restoreData.version?.version_name,
196
+ version_number: restoreData.version?.version_number,
197
+ },
198
+ message: `Persona restored to ${restoreData.version?.version_name}`,
199
+ };
200
+ }
201
+ // ─────────────────────────────────────────────────────────────────────────
202
+ // version_policy - Get or update version policy
203
+ // ─────────────────────────────────────────────────────────────────────────
204
+ case "version_policy": {
205
+ // Check if we're updating or just getting
206
+ const hasUpdates = args.auto_on_deploy !== undefined ||
207
+ args.auto_on_sync !== undefined ||
208
+ args.max_versions !== undefined;
209
+ if (hasUpdates) {
210
+ const updated = engine.updatePolicy(persona.id, {
211
+ auto_version_on_deploy: args.auto_on_deploy,
212
+ auto_version_on_sync: args.auto_on_sync,
213
+ max_versions: args.max_versions,
214
+ });
215
+ return {
216
+ success: true,
217
+ persona_id: persona.id,
218
+ persona_name: persona.name,
219
+ policy: updated,
220
+ message: "Policy updated",
221
+ };
222
+ }
223
+ // Just get current policy
224
+ const policy = engine.getPolicy(persona.id);
225
+ return {
226
+ persona_id: persona.id,
227
+ persona_name: persona.name,
228
+ policy,
229
+ };
230
+ }
231
+ default:
232
+ return { error: `Unknown version mode: ${mode}` };
233
+ }
234
+ }
@@ -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'
@@ -2,10 +2,15 @@
2
2
  * Template Handler
3
3
  *
4
4
  * Provides workflow patterns, widget references, config templates, and qualifying questions.
5
+ *
6
+ * Config templates follow API-first pattern:
7
+ * 1. Try to get from API (getPersonaTemplates)
8
+ * 2. Fall back to generated templates from proto definitions
5
9
  */
6
- import { WORKFLOW_PATTERNS, QUALIFYING_QUESTIONS, VOICE_PERSONA_TEMPLATE, getWidgetsForPersonaType, getQualifyingQuestionsByCategory, getRequiredQualifyingQuestions, } from "../../../sdk/knowledge.js";
10
+ import { WORKFLOW_PATTERNS, QUALIFYING_QUESTIONS, getWidgetsForPersonaType, getQualifyingQuestionsByCategory, getRequiredQualifyingQuestions, } from "../../../sdk/knowledge.js";
11
+ import { getTemplateFallback, getTemplateFieldDocs } from "../../../sdk/generated/template-fallbacks.js";
7
12
  /**
8
- * Handle template tool requests
13
+ * Handle template tool requests (sync operations only)
9
14
  */
10
15
  export function handleTemplate(args) {
11
16
  // Get specific pattern
@@ -38,14 +43,6 @@ export function handleTemplate(args) {
38
43
  const widgets = getWidgetsForPersonaType(args.widgets);
39
44
  return { type: args.widgets, widgets };
40
45
  }
41
- // Config template
42
- if (args.config) {
43
- if (args.config === "voice") {
44
- return VOICE_PERSONA_TEMPLATE;
45
- }
46
- // Add chat/dashboard templates here
47
- return { type: args.config, template: {} };
48
- }
49
46
  // Qualifying questions
50
47
  if (args.questions) {
51
48
  let questions = QUALIFYING_QUESTIONS;
@@ -57,5 +54,73 @@ export function handleTemplate(args) {
57
54
  }
58
55
  return { questions, count: questions.length };
59
56
  }
57
+ // Config template - use async handler for API-first approach
58
+ if (args.config) {
59
+ // For sync handler, return fallback with note to use async version
60
+ const type = args.config;
61
+ const fallback = getTemplateFallback(type);
62
+ const docs = getTemplateFieldDocs(type);
63
+ if (fallback) {
64
+ return {
65
+ type,
66
+ template: fallback,
67
+ field_docs: docs,
68
+ _source: "fallback",
69
+ _note: "This is a generated fallback. For live templates, use handleTemplateAsync with client.",
70
+ };
71
+ }
72
+ return { error: `No template available for type: ${args.config}` };
73
+ }
60
74
  return { error: "Specify pattern, patterns=true, widgets, config, or questions=true" };
61
75
  }
76
+ /**
77
+ * Handle template tool requests with API access (async).
78
+ * Uses API-first approach with generated fallback.
79
+ */
80
+ export async function handleTemplateAsync(args, client) {
81
+ // Config template - API first with fallback
82
+ if (args.config) {
83
+ const type = args.config;
84
+ // Map type to API trigger_type
85
+ const triggerTypeMap = {
86
+ voice: "VOICEBOT_AI_EMPLOYEE",
87
+ chat: "CHATBOT",
88
+ dashboard: "DOCUMENT_TRIGGER",
89
+ };
90
+ const targetTriggerType = triggerTypeMap[type];
91
+ try {
92
+ // Try API first
93
+ const templates = await client.getPersonaTemplates();
94
+ const template = templates.find(t => t.trigger_type === targetTriggerType ||
95
+ t.name?.toLowerCase().includes(type));
96
+ if (template?.proto_config) {
97
+ return {
98
+ type,
99
+ template: template.proto_config,
100
+ template_id: template.id,
101
+ template_name: template.name,
102
+ _source: "api",
103
+ };
104
+ }
105
+ }
106
+ catch (err) {
107
+ // API failed, fall through to fallback
108
+ console.warn(`Failed to fetch templates from API: ${err}`);
109
+ }
110
+ // Fall back to generated template
111
+ const fallback = getTemplateFallback(type);
112
+ const docs = getTemplateFieldDocs(type);
113
+ if (fallback) {
114
+ return {
115
+ type,
116
+ template: fallback,
117
+ field_docs: docs,
118
+ _source: "fallback",
119
+ _note: "API templates unavailable, using generated fallback from proto definitions.",
120
+ };
121
+ }
122
+ return { error: `No template available for type: ${args.config}` };
123
+ }
124
+ // For non-config operations, use sync handler
125
+ return handleTemplate(args);
126
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Workflow Analyze Handler
3
+ *
4
+ * Analyzes workflow structure, detects issues, and provides metrics.
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
12
+ */
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
+ /**
17
+ * Calculate workflow metrics
18
+ */
19
+ function calculateMetrics(workflow) {
20
+ const actions = workflow.actions;
21
+ if (!actions)
22
+ return { node_count: 0, connection_count: 0 };
23
+ // Count connections
24
+ let connectionCount = 0;
25
+ for (const action of actions) {
26
+ const inputs = action.inputs;
27
+ if (inputs) {
28
+ connectionCount += Object.keys(inputs).length;
29
+ }
30
+ }
31
+ // Check for HITL
32
+ const hasHitl = actions.some(a => {
33
+ const name = a.name?.toLowerCase() ?? "";
34
+ return name.includes("hitl") || name.includes("human");
35
+ });
36
+ // Get trigger type
37
+ const trigger = workflow.trigger;
38
+ const triggerType = trigger?.trigger_type;
39
+ return {
40
+ node_count: actions.length,
41
+ connection_count: connectionCount,
42
+ has_hitl: hasHitl,
43
+ trigger_type: triggerType,
44
+ };
45
+ }
46
+ /**
47
+ * Handle workflow analyze mode
48
+ */
49
+ export async function handleWorkflowAnalyze(args, client) {
50
+ const personaId = args.persona_id;
51
+ let workflow = args.workflow_def;
52
+ let persona = null;
53
+ // Get workflow from persona if not provided
54
+ if (personaId && !workflow) {
55
+ const fetchedPersona = await client.getPersonaById(personaId);
56
+ if (!fetchedPersona) {
57
+ return { error: `Persona not found: ${personaId}` };
58
+ }
59
+ persona = fetchedPersona;
60
+ workflow = fetchedPersona.workflow_def;
61
+ }
62
+ if (!workflow) {
63
+ return { error: "No workflow to analyze. Provide workflow_def or persona_id." };
64
+ }
65
+ // Determine what to include
66
+ const include = args.include || ["issues", "connections", "fixes", "metrics"];
67
+ const result = {
68
+ mode: "analyze",
69
+ persona_id: personaId,
70
+ persona_name: persona?.name,
71
+ environment: "demo",
72
+ };
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
+ if (persona) {
119
+ result.persona = {
120
+ id: persona.id,
121
+ name: persona.name
122
+ };
123
+ }
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
+ return result;
171
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Workflow Compare Handler
3
+ *
4
+ * Compares two workflow versions to identify differences.
5
+ */
6
+ /**
7
+ * Compare two workflows and return differences
8
+ */
9
+ function compareWorkflows(workflow1, workflow2) {
10
+ const differences = [];
11
+ if (!workflow1 && !workflow2)
12
+ return differences;
13
+ if (!workflow1) {
14
+ differences.push({ type: "added", path: "workflow", new_value: "entire workflow" });
15
+ return differences;
16
+ }
17
+ if (!workflow2) {
18
+ differences.push({ type: "removed", path: "workflow", old_value: "entire workflow" });
19
+ return differences;
20
+ }
21
+ // Compare actions
22
+ const actions1 = workflow1.actions || [];
23
+ const actions2 = workflow2.actions || [];
24
+ const actionIds1 = new Set(actions1.map(a => (a.id ?? a.name)));
25
+ const actionIds2 = new Set(actions2.map(a => (a.id ?? a.name)));
26
+ // Find added/removed actions
27
+ for (const id of actionIds2) {
28
+ if (!actionIds1.has(id)) {
29
+ differences.push({ type: "added", path: `actions.${id}`, new_value: id });
30
+ }
31
+ }
32
+ for (const id of actionIds1) {
33
+ if (!actionIds2.has(id)) {
34
+ differences.push({ type: "removed", path: `actions.${id}`, old_value: id });
35
+ }
36
+ }
37
+ // Node count change
38
+ if (actions1.length !== actions2.length) {
39
+ differences.push({
40
+ type: "changed",
41
+ path: "actions.length",
42
+ old_value: actions1.length,
43
+ new_value: actions2.length,
44
+ });
45
+ }
46
+ return differences;
47
+ }
48
+ /**
49
+ * Handle workflow compare mode
50
+ */
51
+ export async function handleWorkflowCompare(args, client) {
52
+ const personaId = args.persona_id;
53
+ const compareTo = args.compare_to;
54
+ if (!personaId || !compareTo) {
55
+ return { error: "persona_id and compare_to required for compare mode" };
56
+ }
57
+ const persona1 = await client.getPersonaById(personaId);
58
+ const persona2 = await client.getPersonaById(compareTo);
59
+ if (!persona1 || !persona2) {
60
+ return { error: "One or both personas not found" };
61
+ }
62
+ const workflow1 = persona1.workflow_def;
63
+ const workflow2 = persona2.workflow_def;
64
+ return {
65
+ mode: "compare",
66
+ persona_1: { id: persona1.id, name: persona1.name },
67
+ persona_2: { id: persona2.id, name: persona2.name },
68
+ differences: compareWorkflows(workflow1, workflow2),
69
+ };
70
+ }