@ema.co/mcp-toolkit 1.5.1 → 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.

@@ -46,204 +46,244 @@ export function generateConsolidatedTools(envNames, defaultEnv) {
46
46
  inputSchema: { type: "object", properties: {}, required: [] },
47
47
  },
48
48
  // ═══════════════════════════════════════════════════════════════════════
49
- // 2. PERSONA - AI Employee management (CRUD + compare + versioning)
49
+ // 2. PERSONA - Unified AI Employee management (create, modify, analyze, list)
50
50
  // ═══════════════════════════════════════════════════════════════════════
51
51
  {
52
52
  name: "persona",
53
- description: `Unified AI Employee (persona) management.
53
+ description: `Create, modify, analyze, or list AI Employees. ONE tool for everything.
54
54
 
55
- **Get single** (default when id provided):
56
- persona(id="IT Support Bot")
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
- **List/Search** (when --all or filters used):
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
- **Create**:
65
- persona(mode="create", name="New Bot", type="voice")
108
+ ## Simple vs Complex Workflows
109
+
110
+ **Simple** (Q&A, search + respond): Deploys directly → \`status: "success"\`
66
111
 
67
- **Update**:
68
- persona(id="abc-123", mode="update", name="Renamed")
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
69
116
 
70
- **Compare**:
71
- persona(id="abc-123", mode="compare", compare_to="def-456")
117
+ For complex workflows, send the prompt to an LLM and deploy:
118
+ \`persona(workflow_def=<llm_response>, ...)\`
72
119
 
73
- **Templates** (list available templates):
74
- persona(templates=true)
120
+ ## Key Rules
75
121
 
76
- **Version Management** (track configuration history):
77
- persona(id="abc-123", mode="version_create", message="Before major update")
78
- persona(id="abc-123", mode="version_list")
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
- // ID (or exact name) (optional - if omitted with all=true, lists all)
126
+ // === IDENTITY ===
85
127
  id: {
86
128
  type: "string",
87
- description: "Persona ID (UUID) or exact name. Omit for list operations."
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. Persona ID (UUID) or exact name.",
134
+ description: "DEPRECATED: use id.",
94
135
  },
95
- // Mode (defaults to "get" if id provided, "list" if all=true)
96
- mode: {
136
+ // === CREATE/MODIFY (the main way to use this tool) ===
137
+ input: {
97
138
  type: "string",
98
- enum: ["get", "list", "create", "update", "compare", "version_create", "version_list", "version_get", "version_compare", "version_restore", "version_policy"],
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
- // List/Search flags
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
- // Get flags
108
- include_workflow: { type: "boolean", description: "Include full workflow_def" },
109
- include_fingerprint: { type: "boolean", description: "Include config hash" },
110
- // Create flags
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: "Voice/chat settings (welcomeMessage, identityAndPurpose, etc.) for update"
176
+ description: "Override voice/chat settings. Usually auto-generated from input."
126
177
  },
127
- // Compare flags
128
- compare_to: { type: "string", description: "Second persona ID (for compare)" },
129
- compare_env: { type: "string", description: "Environment of compare_to persona" },
130
- // Templates flag
178
+ workflow: {
179
+ type: "object",
180
+ description: "Direct workflow JSON (advanced). Usually auto-generated."
181
+ },
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 ===
131
191
  templates: { type: "boolean", description: "List available templates" },
132
- // Version management flags
133
- version: { type: "string", description: "Version identifier (e.g., 'v3', 'latest', or UUID)" },
134
- v1: { type: "string", description: "First version for comparison (version_compare mode)" },
135
- v2: { type: "string", description: "Second version for comparison (version_compare mode)" },
136
- message: { type: "string", description: "Version message/description (version_create mode)" },
137
- auto_on_deploy: { type: "boolean", description: "Auto-create version on deploy (version_policy mode)" },
138
- auto_on_sync: { type: "boolean", description: "Auto-create version on sync (version_policy mode)" },
139
- max_versions: { type: "number", description: "Max versions to keep (version_policy mode)" },
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" },
140
205
  }),
141
206
  },
142
207
  // ═══════════════════════════════════════════════════════════════════════
143
- // 3. WORKFLOW - Unified workflow operations (greenfield & brownfield)
208
+ // 3. WORKFLOW - DEPRECATED: Use persona() instead
144
209
  // ═══════════════════════════════════════════════════════════════════════
145
210
  {
146
211
  name: "workflow",
147
- description: `Unified workflow operations. Automatically detects greenfield vs brownfield.
148
-
149
- **Greenfield** (NEW workflow - no persona_id):
150
- workflow(input="IT helpdesk with KB search")
151
- workflow(input="customer support bot", preview=false) # Generate AND deploy to new persona
152
-
153
- **Brownfield** (MODIFY existing - persona_id + input):
154
- workflow(persona_id="abc", input="add HITL before email")
155
- workflow(persona_id="abc", input="consolidate the 6 custom agents into one unified agent")
156
- workflow(persona_id="abc", input="remove the orphan nodes")
157
- workflow(persona_id="abc", input="replace the email LLM nodes with entity_extraction")
158
- workflow(persona_id="abc", input="...", preview=false) # Modify AND deploy
212
+ description: `⚠️ DEPRECATED: Use \`persona()\` instead. This tool routes to persona.
159
213
 
160
- **Analyze** (inspect - persona_id only, no input):
161
- workflow(persona_id="abc-123")
162
- workflow(persona_id="abc-123", include=["issues", "fixes"])
214
+ ## Migration Guide
163
215
 
164
- **Optimize** (auto-fix issues):
165
- workflow(persona_id="abc", optimize=true)
166
- workflow(persona_id="abc", optimize=true, preview=false) # Fix AND deploy
216
+ OLD (deprecated):
217
+ workflow(input="...", type="voice", name="Bot", preview=false)
218
+ workflow(persona_id="abc", input="add HITL")
167
219
 
168
- **Compare**:
169
- workflow(persona_id="abc", compare_to="def")
220
+ NEW (use this):
221
+ persona(input="...", type="voice", name="Bot", preview=false)
222
+ persona(id="abc", input="add HITL")
170
223
 
171
- **Generate extraction schema from metadata**:
172
- workflow(mode="extraction_schema", metadata_file="/path/to/metadata.json")
173
- workflow(mode="extraction_schema", metadata_json={...})
174
-
175
- ## Key Concepts
176
-
177
- - **preview=true** (default): Returns result without deploying. Safe for exploration.
178
- - **preview=false**: Deploys the result.
179
- - **Mode is auto-detected**: No need to specify mode - just provide what you have.
180
- - **Complex changes supported**: Consolidate, remove, replace, rewire nodes.
181
- - **Dynamic schema generation**: Parse JSON metadata to generate extraction schemas automatically.`,
224
+ The \`persona\` tool now handles everything: create, modify, analyze, list.
225
+ This \`workflow\` tool still works but shows a deprecation warning.`,
182
226
  inputSchema: withEnv({
183
- // Input - natural language description of what you want
227
+ // === REQUIRED for creating/modifying ===
184
228
  input: {
185
- anyOf: [
186
- { type: "string", description: "Natural language description" },
187
- { type: "object", description: "Workflow intent/spec object" },
188
- ],
189
- description: "What you want: 'IT helpdesk bot' (greenfield) or 'consolidate agents into one' (brownfield with persona_id)",
229
+ type: "string",
230
+ description: "Natural language description of what you want. Examples: 'Voice AI for sales development' (new) or 'add HITL before emails' (modify existing)",
190
231
  },
191
- // Target persona (if provided = brownfield, if not = greenfield)
232
+ // === REQUIRED for modifying existing ===
192
233
  persona_id: {
193
234
  type: "string",
194
- description: "Existing persona to modify. If provided with input = brownfield modification."
235
+ description: "ID of existing persona to modify or analyze. Omit for creating new personas."
195
236
  },
196
- // Preview vs Deploy (default: preview=true for safety)
237
+ // === REQUIRED for new Voice/Chat/Dashboard ===
238
+ type: {
239
+ type: "string",
240
+ enum: ["voice", "chat", "dashboard"],
241
+ description: "REQUIRED for new personas. Type of AI Employee to create."
242
+ },
243
+ // === Control deployment ===
197
244
  preview: {
198
245
  type: "boolean",
199
- description: "Preview changes without deploying. Default: true. Set false to deploy."
246
+ description: "Default: true (safe). Set false to deploy changes to Ema platform."
247
+ },
248
+ // === For new personas (greenfield with preview=false) ===
249
+ name: {
250
+ type: "string",
251
+ description: "Name for the new persona. Required when creating with preview=false."
252
+ },
253
+ description: {
254
+ type: "string",
255
+ description: "Description of what the persona does."
200
256
  },
201
- // Optimize flag - auto-fix detected issues
257
+ // === Voice/Chat settings (auto-generated, but can override) ===
258
+ proto_config: {
259
+ type: "object",
260
+ description: "Voice/chat settings. Auto-generated from input. Override specific settings like welcomeMessage, identityAndPurpose."
261
+ },
262
+ // === For analysis/optimization ===
202
263
  optimize: {
203
264
  type: "boolean",
204
- description: "Auto-fix detected issues in the workflow. Use with persona_id.",
265
+ description: "Auto-fix detected issues. Use with persona_id.",
205
266
  },
206
- // Compare target
207
- compare_to: { type: "string", description: "Second persona ID for comparison" },
208
- // Analyze options
209
267
  include: {
210
268
  type: "array",
211
269
  items: { type: "string", enum: ["issues", "connections", "fixes", "metrics"] },
212
- description: "What to include in analysis. Default: all.",
270
+ description: "What to include in analysis output.",
213
271
  },
214
- // Persona type (for greenfield)
215
- type: {
216
- type: "string",
217
- enum: ["voice", "chat", "dashboard"],
218
- description: "Persona type for new workflows (default: chat)"
219
- },
220
- // Direct workflow input (for analysis)
272
+ // === Advanced/Legacy ===
221
273
  workflow_def: {
222
274
  type: "object",
223
- description: "Workflow JSON for analysis (alternative to persona_id)"
275
+ description: "Direct workflow JSON (advanced). Usually auto-generated from input."
224
276
  },
225
- // Proto config override
226
- proto_config: { type: "object", description: "Persona config override (voice settings, etc.)" },
227
- // Legacy mode support (for backwards compatibility)
277
+ compare_to: { type: "string", description: "Second persona ID for comparison" },
228
278
  mode: {
229
279
  type: "string",
230
280
  enum: ["generate", "extend", "optimize", "analyze", "compare", "compile", "extraction_schema"],
231
- description: "DEPRECATED: Mode is auto-detected. Use explicitly for compile or extraction_schema mode."
232
- },
233
- // Extraction schema generation (dynamic, no hardcoding)
234
- metadata_file: {
235
- type: "string",
236
- description: "Path to JSON metadata file for extraction schema generation"
281
+ description: "Usually auto-detected. Only specify for extraction_schema or compile modes."
237
282
  },
238
- metadata_json: {
239
- type: "object",
240
- description: "JSON metadata object (alternative to metadata_file) - dynamically parsed to generate extraction fields"
241
- },
242
- // Compile mode inputs (explicit node spec)
243
- name: { type: "string", description: "Workflow name (for compile mode)" },
244
- description: { type: "string", description: "Workflow description (for compile mode)" },
245
- nodes: { type: "array", description: "Node definitions (for compile mode)" },
246
- result_mappings: { type: "array", description: "Output mappings (for compile mode)" },
283
+ metadata_file: { type: "string", description: "For extraction_schema mode: path to JSON metadata" },
284
+ metadata_json: { type: "object", description: "For extraction_schema mode: inline JSON metadata" },
285
+ nodes: { type: "array", description: "For compile mode: node definitions" },
286
+ result_mappings: { type: "array", description: "For compile mode: output mappings" },
247
287
  }),
248
288
  },
