@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
package/README.md
CHANGED
|
@@ -221,9 +221,17 @@ environments:
|
|
|
221
221
|
persona(input="IT helpdesk with KB search", type="chat", name="IT Support", preview=false)
|
|
222
222
|
```
|
|
223
223
|
|
|
224
|
-
**Modify existing AI Employee:**
|
|
224
|
+
**Modify existing AI Employee (LLM-Driven):**
|
|
225
225
|
```typescript
|
|
226
|
-
|
|
226
|
+
// Step 1: Get workflow context
|
|
227
|
+
persona(mode="modify", id="abc-123")
|
|
228
|
+
// Returns: current_nodes, available_actions, example_operations
|
|
229
|
+
|
|
230
|
+
// Step 2: Build and execute structured operations
|
|
231
|
+
persona(mode="modify", id="abc-123", operations=[
|
|
232
|
+
{ type: "insert", insert: { action_type: "hitl", insert_before: "send_email" }},
|
|
233
|
+
{ type: "remove", remove: { nodes: ["unused_node"] }}
|
|
234
|
+
], preview=true)
|
|
227
235
|
```
|
|
228
236
|
|
|
229
237
|
**Fix issues automatically:**
|
|
@@ -4,28 +4,13 @@
|
|
|
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;
|
|
@@ -1,48 +1,117 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Data Handler - Operations on persona data (knowledge base, dashboard rows)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Supports both `method` (V2) and `mode` (legacy) parameters.
|
|
5
|
+
*
|
|
6
|
+
* Available methods:
|
|
5
7
|
* - list: List data sources
|
|
6
8
|
* - stats: Get file counts by status
|
|
9
|
+
* - templates: List/get generation templates
|
|
10
|
+
* - generate: Generate content via Document Generation API
|
|
11
|
+
* - get: Get file contents by ID
|
|
7
12
|
* - copy: Copy data from another persona (with optional sanitization)
|
|
8
13
|
* - replicate: Fast copy by S3 reference (no file transfer)
|
|
9
14
|
* - upload: Upload data source (file path or LLM-generated content)
|
|
10
15
|
* - delete: Delete data source
|
|
11
|
-
* -
|
|
16
|
+
* - embedding: Check/toggle embedding status (delegates to knowledge)
|
|
12
17
|
* - refresh: Rerun workflow for existing dashboard rows
|
|
13
18
|
* - regenerate: Modify document section with new query
|
|
14
19
|
* - replace: Replace entire document content (full replacement, not partial)
|
|
20
|
+
* - sanitize: Sanitize data items for a persona
|
|
15
21
|
*
|
|
16
|
-
* Note: Embeddings enabled by default on upload - no toggle API
|
|
17
22
|
* Note: Schema is now at persona level: persona(method="schema", id="...")
|
|
18
|
-
* Note: Document delete not available in API - use replace with empty or regenerate
|
|
19
|
-
* Note: For dashboard fixed inputs, use upload with row context (not replace)
|
|
20
23
|
*/
|
|
21
24
|
import { errorResult } from "../types.js";
|
|
22
25
|
// Import handlers
|
|
23
26
|
import { handleDashboardClone } from "./dashboard-clone.js";
|
|
27
|
+
// Import generation templates
|
|
28
|
+
import { GENERATION_TEMPLATES, listGenerationTemplates, getGenerationTemplate } from "./templates.js";
|
|
24
29
|
/**
|
|
25
30
|
* Main data handler with method-based dispatch
|
|
31
|
+
*
|
|
32
|
+
* @param args - Handler arguments
|
|
33
|
+
* @param client - Ema API client
|
|
34
|
+
* @param readFile - Optional file reader function (for compatibility)
|
|
26
35
|
*/
|
|
27
|
-
export async function handleData(args, client) {
|
|
36
|
+
export async function handleData(args, client, readFile) {
|
|
28
37
|
const personaId = args.persona_id;
|
|
29
|
-
|
|
38
|
+
// Get method from data object or top-level (support both 'method' and 'mode')
|
|
39
|
+
const dataArgs = args.data;
|
|
40
|
+
const method = (dataArgs?.method ??
|
|
41
|
+
dataArgs?.mode ??
|
|
42
|
+
args.method ??
|
|
43
|
+
args.mode);
|
|
44
|
+
// Templates mode doesn't require persona_id
|
|
45
|
+
if (!personaId && method !== "templates") {
|
|
30
46
|
return errorResult("Data operations require persona_id");
|
|
31
47
|
}
|
|
32
|
-
//
|
|
33
|
-
const dataArgs = args.data;
|
|
34
|
-
const method = (dataArgs?.method ?? args.method);
|
|
48
|
+
// Mode inference when method is omitted
|
|
35
49
|
if (!method) {
|
|
36
|
-
return
|
|
37
|
-
error: "Data operations require explicit method parameter",
|
|
38
|
-
hint: "Valid methods: list, stats, copy, replicate, upload, delete, search, embed, refresh, regenerate, replace",
|
|
39
|
-
};
|
|
50
|
+
return inferMethodAndClarify(args, dataArgs);
|
|
40
51
|
}
|
|
41
52
|
switch (method) {
|
|
53
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
54
|
+
// Templates - list or get generation templates
|
|
55
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
56
|
+
case "templates": {
|
|
57
|
+
const templateName = (dataArgs?.template ?? dataArgs?.name ?? args.template);
|
|
58
|
+
if (templateName) {
|
|
59
|
+
const template = getGenerationTemplate(templateName);
|
|
60
|
+
if (!template) {
|
|
61
|
+
return {
|
|
62
|
+
error: `Template not found: ${templateName}`,
|
|
63
|
+
available: Object.keys(GENERATION_TEMPLATES),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
method: "templates",
|
|
68
|
+
name: templateName,
|
|
69
|
+
...template,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// List all templates
|
|
73
|
+
return {
|
|
74
|
+
method: "templates",
|
|
75
|
+
templates: listGenerationTemplates(),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
79
|
+
// Generate - create new content via Document Generation API
|
|
80
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
81
|
+
case "generate": {
|
|
82
|
+
if (!personaId) {
|
|
83
|
+
return errorResult("persona_id is required for generate method");
|
|
84
|
+
}
|
|
85
|
+
return handleDataGenerate(personaId, dataArgs ?? args, client);
|
|
86
|
+
}
|
|
87
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
88
|
+
// Get - retrieve file contents by ID
|
|
89
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
90
|
+
case "get": {
|
|
91
|
+
if (!personaId) {
|
|
92
|
+
return errorResult("persona_id is required for get method");
|
|
93
|
+
}
|
|
94
|
+
const fileId = (dataArgs?.file_id ?? dataArgs?.id ?? args.file_id);
|
|
95
|
+
if (!fileId) {
|
|
96
|
+
return errorResult("data.get requires file_id parameter");
|
|
97
|
+
}
|
|
98
|
+
return handleDataGet(personaId, fileId, client);
|
|
99
|
+
}
|
|
100
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
101
|
+
// Embedding - check/toggle embedding status (delegates to knowledge)
|
|
102
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
103
|
+
case "embedding":
|
|
104
|
+
case "embed": {
|
|
105
|
+
if (!personaId) {
|
|
106
|
+
return errorResult("persona_id is required for embedding method");
|
|
107
|
+
}
|
|
108
|
+
const enabled = dataArgs?.enabled;
|
|
109
|
+
return handleDataEmbedding(personaId, enabled, client);
|
|
110
|
+
}
|
|
42
111
|
case "list":
|
|
43
112
|
return handleDataList(personaId, client);
|
|
44
113
|
case "stats":
|
|
45
|
-
return handleDataStats(personaId, dataArgs?.widget_name, client);
|
|
114
|
+
return handleDataStats(personaId, (dataArgs?.widget_name ?? args.widget_name), client);
|
|
46
115
|
case "copy": {
|
|
47
116
|
const sourceId = (dataArgs?.from ?? args.source_persona_id);
|
|
48
117
|
if (!sourceId) {
|
|
@@ -64,17 +133,23 @@ export async function handleData(args, client) {
|
|
|
64
133
|
case "sanitize":
|
|
65
134
|
return handleDataSanitize(personaId, dataArgs?.examples, dataArgs?.apply, client);
|
|
66
135
|
case "replicate":
|
|
67
|
-
return handleDataReplicate(personaId, (dataArgs?.from ?? args.source_persona_id), dataArgs?.widget_mappings, dataArgs?.wait, client);
|
|
136
|
+
return handleDataReplicate(personaId, (dataArgs?.from ?? args.from ?? args.source_persona_id), (dataArgs?.widget_mappings ?? args.widget_mappings), (dataArgs?.wait ?? args.wait), client);
|
|
68
137
|
case "upload": {
|
|
69
138
|
// Upload file or LLM-generated content (dashboard rows)
|
|
70
|
-
const filePath = dataArgs?.path;
|
|
139
|
+
const filePath = (dataArgs?.path ?? dataArgs?.file ?? args.file);
|
|
71
140
|
const items = dataArgs?.items;
|
|
72
141
|
if (filePath) {
|
|
73
|
-
// File upload -
|
|
142
|
+
// File upload - use provided readFile or fall back to fs
|
|
74
143
|
try {
|
|
75
|
-
|
|
144
|
+
let fileContent;
|
|
145
|
+
if (readFile) {
|
|
146
|
+
fileContent = await readFile(filePath);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
const fs = await import("fs/promises");
|
|
150
|
+
fileContent = await fs.readFile(filePath);
|
|
151
|
+
}
|
|
76
152
|
const path = await import("path");
|
|
77
|
-
const fileContent = await fs.readFile(filePath);
|
|
78
153
|
const filename = path.basename(filePath);
|
|
79
154
|
const result = await client.uploadDataSource(personaId, fileContent, filename);
|
|
80
155
|
return {
|
|
@@ -118,7 +193,7 @@ export async function handleData(args, client) {
|
|
|
118
193
|
}
|
|
119
194
|
case "delete": {
|
|
120
195
|
// Delete data source file
|
|
121
|
-
const fileId = (dataArgs?.file_id ?? dataArgs?.id);
|
|
196
|
+
const fileId = (dataArgs?.file_id ?? dataArgs?.id ?? args.file_id);
|
|
122
197
|
if (!fileId) {
|
|
123
198
|
return { error: "data.delete requires file_id or id parameter" };
|
|
124
199
|
}
|
|
@@ -147,7 +222,6 @@ export async function handleData(args, client) {
|
|
|
147
222
|
hint: "Search is performed via the persona's workflow, not direct API",
|
|
148
223
|
};
|
|
149
224
|
}
|
|
150
|
-
// Note: Embeddings are enabled by default when uploading files - no toggle API
|
|
151
225
|
case "refresh": {
|
|
152
226
|
// Generic refresh - currently supports dashboard rows
|
|
153
227
|
// Could extend to documents (re-generate), KB files (re-embed), etc.
|
|
@@ -233,7 +307,7 @@ export async function handleData(args, client) {
|
|
|
233
307
|
default:
|
|
234
308
|
return {
|
|
235
309
|
error: `Unknown data method: ${method}`,
|
|
236
|
-
hint: "Valid methods: list, stats, copy, replicate, upload, delete,
|
|
310
|
+
hint: "Valid methods: list, stats, templates, generate, get, copy, replicate, upload, delete, embedding, refresh, regenerate, replace, sanitize",
|
|
237
311
|
};
|
|
238
312
|
}
|
|
239
313
|
}
|
|
@@ -261,15 +335,24 @@ async function handleDataList(personaId, client) {
|
|
|
261
335
|
*/
|
|
262
336
|
async function handleDataStats(personaId, widgetName, client) {
|
|
263
337
|
try {
|
|
264
|
-
|
|
338
|
+
// Pass widget_name to API if provided, otherwise let API return default
|
|
339
|
+
const stats = await client.getDataSourceAggregates(personaId, widgetName);
|
|
265
340
|
return {
|
|
266
341
|
method: "stats",
|
|
267
342
|
persona_id: personaId,
|
|
268
|
-
|
|
343
|
+
widget_name: stats.widgetName ?? widgetName ?? "all",
|
|
344
|
+
total: stats.total,
|
|
345
|
+
pending: stats.pending,
|
|
346
|
+
success: stats.success,
|
|
347
|
+
failed: stats.failed,
|
|
348
|
+
_tip: "Use data(method='list') to see individual files",
|
|
269
349
|
};
|
|
270
350
|
}
|
|
271
351
|
catch (error) {
|
|
272
|
-
return {
|
|
352
|
+
return {
|
|
353
|
+
error: `Failed to get data stats: ${error instanceof Error ? error.message : String(error)}`,
|
|
354
|
+
persona_id: personaId,
|
|
355
|
+
};
|
|
273
356
|
}
|
|
274
357
|
}
|
|
275
358
|
/**
|
|
@@ -322,31 +405,110 @@ async function handleDataSanitize(personaId, examples, apply, client) {
|
|
|
322
405
|
}
|
|
323
406
|
/**
|
|
324
407
|
* Fast replication by S3 reference (no file transfer)
|
|
408
|
+
*
|
|
409
|
+
* Behavior matches the monolith handleData mode=replicate:
|
|
410
|
+
* - Default: waits for replication to complete
|
|
411
|
+
* - Can skip waiting with wait=false
|
|
412
|
+
* - Validates widget_mappings format
|
|
325
413
|
*/
|
|
326
|
-
async function handleDataReplicate(targetPersonaId, sourcePersonaId,
|
|
414
|
+
async function handleDataReplicate(targetPersonaId, sourcePersonaId, widgetMappingsRaw, wait, client) {
|
|
327
415
|
if (!sourcePersonaId) {
|
|
328
|
-
return {
|
|
416
|
+
return {
|
|
417
|
+
error: "from is required for replicate mode (source persona ID)",
|
|
418
|
+
hint: "Use data(method='replicate', from='source-persona-id') to copy data by reference",
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
// Parse and validate widget mappings if provided
|
|
422
|
+
let widgetMappings;
|
|
423
|
+
if (widgetMappingsRaw !== undefined) {
|
|
424
|
+
// Validate that widget_mappings is an array
|
|
425
|
+
if (!Array.isArray(widgetMappingsRaw)) {
|
|
426
|
+
return {
|
|
427
|
+
error: "widget_mappings must be an array",
|
|
428
|
+
hint: "Use widget_mappings=[{source_widget: 'kb', target_widget: 'kb'}]",
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
// Validate each mapping has at least source_widget
|
|
432
|
+
try {
|
|
433
|
+
widgetMappings = widgetMappingsRaw.map((m, i) => {
|
|
434
|
+
if (typeof m !== "object" || m === null) {
|
|
435
|
+
throw new Error(`widget_mappings[${i}] must be an object`);
|
|
436
|
+
}
|
|
437
|
+
const mapping = m;
|
|
438
|
+
const sourceWidget = mapping.source_widget ?? mapping.sourceWidget;
|
|
439
|
+
if (!sourceWidget || typeof sourceWidget !== "string") {
|
|
440
|
+
throw new Error(`widget_mappings[${i}].source_widget is required`);
|
|
441
|
+
}
|
|
442
|
+
return {
|
|
443
|
+
sourceWidget,
|
|
444
|
+
targetWidget: mapping.target_widget ?? mapping.targetWidget,
|
|
445
|
+
};
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
catch (validationErr) {
|
|
449
|
+
return {
|
|
450
|
+
error: `Invalid widget_mappings: ${validationErr.message}`,
|
|
451
|
+
hint: "Each mapping must have source_widget (string). target_widget is optional.",
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
// Default: replicate knowledge_base
|
|
457
|
+
widgetMappings = [{ sourceWidget: "knowledge_base" }];
|
|
329
458
|
}
|
|
459
|
+
const shouldWait = wait !== false; // Default to true
|
|
330
460
|
try {
|
|
331
|
-
//
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
461
|
+
// Call replicateData - copies S3 references, not actual files
|
|
462
|
+
const result = await client.replicateData({ type: "persona", id: sourcePersonaId }, { type: "persona", id: targetPersonaId }, widgetMappings);
|
|
463
|
+
// If wait=true (default), poll for completion
|
|
464
|
+
if (shouldWait && result.results.length > 0) {
|
|
465
|
+
const requestIds = result.results.map((r) => r.requestId).filter(Boolean);
|
|
466
|
+
const statuses = [];
|
|
467
|
+
for (const requestId of requestIds) {
|
|
468
|
+
try {
|
|
469
|
+
const status = await client.waitForReplication(requestId, targetPersonaId, {
|
|
470
|
+
timeoutMs: 60_000,
|
|
471
|
+
pollIntervalMs: 1_000,
|
|
472
|
+
});
|
|
473
|
+
statuses.push({ requestId, status: status.status, failedReason: status.failedReason });
|
|
474
|
+
}
|
|
475
|
+
catch (pollErr) {
|
|
476
|
+
statuses.push({ requestId, status: "timeout", failedReason: String(pollErr) });
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
const allCompleted = statuses.every(s => s.status === "completed");
|
|
480
|
+
const anyFailed = statuses.some(s => s.status === "failed");
|
|
481
|
+
return {
|
|
482
|
+
success: allCompleted,
|
|
483
|
+
source_persona_id: sourcePersonaId,
|
|
484
|
+
target_persona_id: targetPersonaId,
|
|
485
|
+
replication_results: result.results,
|
|
486
|
+
final_statuses: statuses,
|
|
487
|
+
message: allCompleted
|
|
488
|
+
? "Data replicated successfully (by reference, no files copied)"
|
|
489
|
+
: anyFailed
|
|
490
|
+
? "Some replications failed"
|
|
491
|
+
: "Replication completed with warnings",
|
|
492
|
+
_tip: "Replication copies S3 references - much faster than downloading and re-uploading files",
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
// If wait=false, return immediately with request IDs
|
|
340
496
|
return {
|
|
341
|
-
|
|
497
|
+
success: true,
|
|
342
498
|
source_persona_id: sourcePersonaId,
|
|
343
499
|
target_persona_id: targetPersonaId,
|
|
344
|
-
|
|
345
|
-
|
|
500
|
+
replication_results: result.results,
|
|
501
|
+
message: "Replication initiated (async)",
|
|
502
|
+
_tip: "Use getReplicationStatus with request IDs to check progress",
|
|
346
503
|
};
|
|
347
504
|
}
|
|
348
505
|
catch (error) {
|
|
349
|
-
return {
|
|
506
|
+
return {
|
|
507
|
+
error: `Replication failed: ${error.message}`,
|
|
508
|
+
source_persona_id: sourcePersonaId,
|
|
509
|
+
target_persona_id: targetPersonaId,
|
|
510
|
+
hint: "Ensure both personas exist and have compatible data structures",
|
|
511
|
+
};
|
|
350
512
|
}
|
|
351
513
|
}
|
|
352
514
|
/**
|
|
@@ -367,6 +529,188 @@ async function handleDashboardRefresh(personaId, rowId, client) {
|
|
|
367
529
|
return { error: `Failed to refresh dashboard row: ${error instanceof Error ? error.message : String(error)}` };
|
|
368
530
|
}
|
|
369
531
|
}
|
|
532
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
533
|
+
// Method inference and clarification when method is omitted
|
|
534
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
535
|
+
function inferMethodAndClarify(args, dataArgs) {
|
|
536
|
+
// Try to infer from params if unambiguous
|
|
537
|
+
if ((args.file || dataArgs?.path) && !args.file_id && !dataArgs?.file_id) {
|
|
538
|
+
// Has file path but no file_id → likely wants upload
|
|
539
|
+
return {
|
|
540
|
+
status: "clarification_needed",
|
|
541
|
+
message: "What operation would you like to perform?",
|
|
542
|
+
inferred: "upload",
|
|
543
|
+
hint: "You provided a file path. Did you mean method='upload'?",
|
|
544
|
+
options: ["upload", "list", "generate", "embedding"],
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
if ((args.file_id || dataArgs?.file_id) && (args.input || dataArgs?.input)) {
|
|
548
|
+
// Has file_id and input → likely wants regenerate
|
|
549
|
+
return {
|
|
550
|
+
status: "clarification_needed",
|
|
551
|
+
message: "What operation would you like to perform?",
|
|
552
|
+
inferred: "regenerate",
|
|
553
|
+
hint: "You provided file_id and input. Did you mean method='regenerate'?",
|
|
554
|
+
options: ["regenerate", "replace", "delete", "get"],
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
if (args.input || dataArgs?.input || args.from || dataArgs?.from) {
|
|
558
|
+
// Has input or from → likely wants generate
|
|
559
|
+
return {
|
|
560
|
+
status: "clarification_needed",
|
|
561
|
+
message: "What operation would you like to perform?",
|
|
562
|
+
inferred: "generate",
|
|
563
|
+
hint: "You provided input/from. Did you mean method='generate'?",
|
|
564
|
+
options: ["generate", "list"],
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
if (args.template || dataArgs?.template) {
|
|
568
|
+
// Wants a specific template
|
|
569
|
+
return {
|
|
570
|
+
status: "clarification_needed",
|
|
571
|
+
message: "What operation would you like to perform?",
|
|
572
|
+
inferred: "templates",
|
|
573
|
+
hint: "You provided template name. Did you mean method='templates'?",
|
|
574
|
+
options: ["templates"],
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
// Default clarification
|
|
578
|
+
return {
|
|
579
|
+
status: "clarification_needed",
|
|
580
|
+
message: "What operation would you like to perform?",
|
|
581
|
+
options: ["list", "upload", "generate", "templates", "get", "delete", "copy", "replicate", "embedding", "sanitize", "refresh", "regenerate", "replace"],
|
|
582
|
+
hint: "Specify method='...' for direct execution. Example: data(persona_id='abc', method='list')",
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
586
|
+
// Generate - create new content via Document Generation API
|
|
587
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
588
|
+
async function handleDataGenerate(personaId, dataArgs, client) {
|
|
589
|
+
const from = dataArgs.from;
|
|
590
|
+
const input = dataArgs.input;
|
|
591
|
+
const count = dataArgs.count ?? 1;
|
|
592
|
+
const format = dataArgs.format ?? "markdown";
|
|
593
|
+
const data = dataArgs.data;
|
|
594
|
+
if (!from && !input) {
|
|
595
|
+
return {
|
|
596
|
+
error: "Either 'from' (template name) or 'input' (natural language) is required for generate method",
|
|
597
|
+
available_templates: Object.keys(GENERATION_TEMPLATES),
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
// Build instructions from template or input
|
|
601
|
+
let instructions;
|
|
602
|
+
if (from) {
|
|
603
|
+
const template = getGenerationTemplate(from);
|
|
604
|
+
if (!template) {
|
|
605
|
+
return {
|
|
606
|
+
error: `Template not found: ${from}`,
|
|
607
|
+
available: Object.keys(GENERATION_TEMPLATES),
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
// Build instructions from template
|
|
611
|
+
instructions = template.prompt
|
|
612
|
+
.replace("{count}", String(count))
|
|
613
|
+
.replace("{format}", format);
|
|
614
|
+
if (data) {
|
|
615
|
+
// Inject additional context
|
|
616
|
+
instructions += `\n\nAdditional context: ${JSON.stringify(data)}`;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
instructions = input;
|
|
621
|
+
if (count > 1) {
|
|
622
|
+
instructions = `Generate ${count} items: ${instructions}`;
|
|
623
|
+
}
|
|
624
|
+
instructions += `\n\nOutput format: ${format}`;
|
|
625
|
+
}
|
|
626
|
+
// Call Document Generation API
|
|
627
|
+
try {
|
|
628
|
+
const result = await client.generateDocument(personaId, instructions, {
|
|
629
|
+
timeoutMs: 120_000,
|
|
630
|
+
pollIntervalMs: 2_000,
|
|
631
|
+
});
|
|
632
|
+
return {
|
|
633
|
+
method: "generate",
|
|
634
|
+
persona_id: personaId,
|
|
635
|
+
template: from,
|
|
636
|
+
count,
|
|
637
|
+
format,
|
|
638
|
+
generated: result,
|
|
639
|
+
_tip: "Content generated via Document Generation API. Upload as data source if needed.",
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
catch (error) {
|
|
643
|
+
return { error: `Failed to generate content: ${error instanceof Error ? error.message : String(error)}` };
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
647
|
+
// Get - retrieve file contents by ID
|
|
648
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
649
|
+
async function handleDataGet(personaId, fileId, client) {
|
|
650
|
+
try {
|
|
651
|
+
// Get file info first
|
|
652
|
+
const files = await client.listDataSourceFiles(personaId);
|
|
653
|
+
const file = files.files.find(f => f.id === fileId || f.filename === fileId);
|
|
654
|
+
if (!file) {
|
|
655
|
+
return {
|
|
656
|
+
error: `File not found: ${fileId}`,
|
|
657
|
+
hint: "Use data(method='list') to see available files",
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
// Note: Actual file content retrieval depends on file type and API capabilities
|
|
661
|
+
// For now, return file metadata
|
|
662
|
+
return {
|
|
663
|
+
method: "get",
|
|
664
|
+
persona_id: personaId,
|
|
665
|
+
file_id: fileId,
|
|
666
|
+
file: file,
|
|
667
|
+
note: "File metadata returned. Full content retrieval depends on file type.",
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
catch (error) {
|
|
671
|
+
return { error: `Failed to get file: ${error instanceof Error ? error.message : String(error)}` };
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
675
|
+
// Embedding - check/toggle embedding status
|
|
676
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
677
|
+
async function handleDataEmbedding(personaId, enabled, client) {
|
|
678
|
+
try {
|
|
679
|
+
const persona = await client.getPersonaById(personaId);
|
|
680
|
+
if (!persona) {
|
|
681
|
+
return { error: `Persona not found: ${personaId}` };
|
|
682
|
+
}
|
|
683
|
+
if (enabled !== undefined) {
|
|
684
|
+
// Toggle embedding
|
|
685
|
+
// IMPORTANT: The Ema API requires workflow to be sent along with proto_config
|
|
686
|
+
// for proto_config changes to persist
|
|
687
|
+
await client.updateAiEmployee({
|
|
688
|
+
persona_id: personaId,
|
|
689
|
+
embedding_enabled: enabled,
|
|
690
|
+
proto_config: persona.proto_config,
|
|
691
|
+
workflow: persona.workflow_def,
|
|
692
|
+
});
|
|
693
|
+
return {
|
|
694
|
+
method: "embedding",
|
|
695
|
+
persona_id: personaId,
|
|
696
|
+
enabled,
|
|
697
|
+
previous_enabled: persona.embedding_enabled,
|
|
698
|
+
success: true,
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
// Get status
|
|
703
|
+
return {
|
|
704
|
+
method: "embedding",
|
|
705
|
+
persona_id: personaId,
|
|
706
|
+
embedding_enabled: persona.embedding_enabled,
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
catch (error) {
|
|
711
|
+
return { error: `Failed to manage embedding: ${error instanceof Error ? error.message : String(error)}` };
|
|
712
|
+
}
|
|
713
|
+
}
|
|
370
714
|
// Legacy exports for backwards compatibility
|
|
371
715
|
export const DATA_MODE_HANDLERS = {
|
|
372
716
|
dashboard_clone: handleDashboardClone,
|