@ema.co/mcp-toolkit 2026.2.27 → 2026.2.28

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 (58) hide show
  1. package/.context/public/guides/ema-user-guide.md +7 -6
  2. package/.context/public/guides/mcp-tools-guide.md +46 -23
  3. package/dist/config/index.js +11 -0
  4. package/dist/config/workflow-patterns.js +361 -0
  5. package/dist/mcp/autobuilder.js +2 -2
  6. package/dist/mcp/domain/generation-schema.js +15 -9
  7. package/dist/mcp/domain/structural-rules.js +3 -3
  8. package/dist/mcp/domain/validation-rules.js +20 -27
  9. package/dist/mcp/domain/workflow-generator.js +3 -3
  10. package/dist/mcp/domain/workflow-graph.js +1 -1
  11. package/dist/mcp/guidance.js +60 -1
  12. package/dist/mcp/handlers/conversation/adapter.js +13 -0
  13. package/dist/mcp/handlers/conversation/create.js +19 -0
  14. package/dist/mcp/handlers/conversation/delete.js +18 -0
  15. package/dist/mcp/handlers/conversation/formatters.js +62 -0
  16. package/dist/mcp/handlers/conversation/history.js +15 -0
  17. package/dist/mcp/handlers/conversation/index.js +43 -0
  18. package/dist/mcp/handlers/conversation/list.js +40 -0
  19. package/dist/mcp/handlers/conversation/messages.js +13 -0
  20. package/dist/mcp/handlers/conversation/rename.js +16 -0
  21. package/dist/mcp/handlers/conversation/send.js +90 -0
  22. package/dist/mcp/handlers/data/index.js +169 -3
  23. package/dist/mcp/handlers/feedback/client-id.js +49 -0
  24. package/dist/mcp/handlers/feedback/coalesce.js +167 -0
  25. package/dist/mcp/handlers/feedback/index.js +42 -1
  26. package/dist/mcp/handlers/feedback/outbox.js +301 -0
  27. package/dist/mcp/handlers/feedback/probes.js +127 -0
  28. package/dist/mcp/handlers/feedback/remote-store.js +59 -0
  29. package/dist/mcp/handlers/feedback/store.js +13 -1
  30. package/dist/mcp/handlers/persona/delete.js +7 -28
  31. package/dist/mcp/handlers/persona/update.js +7 -26
  32. package/dist/mcp/handlers/persona/version.js +30 -15
  33. package/dist/mcp/handlers/template/adapter.js +23 -0
  34. package/dist/mcp/handlers/template/crud.js +174 -0
  35. package/dist/mcp/handlers/template/index.js +6 -7
  36. package/dist/mcp/handlers/workflow/adapter.js +30 -46
  37. package/dist/mcp/handlers/workflow/index.js +2 -2
  38. package/dist/mcp/handlers/workflow/validation.js +2 -2
  39. package/dist/mcp/knowledge-guidance-topics.js +90 -53
  40. package/dist/mcp/knowledge.js +7 -357
  41. package/dist/mcp/prompts.js +5 -5
  42. package/dist/mcp/resources-dynamic.js +46 -38
  43. package/dist/mcp/resources-validation.js +5 -5
  44. package/dist/mcp/server.js +38 -5
  45. package/dist/mcp/tools.js +340 -8
  46. package/dist/sdk/client-adapter.js +90 -2
  47. package/dist/sdk/client.js +7 -0
  48. package/dist/sdk/ema-client.js +242 -27
  49. package/dist/sdk/generated/agent-catalog.js +96 -39
  50. package/dist/sdk/generated/deprecated-actions.js +1 -1
  51. package/dist/sdk/grpc-client.js +67 -5
  52. package/dist/sync/central-factory.js +86 -0
  53. package/dist/sync/central-version-storage.js +387 -0
  54. package/dist/sync/dis-port.js +75 -0
  55. package/dist/sync/version-policy.js +29 -31
  56. package/dist/sync/version-storage-interface.js +11 -0
  57. package/dist/sync/version-storage.js +22 -22
  58. package/package.json +2 -1
@@ -1,10 +1,18 @@
1
1
  /**
2
- * Agent Catalog — Auto-Generated API Fallback
2
+ * Agent Catalog — API Fallback + Enrichments
3
3
  *
4
- * This is the AGENT_CATALOG array extracted from knowledge.ts.
5
- * Used as a fallback when the API is unavailable.
4
+ * SINGLE SOURCE OF TRUTH for action definitions used when API is unavailable.
5
+ * Enriched with `tier` and `aliases` for guidance quality.
6
+ *
7
+ * Tier definitions:
8
+ * - core: Production-proven, actively recommended
9
+ * - standard: Available, use when intent calls for it
10
+ * - specialized: Domain-specific verticals
11
+ * - experimental: Minimal/zero production usage
12
+ *
13
+ * IMPORTANT: respond_for_external_actions is ONLY for external_action_caller results.
14
+ * For KB Q&A after search, use call_llm with named_inputs (89% of production personas).
6
15
  *
7
- * Generated from source repos via scheduled workflows.
8
16
  * See: .context/core/guides/source-repos.md for full architecture
9
17
  */
