@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,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
|
+
}
|
|
@@ -4,28 +4,13 @@
|
|
|
4
4
|
* Handles cross-environment synchronization of personas.
|
|
5
5
|
*/
|
|
6
6
|
import { resolvePersona } from "../utils.js";
|
|
7
|
-
|
|
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
|
|
25
|
-
|
|
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,
|
|
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
|
+
}
|
|
@@ -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
|
+
}
|