@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
|
@@ -19,7 +19,7 @@ import { generateInputDescription, SUPPORTED_OPERATIONS, LIMITATIONS } from "./w
|
|
|
19
19
|
/**
|
|
20
20
|
* Generate the v2 tool set
|
|
21
21
|
*/
|
|
22
|
-
export function
|
|
22
|
+
export function generateTools(envNames, defaultEnv) {
|
|
23
23
|
const envDescription = envNames.length > 0
|
|
24
24
|
? `Environment: ${envNames.join(", ")}. Default: ${defaultEnv}`
|
|
25
25
|
: "Environment name";
|
|
@@ -127,8 +127,8 @@ persona(
|
|
|
127
127
|
// === EXPLICIT METHOD (required) ===
|
|
128
128
|
method: {
|
|
129
129
|
type: "string",
|
|
130
|
-
enum: ["list", "get", "create", "update", "delete", "
|
|
131
|
-
description: "Operation to perform
|
|
130
|
+
enum: ["list", "get", "create", "update", "delete", "sanitize", "schema", "snapshot", "history", "restore"],
|
|
131
|
+
description: "Operation to perform. LLM does analysis/comparison - use 'get' to retrieve data, then reason about it."
|
|
132
132
|
},
|
|
133
133
|
// === Identity ===
|
|
134
134
|
id: {
|
|
@@ -197,11 +197,6 @@ persona(
|
|
|
197
197
|
type: "string",
|
|
198
198
|
description: "Version to restore, e.g. 'v3' (for method=restore)"
|
|
199
199
|
},
|
|
200
|
-
// === Compare params (for method=compare) ===
|
|
201
|
-
to: {
|
|
202
|
-
type: "string",
|
|
203
|
-
description: "Persona ID or snapshot to compare to (for method=compare)"
|
|
204
|
-
},
|
|
205
200
|
// === Data sub-resource ===
|
|
206
201
|
data: {
|
|
207
202
|
type: "object",
|
|
@@ -359,76 +354,47 @@ persona(
|
|
|
359
354
|
},
|
|
360
355
|
},
|
|
361
356
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
362
|
-
// 3. WORKFLOW -
|
|
357
|
+
// 3. WORKFLOW - Data retrieval and deployment ONLY (LLM does all thinking)
|
|
363
358
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
364
359
|
{
|
|
365
360
|
name: "workflow",
|
|
366
|
-
description: `
|
|
367
|
-
|
|
368
|
-
Use this for COMPLEX workflows (categorizers, multi-path routing, entity extraction chains).
|
|
369
|
-
For SIMPLE modifications to existing personas, use persona(method="update", input="...") instead.
|
|
370
|
-
|
|
371
|
-
## Generate (from requirements)
|
|
372
|
-
- \`workflow(mode="generate", input="Voice AI for support with routing")\` - new workflow
|
|
373
|
-
- \`workflow(mode="generate", input="...", persona_id="abc")\` - with widget context from existing persona
|
|
374
|
-
- Returns \`llm_prompt\` for complex workflows - includes available widgets if persona_id provided
|
|
375
|
-
- Returns \`available_widgets\` array showing what widget names to use in bindings
|
|
361
|
+
description: `Get workflow data or deploy LLM-generated workflows.
|
|
376
362
|
|
|
377
|
-
|
|
378
|
-
- \`workflow(mode="analyze", persona_id="abc")\` - detect issues, get WorkflowSpec
|
|
363
|
+
**MCP provides data. LLM does the thinking.**
|
|
379
364
|
|
|
380
|
-
##
|
|
381
|
-
- \`workflow(mode="
|
|
365
|
+
## Get (return data for LLM to work with)
|
|
366
|
+
- \`workflow(mode="get", persona_id="abc")\` - returns workflow_def, schema, patterns, widgets
|
|
367
|
+
- LLM analyzes, compares, and generates workflows using this data
|
|
382
368
|
|
|
383
|
-
## Deploy (
|
|
384
|
-
- \`workflow(mode="deploy", persona_id="abc", workflow_def={...})\` -
|
|
369
|
+
## Deploy (execute LLM's result)
|
|
370
|
+
- \`workflow(mode="deploy", persona_id="abc", workflow_def={...})\` - deploy LLM-generated workflow
|
|
385
371
|
|
|
386
|
-
**Workflow for
|
|
387
|
-
1. \`workflow(mode="
|
|
388
|
-
2.
|
|
389
|
-
3. \`workflow(mode="
|
|
390
|
-
4. \`persona(method="update", id="abc", workflow_def={...})\` → deploy
|
|
391
|
-
|
|
392
|
-
**IMPORTANT**: Workflows reference widgets by name (widgetName). Pass persona_id to get available widget names.`,
|
|
372
|
+
**Workflow for creation:**
|
|
373
|
+
1. \`workflow(mode="get", persona_id="abc")\` → get schema, patterns, current workflow
|
|
374
|
+
2. LLM generates workflow_def (LLM does the work)
|
|
375
|
+
3. \`workflow(mode="deploy", persona_id="abc", workflow_def={...})\` → MCP deploys`,
|
|
393
376
|
inputSchema: {
|
|
394
377
|
type: "object",
|
|
395
378
|
properties: {
|
|
396
379
|
mode: {
|
|
397
380
|
type: "string",
|
|
398
|
-
enum: ["
|
|
399
|
-
description: "
|
|
400
|
-
},
|
|
401
|
-
input: {
|
|
402
|
-
type: "string",
|
|
403
|
-
description: "Natural language requirements (for mode=generate)",
|
|
381
|
+
enum: ["get", "deploy"],
|
|
382
|
+
description: "get = return data for LLM, deploy = execute LLM's workflow_def",
|
|
404
383
|
},
|
|
405
384
|
persona_id: {
|
|
406
385
|
type: "string",
|
|
407
|
-
description: "Persona ID (
|
|
408
|
-
},
|
|
409
|
-
workflow_spec: {
|
|
410
|
-
type: "object",
|
|
411
|
-
description: "WorkflowSpec object (for mode=compile)",
|
|
386
|
+
description: "Persona ID (required)",
|
|
412
387
|
},
|
|
413
388
|
workflow_def: {
|
|
414
389
|
type: "object",
|
|
415
|
-
description: "workflow_def JSON (for mode=deploy)",
|
|
416
|
-
},
|
|
417
|
-
type: {
|
|
418
|
-
type: "string",
|
|
419
|
-
enum: ["voice", "chat", "dashboard"],
|
|
420
|
-
description: "Persona type (for mode=generate)",
|
|
421
|
-
},
|
|
422
|
-
preview: {
|
|
423
|
-
type: "boolean",
|
|
424
|
-
description: "Preview without deploying (default: true)",
|
|
390
|
+
description: "workflow_def JSON (for mode=deploy) - LLM generates this",
|
|
425
391
|
},
|
|
426
392
|
env: {
|
|
427
393
|
type: "string",
|
|
428
394
|
description: envDescription,
|
|
429
395
|
},
|
|
430
396
|
},
|
|
431
|
-
required: ["mode"],
|
|
397
|
+
required: ["mode", "persona_id"],
|
|
432
398
|
},
|
|
433
399
|
},
|
|
434
400
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* This file defines supported workflow modification operations.
|
|
5
5
|
* Used by:
|
|
6
6
|
* - analyzeModificationRequest() in handlers-consolidated.ts
|
|
7
|
-
* - Tool descriptions in tools
|
|
7
|
+
* - Tool descriptions in tools.ts
|
|
8
8
|
* - Tests to verify capabilities
|
|
9
9
|
*
|
|
10
10
|
* Add new operations here, and they'll automatically appear in tool docs.
|
|
@@ -64,7 +64,7 @@ export const ALTERNATIVES = {
|
|
|
64
64
|
};
|
|
65
65
|
/**
|
|
66
66
|
* Generate tool description for the `input` parameter.
|
|
67
|
-
* Called by tools
|
|
67
|
+
* Called by tools.ts to ensure description matches actual capabilities.
|
|
68
68
|
*/
|
|
69
69
|
export function generateInputDescription() {
|
|
70
70
|
const ops = SUPPORTED_OPERATIONS.map(op => op.example).join("', '");
|
|
@@ -1,34 +1,80 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* EmaClientAdapter -
|
|
2
|
+
* EmaClientAdapter - Drop-in replacement for legacy EmaClient using EmaClientV2
|
|
3
3
|
*
|
|
4
|
-
* This adapter
|
|
5
|
-
*
|
|
6
|
-
* the same interface as EmaClient for seamless comparison.
|
|
4
|
+
* This adapter provides full compatibility with the legacy EmaClient interface
|
|
5
|
+
* while using the new auto-generated EmaClientV2 under the hood.
|
|
7
6
|
*
|
|
8
7
|
* Usage:
|
|
9
8
|
* ```typescript
|
|
10
|
-
*
|
|
9
|
+
* // Replace: import { EmaClient } from './client.js';
|
|
10
|
+
* // With: import { EmaClientAdapter as EmaClient } from './client-adapter.js';
|
|
11
|
+
*
|
|
12
|
+
* const client = new EmaClientAdapter({
|
|
11
13
|
* name: 'dev',
|
|
12
14
|
* baseUrl: 'https://api.dev.ema.co',
|
|
13
15
|
* bearerToken: 'xxx',
|
|
14
16
|
* });
|
|
15
17
|
*
|
|
16
18
|
* // Same API as EmaClient
|
|
17
|
-
* const personas = await
|
|
19
|
+
* const personas = await client.getPersonasForTenant();
|
|
18
20
|
* ```
|
|
19
21
|
*/
|
|
20
22
|
import { EmaClientV2 } from './ema-client.js';
|
|
21
23
|
/**
|
|
22
24
|
* Adapter that wraps EmaClientV2 with the legacy EmaClient interface.
|
|
23
25
|
*
|
|
24
|
-
* This
|
|
26
|
+
* This is a drop-in replacement for EmaClient.
|
|
25
27
|
*/
|
|
26
28
|
export class EmaClientAdapter {
|
|
27
29
|
client;
|
|
28
30
|
env;
|
|
29
|
-
|
|
31
|
+
tokenRefreshConfig;
|
|
32
|
+
refreshIntervalId;
|
|
33
|
+
constructor(env, opts) {
|
|
30
34
|
this.env = env;
|
|
31
35
|
this.client = new EmaClientV2(env);
|
|
36
|
+
this.tokenRefreshConfig = opts?.tokenRefresh;
|
|
37
|
+
// Start background refresh if configured
|
|
38
|
+
if (this.tokenRefreshConfig?.backgroundRefresh !== false && this.tokenRefreshConfig?.refreshCallback) {
|
|
39
|
+
this.startBackgroundRefresh();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Start background token refresh */
|
|
43
|
+
startBackgroundRefresh() {
|
|
44
|
+
const intervalMs = this.tokenRefreshConfig?.refreshIntervalMs ?? 50 * 60 * 1000; // 50 minutes default
|
|
45
|
+
this.refreshIntervalId = setInterval(async () => {
|
|
46
|
+
try {
|
|
47
|
+
await this.refreshToken();
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
console.error('[EmaClientAdapter] Background token refresh failed:', e);
|
|
51
|
+
}
|
|
52
|
+
}, intervalMs);
|
|
53
|
+
}
|
|
54
|
+
/** Stop background token refresh */
|
|
55
|
+
stopBackgroundRefresh() {
|
|
56
|
+
if (this.refreshIntervalId) {
|
|
57
|
+
clearInterval(this.refreshIntervalId);
|
|
58
|
+
this.refreshIntervalId = undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Manually trigger token refresh */
|
|
62
|
+
async refreshToken() {
|
|
63
|
+
if (!this.tokenRefreshConfig?.refreshCallback) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const newToken = await this.tokenRefreshConfig.refreshCallback();
|
|
68
|
+
this.updateToken(newToken);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Access the underlying EmaClientV2 for advanced use */
|
|
76
|
+
get v2Client() {
|
|
77
|
+
return this.client;
|
|
32
78
|
}
|
|
33
79
|
/**
|
|
34
80
|
* Get the environment name
|
|
@@ -54,28 +100,23 @@ export class EmaClientAdapter {
|
|
|
54
100
|
updateToken(newToken) {
|
|
55
101
|
this.client.updateToken(newToken);
|
|
56
102
|
}
|
|
57
|
-
//
|
|
58
|
-
// Persona Operations
|
|
59
|
-
//
|
|
103
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
104
|
+
// Persona Operations
|
|
105
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
60
106
|
/**
|
|
61
107
|
* List all personas for the current tenant
|
|
62
108
|
*/
|
|
63
109
|
async getPersonasForTenant() {
|
|
64
110
|
const personas = await this.client.listPersonas();
|
|
65
|
-
// Map generated PersonaDto to legacy PersonaDTO interface
|
|
66
111
|
return personas.map(p => this.mapToLegacyPersona(p));
|
|
67
112
|
}
|
|
68
|
-
/**
|
|
69
|
-
* Alias for getPersonasForTenant
|
|
70
|
-
*/
|
|
113
|
+
/** Alias for getPersonasForTenant */
|
|
71
114
|
async getAiEmployeesForTenant() {
|
|
72
115
|
return this.getPersonasForTenant();
|
|
73
116
|
}
|
|
74
117
|
/**
|
|
75
118
|
* Get a single persona by ID
|
|
76
|
-
*
|
|
77
|
-
* Returns null for non-existent personas (matching legacy EmaClient behavior).
|
|
78
|
-
* The API may return 404 or 422 for invalid IDs - both are treated as "not found".
|
|
119
|
+
* Returns null for non-existent personas.
|
|
79
120
|
*/
|
|
80
121
|
async getPersonaById(personaId) {
|
|
81
122
|
try {
|
|
@@ -85,7 +126,6 @@ export class EmaClientAdapter {
|
|
|
85
126
|
return this.mapToLegacyPersona(persona);
|
|
86
127
|
}
|
|
87
128
|
catch (error) {
|
|
88
|
-
// Handle 404 (not found) and 422 (invalid ID format) as null
|
|
89
129
|
if (error instanceof Error) {
|
|
90
130
|
const apiError = error;
|
|
91
131
|
if (apiError.statusCode === 404 || apiError.statusCode === 422) {
|
|
@@ -95,26 +135,220 @@ export class EmaClientAdapter {
|
|
|
95
135
|
throw error;
|
|
96
136
|
}
|
|
97
137
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Create a new AI Employee
|
|
140
|
+
*/
|
|
141
|
+
async createAiEmployee(req) {
|
|
142
|
+
// persona_template_id is required by the API
|
|
143
|
+
const templateId = req.persona_template_id ?? req.template_id;
|
|
144
|
+
if (!templateId) {
|
|
145
|
+
throw new Error('persona_template_id or template_id is required');
|
|
146
|
+
}
|
|
147
|
+
const result = await this.client.createAiEmployee({
|
|
148
|
+
name: req.name,
|
|
149
|
+
persona_template_id: templateId,
|
|
150
|
+
description: req.description,
|
|
151
|
+
});
|
|
152
|
+
return {
|
|
153
|
+
persona_id: result.persona_id ?? '',
|
|
154
|
+
success: !!result.persona_id,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Update an existing AI Employee
|
|
159
|
+
*/
|
|
160
|
+
async updateAiEmployee(req) {
|
|
161
|
+
const result = await this.client.updateAiEmployee({
|
|
162
|
+
persona_id: req.persona_id,
|
|
163
|
+
workflow: req.workflow,
|
|
164
|
+
proto_config: req.proto_config,
|
|
165
|
+
name: req.name,
|
|
166
|
+
description: req.description,
|
|
167
|
+
});
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Delete a persona
|
|
172
|
+
*/
|
|
173
|
+
async deletePersona(personaId) {
|
|
174
|
+
return this.client.deletePersona(personaId);
|
|
175
|
+
}
|
|
176
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
177
|
+
// Template Operations
|
|
178
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
101
179
|
/**
|
|
102
180
|
* List all persona templates
|
|
103
181
|
*/
|
|
104
182
|
async getPersonaTemplates() {
|
|
105
183
|
const templates = await this.client.listPersonaTemplates();
|
|
106
|
-
// Map generated PersonaTemplateDto to legacy PersonaTemplateDTO interface
|
|
107
184
|
return templates.map(t => this.mapToLegacyTemplate(t));
|
|
108
185
|
}
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
186
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
187
|
+
// Action Operations
|
|
188
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
189
|
+
/**
|
|
190
|
+
* List all available actions
|
|
191
|
+
*/
|
|
192
|
+
async listActions() {
|
|
193
|
+
const response = await this.client.listActions();
|
|
194
|
+
// Map gRPC ActionType to ActionDTO
|
|
195
|
+
return (response.actions ?? []).map(a => {
|
|
196
|
+
const typeName = a.typeName;
|
|
197
|
+
const name = typeName?.name?.name ?? '';
|
|
198
|
+
return {
|
|
199
|
+
id: name,
|
|
200
|
+
name: name,
|
|
201
|
+
displayName: a.displayName ?? name,
|
|
202
|
+
description: a.description ?? '',
|
|
203
|
+
category: String(a.category ?? ''),
|
|
204
|
+
typeName: typeName,
|
|
205
|
+
// inputs/outputs are complex proto maps - keep as raw for now
|
|
206
|
+
inputs: a.inputs,
|
|
207
|
+
outputs: a.outputs,
|
|
208
|
+
};
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
/** Alias for listActions */
|
|
212
|
+
async listAgents() {
|
|
213
|
+
return this.listActions();
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* List actions used in a specific workflow
|
|
217
|
+
*/
|
|
218
|
+
async listActionsFromWorkflow(workflowId) {
|
|
219
|
+
const response = await this.client.listActionsFromWorkflow(workflowId);
|
|
220
|
+
// Map ActionType[] to string[] of action names
|
|
221
|
+
return (response.actions ?? []).map(a => {
|
|
222
|
+
const typeName = a.typeName;
|
|
223
|
+
return typeName?.name?.name ?? '';
|
|
224
|
+
}).filter(Boolean);
|
|
225
|
+
}
|
|
226
|
+
/** Alias for listActionsFromWorkflow */
|
|
227
|
+
async listAgentsFromWorkflow(workflowId) {
|
|
228
|
+
return this.listActionsFromWorkflow(workflowId);
|
|
229
|
+
}
|
|
230
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
231
|
+
// Dashboard Operations
|
|
232
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
233
|
+
/**
|
|
234
|
+
* Get dashboard schema
|
|
235
|
+
*/
|
|
236
|
+
async getDashboardSchema(personaId, _dashboardId) {
|
|
237
|
+
const response = await this.client.getDashboardSchema();
|
|
238
|
+
// Extract columns from proto response
|
|
239
|
+
const schema = response;
|
|
240
|
+
return {
|
|
241
|
+
columns: schema.schema?.columns ?? [],
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
245
|
+
// Data Source Operations
|
|
246
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
247
|
+
/**
|
|
248
|
+
* Upload a data source file
|
|
249
|
+
*/
|
|
250
|
+
async uploadDataSource(personaId, fileContent, filename, opts) {
|
|
251
|
+
return this.client.uploadDataSource(personaId, fileContent, filename, opts);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Delete a data source file
|
|
255
|
+
*/
|
|
256
|
+
async deleteDataSource(personaId, fileId) {
|
|
257
|
+
return this.client.deleteDataSource(personaId, fileId);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* List data source files
|
|
261
|
+
*/
|
|
262
|
+
async listDataSourceFiles(personaId, opts) {
|
|
263
|
+
const response = await this.client.listDataSourceFiles(personaId, opts);
|
|
264
|
+
// Map proto ContentNode to simplified file object
|
|
265
|
+
const nodes = response.nodes ?? [];
|
|
266
|
+
return {
|
|
267
|
+
files: nodes.map(n => ({
|
|
268
|
+
id: n.id ?? '',
|
|
269
|
+
name: n.name ?? '',
|
|
270
|
+
size: n.size ?? 0,
|
|
271
|
+
uploadedAt: n.createdAt ?? '',
|
|
272
|
+
})),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Get data source aggregates
|
|
277
|
+
*/
|
|
278
|
+
async getDataSourceAggregates(personaId, widgetName) {
|
|
279
|
+
const response = await this.client.getDataSourceAggregates(personaId, widgetName);
|
|
280
|
+
// Map proto aggregates
|
|
281
|
+
const agg = response;
|
|
282
|
+
const aggregates = agg.aggregates ?? [];
|
|
283
|
+
let total = 0, processed = 0, failed = 0;
|
|
284
|
+
for (const a of aggregates) {
|
|
285
|
+
total += a.count ?? 0;
|
|
286
|
+
if (a.status === 'processed' || a.status === 'PROCESSED')
|
|
287
|
+
processed += a.count ?? 0;
|
|
288
|
+
if (a.status === 'failed' || a.status === 'FAILED')
|
|
289
|
+
failed += a.count ?? 0;
|
|
290
|
+
}
|
|
291
|
+
return { total, processed, failed };
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Replicate data between personas
|
|
295
|
+
*/
|
|
296
|
+
async replicateData(sourcePersonaId, targetPersonaId, widgetMappings) {
|
|
297
|
+
const response = await this.client.replicateData(sourcePersonaId, targetPersonaId, widgetMappings);
|
|
298
|
+
const rep = response;
|
|
299
|
+
return { requestId: rep.requestId ?? '' };
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Get replication status
|
|
303
|
+
*/
|
|
304
|
+
async getReplicationStatus(requestId, _personaId) {
|
|
305
|
+
const response = await this.client.getReplicationStatus(requestId);
|
|
306
|
+
const rep = response;
|
|
307
|
+
const statusMap = {
|
|
308
|
+
0: 'unknown',
|
|
309
|
+
1: 'in_progress',
|
|
310
|
+
2: 'completed',
|
|
311
|
+
3: 'failed',
|
|
312
|
+
};
|
|
313
|
+
return {
|
|
314
|
+
status: statusMap[rep.status ?? 0] ?? 'unknown',
|
|
315
|
+
progress: rep.progress,
|
|
316
|
+
failedReason: rep.failedReason,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Wait for replication to complete
|
|
321
|
+
*/
|
|
322
|
+
async waitForReplication(requestId, personaId, opts) {
|
|
323
|
+
return this.client.waitForReplication(requestId, personaId, opts);
|
|
324
|
+
}
|
|
325
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
326
|
+
// Sync Metadata Operations
|
|
327
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
328
|
+
/**
|
|
329
|
+
* Get sync metadata from a persona
|
|
330
|
+
*/
|
|
331
|
+
getSyncMetadata(persona) {
|
|
332
|
+
// Sync metadata is stored in custom_metadata
|
|
333
|
+
const customMeta = persona.custom_metadata;
|
|
334
|
+
if (!customMeta)
|
|
335
|
+
return null;
|
|
336
|
+
const syncData = customMeta['ema_mcp_sync'];
|
|
337
|
+
if (!syncData || typeof syncData !== 'object')
|
|
338
|
+
return null;
|
|
339
|
+
return syncData;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Check if persona was synced from another environment
|
|
343
|
+
*/
|
|
344
|
+
isSyncedPersona(persona) {
|
|
345
|
+
return this.getSyncMetadata(persona) !== null;
|
|
346
|
+
}
|
|
347
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
348
|
+
// Private Helpers
|
|
349
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
112
350
|
/**
|
|
113
351
|
* Map generated PersonaDto to legacy PersonaDTO interface
|
|
114
|
-
*
|
|
115
|
-
* The generated type has nullable fields (string | null | undefined)
|
|
116
|
-
* while the legacy type expects (string | undefined). This method
|
|
117
|
-
* normalizes null to undefined for compatibility.
|
|
118
352
|
*/
|
|
119
353
|
mapToLegacyPersona(p) {
|
|
120
354
|
return {
|
|
@@ -122,10 +356,10 @@ export class EmaClientAdapter {
|
|
|
122
356
|
name: this.nullToUndefined(p.name),
|
|
123
357
|
description: this.nullToUndefined(p.description),
|
|
124
358
|
template_id: this.nullToUndefined(p.template_id),
|
|
125
|
-
templateId: this.nullToUndefined(p.template_id),
|
|
359
|
+
templateId: this.nullToUndefined(p.template_id),
|
|
126
360
|
workflow_id: this.nullToUndefined(p.workflow_id),
|
|
127
361
|
workflow_def: p.workflow_def,
|
|
128
|
-
workflow: p.workflow_def,
|
|
362
|
+
workflow: p.workflow_def,
|
|
129
363
|
workflow_interface: p.workflow_interface,
|
|
130
364
|
proto_config: p.proto_config,
|
|
131
365
|
status: this.nullToUndefined(p.status),
|
|
@@ -137,6 +371,7 @@ export class EmaClientAdapter {
|
|
|
137
371
|
workflow_dashboard_id: this.nullToUndefined(p.workflow_dashboard_id),
|
|
138
372
|
persona_interaction_type: this.nullToUndefined(p.persona_interaction_type),
|
|
139
373
|
embedding_enabled: p.embedding_enabled,
|
|
374
|
+
custom_metadata: p.custom_metadata,
|
|
140
375
|
};
|
|
141
376
|
}
|
|
142
377
|
/**
|
package/dist/sdk/client.js
CHANGED
|
@@ -393,27 +393,43 @@ export class EmaClient {
|
|
|
393
393
|
const existingPersona = await this.getPersonaById(req.persona_id);
|
|
394
394
|
const existingWorkflow = existingPersona?.workflow_def;
|
|
395
395
|
const existingWfName = existingWorkflow?.workflowName;
|
|
396
|
+
// Deep clone workflow for modifications
|
|
397
|
+
const fixedWorkflow = JSON.parse(JSON.stringify(req.workflow));
|
|
396
398
|
if (existingWfName?.name) {
|
|
397
|
-
//
|
|
398
|
-
const fixedWorkflow = JSON.parse(JSON.stringify(req.workflow));
|
|
399
|
+
// Copy namespace from existing workflow
|
|
399
400
|
fixedWorkflow.workflowName = existingWfName;
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
if (results) {
|
|
403
|
-
const fixedResults = {};
|
|
404
|
-
for (const [key, value] of Object.entries(results)) {
|
|
405
|
-
if (value.actionName && value.outputName) {
|
|
406
|
-
const correctKey = `${value.actionName}.${value.outputName}`;
|
|
407
|
-
fixedResults[correctKey] = value;
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
fixedWorkflow.results = fixedResults;
|
|
401
|
+
if (opts?.verbose || process.env.EMA_DEBUG) {
|
|
402
|
+
console.error("[EmaClient] Copied workflow namespace from existing:", existingWfName.name);
|
|
411
403
|
}
|
|
412
|
-
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
// CRITICAL FIX: Generate a valid namespace for personas without existing workflows.
|
|
407
|
+
// The API requires workflowName to match the persona's namespace format.
|
|
408
|
+
// Format: ["ema", "personas", "<persona_id>"] with name "workflow"
|
|
409
|
+
const generatedWfName = {
|
|
410
|
+
name: {
|
|
411
|
+
namespaces: ["ema", "personas", req.persona_id],
|
|
412
|
+
name: "workflow",
|
|
413
|
+
},
|
|
414
|
+
};
|
|
415
|
+
fixedWorkflow.workflowName = generatedWfName;
|
|
413
416
|
if (opts?.verbose || process.env.EMA_DEBUG) {
|
|
414
|
-
console.error("[EmaClient]
|
|
417
|
+
console.error("[EmaClient] Generated workflow namespace for new persona:", generatedWfName.name);
|
|
415
418
|
}
|
|
416
419
|
}
|
|
420
|
+
// Also fix results format if needed - API expects "<actionName>.<outputName>" keys
|
|
421
|
+
const results = fixedWorkflow.results;
|
|
422
|
+
if (results) {
|
|
423
|
+
const fixedResults = {};
|
|
424
|
+
for (const [key, value] of Object.entries(results)) {
|
|
425
|
+
if (value.actionName && value.outputName) {
|
|
426
|
+
const correctKey = `${value.actionName}.${value.outputName}`;
|
|
427
|
+
fixedResults[correctKey] = value;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
fixedWorkflow.results = fixedResults;
|
|
431
|
+
}
|
|
432
|
+
finalReq = { ...req, workflow: fixedWorkflow };
|
|
417
433
|
}
|
|
418
434
|
// Debug logging for troubleshooting
|
|
419
435
|
if (opts?.verbose || process.env.EMA_DEBUG) {
|
|
@@ -534,10 +550,23 @@ export class EmaClient {
|
|
|
534
550
|
});
|
|
535
551
|
if (!resp.ok) {
|
|
536
552
|
const body = await resp.text();
|
|
553
|
+
// Try to extract error details from response
|
|
554
|
+
let errorDetail = "";
|
|
555
|
+
try {
|
|
556
|
+
const parsed = JSON.parse(body);
|
|
557
|
+
errorDetail = parsed.error || parsed.message || parsed.detail || "";
|
|
558
|
+
}
|
|
559
|
+
catch {
|
|
560
|
+
errorDetail = body.slice(0, 200);
|
|
561
|
+
}
|
|
562
|
+
// Build informative error message
|
|
563
|
+
const message = errorDetail
|
|
564
|
+
? `create_ai_employee failed (${this.env.name}): ${errorDetail}`
|
|
565
|
+
: `create_ai_employee failed (${this.env.name}) - status ${resp.status}`;
|
|
537
566
|
throw new EmaApiError({
|
|
538
567
|
statusCode: resp.status,
|
|
539
568
|
body,
|
|
540
|
-
message
|
|
569
|
+
message,
|
|
541
570
|
});
|
|
542
571
|
}
|
|
543
572
|
return (await resp.json());
|