@ema.co/mcp-toolkit 1.5.1 → 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.

Potentially problematic release.


This version of @ema.co/mcp-toolkit might be problematic. Click here for more details.

@@ -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) {
@@ -238,12 +263,13 @@ export async function handlePersona(args, client, getTemplateId, createClientFor
238
263
  }
239
264
  // IMPORTANT: The Ema API requires workflow to be sent along with proto_config
240
265
  // for proto_config changes to persist. This matches what the UI does.
266
+ const workflowToSet = args.workflow ?? existingWorkflow;
241
267
  await client.updateAiEmployee({
242
268
  persona_id: persona.id,
243
269
  name: args.name,
244
270
  description: args.description,
245
271
  proto_config: mergedProtoConfig,
246
- workflow: existingWorkflow, // Include workflow for proto_config to persist
272
+ workflow: workflowToSet, // Use provided workflow or existing workflow for proto_config to persist
247
273
  enabled_by_user: typeof args.enabled === "boolean" ? args.enabled : undefined,
248
274
  });
249
275
  return {
@@ -544,7 +570,7 @@ export async function handlePersona(args, client, getTemplateId, createClientFor
544
570
  // ═══════════════════════════════════════════════════════════════════════════
545
571
  // WORKFLOW Handler - Unified greenfield/brownfield operations
546
572
  // ═══════════════════════════════════════════════════════════════════════════
547
- export async function handleWorkflow(args, client) {
573
+ export async function handleWorkflow(args, client, getTemplateId) {
548
574
  const personaId = args.persona_id;
549
575
  const workflowDef = args.workflow_def;
550
576
  const input = args.input;
@@ -570,8 +596,12 @@ export async function handleWorkflow(args, client) {
570
596
  // Auto-fix issues
571
597
  effectiveMode = "optimize";
572
598
  }
599
+ else if (personaId && workflowDef) {
600
+ // DEPLOY: Persona exists + workflow_def provided → deploy directly (no input needed)
601
+ effectiveMode = "deploy";
602
+ }
573
603
  else if (personaId && input) {
574
- // BROWNFIELD: Modify existing workflow
604
+ // MODIFY: Persona exists + input provided → generate new workflow from input
575
605
  effectiveMode = "modify"; // New unified mode for all brownfield changes
576
606
  }
577
607
  else if (input && !personaId) {
@@ -589,8 +619,10 @@ export async function handleWorkflow(args, client) {
589
619
  const legacyMode = args.mode;
590
620
  if (legacyMode === "extend")
591
621
  effectiveMode = "modify";
592
- if (legacyMode === "deploy" && personaId && input)
593
- effectiveMode = "modify";
622
+ if (legacyMode === "deploy") {
623
+ // If explicit deploy mode, use it (works with or without input)
624
+ effectiveMode = "deploy";
625
+ }
594
626
  switch (effectiveMode) {
595
627
  case "extraction_schema": {
596
628
  // Generate extraction schema from JSON metadata (fully dynamic, no hardcoding)
@@ -745,6 +777,8 @@ export async function handleWorkflow(args, client) {
745
777
  if (!input) {
746
778
  return { error: "input required for generate mode" };
747
779
  }
780
+ // Load action registry for API-driven action versions/namespaces
781
+ const actionRegistry = await ensureActionRegistry(client);
748
782
  const parseResult = parseInput(input);
749
783
  if (!parseResult.validation.complete) {
750
784
  return {
@@ -754,10 +788,75 @@ export async function handleWorkflow(args, client) {
754
788
  questions: parseResult.validation.questions,
755
789
  };
756
790
  }
757
- // Convert to spec and compile
758
- const spec = intentToSpec(parseResult.intent);
759
- const compiled = compileWorkflow(spec);
760
- // 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)
761
860
  const issues = detectWorkflowIssues(compiled.workflow_def);
762
861
  const result = {
763
862
  mode: "generate",
@@ -766,6 +865,14 @@ export async function handleWorkflow(args, client) {
766
865
  proto_config: compiled.proto_config,
767
866
  validation: parseResult.validation,
768
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
+ }
769
876
  if (issues.length > 0) {
770
877
  result.issues = issues;
771
878
  }
@@ -783,7 +890,141 @@ export async function handleWorkflow(args, client) {
783
890
  result.deployed_to = { persona_id: personaId, persona_name: persona.name };
784
891
  }
785
892
  else if (!preview && !personaId) {
786
- result.hint = "Provide persona_id to deploy, or use persona(mode='create') first";
893
+ // GREENFIELD: Create new persona from template, then deploy workflow + proto_config
894
+ const personaName = args.name;
895
+ if (personaName) {
896
+ const personaType = args.type || "chat";
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)
902
+ };
903
+ // Use registry to find template by type
904
+ const templateFromRegistry = actionRegistry.getTemplateForType(personaType);
905
+ const templateId = getTemplateId?.(personaType) || templateFromRegistry?.id || FALLBACK_TEMPLATES[personaType];
906
+ if (!templateId) {
907
+ return {
908
+ error: `No template found for type "${personaType}". Provide template_id or use type: voice, chat, or dashboard.`
909
+ };
910
+ }
911
+ // Step 1: Create the persona from template
912
+ const createResult = await client.createAiEmployee({
913
+ name: personaName,
914
+ description: args.description,
915
+ template_id: templateId,
916
+ });
917
+ const newPersonaId = createResult.persona_id ?? createResult.id;
918
+ if (!newPersonaId) {
919
+ return { error: "Failed to create persona: no ID returned" };
920
+ }
921
+ // Step 2: Fetch the newly created persona to get template's valid structure
922
+ const newPersona = await client.getPersonaById(newPersonaId);
923
+ if (!newPersona) {
924
+ return { error: `Failed to fetch newly created persona: ${newPersonaId}` };
925
+ }
926
+ // Step 3: Merge proto_config - keep template structure, update widget values
927
+ const existingProtoConfig = (newPersona.proto_config ?? {});
928
+ const generatedProtoConfig = args.proto_config || compiled.proto_config || {};
929
+ // Merge widgets by name - template widgets provide structure, generated values override
930
+ const existingWidgets = (existingProtoConfig.widgets ?? []);
931
+ const generatedWidgets = (generatedProtoConfig.widgets ?? []);
932
+ const widgetMap = new Map();
933
+ // Start with template widgets (valid structure)
934
+ for (const w of existingWidgets) {
935
+ if (typeof w.name === "string" && w.name.trim().length > 0) {
936
+ widgetMap.set(w.name, w);
937
+ }
938
+ }
939
+ // Merge generated widget VALUES into template structure
940
+ for (const genWidget of generatedWidgets) {
941
+ const widgetName = genWidget.name;
942
+ if (typeof widgetName === "string" && widgetName.trim().length > 0) {
943
+ const existing = widgetMap.get(widgetName);
944
+ if (existing) {
945
+ // Merge: keep template structure, update config values
946
+ const merged = { ...existing };
947
+ // The widget config is stored under the widget name (e.g., conversationSettings)
948
+ if (genWidget[widgetName]) {
949
+ merged[widgetName] = { ...(existing[widgetName] || {}), ...genWidget[widgetName] };
950
+ }
951
+ widgetMap.set(widgetName, merged);
952
+ }
953
+ else {
954
+ // New widget not in template - add as-is
955
+ widgetMap.set(widgetName, genWidget);
956
+ }
957
+ }
958
+ }
959
+ const mergedProtoConfig = {
960
+ ...existingProtoConfig,
961
+ widgets: Array.from(widgetMap.values()),
962
+ };
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
1014
+ result.deployed_to = {
1015
+ persona_id: newPersonaId,
1016
+ persona_name: personaName,
1017
+ created: true
1018
+ };
1019
+ result.status = "deployed";
1020
+ result.next_steps = [
1021
+ `Persona "${personaName}" created with template workflow.`,
1022
+ `To customize the workflow, use: workflow(persona_id="${newPersonaId}", input="add search node", preview=false)`,
1023
+ ];
1024
+ }
1025
+ else {
1026
+ result.hint = "Provide name to create new persona, or persona_id to deploy to existing persona";
1027
+ }
787
1028
  }
788
1029
  if (preview) {
789
1030
  result.next_steps = [
@@ -809,6 +1050,88 @@ export async function handleWorkflow(args, client) {
809
1050
  if (!persona) {
810
1051
  return { error: `Persona not found: ${personaId}` };
811
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
+ }
812
1135
  const existingWorkflow = persona.workflow_def;
813
1136
  if (!existingWorkflow) {
814
1137
  return {
@@ -959,8 +1282,68 @@ export async function handleWorkflow(args, client) {
959
1282
  }
960
1283
  return result;
961
1284
  }
962
- // Note: mode="deploy" is now handled by preview=false on extend/optimize/generate
963
- // Legacy calls are converted at the top of the handler
1285
+ case "deploy": {
1286
+ // DEPLOY: Direct workflow deployment to existing persona (no input required)
1287
+ if (!personaId) {
1288
+ return { error: "persona_id required for deploy mode" };
1289
+ }
1290
+ if (!workflowDef) {
1291
+ return { error: "workflow_def required for deploy mode" };
1292
+ }
1293
+ const persona = await client.getPersonaById(personaId);
1294
+ if (!persona) {
1295
+ return { error: `Persona not found: ${personaId}` };
1296
+ }
1297
+ // Sanitize workflow before deployment
1298
+ const sanitizedWorkflow = sanitizeWorkflowForDeploy(workflowDef);
1299
+ // Determine proto_config to use: provided > existing
1300
+ const existingProtoConfig = persona.proto_config;
1301
+ const providedProtoConfig = args.proto_config;
1302
+ const protoConfigToUse = providedProtoConfig || existingProtoConfig || {};
1303
+ // Check if this is a voice persona and warn if critical settings are missing
1304
+ const projectSettings = protoConfigToUse.projectSettings;
1305
+ const isVoice = projectSettings?.projectType === 5;
1306
+ const warnings = [];
1307
+ if (isVoice && !providedProtoConfig) {
1308
+ // Check if existing proto_config has conversationSettings populated
1309
+ const widgets = (protoConfigToUse.widgets ?? []);
1310
+ const convSettings = widgets.find(w => w.name === "conversationSettings");
1311
+ const convConfig = convSettings?.conversationSettings;
1312
+ if (!convConfig?.welcomeMessage || !convConfig?.identityAndPurpose) {
1313
+ warnings.push("Voice persona detected but proto_config not provided. Voice settings (welcomeMessage, identityAndPurpose, etc.) may be empty or generic. " +
1314
+ "If you generated the workflow with workflow(input=...), pass the generated proto_config along with workflow_def to preserve voice settings.");
1315
+ }
1316
+ }
1317
+ // Deploy workflow (always deploy, preview is not applicable for explicit deploy mode)
1318
+ try {
1319
+ await client.updateAiEmployee({
1320
+ persona_id: personaId,
1321
+ workflow: sanitizedWorkflow,
1322
+ proto_config: protoConfigToUse,
1323
+ });
1324
+ const result = {
1325
+ mode: "deploy",
1326
+ status: "deployed",
1327
+ persona_id: personaId,
1328
+ persona_name: persona.name,
1329
+ workflow_deployed: true,
1330
+ };
1331
+ if (warnings.length > 0) {
1332
+ result.warnings = warnings;
1333
+ }
1334
+ return result;
1335
+ }
1336
+ catch (err) {
1337
+ const errorMessage = err instanceof Error ? err.message : String(err);
1338
+ return {
1339
+ mode: "deploy",
1340
+ status: "failed",
1341
+ persona_id: personaId,
1342
+ persona_name: persona.name,
1343
+ error: errorMessage,
1344
+ };
1345
+ }
1346
+ }
964
1347
  case "compare": {
965
1348
  if (!personaId || !args.compare_to) {
966
1349
  return { error: "persona_id and compare_to required for compare mode" };
@@ -984,6 +1367,8 @@ export async function handleWorkflow(args, client) {
984
1367
  if (!nodes) {
985
1368
  return { error: "nodes required for compile mode" };
986
1369
  }
1370
+ // Load action registry for API-driven action versions/namespaces
1371
+ const compileRegistry = await ensureActionRegistry(client);
987
1372
  const spec = {
988
1373
  name: args.name || "Compiled Workflow",
989
1374
  description: args.description || "Generated workflow",
@@ -991,10 +1376,11 @@ export async function handleWorkflow(args, client) {
991
1376
  nodes: nodes,
992
1377
  result_mappings: resultMappings || [],
993
1378
  };
994
- const compiled = compileWorkflow(spec);
1379
+ const compiled = compileWorkflow(spec, { registry: compileRegistry });
995
1380
  return {
996
1381
  workflow_def: compiled.workflow_def,
997
1382
  proto_config: compiled.proto_config,
1383
+ registry_loaded: compileRegistry.isLoaded(),
998
1384
  };
999
1385
  }
1000
1386
  case "optimize": {