10
18
  export const AGENT_CATALOG = [
@@ -24,6 +32,8 @@ export const AGENT_CATALOG = [
24
32
  "Each user message triggers a NEW workflow execution",
25
33
  "chat_conversation accumulates; user_query is current message only",
26
34
  ],
35
+ tier: "core",
36
+ aliases: ["Chat Entry Point"],
27
37
  },
28
38
  {
29
39
  actionName: "document_trigger",
@@ -35,6 +45,8 @@ export const AGENT_CATALOG = [
35
45
  { name: "user_query", type: "WELL_KNOWN_TYPE_DOCUMENT", description: "Uploaded document(s)" },
36
46
  ],
37
47
  whenToUse: "When workflow is triggered by document upload for processing/extraction",
48
+ tier: "core",
49
+ aliases: ["Document Entry Point", "Upload Trigger"],
38
50
  },
39
51
  {
40
52
  actionName: "voice_trigger",
@@ -47,6 +59,8 @@ export const AGENT_CATALOG = [
47
59
  { name: "user_query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Current utterance" },
48
60
  ],
49
61
  whenToUse: "Voice AI employees handling phone calls",
62
+ tier: "core",
63
+ aliases: ["Voice Entry Point", "Phone Call Trigger"],
50
64
  },
51
65
  // Routing / Classification
