@ema.co/mcp-toolkit 2026.1.24 → 2026.1.26-3

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.

Files changed (81) hide show
  1. package/README.md +10 -2
  2. package/dist/mcp/handlers/action/index.js +3 -18
  3. package/dist/mcp/handlers/data/index.js +385 -41
  4. package/dist/mcp/handlers/data/templates.js +107 -0
  5. package/dist/mcp/handlers/deprecation.js +50 -0
  6. package/dist/mcp/handlers/env/index.js +8 -4
  7. package/dist/mcp/handlers/knowledge/index.js +44 -237
  8. package/dist/mcp/handlers/persona/create.js +5 -11
  9. package/dist/mcp/handlers/persona/index.js +5 -1
  10. package/dist/mcp/handlers/persona/version.js +234 -0
  11. package/dist/mcp/handlers/reference/index.js +101 -1
  12. package/dist/mcp/handlers/sync/index.js +3 -18
  13. package/dist/mcp/handlers/template/index.js +75 -10
  14. package/dist/mcp/handlers/workflow/analyze.js +171 -0
  15. package/dist/mcp/handlers/workflow/compare.js +70 -0
  16. package/dist/mcp/handlers/workflow/compile.js +39 -0
  17. package/dist/mcp/handlers/workflow/deploy.js +73 -0
  18. package/dist/mcp/handlers/workflow/generate.js +350 -0
  19. package/dist/mcp/handlers/workflow/index.js +136 -0
  20. package/dist/mcp/handlers/workflow/modify.js +456 -0
  21. package/dist/mcp/handlers/workflow/optimize.js +136 -0
  22. package/dist/mcp/handlers/workflow/types.js +4 -0
  23. package/dist/mcp/handlers/workflow/utils.js +132 -0
  24. package/dist/mcp/handlers-consolidated.js +62 -2691
  25. package/dist/mcp/prompts.js +13 -14
  26. package/dist/mcp/resources.js +55 -54
  27. package/dist/mcp/server.js +93 -124
  28. package/dist/mcp/{tools-v2.js → tools.js} +1 -1
  29. package/dist/mcp/workflow-operations.js +2 -2
  30. package/dist/sdk/client-adapter.js +267 -32
  31. package/dist/sdk/client.js +31 -15
  32. package/dist/sdk/ema-client.js +183 -0
  33. package/dist/sdk/generated/template-fallbacks.js +123 -0
  34. package/dist/sdk/guidance.js +65 -11
  35. package/dist/sdk/index.js +3 -1
  36. package/dist/sdk/knowledge.js +16 -86
  37. package/dist/sdk/workflow-intent.js +27 -0
  38. package/dist/sdk/workflow-transformer.js +0 -342
  39. package/docs/DEBUG-ANALYSIS-unused-category-type-mismatch.md +481 -0
  40. package/docs/TODO-fix-analyzer-and-modify.md +182 -0
  41. package/package.json +9 -4
  42. package/dist/mcp/tools-consolidated.js +0 -866
  43. package/dist/mcp/tools-legacy.js +0 -736
  44. package/docs/CODEBASE-ANALYSIS-2026-01-23.md +0 -936
  45. package/docs/CODEBASE-ANALYSIS-PRIORITIZED.md +0 -774
  46. package/docs/api-contracts.md +0 -216
  47. package/docs/auto-builder-analysis.md +0 -271
  48. package/docs/blog/mcp-tool-design-lessons.md +0 -309
  49. package/docs/data-architecture.md +0 -166
  50. package/docs/ema-auto-builder-guide.html +0 -394
  51. package/docs/lessons-learned.md +0 -209
  52. package/docs/llm-native-workflow-design.md +0 -252
  53. package/docs/local-generation.md +0 -508
  54. package/docs/mcp-flow-diagram.md +0 -135
  55. package/docs/migration/action-composition-migration.md +0 -270
  56. package/docs/naming-conventions.md +0 -278
  57. package/docs/proposals/HANDOFF-tool-restructure.md +0 -526
  58. package/docs/proposals/action-composition.md +0 -490
  59. package/docs/proposals/explicit-method-restructure.md +0 -328
  60. package/docs/proposals/mcp-tool-restructure-2026-01.md +0 -366
  61. package/docs/proposals/self-contained-guidance.md +0 -427
  62. package/docs/proto-sdk-generation.md +0 -242
  63. package/docs/release-impact.md +0 -102
  64. package/docs/release-process.md +0 -157
  65. package/docs/staging.RULE.md +0 -142
  66. package/docs/test-persona-creation.md +0 -196
  67. package/docs/tool-consolidation-v2.md +0 -225
  68. package/docs/tool-response-standards.md +0 -256
  69. package/resources/docs/getting-started.md +0 -97
  70. package/resources/templates/auto-builder-rules.md +0 -224
  71. package/resources/templates/chat-ai/README.md +0 -119
  72. package/resources/templates/chat-ai/persona-config.json +0 -111
  73. package/resources/templates/dashboard-ai/README.md +0 -156
  74. package/resources/templates/dashboard-ai/persona-config.json +0 -180
  75. package/resources/templates/demo-scenarios/README.md +0 -63
  76. package/resources/templates/demo-scenarios/test-published-package.md +0 -116
  77. package/resources/templates/document-gen-ai/README.md +0 -132
  78. package/resources/templates/document-gen-ai/persona-config.json +0 -316
  79. package/resources/templates/voice-ai/README.md +0 -123
  80. package/resources/templates/voice-ai/persona-config.json +0 -74
  81. package/resources/templates/voice-ai/workflow-prompt.md +0 -121
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Workflow Compare Handler
3
+ *
4
+ * Compares two workflow versions to identify differences.
5
+ */
6
+ /**
7
+ * Compare two workflows and return differences
8
+ */
9
+ function compareWorkflows(workflow1, workflow2) {
10
+ const differences = [];
11
+ if (!workflow1 && !workflow2)
12
+ return differences;
13
+ if (!workflow1) {
14
+ differences.push({ type: "added", path: "workflow", new_value: "entire workflow" });
15
+ return differences;
16
+ }
17
+ if (!workflow2) {
18
+ differences.push({ type: "removed", path: "workflow", old_value: "entire workflow" });
19
+ return differences;
20
+ }
21
+ // Compare actions
22
+ const actions1 = workflow1.actions || [];
23
+ const actions2 = workflow2.actions || [];
24
+ const actionIds1 = new Set(actions1.map(a => (a.id ?? a.name)));
25
+ const actionIds2 = new Set(actions2.map(a => (a.id ?? a.name)));
26
+ // Find added/removed actions
27
+ for (const id of actionIds2) {
28
+ if (!actionIds1.has(id)) {
29
+ differences.push({ type: "added", path: `actions.${id}`, new_value: id });
30
+ }
31
+ }
32
+ for (const id of actionIds1) {
33
+ if (!actionIds2.has(id)) {
34
+ differences.push({ type: "removed", path: `actions.${id}`, old_value: id });
35
+ }
36
+ }
37
+ // Node count change
38
+ if (actions1.length !== actions2.length) {
39
+ differences.push({
40
+ type: "changed",
41
+ path: "actions.length",
42
+ old_value: actions1.length,
43
+ new_value: actions2.length,
44
+ });
45
+ }
46
+ return differences;
47
+ }
48
+ /**
49
+ * Handle workflow compare mode
50
+ */
51
+ export async function handleWorkflowCompare(args, client) {
52
+ const personaId = args.persona_id;
53
+ const compareTo = args.compare_to;
54
+ if (!personaId || !compareTo) {
55
+ return { error: "persona_id and compare_to required for compare mode" };
56
+ }
57
+ const persona1 = await client.getPersonaById(personaId);
58
+ const persona2 = await client.getPersonaById(compareTo);
59
+ if (!persona1 || !persona2) {
60
+ return { error: "One or both personas not found" };
61
+ }
62
+ const workflow1 = persona1.workflow_def;
63
+ const workflow2 = persona2.workflow_def;
64
+ return {
65
+ mode: "compare",
66
+ persona_1: { id: persona1.id, name: persona1.name },
67
+ persona_2: { id: persona2.id, name: persona2.name },
68
+ differences: compareWorkflows(workflow1, workflow2),
69
+ };
70
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Workflow Compile Handler
3
+ *
4
+ * Compiles a WorkflowSpec (simplified nodes array) into a full workflow_def.
5
+ */
6
+ import { compileWorkflow } from "../../../sdk/workflow-generator.js";
7
+ import { ensureActionRegistry } from "../../../sdk/action-registry.js";
8
+ /**
9
+ * Handle workflow compile mode
10
+ *
11
+ * Accepts nodes in simplified format and compiles to workflow_def.
12
+ * Uses the same pattern as the monolith - passes through to compileWorkflow.
13
+ */
14
+ export async function handleWorkflowCompile(args, client) {
15
+ const nodes = args.nodes;
16
+ const resultMappings = args.result_mappings;
17
+ if (!nodes) {
18
+ return { error: "nodes required for compile mode" };
19
+ }
20
+ // Load action registry for API-driven action versions/namespaces
21
+ const compileRegistry = await ensureActionRegistry(client);
22
+ // Build spec - use any to allow flexible node format from API
23
+ // compileWorkflow will validate and transform as needed
24
+ const spec = {
25
+ name: args.name || "Compiled Workflow",
26
+ description: args.description || "Generated workflow",
27
+ personaType: args.type || "chat",
28
+ nodes: nodes,
29
+ resultMappings: resultMappings || [],
30
+ };
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ const compiled = compileWorkflow(spec, { registry: compileRegistry });
33
+ return {
34
+ mode: "compile",
35
+ workflow_def: compiled.workflow_def,
36
+ proto_config: compiled.proto_config,
37
+ registry_loaded: compileRegistry.isLoaded(),
38
+ };
39
+ }
@@ -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
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Workflow Handler - Unified greenfield/brownfield operations
3
+ *
4
+ * Modes:
5
+ * - generate: Create new workflow from input
6
+ * - modify/extend: Transform existing workflow
7
+ * - analyze: Return current state + issues
8
+ * - deploy: Deploy workflow to persona
9
+ * - compare: Compare two workflow versions
10
+ * - compile: Compile WorkflowSpec to workflow_def
11
+ * - optimize: Auto-fix issues
12
+ *
13
+ * Note: During extraction, individual mode handlers are being moved here.
14
+ * Until complete, delegates to handlers-consolidated.ts.
15
+ */
16
+ // Re-export types
17
+ export * from "./types.js";
18
+ // Re-export utilities
19
+ export * from "./utils.js";
20
+ // Import mode handlers
21
+ import { handleWorkflowAnalyze } from "./analyze.js";
22
+ import { handleWorkflowCompare } from "./compare.js";
23
+ import { handleWorkflowCompile } from "./compile.js";
24
+ import { handleWorkflowOptimize } from "./optimize.js";
25
+ import { handleWorkflowDeploy } from "./deploy.js";
26
+ import { handleWorkflowGenerate } from "./generate.js";
27
+ import { handleWorkflowModify } from "./modify.js";
28
+ // Re-export mode handlers
29
+ export { handleWorkflowAnalyze } from "./analyze.js";
30
+ export { handleWorkflowCompare } from "./compare.js";
31
+ export { handleWorkflowCompile } from "./compile.js";
32
+ export { handleWorkflowOptimize } from "./optimize.js";
33
+ export { handleWorkflowDeploy } from "./deploy.js";
34
+ export { handleWorkflowGenerate } from "./generate.js";
35
+ export { handleWorkflowModify } from "./modify.js";
36
+ export { applyWorkflowModifications, buildWorkflowAction, buildInputBinding, } from "./modify.js";
37
+ /**
38
+ * Main workflow handler with mode-based dispatch
39
+ *
40
+ * Routes to the appropriate extracted handler based on effective mode.
41
+ */
42
+ export async function handleWorkflow(args, client, getTemplateId) {
43
+ const personaId = args.persona_id;
44
+ const workflowDef = args.workflow_def;
45
+ const input = args.input;
46
+ const optimize = args.optimize;
47
+ const compareTo = args.compare_to;
48
+ const operations = args.operations;
49
+ // Smart mode detection - user doesn't need to specify
50
+ let effectiveMode;
51
+ if (args.mode === "compile") {
52
+ effectiveMode = "compile";
53
+ }
54
+ else if (compareTo) {
55
+ effectiveMode = "compare";
56
+ }
57
+ else if (optimize && personaId) {
58
+ effectiveMode = "optimize";
59
+ }
60
+ else if (personaId && workflowDef) {
61
+ effectiveMode = "deploy";
62
+ }
63
+ else if (personaId && (input || operations)) {
64
+ // Modify mode: either natural language input OR structured operations
65
+ effectiveMode = "modify";
66
+ }
67
+ else if (input && !personaId) {
68
+ effectiveMode = "generate";
69
+ }
70
+ else if (personaId || workflowDef || args.include) {
71
+ effectiveMode = "analyze";
72
+ }
73
+ else {
74
+ effectiveMode = "analyze";
75
+ }
76
+ // Explicit mode support
77
+ const legacyMode = args.mode;
78
+ if (legacyMode === "extend")
79
+ effectiveMode = "modify";
80
+ if (legacyMode === "modify")
81
+ effectiveMode = "modify";
82
+ if (legacyMode === "deploy")
83
+ effectiveMode = "deploy";
84
+ // Route to extracted handler
85
+ switch (effectiveMode) {
86
+ case "generate":
87
+ return handleWorkflowGenerate(args, client, getTemplateId);
88
+ case "modify":
89
+ case "extend":
90
+ return handleWorkflowModify(args, client);
91
+ case "analyze":
92
+ return handleWorkflowAnalyze(args, client);
93
+ case "deploy":
94
+ return handleWorkflowDeploy(args, client);
95
+ case "compare":
96
+ return handleWorkflowCompare(args, client);
97
+ case "compile":
98
+ return handleWorkflowCompile(args, client);
99
+ case "optimize":
100
+ return handleWorkflowOptimize(args, client);
101
+ default:
102
+ return { error: `Unknown mode: ${effectiveMode}` };
103
+ }
104
+ }
105
+ /**
106
+ * Check if a workflow mode has been extracted
107
+ */
108
+ export function hasExtractedWorkflowHandler(mode) {
109
+ const extractedModes = ["analyze", "compare", "compile", "optimize", "deploy", "generate", "modify", "extend"];
110
+ return extractedModes.includes(mode);
111
+ }
112
+ /**
113
+ * Get handler for a specific workflow mode
114
+ * Note: generate handler has a different signature (requires getTemplateId)
115
+ */
116
+ export function getWorkflowModeHandler(mode) {
117
+ switch (mode) {
118
+ case "analyze":
119
+ return handleWorkflowAnalyze;
120
+ case "compare":
121
+ return handleWorkflowCompare;
122
+ case "compile":
123
+ return handleWorkflowCompile;
124
+ case "optimize":
125
+ return handleWorkflowOptimize;
126
+ case "deploy":
127
+ return handleWorkflowDeploy;
128
+ case "generate":
129
+ return handleWorkflowGenerate;
130
+ case "modify":
131
+ case "extend":
132
+ return handleWorkflowModify;
133
+ default:
134
+ return undefined;
135
+ }
136
+ }