@ema.co/mcp-toolkit 1.5.2 → 1.6.0
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-consolidated.js +251 -20
- package/dist/mcp/server.js +1 -203
- package/dist/mcp/tools-consolidated.js +146 -100
- package/dist/sdk/action-registry.js +128 -0
- package/dist/sdk/client.js +58 -90
- package/dist/sdk/generated/api-types.js +11 -0
- package/dist/sdk/index.js +15 -1
- package/dist/sdk/knowledge.js +38 -8
- package/dist/sdk/quality-gates.js +386 -0
- package/dist/sdk/structural-rules.js +290 -0
- package/dist/sdk/workflow-generator.js +88 -34
- package/dist/sdk/workflow-intent.js +237 -24
- package/dist/sdk/workflow-optimizer.js +665 -0
- package/dist/sdk/workflow-tracer.js +648 -0
- package/dist/sdk/workflow-transformer.js +10 -0
- package/dist/sdk/workflow-validator.js +391 -0
- package/docs/.temp/datasource-attach.har +198369 -0
- package/docs/.temp/grpcweb.gar +1 -0
- package/docs/local-generation.md +508 -0
- package/docs/mcp-flow-diagram.md +135 -0
- package/docs/mcp-tools-guide.md +157 -203
- package/docs/openapi.json +8000 -0
- package/docs/release-process.md +153 -0
- package/docs/tool-consolidation-proposal.md +166 -378
- package/package.json +3 -1
|
@@ -46,138 +46,183 @@ export function generateConsolidatedTools(envNames, defaultEnv) {
|
|
|
46
46
|
inputSchema: { type: "object", properties: {}, required: [] },
|
|
47
47
|
},
|
|
48
48
|
// ═══════════════════════════════════════════════════════════════════════
|
|
49
|
-
// 2. PERSONA - AI Employee management (
|
|
49
|
+
// 2. PERSONA - Unified AI Employee management (create, modify, analyze, list)
|
|
50
50
|
// ═══════════════════════════════════════════════════════════════════════
|
|
51
51
|
{
|
|
52
52
|
name: "persona",
|
|
53
|
-
description: `
|
|
53
|
+
description: `Create, modify, analyze, or list AI Employees. ONE tool for everything.
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
## ⚠️ ONE CALL CREATES EVERYTHING - THEN STOP
|
|
56
|
+
|
|
57
|
+
If requirements are unclear, first call \`template(questions=true)\` to get what to ask.
|
|
58
|
+
Then make ONE call with all gathered info:
|
|
59
|
+
|
|
60
|
+
\`\`\`
|
|
61
|
+
persona(
|
|
62
|
+
input="Voice AI SDR: qualifies leads, identifies use-case, sends follow-up email",
|
|
63
|
+
type="voice",
|
|
64
|
+
name="SP - SDR Test", // REQUIRED: The actual persona name shown in Ema
|
|
65
|
+
preview=false
|
|
66
|
+
)
|
|
67
|
+
\`\`\`
|
|
68
|
+
|
|
69
|
+
**After success, STOP. Do NOT make follow-up calls to "fix" or "enhance".**
|
|
70
|
+
|
|
71
|
+
**MCP handles internally:** template selection, config generation, widget formatting, welcome message, API calls.
|
|
72
|
+
|
|
73
|
+
## CRITICAL: The \`name\` Parameter
|
|
74
|
+
|
|
75
|
+
The \`name\` parameter is the **actual persona name** in the Ema platform - NOT derived from input.
|
|
76
|
+
|
|
77
|
+
❌ name omitted → MCP parses name from input (often wrong)
|
|
78
|
+
✅ name="SP - SDR Test" → Exact name shown in platform
|
|
79
|
+
|
|
80
|
+
## Create NEW AI Employee
|
|
81
|
+
|
|
82
|
+
persona(input="<what it should do>", type="voice", name="Actual Name", preview=false)
|
|
83
|
+
|
|
84
|
+
## Modify EXISTING (workflow changes)
|
|
85
|
+
|
|
86
|
+
persona(id="abc-123", input="add HITL before email", preview=false)
|
|
87
|
+
|
|
88
|
+
## Update Config Only (voice settings, welcome message)
|
|
89
|
+
|
|
90
|
+
persona(id="abc-123", input="Update welcome message to: Hello!", preview=false)
|
|
91
|
+
|
|
92
|
+
MCP auto-detects config vs workflow changes.
|
|
93
|
+
|
|
94
|
+
## Analyze/Get
|
|
95
|
+
|
|
96
|
+
persona(id="abc-123")
|
|
57
97
|
persona(id="abc-123", include_workflow=true)
|
|
58
98
|
|
|
59
|
-
|
|
99
|
+
## Optimize (auto-fix issues)
|
|
100
|
+
|
|
101
|
+
persona(id="abc-123", optimize=true, preview=false)
|
|
102
|
+
|
|
103
|
+
## List/Search
|
|
104
|
+
|
|
60
105
|
persona(all=true)
|
|
61
106
|
persona(query="support", status="active")
|
|
62
|
-
persona(trigger_type="voice")
|
|
63
107
|
|
|
64
|
-
|
|
65
|
-
persona(mode="create", name="New Bot", type="voice")
|
|
108
|
+
## Simple vs Complex Workflows
|
|
66
109
|
|
|
67
|
-
**
|
|
68
|
-
persona(id="abc-123", mode="update", name="Renamed")
|
|
110
|
+
**Simple** (Q&A, search + respond): Deploys directly → \`status: "success"\`
|
|
69
111
|
|
|
70
|
-
**
|
|
71
|
-
|
|
112
|
+
**Complex** (email, HITL, multi-intent): Returns \`status: "needs_llm_generation"\` with:
|
|
113
|
+
- \`llm_prompt\`: System + user prompts for workflow generation
|
|
114
|
+
- \`available_actions\`: Action catalog from API
|
|
115
|
+
- \`hint\`: How to complete deployment
|
|
72
116
|
|
|
73
|
-
|
|
74
|
-
persona(
|
|
117
|
+
For complex workflows, send the prompt to an LLM and deploy:
|
|
118
|
+
\`persona(workflow_def=<llm_response>, ...)\`
|
|
119
|
+
|
|
120
|
+
## Key Rules
|
|
75
121
|
|
|
76
|
-
**
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
persona(id="abc-123", mode="version_get", version="v3")
|
|
80
|
-
persona(id="abc-123", mode="version_compare", v1="v2", v2="v3")
|
|
81
|
-
persona(id="abc-123", mode="version_restore", version="v2")
|
|
82
|
-
persona(id="abc-123", mode="version_policy", auto_on_deploy=true)`,
|
|
122
|
+
1. **ONE CALL** - Put everything in \`input\`, explicit \`name\`, MCP handles rest
|
|
123
|
+
2. **STOP after success** - Don't make follow-up "fix" calls
|
|
124
|
+
3. **preview=false** - Deploys to Ema platform`,
|
|
83
125
|
inputSchema: withEnv({
|
|
84
|
-
//
|
|
126
|
+
// === IDENTITY ===
|
|
85
127
|
id: {
|
|
86
128
|
type: "string",
|
|
87
|
-
description: "Persona ID (UUID) or exact name. Omit
|
|
129
|
+
description: "Persona ID (UUID) or exact name. Omit when creating new."
|
|
88
130
|
},
|
|
89
|
-
// Deprecated alias (backwards compatibility)
|
|
90
131
|
identifier: {
|
|
91
132
|
type: "string",
|
|
92
133
|
deprecated: true,
|
|
93
|
-
description: "DEPRECATED: use id.
|
|
134
|
+
description: "DEPRECATED: use id.",
|
|
94
135
|
},
|
|
95
|
-
//
|
|
96
|
-
|
|
136
|
+
// === CREATE/MODIFY (the main way to use this tool) ===
|
|
137
|
+
input: {
|
|
97
138
|
type: "string",
|
|
98
|
-
|
|
99
|
-
description: "Operation mode. Default: 'get' with id, 'list' without."
|
|
139
|
+
description: "Natural language description. For new: 'Voice AI for sales...'. For modify: 'add HITL before email'.",
|
|
100
140
|
},
|
|
101
|
-
|
|
141
|
+
type: {
|
|
142
|
+
type: "string",
|
|
143
|
+
enum: ["voice", "chat", "dashboard"],
|
|
144
|
+
description: "AI Employee type. REQUIRED for creating new."
|
|
145
|
+
},
|
|
146
|
+
name: { type: "string", description: "REQUIRED for new: The actual persona name shown in Ema platform (e.g., 'SP - SDR Test'). Don't derive from input." },
|
|
147
|
+
description: { type: "string", description: "Description of what it does." },
|
|
148
|
+
preview: {
|
|
149
|
+
type: "boolean",
|
|
150
|
+
description: "Default: true (safe). Set false to deploy changes."
|
|
151
|
+
},
|
|
152
|
+
// === ANALYZE/OPTIMIZE ===
|
|
153
|
+
optimize: {
|
|
154
|
+
type: "boolean",
|
|
155
|
+
description: "Auto-fix detected issues. Use with id.",
|
|
156
|
+
},
|
|
157
|
+
include: {
|
|
158
|
+
type: "array",
|
|
159
|
+
items: { type: "string", enum: ["issues", "connections", "fixes", "metrics"] },
|
|
160
|
+
description: "What to include in analysis output.",
|
|
161
|
+
},
|
|
162
|
+
include_workflow: { type: "boolean", description: "Include full workflow_def in response" },
|
|
163
|
+
include_fingerprint: { type: "boolean", description: "Include config hash" },
|
|
164
|
+
// === LIST/SEARCH ===
|
|
102
165
|
all: { type: "boolean", description: "List all personas" },
|
|
103
166
|
query: { type: "string", description: "Search by name (partial match)" },
|
|
104
167
|
status: { type: "string", description: "Filter: 'active', 'inactive', 'draft'" },
|
|
105
168
|
trigger_type: { type: "string", description: "Filter: 'voice', 'chat', 'dashboard'" },
|
|
106
169
|
limit: { type: "number", description: "Max results (default: 50)" },
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
//
|
|
111
|
-
name: { type: "string", description: "Name (for create/update)" },
|
|
112
|
-
description: { type: "string", description: "Description (for create/update)" },
|
|
113
|
-
type: {
|
|
114
|
-
type: "string",
|
|
115
|
-
enum: ["voice", "chat", "dashboard"],
|
|
116
|
-
description: "Persona type (for create)"
|
|
117
|
-
},
|
|
118
|
-
template_id: { type: "string", description: "Template ID (for create)" },
|
|
119
|
-
clone_from: { type: "string", description: "Clone from persona ID (for create)" },
|
|
120
|
-
clone_data: { type: "boolean", description: "Also clone knowledge base files when using clone_from (default: false)" },
|
|
121
|
-
// Update flags
|
|
122
|
-
enabled: { type: "boolean", description: "Enable/disable (for update)" },
|
|
170
|
+
// === COMPARE ===
|
|
171
|
+
compare_to: { type: "string", description: "Second persona ID for comparison" },
|
|
172
|
+
compare_env: { type: "string", description: "Environment of compare_to persona" },
|
|
173
|
+
// === ADVANCED/OVERRIDE ===
|
|
123
174
|
proto_config: {
|
|
124
175
|
type: "object",
|
|
125
|
-
description: "
|
|
176
|
+
description: "Override voice/chat settings. Usually auto-generated from input."
|
|
126
177
|
},
|
|
127
178
|
workflow: {
|
|
128
179
|
type: "object",
|
|
129
|
-
description: "
|
|
180
|
+
description: "Direct workflow JSON (advanced). Usually auto-generated."
|
|
130
181
|
},
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
182
|
+
workflow_def: {
|
|
183
|
+
type: "object",
|
|
184
|
+
description: "Alias for workflow (backwards compatibility)."
|
|
185
|
+
},
|
|
186
|
+
template_id: { type: "string", description: "Specific template ID (usually auto-selected)" },
|
|
187
|
+
clone_from: { type: "string", description: "Clone from existing persona ID" },
|
|
188
|
+
clone_data: { type: "boolean", description: "Also clone knowledge base files" },
|
|
189
|
+
enabled: { type: "boolean", description: "Enable/disable persona" },
|
|
190
|
+
// === TEMPLATES ===
|
|
135
191
|
templates: { type: "boolean", description: "List available templates" },
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
192
|
+
// === VERSION MANAGEMENT ===
|
|
193
|
+
mode: {
|
|
194
|
+
type: "string",
|
|
195
|
+
enum: ["version_create", "version_list", "version_get", "version_compare", "version_restore", "version_policy"],
|
|
196
|
+
description: "Version management mode. Only needed for version operations."
|
|
197
|
+
},
|
|
198
|
+
version: { type: "string", description: "Version identifier (e.g., 'v3', 'latest')" },
|
|
199
|
+
v1: { type: "string", description: "First version for comparison" },
|
|
200
|
+
v2: { type: "string", description: "Second version for comparison" },
|
|
201
|
+
message: { type: "string", description: "Version message/description" },
|
|
202
|
+
auto_on_deploy: { type: "boolean", description: "Auto-create version on deploy" },
|
|
203
|
+
auto_on_sync: { type: "boolean", description: "Auto-create version on sync" },
|
|
204
|
+
max_versions: { type: "number", description: "Max versions to keep" },
|
|
144
205
|
}),
|
|
145
206
|
},
|
|
146
207
|
// ═══════════════════════════════════════════════════════════════════════
|
|
147
|
-
// 3. WORKFLOW -
|
|
208
|
+
// 3. WORKFLOW - DEPRECATED: Use persona() instead
|
|
148
209
|
// ═══════════════════════════════════════════════════════════════════════
|
|
149
210
|
{
|
|
150
211
|
name: "workflow",
|
|
151
|
-
description:
|
|
152
|
-
|
|
153
|
-
## Creating a NEW AI Employee (Greenfield)
|
|
154
|
-
|
|
155
|
-
Creates persona from template, configures settings. Template provides valid workflow structure.
|
|
156
|
-
|
|
157
|
-
workflow(input="Voice AI for sales development", name="My Sales SDR", type="voice", preview=false)
|
|
212
|
+
description: `⚠️ DEPRECATED: Use \`persona()\` instead. This tool routes to persona.
|
|
158
213
|
|
|
159
|
-
|
|
214
|
+
## Migration Guide
|
|
160
215
|
|
|
161
|
-
|
|
216
|
+
OLD (deprecated):
|
|
217
|
+
workflow(input="...", type="voice", name="Bot", preview=false)
|
|
218
|
+
workflow(persona_id="abc", input="add HITL")
|
|
162
219
|
|
|
163
|
-
|
|
220
|
+
NEW (use this):
|
|
221
|
+
persona(input="...", type="voice", name="Bot", preview=false)
|
|
222
|
+
persona(id="abc", input="add HITL")
|
|
164
223
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
workflow(persona_id="abc-123", input="add HITL approval before sending emails")
|
|
168
|
-
workflow(persona_id="abc-123", input="add a new Sales intent category", preview=false)
|
|
169
|
-
|
|
170
|
-
## Analyzing a Workflow
|
|
171
|
-
|
|
172
|
-
workflow(persona_id="abc-123") # Returns issues, connections, metrics
|
|
173
|
-
workflow(persona_id="abc-123", optimize=true, preview=false) # Auto-fix and deploy
|
|
174
|
-
|
|
175
|
-
## Key Rules
|
|
176
|
-
|
|
177
|
-
1. **preview=true (default)**: Safe - returns result without deploying
|
|
178
|
-
2. **preview=false**: Deploys changes to Ema platform
|
|
179
|
-
3. **Greenfield creates from template** - then modify workflow separately if needed
|
|
180
|
-
4. **Brownfield transforms existing** - uses decompile → transform → compile`,
|
|
224
|
+
The \`persona\` tool now handles everything: create, modify, analyze, list.
|
|
225
|
+
This \`workflow\` tool still works but shows a deprecation warning.`,
|
|
181
226
|
inputSchema: withEnv({
|
|
182
227
|
// === REQUIRED for creating/modifying ===
|
|
183
228
|
input: {
|
|
@@ -296,23 +341,24 @@ Uses LLM-native workflow transformation. Fetches existing workflow, transforms i
|
|
|
296
341
|
// ═══════════════════════════════════════════════════════════════════════
|
|
297
342
|
{
|
|
298
343
|
name: "template",
|
|
299
|
-
description: `Get
|
|
344
|
+
description: `Get qualifying questions and reference patterns.
|
|
345
|
+
|
|
346
|
+
## 🎯 PRIMARY USE: Get Questions to Ask User
|
|
347
|
+
|
|
348
|
+
**Before creating an AI Employee, call this to get what to ask:**
|
|
349
|
+
template(questions=true) // All qualifying questions
|
|
350
|
+
template(questions=true, category="Voice") // Voice-specific questions
|
|
300
351
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
template(patterns=true)
|
|
304
|
-
template(patterns=true, type="voice")
|
|
352
|
+
Returns structured questions about: type, intents, data sources, actions, approvals, etc.
|
|
353
|
+
Ask the user these questions, then put answers into ONE workflow() call.
|
|
305
354
|
|
|
306
|
-
|
|
307
|
-
template(widgets="voice")
|
|
308
|
-
template(widgets="chat")
|
|
355
|
+
## Reference (understand options, not for manual building)
|
|
309
356
|
|
|
310
|
-
|
|
311
|
-
template(
|
|
357
|
+
template(pattern="intent-routing") // See pattern structure
|
|
358
|
+
template(patterns=true) // List available patterns
|
|
359
|
+
template(widgets="voice") // Widget reference
|
|
312
360
|
|
|
313
|
-
|
|
314
|
-
template(questions=true)
|
|
315
|
-
template(questions=true, category="Voice")`,
|
|
361
|
+
⚠️ Do NOT copy/paste configs from these - MCP generates them internally.`,
|
|
316
362
|
inputSchema: {
|
|
317
363
|
type: "object",
|
|
318
364
|
properties: {
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Action Registry - Helpers for extracting action metadata from API
|
|
3
|
+
*
|
|
4
|
+
* Uses existing client.listActions() - no duplicate caching needed.
|
|
5
|
+
* Just provides helpers to extract version/namespace from raw API response.
|
|
6
|
+
*/
|
|
7
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8
|
+
// Helpers to extract version/namespace from raw API response
|
|
9
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
10
|
+
/**
|
|
11
|
+
* Extract action name, version, and namespaces from raw API response.
|
|
12
|
+
*/
|
|
13
|
+
export function parseActionDefinition(action) {
|
|
14
|
+
// API returns typeName.name.namespaces and typeName.version
|
|
15
|
+
const raw = action;
|
|
16
|
+
const typeName = raw.typeName;
|
|
17
|
+
if (!typeName?.name?.name)
|
|
18
|
+
return null;
|
|
19
|
+
return {
|
|
20
|
+
name: typeName.name.name,
|
|
21
|
+
version: typeName.version ?? "v0",
|
|
22
|
+
namespaces: typeName.name.namespaces ?? ["actions", "emainternal"],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Extract template info from raw API response.
|
|
27
|
+
*/
|
|
28
|
+
export function parseTemplateDefinition(template) {
|
|
29
|
+
if (!template.id || !template.name)
|
|
30
|
+
return null;
|
|
31
|
+
const raw = template;
|
|
32
|
+
return {
|
|
33
|
+
id: template.id,
|
|
34
|
+
name: template.name,
|
|
35
|
+
triggerType: raw.trigger_type ?? 1,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
39
|
+
// Registry Class (lightweight, uses client directly)
|
|
40
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
41
|
+
export class ActionRegistry {
|
|
42
|
+
actions = new Map();
|
|
43
|
+
templates = new Map();
|
|
44
|
+
templatesByType = new Map();
|
|
45
|
+
loaded = false;
|
|
46
|
+
/**
|
|
47
|
+
* Load from raw API data.
|
|
48
|
+
* Call client.listActions() and client.getPersonaTemplates() externally
|
|
49
|
+
* and pass the results here.
|
|
50
|
+
*/
|
|
51
|
+
loadFromData(actions, templates) {
|
|
52
|
+
this.actions.clear();
|
|
53
|
+
this.templates.clear();
|
|
54
|
+
this.templatesByType.clear();
|
|
55
|
+
for (const action of actions) {
|
|
56
|
+
const def = parseActionDefinition(action);
|
|
57
|
+
if (def) {
|
|
58
|
+
this.actions.set(def.name, def);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
for (const template of templates) {
|
|
62
|
+
const def = parseTemplateDefinition(template);
|
|
63
|
+
if (def) {
|
|
64
|
+
this.templates.set(def.id, def);
|
|
65
|
+
// First template of each trigger type wins
|
|
66
|
+
if (!this.templatesByType.has(def.triggerType)) {
|
|
67
|
+
this.templatesByType.set(def.triggerType, def);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
this.loaded = true;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get action version. Falls back to "v0".
|
|
75
|
+
*/
|
|
76
|
+
getVersion(actionName) {
|
|
77
|
+
return this.actions.get(actionName)?.version ?? "v0";
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get action namespaces. Falls back to ["actions", "emainternal"].
|
|
81
|
+
*/
|
|
82
|
+
getNamespaces(actionName) {
|
|
83
|
+
return this.actions.get(actionName)?.namespaces ?? ["actions", "emainternal"];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get action definition.
|
|
87
|
+
*/
|
|
88
|
+
getAction(actionName) {
|
|
89
|
+
return this.actions.get(actionName);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Get template by ID.
|
|
93
|
+
*/
|
|
94
|
+
getTemplate(id) {
|
|
95
|
+
return this.templates.get(id);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get template for persona type.
|
|
99
|
+
* Trigger types: 1=CHAT, 2=DASHBOARD, 4=VOICE
|
|
100
|
+
*/
|
|
101
|
+
getTemplateForType(type) {
|
|
102
|
+
const triggerTypes = {
|
|
103
|
+
voice: 4,
|
|
104
|
+
chat: 1,
|
|
105
|
+
dashboard: 2,
|
|
106
|
+
};
|
|
107
|
+
return this.templatesByType.get(triggerTypes[type]);
|
|
108
|
+
}
|
|
109
|
+
isLoaded() {
|
|
110
|
+
return this.loaded;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
114
|
+
// Factory function (loads from API)
|
|
115
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
116
|
+
/**
|
|
117
|
+
* Create and load action registry from API.
|
|
118
|
+
* Uses client methods directly - no caching here (resources.ts handles that for MCP).
|
|
119
|
+
*/
|
|
120
|
+
export async function ensureActionRegistry(client) {
|
|
121
|
+
const registry = new ActionRegistry();
|
|
122
|
+
const [actions, templates] = await Promise.all([
|
|
123
|
+
client.listActions().catch(() => []),
|
|
124
|
+
client.getPersonaTemplates().catch(() => []),
|
|
125
|
+
]);
|
|
126
|
+
registry.loadFromData(actions, templates);
|
|
127
|
+
return registry;
|
|
128
|
+
}
|
package/dist/sdk/client.js
CHANGED
|
@@ -100,80 +100,22 @@ export class EmaClient {
|
|
|
100
100
|
const controller = new AbortController();
|
|
101
101
|
const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
102
102
|
const fullUrl = `${this.env.baseUrl.replace(/\/$/, "")}${path}`;
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const jsonAny = opts?.json;
|
|
112
|
-
const workflowAny = jsonAny?.workflow ?? jsonAny?.workflow_def;
|
|
113
|
-
const actions = workflowAny?.actions ?? [];
|
|
114
|
-
const actionNames = Array.isArray(actions)
|
|
115
|
-
? actions
|
|
116
|
-
.slice(0, 50)
|
|
117
|
-
.map((a) => a?.name)
|
|
118
|
-
.filter((n) => typeof n === "string")
|
|
119
|
-
: [];
|
|
120
|
-
const containsHitl = actionNames.some((n) => n.toLowerCase().includes("hitl")) || JSON.stringify(actionNames).toLowerCase().includes("hitl");
|
|
121
|
-
const jsonSize = opts?.json !== undefined ? JSON.stringify(opts.json).length : 0;
|
|
122
|
-
fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/sdk/client.ts:requestWithRetries:pre', message: 'HTTP request', data: { env: this.env.name, codeDir: process.env.EMA_MCP_CODE_DIR ?? null, cwd: process.cwd(), method, path, isUpdatePersona, attempt, retries, hasJson: opts?.json !== undefined, jsonSize, hasWorkflow: !!workflowAny, actionCount: Array.isArray(actions) ? actions.length : 0, containsHitl }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'pre-fix', hypothesisId: 'H3' }) }).catch(() => { });
|
|
123
|
-
}
|
|
103
|
+
// gRPC-Connect endpoints need Connect-Protocol-Version header
|
|
104
|
+
const isGrpcConnect = path.includes(".v1.") || path.includes(".v2.");
|
|
105
|
+
const headers = {
|
|
106
|
+
Authorization: `Bearer ${this.env.bearerToken}`,
|
|
107
|
+
"Content-Type": "application/json",
|
|
108
|
+
};
|
|
109
|
+
if (isGrpcConnect) {
|
|
110
|
+
headers["Connect-Protocol-Version"] = "1";
|
|
124
111
|
}
|
|
125
|
-
catch { }
|
|
126
|
-
// #endregion agent log
|
|
127
112
|
const response = await fetch(fullUrl, {
|
|
128
113
|
method,
|
|
129
|
-
headers
|
|
130
|
-
Authorization: `Bearer ${this.env.bearerToken}`,
|
|
131
|
-
"Content-Type": "application/json",
|
|
132
|
-
},
|
|
114
|
+
headers,
|
|
133
115
|
body: opts?.json !== undefined ? JSON.stringify(opts.json) : undefined,
|
|
134
116
|
signal: controller.signal,
|
|
135
117
|
});
|
|
136
118
|
clearTimeout(timeoutId);
|
|
137
|
-
// #region agent log
|
|
138
|
-
// H1/H2/H4: capture response status + request id for correlation (no secrets).
|
|
139
|
-
try {
|
|
140
|
-
if (process.env.EMA_MCP_DEBUG_LOGS !== "1") {
|
|
141
|
-
// disabled
|
|
142
|
-
}
|
|
143
|
-
else {
|
|
144
|
-
const reqId = response.headers.get("x-request-id") ?? response.headers.get("x-correlation-id") ?? response.headers.get("x-amzn-trace-id");
|
|
145
|
-
fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/sdk/client.ts:requestWithRetries:post', message: 'HTTP response', data: { env: this.env.name, method, path, status: response.status, ok: response.ok, attempt, durationMs: Date.now() - startedAt, reqId }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'pre-fix', hypothesisId: 'H1' }) }).catch(() => { });
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
catch { }
|
|
149
|
-
// #endregion agent log
|
|
150
|
-
// #region agent log
|
|
151
|
-
// H1/H2: for update_persona failures, log first part of error body (redacted/truncated).
|
|
152
|
-
try {
|
|
153
|
-
if (process.env.EMA_MCP_DEBUG_LOGS !== "1") {
|
|
154
|
-
// disabled
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
const isUpdatePersona = path.includes("/api/personas/update_persona");
|
|
158
|
-
if (isUpdatePersona && !response.ok) {
|
|
159
|
-
const cloned = response.clone();
|
|
160
|
-
const txt = await cloned.text();
|
|
161
|
-
const snippet = txt.length > 1200 ? `${txt.slice(0, 1200)}…(truncated)` : txt;
|
|
162
|
-
let parsedDetail;
|
|
163
|
-
let hasGweIssues = false;
|
|
164
|
-
try {
|
|
165
|
-
const j = JSON.parse(txt);
|
|
166
|
-
const detail = j.detail ?? j.message ?? j.error;
|
|
167
|
-
parsedDetail = typeof detail === "string" ? detail : undefined;
|
|
168
|
-
hasGweIssues = Array.isArray(j.gwe_issues);
|
|
169
|
-
}
|
|
170
|
-
catch { }
|
|
171
|
-
fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/sdk/client.ts:requestWithRetries:errorBody', message: 'HTTP error body (update_persona)', data: { env: this.env.name, method, path, status: response.status, parsedDetail, hasGweIssues, bodySnippet: snippet }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'pre-fix', hypothesisId: 'H2' }) }).catch(() => { });
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
catch { }
|
|
176
|
-
// #endregion agent log
|
|
177
119
|
// Handle 401 - attempt token refresh once
|
|
178
120
|
if (response.status === 401 && !hasAttemptedRefresh && this.tokenRefreshConfig?.refreshCallback) {
|
|
179
121
|
hasAttemptedRefresh = true;
|
|
@@ -324,15 +266,6 @@ export class EmaClient {
|
|
|
324
266
|
});
|
|
325
267
|
if (!resp.ok) {
|
|
326
268
|
const body = await resp.text();
|
|
327
|
-
// #region agent log
|
|
328
|
-
try {
|
|
329
|
-
if (this.debugLogsEnabled) {
|
|
330
|
-
const snippet = body.length > 2000 ? `${body.slice(0, 2000)}…(truncated)` : body;
|
|
331
|
-
fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/sdk/client.ts:checkWorkflow:error', message: 'CheckWorkflow failed', data: { env: this.env.name, status: resp.status, bodySnippet: snippet }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'pre-fix', hypothesisId: 'H5' }) }).catch(() => { });
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
catch { }
|
|
335
|
-
// #endregion agent log
|
|
336
269
|
throw new EmaApiError({
|
|
337
270
|
statusCode: resp.status,
|
|
338
271
|
body,
|
|
@@ -340,35 +273,70 @@ export class EmaClient {
|
|
|
340
273
|
});
|
|
341
274
|
}
|
|
342
275
|
const data = (await resp.json());
|
|
343
|
-
// #region agent log
|
|
344
|
-
try {
|
|
345
|
-
if (this.debugLogsEnabled) {
|
|
346
|
-
const keys = Object.keys(data);
|
|
347
|
-
fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/sdk/client.ts:checkWorkflow:ok', message: 'CheckWorkflow ok', data: { env: this.env.name, keys }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'pre-fix', hypothesisId: 'H5' }) }).catch(() => { });
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
catch { }
|
|
351
|
-
// #endregion agent log
|
|
352
276
|
return data;
|
|
353
277
|
}
|
|
354
278
|
async updateAiEmployee(req, opts) {
|
|
355
279
|
// Use /api/personas/update_persona - same endpoint as Ema frontend
|
|
280
|
+
// Debug logging for troubleshooting
|
|
281
|
+
if (opts?.verbose || process.env.EMA_DEBUG) {
|
|
282
|
+
console.error("[EmaClient] updateAiEmployee request:", JSON.stringify({
|
|
283
|
+
persona_id: req.persona_id,
|
|
284
|
+
has_workflow: !!req.workflow,
|
|
285
|
+
workflow_actions: req.workflow ? req.workflow.actions : undefined,
|
|
286
|
+
has_proto_config: !!req.proto_config,
|
|
287
|
+
}, null, 2));
|
|
288
|
+
}
|
|
356
289
|
const resp = await this.requestWithRetries("POST", "/api/personas/update_persona", {
|
|
357
290
|
json: req,
|
|
358
291
|
});
|
|
359
292
|
if (!resp.ok) {
|
|
360
293
|
const body = await resp.text();
|
|
294
|
+
// Debug logging for errors
|
|
295
|
+
if (opts?.verbose || process.env.EMA_DEBUG) {
|
|
296
|
+
console.error("[EmaClient] updateAiEmployee FAILED:", resp.status, body);
|
|
297
|
+
console.error("[EmaClient] Request that failed:", JSON.stringify(req, null, 2).slice(0, 5000));
|
|
298
|
+
}
|
|
361
299
|
// Try to parse error body for better error messages
|
|
362
300
|
let errorDetail = "";
|
|
363
301
|
try {
|
|
364
302
|
const errorJson = JSON.parse(body);
|
|
365
303
|
// Ema API returns InvalidPersonaResponse with 'detail' field
|
|
366
304
|
errorDetail = errorJson.detail ?? errorJson.message ?? errorJson.error ?? "";
|
|
367
|
-
//
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
305
|
+
// Parse GWE (workflow) issues - it's an object, not an array
|
|
306
|
+
// See WorkflowIssues schema in OpenAPI spec
|
|
307
|
+
if (errorJson.gwe_issues && typeof errorJson.gwe_issues === "object") {
|
|
308
|
+
const gwe = errorJson.gwe_issues;
|
|
309
|
+
const issuesParts = [];
|
|
310
|
+
if (gwe.detail) {
|
|
311
|
+
issuesParts.push(`Detail: ${gwe.detail}`);
|
|
312
|
+
}
|
|
313
|
+
if (Array.isArray(gwe.missing_widgets) && gwe.missing_widgets.length > 0) {
|
|
314
|
+
issuesParts.push(`Missing widgets: ${gwe.missing_widgets.join(", ")}`);
|
|
315
|
+
}
|
|
316
|
+
if (Array.isArray(gwe.unready_widgets) && gwe.unready_widgets.length > 0) {
|
|
317
|
+
const unready = gwe.unready_widgets.map((w) => `${w.widget_name}: ${w.message}`);
|
|
318
|
+
issuesParts.push(`Unready widgets: ${unready.join("; ")}`);
|
|
319
|
+
}
|
|
320
|
+
if (Array.isArray(gwe.missing_inputs) && gwe.missing_inputs.length > 0) {
|
|
321
|
+
const missing = gwe.missing_inputs.map((i) => `${i.action_name}.${i.input_name}`);
|
|
322
|
+
issuesParts.push(`Missing inputs: ${missing.join(", ")}`);
|
|
323
|
+
}
|
|
324
|
+
if (Array.isArray(gwe.mismatched_inputs) && gwe.mismatched_inputs.length > 0) {
|
|
325
|
+
const mismatched = gwe.mismatched_inputs.map((i) => `${i.action_name}.${i.input_name}`);
|
|
326
|
+
issuesParts.push(`Mismatched inputs: ${mismatched.join(", ")}`);
|
|
327
|
+
}
|
|
328
|
+
if (Array.isArray(gwe.named_result_errors) && gwe.named_result_errors.length > 0) {
|
|
329
|
+
const resultErrs = gwe.named_result_errors.map((e) => `${e.named_result_key}: ${e.error_description}`);
|
|
330
|
+
issuesParts.push(`Result errors: ${resultErrs.join("; ")}`);
|
|
331
|
+
}
|
|
332
|
+
if (gwe.has_cycles) {
|
|
333
|
+
issuesParts.push("Workflow has cycles");
|
|
334
|
+
}
|
|
335
|
+
if (gwe.has_no_outputs) {
|
|
336
|
+
issuesParts.push("Workflow has no outputs");
|
|
337
|
+
}
|
|
338
|
+
if (issuesParts.length > 0) {
|
|
339
|
+
errorDetail += ` [GWE Issues: ${issuesParts.join(" | ")}]`;
|
|
372
340
|
}
|
|
373
341
|
}
|
|
374
342
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-generated TypeScript types from Ema Platform OpenAPI spec.
|
|
3
|
+
* Generated from: Ema Platform API v1.0.0
|
|
4
|
+
* Generated at: 2026-01-16T13:46:50.729Z
|
|
5
|
+
*
|
|
6
|
+
* DO NOT EDIT MANUALLY - regenerate with: npm run generate:types
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
10
|
+
// API Operation Types
|
|
11
|
+
// ─────────────────────────────────────────────────────────────────────────────
|