@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.
- package/README.md +10 -2
- package/dist/mcp/handlers/action/index.js +3 -18
- package/dist/mcp/handlers/data/index.js +385 -41
- package/dist/mcp/handlers/data/templates.js +107 -0
- package/dist/mcp/handlers/deprecation.js +50 -0
- package/dist/mcp/handlers/env/index.js +8 -4
- package/dist/mcp/handlers/knowledge/index.js +44 -237
- package/dist/mcp/handlers/persona/create.js +47 -18
- package/dist/mcp/handlers/persona/index.js +14 -11
- package/dist/mcp/handlers/persona/update.js +4 -2
- package/dist/mcp/handlers/persona/version.js +234 -0
- package/dist/mcp/handlers/sync/index.js +3 -18
- package/dist/mcp/handlers/template/index.js +75 -10
- package/dist/mcp/handlers/workflow/analyze.js +171 -0
- package/dist/mcp/handlers/workflow/compare.js +70 -0
- package/dist/mcp/handlers/workflow/deploy.js +73 -0
- package/dist/mcp/handlers/workflow/generate.js +350 -0
- package/dist/mcp/handlers/workflow/index.js +294 -0
- package/dist/mcp/handlers/workflow/modify.js +456 -0
- package/dist/mcp/handlers/workflow/optimize.js +136 -0
- package/dist/mcp/handlers/workflow/types.js +4 -0
- package/dist/mcp/handlers/workflow/utils.js +30 -0
- package/dist/mcp/handlers-consolidated.js +73 -2696
- package/dist/mcp/prompts.js +83 -43
- package/dist/mcp/resources.js +382 -57
- package/dist/mcp/server.js +199 -391
- package/dist/mcp/{tools-v2.js → tools.js} +20 -54
- package/dist/mcp/workflow-operations.js +2 -2
- package/dist/sdk/client-adapter.js +267 -32
- package/dist/sdk/client.js +45 -16
- package/dist/sdk/ema-client.js +183 -0
- package/dist/sdk/generated/deprecated-actions.js +171 -0
- package/dist/sdk/generated/template-fallbacks.js +123 -0
- package/dist/sdk/guidance.js +65 -11
- package/dist/sdk/index.js +3 -1
- package/dist/sdk/knowledge.js +139 -86
- package/dist/sdk/workflow-intent.js +27 -0
- package/dist/sdk/workflow-transformer.js +0 -342
- package/docs/mcp-tools-guide.md +37 -45
- package/package.json +10 -4
- package/dist/mcp/handlers/persona/analyze.js +0 -275
- package/dist/mcp/handlers/persona/compare.js +0 -32
- package/dist/mcp/tools-consolidated.js +0 -875
- package/dist/mcp/tools-legacy.js +0 -736
- package/docs/CODEBASE-ANALYSIS-2026-01-23.md +0 -936
- package/docs/CODEBASE-ANALYSIS-PRIORITIZED.md +0 -774
- package/docs/api-contracts.md +0 -216
- package/docs/auto-builder-analysis.md +0 -271
- package/docs/blog/mcp-tool-design-lessons.md +0 -309
- package/docs/data-architecture.md +0 -166
- package/docs/demos/ap-invoice-generation.md +0 -347
- package/docs/demos/ap-invoice-processing.md +0 -271
- package/docs/ema-auto-builder-guide.html +0 -394
- package/docs/lessons-learned.md +0 -209
- package/docs/llm-native-workflow-design.md +0 -252
- package/docs/local-generation.md +0 -508
- package/docs/mcp-flow-diagram.md +0 -135
- package/docs/migration/action-composition-migration.md +0 -270
- package/docs/naming-conventions.md +0 -278
- package/docs/proposals/HANDOFF-tool-restructure.md +0 -526
- package/docs/proposals/action-composition.md +0 -490
- package/docs/proposals/explicit-method-restructure.md +0 -328
- package/docs/proposals/mcp-tool-restructure-2026-01.md +0 -366
- package/docs/proposals/self-contained-guidance.md +0 -427
- package/docs/proto-sdk-generation.md +0 -242
- package/docs/release-impact.md +0 -102
- package/docs/release-process.md +0 -157
- package/docs/staging.RULE.md +0 -142
- package/docs/test-persona-creation.md +0 -196
- package/docs/tool-consolidation-v2.md +0 -225
- package/docs/tool-response-standards.md +0 -256
- package/resources/demo-kits/README.md +0 -175
- package/resources/demo-kits/finance-ap/manifest.json +0 -150
- package/resources/demo-kits/tags.json +0 -91
- package/resources/docs/getting-started.md +0 -97
- package/resources/templates/auto-builder-rules.md +0 -224
- package/resources/templates/chat-ai/README.md +0 -119
- package/resources/templates/chat-ai/persona-config.json +0 -111
- package/resources/templates/dashboard-ai/README.md +0 -156
- package/resources/templates/dashboard-ai/persona-config.json +0 -180
- package/resources/templates/demo-scenarios/README.md +0 -63
- package/resources/templates/demo-scenarios/test-published-package.md +0 -116
- package/resources/templates/document-gen-ai/README.md +0 -132
- package/resources/templates/document-gen-ai/persona-config.json +0 -316
- package/resources/templates/voice-ai/README.md +0 -123
- package/resources/templates/voice-ai/persona-config.json +0 -74
- package/resources/templates/voice-ai/workflow-prompt.md +0 -121
package/dist/sdk/knowledge.js
CHANGED
|
@@ -920,90 +920,15 @@ export const QUALIFYING_QUESTIONS = [
|
|
|
920
920
|
{ category: "Guardrails", question: "Are there compliance requirements? (regulations, policies)", whyItMatters: "May need response_validator or compliance_document_analyzer", required: false },
|
|
921
921
|
];
|
|
922
922
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
923
|
-
// Voice AI Templates
|
|
923
|
+
// Voice AI Templates - REMOVED
|
|
924
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
925
|
+
// VOICE_PERSONA_TEMPLATE has been removed.
|
|
926
|
+
// Use the API-first approach instead:
|
|
927
|
+
// 1. client.getPersonaTemplates() - source of truth from API
|
|
928
|
+
// 2. getTemplateFallback("voice") from src/sdk/generated/template-fallbacks.ts
|
|
929
|
+
//
|
|
930
|
+
// See: scripts/generate-templates.ts for how fallbacks are generated from protos.
|
|
924
931
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
925
|
-
export const VOICE_PERSONA_TEMPLATE = {
|
|
926
|
-
welcomeMessage: "Hello, thank you for calling {Company}. This is {AI Name}. How can I help you today?",
|
|
927
|
-
identityAndPurpose: `You are {AI Name}, a Voice AI assistant for {Company}.
|
|
928
|
-
|
|
929
|
-
Primary responsibility: {Main purpose}
|
|
930
|
-
|
|
931
|
-
Your responsibilities:
|
|
932
|
-
1. {Responsibility 1}
|
|
933
|
-
2. {Responsibility 2}
|
|
934
|
-
3. {Responsibility 3}
|
|
935
|
-
|
|
936
|
-
Rules:
|
|
937
|
-
- {Rule 1}
|
|
938
|
-
- {Rule 2}
|
|
939
|
-
- Always be professional and helpful
|
|
940
|
-
- Never provide information you're not certain about`,
|
|
941
|
-
takeActionInstructions: `</Case 1>
|
|
942
|
-
{Action 1 Name}
|
|
943
|
-
|
|
944
|
-
Trigger When:
|
|
945
|
-
{Condition that triggers this action}
|
|
946
|
-
|
|
947
|
-
Intent for tool call: "{Tool Intent Name}"
|
|
948
|
-
|
|
949
|
-
Required parameters:
|
|
950
|
-
{ "{param1}": "", "{param2}": "" }
|
|
951
|
-
</Case 1>
|
|
952
|
-
|
|
953
|
-
</Case 2>
|
|
954
|
-
{Action 2 Name}
|
|
955
|
-
|
|
956
|
-
Trigger When:
|
|
957
|
-
{Condition that triggers this action}
|
|
958
|
-
|
|
959
|
-
Intent for tool call: "{Tool Intent Name}"
|
|
960
|
-
|
|
961
|
-
Required parameters:
|
|
962
|
-
{ "{param1}": "" }
|
|
963
|
-
</Case 2>`,
|
|
964
|
-
hangupInstructions: `End the call when:
|
|
965
|
-
- The caller explicitly says goodbye or asks to hang up
|
|
966
|
-
- The caller confirms they have no more questions
|
|
967
|
-
- {Additional hangup conditions}
|
|
968
|
-
|
|
969
|
-
Before ending:
|
|
970
|
-
- Confirm all issues are resolved
|
|
971
|
-
- Offer any follow-up information
|
|
972
|
-
- Thank the caller`,
|
|
973
|
-
transferCallInstructions: `Transfer the call when:
|
|
974
|
-
- The caller explicitly requests a human agent
|
|
975
|
-
- The issue is beyond AI capabilities
|
|
976
|
-
- {Complex scenario requiring human}
|
|
977
|
-
|
|
978
|
-
Before transferring:
|
|
979
|
-
- Inform the caller you're transferring them
|
|
980
|
-
- Summarize the issue for the human agent`,
|
|
981
|
-
speechCharacteristics: `**Conversational Style:**
|
|
982
|
-
- Keep responses brief (2-3 sentences per turn)
|
|
983
|
-
- Use warm, professional tone
|
|
984
|
-
- Speak clearly at moderate pace
|
|
985
|
-
|
|
986
|
-
**Natural Speech:**
|
|
987
|
-
- Use brief pauses between sentences
|
|
988
|
-
- Acknowledge with 'I understand', 'Of course', 'Certainly'
|
|
989
|
-
- Avoid robotic language
|
|
990
|
-
|
|
991
|
-
**TTS Pronunciation Rules:**
|
|
992
|
-
- Spell out IDs: 'A-B-C-1-2-3'
|
|
993
|
-
- Pause between numbers: 'Your code is... 1... 2... 3... 4'
|
|
994
|
-
- {Domain-specific pronunciation rules}`,
|
|
995
|
-
systemPrompt: `Tool Calling Instructions:
|
|
996
|
-
|
|
997
|
-
1. Always collect ALL required parameters before calling a tool
|
|
998
|
-
2. Confirm parameter values with the caller before executing
|
|
999
|
-
3. Wait for tool response before calling another tool
|
|
1000
|
-
4. NEVER mention tool names to the user
|
|
1001
|
-
5. NEVER guess parameter values - always ask
|
|
1002
|
-
6. Use plain language: "Let me look that up" not "Calling the API"
|
|
1003
|
-
7. Handle delays with wait message: "One moment while I check..."
|
|
1004
|
-
8. Handle errors gracefully, offer alternatives`,
|
|
1005
|
-
waitMessage: "One moment while I look that up for you...",
|
|
1006
|
-
};
|
|
1007
932
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1008
933
|
// Common Mistakes & Debugging
|
|
1009
934
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -2592,13 +2517,18 @@ function detectUnusedCategories(nodes, workflowDef) {
|
|
|
2592
2517
|
const categorizers = nodes.filter(n => n.action_name === "chat_categorizer" ||
|
|
2593
2518
|
n.action_name === "intent_classifier");
|
|
2594
2519
|
for (const categorizer of categorizers) {
|
|
2595
|
-
// Find the enumType for this categorizer
|
|
2596
|
-
|
|
2520
|
+
// Find the enumType for this categorizer using typeArguments reference (robust)
|
|
2521
|
+
// The action's typeArguments.categories.enumType points to the correct enumType
|
|
2522
|
+
const categorizerAction = actions.find(a => a.name === categorizer.id);
|
|
2523
|
+
const typeArgs = categorizerAction?.typeArguments;
|
|
2524
|
+
const enumTypeRefName = typeArgs?.categories?.enumType?.name?.name;
|
|
2597
2525
|
const enumType = enumTypes.find(e => {
|
|
2598
2526
|
const name = e.name;
|
|
2599
2527
|
const innerName = name?.name;
|
|
2600
2528
|
const nameStr = String(innerName?.name ?? name?.name ?? "");
|
|
2601
|
-
|
|
2529
|
+
// Match by typeArguments reference (preferred) or by categorizer ID (fallback)
|
|
2530
|
+
// DO NOT use generic "enumType" fallback - that causes cross-categorizer pollution
|
|
2531
|
+
return (enumTypeRefName && nameStr === enumTypeRefName) || nameStr.includes(categorizer.id);
|
|
2602
2532
|
});
|
|
2603
2533
|
if (!enumType)
|
|
2604
2534
|
continue;
|
|
@@ -3768,3 +3698,126 @@ ${issue.missing?.includes("failure") || issue.missing?.includes("both") ? `
|
|
|
3768
3698
|
}
|
|
3769
3699
|
return fixes;
|
|
3770
3700
|
}
|
|
3701
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3702
|
+
// Deprecated Actions Registry (GENERATED - DO NOT EDIT)
|
|
3703
|
+
//
|
|
3704
|
+
// PRIMARY SOURCE: API via client.listActions() where action.deprecated === true
|
|
3705
|
+
// FALLBACK: Generated from ema repo via: npm run generate:deprecated-actions
|
|
3706
|
+
//
|
|
3707
|
+
// The generated file is synced automatically by GH Actions from ema repo.
|
|
3708
|
+
// To manually regenerate: npm run generate:deprecated-actions -- --path /path/to/ema
|
|
3709
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3710
|
+
// Re-export from generated file (maintains backwards compatibility)
|
|
3711
|
+
export { DEPRECATED_ACTIONS_WITH_REPLACEMENT, DEPRECATED_ACTIONS_NO_REPLACEMENT, ALL_DEPRECATED_ACTIONS, DEPRECATED_AGENT_TYPES, DEPRECATED_ACTIONS_MAP, isActionDeprecated, getActionReplacement, } from "./generated/deprecated-actions.js";
|
|
3712
|
+
/**
|
|
3713
|
+
* Constraints that must be satisfied for a workflow to be enabled.
|
|
3714
|
+
* These are checked by the Ema backend when attempting to enable a persona.
|
|
3715
|
+
*/
|
|
3716
|
+
export const WORKFLOW_ENABLING_CONSTRAINTS = [
|
|
3717
|
+
{
|
|
3718
|
+
id: 1,
|
|
3719
|
+
name: "has_outputs",
|
|
3720
|
+
description: "Workflow must have at least one output wired to WORKFLOW_OUTPUT",
|
|
3721
|
+
errorState: "WORKFLOW_CONTEXT_MISMATCH",
|
|
3722
|
+
errorMessage: "Workflow has no outputs",
|
|
3723
|
+
fix: "Add results.WORKFLOW_OUTPUT = { actionName: '<response_node>', outputName: '<output_field>' }",
|
|
3724
|
+
critical: true,
|
|
3725
|
+
},
|
|
3726
|
+
{
|
|
3727
|
+
id: 2,
|
|
3728
|
+
name: "is_runnable",
|
|
3729
|
+
description: "Workflow must have no missing inputs (all required inputs must be wired)",
|
|
3730
|
+
errorState: "WORKFLOW_INVALID",
|
|
3731
|
+
errorMessage: "Workflow has missing inputs",
|
|
3732
|
+
fix: "Wire all required inputs for each action. Check action schemas for required fields.",
|
|
3733
|
+
critical: true,
|
|
3734
|
+
},
|
|
3735
|
+
{
|
|
3736
|
+
id: 3,
|
|
3737
|
+
name: "no_type_mismatches",
|
|
3738
|
+
description: "All input bindings must have compatible types with their source outputs",
|
|
3739
|
+
errorState: "WORKFLOW_INVALID",
|
|
3740
|
+
errorMessage: "Type mismatch in input bindings",
|
|
3741
|
+
fix: "Ensure source output types match target input types (e.g., TEXT_WITH_SOURCES → TEXT_WITH_SOURCES)",
|
|
3742
|
+
critical: true,
|
|
3743
|
+
},
|
|
3744
|
+
{
|
|
3745
|
+
id: 4,
|
|
3746
|
+
name: "no_cycles",
|
|
3747
|
+
description: "Workflow graph must be acyclic (no circular dependencies)",
|
|
3748
|
+
errorState: "WORKFLOW_INVALID",
|
|
3749
|
+
errorMessage: "Workflow contains cycles",
|
|
3750
|
+
fix: "Remove circular dependencies between nodes. Use parallel branches instead.",
|
|
3751
|
+
critical: true,
|
|
3752
|
+
},
|
|
3753
|
+
{
|
|
3754
|
+
id: 5,
|
|
3755
|
+
name: "no_named_result_errors",
|
|
3756
|
+
description: "All named results must reference valid action outputs",
|
|
3757
|
+
errorState: "WORKFLOW_INVALID",
|
|
3758
|
+
errorMessage: "Named result references invalid action or output",
|
|
3759
|
+
fix: "Verify WORKFLOW_OUTPUT references an existing action name and output field",
|
|
3760
|
+
critical: true,
|
|
3761
|
+
},
|
|
3762
|
+
{
|
|
3763
|
+
id: 6,
|
|
3764
|
+
name: "widgets_exist",
|
|
3765
|
+
description: "All widgets required by actions must exist in persona config",
|
|
3766
|
+
errorState: "WORKFLOW_CONTEXT_MISMATCH",
|
|
3767
|
+
errorMessage: "Missing required widgets",
|
|
3768
|
+
fix: "Add required widgets to proto_config (e.g., conversationSettings for voice)",
|
|
3769
|
+
critical: false,
|
|
3770
|
+
},
|
|
3771
|
+
{
|
|
3772
|
+
id: 7,
|
|
3773
|
+
name: "widgets_ready",
|
|
3774
|
+
description: "FILE_UPLOAD widgets must have ready=true (files processed)",
|
|
3775
|
+
errorState: "WORKFLOW_CONTEXT_MISMATCH",
|
|
3776
|
+
errorMessage: "Widgets not ready",
|
|
3777
|
+
fix: "Wait for file upload processing to complete before enabling",
|
|
3778
|
+
critical: false,
|
|
3779
|
+
},
|
|
3780
|
+
];
|
|
3781
|
+
/**
|
|
3782
|
+
* Minimum viable workflow structure by trigger type.
|
|
3783
|
+
* Source: ema/ema_backend/db/system_values/persona_templates/
|
|
3784
|
+
*/
|
|
3785
|
+
export const MINIMUM_VIABLE_WORKFLOWS = {
|
|
3786
|
+
CHAT: {
|
|
3787
|
+
triggerType: "CHAT",
|
|
3788
|
+
requiredNodes: ["chat_trigger"],
|
|
3789
|
+
requiredOutputs: ["WORKFLOW_OUTPUT"],
|
|
3790
|
+
exampleStructure: "chat_trigger → call_llm/custom_agent → WORKFLOW_OUTPUT",
|
|
3791
|
+
notes: "Minimal chat requires trigger and one response-producing action",
|
|
3792
|
+
},
|
|
3793
|
+
CHATBOT: {
|
|
3794
|
+
triggerType: "CHATBOT",
|
|
3795
|
+
requiredNodes: ["chat_trigger"],
|
|
3796
|
+
requiredOutputs: ["WORKFLOW_OUTPUT"],
|
|
3797
|
+
requiredWidgets: ["chatbotSdkConfig"],
|
|
3798
|
+
exampleStructure: "chat_trigger → call_llm/custom_agent → WORKFLOW_OUTPUT",
|
|
3799
|
+
notes: "Same as CHAT but requires chatbotSdkConfig widget for branding",
|
|
3800
|
+
},
|
|
3801
|
+
VOICE: {
|
|
3802
|
+
triggerType: "CHAT", // Voice uses CHAT trigger type
|
|
3803
|
+
requiredNodes: ["chat_trigger"],
|
|
3804
|
+
requiredOutputs: ["WORKFLOW_OUTPUT"],
|
|
3805
|
+
requiredWidgets: ["conversationSettings", "voiceSettings", "callSettings", "vadSettings"],
|
|
3806
|
+
exampleStructure: "chat_trigger → call_llm/custom_agent → WORKFLOW_OUTPUT",
|
|
3807
|
+
notes: "Voice uses CHAT trigger but requires voice-specific widgets for call handling",
|
|
3808
|
+
},
|
|
3809
|
+
DASHBOARD: {
|
|
3810
|
+
triggerType: "DASHBOARD",
|
|
3811
|
+
requiredNodes: ["multi-trigger-doc"],
|
|
3812
|
+
requiredOutputs: ["WORKFLOW_OUTPUT"],
|
|
3813
|
+
exampleStructure: "multi-trigger-doc → custom_agent/extraction → WORKFLOW_OUTPUT",
|
|
3814
|
+
notes: "Dashboard workflows process documents, typically with extraction or analysis",
|
|
3815
|
+
},
|
|
3816
|
+
THREAD: {
|
|
3817
|
+
triggerType: "THREAD",
|
|
3818
|
+
requiredNodes: ["thread_trigger"],
|
|
3819
|
+
requiredOutputs: ["WORKFLOW_OUTPUT"],
|
|
3820
|
+
exampleStructure: "thread_trigger → handler → WORKFLOW_OUTPUT",
|
|
3821
|
+
notes: "Thread workflows handle ticket/agent assist scenarios",
|
|
3822
|
+
},
|
|
3823
|
+
};
|
|
@@ -528,7 +528,25 @@ const PERSONA_TYPE_PATTERNS = [
|
|
|
528
528
|
{ pattern: /voice|call|phone/i, type: "voice" },
|
|
529
529
|
{ pattern: /document|batch|upload/i, type: "dashboard" },
|
|
530
530
|
];
|
|
531
|
+
/**
|
|
532
|
+
* @deprecated Use LLM-based intent extraction instead of regex parsing.
|
|
533
|
+
*
|
|
534
|
+
* This function uses regex patterns to parse natural language, which is an
|
|
535
|
+
* anti-pattern. The LLM (Agent) should understand user intent naturally
|
|
536
|
+
* and provide structured data directly.
|
|
537
|
+
*
|
|
538
|
+
* For greenfield workflow generation, consider using:
|
|
539
|
+
* - Intent Architect for complex requirements analysis
|
|
540
|
+
* - Direct structured input (WorkflowSpec) from the Agent
|
|
541
|
+
*
|
|
542
|
+
* This function is retained for backward compatibility but should not be
|
|
543
|
+
* used in new code paths.
|
|
544
|
+
*
|
|
545
|
+
* @see src/mcp/AGENTS.md for the LLM-driven architecture guidelines
|
|
546
|
+
*/
|
|
531
547
|
export function parseNaturalLanguage(text) {
|
|
548
|
+
console.warn("[DEPRECATED] parseNaturalLanguage() uses regex parsing which is an anti-pattern. " +
|
|
549
|
+
"Consider using LLM-based intent extraction or structured input instead.");
|
|
532
550
|
// Detect persona type (default: chat)
|
|
533
551
|
let personaType = "chat";
|
|
534
552
|
for (const { pattern, type } of PERSONA_TYPE_PATTERNS) {
|
|
@@ -1457,11 +1475,20 @@ export function intentToSpec(intent) {
|
|
|
1457
1475
|
...(intent.persona_type === "chat" && { chatConfig: { name: intent.name } }),
|
|
1458
1476
|
};
|
|
1459
1477
|
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Parse input and extract workflow intent.
|
|
1480
|
+
*
|
|
1481
|
+
* Note: The "natural_language" path uses deprecated regex-based parsing.
|
|
1482
|
+
* For new code, prefer providing structured input (partial_spec or full_spec)
|
|
1483
|
+
* directly from the Agent, which understands user intent naturally.
|
|
1484
|
+
*/
|
|
1460
1485
|
export function parseInput(input) {
|
|
1461
1486
|
const inputType = detectInputType(input);
|
|
1462
1487
|
let intent;
|
|
1463
1488
|
switch (inputType) {
|
|
1464
1489
|
case "natural_language":
|
|
1490
|
+
// DEPRECATED: This path uses regex-based parsing.
|
|
1491
|
+
// Prefer structured input from the Agent instead.
|
|
1465
1492
|
intent = parseNaturalLanguage(String(input));
|
|
1466
1493
|
break;
|
|
1467
1494
|
case "partial_spec":
|
|
@@ -655,345 +655,3 @@ Return ONLY the modified WorkflowSpec JSON, no explanation needed:
|
|
|
655
655
|
\`\`\`json
|
|
656
656
|
`;
|
|
657
657
|
}
|
|
658
|
-
/**
|
|
659
|
-
* @deprecated MCP should not parse natural language - the Agent IS the LLM.
|
|
660
|
-
* Use agent reasoning instead. Will be removed in future version.
|
|
661
|
-
*
|
|
662
|
-
* Parse natural language intent into deterministic operations.
|
|
663
|
-
* Returns operations that can be executed directly, or indicates LLM is needed.
|
|
664
|
-
*/
|
|
665
|
-
export function parseIntent(input, spec) {
|
|
666
|
-
const ops = [];
|
|
667
|
-
const lowerInput = input.toLowerCase();
|
|
668
|
-
const nodeIds = spec.nodes.map(n => n.id);
|
|
669
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
670
|
-
// REMOVE NODE: "remove node X", "delete X", "remove X"
|
|
671
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
672
|
-
if (lowerInput.includes("remove") || lowerInput.includes("delete")) {
|
|
673
|
-
// Strategy 1: Exact node ID in input
|
|
674
|
-
for (const nodeId of nodeIds) {
|
|
675
|
-
if (input.includes(nodeId)) {
|
|
676
|
-
ops.push({ type: "remove", nodeId });
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
// Strategy 2: "remove the node X" pattern
|
|
680
|
-
if (ops.length === 0) {
|
|
681
|
-
const match = input.match(/(?:remove|delete)\s+(?:the\s+)?node\s+["']?([a-zA-Z0-9_]+)["']?/i);
|
|
682
|
-
if (match) {
|
|
683
|
-
const nodeId = nodeIds.find(id => id.toLowerCase() === match[1].toLowerCase());
|
|
684
|
-
if (nodeId) {
|
|
685
|
-
ops.push({ type: "remove", nodeId });
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
691
|
-
// RENAME NODE: "rename X to Y", "change displayName of X to Y"
|
|
692
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
693
|
-
if (lowerInput.includes("rename") || lowerInput.includes("displayname")) {
|
|
694
|
-
const match = input.match(/(?:rename|change\s+displayName\s+of)\s+["']?([a-zA-Z0-9_]+)["']?\s+to\s+["']?([^"'\n]+)["']?/i);
|
|
695
|
-
if (match) {
|
|
696
|
-
const nodeId = nodeIds.find(id => id.toLowerCase() === match[1].toLowerCase());
|
|
697
|
-
if (nodeId) {
|
|
698
|
-
ops.push({ type: "rename", nodeId, newDisplayName: match[2].trim() });
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
703
|
-
// ADD RUNIF: "add runIf to X when Y.Z equals V"
|
|
704
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
705
|
-
if (lowerInput.includes("runif") || lowerInput.includes("conditional") ||
|
|
706
|
-
(lowerInput.includes("only run") && lowerInput.includes("when"))) {
|
|
707
|
-
// Pattern: "add runIf to NODE when SOURCE.OUTPUT equals VALUE"
|
|
708
|
-
const match = input.match(/(?:add\s+)?runIf\s+to\s+["']?([a-zA-Z0-9_]+)["']?\s+when\s+["']?([a-zA-Z0-9_]+)["']?\.["']?([a-zA-Z0-9_]+)["']?\s+(?:equals?|==|eq)\s+["']?([^"'\n]+)["']?/i);
|
|
709
|
-
if (match) {
|
|
710
|
-
const nodeId = nodeIds.find(id => id.toLowerCase() === match[1].toLowerCase());
|
|
711
|
-
const sourceAction = nodeIds.find(id => id.toLowerCase() === match[2].toLowerCase());
|
|
712
|
-
if (nodeId && sourceAction) {
|
|
713
|
-
ops.push({
|
|
714
|
-
type: "add_runif",
|
|
715
|
-
nodeId,
|
|
716
|
-
condition: {
|
|
717
|
-
sourceAction,
|
|
718
|
-
sourceOutput: match[3],
|
|
719
|
-
operator: "eq",
|
|
720
|
-
value: match[4].trim(),
|
|
721
|
-
},
|
|
722
|
-
});
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
727
|
-
// ADD TO RESULTS: "add X to results", "include X in output"
|
|
728
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
729
|
-
if (lowerInput.includes("add") && (lowerInput.includes("result") || lowerInput.includes("output"))) {
|
|
730
|
-
for (const nodeId of nodeIds) {
|
|
731
|
-
if (input.includes(nodeId)) {
|
|
732
|
-
// Determine output name based on node type
|
|
733
|
-
const node = spec.nodes.find(n => n.id === nodeId);
|
|
734
|
-
const output = node?.actionType === "search" ? "search_results" : "response_with_sources";
|
|
735
|
-
ops.push({ type: "add_to_results", nodeId, output });
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
// If we found deterministic ops, return them
|
|
740
|
-
if (ops.length > 0) {
|
|
741
|
-
return { ops, needs_llm: false };
|
|
742
|
-
}
|
|
743
|
-
// Otherwise, this needs LLM for complex interpretation
|
|
744
|
-
return {
|
|
745
|
-
ops: [],
|
|
746
|
-
needs_llm: true,
|
|
747
|
-
reason: "Could not parse deterministic operations from input. LLM interpretation needed."
|
|
748
|
-
};
|
|
749
|
-
}
|
|
750
|
-
/**
|
|
751
|
-
* @deprecated MCP should not apply operations - the Agent builds workflow_spec.
|
|
752
|
-
* Will be removed in future version.
|
|
753
|
-
*
|
|
754
|
-
* Apply deterministic modifications to a WorkflowSpec.
|
|
755
|
-
*/
|
|
756
|
-
export function applyOperations(spec, ops) {
|
|
757
|
-
// Deep clone to avoid mutations
|
|
758
|
-
const newSpec = JSON.parse(JSON.stringify(spec));
|
|
759
|
-
const changes = [];
|
|
760
|
-
const errors = [];
|
|
761
|
-
for (const op of ops) {
|
|
762
|
-
switch (op.type) {
|
|
763
|
-
case "remove": {
|
|
764
|
-
const idx = newSpec.nodes.findIndex(n => n.id === op.nodeId);
|
|
765
|
-
if (idx >= 0) {
|
|
766
|
-
newSpec.nodes.splice(idx, 1);
|
|
767
|
-
// Also remove from resultMappings
|
|
768
|
-
newSpec.resultMappings = newSpec.resultMappings.filter(rm => rm.nodeId !== op.nodeId);
|
|
769
|
-
// Clean up any references in other nodes' inputs - REMOVE them, don't just warn
|
|
770
|
-
for (const node of newSpec.nodes) {
|
|
771
|
-
if (node.inputs) {
|
|
772
|
-
for (const [key, binding] of Object.entries(node.inputs)) {
|
|
773
|
-
if (binding.actionName === op.nodeId) {
|
|
774
|
-
// Remove the dangling reference to prevent API errors
|
|
775
|
-
delete node.inputs[key];
|
|
776
|
-
errors.push(`Warning: Removed dangling reference ${node.id}.${key} → ${op.nodeId}`);
|
|
777
|
-
}
|
|
778
|
-
// Also check namedInputs for dangling refs
|
|
779
|
-
if (binding.namedInputs) {
|
|
780
|
-
const originalLen = binding.namedInputs.length;
|
|
781
|
-
binding.namedInputs = binding.namedInputs.filter(ni => ni.binding?.actionName !== op.nodeId);
|
|
782
|
-
if (binding.namedInputs.length < originalLen) {
|
|
783
|
-
errors.push(`Warning: Removed dangling namedInput reference in ${node.id}.${key} → ${op.nodeId}`);
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
changes.push(`Removed node: ${op.nodeId}`);
|
|
790
|
-
}
|
|
791
|
-
else {
|
|
792
|
-
errors.push(`Node not found: ${op.nodeId}`);
|
|
793
|
-
}
|
|
794
|
-
break;
|
|
795
|
-
}
|
|
796
|
-
case "rename": {
|
|
797
|
-
const node = newSpec.nodes.find(n => n.id === op.nodeId);
|
|
798
|
-
if (node) {
|
|
799
|
-
const oldName = node.displayName;
|
|
800
|
-
node.displayName = op.newDisplayName;
|
|
801
|
-
changes.push(`Renamed ${op.nodeId}: "${oldName}" → "${op.newDisplayName}"`);
|
|
802
|
-
}
|
|
803
|
-
else {
|
|
804
|
-
errors.push(`Node not found: ${op.nodeId}`);
|
|
805
|
-
}
|
|
806
|
-
break;
|
|
807
|
-
}
|
|
808
|
-
case "add_runif": {
|
|
809
|
-
const node = newSpec.nodes.find(n => n.id === op.nodeId);
|
|
810
|
-
if (node) {
|
|
811
|
-
node.runIf = op.condition;
|
|
812
|
-
changes.push(`Added runIf to ${op.nodeId}: when ${op.condition.sourceAction}.${op.condition.sourceOutput} ${op.condition.operator} "${op.condition.value}"`);
|
|
813
|
-
}
|
|
814
|
-
else {
|
|
815
|
-
errors.push(`Node not found: ${op.nodeId}`);
|
|
816
|
-
}
|
|
817
|
-
break;
|
|
818
|
-
}
|
|
819
|
-
case "remove_runif": {
|
|
820
|
-
const node = newSpec.nodes.find(n => n.id === op.nodeId);
|
|
821
|
-
if (node) {
|
|
822
|
-
delete node.runIf;
|
|
823
|
-
changes.push(`Removed runIf from ${op.nodeId}`);
|
|
824
|
-
}
|
|
825
|
-
else {
|
|
826
|
-
errors.push(`Node not found: ${op.nodeId}`);
|
|
827
|
-
}
|
|
828
|
-
break;
|
|
829
|
-
}
|
|
830
|
-
case "add_to_results": {
|
|
831
|
-
const exists = newSpec.resultMappings.some(rm => rm.nodeId === op.nodeId);
|
|
832
|
-
if (!exists) {
|
|
833
|
-
newSpec.resultMappings.push({ nodeId: op.nodeId, output: op.output });
|
|
834
|
-
changes.push(`Added ${op.nodeId}.${op.output} to results`);
|
|
835
|
-
}
|
|
836
|
-
else {
|
|
837
|
-
changes.push(`${op.nodeId} already in results`);
|
|
838
|
-
}
|
|
839
|
-
break;
|
|
840
|
-
}
|
|
841
|
-
case "remove_from_results": {
|
|
842
|
-
const idx = newSpec.resultMappings.findIndex(rm => rm.nodeId === op.nodeId);
|
|
843
|
-
if (idx >= 0) {
|
|
844
|
-
newSpec.resultMappings.splice(idx, 1);
|
|
845
|
-
changes.push(`Removed ${op.nodeId} from results`);
|
|
846
|
-
}
|
|
847
|
-
break;
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
return { spec: newSpec, changes, errors };
|
|
852
|
-
}
|
|
853
|
-
/**
|
|
854
|
-
* Directly patch a workflow_def without recompilation.
|
|
855
|
-
* This preserves ALL original fields and only modifies what's necessary.
|
|
856
|
-
*
|
|
857
|
-
* Use this for simple operations like remove/rename that don't need full recompile.
|
|
858
|
-
*/
|
|
859
|
-
function patchWorkflowDef(workflowDef, ops) {
|
|
860
|
-
const patched = JSON.parse(JSON.stringify(workflowDef));
|
|
861
|
-
const actions = patched.actions;
|
|
862
|
-
const results = patched.results;
|
|
863
|
-
const changes = [];
|
|
864
|
-
const errors = [];
|
|
865
|
-
for (const op of ops) {
|
|
866
|
-
switch (op.type) {
|
|
867
|
-
case "remove": {
|
|
868
|
-
const idx = actions.findIndex((a) => a.name === op.nodeId);
|
|
869
|
-
if (idx >= 0) {
|
|
870
|
-
actions.splice(idx, 1);
|
|
871
|
-
changes.push(`Removed node: ${op.nodeId}`);
|
|
872
|
-
// Remove from results
|
|
873
|
-
for (const key of Object.keys(results)) {
|
|
874
|
-
if (results[key].actionName === op.nodeId) {
|
|
875
|
-
delete results[key];
|
|
876
|
-
changes.push(`Removed result mapping: ${key}`);
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
// Clean up dangling input references
|
|
880
|
-
for (const action of actions) {
|
|
881
|
-
const inputs = action.inputs;
|
|
882
|
-
if (inputs) {
|
|
883
|
-
for (const [inputKey, inputVal] of Object.entries(inputs)) {
|
|
884
|
-
const actionOutput = inputVal.actionOutput;
|
|
885
|
-
if (actionOutput?.actionName === op.nodeId) {
|
|
886
|
-
delete inputs[inputKey];
|
|
887
|
-
errors.push(`Warning: Removed dangling reference ${action.name}.${inputKey} → ${op.nodeId}`);
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
else {
|
|
894
|
-
errors.push(`Node not found: ${op.nodeId}`);
|
|
895
|
-
}
|
|
896
|
-
break;
|
|
897
|
-
}
|
|
898
|
-
case "rename": {
|
|
899
|
-
const action = actions.find((a) => a.name === op.nodeId);
|
|
900
|
-
if (action) {
|
|
901
|
-
const displaySettings = action.displaySettings;
|
|
902
|
-
if (displaySettings) {
|
|
903
|
-
displaySettings.displayName = op.newDisplayName;
|
|
904
|
-
changes.push(`Renamed ${op.nodeId} to "${op.newDisplayName}"`);
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
else {
|
|
908
|
-
errors.push(`Node not found for rename: ${op.nodeId}`);
|
|
909
|
-
}
|
|
910
|
-
break;
|
|
911
|
-
}
|
|
912
|
-
// For other operations, we'd need full recompile
|
|
913
|
-
default:
|
|
914
|
-
errors.push(`Direct patch not supported for operation: ${op.type}`);
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
return { workflow_def: patched, changes, errors };
|
|
918
|
-
}
|
|
919
|
-
/**
|
|
920
|
-
* @deprecated MCP should not transform from intent - the Agent builds workflow_spec.
|
|
921
|
-
* The agent uses analyze to get current spec, reasons about changes, and calls
|
|
922
|
-
* update with the new workflow_spec it built.
|
|
923
|
-
* Will be removed in future version.
|
|
924
|
-
*
|
|
925
|
-
* Transform a workflow based on natural language intent.
|
|
926
|
-
*
|
|
927
|
-
* @param workflowDef - The existing workflow_def JSON
|
|
928
|
-
* @param input - Natural language modification request
|
|
929
|
-
* @param personaType - The persona type (voice, chat, dashboard)
|
|
930
|
-
* @returns IntentTransformResult with modified workflow or LLM prompt
|
|
931
|
-
*/
|
|
932
|
-
export function transformWorkflowFromIntent(workflowDef, input, personaType = "chat") {
|
|
933
|
-
// Step 1: Decompile to spec (for parsing)
|
|
934
|
-
const spec = decompileWorkflow(workflowDef, personaType);
|
|
935
|
-
// Step 2: Parse intent into operations
|
|
936
|
-
const { ops, needs_llm, reason } = parseIntent(input, spec);
|
|
937
|
-
if (needs_llm) {
|
|
938
|
-
// Complex modification - return LLM prompt
|
|
939
|
-
return {
|
|
940
|
-
success: false,
|
|
941
|
-
needs_llm: true,
|
|
942
|
-
llm_prompt: createModificationPrompt(spec, input),
|
|
943
|
-
changes: [],
|
|
944
|
-
errors: [reason || "LLM interpretation needed"],
|
|
945
|
-
};
|
|
946
|
-
}
|
|
947
|
-
// Step 3: Check if we can use direct patching (simpler, preserves all fields)
|
|
948
|
-
const simpleOps = ["remove", "rename"];
|
|
949
|
-
const allSimple = ops.every(op => simpleOps.includes(op.type));
|
|
950
|
-
if (allSimple && ops.length > 0) {
|
|
951
|
-
// Use direct patching - no recompilation needed
|
|
952
|
-
const { workflow_def, changes, errors } = patchWorkflowDef(workflowDef, ops);
|
|
953
|
-
if (errors.some(e => !e.startsWith("Warning"))) {
|
|
954
|
-
return {
|
|
955
|
-
success: false,
|
|
956
|
-
workflow_def,
|
|
957
|
-
changes,
|
|
958
|
-
errors,
|
|
959
|
-
};
|
|
960
|
-
}
|
|
961
|
-
return {
|
|
962
|
-
success: true,
|
|
963
|
-
workflow_def,
|
|
964
|
-
changes,
|
|
965
|
-
errors,
|
|
966
|
-
};
|
|
967
|
-
}
|
|
968
|
-
// Step 4: Complex operations - use full decompile → transform → compile
|
|
969
|
-
const { spec: modifiedSpec, changes, errors } = applyOperations(spec, ops);
|
|
970
|
-
if (errors.some(e => !e.startsWith("Warning"))) {
|
|
971
|
-
return {
|
|
972
|
-
success: false,
|
|
973
|
-
spec: modifiedSpec,
|
|
974
|
-
changes,
|
|
975
|
-
errors,
|
|
976
|
-
};
|
|
977
|
-
}
|
|
978
|
-
// Step 5: Compile back to workflow_def
|
|
979
|
-
try {
|
|
980
|
-
const compiled = compileWorkflow(modifiedSpec);
|
|
981
|
-
// Preserve original workflowName
|
|
982
|
-
compiled.workflow_def.workflowName = workflowDef.workflowName;
|
|
983
|
-
return {
|
|
984
|
-
success: true,
|
|
985
|
-
spec: modifiedSpec,
|
|
986
|
-
workflow_def: compiled.workflow_def,
|
|
987
|
-
changes,
|
|
988
|
-
errors,
|
|
989
|
-
};
|
|
990
|
-
}
|
|
991
|
-
catch (e) {
|
|
992
|
-
return {
|
|
993
|
-
success: false,
|
|
994
|
-
spec: modifiedSpec,
|
|
995
|
-
changes,
|
|
996
|
-
errors: [...errors, `Compilation failed: ${e instanceof Error ? e.message : String(e)}`],
|
|
997
|
-
};
|
|
998
|
-
}
|
|
999
|
-
}
|