52
66
  {
@@ -71,6 +85,8 @@ export const AGENT_CATALOG = [
71
85
  whenToUse: "When you need to route chat conversations to different processing paths based on intent",
72
86
  whenNotToUse: "Simple single-path workflows that don't need routing",
73
87
  example: "Categories: [Market_Impact, Client_Lookup, Compliance_Check, Fallback]",
88
+ tier: "core",
89
+ aliases: ["Intent Router", "Conversation Classifier"],
74
90
  },
75
91
  {
76
92
  actionName: "text_categorizer",
@@ -87,6 +103,8 @@ export const AGENT_CATALOG = [
87
103
  "Same rules as chat_categorizer: must have Fallback, edges for each category",
88
104
  ],
89
105
  whenToUse: "When routing based on text content rather than full conversation history",
106
+ tier: "standard",
107
+ aliases: ["Text Router"],
90
108
  },
91
109
  {
92
110
  actionName: "document_categorizer",
@@ -100,6 +118,8 @@ export const AGENT_CATALOG = [
100
118
  { name: "category", type: "WELL_KNOWN_TYPE_ENUM", description: "Document classification" },
101
119
  ],
102
120
  whenToUse: "When routing based on document type or content",
121
+ tier: "standard",
122
+ aliases: ["Document Router"],
103
123
  },
104
124
  {
105
125
  actionName: "conversation_to_search_query",
@@ -118,6 +138,8 @@ export const AGENT_CATALOG = [
118
138
  "May still be needed WITH chat_conversation for downstream agents requiring specific format",
119
139
  "Use to reduce conversation size due to LLM context window limits",
120
140
  ],
141
+ tier: "standard",
142
+ aliases: ["Query Refiner", "Conversation to Query"],
121
143
  },
122
144
  // Search & Retrieval
123
145
  {
@@ -135,6 +157,8 @@ export const AGENT_CATALOG = [
135
157
  whenToUse: "When searching static uploaded documents or internal knowledge base. Always use search/v2 (not deprecated search/v0).",
136
158
  whenNotToUse: "For real-time web content — use live_web_search or ai_web_search instead",
137
159
  example: "FAQ lookup, policy search, documentation assistant",
160
+ tier: "core",
161
+ aliases: ["Knowledge Search", "KB Lookup", "File Search"],
138
162
  },
139
163
  {
140
164
  actionName: "live_web_search",
@@ -149,27 +173,11 @@ export const AGENT_CATALOG = [
149
173
  ],
150
174
  whenToUse: "When you need real-time external information not in the knowledge base",
151
175
  example: "Current events, live data, external research",
176
+ tier: "standard",
177
+ aliases: ["Web Search", "Real-Time Search"],
152
178
  },
153
- {
154
- actionName: "combine_search_results",
155
- displayName: "Combine Search Results",
156
- category: "search",
157
- description: "Merges results from multiple search sources with deduplication.",
158
- inputs: [
159
- { name: "search_results_1", type: "WELL_KNOWN_TYPE_SEARCH_RESULT", required: true, description: "First result set" },
160
- { name: "search_results_2", type: "WELL_KNOWN_TYPE_SEARCH_RESULT", required: true, description: "Second result set" },
161
- ],
162
- outputs: [
163
- { name: "combined_results", type: "WELL_KNOWN_TYPE_SEARCH_RESULT", description: "Merged, deduplicated results" },
164
- ],
165
- criticalRules: [
166
- "AVOID using unless necessary - prefer combine_and_rerank_search_results or call_llm with named_inputs",
167
- "combine_and_rerank_search_results provides intelligent relevance ranking",
168
- "call_llm with named_inputs allows prompt-based ranking control",
169
- ],
170
- whenToUse: "When combining local + web search, or multiple knowledge bases",
171
- whenNotToUse: "When you need intelligent ranking - use combine_and_rerank_search_results instead",
172
- },
179
+ // combine_search_results REMOVED — does not exist in the live API.
180
+ // Use combine_and_rerank_search_results instead.
173
181
  {
174
182
  actionName: "combine_and_rerank_search_results",
175
183
  displayName: "Rerank Search Results",
@@ -183,6 +191,8 @@ export const AGENT_CATALOG = [
183
191
  { name: "reranked_results", type: "WELL_KNOWN_TYPE_SEARCH_RESULT", description: "Reranked results" },
184
192
  ],
185
193
  whenToUse: "When you need to prioritize results by relevance after combining multiple sources",
194
+ tier: "standard",
195
+ aliases: ["Rerank Results", "Result Prioritizer"],
186
196
  },
187
197
  {
188
198
  actionName: "document_metasearch",
@@ -197,6 +207,8 @@ export const AGENT_CATALOG = [
197
207
  { name: "document_metasearch_results", type: "WELL_KNOWN_TYPE_SEARCH_RESULT", description: "Metadata search results" },
198
208
  ],
199
209
  whenToUse: "When searching by document metadata rather than content",
210
+ tier: "standard",
211
+ aliases: ["Metadata Search"],
200
212
  },
201
213
  // Generation & Response
202
214
  {
@@ -222,6 +234,8 @@ export const AGENT_CATALOG = [
222
234
  ],
223
235
  example: "named_inputs_Market_Context, named_inputs_Client_Data | " +
224
236
  "LLM Template prompt: 'Generate structured content with clear ## section headers, organized by themes'",
237
+ tier: "core",
238
+ aliases: ["Respond", "LLM Response", "Generate Text"],
225
239
  },
226
240
  {
227
241
  actionName: "generate_document",
@@ -243,6 +257,8 @@ export const AGENT_CATALOG = [
243
257
  "Optional: Add CSS/styling in markdown for professional formatting",
244
258
  ],
245
259
  example: "detailed_content (call_llm) → generate_doc → send_email.named_inputs_Attachment",
260
+ tier: "standard",
261
+ aliases: ["Publish Document", "Create PDF"],
246
262
  },
247
263
  {
248
264
  actionName: "respond_with_sources",
@@ -261,6 +277,8 @@ export const AGENT_CATALOG = [
261
277
  "Enable use_citation_based_filtering for trust",
262
278
  "Implement confidence thresholds for quality control",
263
279
  ],
280
+ tier: "standard",
281
+ aliases: ["Answer with Citations", "Grounded Response"],
264
282
  },
265
283
  {
266
284
  actionName: "respond_for_external_actions",
@@ -275,6 +293,8 @@ export const AGENT_CATALOG = [
275
293
  { name: "response", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Human-friendly explanation" },
276
294
  ],
277
295
  whenToUse: "After external_action_caller to explain results to user",
296
+ tier: "standard",
297
+ aliases: ["Explain Tool Results"],
278
298
  },
279
299
  {
280
300
  actionName: "fixed_response",
@@ -299,6 +319,8 @@ export const AGENT_CATALOG = [
299
319
  ],
300
320
  example: "Template: 'Dear {{Customer_Name}}, your order {{Order_ID}} has been {{Status}}.' " +
301
321
  "→ Connect entity_extraction.customer_name to named_inputs_Customer_Name",
322
+ tier: "core",
323
+ aliases: ["Template Response", "Static Response"],
302
324
  },
303
325
  {
304
326
  actionName: "send_email_agent",
@@ -322,6 +344,8 @@ export const AGENT_CATALOG = [
322
344
  "Never connect summarized_conversation or search_results directly to email_to",
323
345
  ],
324
346
  example: "entity_extraction → fixed_response('{{email}}') → send_email.email_to (CORRECT pattern)",
347
+ tier: "core",
348
+ aliases: ["Email Sender"],
325
349
  },
326
350
  // External Actions
327
351
  {
@@ -342,6 +366,8 @@ export const AGENT_CATALOG = [
342
366
  "external_action_caller does NOT support HITL — only send_email_agent and entity_extraction_with_documents do",
343
367
  ],
344
368
  example: "ServiceNow ticket creation, Salesforce record update, Email sending",
369
+ tier: "core",
370
+ aliases: ["Call API", "Tool Caller", "Intelligent Actions"],
345
371
  },
346
372
  // Entity & Rule Processing
347
373
  {
@@ -362,6 +388,8 @@ export const AGENT_CATALOG = [
362
388
  ],
363
389
  whenToUse: "When you need structured data extraction for cross-document linking, API calls, or audit trails with citations",
364
390
  whenNotToUse: "For simple conversational queries—use LLM resolution instead",
391
+ tier: "core",
392
+ aliases: ["Extract Fields", "Data Extraction"],
365
393
  },
366
394
  {
367
395
  actionName: "rule_validation_with_documents",
@@ -376,31 +404,38 @@ export const AGENT_CATALOG = [
376
404
  { name: "ruleset_output", type: "WELL_KNOWN_TYPE_ANY", description: "Pass/Fail results per rule with explanations" },
377
405
  ],
378
406
  whenToUse: "For compliance checks, business rule validation, threshold checking. Rules (e.g., 'Required Fields', 'Data Variance') are defined in the node's UI settings — the LLM only needs to wire inputs/outputs correctly.",
407
+ tier: "standard",
408
+ aliases: ["Business Rules", "Compliance Checker"],
379
409
  },
380
410
  // Human Collaboration
381
- // NOTE: general_hitl is NOT a deployable node — it appears in catalogs but cannot be deployed.
382
- // HITL is implemented as a FLAG on specific agents: entity_extraction_with_documents and send_email_agent.
383
- // Kept here for reference when analyzing existing workflows that may use it.
384
411
  {
385
412
  actionName: "general_hitl",
386
- displayName: "Human Collaboration (NOT RECOMMENDED — use agent flags instead)",
413
+ displayName: "Human Collaboration Agent",
387
414
  category: "collaboration",
388
- description: "NOT DEPLOYABLE general_hitl appears in catalogs but cannot be deployed. HITL approval is a flag on entity_extraction_with_documents and send_email_agent only. Do NOT add general_hitl nodes to workflows.",
415
+ description: "Pauses workflow to collect human input, then resumes. Supports three modes: Conversational (back-and-forth dialogue with success/failure criteria), Standard Form (structured fields: string, number, boolean, multi-select), and Custom Form (external hosted form via URL). Uses intelligent categorization to detect success, failure, or need for continued conversation.",
389
416
  inputs: [
390
- { name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Context for review" },
417
+ { name: "conversation", type: "WELL_KNOWN_TYPE_CHAT_CONVERSATION", required: true, description: "Chat conversation for ongoing dialogue context" },
418
+ { name: "named_inputs", type: "WELL_KNOWN_TYPE_ANY", required: false, description: "Flexible inputs from other agents (search results, tool outputs, text, structured data)" },
391
419
  ],
392
420
  outputs: [
393
- { name: "hitl_status_HITL Success", type: "WELL_KNOWN_TYPE_ANY", description: "Human approved path" },
394
- { name: "hitl_status_HITL Failure", type: "WELL_KNOWN_TYPE_ANY", description: "Human rejected path" },
421
+ { name: "human_collaboration_status", type: "WELL_KNOWN_TYPE_ENUM", description: "HITL Success or HITL Failure — use with runIf for branching" },
422
+ { name: "summarized_conversation", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Summary of the full conversation history" },
395
423
  ],
396
424
  criticalRules: [
397
- "general_hitl is NOT deployable do NOT add it to any workflow",
398
- "If user asks for approval: enable the HITL flag on entity_extraction_with_documents or send_email_agent (only nodes that support HITL)",
399
- "external_action_caller does NOT support HITL despite appearing in older guidance",
400
- "If you encounter general_hitl in an existing workflow: it may not function correctly",
401
- ],
402
- whenToUse: "DO NOT USE general_hitl is not deployable. HITL is a flag on entity_extraction_with_documents and send_email_agent only.",
403
- example: "For approval: enable HITL flag on send_email_agent (disable_human_interaction: false)",
425
+ "Three modes: Conversational (dialogue), Standard Form (structured input), Custom Form (external URL)",
426
+ "Conversational mode: define success criteria, failure criteria, and instructions for Ema's behavior",
427
+ "Standard Form mode: define fields (string, number, boolean, multi-select). Output is JSON of submitted values",
428
+ "Custom Form mode: requires form URL, button label, and display dimensions",
429
+ "Max exchanges setting prevents infinite loops — defaults to 5. Exceeding max = HITL Failure",
430
+ "Context window controls how many recent messages the categorizer considers (default: 10)",
431
+ "Use runIf conditions on downstream nodes to branch on HITL Success vs HITL Failure",
432
+ "entity_extraction_with_documents and send_email_agent also support HITL via their own flags (disable_human_interaction: false)",
433
+ ],
434
+ whenToUse: "Content approval workflows, quality assurance checkpoints, interactive data collection, approval workflows for business decisions",
435
+ whenNotToUse: "Simple yes/no approval on email sends — use send_email_agent's built-in HITL flag instead",
436
+ example: "Content approval: success='User approves content', instructions='Present content and ask for feedback', max_exchanges=3",
437
+ tier: "core",
438
+ aliases: ["HITL", "Human Review", "Approval Gate", "Human-in-the-Loop"],
404
439
  },
405
440
  // Validation & Guardrails
406
441
  {
@@ -416,6 +451,8 @@ export const AGENT_CATALOG = [
416
451
  { name: "abstain_reason", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Reason if validation fails" },
417
452
  ],
418
453
  whenToUse: "For guardrails, compliance checking on generated responses, quality control",
454
+ tier: "experimental",
455
+ aliases: ["Guardrail", "Response Checker"],
419
456
  },
420
457
  {
421
458
  actionName: "abstain_action",
@@ -429,6 +466,7 @@ export const AGENT_CATALOG = [
429
466
  { name: "abstain_reason", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Abstention message" },
430
467
  ],
431
468
  whenToUse: "When the AI should explicitly decline to answer or redirect",
469
+ tier: "experimental",
432
470
  },
433
471
  // Sentiment & Analysis
434
472
  {
@@ -443,6 +481,7 @@ export const AGENT_CATALOG = [
443
481
  { name: "sentiment", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Sentiment analysis" },
444
482
  ],
445
483
  whenToUse: "When you need to gauge user emotion for routing or response tone adjustment",
484
+ tier: "standard",
446
485
  },
447
486
  // Domain Agents - Finance
448
487
  {
@@ -453,6 +492,7 @@ export const AGENT_CATALOG = [
453
492
  inputs: [{ name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Risk query" }],
454
493
  outputs: [{ name: "risk_assessment", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Risk analysis" }],
455
494
  whenToUse: "Financial services: portfolio risk, credit assessment, exposure analysis",
495
+ tier: "specialized",
456
496
  },
457
497
  {
458
498
  actionName: "financial_statement_analyzer",
@@ -462,6 +502,7 @@ export const AGENT_CATALOG = [
462
502
  inputs: [{ name: "documents", type: "WELL_KNOWN_TYPE_DOCUMENT", required: true, description: "Financial documents" }],
463
503
  outputs: [{ name: "analysis", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Financial analysis" }],
464
504
  whenToUse: "Analyzing P&L, balance sheets, annual reports",
505
+ tier: "specialized",
465
506
  },
466
507
  {
467
508
  actionName: "tax_advisor",
@@ -471,6 +512,7 @@ export const AGENT_CATALOG = [
471
512
  inputs: [{ name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Tax question" }],
472
513
  outputs: [{ name: "tax_advice", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Tax guidance" }],
473
514
  whenToUse: "Tax planning, compliance questions, deduction analysis",
515
+ tier: "specialized",
474
516
  },
475
517
  {
476
518
  actionName: "audit_evidence_verifier",
@@ -480,6 +522,7 @@ export const AGENT_CATALOG = [
480
522
  inputs: [{ name: "documents", type: "WELL_KNOWN_TYPE_DOCUMENT", required: true, description: "Audit documents" }],
481
523
  outputs: [{ name: "verification", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Verification results" }],
482
524
  whenToUse: "Audit support, evidence verification, compliance documentation",
525
+ tier: "specialized",
483
526
  },
484
527
  // Domain Agents - Healthcare
485
528
  {
@@ -490,6 +533,7 @@ export const AGENT_CATALOG = [
490
533
  inputs: [{ name: "documents", type: "WELL_KNOWN_TYPE_DOCUMENT", required: true, description: "Medical records" }],
491
534
  outputs: [{ name: "summary", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Record summary" }],
492
535
  whenToUse: "Healthcare: summarizing patient records, medical history, clinical notes",
536
+ tier: "specialized",
493
537
  },
494
538
  {
495
539
  actionName: "medical_research_assistant",
@@ -499,6 +543,7 @@ export const AGENT_CATALOG = [
499
543
  inputs: [{ name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Research query" }],
500
544
  outputs: [{ name: "research", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Research findings" }],
501
545
  whenToUse: "Medical research, literature review, clinical guidance",
546
+ tier: "specialized",
502
547
  },
503
548
  {
504
549
  actionName: "insurance_claim_summarizer",
@@ -508,6 +553,7 @@ export const AGENT_CATALOG = [
508
553
  inputs: [{ name: "documents", type: "WELL_KNOWN_TYPE_DOCUMENT", required: true, description: "Claim documents" }],
509
554
  outputs: [{ name: "summary", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Claim summary" }],
510
555
  whenToUse: "Insurance processing, claim analysis",
556
+ tier: "specialized",
511
557
  },
512
558
  // Domain Agents - Legal
513
559
  {
@@ -518,6 +564,7 @@ export const AGENT_CATALOG = [
518
564
  inputs: [{ name: "documents", type: "WELL_KNOWN_TYPE_DOCUMENT", required: true, description: "Documents to analyze" }],
519
565
  outputs: [{ name: "compliance_findings", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Compliance analysis" }],
520
566
  whenToUse: "Regulatory compliance, contract review, policy compliance",
567
+ tier: "specialized",
521
568
  },
522
569
  {
523
570
  actionName: "legal_expert",
@@ -527,6 +574,7 @@ export const AGENT_CATALOG = [
527
574
  inputs: [{ name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Legal question" }],
528
575
  outputs: [{ name: "legal_analysis", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Legal guidance" }],
529
576
  whenToUse: "Legal research, contract analysis, regulatory questions",
577
+ tier: "specialized",
530
578
  },
531
579
  {
532
580
  actionName: "information_redacter",
@@ -536,6 +584,7 @@ export const AGENT_CATALOG = [
536
584
  inputs: [{ name: "documents", type: "WELL_KNOWN_TYPE_DOCUMENT", required: true, description: "Documents to redact" }],
537
585
  outputs: [{ name: "redacted", type: "WELL_KNOWN_TYPE_DOCUMENT", description: "Redacted documents" }],
538
586
  whenToUse: "Privacy compliance, document sanitization",
587
+ tier: "specialized",
539
588
  },
540
589
  // Domain Agents - IT/Security
541
590
  {
@@ -546,6 +595,7 @@ export const AGENT_CATALOG = [
546
595
  inputs: [{ name: "email_content", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Email to analyze" }],
547
596
  outputs: [{ name: "detection_result", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Phishing analysis" }],
548
597
  whenToUse: "Email security, security operations",
598
+ tier: "specialized",
549
599
  },
550
600
  {
551
601
  actionName: "cybersecurity_expert",
@@ -555,6 +605,7 @@ export const AGENT_CATALOG = [
555
605
  inputs: [{ name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Security query" }],
556
606
  outputs: [{ name: "security_analysis", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Security guidance" }],
557
607
  whenToUse: "Security incident analysis, vulnerability assessment",
608
+ tier: "specialized",
558
609
  },
559
610
  {
560
611
  actionName: "technical_support",
@@ -564,6 +615,7 @@ export const AGENT_CATALOG = [
564
615
  inputs: [{ name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Support query" }],
565
616
  outputs: [{ name: "support_response", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Technical guidance" }],
566
617
  whenToUse: "IT helpdesk, troubleshooting, technical guidance",
618
+ tier: "specialized",
567
619
  },
568
620
  // Domain Agents - Sales
569
621
  {
@@ -574,6 +626,7 @@ export const AGENT_CATALOG = [
574
626
  inputs: [{ name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Sales query" }],
575
627
  outputs: [{ name: "intelligence", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Sales insights" }],
576
628
  whenToUse: "Account research, competitive analysis, opportunity assessment",
629
+ tier: "specialized",
577
630
  },
578
631
  {
579
632
  actionName: "email_writer",
@@ -583,6 +636,7 @@ export const AGENT_CATALOG = [
583
636
  inputs: [{ name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Email context" }],
584
637
  outputs: [{ name: "email", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Generated email" }],
585
638
  whenToUse: "Sales outreach, customer communication, follow-ups",
639
+ tier: "specialized",
586
640
  },
587
641
  // Formatting Agents
588
642
  {
@@ -593,6 +647,7 @@ export const AGENT_CATALOG = [
593
647
  inputs: [{ name: "content", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Content to format" }],
594
648
  outputs: [{ name: "json", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "JSON formatted output" }],
595
649
  whenToUse: "When structured JSON output is required",
650
+ tier: "standard",
596
651
  },
597
652
  {
598
653
  actionName: "json_extractor",
@@ -602,6 +657,7 @@ export const AGENT_CATALOG = [
602
657
  inputs: [{ name: "content", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Content containing JSON" }],
603
658
  outputs: [{ name: "extracted_json", type: "WELL_KNOWN_TYPE_ANY", description: "Extracted JSON object" }],
604
659
  whenToUse: "When you need to parse JSON from LLM responses",
660
+ tier: "standard",
605
661
  },
606
662
  {
607
663
  actionName: "markdown_formatter",
@@ -611,5 +667,6 @@ export const AGENT_CATALOG = [
611
667
  inputs: [{ name: "content", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Content to format" }],
612
668
  outputs: [{ name: "markdown", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Markdown formatted output" }],
613
669
  whenToUse: "When rich text formatting is needed",
670
+ tier: "standard",
614
671
  },
615
672
  ];
@@ -240,7 +240,7 @@ export const DEPRECATED_ACTIONS_MAP = {
240
240
  "json_mapper/v0": { replacement: "json_mapper/v1" },
241
241
  "meta_respond/v0": {},
242
242
  "meta_respond/v2": {},
243
- "respond_with_sources/v0": { replacement: "respond_for_external_actions", notes: "Better tool result handling" },
243
+ "respond_with_sources/v0": { replacement: "call_llm with named_inputs", notes: "For KB Q&A use call_llm; respond_for_external_actions is ONLY for external_action_caller results" },
244
244
  "rule_validation_with_documents/v0": { replacement: "rule_validation_with_documents/v1" },
245
245
  "search/v0": { replacement: "search/v2", notes: "v2 requires datastore_configs input" },
246
246
  "text_categorizer/v0": { replacement: "text_categorizer/v1" },
@@ -14,9 +14,10 @@ import { createGrpcWebTransport } from '@connectrpc/connect-node';
14
14
  import { create, fromJson } from '@bufbuild/protobuf';
15
15
  // Generated service definitions (v2: services and schemas in same _pb.ts file)
16
16
  import { ActionManager, ListActionsRequestSchema, ListActionsFromWorkflowRequestSchema, GetActionRequestSchema, } from './generated/protos/service/workflows/v1/action_registry_pb.js';
17
- import { DashboardsService, GetDashboardRowsRequestSchema, GetDashboardSchemaRequestSchema, } from './generated/protos/service/workflows/v1/dashboards_pb.js';
17
+ import { DashboardsService, GetDashboardRowsRequestSchema, GetDashboardSchemaRequestSchema, ContinueWorkflowForRowRequestSchema, } from './generated/protos/service/workflows/v1/dashboards_pb.js';
18
+ import { ContinuationMessageSchema, } from './generated/protos/service/workflows/v1/coordinator_pb.js';
18
19
  import { WorkflowManager, CheckWorkflowRequestSchema, GetWorkflowRequestSchema, } from './generated/protos/service/workflows/v1/rpc/workflow_rpc_pb.js';
19
- import { DataIngestService, GetRootContentNodesRequestSchema, GetContentNodeAggregatesRequestSchema, ReplicateDataRequestSchema, GetReplicationStatusRequestSchema, FilePickerGroupType, WidgetReplicationMappingSchema, } from './generated/protos/service/dataingest/v1/dataingest_pb.js';
20
+ import { DataIngestService, GetRootContentNodesRequestSchema, GetContentNodeAggregatesRequestSchema, ReplicateDataRequestSchema, GetReplicationStatusRequestSchema, GetSignedUrlRequestSchema, DeleteFilesForGroupRequestSchema, DeletionGroupType, FilePickerGroupType, WidgetReplicationMappingSchema, } from './generated/protos/service/dataingest/v1/dataingest_pb.js';
20
21
  import { ConversationReviewService, GetConversationReviewsRequestSchema, GetConversationReviewDetailRequestSchema, ConversationReviewFiltersSchema, } from './generated/protos/service/conversation_review/v1/conversation_review_pb.js';
21
22
  import { DebugLogService, WorkflowLevelDebugLogRequestSchema, ActionLevelShowWorkLogRequestSchema, } from './generated/protos/service/persona/v1/debug_logs_pb.js';
22
23
  import { DebuggerService, SearchMessagesRequestSchema as DebuggerSearchMessagesRequestSchema, } from './generated/protos/service/debugger/service_pb.js';
@@ -163,6 +164,22 @@ export class GrpcClient {
163
164
  const request = create(GetDashboardSchemaRequestSchema, {});
164
165
  return this.dashboardsService.getDashboardSchema(request);
165
166
  }
167
+ /**
168
+ * Continue a paused (HITL) workflow for a dashboard row.
169
+ * Sends a continuation message (text, button selection, or form submission)
170
+ * to resume the workflow.
171
+ *
172
+ * @param rowId - The dashboard row ID
173
+ * @param continuationMessage - The continuation message as JSON (matches ContinuationMessage proto)
174
+ * @returns Empty response on success
175
+ */
176
+ async continueWorkflowForRow(rowId, continuationMessage) {
177
+ const request = create(ContinueWorkflowForRowRequestSchema, {
178
+ rowId,
179
+ continuationMessage: fromJson(ContinuationMessageSchema, continuationMessage),
180
+ });
181
+ return this.dashboardsService.continueWorkflowForRow(request);
182
+ }
166
183
  // ─────────────────────────────────────────────────────────────────────────────
167
184
  // WorkflowManager RPCs
168
185
  // ─────────────────────────────────────────────────────────────────────────────
@@ -218,7 +235,10 @@ export class GrpcClient {
218
235
  page: opts?.page ?? 1,
219
236
  limit: opts?.limit ?? 100,
220
237
  });
221
- return this.dataIngestService.getRootContentNodes(request);
238
+ // DataIngestService has PERSONA_REQUIRED auth — pass persona via gRPC header
239
+ return this.dataIngestService.getRootContentNodes(request, {
240
+ headers: new Headers({ 'x-persona-id': personaId }),
241
+ });
222
242
  }
223
243
  /**
224
244
  * Get content node aggregates (file counts by status).
@@ -233,7 +253,10 @@ export class GrpcClient {
233
253
  filePickerGroupId: personaId,
234
254
  filePickerGroupType: FilePickerGroupType.PERSONA,
235
255
  });
236
- return this.dataIngestService.getContentNodeAggregates(request);
256
+ // DataIngestService has PERSONA_REQUIRED auth — pass persona via gRPC header
257
+ return this.dataIngestService.getContentNodeAggregates(request, {
258
+ headers: new Headers({ 'x-persona-id': personaId }),
259
+ });
237
260
  }
238
261
  /**
239
262
  * Replicate data from one persona to another.
@@ -259,7 +282,24 @@ export class GrpcClient {
259
282
  ...(m.targetWidget ? { destinationWidgetName: m.targetWidget } : {}),
260
283
  })),
261
284
  });
262
- return this.dataIngestService.replicateData(request);
285
+ // DataIngestService has PERSONA_REQUIRED auth — use target persona for write auth
286
+ return this.dataIngestService.replicateData(request, {
287
+ headers: new Headers({ 'x-persona-id': target.id }),
288
+ });
289
+ }
290
+ /**
291
+ * Get a signed download URL for a file by its UploadedFile.Id.
292
+ *
293
+ * @param fileId - The UploadedFile.Id (from ContentNodeResponse.id, NOT contentNodeId)
294
+ * @returns Signed URL, MIME type, title, and deletion status
295
+ */
296
+ async getSignedUrl(fileId, personaId) {
297
+ const request = create(GetSignedUrlRequestSchema, { fileId });
298
+ // DataIngestService may require PERSONA_REQUIRED auth
299
+ const callOpts = personaId
300
+ ? { headers: new Headers({ 'x-persona-id': personaId }) }
301
+ : undefined;
302
+ return this.dataIngestService.getSignedUrl(request, callOpts);
263
303
  }
264
304
  /**
265
305
  * Get the status of a replication request.
@@ -273,6 +313,28 @@ export class GrpcClient {
273
313
  });
274
314
  return this.dataIngestService.getReplicationStatus(request);
275
315
  }
316
+ /**
317
+ * Delete files from a group (persona/project/dashboard_row) via gRPC.
318
+ *
319
+ * NOTE: DIS does NOT expose a REST DELETE endpoint for files.
320
+ * This gRPC method is the ONLY way to delete files.
321
+ *
322
+ * @param personaId - The persona ID (group)
323
+ * @param fileIds - Array of UploadedFile.Id values to delete
324
+ * @param widgetName - Optional widget name scope
325
+ */
326
+ async deleteFilesForGroup(personaId, fileIds, widgetName) {
327
+ const request = create(DeleteFilesForGroupRequestSchema, {
328
+ groupId: personaId,
329
+ groupType: DeletionGroupType.PERSONA,
330
+ fileIds,
331
+ ...(widgetName ? { widgetName } : {}),
332
+ });
333
+ // DataIngestService has PERSONA_REQUIRED auth
334
+ return this.dataIngestService.deleteFilesForGroup(request, {
335
+ headers: new Headers({ 'x-persona-id': personaId }),
336
+ });
337
+ }
276
338
  // ─────────────────────────────────────────────────────────────────────────────
277
339
  // ConversationReviewService RPCs (Audit)
278
340
  // ─────────────────────────────────────────────────────────────────────────────
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Central storage factory — creates DIS-backed version storage + policy engine.
3
+ *
4
+ * Used by all 4 call sites (version.ts, update.ts, delete.ts, workflow/adapter.ts)
5
+ * to avoid duplicating construction logic.
6
+ *
7
+ * Also provides lazy migration from local (.ema-versions/) to central (DIS).
8
+ */
9
+ import { DISAdapter } from "./dis-port.js";
10
+ import { CentralVersionStorage } from "./central-version-storage.js";
11
+ import { VersionPolicyEngine } from "./version-policy.js";
12
+ /**
13
+ * Extract a DISClientLike from an SDK client.
14
+ *
15
+ * At runtime, `createClient()` returns EmaClientAdapter which wraps EmaClientV2.
16
+ * EmaClientV2 satisfies DISClientLike (has uploadDataSource, downloadFile,
17
+ * deleteDataSource, listDataSourceFiles with raw gRPC response).
18
+ *
19
+ * This function checks for the `.v2Client` accessor (EmaClientAdapter) and
20
+ * falls back to using the client directly (e.g., in tests with a mock).
21
+ */
22
+ function extractDISClient(client) {
23
+ // If the client has a v2Client accessor (EmaClientAdapter), use the inner V2 client
24
+ const maybeAdapter = client;
25
+ if (maybeAdapter.v2Client) {
26
+ return maybeAdapter.v2Client;
27
+ }
28
+ // Fallback: assume the client itself satisfies DISClientLike (tests, direct V2 usage)
29
+ return client;
30
+ }
31
+ /**
32
+ * Create a central version storage + policy engine pair for a persona.
33
+ *
34
+ * @param client - SDK client. Typically the EmaClient/EmaClientAdapter from createClient().
35
+ * The factory auto-extracts the underlying EmaClientV2 for raw DIS operations.
36
+ * @param personaId - The persona ID to scope storage to
37
+ */
38
+ export function createCentralStorageEngine(client, personaId) {
39
+ const disClient = extractDISClient(client);
40
+ const port = new DISAdapter(disClient, personaId);
41
+ const storage = new CentralVersionStorage(port, personaId);
42
+ const engine = new VersionPolicyEngine(storage);
43
+ return { storage, engine };
44
+ }
45
+ /**
46
+ * Lazily migrate local versions to central storage on first access.
47
+ *
48
+ * Called by version.ts before creating a new snapshot. If central already has
49
+ * versions, this is a no-op. Otherwise, reads the last `limit` local versions
50
+ * and uploads them to DIS, preserving version numbers and parent chains.
51
+ *
52
+ * @param localStorage - Local git-backed storage (VersionStorage)
53
+ * @param centralStorage - DIS-backed central storage
54
+ * @param personaId - Persona to migrate
55
+ * @param limit - Max versions to migrate (default: 5, most recent)
56
+ * @returns Number of versions migrated (0 if central already populated or no local versions)
57
+ */
58
+ export async function migrateLocalToCentral(localStorage, centralStorage, personaId, limit = 5) {
59
+ // Skip if central already has versions (idempotent)
60
+ const centralManifest = await centralStorage.getManifest(personaId);
61
+ if (centralManifest && centralManifest.versions.length > 0) {
62
+ return 0;
63
+ }
64
+ // Read local versions
65
+ const localVersions = await localStorage.listVersions(personaId);
66
+ if (localVersions.length === 0) {
67
+ return 0;
68
+ }
69
+ // Take the most recent `limit` versions, sorted ascending for upload order
70
+ const sorted = [...localVersions].sort((a, b) => b.version_number - a.version_number);
71
+ const toMigrate = sorted.slice(0, limit).reverse();
72
+ let migrated = 0;
73
+ for (const entry of toMigrate) {
74
+ try {
75
+ const snapshot = await localStorage.getVersion(personaId, entry.version_number);
76
+ if (!snapshot)
77
+ continue;
78
+ await centralStorage.saveVersion(snapshot);
79
+ migrated++;
80
+ }
81
+ catch {
82
+ // Best-effort: skip failed individual versions
83
+ }
84
+ }
85
+ return migrated;
86
+ }