@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.
- package/dist/mcp/handlers-consolidated.js +400 -14
- package/dist/mcp/prompts.js +80 -123
- package/dist/mcp/server.js +134 -209
- package/dist/mcp/tools-consolidated.js +212 -150
- package/dist/sdk/action-registry.js +128 -0
- package/dist/sdk/client.js +58 -90
- package/dist/sdk/demo-generator.js +978 -0
- package/dist/sdk/generated/api-types.js +11 -0
- package/dist/sdk/index.js +15 -1
- package/dist/sdk/knowledge.js +38 -8
- package/dist/sdk/quality-gates.js +386 -0
- package/dist/sdk/structural-rules.js +290 -0
- package/dist/sdk/workflow-generator.js +187 -39
- package/dist/sdk/workflow-intent.js +246 -24
- package/dist/sdk/workflow-optimizer.js +665 -0
- package/dist/sdk/workflow-tracer.js +648 -0
- package/dist/sdk/workflow-transformer.js +10 -0
- package/dist/sdk/workflow-validator.js +391 -0
- package/docs/.temp/datasource-attach.har +198369 -0
- package/docs/.temp/grpcweb.gar +1 -0
- package/docs/local-generation.md +508 -0
- package/docs/mcp-flow-diagram.md +135 -0
- package/docs/mcp-tools-guide.md +163 -197
- package/docs/openapi.json +8000 -0
- package/docs/release-process.md +153 -0
- package/docs/test-persona-creation.md +196 -0
- package/docs/tool-consolidation-proposal.md +166 -378
- package/package.json +3 -1
- package/resources/templates/demo-scenarios/README.md +63 -0
|
@@ -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 {
|
|
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:
|
|
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
|
-
//
|
|
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"
|
|
593
|
-
|
|
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
|
-
//
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
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
|
-
|
|
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
|
-
|
|
963
|
-
|
|
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": {
|