@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,234 @@
1
+ /**
2
+ * Version Management Handlers
3
+ *
4
+ * Provides local version tracking for personas (snapshot, history, restore).
5
+ * Versions are stored in the local workspace, not on the Ema platform.
6
+ */
7
+ import { createVersionStorage } from "../../../sdk/version-storage.js";
8
+ import { createVersionPolicyEngine } from "../../../sdk/version-policy.js";
9
+ /**
10
+ * Check if a mode is a version management mode.
11
+ */
12
+ export function isVersionMode(mode) {
13
+ return [
14
+ "snapshot",
15
+ "history",
16
+ "restore",
17
+ "version_create",
18
+ "version_list",
19
+ "version_get",
20
+ "version_compare",
21
+ "version_restore",
22
+ "version_policy",
23
+ ].includes(mode);
24
+ }
25
+ /**
26
+ * Handle version management operations.
27
+ *
28
+ * @param mode - The version mode (snapshot, history, restore, version_*)
29
+ * @param args - Handler arguments including id, version, message, etc.
30
+ * @param client - Ema client for fetching personas
31
+ * @param persona - Resolved persona (pre-fetched)
32
+ * @param versionContext - Workspace and environment context
33
+ */
34
+ export async function handleVersion(mode, args, client, persona, versionContext) {
35
+ const storage = createVersionStorage(versionContext.workspaceRoot);
36
+ const engine = createVersionPolicyEngine(storage);
37
+ switch (mode) {
38
+ // ─────────────────────────────────────────────────────────────────────────
39
+ // snapshot / version_create - Create a new version snapshot
40
+ // ─────────────────────────────────────────────────────────────────────────
41
+ case "snapshot":
42
+ case "version_create": {
43
+ // Fetch full persona with workflow
44
+ const fullPersona = await client.getPersonaById(persona.id);
45
+ if (!fullPersona) {
46
+ return { error: `Could not fetch full persona: ${persona.id}` };
47
+ }
48
+ const result = engine.forceCreateVersion(fullPersona, {
49
+ environment: versionContext.environment,
50
+ tenant_id: versionContext.tenant_id,
51
+ message: args.message,
52
+ created_by: "mcp-toolkit",
53
+ });
54
+ if (!result.created || !result.version) {
55
+ return { error: result.reason };
56
+ }
57
+ return {
58
+ success: true,
59
+ version: {
60
+ id: result.version.id,
61
+ version_number: result.version.version_number,
62
+ version_name: result.version.version_name,
63
+ content_hash: result.version.content_hash,
64
+ created_at: result.version.created_at,
65
+ message: result.version.message,
66
+ },
67
+ changes_from_parent: result.changes_from_parent,
68
+ versions_pruned: result.versions_pruned,
69
+ };
70
+ }
71
+ // ─────────────────────────────────────────────────────────────────────────
72
+ // history / version_list - List version history
73
+ // ─────────────────────────────────────────────────────────────────────────
74
+ case "history":
75
+ case "version_list": {
76
+ const versions = engine.listVersions(persona.id, {
77
+ limit: args.limit,
78
+ });
79
+ return {
80
+ persona_id: persona.id,
81
+ persona_name: persona.name,
82
+ versions,
83
+ count: versions.length,
84
+ };
85
+ }
86
+ // ─────────────────────────────────────────────────────────────────────────
87
+ // version_get - Get a specific version
88
+ // ─────────────────────────────────────────────────────────────────────────
89
+ case "version_get": {
90
+ const versionId = args.version ?? "latest";
91
+ const version = engine.getVersion(persona.id, versionId);
92
+ if (!version) {
93
+ return { error: `Version not found: ${versionId}` };
94
+ }
95
+ return {
96
+ persona_id: persona.id,
97
+ persona_name: persona.name,
98
+ version: {
99
+ id: version.id,
100
+ version_number: version.version_number,
101
+ version_name: version.version_name,
102
+ content_hash: version.content_hash,
103
+ created_at: version.created_at,
104
+ created_by: version.created_by,
105
+ trigger: version.trigger,
106
+ message: version.message,
107
+ changes_summary: version.changes_summary,
108
+ },
109
+ snapshot: {
110
+ display_name: version.snapshot.display_name,
111
+ description: version.snapshot.description,
112
+ workflow_id: version.snapshot.workflow_id,
113
+ trigger_type: version.snapshot.trigger_type,
114
+ embedding_enabled: version.snapshot.embedding_enabled,
115
+ has_workflow: !!version.snapshot.workflow_definition,
116
+ has_proto_config: !!version.snapshot.proto_config,
117
+ },
118
+ };
119
+ }
120
+ // ─────────────────────────────────────────────────────────────────────────
121
+ // version_compare - Compare two versions
122
+ // ─────────────────────────────────────────────────────────────────────────
123
+ case "version_compare": {
124
+ const v1 = args.v1;
125
+ const v2 = args.v2;
126
+ if (!v1 || !v2) {
127
+ return { error: "v1 and v2 required for version_compare mode" };
128
+ }
129
+ const result = engine.compareVersions(persona.id, v1, v2);
130
+ if (!result.success) {
131
+ return { error: result.error };
132
+ }
133
+ return {
134
+ persona_id: persona.id,
135
+ persona_name: persona.name,
136
+ comparison: {
137
+ v1: result.diff?.v1,
138
+ v2: result.diff?.v2,
139
+ identical: result.diff?.identical,
140
+ changed_fields: result.diff?.changed_fields,
141
+ workflow_diff: result.diff?.workflow_diff,
142
+ },
143
+ summary: result.summary,
144
+ };
145
+ }
146
+ // ─────────────────────────────────────────────────────────────────────────
147
+ // restore / version_restore - Restore to a previous version
148
+ // ─────────────────────────────────────────────────────────────────────────
149
+ case "restore":
150
+ case "version_restore": {
151
+ const versionId = args.version;
152
+ if (!versionId) {
153
+ return { error: "version required for restore mode" };
154
+ }
155
+ const restoreData = engine.getRestoreData(persona.id, versionId);
156
+ if (!restoreData.success || !restoreData.restore_payload) {
157
+ return { error: restoreData.error ?? "Failed to get restore data" };
158
+ }
159
+ // Create a version snapshot before restoring (audit trail)
160
+ const fullPersona = await client.getPersonaById(persona.id);
161
+ if (fullPersona) {
162
+ engine.forceCreateVersion(fullPersona, {
163
+ environment: versionContext.environment,
164
+ tenant_id: versionContext.tenant_id,
165
+ message: `Before restore to ${restoreData.version?.version_name}`,
166
+ created_by: "mcp-toolkit",
167
+ });
168
+ }
169
+ // Apply the restore
170
+ const payload = restoreData.restore_payload;
171
+ await client.updateAiEmployee({
172
+ persona_id: payload.persona_id,
173
+ name: payload.name,
174
+ description: payload.description,
175
+ proto_config: payload.proto_config,
176
+ workflow: payload.workflow ?? undefined,
177
+ welcome_messages: payload.welcome_messages ?? undefined,
178
+ embedding_enabled: payload.embedding_enabled ?? undefined,
179
+ });
180
+ // Create post-restore version
181
+ const restoredPersona = await client.getPersonaById(persona.id);
182
+ if (restoredPersona) {
183
+ engine.forceCreateVersion(restoredPersona, {
184
+ environment: versionContext.environment,
185
+ tenant_id: versionContext.tenant_id,
186
+ message: `Restored to ${restoreData.version?.version_name}`,
187
+ created_by: "mcp-toolkit",
188
+ });
189
+ }
190
+ return {
191
+ success: true,
192
+ persona_id: persona.id,
193
+ restored_to: {
194
+ version_id: restoreData.version?.id,
195
+ version_name: restoreData.version?.version_name,
196
+ version_number: restoreData.version?.version_number,
197
+ },
198
+ message: `Persona restored to ${restoreData.version?.version_name}`,
199
+ };
200
+ }
201
+ // ─────────────────────────────────────────────────────────────────────────
202
+ // version_policy - Get or update version policy
203
+ // ─────────────────────────────────────────────────────────────────────────
204
+ case "version_policy": {
205
+ // Check if we're updating or just getting
206
+ const hasUpdates = args.auto_on_deploy !== undefined ||
207
+ args.auto_on_sync !== undefined ||
208
+ args.max_versions !== undefined;
209
+ if (hasUpdates) {
210
+ const updated = engine.updatePolicy(persona.id, {
211
+ auto_version_on_deploy: args.auto_on_deploy,
212
+ auto_version_on_sync: args.auto_on_sync,
213
+ max_versions: args.max_versions,
214
+ });
215
+ return {
216
+ success: true,
217
+ persona_id: persona.id,
218
+ persona_name: persona.name,
219
+ policy: updated,
220
+ message: "Policy updated",
221
+ };
222
+ }
223
+ // Just get current policy
224
+ const policy = engine.getPolicy(persona.id);
225
+ return {
226
+ persona_id: persona.id,
227
+ persona_name: persona.name,
228
+ policy,
229
+ };
230
+ }
231
+ default:
232
+ return { error: `Unknown version mode: ${mode}` };
233
+ }
234
+ }
@@ -276,13 +276,113 @@ export async function handleReference(args, context) {
276
276
  const result = validateWorkflowPrompt(args.validate_prompt);
277
277
  return result;
278
278
  }
