@ema.co/mcp-toolkit 2026.1.25 → 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 (85) 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/sync/index.js +3 -18
  12. package/dist/mcp/handlers/template/index.js +75 -10
  13. package/dist/mcp/handlers/workflow/analyze.js +171 -0
  14. package/dist/mcp/handlers/workflow/compare.js +70 -0
  15. package/dist/mcp/handlers/workflow/compile.js +39 -0
  16. package/dist/mcp/handlers/workflow/deploy.js +73 -0
  17. package/dist/mcp/handlers/workflow/generate.js +350 -0
  18. package/dist/mcp/handlers/workflow/index.js +136 -0
  19. package/dist/mcp/handlers/workflow/modify.js +456 -0
  20. package/dist/mcp/handlers/workflow/optimize.js +136 -0
  21. package/dist/mcp/handlers/workflow/types.js +4 -0
  22. package/dist/mcp/handlers/workflow/utils.js +132 -0
  23. package/dist/mcp/handlers-consolidated.js +62 -2691
  24. package/dist/mcp/prompts.js +13 -14
  25. package/dist/mcp/resources.js +55 -54
  26. package/dist/mcp/server.js +93 -124
  27. package/dist/mcp/{tools-v2.js → tools.js} +1 -1
  28. package/dist/mcp/workflow-operations.js +2 -2
  29. package/dist/sdk/client-adapter.js +267 -32
  30. package/dist/sdk/client.js +31 -15
  31. package/dist/sdk/ema-client.js +183 -0
  32. package/dist/sdk/generated/template-fallbacks.js +123 -0
  33. package/dist/sdk/guidance.js +65 -11
  34. package/dist/sdk/index.js +3 -1
  35. package/dist/sdk/knowledge.js +16 -86
  36. package/dist/sdk/workflow-intent.js +27 -0
  37. package/dist/sdk/workflow-transformer.js +0 -342
  38. package/docs/DEBUG-ANALYSIS-unused-category-type-mismatch.md +481 -0
  39. package/docs/TODO-fix-analyzer-and-modify.md +182 -0
  40. package/package.json +9 -4
  41. package/dist/mcp/tools-consolidated.js +0 -875
  42. package/dist/mcp/tools-legacy.js +0 -736
  43. package/docs/CODEBASE-ANALYSIS-2026-01-23.md +0 -936
  44. package/docs/CODEBASE-ANALYSIS-PRIORITIZED.md +0 -774
  45. package/docs/api-contracts.md +0 -216
  46. package/docs/auto-builder-analysis.md +0 -271
  47. package/docs/blog/mcp-tool-design-lessons.md +0 -309
  48. package/docs/data-architecture.md +0 -166
  49. package/docs/demos/ap-invoice-generation.md +0 -347
  50. package/docs/demos/ap-invoice-processing.md +0 -271
  51. package/docs/ema-auto-builder-guide.html +0 -394
  52. package/docs/lessons-learned.md +0 -209
  53. package/docs/llm-native-workflow-design.md +0 -252
  54. package/docs/local-generation.md +0 -508
  55. package/docs/mcp-flow-diagram.md +0 -135
  56. package/docs/migration/action-composition-migration.md +0 -270
  57. package/docs/naming-conventions.md +0 -278
  58. package/docs/proposals/HANDOFF-tool-restructure.md +0 -526
  59. package/docs/proposals/action-composition.md +0 -490
  60. package/docs/proposals/explicit-method-restructure.md +0 -328
  61. package/docs/proposals/mcp-tool-restructure-2026-01.md +0 -366
  62. package/docs/proposals/self-contained-guidance.md +0 -427
  63. package/docs/proto-sdk-generation.md +0 -242
  64. package/docs/release-impact.md +0 -102
  65. package/docs/release-process.md +0 -157
  66. package/docs/staging.RULE.md +0 -142
  67. package/docs/test-persona-creation.md +0 -196
  68. package/docs/tool-consolidation-v2.md +0 -225
  69. package/docs/tool-response-standards.md +0 -256
  70. package/resources/demo-kits/README.md +0 -175
  71. package/resources/demo-kits/finance-ap/manifest.json +0 -150
  72. package/resources/demo-kits/tags.json +0 -91
  73. package/resources/docs/getting-started.md +0 -97
  74. package/resources/templates/auto-builder-rules.md +0 -224
  75. package/resources/templates/chat-ai/README.md +0 -119
  76. package/resources/templates/chat-ai/persona-config.json +0 -111
  77. package/resources/templates/dashboard-ai/README.md +0 -156
  78. package/resources/templates/dashboard-ai/persona-config.json +0 -180
  79. package/resources/templates/demo-scenarios/README.md +0 -63
  80. package/resources/templates/demo-scenarios/test-published-package.md +0 -116
  81. package/resources/templates/document-gen-ai/README.md +0 -132
  82. package/resources/templates/document-gen-ai/persona-config.json +0 -316
  83. package/resources/templates/voice-ai/README.md +0 -123
  84. package/resources/templates/voice-ai/persona-config.json +0 -74
  85. package/resources/templates/voice-ai/workflow-prompt.md +0 -121
