@ema.co/mcp-toolkit 1.5.2 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,9 @@ import { createVersionStorage } from "../sdk/version-storage.js";
8
8
  import { createVersionPolicyEngine } from "../sdk/version-policy.js";
9
9
  import { AGENT_CATALOG, WORKFLOW_PATTERNS, QUALIFYING_QUESTIONS, PLATFORM_CONCEPTS, WORKFLOW_EXECUTION_MODEL, COMMON_MISTAKES, DEBUG_CHECKLIST, GUIDANCE_TOPICS, VOICE_PERSONA_TEMPLATE, getAgentByName, getWidgetsForPersonaType, checkTypeCompatibility, getQualifyingQuestionsByCategory, getRequiredQualifyingQuestions, getConceptByTerm, suggestAgentsForUseCase, validateWorkflowPrompt, detectWorkflowIssues, validateWorkflowConnections, suggestWorkflowFixes, } from "../sdk/knowledge.js";
10
10
  import { compileWorkflow } from "../sdk/workflow-generator.js";
11
- import { parseInput, intentToSpec } from "../sdk/workflow-intent.js";
11
+ import { ensureActionRegistry } from "../sdk/action-registry.js";
12
+ import { parseInput, intentToSpec, generateWorkflow } from "../sdk/workflow-intent.js";
13
+ import { ensureSchemaRegistry, validateWorkflowSpec, generateActionCatalogForLLM } from "../sdk/workflow-validator.js";
12
14
  import { analyzeExecutionFlow, generateASCIIFlow } from "../sdk/workflow-execution-analyzer.js";
13
15
  // ─────────────────────────────────────────────────────────────────────────────
14
16
  // Widget Validation Helpers
