@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
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Deploy Handler
|
|
3
|
+
*
|
|
4
|
+
* Deploys a workflow_def to an existing persona.
|
|
5
|
+
*/
|
|
6
|
+
import { sanitizeWorkflowForDeploy } from "./utils.js";
|
|
7
|
+
/**
|
|
8
|
+
* Handle workflow deploy mode
|
|
9
|
+
*/
|
|
10
|
+
export async function handleWorkflowDeploy(args, client) {
|
|
11
|
+
const personaId = args.persona_id;
|
|
12
|
+
const workflowDef = args.workflow_def;
|
|
13
|
+
// DEPLOY: Direct workflow deployment to existing persona (no input required)
|
|
14
|
+
if (!personaId) {
|
|
15
|
+
return { error: "persona_id required for deploy mode" };
|
|
16
|
+
}
|
|
17
|
+
if (!workflowDef) {
|
|
18
|
+
return { error: "workflow_def required for deploy mode" };
|
|
19
|
+
}
|
|
20
|
+
const persona = await client.getPersonaById(personaId);
|
|
21
|
+
if (!persona) {
|
|
22
|
+
return { error: `Persona not found: ${personaId}` };
|
|
23
|
+
}
|
|
24
|
+
// Sanitize workflow before deployment
|
|
25
|
+
const sanitizedWorkflow = sanitizeWorkflowForDeploy(workflowDef);
|
|
26
|
+
// Determine proto_config to use: provided > existing
|
|
27
|
+
const existingProtoConfig = persona.proto_config;
|
|
28
|
+
const providedProtoConfig = args.proto_config;
|
|
29
|
+
const protoConfigToUse = providedProtoConfig || existingProtoConfig || {};
|
|
30
|
+
// Check if this is a voice persona and warn if critical settings are missing
|
|
31
|
+
const projectSettings = protoConfigToUse.projectSettings;
|
|
32
|
+
const isVoice = projectSettings?.projectType === 5;
|
|
33
|
+
const warnings = [];
|
|
34
|
+
if (isVoice && !providedProtoConfig) {
|
|
35
|
+
// Check if existing proto_config has conversationSettings populated
|
|
36
|
+
const widgets = (protoConfigToUse.widgets ?? []);
|
|
37
|
+
const convSettings = widgets.find(w => w.name === "conversationSettings");
|
|
38
|
+
const convConfig = convSettings?.conversationSettings;
|
|
39
|
+
if (!convConfig?.welcomeMessage || !convConfig?.identityAndPurpose) {
|
|
40
|
+
warnings.push("Voice persona detected but proto_config not provided. Voice settings (welcomeMessage, identityAndPurpose, etc.) may be empty or generic. " +
|
|
41
|
+
"If you generated the workflow with workflow(input=...), pass the generated proto_config along with workflow_def to preserve voice settings.");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Deploy workflow (always deploy, preview is not applicable for explicit deploy mode)
|
|
45
|
+
try {
|
|
46
|
+
await client.updateAiEmployee({
|
|
47
|
+
persona_id: personaId,
|
|
48
|
+
workflow: sanitizedWorkflow,
|
|
49
|
+
proto_config: protoConfigToUse,
|
|
50
|
+
});
|
|
51
|
+
const result = {
|
|
52
|
+
mode: "deploy",
|
|
53
|
+
status: "deployed",
|
|
54
|
+
persona_id: personaId,
|
|
55
|
+
persona_name: persona.name,
|
|
56
|
+
workflow_deployed: true,
|
|
57
|
+
};
|
|
58
|
+
if (warnings.length > 0) {
|
|
59
|
+
result.warnings = warnings;
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
65
|
+
return {
|
|
66
|
+
mode: "deploy",
|
|
67
|
+
status: "failed",
|
|
68
|
+
persona_id: personaId,
|
|
69
|
+
persona_name: persona.name,
|
|
70
|
+
error: errorMessage,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Generate Handler
|
|
3
|
+
*
|
|
4
|
+
* Handles greenfield workflow generation from natural language input.
|
|
5
|
+
* Supports:
|
|
6
|
+
* - Simple workflows (direct generation)
|
|
7
|
+
* - Complex workflows (returns LLM prompt for agent to complete)
|
|
8
|
+
* - Intent Architect integration for qualification questions
|
|
9
|
+
* - Auto-deploy to new or existing personas
|
|
10
|
+
*/
|
|
11
|
+
import { parseInput, intentToSpec, generateWorkflow } from "../../../sdk/workflow-intent.js";
|
|
12
|
+
import { compileWorkflow } from "../../../sdk/workflow-generator.js";
|
|
13
|
+
import { detectWorkflowIssues } from "../../../sdk/knowledge.js";
|
|
14
|
+
import { runIntentArchitect } from "../../../sdk/intent-architect.js";
|
|
15
|
+
import { ensureActionRegistry } from "../../../sdk/action-registry.js";
|
|
16
|
+
import { ensureSchemaRegistry, validateWorkflowSpec, generateActionCatalogForLLM } from "../../../sdk/workflow-validator.js";
|
|
17
|
+
import { getTemplates, normalizeTriggerType } from "../../handlers/index.js";
|
|
18
|
+
/**
|
|
19
|
+
* Get persona widget context for workflow bindings
|
|
20
|
+
*/
|
|
21
|
+
async function getPersonaWidgets(personaId, client) {
|
|
22
|
+
if (!personaId)
|
|
23
|
+
return [];
|
|
24
|
+
try {
|
|
25
|
+
const persona = await client.getPersonaById(personaId);
|
|
26
|
+
const protoConfig = persona?.proto_config;
|
|
27
|
+
if (protoConfig?.widgets) {
|
|
28
|
+
return protoConfig.widgets
|
|
29
|
+
.filter(w => typeof w.name === "string" && w.name.trim().length > 0)
|
|
30
|
+
.map(w => ({
|
|
31
|
+
name: w.name,
|
|
32
|
+
type: String(w.type ?? "unknown"),
|
|
33
|
+
title: w.title,
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Persona not found or no widgets - continue without context
|
|
39
|
+
}
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Enhance an LLM prompt with action catalog and widget context
|
|
44
|
+
*/
|
|
45
|
+
function enhancePromptWithContext(prompt, schemaRegistry, personaWidgets) {
|
|
46
|
+
let enhancedPrompt = prompt;
|
|
47
|
+
let availableActions = [];
|
|
48
|
+
let availableTemplates = [];
|
|
49
|
+
if (!prompt) {
|
|
50
|
+
return { prompt: enhancedPrompt, availableActions, availableTemplates };
|
|
51
|
+
}
|
|
52
|
+
let systemAdditions = "";
|
|
53
|
+
// Add action catalog if available
|
|
54
|
+
if (schemaRegistry) {
|
|
55
|
+
const actionCatalog = generateActionCatalogForLLM(schemaRegistry);
|
|
56
|
+
systemAdditions += "\n\n## Available Actions\n" + actionCatalog;
|
|
57
|
+
availableActions = schemaRegistry.getAllActions().map((a) => a.name);
|
|
58
|
+
availableTemplates = schemaRegistry.getAllTemplates().map((t) => ({ id: t.id, name: t.name, type: t.type }));
|
|
59
|
+
}
|
|
60
|
+
// Add persona widget context if available
|
|
61
|
+
if (personaWidgets.length > 0) {
|
|
62
|
+
systemAdditions += "\n\n## Available Persona Widgets\n";
|
|
63
|
+
systemAdditions += "Use these exact widget names in workflow bindings (widgetName field):\n";
|
|
64
|
+
for (const w of personaWidgets) {
|
|
65
|
+
systemAdditions += `- \`${w.name}\` (${w.type})${w.title ? ` - "${w.title}"` : ""}\n`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (systemAdditions) {
|
|
69
|
+
enhancedPrompt = {
|
|
70
|
+
system: prompt.system + systemAdditions,
|
|
71
|
+
user: prompt.user,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return { prompt: enhancedPrompt, availableActions, availableTemplates };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Deploy workflow to a new persona (greenfield creation)
|
|
78
|
+
*/
|
|
79
|
+
async function deployToNewPersona(args, client, compiled, actionRegistry, getTemplateId) {
|
|
80
|
+
const personaName = args.name;
|
|
81
|
+
if (!personaName) {
|
|
82
|
+
return { success: false, hint: "Provide name to create new persona, or persona_id to deploy to existing persona" };
|
|
83
|
+
}
|
|
84
|
+
const personaType = args.type || "chat";
|
|
85
|
+
// Dynamic template lookup
|
|
86
|
+
const templates = await getTemplates(client);
|
|
87
|
+
const matchingTemplate = templates.find(t => normalizeTriggerType(t.trigger_type) === personaType.toLowerCase());
|
|
88
|
+
const templateFromRegistry = actionRegistry.getTemplateForType(personaType);
|
|
89
|
+
const templateId = getTemplateId?.(personaType) || templateFromRegistry?.id || matchingTemplate?.id;
|
|
90
|
+
if (!templateId) {
|
|
91
|
+
const availableTypes = [...new Set(templates.map(t => normalizeTriggerType(t.trigger_type)).filter(Boolean))];
|
|
92
|
+
return {
|
|
93
|
+
success: false,
|
|
94
|
+
error: `No template found for type "${personaType}".`,
|
|
95
|
+
hint: `Available types: ${availableTypes.join(", ")}. Provide template_id directly or use one of the available types.`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// Step 1: Create the persona from template
|
|
99
|
+
const createResult = await client.createAiEmployee({
|
|
100
|
+
name: personaName,
|
|
101
|
+
description: args.description,
|
|
102
|
+
template_id: templateId,
|
|
103
|
+
});
|
|
104
|
+
const newPersonaId = createResult.persona_id ?? createResult.id;
|
|
105
|
+
if (!newPersonaId) {
|
|
106
|
+
return { success: false, error: "Failed to create persona: no ID returned" };
|
|
107
|
+
}
|
|
108
|
+
// Step 2: Fetch the newly created persona
|
|
109
|
+
const newPersona = await client.getPersonaById(newPersonaId);
|
|
110
|
+
if (!newPersona) {
|
|
111
|
+
return { success: false, error: `Failed to fetch newly created persona: ${newPersonaId}` };
|
|
112
|
+
}
|
|
113
|
+
// Step 3: Merge proto_config
|
|
114
|
+
const existingProtoConfig = (newPersona.proto_config ?? {});
|
|
115
|
+
const generatedProtoConfig = args.proto_config || compiled.proto_config || {};
|
|
116
|
+
const existingWidgets = (existingProtoConfig.widgets ?? []);
|
|
117
|
+
const generatedWidgets = (generatedProtoConfig.widgets ?? []);
|
|
118
|
+
const widgetMap = new Map();
|
|
119
|
+
for (const w of existingWidgets) {
|
|
120
|
+
if (typeof w.name === "string" && w.name.trim().length > 0) {
|
|
121
|
+
widgetMap.set(w.name, w);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
for (const genWidget of generatedWidgets) {
|
|
125
|
+
const widgetName = genWidget.name;
|
|
126
|
+
if (typeof widgetName === "string" && widgetName.trim().length > 0) {
|
|
127
|
+
const existing = widgetMap.get(widgetName);
|
|
128
|
+
if (existing) {
|
|
129
|
+
const merged = { ...existing };
|
|
130
|
+
if (genWidget[widgetName]) {
|
|
131
|
+
merged[widgetName] = { ...(existing[widgetName] || {}), ...genWidget[widgetName] };
|
|
132
|
+
}
|
|
133
|
+
widgetMap.set(widgetName, merged);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
widgetMap.set(widgetName, genWidget);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const mergedProtoConfig = {
|
|
141
|
+
...existingProtoConfig,
|
|
142
|
+
widgets: Array.from(widgetMap.values()),
|
|
143
|
+
};
|
|
144
|
+
// Step 4: Deploy workflow
|
|
145
|
+
// NOTE: The SDK's updateAiEmployee() handles workflowName namespace automatically.
|
|
146
|
+
// It will copy from existing workflow if present, or generate a valid namespace if not.
|
|
147
|
+
// It also fixes the results format. No need to manually manipulate these here.
|
|
148
|
+
try {
|
|
149
|
+
await client.updateAiEmployee({
|
|
150
|
+
persona_id: newPersonaId,
|
|
151
|
+
workflow: compiled.workflow_def,
|
|
152
|
+
proto_config: mergedProtoConfig,
|
|
153
|
+
}, { verbose: true });
|
|
154
|
+
return { success: true, personaId: newPersonaId, personaName };
|
|
155
|
+
}
|
|
156
|
+
catch (deployError) {
|
|
157
|
+
const errMsg = deployError instanceof Error ? deployError.message : String(deployError);
|
|
158
|
+
// Still set proto_config
|
|
159
|
+
await client.updateAiEmployee({
|
|
160
|
+
persona_id: newPersonaId,
|
|
161
|
+
proto_config: mergedProtoConfig,
|
|
162
|
+
});
|
|
163
|
+
return {
|
|
164
|
+
success: false,
|
|
165
|
+
personaId: newPersonaId,
|
|
166
|
+
personaName,
|
|
167
|
+
workflowDeployError: errMsg,
|
|
168
|
+
workflowAttempted: compiled.workflow_def,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Handle workflow generate mode
|
|
174
|
+
*/
|
|
175
|
+
export async function handleWorkflowGenerate(args, client, getTemplateId) {
|
|
176
|
+
const personaId = args.persona_id;
|
|
177
|
+
const input = args.input;
|
|
178
|
+
const preview = args.preview !== false;
|
|
179
|
+
if (!input) {
|
|
180
|
+
return { error: "input required for generate mode" };
|
|
181
|
+
}
|
|
182
|
+
// Load action registry
|
|
183
|
+
const actionRegistry = await ensureActionRegistry(client);
|
|
184
|
+
// Parse input
|
|
185
|
+
const parseResult = parseInput(input);
|
|
186
|
+
if (!parseResult.validation.complete) {
|
|
187
|
+
return {
|
|
188
|
+
status: "incomplete",
|
|
189
|
+
input_type: parseResult.input_type,
|
|
190
|
+
missing: parseResult.validation.missing,
|
|
191
|
+
questions: parseResult.validation.questions,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
// Override persona_type from args.type if provided
|
|
195
|
+
if (args.type) {
|
|
196
|
+
parseResult.intent.persona_type = args.type;
|
|
197
|
+
}
|
|
198
|
+
// Load schema registry
|
|
199
|
+
let schemaRegistry = null;
|
|
200
|
+
try {
|
|
201
|
+
schemaRegistry = await ensureSchemaRegistry(client);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Schema registry unavailable - skip API validation
|
|
205
|
+
}
|
|
206
|
+
// Get persona widgets for context
|
|
207
|
+
const personaWidgets = await getPersonaWidgets(personaId, client);
|
|
208
|
+
// Run Intent Architect for complexity assessment
|
|
209
|
+
const maxComplexity = args.max_complexity || undefined;
|
|
210
|
+
const architectResult = runIntentArchitect(input, {
|
|
211
|
+
persona_type: parseResult.intent.persona_type,
|
|
212
|
+
available_integrations: schemaRegistry?.getAllActions().slice(0, 20).map((a) => a.displayName || a.name),
|
|
213
|
+
}, { max_complexity: maxComplexity });
|
|
214
|
+
// For moderate/complex: return Intent Architect result
|
|
215
|
+
if (!architectResult.strategy.can_proceed) {
|
|
216
|
+
const enhanced = enhancePromptWithContext(architectResult.prompt_package, schemaRegistry, personaWidgets);
|
|
217
|
+
return {
|
|
218
|
+
status: "needs_intent_architect",
|
|
219
|
+
available_widgets: personaWidgets.length > 0 ? personaWidgets : undefined,
|
|
220
|
+
assessment: architectResult.assessment,
|
|
221
|
+
strategy: architectResult.strategy,
|
|
222
|
+
questions: architectResult.questions,
|
|
223
|
+
llm_prompt: enhanced.prompt,
|
|
224
|
+
hint: architectResult.strategy.next_step,
|
|
225
|
+
reason: architectResult.legacy?.signals.reason,
|
|
226
|
+
complexity: architectResult.legacy?.complexity,
|
|
227
|
+
approach: architectResult.strategy.approach,
|
|
228
|
+
gates_to_ask: architectResult.strategy.gates_to_ask,
|
|
229
|
+
fallback_spec: intentToSpec(parseResult.intent),
|
|
230
|
+
available_actions: enhanced.availableActions,
|
|
231
|
+
available_templates: enhanced.availableTemplates,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
// SIMPLE complexity: generate directly
|
|
235
|
+
const genResult = generateWorkflow(parseResult.intent);
|
|
236
|
+
if (genResult.needs_llm) {
|
|
237
|
+
const enhanced = enhancePromptWithContext(genResult.llm_prompt, schemaRegistry, personaWidgets);
|
|
238
|
+
return {
|
|
239
|
+
status: "needs_llm_generation",
|
|
240
|
+
reason: genResult.reason,
|
|
241
|
+
complexity: genResult.complexity,
|
|
242
|
+
llm_prompt: enhanced.prompt,
|
|
243
|
+
hint: "Send llm_prompt to an LLM, then call persona(workflow_def=<parsed_response>) to deploy.",
|
|
244
|
+
fallback_spec: intentToSpec(parseResult.intent),
|
|
245
|
+
available_actions: enhanced.availableActions,
|
|
246
|
+
available_templates: enhanced.availableTemplates,
|
|
247
|
+
available_widgets: personaWidgets.length > 0 ? personaWidgets : undefined,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
// Simple workflow - compile
|
|
251
|
+
const spec = genResult.spec;
|
|
252
|
+
if (args.name) {
|
|
253
|
+
spec.name = args.name;
|
|
254
|
+
}
|
|
255
|
+
if (args.description) {
|
|
256
|
+
spec.description = args.description;
|
|
257
|
+
}
|
|
258
|
+
// Validate spec before compiling
|
|
259
|
+
let specValidation = null;
|
|
260
|
+
if (schemaRegistry) {
|
|
261
|
+
specValidation = validateWorkflowSpec(spec, schemaRegistry);
|
|
262
|
+
if (!specValidation.valid) {
|
|
263
|
+
return {
|
|
264
|
+
status: "validation_failed",
|
|
265
|
+
errors: specValidation.errors,
|
|
266
|
+
warnings: specValidation.warnings,
|
|
267
|
+
action_coverage: specValidation.action_coverage,
|
|
268
|
+
hint: "Fix the validation errors and try again. Unknown actions may need to be checked against ListActions.",
|
|
269
|
+
spec_attempted: spec,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const compiled = compileWorkflow(spec, { registry: actionRegistry });
|
|
274
|
+
// Validate generated workflow
|
|
275
|
+
const issues = detectWorkflowIssues(compiled.workflow_def);
|
|
276
|
+
const result = {
|
|
277
|
+
mode: "generate",
|
|
278
|
+
status: preview ? "preview" : "deployed",
|
|
279
|
+
workflow_def: compiled.workflow_def,
|
|
280
|
+
proto_config: compiled.proto_config,
|
|
281
|
+
validation: parseResult.validation,
|
|
282
|
+
};
|
|
283
|
+
if (specValidation) {
|
|
284
|
+
result.api_validation = {
|
|
285
|
+
valid: specValidation.valid,
|
|
286
|
+
warnings: specValidation.warnings,
|
|
287
|
+
action_coverage: specValidation.action_coverage,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
if (issues.length > 0) {
|
|
291
|
+
result.issues = issues;
|
|
292
|
+
}
|
|
293
|
+
// Deploy if not preview
|
|
294
|
+
if (!preview && personaId) {
|
|
295
|
+
const persona = await client.getPersonaById(personaId);
|
|
296
|
+
if (!persona) {
|
|
297
|
+
return { error: `Persona not found: ${personaId}` };
|
|
298
|
+
}
|
|
299
|
+
await client.updateAiEmployee({
|
|
300
|
+
persona_id: personaId,
|
|
301
|
+
workflow: compiled.workflow_def,
|
|
302
|
+
proto_config: args.proto_config || compiled.proto_config || persona.proto_config,
|
|
303
|
+
});
|
|
304
|
+
result.deployed_to = { persona_id: personaId, persona_name: persona.name };
|
|
305
|
+
}
|
|
306
|
+
else if (!preview && !personaId) {
|
|
307
|
+
// Greenfield: create new persona
|
|
308
|
+
const deployResult = await deployToNewPersona(args, client, compiled, actionRegistry, getTemplateId);
|
|
309
|
+
if (deployResult.workflowDeployError) {
|
|
310
|
+
result.workflow_deploy_error = deployResult.workflowDeployError;
|
|
311
|
+
result.workflow_attempted = deployResult.workflowAttempted;
|
|
312
|
+
result.status = "partial";
|
|
313
|
+
result.hint = "Persona created, config set, but workflow deploy failed. Check workflow_attempted for details.";
|
|
314
|
+
result.deployed_to = {
|
|
315
|
+
persona_id: deployResult.personaId,
|
|
316
|
+
persona_name: deployResult.personaName,
|
|
317
|
+
created: true,
|
|
318
|
+
workflow_deployed: false,
|
|
319
|
+
};
|
|
320
|
+
return result;
|
|
321
|
+
}
|
|
322
|
+
else if (deployResult.success) {
|
|
323
|
+
result.deployed_to = {
|
|
324
|
+
persona_id: deployResult.personaId,
|
|
325
|
+
persona_name: deployResult.personaName,
|
|
326
|
+
created: true,
|
|
327
|
+
};
|
|
328
|
+
result.status = "deployed";
|
|
329
|
+
result.next_steps = [
|
|
330
|
+
`Persona "${deployResult.personaName}" created with template workflow.`,
|
|
331
|
+
`To customize the workflow, use: workflow(persona_id="${deployResult.personaId}", input="add search node", preview=false)`,
|
|
332
|
+
];
|
|
333
|
+
}
|
|
334
|
+
else if (deployResult.error) {
|
|
335
|
+
return { error: deployResult.error, hint: deployResult.hint };
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
result.hint = deployResult.hint;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (preview) {
|
|
342
|
+
result.next_steps = [
|
|
343
|
+
"Review the generated workflow_def",
|
|
344
|
+
personaId
|
|
345
|
+
? `Deploy with: workflow(mode="generate", input=..., persona_id="${personaId}", preview=false)`
|
|
346
|
+
: "Create persona first: persona(mode='create', name='...', type='...')",
|
|
347
|
+
];
|
|
348
|
+
}
|
|
349
|
+
return result;
|
|
350
|
+
}
|