279
+ // ─────────────────────────────────────────────────────────────────────────
280
+ // type="demo_kits" or demo_kits=true - List demo kits
281
+ // ─────────────────────────────────────────────────────────────────────────
282
+ if (type === "demo_kits" || args.demo_kits) {
283
+ return {
284
+ demo_kits: [
285
+ {
286
+ id: "finance-ap",
287
+ name: "Finance - Accounts Payable Automation",
288
+ domain: "finance",
289
+ personas: 2,
290
+ tags: ["finance", "invoice", "automation", "coupa"],
291
+ description: "End-to-end AP invoice processing and generation",
292
+ },
293
+ {
294
+ id: "sales-sdr",
295
+ name: "Sales Development",
296
+ domain: "sales",
297
+ personas: 2,
298
+ tags: ["sales", "outbound", "prospecting"],
299
+ description: "SDR workflow automation for prospecting and outreach",
300
+ },
301
+ {
302
+ id: "support-tier1",
303
+ name: "Customer Support T1",
304
+ domain: "support",
305
+ personas: 2,
306
+ tags: ["support", "voice", "chat"],
307
+ description: "Tier 1 customer support automation",
308
+ },
309
+ ],
310
+ count: 3,
311
+ _tip: "Use reference(demo_kit=\"finance-ap\") to get full kit details",
312
+ };
313
+ }
314
+ // ─────────────────────────────────────────────────────────────────────────
315
+ // demo_kit="{id}" - Get specific demo kit
316
+ // ─────────────────────────────────────────────────────────────────────────
317
+ if (args.demo_kit) {
318
+ const kitId = args.demo_kit;
319
+ // Return finance-ap kit (the one we created)
320
+ if (kitId === "finance-ap") {
321
+ return {
322
+ id: "finance-ap",
323
+ name: "Finance - Accounts Payable Automation",
324
+ version: "1.0.0",
325
+ domain: "finance",
326
+ tags: ["finance", "invoice", "automation", "ap-automation", "ar-automation", "coupa", "usage-billing"],
327
+ personas: [
328
+ {
329
+ id: "fa91cb51-58fe-4255-b2d2-0b3e3a72cf7e",
330
+ name: "AP Invoice Processing",
331
+ role: "primary",
332
+ description: "Processes inbound invoices: extract, validate, create in Coupa, notify supplier",
333
+ workflow_nodes: 5,
334
+ },
335
+ {
336
+ id: "d73c8dca-d340-4541-a7ff-ae69f2810906",
337
+ name: "AP Invoice Generation",
338
+ role: "primary",
339
+ description: "Generates outbound invoices: validate customer/usage/contract, generate document",
340
+ workflow_nodes: 12,
341
+ },
342
+ ],
343
+ target_audience: {
344
+ primary: "AP Manager / Controller",
345
+ secondary: ["CFO", "RevOps Manager", "IT Director"],
346
+ company_size: "mid-market to enterprise",
347
+ industries: ["manufacturing", "retail", "saas", "distribution"],
348
+ },
349
+ roi_summary: {
350
+ ap_processing_savings: "$105K/month at 10K invoices",
351
+ invoice_generation_savings: "$73K/month at 500 invoices",
352
+ revenue_protection: "$450K/month at $10M billings",
353
+ },
354
+ demo_script: "docs/demos/ap-invoice-processing.md",
355
+ _tip: "Use persona(method=\"analyze\", id=\"...\") to analyze each persona's workflow",
356
+ };
357
+ }
358
+ return { error: `Demo kit not found: ${kitId}`, available: ["finance-ap", "sales-sdr", "support-tier1"] };
359
+ }
360
+ // ─────────────────────────────────────────────────────────────────────────
361
+ // tags=true - Get tagging taxonomy
362
+ // ─────────────────────────────────────────────────────────────────────────
363
+ if (args.tags) {
364
+ return {
365
+ categories: {
366
+ domain: ["finance", "sales", "support", "hr", "it", "legal", "marketing", "operations", "procurement"],
367
+ capability: ["voice", "chat", "dashboard", "document", "automation", "analysis", "extraction", "validation"],
368
+ use_case: ["invoice", "billing", "onboarding", "compliance", "reporting", "routing", "scheduling", "order", "claims"],
369
+ integration: ["coupa", "salesforce", "servicenow", "workday", "sap", "oracle", "netsuite", "hubspot", "zendesk", "jira"],
370
+ complexity: ["simple", "moderate", "complex"],
371
+ bundle: ["demo-kit", "bundle", "template", "example"],
372
+ },
373
+ _tip: "Use tags to filter personas and demo kits. Tags help organize and discover related content.",
374
+ };
375
+ }
279
376
  return {
280
- error: "Specify type (envs|actions|templates|patterns|widgets|concepts), or use concept, guidance, questions, mistakes, checklist, execution, check_types, validate_prompt",
377
+ error: "Specify type (envs|actions|templates|patterns|widgets|concepts|demo_kits), or use concept, guidance, questions, mistakes, checklist, execution, check_types, validate_prompt, demo_kit, tags",
281
378
  examples: [
282
379
  'reference(type="envs")',
283
380
  'reference(type="actions")',
284
381
  'reference(type="actions", id="send_email")',
285
382
  'reference(type="patterns")',
383
+ 'reference(type="demo_kits")',
384
+ 'reference(demo_kit="finance-ap")',
385
+ 'reference(tags=true)',
286
386
  'reference(concept="HITL")',
287
387
  'reference(guidance="categorizer-routing")',
288
388
  'reference(questions=true)',
@@ -4,28 +4,13 @@
4
4
  * Handles cross-environment synchronization of personas.
5
5
  */
6
6
  import { resolvePersona } from "../utils.js";
7
- // Deprecated param mappings for backwards compatibility
8
- const DEPRECATED_PARAMS = {
9
- identifier: { newName: "id", message: "'identifier' is deprecated, use 'id' instead (will be removed in v2.0.0)" },
10
- };
11
- function checkDeprecatedParams(args) {
12
- const warnings = [];
13
- for (const [oldName, info] of Object.entries(DEPRECATED_PARAMS)) {
14
- if (args[oldName] !== undefined) {
15
- warnings.push(info.message);
16
- }
17
- }
18
- return warnings;
19
- }
7
+ import { handleDeprecatedParams } from "../deprecation.js";
20
8
  /**
21
9
  * Handle sync tool requests - sync personas across environments
22
10
  */
23
11
  export async function handleSync(args, createClient, getSyncOptions) {
24
- // Check for deprecated params and log warnings
25
- const deprecationWarnings = checkDeprecatedParams(args);
26
- for (const warning of deprecationWarnings) {
27
- console.warn(`[sync] Deprecation: ${warning}`);
28
- }
12
+ // Check for deprecated params
13
+ handleDeprecatedParams(args, "sync");
29
14
  const mode = args.mode || "run";
30
15
  const id = args.id;
31
16
  const identifier = args.identifier; // deprecated alias for 'id'
@@ -2,10 +2,15 @@
2
2
  * Template Handler
3
3
  *
4
4
  * Provides workflow patterns, widget references, config templates, and qualifying questions.
5
+ *
6
+ * Config templates follow API-first pattern:
7
+ * 1. Try to get from API (getPersonaTemplates)
8
+ * 2. Fall back to generated templates from proto definitions
5
9
  */
6
- import { WORKFLOW_PATTERNS, QUALIFYING_QUESTIONS, VOICE_PERSONA_TEMPLATE, getWidgetsForPersonaType, getQualifyingQuestionsByCategory, getRequiredQualifyingQuestions, } from "../../../sdk/knowledge.js";
10
+ import { WORKFLOW_PATTERNS, QUALIFYING_QUESTIONS, getWidgetsForPersonaType, getQualifyingQuestionsByCategory, getRequiredQualifyingQuestions, } from "../../../sdk/knowledge.js";
11
+ import { getTemplateFallback, getTemplateFieldDocs } from "../../../sdk/generated/template-fallbacks.js";
7
12
  /**
8
- * Handle template tool requests
13
+ * Handle template tool requests (sync operations only)
9
14
  */
10
15
  export function handleTemplate(args) {
11
16
  // Get specific pattern
@@ -38,14 +43,6 @@ export function handleTemplate(args) {
38
43
  const widgets = getWidgetsForPersonaType(args.widgets);
39
44
  return { type: args.widgets, widgets };
40
45
  }
41
- // Config template
42
- if (args.config) {
43
- if (args.config === "voice") {
44
- return VOICE_PERSONA_TEMPLATE;
45
- }
46
- // Add chat/dashboard templates here
47
- return { type: args.config, template: {} };
48
- }
49
46
  // Qualifying questions
50
47
  if (args.questions) {
51
48
  let questions = QUALIFYING_QUESTIONS;
@@ -57,5 +54,73 @@ export function handleTemplate(args) {
57
54
  }
58
55
  return { questions, count: questions.length };
59
56
  }
57
+ // Config template - use async handler for API-first approach
58
+ if (args.config) {
59
+ // For sync handler, return fallback with note to use async version
60
+ const type = args.config;
61
+ const fallback = getTemplateFallback(type);
62
+ const docs = getTemplateFieldDocs(type);
63
+ if (fallback) {
64
+ return {
65
+ type,
66
+ template: fallback,
67
+ field_docs: docs,
68
+ _source: "fallback",
69
+ _note: "This is a generated fallback. For live templates, use handleTemplateAsync with client.",
70
+ };
71
+ }
72
+ return { error: `No template available for type: ${args.config}` };
73
+ }
60
74
  return { error: "Specify pattern, patterns=true, widgets, config, or questions=true" };
61
75
  }
76
+ /**
77
+ * Handle template tool requests with API access (async).
78
+ * Uses API-first approach with generated fallback.
79
+ */
80
+ export async function handleTemplateAsync(args, client) {
81
+ // Config template - API first with fallback
82
+ if (args.config) {
83
+ const type = args.config;
84
+ // Map type to API trigger_type
85
+ const triggerTypeMap = {
86
+ voice: "VOICEBOT_AI_EMPLOYEE",
87
+ chat: "CHATBOT",
88
+ dashboard: "DOCUMENT_TRIGGER",
89
+ };
90
+ const targetTriggerType = triggerTypeMap[type];
91
+ try {
92
+ // Try API first
93
+ const templates = await client.getPersonaTemplates();
94
+ const template = templates.find(t => t.trigger_type === targetTriggerType ||
95
+ t.name?.toLowerCase().includes(type));
96
+ if (template?.proto_config) {
97
+ return {
98
+ type,
99
+ template: template.proto_config,
100
+ template_id: template.id,
101
+ template_name: template.name,
102
+ _source: "api",
103
+ };
104
+ }
105
+ }
106
+ catch (err) {
107
+ // API failed, fall through to fallback
108
+ console.warn(`Failed to fetch templates from API: ${err}`);
109
+ }
110
+ // Fall back to generated template
111
+ const fallback = getTemplateFallback(type);
112
+ const docs = getTemplateFieldDocs(type);
113
+ if (fallback) {
114
+ return {
115
+ type,
116
+ template: fallback,
117
+ field_docs: docs,
118
+ _source: "fallback",
119
+ _note: "API templates unavailable, using generated fallback from proto definitions.",
120
+ };
121
+ }
122
+ return { error: `No template available for type: ${args.config}` };
123
+ }
124
+ // For non-config operations, use sync handler
125
+ return handleTemplate(args);
126
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Workflow Analyze Handler
3
+ *
4
+ * Analyzes workflow structure, detects issues, and provides metrics.
5
+ *
6
+ * Features:
7
+ * - Issue detection (critical, warning, info)
8
+ * - Connection validation
9
+ * - Metrics calculation
10
+ * - Execution flow analysis (loops, dead code, multiple responders)
11
+ * - Auto-fix support
12
+ */
13
+ import { detectWorkflowIssues, suggestWorkflowFixes, validateWorkflowConnections } from "../../../sdk/knowledge.js";
14
+ import { autoFixWorkflow } from "../../../sdk/workflow-fixer.js";
15
+ import { analyzeExecutionFlow, generateASCIIFlow } from "../../../sdk/workflow-execution-analyzer.js";
16
+ /**
17
+ * Calculate workflow metrics
18
+ */
19
+ function calculateMetrics(workflow) {
20
+ const actions = workflow.actions;
21
+ if (!actions)
22
+ return { node_count: 0, connection_count: 0 };
23
+ // Count connections
24
+ let connectionCount = 0;
25
+ for (const action of actions) {
26
+ const inputs = action.inputs;
27
+ if (inputs) {
28
+ connectionCount += Object.keys(inputs).length;
29
+ }
30
+ }
31
+ // Check for HITL
32
+ const hasHitl = actions.some(a => {
33
+ const name = a.name?.toLowerCase() ?? "";
34
+ return name.includes("hitl") || name.includes("human");
35
+ });
36
+ // Get trigger type
37
+ const trigger = workflow.trigger;
38
+ const triggerType = trigger?.trigger_type;
39
+ return {
40
+ node_count: actions.length,
41
+ connection_count: connectionCount,
42
+ has_hitl: hasHitl,
43
+ trigger_type: triggerType,
44
+ };
45
+ }
46
+ /**
47
+ * Handle workflow analyze mode
48
+ */
49
+ export async function handleWorkflowAnalyze(args, client) {
50
+ const personaId = args.persona_id;
51
+ let workflow = args.workflow_def;
52
+ let persona = null;
53
+ // Get workflow from persona if not provided
54
+ if (personaId && !workflow) {
55
+ const fetchedPersona = await client.getPersonaById(personaId);
56
+ if (!fetchedPersona) {
57
+ return { error: `Persona not found: ${personaId}` };
58
+ }
59
+ persona = fetchedPersona;
60
+ workflow = fetchedPersona.workflow_def;
61
+ }
62
+ if (!workflow) {
63
+ return { error: "No workflow to analyze. Provide workflow_def or persona_id." };
64
+ }
65
+ // Determine what to include
66
+ const include = args.include || ["issues", "connections", "fixes", "metrics"];
67
+ const result = {
68
+ mode: "analyze",
69
+ persona_id: personaId,
70
+ persona_name: persona?.name,
71
+ environment: "demo",
72
+ };
73
+ // Issues and fixes
74
+ if (include.includes("issues") || include.includes("fixes")) {
75
+ const issues = detectWorkflowIssues(workflow);
76
+ if (include.includes("issues")) {
77
+ result.issues = issues;
78
+ }
79
+ if (include.includes("fixes")) {
80
+ result.fixes = suggestWorkflowFixes(issues);
81
+ }
82
+ result.issue_summary = {
83
+ total: issues.length,
84
+ critical: issues.filter((i) => i.severity === "critical").length,
85
+ warning: issues.filter((i) => i.severity === "warning").length,
86
+ info: issues.filter((i) => i.severity === "info").length,
87
+ };
88
+ result.validation_passed = issues.filter((i) => i.severity === "critical").length === 0;
89
+ }
90
+ // Connection validation
91
+ if (include.includes("connections")) {
92
+ result.connections = validateWorkflowConnections(workflow);
93
+ }
94
+ // Metrics
95
+ if (include.includes("metrics")) {
96
+ result.metrics = calculateMetrics(workflow);
97
+ }
98
+ // Execution flow analysis
99
+ if (include.includes("execution_flow")) {
100
+ const execAnalysis = analyzeExecutionFlow(workflow);
101
+ result.execution_flow = {
102
+ summary: execAnalysis.summary,
103
+ loops: execAnalysis.loops,
104
+ multiple_responder_issues: execAnalysis.multipleResponderIssues,
105
+ redundant_classifiers: execAnalysis.redundantClassifiers,
106
+ data_flow_issues: execAnalysis.dataFlowIssues,
107
+ dead_code_paths: execAnalysis.deadCodePaths,
108
+ };
109
+ // Include ASCII visualization if requested
110
+ if (args.visualize) {
111
+ result.execution_flow_ascii = generateASCIIFlow(execAnalysis);
112
+ }
113
+ // Add specific warnings for triple response risk
114
+ if (execAnalysis.summary.mayRepeatResponses) {
115
+ result.triple_response_warning = "⚠️ This workflow may cause duplicate/triple responses due to ungated parallel responders";
116
+ }
117
+ }
118
+ if (persona) {
119
+ result.persona = {
120
+ id: persona.id,
121
+ name: persona.name
122
+ };
123
+ }
124
+ // Calculate optimization guidance
125
+ const issues = result.issues;
126
+ const criticalCount = issues?.filter(i => i.severity === "critical").length ?? 0;
127
+ const warningCount = issues?.filter(i => i.severity === "warning").length ?? 0;
128
+ // AUTO-FIX: If fix=true, apply fixes and deploy
129
+ if (args.fix && personaId && persona && workflow) {
130
+ const fixResult = autoFixWorkflow(workflow);
131
+ result.fixes_applied = fixResult.fixesApplied;
132
+ result.fixes_warnings = fixResult.warnings;
133
+ if (fixResult.fixesApplied.length > 0 && fixResult.success) {
134
+ // Deploy the fixed workflow
135
+ try {
136
+ const fixedWorkflow = fixResult.workflowDef;
137
+ await client.updateAiEmployee({
138
+ persona_id: personaId,
139
+ workflow: fixedWorkflow,
140
+ proto_config: persona.proto_config,
141
+ });
142
+ result.fix_status = "deployed";
143
+ result.fixes_deployed = fixResult.fixesApplied.length;
144
+ result.status = `✅ Applied ${fixResult.fixesApplied.length} fixes and deployed`;
145
+ }
146
+ catch (deployErr) {
147
+ result.fix_status = "failed";
148
+ result.fix_error = deployErr instanceof Error ? deployErr.message : String(deployErr);
149
+ result.status = `⚠️ Fixes applied but deploy failed: ${result.fix_error}`;
150
+ }
151
+ }
152
+ else if (fixResult.fixesApplied.length === 0) {
153
+ result.fix_status = "no_fixes_needed";
154
+ result.status = "✅ No auto-fixable issues found";
155
+ }
156
+ else {
157
+ result.fix_status = "failed";
158
+ result.status = "⚠️ Fix generation failed";
159
+ }
160
+ return result;
161
+ }
162
+ // Provide optimization suggestion
163
+ if (criticalCount > 0 || warningCount > 0) {
164
+ result.optimization_suggestion = `This workflow has ${criticalCount} critical and ${warningCount} warning issues. ` +
165
+ `Use workflow(persona_id="${personaId ?? 'ID'}", optimize=true) to auto-fix.`;
166
+ }
167
+ else {
168
+ result.status = "✅ Workflow is healthy - no issues detected";
169
+ }
170
+ return result;
171
+ }