@@ -61,6 +63,29 @@ export async function handlePersona(args, client, getTemplateId, createClientFor
61
63
  const identifier = args.identifier; // deprecated alias
62
64
  const idOrName = id ?? identifier;
63
65
  const mode = args.mode;
66
+ // ═══════════════════════════════════════════════════════════════════════════
67
+ // UNIFIED HANDLING: Route workflow operations to handleWorkflow
68
+ // ═══════════════════════════════════════════════════════════════════════════
69
+ const input = args.input;
70
+ const optimize = args.optimize;
71
+ const workflowDef = args.workflow_def ?? args.workflow;
72
+ // If input is provided → this is a create/modify workflow operation
73
+ // If optimize is provided → this is an optimize operation
74
+ // If id + workflowDef provided → this is a deploy operation
75
+ // Route these to handleWorkflow
76
+ if (input || optimize || (idOrName && workflowDef)) {
77
+ // Map persona args to workflow args
78
+ const workflowArgs = {
79
+ ...args,
80
+ persona_id: idOrName, // workflow uses persona_id not id
81
+ };
82
+ delete workflowArgs.id;
83
+ delete workflowArgs.identifier;
84
+ return handleWorkflow(workflowArgs, client, getTemplateId);
85
+ }
86
+ // ═══════════════════════════════════════════════════════════════════════════
87
+ // Standard persona operations (get, list, compare, version management)
88
+ // ═══════════════════════════════════════════════════════════════════════════
64
89
  // Determine effective mode
65
90
  let effectiveMode = mode;
66
91
  if (!effectiveMode) {
@@ -752,6 +777,8 @@ export async function handleWorkflow(args, client, getTemplateId) {
752
777
  if (!input) {
753
778
  return { error: "input required for generate mode" };
754
779
  }
780
+ // Load action registry for API-driven action versions/namespaces
781
+ const actionRegistry = await ensureActionRegistry(client);
755
782
  const parseResult = parseInput(input);
756
783
  if (!parseResult.validation.complete) {
757
784
  return {
@@ -761,10 +788,75 @@ export async function handleWorkflow(args, client, getTemplateId) {
761
788
  questions: parseResult.validation.questions,
762
789
  };
763
790
  }
764
- // Convert to spec and compile
765
- const spec = intentToSpec(parseResult.intent);
766
- const compiled = compileWorkflow(spec);
767
- // Validate the generated workflow
791
+ // Override persona_type from args.type if provided (args.type takes precedence)
792
+ if (args.type) {
793
+ parseResult.intent.persona_type = args.type;
794
+ }
795
+ // Check if intent requires LLM-driven generation
796
+ const genResult = generateWorkflow(parseResult.intent);
797
+ // Load schema registry for API-driven validation (graceful degradation if unavailable)
798
+ let schemaRegistry;
799
+ try {
800
+ schemaRegistry = await ensureSchemaRegistry(client);
801
+ }
802
+ catch {
803
+ // Schema registry unavailable - skip API validation
804
+ schemaRegistry = null;
805
+ }
806
+ if (genResult.needs_llm) {
807
+ // Complex workflow - return prompt for LLM to generate
808
+ // Enhanced with API-driven action catalog if available
809
+ let enhancedPrompt = genResult.llm_prompt;
810
+ let availableActions = [];
811
+ let availableTemplates = [];
812
+ if (schemaRegistry) {
813
+ const actionCatalog = generateActionCatalogForLLM(schemaRegistry);
814
+ enhancedPrompt = genResult.llm_prompt ? {
815
+ system: genResult.llm_prompt.system + "\n\n" + actionCatalog,
816
+ user: genResult.llm_prompt.user,
817
+ } : undefined;
818
+ availableActions = schemaRegistry.getAllActions().map(a => a.name);
819
+ availableTemplates = schemaRegistry.getAllTemplates().map(t => ({ id: t.id, name: t.name, type: t.type }));
820
+ }
821
+ return {
822
+ status: "needs_llm_generation",
823
+ reason: genResult.reason,
824
+ complexity: genResult.complexity,
825
+ llm_prompt: enhancedPrompt,
826
+ hint: "Send llm_prompt to an LLM, then call persona(workflow_def=<parsed_response>) to deploy.",
827
+ // Also provide basic spec as fallback (without complex chains)
828
+ fallback_spec: intentToSpec(parseResult.intent),
829
+ // Include catalogs for reference (if available)
830
+ available_actions: availableActions,
831
+ available_templates: availableTemplates,
832
+ };
833
+ }
834
+ // Simple workflow - use generated spec directly
835
+ const spec = genResult.spec;
836
+ // Override spec name with provided name if given (args.name takes precedence over parsed name)
837
+ if (args.name) {
838
+ spec.name = args.name;
839
+ }
840
+ if (args.description) {
841
+ spec.description = args.description;
842
+ }
843
+ // Validate spec against API schemas BEFORE compiling (if schema registry available)
844
+ let specValidation = null;
845
+ if (schemaRegistry) {
846
+ specValidation = validateWorkflowSpec(spec, schemaRegistry);
847
+ if (!specValidation.valid) {
848
+ return {
849
+ status: "validation_failed",
850
+ errors: specValidation.errors,
851
+ warnings: specValidation.warnings,
852
+ action_coverage: specValidation.action_coverage,
853
+ hint: "Fix the validation errors and try again. Unknown actions may need to be checked against ListActions.",
854
+ spec_attempted: spec,
855
+ };
856
+ }
857
+ }
858
+ const compiled = compileWorkflow(spec, { registry: actionRegistry });
859
+ // Validate the generated workflow (structural checks)
768
860
  const issues = detectWorkflowIssues(compiled.workflow_def);
769
861
  const result = {
770
862
  mode: "generate",
@@ -773,6 +865,14 @@ export async function handleWorkflow(args, client, getTemplateId) {
773
865
  proto_config: compiled.proto_config,
774
866
  validation: parseResult.validation,
775
867
  };
868
+ // Include API validation results if available
869
+ if (specValidation) {
870
+ result.api_validation = {
871
+ valid: specValidation.valid,
872
+ warnings: specValidation.warnings,
873
+ action_coverage: specValidation.action_coverage,
874
+ };
875
+ }
776
876
  if (issues.length > 0) {
777
877
  result.issues = issues;
778
878
  }
@@ -790,18 +890,19 @@ export async function handleWorkflow(args, client, getTemplateId) {
790
890
  result.deployed_to = { persona_id: personaId, persona_name: persona.name };
791
891
  }
792
892
  else if (!preview && !personaId) {
793
- // GREENFIELD: Create new persona from template, then update proto_config only
794
- // The template provides a valid workflow structure - we don't replace it,
795
- // we just configure the persona settings (voice config, welcome message, etc.)
893
+ // GREENFIELD: Create new persona from template, then deploy workflow + proto_config
796
894
  const personaName = args.name;
797
895
  if (personaName) {
798
896
  const personaType = args.type || "chat";
799
- const DEFAULT_TEMPLATES = {
800
- voice: "00000000-0000-0000-0000-00000000001e", // Voice AI template
801
- chat: "00000000-0000-0000-0000-000000000004", // Chat AI template
802
- dashboard: "00000000-0000-0000-0000-000000000002", // Dashboard AI template
897
+ // Try API-driven template lookup first, fallback to hardcoded IDs
898
+ const FALLBACK_TEMPLATES = {
899
+ voice: "00000000-0000-0000-0000-00000000001e", // Voice AI template (fallback)
900
+ chat: "00000000-0000-0000-0000-000000000004", // Chat AI template (fallback)
901
+ dashboard: "00000000-0000-0000-0000-000000000002", // Dashboard AI template (fallback)
803
902
  };
804
- const templateId = getTemplateId?.(personaType) || DEFAULT_TEMPLATES[personaType];
903
+ // Use registry to find template by type
904
+ const templateFromRegistry = actionRegistry.getTemplateForType(personaType);
905
+ const templateId = getTemplateId?.(personaType) || templateFromRegistry?.id || FALLBACK_TEMPLATES[personaType];
805
906
  if (!templateId) {
806
907
  return {
807
908
  error: `No template found for type "${personaType}". Provide template_id or use type: voice, chat, or dashboard.`
@@ -859,12 +960,57 @@ export async function handleWorkflow(args, client, getTemplateId) {
859
960
  ...existingProtoConfig,
860
961
  widgets: Array.from(widgetMap.values()),
861
962
  };
862
- // Step 4: Update persona with merged proto_config only
863
- // DO NOT send workflow - keep the template's valid workflow structure
864
- await client.updateAiEmployee({
865
- persona_id: newPersonaId,
866
- proto_config: mergedProtoConfig,
867
- });
963
+ // Step 4: Deploy BOTH workflow AND proto_config for ALL persona types
964
+ // Voice AI IS driven by workflow (not just identityAndPurpose)
965
+ const existingWorkflow = newPersona.workflow_def;
966
+ const existingWfName = existingWorkflow?.workflowName;
967
+ // Clone our compiled workflow and set the namespace from the existing workflow
968
+ const workflowForDeploy = JSON.parse(JSON.stringify(compiled.workflow_def));
969
+ const wfName = workflowForDeploy.workflowName;
970
+ if (wfName?.name && existingWfName?.name) {
971
+ // Copy the exact namespace from the template workflow
972
+ wfName.name.namespaces = existingWfName.name.namespaces;
973
+ wfName.name.name = existingWfName.name.name; // template_id
974
+ }
975
+ // Fix results format - use "<actionName>.<outputName>" keys
976
+ const compiledResults = workflowForDeploy.results;
977
+ if (compiledResults) {
978
+ const newResults = {};
979
+ for (const [, value] of Object.entries(compiledResults)) {
980
+ if (value.actionName && value.outputName) {
981
+ const key = `${value.actionName}.${value.outputName}`;
982
+ newResults[key] = value;
983
+ }
984
+ }
985
+ workflowForDeploy.results = newResults;
986
+ }
987
+ try {
988
+ await client.updateAiEmployee({
989
+ persona_id: newPersonaId,
990
+ workflow: workflowForDeploy,
991
+ proto_config: mergedProtoConfig,
992
+ }, { verbose: true });
993
+ }
994
+ catch (deployError) {
995
+ // Workflow deploy failed - still set proto_config
996
+ const errMsg = deployError instanceof Error ? deployError.message : String(deployError);
997
+ await client.updateAiEmployee({
998
+ persona_id: newPersonaId,
999
+ proto_config: mergedProtoConfig,
1000
+ });
1001
+ result.workflow_deploy_error = errMsg;
1002
+ result.workflow_attempted = workflowForDeploy;
1003
+ result.status = "partial";
1004
+ result.hint = "Persona created, config set, but workflow deploy failed. Check workflow_attempted for details.";
1005
+ result.deployed_to = {
1006
+ persona_id: newPersonaId,
1007
+ persona_name: personaName,
1008
+ created: true,
1009
+ workflow_deployed: false,
1010
+ };
1011
+ return result;
1012
+ }
1013
+ // Success - workflow deployed for all persona types
868
1014
  result.deployed_to = {
869
1015
  persona_id: newPersonaId,
870
1016
  persona_name: personaName,
@@ -904,6 +1050,88 @@ export async function handleWorkflow(args, client, getTemplateId) {
904
1050
  if (!persona) {
905
1051
  return { error: `Persona not found: ${personaId}` };
906
1052
  }
1053
+ // ─────────────────────────────────────────────────────────────────────────
1054
+ // CONFIG-ONLY DETECTION: Route to proto_config update instead of workflow
1055
+ // ─────────────────────────────────────────────────────────────────────────
1056
+ const lowerInput = input.toLowerCase();
1057
+ const isConfigOnlyUpdate = ((lowerInput.includes("voice settings") || lowerInput.includes("voice config")) ||
1058
+ (lowerInput.includes("welcome message") && !lowerInput.includes("add") && !lowerInput.includes("node")) ||
1059
+ (lowerInput.includes("identity") && lowerInput.includes("purpose")) ||
1060
+ (lowerInput.includes("speech characteristics")) ||
1061
+ (lowerInput.includes("hangup instructions") || lowerInput.includes("hangup")) ||
1062
+ (lowerInput.includes("update") && (lowerInput.includes("persona settings") ||
1063
+ lowerInput.includes("conversation settings") ||
1064
+ lowerInput.includes("proto_config") ||
1065
+ lowerInput.includes("chat settings"))));
1066
+ if (isConfigOnlyUpdate) {
1067
+ // This is a config update, not a workflow modification
1068
+ // Parse the config values from the input and update proto_config only
1069
+ const existingProtoConfig = (persona.proto_config ?? {});
1070
+ const existingWidgets = (existingProtoConfig.widgets ?? []);
1071
+ // Find and update the conversationSettings widget
1072
+ const widgetMap = new Map();
1073
+ for (const w of existingWidgets) {
1074
+ if (typeof w.name === "string" && w.name.trim().length > 0) {
1075
+ widgetMap.set(w.name, { ...w });
1076
+ }
1077
+ }
1078
+ // Parse voice config updates from input
1079
+ const configUpdates = {};
1080
+ // Extract welcome message
1081
+ const welcomeMatch = input.match(/welcome\s*message[:\s]*["']?([^"'\n]+(?:\n(?![0-9]+\.).*)*?)(?:["']|(?=\n\n|\n[0-9]+\.))/i);
1082
+ if (welcomeMatch) {
1083
+ configUpdates.welcomeMessage = welcomeMatch[1].trim().replace(/^["']|["']$/g, '');
1084
+ }
1085
+ // Extract identity and purpose
1086
+ const identityMatch = input.match(/identity\s*(?:and\s*)?purpose[:\s]*["']?(.+?)(?:["']|(?=\n\n[0-9]+\.|$))/is);
1087
+ if (identityMatch) {
1088
+ configUpdates.identityAndPurpose = identityMatch[1].trim().replace(/^["']|["']$/g, '');
1089
+ }
1090
+ // Extract speech characteristics
1091
+ const speechMatch = input.match(/speech\s*characteristics?[:\s]*["']?([^"'\n]+)["']?/i);
1092
+ if (speechMatch) {
1093
+ configUpdates.speechCharacteristics = speechMatch[1].trim().replace(/^["']|["']$/g, '');
1094
+ }
1095
+ // Extract hangup instructions
1096
+ const hangupMatch = input.match(/hangup\s*instructions?[:\s]*["']?(.+?)(?:["']|$)/is);
1097
+ if (hangupMatch) {
1098
+ configUpdates.hangupInstructions = hangupMatch[1].trim().replace(/^["']|["']$/g, '');
1099
+ }
1100
+ // Update conversationSettings widget
1101
+ const convWidget = widgetMap.get("conversationSettings") || {
1102
+ name: "conversationSettings",
1103
+ type: 39,
1104
+ conversationSettings: {},
1105
+ };
1106
+ const convSettings = (convWidget.conversationSettings ?? {});
1107
+ // Merge config updates
1108
+ for (const [key, value] of Object.entries(configUpdates)) {
1109
+ convSettings[key] = value;
1110
+ }
1111
+ convWidget.conversationSettings = convSettings;
1112
+ widgetMap.set("conversationSettings", convWidget);
1113
+ const mergedProtoConfig = {
1114
+ ...existingProtoConfig,
1115
+ widgets: Array.from(widgetMap.values()),
1116
+ };
1117
+ if (!preview) {
1118
+ // Deploy config update only - preserve existing workflow
1119
+ await client.updateAiEmployee({
1120
+ persona_id: personaId,
1121
+ proto_config: mergedProtoConfig,
1122
+ workflow: persona.workflow_def,
1123
+ });
1124
+ }
1125
+ return {
1126
+ mode: "config_update",
1127
+ status: preview ? "preview" : "deployed",
1128
+ persona_id: personaId,
1129
+ persona_name: persona.name,
1130
+ config_updates: configUpdates,
1131
+ note: "Updated persona configuration only. Workflow unchanged.",
1132
+ hint: "To modify the workflow itself, describe workflow changes like 'add search node' or 'add HITL before email'.",
1133
+ };
1134
+ }
907
1135
  const existingWorkflow = persona.workflow_def;
908
1136
  if (!existingWorkflow) {
909
1137
  return {
@@ -1139,6 +1367,8 @@ export async function handleWorkflow(args, client, getTemplateId) {
1139
1367
  if (!nodes) {
1140
1368
  return { error: "nodes required for compile mode" };
1141
1369
  }
1370
+ // Load action registry for API-driven action versions/namespaces
1371
+ const compileRegistry = await ensureActionRegistry(client);
1142
1372
  const spec = {
1143
1373
  name: args.name || "Compiled Workflow",
1144
1374
  description: args.description || "Generated workflow",
@@ -1146,10 +1376,11 @@ export async function handleWorkflow(args, client, getTemplateId) {
1146
1376
  nodes: nodes,
1147
1377
  result_mappings: resultMappings || [],
1148
1378
  };
1149
- const compiled = compileWorkflow(spec);
1379
+ const compiled = compileWorkflow(spec, { registry: compileRegistry });
1150
1380
  return {
1151
1381
  workflow_def: compiled.workflow_def,
1152
1382
  proto_config: compiled.proto_config,
1383
+ registry_loaded: compileRegistry.isLoaded(),
1153
1384
  };
1154
1385
  }
1155
1386
  case "optimize": {
@@ -2613,13 +2613,6 @@ const toolHandlers = {
2613
2613
  preserveExistingNodes: false, // Replace with incoming structure
2614
2614
  forceReplace: false, // Don't force if there are conflicts
2615
2615
  });
2616
- // #region agent log
2617
- // Log brownfield merge result
2618
- try {
2619
- fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/mcp/server.ts:deploy_workflow:brownfieldMerge', message: 'Brownfield merge result', data: { success: mergeResult.success, description: mergeResult.description, diff: { nodesAdded: mergeResult.diff.nodesAdded, nodesRemoved: mergeResult.diff.nodesRemoved, nodesModified: mergeResult.diff.nodesModified, enumsAdded: mergeResult.diff.enumsAdded, enumsModified: mergeResult.diff.enumsModified, hasHitlNodes: mergeResult.diff.hasHitlNodes, conflictCount: mergeResult.diff.conflicts.length }, warnings: mergeResult.warnings, errors: mergeResult.errors, requiresAutobuilder: mergeResult.requiresAutobuilder }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'brownfield-merge', hypothesisId: 'H12' }) }).catch(() => { });
2620
- }
2621
- catch { }
2622
- // #endregion agent log
2623
2616
  if (!mergeResult.success) {
2624
2617
  // Merge failed due to conflicts - return the errors
2625
2618
  return {
@@ -2639,12 +2632,6 @@ const toolHandlers = {
2639
2632
  // Validate the merged workflow structure
2640
2633
  const mergeValidation = validateMergedWorkflow(workflowDef);
2641
2634
  if (!mergeValidation.valid) {
2642
- // #region agent log
2643
- try {
2644
- fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/mcp/server.ts:deploy_workflow:mergeValidationFailed', message: 'Merged workflow validation failed', data: { issues: mergeValidation.issues }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'brownfield-merge', hypothesisId: 'H12' }) }).catch(() => { });
2645
- }
2646
- catch { }
2647
- // #endregion agent log
2648
2635
  return {
2649
2636
  environment: client["env"].name,
2650
2637
  success: false,
@@ -2660,12 +2647,6 @@ const toolHandlers = {
2660
2647
  // If HITL nodes are involved, force Autobuilder deployment
2661
2648
  // (Direct API doesn't support HITL workflow changes)
2662
2649
  if (mergeResult.requiresAutobuilder) {
2663
- // #region agent log
2664
- try {
2665
- fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/mcp/server.ts:deploy_workflow:hitlRequiresAutobuilder', message: 'HITL workflow requires Autobuilder', data: { hasHitlNodes: mergeResult.diff.hasHitlNodes, nodesAdded: mergeResult.diff.nodesAdded }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'brownfield-merge', hypothesisId: 'H11' }) }).catch(() => { });
2666
- }
2667
- catch { }
2668
- // #endregion agent log
2669
2650
  deploymentMethod = "autobuilder";
2670
2651
  }
2671
2652
  }
@@ -2824,14 +2805,6 @@ const toolHandlers = {
2824
2805
  actionOutputField.output = "hitl_status";
2825
2806
  inlineRhsField.enumValue = correctedEnumVal;
2826
2807
  hitlFixCount++;
2827
- // #region agent log
2828
- try {
2829
- if (process.env.EMA_MCP_DEBUG_LOGS === "1") {
2830
- fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/mcp/server.ts:deploy_workflow:hitlAutoFix', message: 'Auto-fixed malformed HITL runIf', data: { nodeName: String(action.name), originalOutput: outputStr, correctedOutput: 'hitl_status', correctedEnumValue: correctedEnumVal }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'hitl-fix', hypothesisId: 'H1' }) }).catch(() => { });
2831
- }
2832
- }
2833
- catch { }
2834
- // #endregion agent log
2835
2808
  }
2836
2809
  }
2837
2810
  }
@@ -2842,57 +2815,6 @@ const toolHandlers = {
2842
2815
  proto_config: mergedProtoConfig,
2843
2816
  workflow: workflowDef,
2844
2817
  };
2845
- // #region agent log
2846
- // H1/H2: confirm what we're attempting to deploy (structure only).
2847
- try {
2848
- if (process.env.EMA_MCP_DEBUG_LOGS !== "1") {
2849
- // disabled
2850
- }
2851
- else {
2852
- const wf = workflowDef ?? {};
2853
- const actions = wf.actions ?? [];
2854
- const actionNames = actions.map((a) => String(a.name ?? "")).filter(Boolean);
2855
- const actionTypeNames = actions
2856
- .map((a) => {
2857
- const an = a.action;
2858
- return String(an?.name?.name ?? "");
2859
- })
2860
- .filter(Boolean);
2861
- const hasHitl = actionTypeNames.some((t) => t.toLowerCase().includes("hitl")) || actionNames.some((n) => n.toLowerCase().includes("hitl"));
2862
- const hasTriggerWhen = actions.some((a) => {
2863
- const inputs = a.inputs;
2864
- return !!inputs && Object.prototype.hasOwnProperty.call(inputs, "trigger_when");
2865
- });
2866
- const runIfCount = actions.reduce((acc, a) => acc + (a.runIf ? 1 : 0), 0);
2867
- const wfName = wf.workflowName ?? {};
2868
- fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/mcp/server.ts:deploy_workflow:preDeploy', message: 'Deploy workflow request summary', data: { env: client["env"].name, personaId, validateFirst, autoFix, actionCount: actions.length, runIfCount, hasHitl, hasTriggerWhen, workflowName: wfName }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'pre-fix', hypothesisId: 'H1' }) }).catch(() => { });
2869
- // H1: Detect malformed HITL runIf patterns
2870
- const malformedHitlPatterns = [];
2871
- for (const action of actions) {
2872
- const runIf = action.runIf;
2873
- if (!runIf)
2874
- continue;
2875
- const lhs = runIf.lhs;
2876
- if (!lhs?.actionOutput)
2877
- continue;
2878
- const actionOutput = lhs.actionOutput;
2879
- const output = String(actionOutput.output ?? "");
2880
- // Malformed pattern: output contains both field name AND enum value like "hitl_status_HITL Success"
2881
- if (output.includes("hitl_status_HITL") || output.includes("hitl_status HITL")) {
2882
- malformedHitlPatterns.push({
2883
- nodeName: String(action.name),
2884
- output,
2885
- issue: `Should be output="hitl_status" with enumValue="${output.replace(/hitl_status[_\s]?/i, "")}"`
2886
- });
2887
- }
2888
- }
2889
- if (malformedHitlPatterns.length > 0) {
2890
- fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/mcp/server.ts:deploy_workflow:malformedHitl', message: 'DETECTED malformed HITL runIf patterns', data: { env: client["env"].name, personaId, malformedCount: malformedHitlPatterns.length, patterns: malformedHitlPatterns }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'debug-hitl', hypothesisId: 'H1' }) }).catch(() => { });
2891
- }
2892
- }
2893
- }
2894
- catch { }
2895
- // #endregion agent log
2896
2818
  // Deployment attempt with automatic fallback
2897
2819
  let deployedVia = "direct_api";
2898
2820
  let autobuilderResult;
@@ -2902,50 +2824,6 @@ const toolHandlers = {
2902
2824
  }
2903
2825
  catch (err) {
2904
2826
  const errorMessage = err instanceof Error ? err.message : String(err);
2905
- // #region agent log
2906
- // H1/H2: capture top-level failure classification (do not log secrets).
2907
- try {
2908
- if (process.env.EMA_MCP_DEBUG_LOGS !== "1") {
2909
- // disabled
2910
- }
2911
- else {
2912
- fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/mcp/server.ts:deploy_workflow:directApiError', message: 'Direct deploy failed', data: { env: client["env"].name, personaId, errorMessage }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'pre-fix', hypothesisId: 'H2' }) }).catch(() => { });
2913
- }
2914
- }
2915
- catch { }
2916
- // #endregion agent log
2917
- // H5: When update_persona returns a generic 500, attempt CheckWorkflow to extract structured validation errors.
2918
- // This is best-effort and only runs when debug logs are enabled to minimize extra traffic.
2919
- try {
2920
- if (process.env.EMA_MCP_DEBUG_LOGS === "1" && workflowDef && errorMessage.toLowerCase().includes("internal server error")) {
2921
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2922
- const check = await client.checkWorkflow(workflowDef);
2923
- const checkJson = JSON.stringify(check);
2924
- const snippet = checkJson.length > 3000 ? `${checkJson.slice(0, 3000)}…(truncated)` : checkJson;
2925
- fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/mcp/server.ts:deploy_workflow:checkWorkflow', message: 'CheckWorkflow output (post-500)', data: { env: client["env"].name, personaId, checkSnippet: snippet }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'pre-fix', hypothesisId: 'H5' }) }).catch(() => { });
2926
- }
2927
- }
2928
- catch (checkErr) {
2929
- try {
2930
- if (process.env.EMA_MCP_DEBUG_LOGS === "1") {
2931
- const m = checkErr instanceof Error ? checkErr.message : String(checkErr);
2932
- fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/mcp/server.ts:deploy_workflow:checkWorkflowError', message: 'CheckWorkflow failed (post-500)', data: { env: client["env"].name, personaId, error: m }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'pre-fix', hypothesisId: 'H5' }) }).catch(() => { });
2933
- }
2934
- }
2935
- catch { }
2936
- }
2937
- // #region agent log
2938
- // H2: Log fallback decision path for 500 errors
2939
- try {
2940
- if (process.env.EMA_MCP_DEBUG_LOGS === "1") {
2941
- const isNoExistingWorkflow = errorMessage.includes("Cannot set persona workflow without existing workflow");
2942
- const isNameMismatch = errorMessage.includes("Workflow name does not match");
2943
- const is500Error = errorMessage.toLowerCase().includes("internal server error") || errorMessage.includes("500");
2944
- fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/mcp/server.ts:deploy_workflow:fallbackDecision', message: 'Evaluating fallback path', data: { env: client["env"].name, personaId, errorMessage, isNoExistingWorkflow, isNameMismatch, is500Error, hasWorkflowDef: !!workflowDef, willFallbackToAutobuilder: isNoExistingWorkflow || is500Error }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'debug-fallback', hypothesisId: 'H2' }) }).catch(() => { });
2945
- }
2946
- }
2947
- catch { }
2948
- // #endregion agent log
2949
2827
  // If direct API fails due to "no existing workflow", try Auto Builder
2950
2828
  if (errorMessage.includes("Cannot set persona workflow without existing workflow") && workflowDef) {
2951
2829
  deploymentMethod = "autobuilder";
@@ -2957,15 +2835,7 @@ const toolHandlers = {
2957
2835
  `(Technical: ${errorMessage})`);
2958
2836
  }
2959
2837
  else if ((errorMessage.toLowerCase().includes("internal server error") || errorMessage.includes("500")) && workflowDef) {
2960
- // #region agent log
2961
- // H2: 500 error - attempt Autobuilder fallback instead of throwing
2962
- try {
2963
- if (process.env.EMA_MCP_DEBUG_LOGS === "1") {
2964
- fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/mcp/server.ts:deploy_workflow:500Fallback', message: '500 error - attempting Autobuilder fallback', data: { env: client["env"].name, personaId, errorMessage }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'debug-fallback', hypothesisId: 'H2' }) }).catch(() => { });
2965
- }
2966
- }
2967
- catch { }
2968
- // #endregion agent log
2838
+ // 500 error - attempt Autobuilder fallback
2969
2839
  deploymentMethod = "autobuilder";
2970
2840
  }
2971
2841
  else {
@@ -5408,34 +5278,6 @@ function generateEntityDocument(entityType, entity, related, tags) {
5408
5278
  const promptRegistry = new PromptRegistry();
5409
5279
  const resourceRegistry = new ResourceRegistry();
5410
5280
  export async function startMcpServer() {
5411
- // #region agent log
5412
- // H6: confirm which server instance is running (cwd/version/env).
5413
- try {
5414
- if (process.env.EMA_MCP_DEBUG_LOGS === "1") {
5415
- fetch("http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22", {
5416
- method: "POST",
5417
- headers: { "Content-Type": "application/json" },
5418
- body: JSON.stringify({
5419
- location: "src/mcp/server.ts:startMcpServer:startup",
5420
- message: "MCP server starting",
5421
- data: {
5422
- cwd: process.cwd(),
5423
- argv0to5: process.argv.slice(0, 6),
5424
- nodeEnv: process.env.NODE_ENV ?? null,
5425
- emaEnvName: process.env.EMA_ENV_NAME ?? null,
5426
- codeDir: process.env.EMA_MCP_CODE_DIR ?? null,
5427
- debugLogs: process.env.EMA_MCP_DEBUG_LOGS ?? null,
5428
- },
5429
- timestamp: Date.now(),
5430
- sessionId: "debug-session",
5431
- runId: "pre-fix",
5432
- hypothesisId: "H6",
5433
- }),
5434
- }).catch(() => { });
5435
- }
5436
- }
5437
- catch { }
5438
- // #endregion agent log
5439
5281
  const server = new Server({ name: "ema", version: "1.0.0" }, {
5440
5282
  capabilities: {
5441
5283
  tools: {},
@@ -5450,28 +5292,6 @@ export async function startMcpServer() {
5450
5292
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
5451
5293
  const { name, arguments: args } = request.params;
5452
5294
  const handler = toolHandlers[name];
5453
- // #region agent log
5454
- // H6: prove which tool is invoked and with which top-level keys (no secrets).
5455
- try {
5456
- if (process.env.EMA_MCP_DEBUG_LOGS === "1") {
5457
- const argKeys = args && typeof args === "object" ? Object.keys(args) : [];
5458
- fetch("http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22", {
5459
- method: "POST",
5460
- headers: { "Content-Type": "application/json" },
5461
- body: JSON.stringify({
5462
- location: "src/mcp/server.ts:CallToolRequestSchema:entry",
5463
- message: "MCP tool invoked",
5464
- data: { toolName: name, argKeys },
5465
- timestamp: Date.now(),
5466
- sessionId: "debug-session",
5467
- runId: "pre-fix",
5468
- hypothesisId: "H6",
5469
- }),
5470
- }).catch(() => { });
5471
- }
5472
- }
5473
- catch { }
5474
- // #endregion agent log
5475
5295
  if (!handler) {
5476
5296
  return {
5477
5297
  content: [{ type: "text", text: JSON.stringify({ error: `Unknown tool: ${name}` }) }],
@@ -5485,28 +5305,6 @@ export async function startMcpServer() {
5485
5305
  };
5486
5306
  }
5487
5307
  catch (error) {
5488
- // #region agent log
5489
- // H6: capture top-level handler exception message.
5490
- try {
5491
- if (process.env.EMA_MCP_DEBUG_LOGS === "1") {
5492
- const msg = error instanceof Error ? error.message : String(error);
5493
- fetch("http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22", {
5494
- method: "POST",
5495
- headers: { "Content-Type": "application/json" },
5496
- body: JSON.stringify({
5497
- location: "src/mcp/server.ts:CallToolRequestSchema:error",
5498
- message: "Tool handler threw",
5499
- data: { toolName: name, errorMessage: msg },
5500
- timestamp: Date.now(),
5501
- sessionId: "debug-session",
5502
- runId: "pre-fix",
5503
- hypothesisId: "H6",
5504
- }),
5505
- }).catch(() => { });
5506
- }
5507
- }
5508
- catch { }
5509
- // #endregion agent log
5510
5308
  return {
5511
5309
  content: [{ type: "text", text: JSON.stringify({ error: error instanceof Error ? error.message : String(error) }) }],
5512
5310
  isError: true,