@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.

Files changed (87) 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 +47 -18
  9. package/dist/mcp/handlers/persona/index.js +14 -11
  10. package/dist/mcp/handlers/persona/update.js +4 -2
  11. package/dist/mcp/handlers/persona/version.js +234 -0
  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/deploy.js +73 -0
  17. package/dist/mcp/handlers/workflow/generate.js +350 -0
  18. package/dist/mcp/handlers/workflow/index.js +294 -0
  19. package/dist/mcp/handlers/workflow/modify.js +456 -0
  20. package/dist/mcp/handlers/workflow/optimize.js +136 -0
  21. package/dist/mcp/handlers/workflow/types.js +4 -0
  22. package/dist/mcp/handlers/workflow/utils.js +30 -0
  23. package/dist/mcp/handlers-consolidated.js +73 -2696
  24. package/dist/mcp/prompts.js +83 -43
  25. package/dist/mcp/resources.js +382 -57
  26. package/dist/mcp/server.js +199 -391
  27. package/dist/mcp/{tools-v2.js → tools.js} +20 -54
  28. package/dist/mcp/workflow-operations.js +2 -2
  29. package/dist/sdk/client-adapter.js +267 -32
  30. package/dist/sdk/client.js +45 -16
  31. package/dist/sdk/ema-client.js +183 -0
  32. package/dist/sdk/generated/deprecated-actions.js +171 -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 +139 -86
  37. package/dist/sdk/workflow-intent.js +27 -0
  38. package/dist/sdk/workflow-transformer.js +0 -342
  39. package/docs/mcp-tools-guide.md +37 -45
  40. package/package.json +10 -4
  41. package/dist/mcp/handlers/persona/analyze.js +0 -275
  42. package/dist/mcp/handlers/persona/compare.js +0 -32
  43. package/dist/mcp/tools-consolidated.js +0 -875
  44. package/dist/mcp/tools-legacy.js +0 -736
  45. package/docs/CODEBASE-ANALYSIS-2026-01-23.md +0 -936
  46. package/docs/CODEBASE-ANALYSIS-PRIORITIZED.md +0 -774
  47. package/docs/api-contracts.md +0 -216
  48. package/docs/auto-builder-analysis.md +0 -271
  49. package/docs/blog/mcp-tool-design-lessons.md +0 -309
  50. package/docs/data-architecture.md +0 -166
  51. package/docs/demos/ap-invoice-generation.md +0 -347
  52. package/docs/demos/ap-invoice-processing.md +0 -271
  53. package/docs/ema-auto-builder-guide.html +0 -394
  54. package/docs/lessons-learned.md +0 -209
  55. package/docs/llm-native-workflow-design.md +0 -252
  56. package/docs/local-generation.md +0 -508
  57. package/docs/mcp-flow-diagram.md +0 -135
  58. package/docs/migration/action-composition-migration.md +0 -270
  59. package/docs/naming-conventions.md +0 -278
  60. package/docs/proposals/HANDOFF-tool-restructure.md +0 -526
  61. package/docs/proposals/action-composition.md +0 -490
  62. package/docs/proposals/explicit-method-restructure.md +0 -328
  63. package/docs/proposals/mcp-tool-restructure-2026-01.md +0 -366
  64. package/docs/proposals/self-contained-guidance.md +0 -427
  65. package/docs/proto-sdk-generation.md +0 -242
  66. package/docs/release-impact.md +0 -102
  67. package/docs/release-process.md +0 -157
  68. package/docs/staging.RULE.md +0 -142
  69. package/docs/test-persona-creation.md +0 -196
  70. package/docs/tool-consolidation-v2.md +0 -225
  71. package/docs/tool-response-standards.md +0 -256
  72. package/resources/demo-kits/README.md +0 -175
  73. package/resources/demo-kits/finance-ap/manifest.json +0 -150
  74. package/resources/demo-kits/tags.json +0 -91
  75. package/resources/docs/getting-started.md +0 -97
  76. package/resources/templates/auto-builder-rules.md +0 -224
  77. package/resources/templates/chat-ai/README.md +0 -119
  78. package/resources/templates/chat-ai/persona-config.json +0 -111
  79. package/resources/templates/dashboard-ai/README.md +0 -156
  80. package/resources/templates/dashboard-ai/persona-config.json +0 -180
  81. package/resources/templates/demo-scenarios/README.md +0 -63
  82. package/resources/templates/demo-scenarios/test-published-package.md +0 -116
  83. package/resources/templates/document-gen-ai/README.md +0 -132
  84. package/resources/templates/document-gen-ai/persona-config.json +0 -316
  85. package/resources/templates/voice-ai/README.md +0 -123
  86. package/resources/templates/voice-ai/persona-config.json +0 -74
  87. 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
- persona(id="abc-123", input="add HITL before email, add compliance intent", preview=false)
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
- // 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 action tool requests - list, get, filter, and search actions
22
10
  */
23
11
  export async function handleAction(args, client) {
24
- // Check for deprecated params and log warnings
25
- const deprecationWarnings = checkDeprecatedParams(args);
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
- * All operations require explicit `method` parameter:
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
- * - search: Search knowledge base (via workflow)
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
- if (!personaId) {
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
- // Get method from data object or top-level
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 - need to read file first
142
+ // File upload - use provided readFile or fall back to fs
74
143
  try {
75
- const fs = await import("fs/promises");
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, search, embed, refresh, regenerate, replace",
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
- const stats = await client.getDataSourceAggregates(personaId, widgetName ?? "fileUpload");
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
- ...stats,
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 { error: `Failed to get data stats: ${error instanceof Error ? error.message : String(error)}` };
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, widgetMappings, wait, client) {
414
+ async function handleDataReplicate(targetPersonaId, sourcePersonaId, widgetMappingsRaw, wait, client) {
327
415
  if (!sourcePersonaId) {
328
- return { error: "data.replicate requires 'from' parameter (source persona ID)" };
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
- // Default widget mappings if not provided
332
- const mappings = widgetMappings?.map(m => ({
333
- sourceWidget: m.source_widget,
334
- targetWidget: m.target_widget ?? m.source_widget,
335
- })) ?? [
336
- { sourceWidget: "fileUpload", targetWidget: "fileUpload" },
337
- { sourceWidget: "kb", targetWidget: "kb" },
338
- ];
339
- const result = await client.replicateData({ type: "persona", id: sourcePersonaId }, { type: "persona", id: targetPersonaId }, mappings);
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
- method: "replicate",
497
+ success: true,
342
498
  source_persona_id: sourcePersonaId,
343
499
  target_persona_id: targetPersonaId,
344
- ...result,
345
- note: "Replication initiated. S3 references copied (no file transfer).",
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 { error: `Failed to replicate data: ${error instanceof Error ? error.message : String(error)}` };
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,