@ema.co/mcp-toolkit 2026.1.26 → 2026.1.27-1
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/dist/mcp/handlers/action/index.js +17 -20
- package/dist/mcp/handlers/data/index.js +72 -6
- package/dist/mcp/handlers/deprecation.js +50 -0
- package/dist/mcp/handlers/env/index.js +3 -3
- 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 +9 -10
- package/dist/mcp/handlers/persona/update.js +4 -2
- package/dist/mcp/handlers/reference/index.js +15 -2
- package/dist/mcp/handlers/sync/index.js +3 -18
- package/dist/mcp/handlers/workflow/analyze.js +53 -105
- package/dist/mcp/handlers/workflow/deploy.js +129 -0
- package/dist/mcp/handlers/workflow/generate.js +8 -28
- package/dist/mcp/handlers/workflow/index.js +258 -85
- package/dist/mcp/handlers/workflow/modify.js +9 -29
- package/dist/mcp/handlers/workflow/optimize.js +22 -108
- package/dist/mcp/handlers/workflow/utils.js +0 -102
- package/dist/mcp/handlers-consolidated.js +15 -38
- package/dist/mcp/prompts.js +82 -44
- package/dist/mcp/resources.js +335 -3
- package/dist/mcp/server.js +242 -457
- package/dist/mcp/tools.js +44 -61
- package/dist/sdk/action-schema-parser.js +11 -5
- package/dist/sdk/client.js +46 -17
- package/dist/sdk/ema-client.js +11 -0
- package/dist/sdk/generated/deprecated-actions.js +171 -0
- package/dist/sdk/guidance.js +58 -35
- package/dist/sdk/index.js +8 -7
- package/dist/sdk/knowledge.js +216 -1932
- package/dist/sdk/quality-gates.js +60 -336
- package/dist/sdk/validation-rules.js +33 -0
- package/dist/sdk/workflow-fixer.js +29 -360
- package/dist/sdk/workflow-intent.js +43 -3
- package/dist/sdk/workflow-transformer.js +0 -342
- package/docs/dashboard-operations.md +35 -0
- package/docs/ema-user-guide.md +66 -0
- package/docs/mcp-tools-guide.md +74 -45
- package/package.json +2 -2
- package/dist/mcp/handlers/persona/analyze.js +0 -275
- package/dist/mcp/handlers/persona/compare.js +0 -32
- package/dist/mcp/handlers/workflow/compile.js +0 -39
- package/docs/DEBUG-ANALYSIS-unused-category-type-mismatch.md +0 -481
- package/docs/TODO-fix-analyzer-and-modify.md +0 -182
- package/resources/action-schema.json +0 -5678
|
@@ -4,35 +4,30 @@
|
|
|
4
4
|
* Provides action/agent lookup, filtering, and documentation.
|
|
5
5
|
*/
|
|
6
6
|
import { AGENT_CATALOG, getAgentByName, suggestAgentsForUseCase, } from "../../../sdk/knowledge.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 action tool requests - list, get, filter, and search actions
|
|
22
10
|
*/
|
|
23
11
|
export async function handleAction(args, client) {
|
|
24
|
-
// Check for deprecated params
|
|
25
|
-
|
|
26
|
-
for (const warning of deprecationWarnings) {
|
|
27
|
-
console.warn(`[action] Deprecation: ${warning}`);
|
|
28
|
-
}
|
|
12
|
+
// Check for deprecated params
|
|
13
|
+
handleDeprecatedParams(args, "action");
|
|
29
14
|
const id = args.id;
|
|
30
15
|
const identifier = args.identifier; // deprecated alias for 'id'
|
|
31
16
|
const idOrName = id ?? identifier;
|
|
32
|
-
// Categories list
|
|
17
|
+
// Categories list - API-first with catalog fallback
|
|
33
18
|
if (args.categories) {
|
|
19
|
+
try {
|
|
20
|
+
const actions = await client.listActions();
|
|
21
|
+
const apiCategories = [...new Set(actions.map(a => a.category).filter(Boolean))];
|
|
22
|
+
if (apiCategories.length > 0) {
|
|
23
|
+
return { categories: apiCategories, count: apiCategories.length, source: "api" };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Fallback to catalog
|
|
28
|
+
}
|
|
34
29
|
const categories = [...new Set(Object.values(AGENT_CATALOG).map(a => a.category))];
|
|
35
|
-
return { categories, count: categories.length };
|
|
30
|
+
return { categories, count: categories.length, source: "catalog" };
|
|
36
31
|
}
|
|
37
32
|
// Suggest for use case
|
|
38
33
|
if (args.suggest) {
|
|
@@ -112,6 +107,7 @@ export async function handleAction(args, client) {
|
|
|
112
107
|
...a,
|
|
113
108
|
documentation: a.name ? getAgentByName(a.name) : undefined,
|
|
114
109
|
})),
|
|
110
|
+
source: "api",
|
|
115
111
|
};
|
|
116
112
|
}
|
|
117
113
|
return {
|
|
@@ -122,6 +118,7 @@ export async function handleAction(args, client) {
|
|
|
122
118
|
category: a.category,
|
|
123
119
|
enabled: a.enabled,
|
|
124
120
|
})),
|
|
121
|
+
source: "api",
|
|
125
122
|
};
|
|
126
123
|
}
|
|
127
124
|
catch (e) {
|
|
@@ -138,6 +138,8 @@ export async function handleData(args, client, readFile) {
|
|
|
138
138
|
// Upload file or LLM-generated content (dashboard rows)
|
|
139
139
|
const filePath = (dataArgs?.path ?? dataArgs?.file ?? args.file);
|
|
140
140
|
const items = dataArgs?.items;
|
|
141
|
+
// Widget name for targeting specific upload widgets (especially for Document Generation personas)
|
|
142
|
+
const widgetName = (dataArgs?.widget_name ?? args.widget_name);
|
|
141
143
|
if (filePath) {
|
|
142
144
|
// File upload - use provided readFile or fall back to fs
|
|
143
145
|
try {
|
|
@@ -151,11 +153,14 @@ export async function handleData(args, client, readFile) {
|
|
|
151
153
|
}
|
|
152
154
|
const path = await import("path");
|
|
153
155
|
const filename = path.basename(filePath);
|
|
154
|
-
const result = await client.uploadDataSource(personaId, fileContent, filename
|
|
156
|
+
const result = await client.uploadDataSource(personaId, fileContent, filename, {
|
|
157
|
+
widgetName: widgetName, // Pass through widget_name for doc gen personas
|
|
158
|
+
});
|
|
155
159
|
return {
|
|
156
160
|
method: "upload",
|
|
157
161
|
persona_id: personaId,
|
|
158
162
|
path: filePath,
|
|
163
|
+
widget_name: widgetName ?? "fileUpload",
|
|
159
164
|
...result,
|
|
160
165
|
};
|
|
161
166
|
}
|
|
@@ -164,14 +169,74 @@ export async function handleData(args, client, readFile) {
|
|
|
164
169
|
}
|
|
165
170
|
}
|
|
166
171
|
else if (items && items.length > 0) {
|
|
167
|
-
// Dashboard row upload (LLM-generated content)
|
|
172
|
+
// Dashboard row upload (LLM-generated content or file attachments)
|
|
168
173
|
try {
|
|
169
174
|
const results = [];
|
|
175
|
+
const path = await import("path");
|
|
176
|
+
const fs = await import("fs/promises");
|
|
170
177
|
for (const item of items) {
|
|
171
|
-
// Convert item to DashboardInput format
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
178
|
+
// Convert item to DashboardInput format
|
|
179
|
+
// Supports: string_value, number_value, boolean_value, document_value
|
|
180
|
+
const inputs = await Promise.all(Object.entries(item).map(async ([key, value]) => {
|
|
181
|
+
// File attachment: { file: "/path/to/file.pdf" } or { file_path: "..." }
|
|
182
|
+
if (typeof value === "object" && value !== null && ("file" in value || "file_path" in value)) {
|
|
183
|
+
const filePath = value.file ?? value.file_path;
|
|
184
|
+
if (typeof filePath === "string") {
|
|
185
|
+
let fileContent;
|
|
186
|
+
if (readFile) {
|
|
187
|
+
fileContent = await readFile(filePath);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
fileContent = await fs.readFile(filePath);
|
|
191
|
+
}
|
|
192
|
+
const filename = path.basename(filePath);
|
|
193
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
194
|
+
const mimeTypes = {
|
|
195
|
+
".pdf": "application/pdf",
|
|
196
|
+
".json": "application/json",
|
|
197
|
+
".txt": "text/plain",
|
|
198
|
+
".csv": "text/csv",
|
|
199
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
200
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
201
|
+
};
|
|
202
|
+
return {
|
|
203
|
+
name: key,
|
|
204
|
+
document_value: [{
|
|
205
|
+
name: filename,
|
|
206
|
+
contents: fileContent.toString("base64"),
|
|
207
|
+
is_base64_encoded: true,
|
|
208
|
+
mime_type: mimeTypes[ext] ?? "application/octet-stream",
|
|
209
|
+
}],
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Inline document: { contents: "...", mime_type: "..." }
|
|
214
|
+
if (typeof value === "object" && value !== null && "contents" in value) {
|
|
215
|
+
const doc = value;
|
|
216
|
+
return {
|
|
217
|
+
name: key,
|
|
218
|
+
document_value: [{
|
|
219
|
+
name: doc.name ?? `${key}.txt`,
|
|
220
|
+
contents: typeof doc.contents === "string" && doc.is_base64_encoded
|
|
221
|
+
? doc.contents
|
|
222
|
+
: Buffer.from(String(doc.contents)).toString("base64"),
|
|
223
|
+
is_base64_encoded: true,
|
|
224
|
+
mime_type: doc.mime_type ?? "text/plain",
|
|
225
|
+
}],
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
// Primitive values
|
|
229
|
+
if (typeof value === "number") {
|
|
230
|
+
return { name: key, number_value: value };
|
|
231
|
+
}
|
|
232
|
+
if (typeof value === "boolean") {
|
|
233
|
+
return { name: key, boolean_value: value };
|
|
234
|
+
}
|
|
235
|
+
// Default: string value
|
|
236
|
+
return {
|
|
237
|
+
name: key,
|
|
238
|
+
string_value: typeof value === "string" ? value : JSON.stringify(value),
|
|
239
|
+
};
|
|
175
240
|
}));
|
|
176
241
|
const result = await client.uploadAndRunDashboardRow(personaId, inputs);
|
|
177
242
|
results.push(result);
|
|
@@ -181,6 +246,7 @@ export async function handleData(args, client, readFile) {
|
|
|
181
246
|
persona_id: personaId,
|
|
182
247
|
uploaded_rows: results.length,
|
|
183
248
|
row_ids: results.map(r => r.row_id),
|
|
249
|
+
_tip: "Dashboard rows created with file attachments trigger workflow execution automatically",
|
|
184
250
|
};
|
|
185
251
|
}
|
|
186
252
|
catch (error) {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deprecation Tracking - Single Source of Truth
|
|
3
|
+
*
|
|
4
|
+
* Centralized deprecation warnings for parameter names.
|
|
5
|
+
* Used by all handlers to ensure consistent messaging.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Deprecated parameter mappings
|
|
9
|
+
*
|
|
10
|
+
* Key: old parameter name
|
|
11
|
+
* Value: { newName, removeVersion }
|
|
12
|
+
*/
|
|
13
|
+
export const DEPRECATED_PARAMS = {
|
|
14
|
+
identifier: { newName: "id", removeVersion: "2.0.0" },
|
|
15
|
+
clone_from: { newName: "from", removeVersion: "2.0.0" },
|
|
16
|
+
template_id: { newName: "from", removeVersion: "2.0.0" },
|
|
17
|
+
clone_data: { newName: "include_data", removeVersion: "2.0.0" },
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Check args for deprecated params and return warnings.
|
|
21
|
+
*/
|
|
22
|
+
export function checkDeprecatedParams(args) {
|
|
23
|
+
const warnings = [];
|
|
24
|
+
for (const [oldName, { newName, removeVersion }] of Object.entries(DEPRECATED_PARAMS)) {
|
|
25
|
+
if (args[oldName] !== undefined) {
|
|
26
|
+
warnings.push(`'${oldName}' is deprecated, use '${newName}' instead (will be removed in v${removeVersion})`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return warnings;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Add deprecation warnings to a result object.
|
|
33
|
+
*/
|
|
34
|
+
export function addDeprecationWarnings(result, warnings) {
|
|
35
|
+
if (warnings.length > 0) {
|
|
36
|
+
return { ...result, _deprecation_warnings: warnings };
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Log and return deprecation warnings for an args object.
|
|
42
|
+
* Call this at the start of handlers that accept deprecated params.
|
|
43
|
+
*/
|
|
44
|
+
export function handleDeprecatedParams(args, context) {
|
|
45
|
+
const warnings = checkDeprecatedParams(args);
|
|
46
|
+
for (const warning of warnings) {
|
|
47
|
+
console.warn(`[${context}] Deprecation: ${warning}`);
|
|
48
|
+
}
|
|
49
|
+
return warnings;
|
|
50
|
+
}
|
|
@@ -25,9 +25,9 @@ export async function handleEnv(_args, getEnvironments, toolkit) {
|
|
|
25
25
|
getting_started: {
|
|
26
26
|
workflow: [
|
|
27
27
|
"1. List personas: persona(method=\"list\")",
|
|
28
|
-
"2. Get
|
|
29
|
-
"3. Analyze
|
|
30
|
-
"4. Preview before deploying:
|
|
28
|
+
"2. Get workflow data: workflow(mode=\"get\", persona_id=\"...\")",
|
|
29
|
+
"3. Analyze workflow: Fetch ema://rules/anti-patterns and check workflow_def against rules",
|
|
30
|
+
"4. Preview before deploying: workflow(mode=\"deploy\", persona_id=\"...\", workflow_def={...}, preview=true)",
|
|
31
31
|
],
|
|
32
32
|
critical_rules: criticalRules,
|
|
33
33
|
resources: [
|
|
@@ -1,247 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Knowledge Handler
|
|
2
|
+
* Knowledge Handler - DEPRECATED
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* This handler is deprecated. Use the 'data' tool instead.
|
|
5
|
+
* All calls are routed to handleData with a deprecation warning.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { handleData } from "../data/index.js";
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Mode mapping from knowledge tool to data tool
|
|
10
|
+
*/
|
|
11
|
+
const MODE_MAPPING = {
|
|
12
|
+
list: "list",
|
|
13
|
+
aggregates: "stats",
|
|
14
|
+
upload: "upload",
|
|
15
|
+
delete: "delete",
|
|
16
|
+
status: "embedding",
|
|
17
|
+
toggle: "embedding",
|
|
18
|
+
attach: "attach",
|
|
19
|
+
dashboard_rows: "dashboard_rows",
|
|
20
|
+
dashboard_clone: "copy",
|
|
21
|
+
dashboard_generate: "generate",
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Handle knowledge tool requests
|
|
10
25
|
*
|
|
11
|
-
* @deprecated Use data() tool instead
|
|
26
|
+
* @deprecated Use data() tool instead. This handler routes to handleData.
|
|
12
27
|
*/
|
|
13
|
-
export async function handleKnowledge(args, client, readFile
|
|
14
|
-
|
|
28
|
+
export async function handleKnowledge(args, client, readFile) {
|
|
29
|
+
console.warn("[knowledge] DEPRECATED: Use data() tool instead");
|
|
15
30
|
const mode = args.mode || "list";
|
|
16
|
-
const
|
|
17
|
-
//
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
migration: {
|
|
22
|
-
"knowledge(mode='list')": "data(mode='list')",
|
|
23
|
-
"knowledge(mode='upload')": "data(mode='upload')",
|
|
24
|
-
"knowledge(mode='delete')": "data(mode='delete')",
|
|
25
|
-
"knowledge(mode='status')": "data(mode='embedding')",
|
|
26
|
-
"knowledge(mode='toggle')": "data(mode='embedding', enabled=...)",
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
} : {};
|
|
30
|
-
// Helper to wrap result with deprecation warning
|
|
31
|
-
const withWarning = (result) => {
|
|
32
|
-
if (typeof result === 'object' && result !== null) {
|
|
33
|
-
return { ...deprecationWarning, ...result };
|
|
34
|
-
}
|
|
35
|
-
return result;
|
|
31
|
+
const mappedMode = MODE_MAPPING[mode] || mode;
|
|
32
|
+
// Route to data handler
|
|
33
|
+
const dataArgs = {
|
|
34
|
+
...args,
|
|
35
|
+
mode: mappedMode,
|
|
36
36
|
};
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const widgetName = args.widget_name;
|
|
53
|
-
const agg = await client.getDataSourceAggregates(personaId, widgetName);
|
|
54
|
-
return withWarning({
|
|
55
|
-
persona_id: personaId,
|
|
56
|
-
widget_name: agg.widgetName,
|
|
57
|
-
total_files: agg.total,
|
|
58
|
-
by_status: {
|
|
59
|
-
pending: agg.pending,
|
|
60
|
-
success: agg.success,
|
|
61
|
-
failed: agg.failed,
|
|
62
|
-
},
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
case "upload": {
|
|
66
|
-
const filePath = args.file;
|
|
67
|
-
if (!filePath) {
|
|
68
|
-
return withWarning({ error: "file path required for upload" });
|
|
69
|
-
}
|
|
70
|
-
const content = await readFile(filePath);
|
|
71
|
-
const filename = filePath.split("/").pop() || "file";
|
|
72
|
-
const result = await client.uploadDataSource(personaId, content, filename, { tags: args.tags });
|
|
73
|
-
return withWarning({ success: true, ...result });
|
|
74
|
-
}
|
|
75
|
-
case "delete": {
|
|
76
|
-
const fileId = args.file_id;
|
|
77
|
-
if (!fileId) {
|
|
78
|
-
return withWarning({ error: "file_id required for delete" });
|
|
79
|
-
}
|
|
80
|
-
const result = await client.deleteDataSource(personaId, fileId);
|
|
81
|
-
return withWarning(result);
|
|
82
|
-
}
|
|
83
|
-
case "status": {
|
|
84
|
-
const persona = await client.getPersonaById(personaId);
|
|
85
|
-
if (!persona) {
|
|
86
|
-
return withWarning({ error: `Persona not found: ${personaId}` });
|
|
87
|
-
}
|
|
88
|
-
return withWarning({
|
|
89
|
-
persona_id: personaId,
|
|
90
|
-
embedding_enabled: persona.embedding_enabled,
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
case "toggle": {
|
|
94
|
-
if (args.enabled === undefined) {
|
|
95
|
-
return withWarning({ error: "enabled flag required for toggle" });
|
|
96
|
-
}
|
|
97
|
-
const persona = await client.getPersonaById(personaId);
|
|
98
|
-
if (!persona) {
|
|
99
|
-
return withWarning({ error: `Persona not found: ${personaId}` });
|
|
100
|
-
}
|
|
101
|
-
// IMPORTANT: The Ema API requires workflow to be sent along with proto_config
|
|
102
|
-
// for proto_config changes to persist. This matches what the UI does.
|
|
103
|
-
await client.updateAiEmployee({
|
|
104
|
-
persona_id: personaId,
|
|
105
|
-
embedding_enabled: args.enabled,
|
|
106
|
-
proto_config: persona.proto_config,
|
|
107
|
-
workflow: persona.workflow_def,
|
|
108
|
-
});
|
|
109
|
-
return withWarning({
|
|
110
|
-
success: true,
|
|
111
|
-
persona_id: personaId,
|
|
112
|
-
embedding_enabled: args.enabled,
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
case "attach": {
|
|
116
|
-
// Attach a data source widget to a workflow node's datastore_configs input
|
|
117
|
-
const nodeName = args.node_name;
|
|
118
|
-
const widgetName = args.widget_name || "fileUpload";
|
|
119
|
-
if (!nodeName) {
|
|
120
|
-
return withWarning({ error: "node_name required (e.g., 'knowledge_search_1')" });
|
|
121
|
-
}
|
|
122
|
-
const persona = await client.getPersonaById(personaId);
|
|
123
|
-
if (!persona) {
|
|
124
|
-
return withWarning({ error: `Persona not found: ${personaId}` });
|
|
125
|
-
}
|
|
126
|
-
const workflowDef = persona.workflow_def;
|
|
127
|
-
if (!workflowDef) {
|
|
128
|
-
return withWarning({ error: "Persona has no workflow_def" });
|
|
129
|
-
}
|
|
130
|
-
const actions = workflowDef.actions;
|
|
131
|
-
if (!actions) {
|
|
132
|
-
return withWarning({ error: "Workflow has no actions" });
|
|
133
|
-
}
|
|
134
|
-
// Find the target node
|
|
135
|
-
const nodeIndex = actions.findIndex(a => a.name === nodeName);
|
|
136
|
-
if (nodeIndex < 0) {
|
|
137
|
-
const availableNodes = actions.map(a => a.name).filter(Boolean);
|
|
138
|
-
return withWarning({
|
|
139
|
-
error: `Node '${nodeName}' not found`,
|
|
140
|
-
available_nodes: availableNodes,
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
const node = actions[nodeIndex];
|
|
144
|
-
const inputs = (node.inputs || {});
|
|
145
|
-
// Build the datastore_configs binding with multiBinding
|
|
146
|
-
const datastoreBinding = {
|
|
147
|
-
multiBinding: {
|
|
148
|
-
elements: [
|
|
149
|
-
{
|
|
150
|
-
widgetConfig: { widgetName },
|
|
151
|
-
autoDetectedBinding: false,
|
|
152
|
-
},
|
|
153
|
-
],
|
|
154
|
-
},
|
|
155
|
-
autoDetectedBinding: false,
|
|
156
|
-
};
|
|
157
|
-
// Update the node's inputs
|
|
158
|
-
inputs.datastore_configs = datastoreBinding;
|
|
159
|
-
node.inputs = inputs;
|
|
160
|
-
actions[nodeIndex] = node;
|
|
161
|
-
workflowDef.actions = actions;
|
|
162
|
-
// Update the persona with the modified workflow
|
|
163
|
-
await client.updateAiEmployee({
|
|
164
|
-
persona_id: personaId,
|
|
165
|
-
proto_config: persona.proto_config,
|
|
166
|
-
workflow: workflowDef,
|
|
167
|
-
});
|
|
168
|
-
return withWarning({
|
|
169
|
-
success: true,
|
|
170
|
-
persona_id: personaId,
|
|
171
|
-
node_name: nodeName,
|
|
172
|
-
widget_name: widgetName,
|
|
173
|
-
message: `Data source '${widgetName}' attached to node '${nodeName}'`,
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
case "dashboard_rows": {
|
|
177
|
-
// Get rows from a dashboard persona
|
|
178
|
-
const persona = await client.getPersonaById(personaId);
|
|
179
|
-
if (!persona) {
|
|
180
|
-
return withWarning({ error: `Persona not found: ${personaId}` });
|
|
181
|
-
}
|
|
182
|
-
const dashboardId = persona.workflow_dashboard_id;
|
|
183
|
-
if (!dashboardId) {
|
|
184
|
-
return withWarning({
|
|
185
|
-
error: "This persona has no dashboard. Use dashboard_rows only for Dashboard-type AI Employees.",
|
|
186
|
-
hint: "Check that the persona has trigger_type=DASHBOARD",
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
const limit = args.limit ?? 100;
|
|
190
|
-
const result = await client.getDashboardRows(dashboardId, personaId, { limit });
|
|
191
|
-
// Return schema and rows in a structured format
|
|
192
|
-
return withWarning({
|
|
193
|
-
persona_id: personaId,
|
|
194
|
-
dashboard_id: dashboardId,
|
|
195
|
-
dashboard_name: result.dashboardName,
|
|
196
|
-
total_rows: result.totalCount,
|
|
197
|
-
schema: {
|
|
198
|
-
input_columns: result.schema.columns.filter(c => c.isInput),
|
|
199
|
-
output_columns: result.schema.columns.filter(c => !c.isInput),
|
|
200
|
-
},
|
|
201
|
-
rows: result.rows.map(row => ({
|
|
202
|
-
id: row.id,
|
|
203
|
-
state: row.state,
|
|
204
|
-
created_at: row.dashboardRowMetadata.createdAt,
|
|
205
|
-
input_values: row.columnValues
|
|
206
|
-
.filter(cv => result.schema.columns.find(c => c.columnId === cv.columnId)?.isInput)
|
|
207
|
-
.map(cv => ({
|
|
208
|
-
column: result.schema.columns.find(c => c.columnId === cv.columnId)?.name ?? cv.columnId,
|
|
209
|
-
value: cv.value.stringValue ?? cv.value.documentCellValue?.documentValues?.[0]?.name ?? cv.value.arrayValue?.arrayValues?.[0]?.stringValue ?? "(complex)",
|
|
210
|
-
})),
|
|
211
|
-
})),
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
case "dashboard_clone": {
|
|
215
|
-
// Delegated to extracted handler
|
|
216
|
-
if (hasExtractedDataHandler("dashboard_clone")) {
|
|
217
|
-
const handler = getDataModeHandler("dashboard_clone");
|
|
218
|
-
// Map args to extracted handler format
|
|
219
|
-
const cloneArgs = {
|
|
220
|
-
id: personaId,
|
|
221
|
-
clone_dashboard_from: args.source_persona_id,
|
|
222
|
-
clone_limit: args.limit ?? 100,
|
|
223
|
-
sanitize: args.sanitize,
|
|
224
|
-
sanitize_examples: args.sanitize_examples,
|
|
225
|
-
};
|
|
226
|
-
return withWarning(await handler(cloneArgs, client));
|
|
227
|
-
}
|
|
228
|
-
return withWarning({ error: "dashboard_clone handler not found" });
|
|
229
|
-
}
|
|
230
|
-
case "dashboard_generate": {
|
|
231
|
-
// Delegated to extracted handler
|
|
232
|
-
if (hasExtractedDataHandler("dashboard_generate")) {
|
|
233
|
-
const handler = getDataModeHandler("dashboard_generate");
|
|
234
|
-
// Map args to extracted handler format
|
|
235
|
-
const generateArgs = {
|
|
236
|
-
id: personaId,
|
|
237
|
-
dashboard_generate: args.count ?? args.dashboard_generate ?? 5,
|
|
238
|
-
dashboard_template: args.template ?? args.dashboard_template,
|
|
239
|
-
};
|
|
240
|
-
return withWarning(await handler(generateArgs, client));
|
|
241
|
-
}
|
|
242
|
-
return withWarning({ error: "dashboard_generate handler not found" });
|
|
243
|
-
}
|
|
244
|
-
default:
|
|
245
|
-
return withWarning({ error: `Unknown mode: ${mode}` });
|
|
37
|
+
// Map old param names to new
|
|
38
|
+
if (args.file)
|
|
39
|
+
dataArgs.file_path = args.file;
|
|
40
|
+
if (args.source_persona_id)
|
|
41
|
+
dataArgs.from = args.source_persona_id;
|
|
42
|
+
const result = await handleData(dataArgs, client, readFile);
|
|
43
|
+
// Add deprecation warning to result
|
|
44
|
+
if (typeof result === "object" && result !== null) {
|
|
45
|
+
return {
|
|
46
|
+
_deprecation: {
|
|
47
|
+
message: "The 'knowledge' tool is deprecated. Use 'data' tool instead.",
|
|
48
|
+
migration: `data(method="${mappedMode}", ...)`,
|
|
49
|
+
},
|
|
50
|
+
...result,
|
|
51
|
+
};
|
|
246
52
|
}
|
|
53
|
+
return result;
|
|
247
54
|
}
|
|
@@ -128,13 +128,48 @@ export async function handleCreate(args, client, getTemplateId) {
|
|
|
128
128
|
}
|
|
129
129
|
// For persona cloning, default include_data to true
|
|
130
130
|
const effectiveIncludeData = sourcePersonaId ? (includeData ?? true) : false;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
131
|
+
let result;
|
|
132
|
+
try {
|
|
133
|
+
result = await client.createAiEmployee({
|
|
134
|
+
name,
|
|
135
|
+
description: args.description,
|
|
136
|
+
template_id: templateId,
|
|
137
|
+
source_persona_id: sourcePersonaId,
|
|
138
|
+
clone_data: effectiveIncludeData,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
143
|
+
const personaType = args.type;
|
|
144
|
+
// Provide recovery guidance based on persona type
|
|
145
|
+
const recovery = {};
|
|
146
|
+
if (personaType === "voice" || templateId?.includes("voice") || templateId?.includes("001e")) {
|
|
147
|
+
recovery.option_1 = "Fetch gold template: ema://templates/voice-ai/workflow-def";
|
|
148
|
+
recovery.option_2 = "Fetch config template: ema://templates/voice-ai/proto-config";
|
|
149
|
+
recovery.option_3 = "See complete guide: ema://templates/voice-ai/complete";
|
|
150
|
+
}
|
|
151
|
+
else if (personaType === "chat") {
|
|
152
|
+
recovery.option_1 = "Use catalog(type='templates') to list available templates";
|
|
153
|
+
recovery.option_2 = "Try template(config='chat') for config structure";
|
|
154
|
+
}
|
|
155
|
+
else if (personaType === "dashboard") {
|
|
156
|
+
recovery.option_1 = "Use catalog(type='templates') to list available templates";
|
|
157
|
+
recovery.option_2 = "Try template(config='dashboard') for config structure";
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
recovery.option_1 = "Use catalog(type='templates') to list available templates";
|
|
161
|
+
recovery.option_2 = "Specify explicit template ID with from='<template-id>'";
|
|
162
|
+
}
|
|
163
|
+
recovery.fallback = "Fetch ema://templates/voice-ai/complete for step-by-step recovery guide";
|
|
164
|
+
return {
|
|
165
|
+
error: `create_ai_employee failed`,
|
|
166
|
+
details: errorMsg,
|
|
167
|
+
environment: client.env?.name || "unknown",
|
|
168
|
+
recovery,
|
|
169
|
+
_tip: "If template creation fails, use gold templates from ema://templates/{type}/workflow-def as fallback.",
|
|
170
|
+
_warning: "NEVER clone a random existing persona. Use gold templates instead.",
|
|
171
|
+
};
|
|
172
|
+
}
|
|
138
173
|
const newPersonaId = result.persona_id ?? result.id;
|
|
139
174
|
if (!newPersonaId) {
|
|
140
175
|
return { error: "Failed to create persona: no ID returned from API" };
|
|
@@ -145,21 +180,15 @@ export async function handleCreate(args, client, getTemplateId) {
|
|
|
145
180
|
let workflowError;
|
|
146
181
|
if (workflowDef && newPersonaId) {
|
|
147
182
|
try {
|
|
148
|
-
// Get the newly created persona to get its proto_config
|
|
183
|
+
// Get the newly created persona to get its proto_config
|
|
149
184
|
const newPersona = await client.getPersonaById(newPersonaId);
|
|
150
185
|
const protoConfig = (newPersona?.proto_config ?? {});
|
|
151
|
-
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
const existingWfName = existingWorkflow?.workflowName;
|
|
155
|
-
const workflowForDeploy = JSON.parse(JSON.stringify(workflowDef));
|
|
156
|
-
if (existingWfName?.name) {
|
|
157
|
-
// Set/overwrite the workflowName to match the existing one
|
|
158
|
-
workflowForDeploy.workflowName = existingWfName;
|
|
159
|
-
}
|
|
186
|
+
// NOTE: The SDK's updateAiEmployee() handles workflowName namespace automatically.
|
|
187
|
+
// It will copy from existing workflow if present, or generate a valid namespace if not.
|
|
188
|
+
// No need to manually manipulate workflowName here.
|
|
160
189
|
await client.updateAiEmployee({
|
|
161
190
|
persona_id: newPersonaId,
|
|
162
|
-
workflow:
|
|
191
|
+
workflow: workflowDef,
|
|
163
192
|
proto_config: protoConfig, // Required: API needs proto_config alongside workflow
|
|
164
193
|
});
|
|
165
194
|
workflowApplied = true;
|