249
289
  // ═══════════════════════════════════════════════════════════════════════
@@ -301,23 +341,24 @@ export function generateConsolidatedTools(envNames, defaultEnv) {
301
341
  // ═══════════════════════════════════════════════════════════════════════
302
342
  {
303
343
  name: "template",
304
- description: `Get workflow patterns, widget references, and configuration templates.
344
+ description: `Get qualifying questions and reference patterns.
345
+
346
+ ## 🎯 PRIMARY USE: Get Questions to Ask User
305
347
 
306
- **Workflow patterns**:
307
- template(pattern="intent-routing")
308
- template(patterns=true)
309
- template(patterns=true, type="voice")
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
310
351
 
311
- **Widget reference**:
312
- template(widgets="voice")
313
- template(widgets="chat")
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.
314
354
 
315
- **Persona config template**:
316
- template(config="voice") # Voice AI settings template
355
+ ## Reference (understand options, not for manual building)
317
356
 
318
- **Qualifying questions** (for requirements gathering):
319
- template(questions=true)
320
- template(questions=true, category="Voice")`,
357
+ template(pattern="intent-routing") // See pattern structure
358
+ template(patterns=true) // List available patterns
359
+ template(widgets="voice") // Widget reference
360
+
361
+ ⚠️ Do NOT copy/paste configs from these - MCP generates them internally.`,
321
362
  inputSchema: {
322
363
  type: "object",
323
364
  properties: {
@@ -536,7 +577,17 @@ export function generateConsolidatedTools(envNames, defaultEnv) {
536
577
  // ═══════════════════════════════════════════════════════════════════════
537
578
  {
538
579
  name: "demo",
539
- description: `Manage demo data for RAG knowledge bases.
580
+ description: `Manage demo data and generate demo kits for reliable demonstrations.
581
+
582
+ **Generate demo kit** (complete demo package for a persona):
583
+ demo(mode="kit", persona_id="...", scenario="sales-sdr")
584
+ demo(mode="kit", persona_id="...", scenario="support-tier1", custom_qa=[{q:"...", a:"..."}])
585
+
586
+ **Validate demo readiness**:
587
+ demo(mode="validate_kit", persona_id="...")
588
+
589
+ **List available scenarios**:
590
+ demo(mode="scenarios")
540
591
 
541
592
  **Consolidate** (join JSON → Markdown):
542
593
  demo(mode="consolidate", source="./data", output="./kb", entity="customer", primary="customers.json")
@@ -547,16 +598,27 @@ export function generateConsolidatedTools(envNames, defaultEnv) {
547
598
  **Validate document**:
548
599
  demo(mode="validate", file="./kb/customer-acme.md")
549
600
 
550
- **Get template**:
601
+ **Get entity template**:
551
602
  demo(mode="template", entity="customer")`,
552
603
  inputSchema: {
553
604
  type: "object",
554
605
  properties: {
555
606
  mode: {
556
607
  type: "string",
557
- enum: ["consolidate", "generate", "validate", "template"],
608
+ enum: ["kit", "validate_kit", "scenarios", "consolidate", "generate", "validate", "template"],
558
609
  description: "Operation"
559
610
  },
611
+ // Kit generation flags
612
+ persona_id: { type: "string", description: "Persona ID for kit generation" },
613
+ scenario: {
614
+ type: "string",
615
+ enum: ["sales-sdr", "support-tier1", "hr-assistant"],
616
+ description: "Demo scenario template"
617
+ },
618
+ custom_qa: {
619
+ type: "array",
620
+ description: "Custom Q&A pairs [{question, answer}]"
621
+ },
560
622
  // Consolidate flags
561
623
  source: { type: "string", description: "Source directory" },
562
624
  output: { type: "string", description: "Output directory" },
@@ -568,7 +630,7 @@ export function generateConsolidatedTools(envNames, defaultEnv) {
568
630
  // Entity type
569
631
  entity: {
570
632
  type: "string",
571
- enum: ["customer", "product", "employee", "scenario", "reference"],
633
+ enum: ["customer", "product", "employee", "faq", "policy", "benefit", "company"],
572
634
  description: "Entity type"
573
635
  },
574
636
  // Generate flags
@@ -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
+ }