@@ -0,0 +1,456 @@
1
+ /**
2
+ * Workflow Modify Handler
3
+ *
4
+ * Handles brownfield workflow modifications via STRUCTURED OPERATIONS.
5
+ *
6
+ * CRITICAL: This handler does NOT parse natural language.
7
+ * The Agent (LLM) builds structured operations and passes them here.
8
+ *
9
+ * See: src/mcp/AGENTS.md for anti-patterns to avoid.
10
+ */
11
+ import { detectWorkflowIssues, suggestWorkflowFixes } from "../../../sdk/knowledge.js";
12
+ import { sanitizeWorkflowForDeploy } from "./utils.js";
13
+ import { ensureSchemaRegistry } from "../../../sdk/workflow-validator.js";
14
+ // ─────────────────────────────────────────────────────────────────────────
15
+ // Action Building Helpers (use ActionRegistry for metadata)
16
+ // ─────────────────────────────────────────────────────────────────────────
17
+ /**
18
+ * Get action metadata from registry.
19
+ * Falls back to defaults if action not found.
20
+ */
21
+ function getActionMetadata(actionType, registry) {
22
+ // Try to get from registry first
23
+ if (registry?.isLoaded()) {
24
+ const action = registry.getAction(actionType);
25
+ if (action) {
26
+ return {
27
+ namespaces: ["actions", "emainternal"], // Standard namespace
28
+ version: action.version,
29
+ apiName: actionType,
30
+ };
31
+ }
32
+ }
33
+ // Fallback defaults (registry should have these, but just in case)
34
+ return {
35
+ namespaces: ["actions", "emainternal"],
36
+ version: "v0",
37
+ apiName: actionType,
38
+ };
39
+ }
40
+ /**
41
+ * Build an input binding for a workflow action.
42
+ */
43
+ function buildInputBinding(config) {
44
+ switch (config.type) {
45
+ case "action_output":
46
+ return {
47
+ actionOutput: {
48
+ actionName: config.actionName,
49
+ output: config.output,
50
+ },
51
+ autoDetectedBinding: false,
52
+ };
53
+ case "inline_string":
54
+ return {
55
+ inline: { wellKnown: { stringValue: config.value } },
56
+ autoDetectedBinding: false,
57
+ };
58
+ case "inline_number":
59
+ return {
60
+ inline: { number: config.value },
61
+ autoDetectedBinding: false,
62
+ };
63
+ case "llm_inferred":
64
+ default:
65
+ return { llmInferred: {} };
66
+ }
67
+ }
68
+ /**
69
+ * Build a complete workflow action structure from InsertNodeConfig.
70
+ */
71
+ function buildWorkflowAction(config, registry) {
72
+ const nodeName = config.node_name || `${config.action_type}_${Date.now()}`;
73
+ const metadata = getActionMetadata(config.action_type, registry);
74
+ const action = {
75
+ name: nodeName,
76
+ action: {
77
+ name: {
78
+ namespaces: metadata.namespaces,
79
+ name: metadata.apiName,
80
+ },
81
+ version: metadata.version,
82
+ },
83
+ inputs: {},
84
+ displaySettings: {
85
+ displayName: config.display_name,
86
+ description: config.description || "",
87
+ coordinates: { x: 1200, y: 400 },
88
+ },
89
+ typeArguments: {},
90
+ tools: [],
91
+ disableHumanInteraction: false,
92
+ };
93
+ // Build inputs from config
94
+ if (config.inputs) {
95
+ const inputs = {};
96
+ for (const [inputName, binding] of Object.entries(config.inputs)) {
97
+ inputs[inputName] = buildInputBinding(binding);
98
+ }
99
+ action.inputs = inputs;
100
+ }
101
+ // Build runIf condition if provided
102
+ if (config.runIf) {
103
+ const operatorMap = { eq: 1, neq: 2 };
104
+ action.runIf = {
105
+ lhs: {
106
+ actionOutput: {
107
+ actionName: config.runIf.sourceAction,
108
+ output: config.runIf.sourceOutput,
109
+ },
110
+ autoDetectedBinding: false,
111
+ },
112
+ operator: operatorMap[config.runIf.operator] || 1,
113
+ rhs: {
114
+ inline: { enumValue: config.runIf.value },
115
+ autoDetectedBinding: false,
116
+ },
117
+ };
118
+ }
119
+ return action;
120
+ }
121
+ // ─────────────────────────────────────────────────────────────────────────
122
+ // Operation Executors
123
+ // ─────────────────────────────────────────────────────────────────────────
124
+ /**
125
+ * Execute an insert operation.
126
+ */
127
+ function executeInsert(actions, op, registry, result) {
128
+ const config = op.insert;
129
+ if (!config) {
130
+ result.changesApplied.push("Insert failed: no configuration provided");
131
+ return;
132
+ }
133
+ if (!config.action_type || !config.display_name) {
134
+ result.changesApplied.push("Insert failed: action_type and display_name required");
135
+ return;
136
+ }
137
+ // Build the new action
138
+ const newAction = buildWorkflowAction(config, registry);
139
+ const newNodeName = String(newAction.name);
140
+ // Add to actions array
141
+ actions.push(newAction);
142
+ result.nodesAdded.push(newNodeName);
143
+ result.changesApplied.push(`Inserted node "${newNodeName}" (${config.display_name})`);
144
+ // Handle insert_before: modify downstream node
145
+ if (config.insert_before) {
146
+ const targetNode = actions.find(a => String(a.name || "").toLowerCase() === config.insert_before.toLowerCase());
147
+ if (!targetNode) {
148
+ result.changesApplied.push(`Warning: target node "${config.insert_before}" not found for downstream modification`);
149
+ return;
150
+ }
151
+ // Option 1: Add runIf to downstream node
152
+ if (config.add_runif_to_downstream && config.new_node_output) {
153
+ const operatorMap = { eq: 1, neq: 2 };
154
+ targetNode.runIf = {
155
+ lhs: {
156
+ actionOutput: {
157
+ actionName: newNodeName,
158
+ output: config.new_node_output,
159
+ },
160
+ autoDetectedBinding: false,
161
+ },
162
+ operator: operatorMap["eq"],
163
+ rhs: {
164
+ inline: { enumValue: "HITL Success" },
165
+ autoDetectedBinding: false,
166
+ },
167
+ };
168
+ result.nodesModified.push(String(targetNode.name));
169
+ result.changesApplied.push(`Added runIf to ${targetNode.name}: waits for ${newNodeName}.${config.new_node_output}`);
170
+ }
171
+ }
172
+ }
173
+ /**
174
+ * Execute a remove operation.
175
+ */
176
+ function executeRemove(actions, op, result) {
177
+ const removeConfig = op.remove;
178
+ if (!removeConfig?.nodes?.length) {
179
+ result.changesApplied.push("Remove failed: no nodes specified");
180
+ return;
181
+ }
182
+ for (const nodeName of removeConfig.nodes) {
183
+ const index = actions.findIndex(a => String(a.name || "") === nodeName);
184
+ if (index >= 0) {
185
+ actions.splice(index, 1);
186
+ result.nodesRemoved.push(nodeName);
187
+ result.changesApplied.push(`Removed node: ${nodeName}`);
188
+ }
189
+ else {
190
+ result.changesApplied.push(`Warning: node "${nodeName}" not found`);
191
+ }
192
+ }
193
+ }
194
+ /**
195
+ * Execute a rewire operation.
196
+ */
197
+ function executeRewire(actions, op, result) {
198
+ const rewireConfig = op.rewire;
199
+ if (!rewireConfig) {
200
+ result.changesApplied.push("Rewire failed: no configuration provided");
201
+ return;
202
+ }
203
+ const { target_node, target_input, source_node, source_output } = rewireConfig;
204
+ // Find target node
205
+ const targetAction = actions.find(a => String(a.name || "").toLowerCase() === target_node.toLowerCase());
206
+ if (!targetAction) {
207
+ result.changesApplied.push(`Rewire failed: target node "${target_node}" not found`);
208
+ return;
209
+ }
210
+ // Update the input binding
211
+ const inputs = (targetAction.inputs || {});
212
+ inputs[target_input] = {
213
+ actionOutput: {
214
+ actionName: source_node,
215
+ output: source_output,
216
+ },
217
+ autoDetectedBinding: false,
218
+ };
219
+ targetAction.inputs = inputs;
220
+ result.nodesModified.push(target_node);
221
+ result.connectionsChanged.push(`${source_node}.${source_output} → ${target_node}.${target_input}`);
222
+ result.changesApplied.push(`Rewired ${target_node}.${target_input} to use ${source_node}.${source_output}`);
223
+ }
224
+ /**
225
+ * Execute a config update operation.
226
+ */
227
+ function executeConfigUpdate(workflow, op, result) {
228
+ const configUpdates = op.config;
229
+ if (!configUpdates) {
230
+ result.changesApplied.push("Config update failed: no configuration provided");
231
+ return;
232
+ }
233
+ // Config updates are typically applied to proto_config, not workflow_def
234
+ // Store them for the caller to apply
235
+ result.changesApplied.push(`Config updates prepared: ${Object.keys(configUpdates).join(", ")}`);
236
+ }
237
+ // ─────────────────────────────────────────────────────────────────────────
238
+ // Main Operation Applier
239
+ // ─────────────────────────────────────────────────────────────────────────
240
+ /**
241
+ * Apply structured modification operations to a workflow.
242
+ *
243
+ * IMPORTANT: This function executes STRUCTURED operations.
244
+ * It does NOT parse natural language - that's the Agent's job.
245
+ */
246
+ function applyWorkflowModifications(existingWorkflow, operations, registry) {
247
+ // Deep copy workflow
248
+ const workflow = JSON.parse(JSON.stringify(existingWorkflow));
249
+ const actions = (workflow.actions || []);
250
+ const result = {
251
+ workflow,
252
+ changesApplied: [],
253
+ nodesAdded: [],
254
+ nodesRemoved: [],
255
+ nodesModified: [],
256
+ connectionsChanged: [],
257
+ };
258
+ for (const op of operations) {
259
+ switch (op.type) {
260
+ case "insert":
261
+ executeInsert(actions, op, registry, result);
262
+ break;
263
+ case "remove":
264
+ executeRemove(actions, op, result);
265
+ break;
266
+ case "rewire":
267
+ executeRewire(actions, op, result);
268
+ break;
269
+ case "update_config":
270
+ executeConfigUpdate(workflow, op, result);
271
+ break;
272
+ case "consolidate":
273
+ // Consolidation (merging multiple nodes into one) is a multi-step operation
274
+ // that requires the Agent to use multiple operations instead:
275
+ // 1. Add the new consolidated node (insert)
276
+ // 2. Rewire connections to use the new node (rewire)
277
+ // 3. Remove the old nodes (remove)
278
+ // Mark as not executed - Agent should break this down
279
+ result.changesApplied.push("SKIPPED: consolidate not supported as a single operation. " +
280
+ "Use insert + rewire + remove operations instead.");
281
+ break;
282
+ default:
283
+ result.changesApplied.push(`Unknown operation type: ${op.type}`);
284
+ }
285
+ }
286
+ workflow.actions = actions;
287
+ result.workflow = workflow;
288
+ return result;
289
+ }
290
+ // ─────────────────────────────────────────────────────────────────────────
291
+ // Main Handler
292
+ // ─────────────────────────────────────────────────────────────────────────
293
+ /**
294
+ * Handle workflow modify/extend mode.
295
+ *
296
+ * This handler accepts STRUCTURED OPERATIONS from the Agent.
297
+ * It does NOT parse natural language input.
298
+ *
299
+ * @param args.persona_id - The persona to modify
300
+ * @param args.operations - Array of structured ModificationOperation
301
+ * @param args.preview - If true, return preview without deploying
302
+ */
303
+ export async function handleWorkflowModify(args, client) {
304
+ const personaId = args.persona_id || args.id;
305
+ if (!personaId) {
306
+ return {
307
+ error: "persona_id required",
308
+ hint: "Specify the persona to modify: persona_id='<id>'",
309
+ };
310
+ }
311
+ // Get operations from args
312
+ const operations = args.operations;
313
+ const preview = Boolean(args.preview);
314
+ // If no structured operations provided, return guidance
315
+ if (!operations || operations.length === 0) {
316
+ // Fetch persona for context
317
+ const persona = await client.getPersonaById(personaId);
318
+ if (!persona) {
319
+ return { error: `Persona "${personaId}" not found` };
320
+ }
321
+ const workflow = persona.workflow_def;
322
+ if (!workflow) {
323
+ return { error: "Persona has no workflow_def" };
324
+ }
325
+ const actions = (workflow.actions || []);
326
+ // Run analysis to give Agent context
327
+ const issues = detectWorkflowIssues(workflow);
328
+ const suggestions = suggestWorkflowFixes(issues);
329
+ // Load schema registry for action catalog
330
+ const schemaRegistry = await ensureSchemaRegistry(client);
331
+ const availableActions = schemaRegistry.isLoaded()
332
+ ? schemaRegistry.getAllActions().map(a => ({ name: a.name, category: a.category, description: a.description }))
333
+ : [];
334
+ return {
335
+ message: "No operations provided. Returning workflow context for Agent to build operations.",
336
+ persona_id: personaId,
337
+ persona_name: persona.name,
338
+ // Current workflow state
339
+ current_nodes: actions.map(a => ({
340
+ name: a.name,
341
+ display_name: a.displaySettings?.displayName,
342
+ action_type: a.action?.name?.name,
343
+ })),
344
+ // Analysis
345
+ issues: issues.map(i => ({ severity: i.severity, type: i.type, node: i.node, reason: i.reason })),
346
+ suggestions,
347
+ // Available actions from registry
348
+ available_actions: availableActions.slice(0, 20), // Top 20 for context
349
+ // Guidance for Agent
350
+ _tip: "Build ModificationOperation[] and pass as 'operations' parameter",
351
+ _next_step: "Use the current_nodes and available_actions to build structured operations",
352
+ // Example operation structures
353
+ example_operations: {
354
+ insert_hitl: {
355
+ type: "insert",
356
+ insert: {
357
+ action_type: "hitl",
358
+ display_name: "Approval Required",
359
+ insert_before: "target_node_name",
360
+ add_runif_to_downstream: true,
361
+ new_node_output: "hitl_status_HITL Success",
362
+ },
363
+ },
364
+ remove_nodes: {
365
+ type: "remove",
366
+ remove: { nodes: ["node_to_remove"] },
367
+ },
368
+ rewire: {
369
+ type: "rewire",
370
+ rewire: {
371
+ target_node: "downstream_node",
372
+ target_input: "query",
373
+ source_node: "upstream_node",
374
+ source_output: "response",
375
+ },
376
+ },
377
+ },
378
+ };
379
+ }
380
+ // Fetch persona
381
+ const persona = await client.getPersonaById(personaId);
382
+ if (!persona) {
383
+ return { error: `Persona "${personaId}" not found` };
384
+ }
385
+ const workflow = persona.workflow_def;
386
+ if (!workflow) {
387
+ return { error: "Persona has no workflow_def to modify" };
388
+ }
389
+ // Load schema registry for action metadata
390
+ const schemaRegistry = await ensureSchemaRegistry(client);
391
+ // Apply the structured operations
392
+ const result = applyWorkflowModifications(workflow, operations, schemaRegistry);
393
+ // Sanitize before deploy
394
+ const sanitized = sanitizeWorkflowForDeploy(result.workflow);
395
+ // Run validation
396
+ const postIssues = detectWorkflowIssues(sanitized);
397
+ const errors = postIssues.filter(i => i.severity === "critical");
398
+ if (preview) {
399
+ return {
400
+ preview: true,
401
+ persona_id: personaId,
402
+ persona_name: persona.name,
403
+ // What would change
404
+ changes_applied: result.changesApplied,
405
+ nodes_added: result.nodesAdded,
406
+ nodes_removed: result.nodesRemoved,
407
+ nodes_modified: result.nodesModified,
408
+ connections_changed: result.connectionsChanged,
409
+ // Validation
410
+ validation_errors: errors.map(e => e.reason || e.type),
411
+ validation_warnings: postIssues.filter(i => i.severity === "warning").map(w => w.reason || w.type),
412
+ // The modified workflow
413
+ modified_workflow: sanitized,
414
+ _tip: errors.length > 0
415
+ ? "Fix validation errors before deploying"
416
+ : "Preview looks good. Call without preview=true to deploy",
417
+ };
418
+ }
419
+ // Deploy if validation passes
420
+ if (errors.length > 0) {
421
+ return {
422
+ error: "Validation failed - cannot deploy",
423
+ validation_errors: errors.map(e => e.reason || e.type),
424
+ changes_attempted: result.changesApplied,
425
+ _tip: "Fix validation errors and try again",
426
+ };
427
+ }
428
+ // Deploy the workflow
429
+ try {
430
+ await client.updateAiEmployee({
431
+ persona_id: personaId,
432
+ workflow: sanitized,
433
+ });
434
+ return {
435
+ success: true,
436
+ persona_id: personaId,
437
+ persona_name: persona.name,
438
+ changes_applied: result.changesApplied,
439
+ nodes_added: result.nodesAdded,
440
+ nodes_removed: result.nodesRemoved,
441
+ nodes_modified: result.nodesModified,
442
+ connections_changed: result.connectionsChanged,
443
+ _tip: "Workflow updated successfully",
444
+ };
445
+ }
446
+ catch (error) {
447
+ return {
448
+ error: `Deploy failed: ${error instanceof Error ? error.message : String(error)}`,
449
+ changes_attempted: result.changesApplied,
450
+ };
451
+ }
452
+ }
453
+ // ─────────────────────────────────────────────────────────────────────────
454
+ // Exports
455
+ // ─────────────────────────────────────────────────────────────────────────
456
+ export { applyWorkflowModifications, buildWorkflowAction, buildInputBinding };
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Workflow Optimize Handler
3
+ *
4
+ * Detects and fixes workflow issues.
5
+ */
6
+ import { detectWorkflowIssues, suggestWorkflowFixes } from "../../../sdk/knowledge.js";
7
+ /**
8
+ * Apply simple workflow fixes for auto_fixable issues
9
+ * Handles orphan removal with cascading dependency cleanup
10
+ */
11
+ function applySimpleFixes(workflow, issues) {
12
+ // Deep clone to avoid mutating original
13
+ const fixed = JSON.parse(JSON.stringify(workflow));
14
+ // Get nodes array from workflow (handle different structures)
15
+ const getNodes = (w) => {
16
+ if (Array.isArray(w.nodes))
17
+ return w.nodes;
18
+ const wd = w.workflow_def;
19
+ if (wd && Array.isArray(wd.nodes))
20
+ return wd.nodes;
21
+ return undefined;
22
+ };
23
+ const nodes = getNodes(fixed);
24
+ if (!nodes)
25
+ return fixed;
26
+ // Collect all orphan node IDs to remove
27
+ const nodesToRemove = new Set();
28
+ for (const issue of issues) {
29
+ if (issue.type === "orphan" && issue.auto_fixable && issue.node) {
30
+ nodesToRemove.add(issue.node);
31
+ }
32
+ }
33
+ if (nodesToRemove.size === 0)
34
+ return fixed;
35
+ // Step 1: Remove orphan nodes
36
+ const filteredNodes = nodes.filter(n => {
37
+ const nodeId = n.id;
38
+ return nodeId && !nodesToRemove.has(nodeId);
39
+ });
40
+ // Step 2: Clean up dangling references in remaining nodes
41
+ for (const node of filteredNodes) {
42
+ const incomingEdges = node.incoming_edges;
43
+ if (incomingEdges && Array.isArray(incomingEdges)) {
44
+ // Filter out edges that reference removed nodes
45
+ node.incoming_edges = incomingEdges.filter(edge => {
46
+ const sourceNodeId = edge.source_node_id;
47
+ return sourceNodeId && !nodesToRemove.has(sourceNodeId);
48
+ });
49
+ }
50
+ // Also clean up inputBindings if they exist (different workflow format)
51
+ const inputBindings = node.inputBindings;
52
+ if (inputBindings && Array.isArray(inputBindings)) {
53
+ node.inputBindings = inputBindings.filter(binding => {
54
+ const actionOutput = binding.actionOutput;
55
+ const actionName = actionOutput?.actionName;
56
+ return !actionName || !nodesToRemove.has(actionName);
57
+ });
58
+ }
59
+ }
60
+ // Update nodes in the workflow
61
+ if (Array.isArray(fixed.nodes)) {
62
+ fixed.nodes = filteredNodes;
63
+ }
64
+ else {
65
+ const wd = fixed.workflow_def;
66
+ if (wd) {
67
+ wd.nodes = filteredNodes;
68
+ }
69
+ }
70
+ return fixed;
71
+ }
72
+ /**
73
+ * Handle workflow optimize mode
74
+ */
75
+ export async function handleWorkflowOptimize(args, client) {
76
+ const personaId = args.persona_id;
77
+ const preview = args.preview !== false;
78
+ if (!personaId) {
79
+ return { error: "persona_id required for optimize mode" };
80
+ }
81
+ const persona = await client.getPersonaById(personaId);
82
+ if (!persona) {
83
+ return { error: `Persona not found: ${personaId}` };
84
+ }
85
+ const existingWorkflow = persona.workflow_def;
86
+ if (!existingWorkflow) {
87
+ return {
88
+ error: `Persona "${persona.name}" has no workflow to optimize`,
89
+ hint: "Use mode='generate' to create a workflow first",
90
+ };
91
+ }
92
+ // Analyze and detect issues
93
+ const issues = detectWorkflowIssues(existingWorkflow);
94
+ const fixes = suggestWorkflowFixes(issues);
95
+ if (issues.length === 0) {
96
+ return {
97
+ mode: "optimize",
98
+ status: "✅ No issues found",
99
+ persona_id: personaId,
100
+ persona_name: persona.name,
101
+ workflow_healthy: true,
102
+ };
103
+ }
104
+ // Apply fixes
105
+ const fixedWorkflow = applySimpleFixes(existingWorkflow, issues);
106
+ const result = {
107
+ mode: "optimize",
108
+ status: preview ? "preview" : "deployed",
109
+ persona_id: personaId,
110
+ persona_name: persona.name,
111
+ issues_found: issues.length,
112
+ issues: issues.map(i => ({
113
+ type: i.type,
114
+ severity: i.severity,
115
+ reason: i.reason,
116
+ })),
117
+ suggested_fixes: fixes,
118
+ fixed_workflow: fixedWorkflow,
119
+ };
120
+ // If preview=false, deploy the fixed workflow
121
+ if (!preview) {
122
+ await client.updateAiEmployee({
123
+ persona_id: personaId,
124
+ workflow: fixedWorkflow,
125
+ proto_config: args.proto_config || persona.proto_config,
126
+ });
127
+ result.deployed = true;
128
+ }
129
+ else {
130
+ result.next_steps = [
131
+ "Review the suggested fixes and fixed_workflow",
132
+ `Deploy with: workflow(mode="optimize", persona_id="${personaId}", preview=false)`,
133
+ ];
134
+ }
135
+ return result;
136
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Shared types for workflow handlers
3
+ */
4
+ export {};