@ema.co/mcp-toolkit 2026.2.13 → 2026.2.23-1

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 (67) hide show
  1. package/.context/public/guides/ema-user-guide.md +12 -16
  2. package/.context/public/guides/mcp-tools-guide.md +203 -334
  3. package/dist/cli/index.js +2 -2
  4. package/dist/mcp/domain/loop-detection.js +89 -0
  5. package/dist/mcp/domain/sanitizer.js +1 -1
  6. package/dist/mcp/domain/structural-rules.js +4 -5
  7. package/dist/mcp/domain/validation-rules.js +5 -5
  8. package/dist/mcp/domain/workflow-graph.js +3 -5
  9. package/dist/mcp/domain/workflow-path-enumerator.js +7 -4
  10. package/dist/mcp/guidance.js +62 -29
  11. package/dist/mcp/handlers/debug/adapter.js +15 -0
  12. package/dist/mcp/handlers/debug/formatters.js +282 -0
  13. package/dist/mcp/handlers/debug/index.js +133 -0
  14. package/dist/mcp/handlers/demo/adapter.js +180 -0
  15. package/dist/mcp/handlers/env/config.js +2 -2
  16. package/dist/mcp/handlers/feedback/index.js +1 -1
  17. package/dist/mcp/handlers/index.js +0 -1
  18. package/dist/mcp/handlers/persona/adapter.js +135 -0
  19. package/dist/mcp/handlers/persona/index.js +237 -8
  20. package/dist/mcp/handlers/persona/schema.js +27 -0
  21. package/dist/mcp/handlers/reference/index.js +6 -4
  22. package/dist/mcp/handlers/sync/adapter.js +200 -0
  23. package/dist/mcp/handlers/workflow/adapter.js +174 -0
  24. package/dist/mcp/handlers/workflow/fix.js +11 -12
  25. package/dist/mcp/handlers/workflow/index.js +12 -40
  26. package/dist/mcp/handlers/workflow/validation.js +1 -1
  27. package/dist/mcp/knowledge-guidance-topics.js +615 -0
  28. package/dist/mcp/knowledge-types.js +7 -0
  29. package/dist/mcp/knowledge.js +75 -1403
  30. package/dist/mcp/resources-dynamic.js +2395 -0
  31. package/dist/mcp/resources-validation.js +408 -0
  32. package/dist/mcp/resources.js +72 -2508
  33. package/dist/mcp/server.js +69 -2825
  34. package/dist/mcp/tools.js +106 -5
  35. package/dist/sdk/client-adapter.js +265 -24
  36. package/dist/sdk/ema-client.js +100 -9
  37. package/dist/sdk/generated/agent-catalog.js +615 -0
  38. package/dist/sdk/generated/api-client/client/client.gen.js +3 -3
  39. package/dist/sdk/generated/api-client/client/index.js +5 -5
  40. package/dist/sdk/generated/api-client/client/utils.gen.js +4 -4
  41. package/dist/sdk/generated/api-client/client.gen.js +1 -1
  42. package/dist/sdk/generated/api-client/core/utils.gen.js +1 -1
  43. package/dist/sdk/generated/api-client/index.js +1 -1
  44. package/dist/sdk/generated/api-client/sdk.gen.js +2 -2
  45. package/dist/sdk/generated/well-known-types.js +99 -0
  46. package/dist/sdk/generated/widget-catalog.js +60 -0
  47. package/dist/sdk/grpc-client.js +115 -1
  48. package/dist/sync/sdk.js +2 -2
  49. package/dist/sync.js +4 -3
  50. package/docs/README.md +17 -9
  51. package/package.json +4 -3
  52. package/.context/public/guides/dashboard-operations.md +0 -349
  53. package/.context/public/guides/email-patterns.md +0 -125
  54. package/.context/public/guides/workflow-builder-patterns.md +0 -708
  55. package/dist/mcp/domain/intent-architect.js +0 -914
  56. package/dist/mcp/domain/quality-gates.js +0 -110
  57. package/dist/mcp/domain/workflow-execution-analyzer.js +0 -412
  58. package/dist/mcp/domain/workflow-intent.js +0 -1806
  59. package/dist/mcp/domain/workflow-merge.js +0 -449
  60. package/dist/mcp/domain/workflow-tracer.js +0 -648
  61. package/dist/mcp/domain/workflow-transformer.js +0 -742
  62. package/dist/mcp/handlers/knowledge/index.js +0 -54
  63. package/dist/mcp/handlers/persona/intent.js +0 -141
  64. package/dist/mcp/handlers/workflow/analyze.js +0 -119
  65. package/dist/mcp/handlers/workflow/compare.js +0 -70
  66. package/dist/mcp/handlers/workflow/generate.js +0 -384
  67. package/dist/mcp/handlers-consolidated.js +0 -333
@@ -1,19 +1,14 @@
1
1
  /**
2
2
  * Ema Platform Knowledge Base
3
3
  *
4
- * This file contains TWO types of content:
4
+ * Curated knowledge constants, patterns, and helper functions.
5
+ * Types are in ./knowledge-types.ts
6
+ * Auto-generated catalogs are in ../sdk/generated/
5
7
  *
6
- * 1. AUTO-GENERATED (API Fallbacks)
7
- * - AGENT_CATALOG, WIDGET_CATALOG, TYPE_COMPATIBILITY
8
- * - Generated from source repos via scheduled workflows
9
- * - Used as fallbacks when API unavailable
10
- * - TODO: Move to src/sdk/generated/ and import here
11
- *
12
- * 2. CURATED KNOWLEDGE (Human-Maintained)
13
- * - PLATFORM_CONCEPTS, WORKFLOW_PATTERNS, GUIDANCE_TOPICS, etc.
14
- * - Source repos are INPUT, not law
15
- * - Optimized for customer-facing MCP users
16
- * - Updated via knowledge scanning workflow + human review
8
+ * Content:
9
+ * - PLATFORM_CONCEPTS, WORKFLOW_PATTERNS, GUIDANCE_TOPICS, etc. (human-maintained)
10
+ * - Helper functions (getAgentsByCategory, suggestAgentsForUseCase, etc.)
11
+ * - Workflow analysis functions (parseWorkflowDef, validateWorkflowConnections)
17
12
  *
18
13
  * Input Sources (for curated content):
19
14
  * - ema-repos/ema/docs/ - Platform documentation
@@ -24,6 +19,13 @@
24
19
  * See: .context/core/guides/source-repos.md for full architecture
25
20
  */
26
21
  // ─────────────────────────────────────────────────────────────────────────────
22
+ // Re-exports: Auto-Generated Catalogs (from sdk/generated/)
23
+ // ─────────────────────────────────────────────────────────────────────────────
24
+ export { AGENT_CATALOG } from "../sdk/generated/agent-catalog.js";
25
+ import { AGENT_CATALOG } from "../sdk/generated/agent-catalog.js";
26
+ export { WIDGET_CATALOG } from "../sdk/generated/widget-catalog.js";
27
+ import { WIDGET_CATALOG } from "../sdk/generated/widget-catalog.js";
28
+ // ─────────────────────────────────────────────────────────────────────────────
27
29
  // Platform Concepts (from Ema User Guide)
28
30
  // ─────────────────────────────────────────────────────────────────────────────
29
31
  export const PLATFORM_CONCEPTS = [
@@ -66,8 +68,8 @@ export const PLATFORM_CONCEPTS = [
66
68
  term: "HITL",
67
69
  definition: "Human-in-the-Loop. An approval/verification step where a human reviews before the workflow continues.",
68
70
  aliases: ["Human Collaboration", "Approval Step", "Review Step"],
69
- relatedTerms: ["general_hitl"],
70
- commonConfusions: "HITL nodes MUST have both success AND failure paths defined.",
71
+ relatedTerms: ["entity_extraction_with_documents", "send_email_agent"],
72
+ commonConfusions: "HITL is ONLY supported on entity_extraction_with_documents and send_email_agent. general_hitl is NOT deployable. external_action_caller does NOT support HITL.",
71
73
  },
72
74
  {
73
75
  term: "Connector",
@@ -125,678 +127,6 @@ then route to "update ticket" instead of "create ticket"`,
125
127
  },
126
128
  },
127
129
  };
128
- // ─────────────────────────────────────────────────────────────────────────────
129
- // Agent Catalog
130
- // ─────────────────────────────────────────────────────────────────────────────
131
- export const AGENT_CATALOG = [
132
- // Triggers
133
- {
134
- actionName: "chat_trigger",
135
- displayName: "Chat Trigger",
136
- category: "trigger",
137
- description: "Entry point for chat-based interactions. Outputs both conversation history and current query.",
138
- inputs: [],
139
- outputs: [
140
- { name: "chat_conversation", type: "WELL_KNOWN_TYPE_CHAT_CONVERSATION", description: "Full conversation history from start" },
141
- { name: "user_query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Current user message only" },
142
- ],
143
- whenToUse: "Starting point for any chat or voice AI workflow",
144
- criticalRules: [
145
- "Each user message triggers a NEW workflow execution",
146
- "chat_conversation accumulates; user_query is current message only",
147
- ],
148
- },
149
- {
150
- actionName: "document_trigger",
151
- displayName: "Document Trigger",
152
- category: "trigger",
153
- description: "Entry point for document processing workflows triggered by file upload.",
154
- inputs: [],
155
- outputs: [
156
- { name: "user_query", type: "WELL_KNOWN_TYPE_DOCUMENT", description: "Uploaded document(s)" },
157
- ],
158
- whenToUse: "When workflow is triggered by document upload for processing/extraction",
159
- },
160
- {
161
- actionName: "voice_trigger",
162
- displayName: "Voice Trigger",
163
- category: "trigger",
164
- description: "Entry point for voice/phone call interactions.",
165
- inputs: [],
166
- outputs: [
167
- { name: "chat_conversation", type: "WELL_KNOWN_TYPE_CHAT_CONVERSATION", description: "Voice conversation history" },
168
- { name: "user_query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Current utterance" },
169
- ],
170
- whenToUse: "Voice AI employees handling phone calls",
171
- },
172
- // Routing / Classification
173
- {
174
- actionName: "chat_categorizer",
175
- displayName: "Intent Classifier",
176
- category: "routing",
177
- description: "Classifies user intent into predefined categories for routing. First routing node after trigger.",
178
- inputs: [
179
- { name: "conversation", type: "WELL_KNOWN_TYPE_CHAT_CONVERSATION", required: true, description: "Chat conversation to classify" },
180
- ],
181
- outputs: [
182
- { name: "category", type: "WELL_KNOWN_TYPE_ENUM", description: "Outputs the matched category enum value (e.g., 'Client_Update', 'Fallback')" },
183
- ],
184
- criticalRules: [
185
- "MUST have at least one outgoing edge",
186
- "runIf condition: compare 'output: category' to 'enumValue: <CategoryName>' (NOT category_<name> format)",
187
- "Handler nodes use runIf conditions to route by category",
188
- "ALWAYS include a Fallback category",
189
- "Create a runIf condition for EACH category or routing fails",
190
- "You MUST create a handler for EACH category",
191
- ],
192
- whenToUse: "When you need to route chat conversations to different processing paths based on intent",
193
- whenNotToUse: "Simple single-path workflows that don't need routing",
194
- example: "Categories: [Market_Impact, Client_Lookup, Compliance_Check, Fallback]",
195
- },
196
- {
197
- actionName: "text_categorizer",
198
- displayName: "Text Categorizer",
199
- category: "routing",
200
- description: "Classifies text content (not conversation) into categories.",
201
- inputs: [
202
- { name: "text", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Text to classify" },
203
- ],
204
- outputs: [
205
- { name: "category", type: "WELL_KNOWN_TYPE_ENUM", description: "Classification result" },
206
- ],
207
- criticalRules: [
208
- "Same rules as chat_categorizer: must have Fallback, edges for each category",
209
- ],
210
- whenToUse: "When routing based on text content rather than full conversation history",
211
- },
212
- {
213
- actionName: "document_categorizer",
214
- displayName: "Document Categorizer",
215
- category: "routing",
216
- description: "Classifies documents into categories for routing.",
217
- inputs: [
218
- { name: "documents", type: "WELL_KNOWN_TYPE_DOCUMENT", required: true, description: "Documents to classify" },
219
- ],
220
- outputs: [
221
- { name: "category", type: "WELL_KNOWN_TYPE_ENUM", description: "Document classification" },
222
- ],
223
- whenToUse: "When routing based on document type or content",
224
- },
225
- {
226
- actionName: "conversation_to_search_query",
227
- displayName: "Conversation Summarizer",
228
- category: "routing",
229
- description: "Converts conversation history to a search query. Configurable turn count.",
230
- inputs: [
231
- { name: "conversation", type: "WELL_KNOWN_TYPE_CHAT_CONVERSATION", required: true, description: "Conversation to summarize" },
232
- ],
233
- outputs: [
234
- { name: "summarized_conversation", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Distilled search query" },
235
- ],
236
- whenToUse: "Before search nodes when you have chat_conversation but need TEXT_WITH_SOURCES. Also for managing long conversation context windows.",
237
- whenNotToUse: "When trigger.user_query is sufficient for simple queries",
238
- criticalRules: [
239
- "May still be needed WITH chat_conversation for downstream agents requiring specific format",
240
- "Use to reduce conversation size due to LLM context window limits",
241
- ],
242
- },
243
- // Search & Retrieval
244
- {
245
- actionName: "search",
246
- displayName: "File Search / Knowledge Search",
247
- category: "search",
248
- description: "Searches uploaded documents/knowledge base using hybrid search (keyword + vector). Use version v2 (search/v2) which requires datastore_configs input. search/v0 is deprecated.",
249
- inputs: [
250
- { name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Search query" },
251
- { name: "datastore_configs", type: "WELL_KNOWN_TYPE_ANY", required: false, description: "Datastore configuration (v2). Wired from persona widget config." },
252
- ],
253
- outputs: [
254
- { name: "search_results", type: "WELL_KNOWN_TYPE_SEARCH_RESULT", description: "Matching passages with sources and citations" },
255
- ],
256
- whenToUse: "When searching static uploaded documents or internal knowledge base. Always use search/v2 (not deprecated search/v0).",
257
- whenNotToUse: "For real-time web content — use live_web_search or ai_web_search instead",
258
- example: "FAQ lookup, policy search, documentation assistant",
259
- },
260
- {
261
- actionName: "live_web_search",
262
- displayName: "Live Web Search / Deep Web Search",
263
- category: "search",
264
- description: "Real-time web search for external information not in knowledge base.",
265
- inputs: [
266
- { name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Search query" },
267
- ],
268
- outputs: [
269
- { name: "web_search_results", type: "WELL_KNOWN_TYPE_SEARCH_RESULT", description: "Web search results" },
270
- ],
271
- whenToUse: "When you need real-time external information not in the knowledge base",
272
- example: "Current events, live data, external research",
273
- },
274
- {
275
- actionName: "combine_search_results",
276
- displayName: "Combine Search Results",
277
- category: "search",
278
- description: "Merges results from multiple search sources with deduplication.",
279
- inputs: [
280
- { name: "search_results_1", type: "WELL_KNOWN_TYPE_SEARCH_RESULT", required: true, description: "First result set" },
281
- { name: "search_results_2", type: "WELL_KNOWN_TYPE_SEARCH_RESULT", required: true, description: "Second result set" },
282
- ],
283
- outputs: [
284
- { name: "combined_results", type: "WELL_KNOWN_TYPE_SEARCH_RESULT", description: "Merged, deduplicated results" },
285
- ],
286
- criticalRules: [
287
- "AVOID using unless necessary - prefer combine_and_rerank_search_results or call_llm with named_inputs",
288
- "combine_and_rerank_search_results provides intelligent relevance ranking",
289
- "call_llm with named_inputs allows prompt-based ranking control",
290
- ],
291
- whenToUse: "When combining local + web search, or multiple knowledge bases",
292
- whenNotToUse: "When you need intelligent ranking - use combine_and_rerank_search_results instead",
293
- },
294
- {
295
- actionName: "combine_and_rerank_search_results",
296
- displayName: "Rerank Search Results",
297
- category: "search",
298
- description: "Combines and reranks search results by relevance.",
299
- inputs: [
300
- { name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Original query for relevance scoring" },
301
- { name: "search_results_lists", type: "WELL_KNOWN_TYPE_SEARCH_RESULT", required: true, description: "Results to rerank" },
302
- ],
303
- outputs: [
304
- { name: "reranked_results", type: "WELL_KNOWN_TYPE_SEARCH_RESULT", description: "Reranked results" },
305
- ],
306
- whenToUse: "When you need to prioritize results by relevance after combining multiple sources",
307
- },
308
- {
309
- actionName: "document_metasearch",
310
- displayName: "Document Metasearch",
311
- category: "search",
312
- description: "Searches across document metadata.",
313
- inputs: [
314
- { name: "template", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Search template" },
315
- ],
316
- outputs: [
317
- { name: "document_metasearch_results", type: "WELL_KNOWN_TYPE_SEARCH_RESULT", description: "Metadata search results" },
318
- ],
319
- whenToUse: "When searching by document metadata rather than content",
320
- },
321
- // Generation & Response
322
- {
323
- actionName: "call_llm",
324
- displayName: "Respond",
325
- category: "generation",
326
- description: "Generates response using LLM with custom instructions. Accepts ANY type via named_inputs. Also used for LLM templating.",
327
- inputs: [
328
- { name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "User query" },
329
- { name: "named_inputs", type: "WELL_KNOWN_TYPE_ANY", required: false, description: "Additional context (use named_inputs_<Name> suffix)" },
330
- ],
331
- outputs: [
332
- { name: "response_with_sources", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Generated response" },
333
- ],
334
- whenToUse: "When you need custom response generation, content synthesis, or LLM templating for documents",
335
- whenNotToUse: "For strict regulatory formats requiring pixel-perfect layouts - use template engine instead",
336
- criticalRules: [
337
- "named_inputs accepts ANY type - use for tool results, search results, etc.",
338
- "Use suffix pattern: named_inputs_<Descriptive_Name>",
339
- "LLM TEMPLATING: Include structured section headers (## Section) in prompt - let LLM determine appropriate sections based on content",
340
- "For document generation: Set temperature 0.3-0.5 for consistent formatting",
341
- "Use structured prompts with clear formatting rules and examples",
342
- ],
343
- example: "named_inputs_Market_Context, named_inputs_Client_Data | " +
344
- "LLM Template prompt: 'Generate structured content with clear ## section headers, organized by themes'",
345
- },
346
- {
347
- actionName: "generate_document",
348
- displayName: "Generate Document",
349
- category: "generation",
350
- description: "Converts markdown content to formatted document (.docx). Use after call_llm for document generation workflows.",
351
- inputs: [
352
- { name: "markdown_file_contents", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Well-structured markdown to convert" },
353
- { name: "template", type: "WELL_KNOWN_TYPE_DOCUMENT", required: false, description: "Optional template from data source for styling" },
354
- ],
355
- outputs: [
356
- { name: "document_link", type: "WELL_KNOWN_TYPE_DOCUMENT", description: "Link to generated document" },
357
- ],
358
- whenToUse: "When converting LLM-generated markdown to professional document format",
359
- criticalRules: [
360
- "Ensure input markdown has clear headers (## Section) and structure",
361
- "For email attachment: use named_inputs (not attachment_links) due to DOCUMENT type",
362
- "Chain pattern: call_llm (content generation) → generate_document → send_email (via named_inputs)",
363
- "Optional: Add CSS/styling in markdown for professional formatting",
364
- ],
365
- example: "detailed_content (call_llm) → generate_doc → send_email.named_inputs_Attachment",
366
- },
367
- {
368
- actionName: "respond_with_sources",
369
- displayName: "Respond using Search Results",
370
- category: "generation",
371
- description: "Generates response grounded in search results with citations.",
372
- inputs: [
373
- { name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "User query" },
374
- { name: "search_results", type: "WELL_KNOWN_TYPE_SEARCH_RESULT", required: true, description: "Search results to ground response" },
375
- ],
376
- outputs: [
377
- { name: "response_with_sources", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Response with citations" },
378
- ],
379
- whenToUse: "When you want responses grounded in specific search results with source citations",
380
- criticalRules: [
381
- "Enable use_citation_based_filtering for trust",
382
- "Implement confidence thresholds for quality control",
383
- ],
384
- },
385
- {
386
- actionName: "respond_for_external_actions",
387
- displayName: "Respond using Tool Result",
388
- category: "generation",
389
- description: "Generates response explaining external tool/action results.",
390
- inputs: [
391
- { name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Original query" },
392
- { name: "external_action_result", type: "WELL_KNOWN_TYPE_ANY", required: true, description: "Tool execution result" },
393
- ],
394
- outputs: [
395
- { name: "response", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Human-friendly explanation" },
396
- ],
397
- whenToUse: "After external_action_caller to explain results to user",
398
- },
399
- {
400
- actionName: "fixed_response",
401
- displayName: "Fixed Response",
402
- category: "generation",
403
- description: "Returns a static/template response with {{variable}} substitution. No LLM call.",
404
- inputs: [
405
- { name: "named_inputs", type: "WELL_KNOWN_TYPE_ANY", required: false, description: "Variables to substitute into template (use named_inputs_<Variable_Name>)" },
406
- { name: "extracted_variables", type: "WELL_KNOWN_TYPE_ANY", required: false, description: "JSON key-value pairs for variable substitution" },
407
- { name: "fixed_response", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Template with {{variable_name}} placeholders" },
408
- ],
409
- outputs: [
410
- { name: "fixed_response_with_sources", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Response with variables substituted" },
411
- ],
412
- whenToUse: "For short, structured messages with variables: confirmations, acknowledgments, simple notifications",
413
- whenNotToUse: "For complex templates - use data source templates with generate_document or fill_document_template instead",
414
- criticalRules: [
415
- "Use {{variable_name}} syntax for dynamic content (case-sensitive)",
416
- "named_inputs takes precedence over extracted_variables when same variable name exists",
417
- "Keep templates SHORT - for long emails/documents use data source templates",
418
- "Variables must match exactly including case",
419
- ],
420
- example: "Template: 'Dear {{Customer_Name}}, your order {{Order_ID}} has been {{Status}}.' " +
421
- "→ Connect entity_extraction.customer_name to named_inputs_Customer_Name",
422
- },
423
- {
424
- actionName: "send_email_agent",
425
- displayName: "Send Email",
426
- category: "external",
427
- description: "Sends email via configured email provider.",
428
- inputs: [
429
- { name: "email_to", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Recipient email address - use fixed_response with {{email}} template" },
430
- { name: "email_subject", type: "WELL_KNOWN_TYPE_ANY", required: false, description: "Email subject line" },
431
- { name: "email_body", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Email body content" },
432
- { name: "attachment_links", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: false, description: "Document links to attach (use named_inputs for DOCUMENT type)" },
433
- ],
434
- outputs: [
435
- { name: "send_status", type: "WELL_KNOWN_TYPE_ANY", description: "Send confirmation" },
436
- ],
437
- whenToUse: "When you need to send email after user confirmation",
438
- criticalRules: [
439
- "email_to requires TEXT_WITH_SOURCES type - use fixed_response with {{email}} template populated from entity_extraction",
440
- "ALWAYS use HITL confirmation before sending (runIf: 'HITL Success')",
441
- "For attachments with DOCUMENT type, use named_inputs instead of attachment_links",
442
- "Never connect summarized_conversation or search_results directly to email_to",
443
- ],
444
- example: "entity_extraction → fixed_response('{{email}}') → send_email.email_to (CORRECT pattern)",
445
- },
446
- // External Actions
447
- {
448
- actionName: "external_action_caller",
449
- displayName: "External Tool Caller / Intelligent Actions",
450
- category: "external",
451
- description: "Calls external APIs/tools (ServiceNow, Salesforce, Workday, etc.).",
452
- inputs: [
453
- { name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Context for tool call" },
454
- { name: "conversation", type: "WELL_KNOWN_TYPE_CHAT_CONVERSATION", required: false, description: "Conversation history" },
455
- ],
456
- outputs: [
457
- { name: "tool_execution_result", type: "WELL_KNOWN_TYPE_ANY", description: "Tool execution result" },
458
- ],
459
- whenToUse: "When you need to call external systems (create ticket, send email, lookup CRM, update records)",
460
- criticalRules: [
461
- "Check conversation history before creating records to avoid duplicates",
462
- "Use HITL for actions with external side effects",
463
- ],
464
- example: "ServiceNow ticket creation, Salesforce record update, Email sending",
465
- },
466
- // Entity & Rule Processing
467
- {
468
- actionName: "entity_extraction_with_documents",
469
- displayName: "Entity Extraction",
470
- category: "entity",
471
- description: "Extracts structured entities from documents for cross-document linking or API calls.",
472
- inputs: [
473
- { name: "documents", type: "WELL_KNOWN_TYPE_DOCUMENT", required: true, description: "Documents to extract from" },
474
- { name: "extraction_columns", type: "WELL_KNOWN_TYPE_ANY", required: true, description: "Schema defining what to extract" },
475
- ],
476
- outputs: [
477
- { name: "extraction_columns", type: "WELL_KNOWN_TYPE_ANY", description: "Extracted structured data" },
478
- ],
479
- criticalRules: [
480
- "Works on DOCUMENTS, not text",
481
- "Requires schema definition for extraction columns",
482
- ],
483
- whenToUse: "When you need structured data extraction for cross-document linking, API calls, or audit trails with citations",
484
- whenNotToUse: "For simple conversational queries—use LLM resolution instead",
485
- },
486
- {
487
- actionName: "rule_validation_with_documents",
488
- displayName: "Rule Validation",
489
- category: "validation",
490
- description: "Validates extracted data against business rules. Rules are configured through the node's settings panel (UI), not via workflow_def inputs. Each rule has a name and description. Output mode is Pass/Fail per rule.",
491
- inputs: [
492
- { name: "primary_docs", type: "WELL_KNOWN_TYPE_DOCUMENT", required: true, description: "Primary documents to validate against" },
493
- { name: "map_of_extracted_columns", type: "WELL_KNOWN_TYPE_ANY", required: true, description: "Extracted data from entity_extraction to validate" },
494
- ],
495
- outputs: [
496
- { name: "ruleset_output", type: "WELL_KNOWN_TYPE_ANY", description: "Pass/Fail results per rule with explanations" },
497
- ],
498
- 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.",
499
- },
500
- // Human Collaboration
501
- // NOTE: general_hitl as a standalone workflow node is NOT currently recommended.
502
- // HITL is implemented as a FLAG on specific agents (e.g., send_email_agent, external_action_caller).
503
- // Kept here for reference when analyzing existing workflows that may use it.
504
- {
505
- actionName: "general_hitl",
506
- displayName: "Human Collaboration (NOT RECOMMENDED — use agent flags instead)",
507
- category: "collaboration",
508
- description: "DEPRECATED for new workflows. HITL approval is now a flag on specific agents (send_email_agent, external_action_caller), not a separate workflow node. Do NOT add general_hitl nodes to new workflows.",
509
- inputs: [
510
- { name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Context for review" },
511
- ],
512
- outputs: [
513
- { name: "hitl_status_HITL Success", type: "WELL_KNOWN_TYPE_ANY", description: "Human approved path" },
514
- { name: "hitl_status_HITL Failure", type: "WELL_KNOWN_TYPE_ANY", description: "Human rejected path" },
515
- ],
516
- criticalRules: [
517
- "DO NOT use general_hitl in new workflows — use HITL flags on the agent that needs approval instead",
518
- "If user asks for approval: enable the HITL flag on the relevant agent (e.g., send_email_agent, external_action_caller)",
519
- "Existing workflows with general_hitl nodes will still function — no migration required",
520
- "If you encounter general_hitl in an existing workflow: leave it, but don't add new ones",
521
- ],
522
- whenToUse: "DO NOT USE for new workflows. HITL is a flag on agents, not a standalone node.",
523
- example: "For approval: enable HITL flag on send_email_agent or external_action_caller",
524
- },
525
- // Validation & Guardrails
526
- {
527
- actionName: "response_validator",
528
- displayName: "Response Validator",
529
- category: "validation",
530
- description: "Validates LLM output against criteria. Guardrail for response quality.",
531
- inputs: [
532
- { name: "reference_query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Original query" },
533
- { name: "response_to_validate", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Response to check" },
534
- ],
535
- outputs: [
536
- { name: "abstain_reason", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Reason if validation fails" },
537
- ],
538
- whenToUse: "For guardrails, compliance checking on generated responses, quality control",
539
- },
540
- {
541
- actionName: "abstain_action",
542
- displayName: "Abstain from Answering",
543
- category: "validation",
544
- description: "Declines to answer when appropriate. Graceful handling of out-of-scope queries.",
545
- inputs: [
546
- { name: "abstain_reason", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Why abstaining" },
547
- ],
548
- outputs: [
549
- { name: "abstain_reason", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Abstention message" },
550
- ],
551
- whenToUse: "When the AI should explicitly decline to answer or redirect",
552
- },
553
- // Sentiment & Analysis
554
- {
555
- actionName: "sentiment_analyzer",
556
- displayName: "Sentiment Analyzer",
557
- category: "analytics",
558
- description: "Analyzes sentiment/emotion in user input.",
559
- inputs: [
560
- { name: "text", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Text to analyze" },
561
- ],
562
- outputs: [
563
- { name: "sentiment", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Sentiment analysis" },
564
- ],
565
- whenToUse: "When you need to gauge user emotion for routing or response tone adjustment",
566
- },
567
- // Domain Agents - Finance
568
- {
569
- actionName: "financial_risk_assessor",
570
- displayName: "Financial Risk Assessor",
571
- category: "finance",
572
- description: "Analyzes financial risk factors for investment/credit decisions.",
573
- inputs: [{ name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Risk query" }],
574
- outputs: [{ name: "risk_assessment", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Risk analysis" }],
575
- whenToUse: "Financial services: portfolio risk, credit assessment, exposure analysis",
576
- },
577
- {
578
- actionName: "financial_statement_analyzer",
579
- displayName: "Financial Statement Analyzer",
580
- category: "finance",
581
- description: "Analyzes financial statements and reports.",
582
- inputs: [{ name: "documents", type: "WELL_KNOWN_TYPE_DOCUMENT", required: true, description: "Financial documents" }],
583
- outputs: [{ name: "analysis", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Financial analysis" }],
584
- whenToUse: "Analyzing P&L, balance sheets, annual reports",
585
- },
586
- {
587
- actionName: "tax_advisor",
588
- displayName: "Tax Advisor",
589
- category: "finance",
590
- description: "Provides tax-related guidance and analysis.",
591
- inputs: [{ name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Tax question" }],
592
- outputs: [{ name: "tax_advice", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Tax guidance" }],
593
- whenToUse: "Tax planning, compliance questions, deduction analysis",
594
- },
595
- {
596
- actionName: "audit_evidence_verifier",
597
- displayName: "Audit Evidence Verifier",
598
- category: "finance",
599
- description: "Verifies audit evidence and documentation.",
600
- inputs: [{ name: "documents", type: "WELL_KNOWN_TYPE_DOCUMENT", required: true, description: "Audit documents" }],
601
- outputs: [{ name: "verification", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Verification results" }],
602
- whenToUse: "Audit support, evidence verification, compliance documentation",
603
- },
604
- // Domain Agents - Healthcare
605
- {
606
- actionName: "medical_record_summarizer",
607
- displayName: "Medical Record Summarizer",
608
- category: "healthcare",
609
- description: "Summarizes medical records and patient history.",
610
- inputs: [{ name: "documents", type: "WELL_KNOWN_TYPE_DOCUMENT", required: true, description: "Medical records" }],
611
- outputs: [{ name: "summary", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Record summary" }],
612
- whenToUse: "Healthcare: summarizing patient records, medical history, clinical notes",
613
- },
614
- {
615
- actionName: "medical_research_assistant",
616
- displayName: "Medical Research Assistant",
617
- category: "healthcare",
618
- description: "Assists with medical research queries.",
619
- inputs: [{ name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Research query" }],
620
- outputs: [{ name: "research", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Research findings" }],
621
- whenToUse: "Medical research, literature review, clinical guidance",
622
- },
623
- {
624
- actionName: "insurance_claim_summarizer",
625
- displayName: "Insurance Claim Summarizer",
626
- category: "healthcare",
627
- description: "Summarizes insurance claims and documentation.",
628
- inputs: [{ name: "documents", type: "WELL_KNOWN_TYPE_DOCUMENT", required: true, description: "Claim documents" }],
629
- outputs: [{ name: "summary", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Claim summary" }],
630
- whenToUse: "Insurance processing, claim analysis",
631
- },
632
- // Domain Agents - Legal
633
- {
634
- actionName: "compliance_document_analyzer",
635
- displayName: "Compliance Document Analyzer",
636
- category: "legal",
637
- description: "Analyzes documents for compliance issues.",
638
- inputs: [{ name: "documents", type: "WELL_KNOWN_TYPE_DOCUMENT", required: true, description: "Documents to analyze" }],
639
- outputs: [{ name: "compliance_findings", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Compliance analysis" }],
640
- whenToUse: "Regulatory compliance, contract review, policy compliance",
641
- },
642
- {
643
- actionName: "legal_expert",
644
- displayName: "Legal Expert",
645
- category: "legal",
646
- description: "Provides legal analysis and guidance.",
647
- inputs: [{ name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Legal question" }],
648
- outputs: [{ name: "legal_analysis", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Legal guidance" }],
649
- whenToUse: "Legal research, contract analysis, regulatory questions",
650
- },
651
- {
652
- actionName: "information_redacter",
653
- displayName: "Information Redacter",
654
- category: "legal",
655
- description: "Redacts sensitive information from documents.",
656
- inputs: [{ name: "documents", type: "WELL_KNOWN_TYPE_DOCUMENT", required: true, description: "Documents to redact" }],
657
- outputs: [{ name: "redacted", type: "WELL_KNOWN_TYPE_DOCUMENT", description: "Redacted documents" }],
658
- whenToUse: "Privacy compliance, document sanitization",
659
- },
660
- // Domain Agents - IT/Security
661
- {
662
- actionName: "phishing_email_detector",
663
- displayName: "Phishing Email Detector",
664
- category: "it_security",
665
- description: "Detects phishing attempts in emails.",
666
- inputs: [{ name: "email_content", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Email to analyze" }],
667
- outputs: [{ name: "detection_result", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Phishing analysis" }],
668
- whenToUse: "Email security, security operations",
669
- },
670
- {
671
- actionName: "cybersecurity_expert",
672
- displayName: "Cybersecurity Expert",
673
- category: "it_security",
674
- description: "Provides cybersecurity analysis and guidance.",
675
- inputs: [{ name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Security query" }],
676
- outputs: [{ name: "security_analysis", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Security guidance" }],
677
- whenToUse: "Security incident analysis, vulnerability assessment",
678
- },
679
- {
680
- actionName: "technical_support",
681
- displayName: "Technical Support",
682
- category: "it_security",
683
- description: "Provides IT technical support guidance.",
684
- inputs: [{ name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Support query" }],
685
- outputs: [{ name: "support_response", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Technical guidance" }],
686
- whenToUse: "IT helpdesk, troubleshooting, technical guidance",
687
- },
688
- // Domain Agents - Sales
689
- {
690
- actionName: "sales_intelligence",
691
- displayName: "Sales Intelligence",
692
- category: "sales",
693
- description: "Provides sales insights and account intelligence.",
694
- inputs: [{ name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Sales query" }],
695
- outputs: [{ name: "intelligence", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Sales insights" }],
696
- whenToUse: "Account research, competitive analysis, opportunity assessment",
697
- },
698
- {
699
- actionName: "email_writer",
700
- displayName: "Email Writer",
701
- category: "sales",
702
- description: "Generates professional email content.",
703
- inputs: [{ name: "query", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Email context" }],
704
- outputs: [{ name: "email", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Generated email" }],
705
- whenToUse: "Sales outreach, customer communication, follow-ups",
706
- },
707
- // Formatting Agents
708
- {
709
- actionName: "json_formatter",
710
- displayName: "JSON Formatter",
711
- category: "formatting",
712
- description: "Formats output as JSON.",
713
- inputs: [{ name: "content", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Content to format" }],
714
- outputs: [{ name: "json", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "JSON formatted output" }],
715
- whenToUse: "When structured JSON output is required",
716
- },
717
- {
718
- actionName: "json_extractor",
719
- displayName: "JSON Extractor",
720
- category: "formatting",
721
- description: "Extracts JSON from LLM output.",
722
- inputs: [{ name: "content", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Content containing JSON" }],
723
- outputs: [{ name: "extracted_json", type: "WELL_KNOWN_TYPE_ANY", description: "Extracted JSON object" }],
724
- whenToUse: "When you need to parse JSON from LLM responses",
725
- },
726
- {
727
- actionName: "markdown_formatter",
728
- displayName: "Markdown Formatter",
729
- category: "formatting",
730
- description: "Formats output as Markdown.",
731
- inputs: [{ name: "content", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", required: true, description: "Content to format" }],
732
- outputs: [{ name: "markdown", type: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", description: "Markdown formatted output" }],
733
- whenToUse: "When rich text formatting is needed",
734
- },
735
- ];
736
- // ─────────────────────────────────────────────────────────────────────────────
737
- // Widget Reference
738
- // ─────────────────────────────────────────────────────────────────────────────
739
- // ═══════════════════════════════════════════════════════════════════════════════════
740
- // WIDGET_CATALOG (NO API ALTERNATIVE - Intentionally Static)
741
- // ═══════════════════════════════════════════════════════════════════════════════════
742
- //
743
- // WHY STATIC: There is no API endpoint to list widget definitions. Widget metadata
744
- // lives in persona template YAML files and is not exposed via the API.
745
- //
746
- // RUNTIME ALTERNATIVE: For a specific persona, fetch widgets dynamically via:
747
- // persona(id="...", include_workflow=true) → proto_config.widgets[].name
748
- //
749
- // SOURCE: ema-repos/ema/ema_backend/db/system_values/persona_templates/prod/*.yaml
750
- // SYNC: Manual - run knowledge scan workflow to detect template changes
751
- //
752
- // TECHNICAL NOTES:
753
- // - Widget 'id' is the WidgetType enum value from protos
754
- // - Widget 'name' is assigned per-template (e.g., "upload", "upload1", "upload2")
755
- // - Proto 'case' values represent TYPE discriminators, not widget names
756
- //
757
- // See .context/core/guides/source-repos.md for full sync instructions
758
- // ═══════════════════════════════════════════════════════════════════════════════════
759
- export const WIDGET_CATALOG = [
760
- // ═══════════════════════════════════════════════════════════════════════════
761
- // Voice AI Widgets
762
- // ═══════════════════════════════════════════════════════════════════════════
763
- // SOURCE: voicebot_ai_employee.yaml
764
- // LAST VERIFIED: 2026-01-27
765
- { id: 38, name: "voiceSettings", description: "Language hints, voice model selection (title: 'Voice and language')", requiredFor: ["voice"], fields: ["languageHints", "voiceModel"] },
766
- { id: 39, name: "conversationSettings", description: "Identity, purpose, action instructions, hangup rules (title: 'Conversational behavior')", requiredFor: ["voice"], fields: ["welcomeMessage", "identityAndPurpose", "takeActionInstructions", "hangupInstructions", "transferCallInstructions", "speechCharacteristics", "systemPrompt", "formFillingInstructions", "waitMessage"] },
767
- { id: 43, name: "vadSettings", description: "Voice activity detection settings", requiredFor: ["voice"], fields: ["turnTimeout", "silenceEndCallTimeout", "maxConversationDuration"] },
768
- { id: 42, name: "dataStorageSettings", description: "Audio/transcript recording settings", requiredFor: ["voice"], fields: ["storeAudioRecording", "storeTranscripts", "storeAgentTranscript"] },
769
- { id: 41, name: "callSettings", description: "Call forwarding, spam prevention settings", requiredFor: ["voice"], fields: ["enableCallForwarding", "callForwardingNumber", "enableSpamCallPrevention"] },
770
- { id: 44, name: "voicebotPhoneNumber", description: "Phone number configuration (title: 'Phone numbers')", requiredFor: ["voice"], fields: ["phoneNumber"] },
771
- { id: 40, name: "voicebotFeedbackCollection", description: "Post-call feedback collection (title: 'Feedback collection')", requiredFor: ["voice"], fields: [] },
772
- // ═══════════════════════════════════════════════════════════════════════════
773
- // Chat AI Widgets
774
- // ═══════════════════════════════════════════════════════════════════════════
775
- // SOURCE: TBD - need to verify from chat template
776
- { id: 28, name: "chatbotSdkConfig", description: "Chat widget configuration and theming", requiredFor: ["chat"], fields: ["theme", "position", "initialMessage"] },
777
- { id: 33, name: "feedbackMessage", description: "Feedback collection settings", requiredFor: ["chat"], fields: ["enabled", "prompt"] },
778
- // ═══════════════════════════════════════════════════════════════════════════
779
- // Document Generation Widgets
780
- // ═══════════════════════════════════════════════════════════════════════════
781
- // SOURCE: ema-repos/ema/ema_backend/db/system_values/persona_templates/prod/document_proposal_manager.yaml
782
- // NOTE: Widget names are defined per-template. These are from Document Proposal Manager.
783
- // To sync: see .context/core/guides/source-repos.md
784
- // LAST VERIFIED: 2026-01-27 from document_proposal_manager.yaml
785
- { id: 3, name: "upload", description: "Content Repository - gold standard company docs (title: 'Content Repository')", requiredFor: ["document"], fields: ["tags"], uploadTarget: true },
786
- { id: 3, name: "upload1", description: "Service Line Documents - business unit specific content (title: 'Service Line Documents')", requiredFor: ["document"], fields: ["tags"], uploadTarget: true },
787
- { id: 3, name: "upload2", description: "Style Guide - formatting and tone guide (title: 'Style Guide')", requiredFor: ["document"], fields: ["tags"], uploadTarget: true },
788
- { id: 29, name: "fileTagging0", description: "File tagging configuration (title: 'Set Tags')", requiredFor: ["document"], fields: ["tagTypes", "fileTagMappings"] },
789
- { id: 5, name: "answerFormat0", description: "Proposal instructions - format, tone, language (title: 'Proposal Instructions')", requiredFor: ["document"], fields: ["textValue"] },
790
- { id: 16, name: "sectionConfigWidget", description: "Section categories with instructions (title: 'Section Categories')", requiredFor: ["document"], fields: ["sections"] },
791
- // ═══════════════════════════════════════════════════════════════════════════
792
- // Common Widgets (all types)
793
- // ═══════════════════════════════════════════════════════════════════════════
794
- // These appear across multiple template types
795
- { id: 3, name: "fileUpload", description: "Default document upload (KB files, title: 'Data sources')", requiredFor: ["voice", "chat", "dashboard"], fields: ["allowedTypes", "maxSize"], uploadTarget: true },
796
- { id: 6, name: "fusionModel", description: "EmaFusion model selection (GPT-4, Claude, etc., title: 'EmaFusion™ model')", requiredFor: ["voice", "chat", "dashboard", "document"], fields: ["allModels", "selectedModels"] },
797
- { id: 8, name: "dataProtection", description: "PII redaction settings", requiredFor: ["voice", "chat", "dashboard", "document"], fields: ["protectedClasses"] },
798
- { id: 9, name: "copyrightCheck", description: "Copyright infringement checker (title: 'Copyright Checker')", requiredFor: ["document"], fields: [] },
799
- ];
800
130
  // Project type mapping
801
131
  export const PROJECT_TYPES = {
802
132
  voice: 5,
@@ -805,38 +135,35 @@ export const PROJECT_TYPES = {
805
135
  document: 3,
806
136
  };
807
137
  // ─────────────────────────────────────────────────────────────────────────────
808
- // Type Compatibility (CANONICAL SOURCE)
138
+ // Type Compatibility (AUTO-GENERATED + CURATED OVERRIDES)
809
139
  // ─────────────────────────────────────────────────────────────────────────────
810
- // This is the canonical, user-facing documentation of type compatibility.
140
+ // Base rules (reflexive, ANY compatibility) are auto-generated from proto.
141
+ // Curated overrides document specific incompatibilities and conversion notes.
811
142
  //
812
- // SOURCE: ema-repos/protos/service/workflows/v1/well_known.proto
813
- // SYNC: TODO - Add to catalog-sync.yml workflow for automatic updates
143
+ // Regenerate base: npm run generate:type-compatibility
144
+ // Source: protos/service/workflows/v1/values.proto values_pb.ts well-known-types.ts
814
145
  //
815
146
  // Other type compatibility definitions serve different purposes:
816
- // - INTENT_TYPE_ROUTING (workflow-intent.ts) - routing logic during intent processing
817
147
  // - SCHEMA_TYPE_COMPATIBILITY (action-schema-parser.ts) - input name matching for validation
818
- //
819
- // See .context/core/guides/source-repos.md for full architecture
820
148
  // ─────────────────────────────────────────────────────────────────────────────
821
- export const TYPE_COMPATIBILITY = [
822
- // Chat conversation compatibility
823
- { sourceType: "WELL_KNOWN_TYPE_CHAT_CONVERSATION", targetType: "WELL_KNOWN_TYPE_CHAT_CONVERSATION", compatible: true },
149
+ import { BASE_TYPE_COMPATIBILITY } from "../sdk/generated/well-known-types.js";
150
+ const CURATED_OVERRIDES = [
151
+ // Chat conversation other types
824
152
  { sourceType: "WELL_KNOWN_TYPE_CHAT_CONVERSATION", targetType: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", compatible: false, note: "Use conversation_to_search_query to convert" },
825
- { sourceType: "WELL_KNOWN_TYPE_CHAT_CONVERSATION", targetType: "WELL_KNOWN_TYPE_ANY", compatible: true, note: "call_llm.named_inputs accepts ANY" },
826
- // Text with sources compatibility
827
- { sourceType: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", targetType: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", compatible: true },
828
- { sourceType: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", targetType: "WELL_KNOWN_TYPE_ANY", compatible: true },
153
+ { sourceType: "WELL_KNOWN_TYPE_CHAT_CONVERSATION", targetType: "WELL_KNOWN_TYPE_SEARCH_RESULT", compatible: false, note: "Search takes query string, not conversation" },
154
+ // Text with sources → other types
829
155
  { sourceType: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", targetType: "WELL_KNOWN_TYPE_CHAT_CONVERSATION", compatible: false, note: "Cannot convert text to conversation" },
830
- // Search result compatibility
831
- { sourceType: "WELL_KNOWN_TYPE_SEARCH_RESULT", targetType: "WELL_KNOWN_TYPE_SEARCH_RESULT", compatible: true },
832
- { sourceType: "WELL_KNOWN_TYPE_SEARCH_RESULT", targetType: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", compatible: false, note: "Use respond_for_external_actions (or call_llm.named_inputs) for search results" },
833
- { sourceType: "WELL_KNOWN_TYPE_SEARCH_RESULT", targetType: "WELL_KNOWN_TYPE_ANY", compatible: true, note: "call_llm.named_inputs accepts ANY" },
834
- // Document compatibility
835
- { sourceType: "WELL_KNOWN_TYPE_DOCUMENT", targetType: "WELL_KNOWN_TYPE_DOCUMENT", compatible: true },
836
- { sourceType: "WELL_KNOWN_TYPE_DOCUMENT", targetType: "WELL_KNOWN_TYPE_ANY", compatible: true },
156
+ { sourceType: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", targetType: "WELL_KNOWN_TYPE_SEARCH_RESULT", compatible: false, note: "Text is not a search result" },
157
+ // Search result other types
158
+ { sourceType: "WELL_KNOWN_TYPE_SEARCH_RESULT", targetType: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", compatible: false, note: "Use respond_for_external_actions or call_llm.named_inputs" },
159
+ { sourceType: "WELL_KNOWN_TYPE_SEARCH_RESULT", targetType: "WELL_KNOWN_TYPE_CHAT_CONVERSATION", compatible: false, note: "Search result is not a conversation" },
160
+ // Document → other types
837
161
  { sourceType: "WELL_KNOWN_TYPE_DOCUMENT", targetType: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES", compatible: false, note: "Use entity_extraction or document-specific agents" },
838
- // Any type compatibility
839
- { sourceType: "WELL_KNOWN_TYPE_ANY", targetType: "WELL_KNOWN_TYPE_ANY", compatible: true },
162
+ { sourceType: "WELL_KNOWN_TYPE_DOCUMENT", targetType: "WELL_KNOWN_TYPE_CHAT_CONVERSATION", compatible: false, note: "Document is not a conversation" },
163
+ ];
164
+ export const TYPE_COMPATIBILITY = [
165
+ ...BASE_TYPE_COMPATIBILITY,
166
+ ...CURATED_OVERRIDES,
840
167
  ];
841
168
  // ─────────────────────────────────────────────────────────────────────────────
842
169
  // Workflow Patterns
@@ -919,8 +246,8 @@ export const WORKFLOW_PATTERNS = [
919
246
  {
920
247
  name: "hitl-approval",
921
248
  personaType: "chat",
922
- description: "Human-in-the-loop approval — use HITL flags on agents, NOT a standalone general_hitl node",
923
- nodes: ["chat_trigger", "external_action_caller (with HITL flag)", "respond_for_external_actions"],
249
+ description: "Human-in-the-loop approval — enable HITL flag on send_email_agent or entity_extraction_with_documents (only nodes that support HITL)",
250
+ nodes: ["chat_trigger", "send_email_agent (with HITL flag)", "respond_for_external_actions"],
924
251
  connections: [
925
252
  "chat_trigger.user_query → external_action_caller.query",
926
253
  "chat_trigger.chat_conversation → external_action_caller.conversation",
@@ -929,9 +256,9 @@ export const WORKFLOW_PATTERNS = [
929
256
  "chat_trigger.chat_conversation → respond_for_external_actions.conversation",
930
257
  "respond_for_external_actions.response → WORKFLOW_OUTPUT",
931
258
  ],
932
- useCase: "High-value transactions, sensitive operations — enable HITL flag on the agent",
259
+ useCase: "High-value transactions, sensitive operations — enable HITL flag on send_email_agent or entity_extraction_with_documents",
933
260
  antiPatterns: [
934
- "Adding general_hitl as a standalone node (use agent HITL flags instead)",
261
+ "Adding general_hitl as a standalone node (it is NOT deployable)",
935
262
  "Not wiring conversation context to response node",
936
263
  ],
937
264
  },
@@ -1047,7 +374,7 @@ export const QUALIFYING_QUESTIONS = [
1047
374
  { category: "Data", question: "Are there multiple data sources that need to be correlated?", whyItMatters: "Requires cross-document linking strategy and combine_search_results", required: false },
1048
375
  { category: "Actions", question: "What external tools/APIs should the AI call? (ServiceNow, Salesforce, Workday, etc.)", whyItMatters: "Configures external_action_caller tools and connectors", required: false },
1049
376
  { category: "Actions", question: "What actions can the AI perform? (create ticket, send email, update record)", whyItMatters: "Lists available tools and determines action scope", required: false },
1050
- { category: "Actions", question: "Which actions require human approval?", whyItMatters: "Enable HITL flag on the relevant agent (send_email_agent, external_action_caller)", required: false },
377
+ { category: "Actions", question: "Which actions require human approval?", whyItMatters: "Enable HITL flag on the relevant agent (only entity_extraction_with_documents and send_email_agent support HITL)", required: false },
1051
378
  { category: "Validation", question: "What validations must be performed? (compliance, rules, thresholds)", whyItMatters: "May need rule_validation_with_documents or response_validator", required: false },
1052
379
  { category: "Output", question: "What should the success response look like?", whyItMatters: "Determines call_llm output formatting and response structure", required: true },
1053
380
  { category: "Output", question: "What should the error/exception response look like?", whyItMatters: "Needs explicit error handling paths and fallback responses", required: false },
@@ -1092,7 +419,7 @@ export const COMMON_MISTAKES = [
1092
419
  { mistake: "Using workflow_def instead of workflow in updates", problem: "API accepts request but changes silently don't persist", solution: "Use 'workflow' field (not 'workflow_def') in updateAiEmployee(). GET returns workflow_def, UPDATE expects workflow." },
1093
420
  { mistake: "Chat workflow without chat-aware response node", problem: "Duplicate/repetitive responses — LLM re-asks questions already answered, loses multi-turn context", solution: "Use respond_for_external_actions (for search results or tool results) instead of generic call_llm. If using call_llm, wire chat_conversation into named_inputs." },
1094
421
  { mistake: "Wiring entity_extraction directly to send_email inputs", problem: "Type mismatch — entity_extraction outputs ANY type, email inputs need TEXT_WITH_SOURCES. Results in corrupted email addresses or deploy errors.", solution: "Use intermediary: entity_extraction → json_mapper → fixed_response({{field}}) → send_email. Or use custom_agent with output_fields=[To,Subject,Body]." },
1095
- { mistake: "Adding general_hitl as a standalone workflow node", problem: "general_hitl as a standalone node is not currently recommended. It may have backend issues and adds workflow complexity.", solution: "Enable the HITL flag on the agent that performs the action (e.g., send_email_agent, external_action_caller). The agent handles approval flow internally. Revisit if general_hitl is re-enabled as standalone." },
422
+ { mistake: "Adding general_hitl as a standalone workflow node", problem: "general_hitl is NOT deployable it appears in catalogs but cannot be deployed. Deploying it will fail.", solution: "Enable the HITL flag on the agent that performs the action. Only entity_extraction_with_documents and send_email_agent support HITL (disable_human_interaction: false). external_action_caller does NOT support HITL." },
1096
423
  ];
1097
424
  export const DEBUG_CHECKLIST = [
1098
425
  { step: 1, action: "Check Status", description: "Is the AI Employee active/ready?", apiField: "status" },
@@ -1107,597 +434,9 @@ export const DEBUG_CHECKLIST = [
1107
434
  { step: 10, action: "Validate Types", description: "Are all connections type-compatible?" },
1108
435
  ];
1109
436
  // ─────────────────────────────────────────────────────────────────────────────
1110
- // Guidance Topics
437
+ // Re-exports: Guidance Topics (from knowledge-guidance-topics.ts)
1111
438
  // ─────────────────────────────────────────────────────────────────────────────
1112
- /**
1113
- * Guidance topics for workflow building.
1114
- *
1115
- * status field indicates confidence level:
1116
- * - "verified": Based on platform mechanics, tested behavior
1117
- * - "experimental": Best practice suggestions, needs validation
1118
- *
1119
- * Experimental guidance uses softer language (consider, may, typically)
1120
- * to avoid biasing LLMs toward unverified patterns.
1121
- */
1122
- export const GUIDANCE_TOPICS = {
1123
- "categorizer-routing": {
1124
- title: "Categorizer Routing Best Practices",
1125
- content: "Every categorizer must have runIf conditions for each category. The runIf compares output 'category' to enumValue. Always include Fallback.",
1126
- status: "verified",
1127
- examples: [
1128
- "runIf: {lhs: {actionOutput: {actionName: 'chat_categorizer', output: 'category'}, autoDetectedBinding: false}, operator: 1, rhs: {inline: {enumValue: 'Market_Impact'}, autoDetectedBinding: false}}",
1129
- "runIf: {lhs: {actionOutput: {actionName: 'chat_categorizer', output: 'category'}, autoDetectedBinding: false}, operator: 1, rhs: {inline: {enumValue: 'Fallback'}, autoDetectedBinding: false}}",
1130
- ],
1131
- criticalRules: [
1132
- "MUST have runIf condition for each category",
1133
- "runIf format: output='category', operator=1 (numeric), enumValue='<CategoryName>'",
1134
- "DO NOT use 'category_<Name>' as output - use 'category' and compare to enum value",
1135
- "ALWAYS include Fallback category",
1136
- "Create handler node with runIf for EACH category",
1137
- ],
1138
- },
1139
- "type-compatibility": {
1140
- title: "Type Compatibility Rules",
1141
- content: "Auto Builder validates type compatibility. Common mistake: connecting chat_conversation directly to search.query.",
1142
- status: "verified",
1143
- examples: [
1144
- "✅ trigger.user_query → search.query (TEXT_WITH_SOURCES → TEXT_WITH_SOURCES)",
1145
- "❌ trigger.chat_conversation → search.query (CHAT_CONVERSATION ≠ TEXT_WITH_SOURCES)",
1146
- "✅ search.search_results → respond_for_external_actions.external_action_result (SEARCH_RESULT → ANY)",
1147
- "❌ search.search_results → call_llm.query (SEARCH_RESULT ≠ TEXT_WITH_SOURCES)",
1148
- "✅ search.search_results → call_llm.named_inputs_* (SEARCH_RESULT → ANY)",
1149
- ],
1150
- criticalRules: [
1151
- "CHAT_CONVERSATION only compatible with chat_categorizer.conversation",
1152
- "SEARCH_RESULT compatible with respond_for_external_actions.external_action_result or named_inputs (respond_with_sources/v0 is deprecated)",
1153
- "call_llm.named_inputs accepts ANY type",
1154
- "Use conversation_to_search_query to convert CHAT_CONVERSATION → TEXT_WITH_SOURCES",
1155
- ],
1156
- },
1157
- "named-inputs": {
1158
- title: "Named Inputs Pattern",
1159
- content: "When connecting to call_llm.named_inputs, use suffix: named_inputs_<Descriptive_Name>. The name appears as a label in the workflow UI.",
1160
- status: "verified",
1161
- examples: [
1162
- "named_inputs_Market_Context",
1163
- "named_inputs_Client_Data",
1164
- "named_inputs_Tool_Result",
1165
- "named_inputs_Web_Search_Results",
1166
- "named_inputs_Validation_Output",
1167
- ],
1168
- },
1169
- "hitl-patterns": {
1170
- title: "Human-in-the-Loop Patterns",
1171
- content: "HITL is implemented as a FLAG on specific agents, NOT as a standalone general_hitl workflow node. " +
1172
- "When the user requests approval for an action, enable the HITL flag on the relevant agent " +
1173
- "(e.g., send_email_agent, external_action_caller). The platform handles the approval UI and routing automatically. " +
1174
- "Do NOT add general_hitl nodes to new workflows.\n\n" +
1175
- "JSON format: Set `disable_human_interaction: false` (or omit — default is false = HITL enabled) on the action node " +
1176
- "in workflow_def.actions[]. To DISABLE HITL, set `disable_human_interaction: true`. " +
1177
- "Note the counter-intuitive naming: false = HITL ON, true = HITL OFF.",
1178
- status: "verified",
1179
- examples: [
1180
- "User wants email approval → enable HITL flag on send_email_agent",
1181
- "User wants action approval → enable HITL flag on external_action_caller",
1182
- "Existing workflow has general_hitl → leave it (still functions), but don't add new ones",
1183
- "JSON: { name: 'send_email', action: { name: 'send_email_agent' }, disable_human_interaction: false, ... } → HITL ENABLED",
1184
- "JSON: { name: 'respond', action: { name: 'call_llm' }, disable_human_interaction: true, ... } → HITL DISABLED (auto-proceed)",
1185
- ],
1186
- criticalRules: [
1187
- "DO NOT use general_hitl as a standalone node in new workflows",
1188
- "HITL = flag on the agent that needs approval (send_email_agent, external_action_caller)",
1189
- "JSON field: `disable_human_interaction` (boolean) on the action node — false=HITL on, true=HITL off",
1190
- "If you encounter existing general_hitl nodes: they still work, no migration needed",
1191
- "When user says 'with approval': set disable_human_interaction: false on the relevant action node",
1192
- ],
1193
- },
1194
- "hitl-policy": {
1195
- title: "HITL Policy (When to Use)",
1196
- content: "HITL is OPTIONAL. Default is auto-proceed without approval gates. Only add HITL when explicitly requested by user.",
1197
- status: "verified",
1198
- examples: [
1199
- "User says 'add email' → Ask about recipient/trigger, NOT about approval",
1200
- "User says 'with approval' or 'confirm before sending' → Add HITL",
1201
- "User says 'auto' or 'directly' → Skip HITL discussion entirely",
1202
- ],
1203
- criticalRules: [
1204
- "DEFAULT: Proceed WITHOUT approval gate unless explicitly requested",
1205
- "Add HITL only when: user says 'confirm before', 'approval required', 'human review'",
1206
- "Skip HITL when: user says 'auto', 'directly', 'no approval', or specifies direct flow",
1207
- "If ambiguous: ASK 'Should [action] require approval, or auto-proceed?'",
1208
- "NEVER auto-add HITL based on action type alone",
1209
- "INFO severity issues = 'Consider this', not 'Must fix'",
1210
- ],
1211
- },
1212
- "multi-source-search": {
1213
- title: "Multi-Source Search Architecture",
1214
- content: "For comprehensive answers, combine local search with web search. Use combine_search_results to merge and deduplicate.",
1215
- status: "verified",
1216
- examples: [
1217
- "search (local) + live_web_search → combine_search_results → respond_for_external_actions",
1218
- "For cross-document linking: entity_extraction → knowledge_graph_generator → document_synthesis",
1219
- ],
1220
- criticalRules: [
1221
- "Use entity extraction when cross-document linking is needed",
1222
- "Use LLM resolution for single conversational queries",
1223
- "Entity extraction requires schema definition",
1224
- ],
1225
- },
1226
- "voice-tool-calling": {
1227
- title: "Voice AI Tool Calling Rules",
1228
- content: "Voice AI has specific rules for tool calling to ensure natural conversation flow.",
1229
- status: "verified",
1230
- criticalRules: [
1231
- "Collect ALL required parameters before calling a tool",
1232
- "Wait for response before calling another tool",
1233
- "NEVER mention tool names to the user",
1234
- "NEVER guess parameter values - ask the user",
1235
- "Use plain language: 'Let me look that up' not 'Calling search API'",
1236
- "Handle delays with waitMessage: 'One moment while I check...'",
1237
- "Handle errors gracefully, offer alternatives",
1238
- ],
1239
- },
1240
- "guardrails": {
1241
- title: "Implementing Guardrails",
1242
- content: "Use response_validator to check LLM output before sending to user. Use abstain_action when the AI should decline to answer.",
1243
- status: "verified",
1244
- examples: [
1245
- "call_llm → response_validator → (valid) → WORKFLOW_OUTPUT",
1246
- "call_llm → response_validator → (invalid) → abstain_action → WORKFLOW_OUTPUT",
1247
- ],
1248
- criticalRules: [
1249
- "Enable use_citation_based_filtering for trust",
1250
- "Implement confidence thresholds",
1251
- "Define clear abstain conditions",
1252
- ],
1253
- },
1254
- "output-mapping": {
1255
- title: "Output Mapping to WORKFLOW_OUTPUT",
1256
- content: "ALL paths must eventually lead to WORKFLOW_OUTPUT. Every response node should have an edge to WORKFLOW_OUTPUT.",
1257
- status: "verified",
1258
- examples: [
1259
- "respond_market.response_with_sources → WORKFLOW_OUTPUT",
1260
- "fallback_response.response_with_sources → WORKFLOW_OUTPUT",
1261
- "approved_response.response → WORKFLOW_OUTPUT",
1262
- "rejected_response.response → WORKFLOW_OUTPUT",
1263
- ],
1264
- },
1265
- "workflow-execution": {
1266
- title: "Workflow Execution Model",
1267
- content: "CRITICAL: Each user_query triggers a NEW workflow execution. The workflow runs ONCE per user message, not once per conversation.",
1268
- status: "verified",
1269
- examples: [
1270
- "❌ User: 'Create ticket' → creates ticket; User: 'Add my phone' → creates ANOTHER ticket",
1271
- "✅ Check chat_conversation for existing ticket, route to 'update' instead of 'create'",
1272
- ],
1273
- criticalRules: [
1274
- "Each user message triggers new workflow execution",
1275
- "chat_conversation accumulates; user_query is current message only",
1276
- "Check context before creating records to avoid duplicates",
1277
- "Use runIf conditions to skip redundant operations",
1278
- ],
1279
- },
1280
- "conversation-vs-query": {
1281
- title: "Conversation vs Query Usage",
1282
- content: "Use user_query for current message, chat_conversation for full history, conversation_summarizer for controlled context.",
1283
- status: "verified",
1284
- examples: [
1285
- "user_query: Simple queries where history doesn't matter",
1286
- "chat_conversation: When you need full context",
1287
- "conversation_summarizer: Long conversations, context window management",
1288
- ],
1289
- criticalRules: [
1290
- "user_query = current message only (TEXT_WITH_SOURCES)",
1291
- "chat_conversation = all history (CHAT_CONVERSATION)",
1292
- "conversation_summarizer may still be needed for downstream agent format requirements",
1293
- ],
1294
- },
1295
- "workflow-structure": {
1296
- title: "Workflow Structure Best Practices",
1297
- content: "Suggested pattern: Conversation Summarizer → Entity Extractor → JSON Mapper → Downstream Consumers. Extract once, pass outputs onward. This may help ensure client context is available before knowledge base searches.",
1298
- status: "experimental",
1299
- examples: [
1300
- "Consider: trigger → summarizer → extractor → json_mapper → [search, routing, responses]",
1301
- "Consider: Single extraction node feeds JSON mapper, which distributes to multiple consumers",
1302
- "May cause issues: Multiple extraction nodes extracting same data from same input",
1303
- "May cause issues: KB search before extraction (can't use client context)",
1304
- ],
1305
- criticalRules: [
1306
- "Consider extracting entities once, then passing structured data via JSON mapper",
1307
- "Extraction before KB search may help if search needs client context",
1308
- "JSON mapper output (output_json) can feed downstream nodes that need structured context",
1309
- "Duplicate extraction nodes may cause redundancy - consider single extractor pattern",
1310
- ],
1311
- },
1312
- "search-node-timing": {
1313
- title: "Knowledge Base Search Node Timing and Usage",
1314
- content: "Search nodes execute based on their input connections. Early search (after summarizer) typically sees only conversation summary. Late search (after JSON mapper) may be able to use client context. Consider multiple search nodes if you need both generic and context-aware retrieval.",
1315
- status: "experimental",
1316
- examples: [
1317
- "Early search: trigger → summarizer → knowledge_search_1 (typically sees only summary)",
1318
- "Late search: trigger → summarizer → extractor → json_mapper → knowledge_search_2 (may access client context)",
1319
- "Template search: json_mapper → fixed_response (query builder) → template_search (may use client context)",
1320
- "Dual search pattern: Early generic search + late context-aware search (consider for comprehensive results)",
1321
- ],
1322
- criticalRules: [
1323
- "Search nodes typically only see data from their input connections",
1324
- "Early search (before extraction) may provide generic retrieval",
1325
- "Late search (after JSON mapper) may be able to incorporate client context",
1326
- "Consider placing template search after JSON mapper if client context is needed",
1327
- "Multiple search nodes may be useful for different purposes (generic vs context-aware)",
1328
- ],
1329
- },
1330
- "node-execution-conditions": {
1331
- title: "When Nodes Execute (Execution Conditions)",
1332
- content: "Nodes execute based on: 1) Input connections (data must flow in), 2) runIf conditions (must evaluate to true), 3) trigger_when conditions (for HITL/conditional nodes). Nodes without valid input paths or with false runIf conditions will NOT execute.",
1333
- status: "verified",
1334
- examples: [
1335
- "Orphan node: No input connection from trigger → node never executes",
1336
- "runIf false: Node has input but runIf condition evaluates false → node skipped",
1337
- "Missing category edge: Categorizer output not connected → downstream nodes never receive category",
1338
- "Dead end: Node executes but output not connected → data lost, no response to user",
1339
- ],
1340
- criticalRules: [
1341
- "Every node MUST have a path from trigger (or be reachable via runIf conditions)",
1342
- "runIf conditions use: lhs (actionOutput reference), operator (1=equals), rhs (enumValue or value)",
1343
- "If runIf evaluates false, node is skipped (doesn't execute)",
1344
- "Nodes without input connections are 'orphans' and never execute",
1345
- "Categorizer nodes need downstream nodes with runIf conditions for each category",
1346
- "Check execution flow: trigger → [conditional paths] → response → WORKFLOW_OUTPUT",
1347
- ],
1348
- },
1349
- "array-preservation": {
1350
- title: "Array Preservation in Workflows",
1351
- content: "Arrays from entity extraction may be flattened or truncated in some cases. Consider explicit configuration if array structure matters. Needs validation.",
1352
- status: "experimental",
1353
- examples: [
1354
- "Consider: Entity extraction schema: { 'participants': { 'type': 'array', 'items': 'string' } }",
1355
- "Consider: JSON mapper - preserve arrays rather than flattening",
1356
- "May occur: Without array config, arrays may return only first element",
1357
- "For multiple recipients: Consider array in to_email or iterator pattern",
1358
- ],
1359
- criticalRules: [
1360
- "Consider defining array types explicitly in extraction schema",
1361
- "JSON mapper may need explicit array mapping configuration",
1362
- "Test array handling with actual workflow to verify behavior",
1363
- "This guidance needs validation - behavior may vary by node type",
1364
- ],
1365
- },
1366
- "search-filtering": {
1367
- title: "Search Filtering Considerations",
1368
- content: "Consider combining semantic query + structured filters. Security filters (tenant_id/client_id) are typically important for isolation. Needs validation with actual platform behavior.",
1369
- status: "experimental",
1370
- examples: [
1371
- "Consider: semantic query + tenant_id filter + path_prefix filter",
1372
- "Consider: Fallback to broader search if filtered results are empty",
1373
- "May cause issues: Only filename/path filter without semantic query",
1374
- "May cause issues: Over-filtering from uncertain extraction",
1375
- ],
1376
- criticalRules: [
1377
- "Consider combining semantic query + structured filters",
1378
- "Security filters (tenant_id/client_id) are typically needed for isolation",
1379
- "Path/filename filters may help narrow scope but verify behavior",
1380
- "If filtered search returns few results, consider broader fallback",
1381
- "Test actual filter behavior - this guidance needs validation",
1382
- ],
1383
- },
1384
- "fallback-response-inputs": {
1385
- title: "Fallback Response Input Considerations",
1386
- content: "Fallback responses may benefit from full conversation context rather than just summarized query. Consider what context the fallback handler needs. Needs validation.",
1387
- status: "experimental",
1388
- examples: [
1389
- "Consider: trigger.chat_conversation → fallback.query (full context)",
1390
- "Consider: Include routing context in fallback inputs",
1391
- "May cause issues: Only summarized_conversation may lose user wording",
1392
- ],
1393
- criticalRules: [
1394
- "Consider what context fallback handler needs",
1395
- "Full conversation may provide better context than summary alone",
1396
- "Include routing context if helpful for fallback logic",
1397
- "Test actual fallback behavior - this guidance needs validation",
1398
- ],
1399
- },
1400
- "separate-vs-merged-inputs": {
1401
- title: "Separate vs Merged Inputs Consideration",
1402
- content: "Consider keeping separate inputs for different data sources. named_inputs_* may help provide semantic clarity. Needs validation.",
1403
- status: "experimental",
1404
- examples: [
1405
- "Consider: named_inputs_Conversation + named_inputs_Templates for clarity",
1406
- "Consider: Separate inputs may help agent reason over each source",
1407
- "May cause issues: Merging all into single input may lose structure",
1408
- ],
1409
- criticalRules: [
1410
- "Consider separate named_inputs_* for different data sources",
1411
- "Separate inputs may provide semantic clarity",
1412
- "Test actual behavior - this guidance needs validation",
1413
- ],
1414
- },
1415
- "folder-path-filtering": {
1416
- title: "Folder Path Filtering Consideration",
1417
- content: "Path filtering may help narrow search scope. Consider combining with semantic query. Verify actual platform support for path filters.",
1418
- status: "experimental",
1419
- examples: [
1420
- "Consider: path_prefix filter + semantic query",
1421
- "May help: Normalized paths for consistent filtering",
1422
- "May cause issues: Exact path matching (can be brittle)",
1423
- ],
1424
- criticalRules: [
1425
- "Consider combining path filters with semantic query",
1426
- "Verify path filter support in actual platform",
1427
- "Test actual behavior - this guidance needs validation",
1428
- ],
1429
- },
1430
- "automated-extraction-json": {
1431
- title: "Extraction and JSON Mapping Pattern",
1432
- content: "Consider extraction → JSON mapper pattern for structured data flow. This may help reduce redundant extraction. Needs validation.",
1433
- status: "experimental",
1434
- examples: [
1435
- "Consider: trigger → summarizer → extractor → json_mapper → consumers",
1436
- "Consider: JSON mapper to normalize extracted values",
1437
- "May help: Single extraction point rather than multiple",
1438
- ],
1439
- criticalRules: [
1440
- "Consider single extraction point with JSON mapper distribution",
1441
- "Extraction schema configuration may help structure output",
1442
- "Test actual behavior - this guidance needs validation",
1443
- ],
1444
- },
1445
- "when-filters-necessary": {
1446
- title: "Filter Necessity Considerations",
1447
- content: "Security/tenant isolation filters are typically important. Verify what filtering your storage layer provides. Needs validation with actual platform.",
1448
- status: "experimental",
1449
- examples: [
1450
- "Consider: tenant_id/client_id filters for security isolation",
1451
- "Consider: Whether storage layer already enforces isolation",
1452
- "May cause issues: Relying only on document annotations for isolation",
1453
- ],
1454
- criticalRules: [
1455
- "Consider security filters for tenant/client isolation",
1456
- "Verify what isolation your storage layer provides",
1457
- "Test actual behavior - this guidance needs validation",
1458
- ],
1459
- },
1460
- "filter-query-guidance": {
1461
- title: "Filter vs Query Considerations",
1462
- content: "Consider filters when user references specific documents or folders. Consider semantic queries when user describes topics. Both may be useful together. Needs validation.",
1463
- status: "experimental",
1464
- examples: [
1465
- "Consider filters: User names specific doc or references folder",
1466
- "Consider query: User describes topic or intent",
1467
- "Consider both: Broad filters + semantic query",
1468
- ],
1469
- criticalRules: [
1470
- "Consider filters when user references specific documents",
1471
- "Consider semantic queries when user describes topics",
1472
- "Combining both may improve results - test actual behavior",
1473
- "This guidance needs validation with actual platform",
1474
- ],
1475
- },
1476
- "node-selection": {
1477
- title: "Node Selection Guide: When to Use What",
1478
- status: "verified",
1479
- content: `Choose the RIGHT node type for each task. Using wrong nodes wastes compute, increases latency, and reduces quality.
1480
-
1481
- ## Decision Tree
1482
-
1483
- ### Q1: Do you need EXTERNAL KNOWLEDGE?
1484
- - YES → Use search node first, then generation
1485
- - NO → Skip search, use generation or transform
1486
-
1487
- ### Q2: Do you need AI REASONING/GENERATION?
1488
- - YES → Use LLM-based node
1489
- - NO → Use transform node (json_mapper, fixed_response)
1490
-
1491
- ### Q3: What KIND of generation?
1492
- - Simple Q&A with citations → respond_for_external_actions
1493
- - Custom generation → call_llm
1494
- - Complex multi-step reasoning → custom_agent
1495
- - Search + synthesize in one → document_synthesis
1496
-
1497
- ### Q4: Do you need STRUCTURED DATA extraction?
1498
- - YES → entity_extraction (NOT call_llm!)
1499
- - NO → Continue with generation
1500
-
1501
- ## Node Cost/Latency
1502
-
1503
- | Node | LLM Calls | Latency | Use When |
1504
- |------|-----------|---------|----------|
1505
- | fixed_response | 0 | <50ms | Static content, config, templates |
1506
- | json_mapper | 0 | <100ms | Transform JSON without reasoning |
1507
- | entity_extraction | 1 | 1-2s | Extract structured data |
1508
- | respond_for_external_actions | 1 | 2-4s | Search + response (conversation-aware) |
1509
- | call_llm | 1 | 2-4s | Custom generation |
1510
- | custom_agent | 1-3 | 3-8s | Complex reasoning |
1511
- | document_synthesis | 2-5 | 5-15s | Multi-search + synthesis |`,
1512
- examples: [
1513
- "✅ Static template → fixed_response (0 LLM calls)",
1514
- "✅ Extract client_name, email → entity_extraction (1 LLM call, typed output)",
1515
- "✅ Simple KB Q&A → respond_for_external_actions (1 LLM call, conversation-aware)",
1516
- "✅ Custom email generation → call_llm with search results in named_inputs",
1517
- "❌ Using call_llm to extract JSON → Use entity_extraction instead",
1518
- "❌ Using document_synthesis for simple Q&A → Use respond_for_external_actions",
1519
- "❌ Using custom_agent for static response → Use fixed_response",
1520
- ],
1521
- criticalRules: [
1522
- "NEVER use LLM for transforms that json_mapper can do",
1523
- "NEVER use call_llm for structured extraction - use entity_extraction",
1524
- "NEVER use document_synthesis when respond_for_external_actions suffices",
1525
- "ALWAYS use fixed_response for static content (templates, config, help text)",
1526
- "Use entity_extraction for typed, reliable structured data",
1527
- "Use call_llm when you need custom instructions or reasoning",
1528
- "Use custom_agent when task requires role context + multi-step reasoning",
1529
- "Use document_synthesis only when you need search-plan-search-synthesize pattern",
1530
- ],
1531
- },
1532
- "llm-node-selection": {
1533
- title: "LLM Node Selection: Which One to Use",
1534
- status: "verified",
1535
- content: `Different LLM nodes serve different purposes. Choose based on your needs:
1536
-
1537
- ## respond_for_external_actions (replaces deprecated respond_with_sources)
1538
- - **Best for**: Simple Q&A with KB citations, tool result explanation
1539
- - **Input**: Search results or tool results via external_action_result
1540
- - **Output**: Response with source citations
1541
- - **When**: User asks question, you search KB, return answer
1542
- - **NOT for**: Custom generation, complex reasoning
1543
-
1544
- ## call_llm
1545
- - **Best for**: Custom text generation with instructions
1546
- - **Input**: Query + optional search/context via named_inputs
1547
- - **Output**: Generated text
1548
- - **When**: Need custom prompts, specific format, controlled generation
1549
- - **NOT for**: Simple Q&A (use respond_for_external_actions)
1550
-
1551
- ## custom_agent
1552
- - **Best for**: Complex tasks requiring role + instructions
1553
- - **Input**: Role instructions + task instructions + named_inputs
1554
- - **Output**: Structured or free-form response (use output_fields for structured)
1555
- - **When**: Multi-step reasoning, persona-based responses, tool-like behavior
1556
- - **NOT for**: Simple generation (overkill)
1557
- - **CRITICAL**: When outputting JSON for json_mapper, use output_fields to define keys
1558
-
1559
- ## document_synthesis
1560
- - **Best for**: Multi-source research with planning
1561
- - **Input**: User request + search results
1562
- - **Output**: Synthesized document
1563
- - **When**: Need to search → plan → search again → synthesize
1564
- - **NOT for**: Simple KB lookup (too heavy)`,
1565
- examples: [
1566
- "User asks 'What is our refund policy?' → respond_for_external_actions",
1567
- "Need to generate email with specific template → call_llm",
1568
- "Need to analyze portfolio and recommend actions → custom_agent",
1569
- "Need to research topic across multiple sources → document_synthesis",
1570
- ],
1571
- criticalRules: [
1572
- "respond_for_external_actions: 1 LLM call, simple Q&A or tool results, conversation-aware",
1573
- "call_llm: 1 LLM call, custom generation, accepts named_inputs",
1574
- "custom_agent: 1-3 LLM calls, complex reasoning, has role context",
1575
- "document_synthesis: 2-5 LLM calls, heavy, only for research tasks",
1576
- "Default to respond_for_external_actions for KB Q&A",
1577
- "Upgrade to call_llm when you need custom instructions",
1578
- "Upgrade to custom_agent for complex reasoning",
1579
- "Use document_synthesis sparingly (latency expensive)",
1580
- ],
1581
- },
1582
- "search-vs-no-search": {
1583
- title: "When to Search vs Direct Generation",
1584
- status: "verified",
1585
- content: `Not every request needs KB search. Search adds latency and can introduce irrelevant context.
1586
-
1587
- ## SEARCH when:
1588
- - User asks factual question about domain knowledge
1589
- - Request references specific documents/data
1590
- - Response accuracy depends on up-to-date information
1591
- - User asks about policies, procedures, product details
1592
-
1593
- ## DON'T SEARCH when:
1594
- - User needs help with task (drafting, formatting)
1595
- - Response is conversational/procedural
1596
- - Information is already in context (extracted entities)
1597
- - User asks general questions (time, greeting)`,
1598
- examples: [
1599
- "✅ Search: 'What is our return policy?' - needs KB",
1600
- "✅ Search: 'Tell me about client Thompson' - needs client data",
1601
- "❌ Don't search: 'Can you draft an email?' - procedural",
1602
- "❌ Don't search: 'Format this as a table' - transform",
1603
- "❌ Don't search: 'Hello, how are you?' - greeting",
1604
- ],
1605
- criticalRules: [
1606
- "Search is NOT free - adds 0.5-2s latency per search",
1607
- "Search can introduce noise if results aren't relevant",
1608
- "Fallback/greeting responses often don't need search",
1609
- "Task-based requests (draft, format, summarize) usually don't need search",
1610
- "Use categorizer to route search vs no-search paths",
1611
- "If entity extraction already has the data, don't search for it again",
1612
- ],
1613
- },
1614
- "custom-agent-json-output": {
1615
- title: "custom_agent + json_mapper Pattern",
1616
- status: "verified",
1617
- content: `When using custom_agent to generate JSON that will be parsed by json_mapper, you MUST properly configure output_fields or use strict prompting.
1618
-
1619
- ## The Problem
1620
-
1621
- Without explicit output_fields, custom_agent returns JSON as a string blob in response_with_sources.
1622
- The json_mapper may fail to extract fields correctly, or output gets misformatted.
1623
-
1624
- ## Solution A: Define output_fields (RECOMMENDED)
1625
-
1626
- Use output_fields with extraction columns matching your JSON keys:
1627
-
1628
- \`\`\`json
1629
- {
1630
- "name": "email_gen",
1631
- "action": { "name": { "namespaces": ["actions", "emainternal"], "name": "custom_agent" }, "version": "v1" },
1632
- "inputs": {
1633
- "task_instructions": { "inline": { "wellKnown": { "textWithSources": {
1634
- "text": "Generate notification email with To, Subject, and Body fields.",
1635
- "sources": []
1636
- }}}},
1637
- "output_fields": {
1638
- "inline": { "array": { "values": [
1639
- { "wellKnown": { "extractionColumn": { "id": "To", "name": "To", "dataType": 1 }}},
1640
- { "wellKnown": { "extractionColumn": { "id": "Subject", "name": "Subject", "dataType": 1 }}},
1641
- { "wellKnown": { "extractionColumn": { "id": "Body", "name": "Body", "dataType": 1 }}}
1642
- ]}}
1643
- }
1644
- }
1645
- }
1646
- \`\`\`
1647
-
1648
- ## Solution B: Strict Prompting + json_mapper
1649
-
1650
- If not using output_fields, instruct LLM to output RAW JSON only:
1651
-
1652
- \`\`\`
1653
- task_instructions: "Output ONLY the JSON object, no markdown. Format: {\\"To\\": \\"...\\", \\"Subject\\": \\"...\\", \\"Body\\": \\"...\\"}"
1654
- \`\`\`
1655
-
1656
- Then wire to json_mapper with matching pathIndices:
1657
- \`\`\`json
1658
- {
1659
- "json_mapper_config": {
1660
- "inline": { "wellKnown": { "jsonMapperConfig": {
1661
- "rules": [
1662
- { "fieldName": "to", "pathIndices": [{"stringIndex": "To"}], "type": 3 },
1663
- { "fieldName": "subject", "pathIndices": [{"stringIndex": "Subject"}], "type": 3 },
1664
- { "fieldName": "body", "pathIndices": [{"stringIndex": "Body"}], "type": 3 }
1665
- ]
1666
- }}}
1667
- }
1668
- }
1669
- \`\`\`
1670
-
1671
- ## Wiring json_mapper to fixed_response
1672
-
1673
- json_mapper outputs go to output_json. Use fixed_response template variables:
1674
-
1675
- \`\`\`json
1676
- {
1677
- "name": "fmt_to",
1678
- "action": { "name": { "namespaces": ["actions", "emainternal"], "name": "fixed_response" }, "version": "v1" },
1679
- "inputs": {
1680
- "template": { "inline": { "wellKnown": { "textWithSources": { "text": "{{to}}", "sources": [] }}}},
1681
- "custom_data": { "actionOutput": { "actionName": "json_map", "output": "output_json" }}
1682
- }
1683
- }
1684
- \`\`\``,
1685
- examples: [
1686
- "Generate email → custom_agent(output_fields=[To,Subject,Body]) → send_email_agent",
1687
- "Generate email → custom_agent(strict prompt) → json_mapper → fixed_response({{to}}) → send_email_agent",
1688
- "❌ custom_agent without output_fields → json_mapper fails to parse",
1689
- "❌ json_mapper output wired directly to send_email_agent → type mismatch (need fixed_response)",
1690
- ],
1691
- criticalRules: [
1692
- "custom_agent + json_mapper: ALWAYS use output_fields OR strict 'ONLY JSON' prompt",
1693
- "json_mapper outputs to output_json - use fixed_response with {{fieldName}} template",
1694
- "send_email_agent inputs require TEXT_WITH_SOURCES - use fixed_response to format",
1695
- "If json_mapper fails to extract: check if LLM is wrapping JSON in markdown (```json)",
1696
- "pathIndices must EXACTLY match JSON keys (case-sensitive)",
1697
- "Default values in json_mapper rules prevent null errors",
1698
- ],
1699
- },
1700
- };
439
+ export { GUIDANCE_TOPICS } from "./knowledge-guidance-topics.js";
1701
440
  // ─────────────────────────────────────────────────────────────────────────────
1702
441
  // Helper Functions
1703
442
  // ─────────────────────────────────────────────────────────────────────────────
@@ -1731,105 +470,38 @@ export function getConceptByTerm(term) {
1731
470
  c.aliases?.some(a => a.toLowerCase() === term.toLowerCase()));
1732
471
  }
1733
472
  export function suggestAgentsForUseCase(useCase) {
1734
- const keywords = useCase.toLowerCase();
1735
- const suggestions = [];
1736
- const added = new Set();
1737
- const addIfNotPresent = (name) => {
1738
- if (!added.has(name)) {
1739
- const agent = getAgentByName(name);
1740
- if (agent) {
1741
- suggestions.push(agent);
1742
- added.add(name);
473
+ const queryTokens = useCase
474
+ .toLowerCase()
475
+ .split(/[\s,;:.\-/|]+/)
476
+ .filter(t => t.length > 1);
477
+ if (queryTokens.length === 0)
478
+ return [];
479
+ const scored = AGENT_CATALOG.map(agent => {
480
+ let score = 0;
481
+ const searchableFields = [
482
+ agent.actionName,
483
+ agent.displayName,
484
+ agent.category,
485
+ agent.description,
486
+ agent.whenToUse,
487
+ agent.whenNotToUse ?? "",
488
+ ...agent.inputs.map(i => `${i.name} ${i.type}`),
489
+ ...agent.outputs.map(o => `${o.name} ${o.type}`),
490
+ ].map(f => f.toLowerCase());
491
+ for (const token of queryTokens) {
492
+ for (const field of searchableFields) {
493
+ if (field.includes(token)) {
494
+ score++;
495
+ break;
496
+ }
1743
497
  }
1744
498
  }
1745
- };
1746
- // Always include trigger
1747
- addIfNotPresent("chat_trigger");
1748
- // Routing
1749
- if (keywords.includes("intent") || keywords.includes("route") || keywords.includes("categor") || keywords.includes("multi") || keywords.includes("different")) {
1750
- addIfNotPresent("chat_categorizer");
1751
- }
1752
- // Search
1753
- if (keywords.includes("search") || keywords.includes("document") || keywords.includes("knowledge") || keywords.includes("faq") || keywords.includes("lookup") || keywords.includes("find")) {
1754
- addIfNotPresent("conversation_to_search_query");
1755
- addIfNotPresent("search");
1756
- addIfNotPresent("respond_for_external_actions");
1757
- }
1758
- // Web search
1759
- if (keywords.includes("web") || keywords.includes("real-time") || keywords.includes("current") || keywords.includes("live")) {
1760
- addIfNotPresent("live_web_search");
1761
- if (added.has("search")) {
1762
- addIfNotPresent("combine_search_results");
1763
- }
1764
- }
1765
- // External tools
1766
- if (keywords.includes("ticket") || keywords.includes("servicenow") || keywords.includes("salesforce") || keywords.includes("api") || keywords.includes("create") || keywords.includes("update") || keywords.includes("crm") || keywords.includes("email")) {
1767
- addIfNotPresent("external_action_caller");
1768
- }
1769
- // HITL — general_hitl is NOT recommended as a standalone node.
1770
- // HITL is a flag on agents. Don't suggest adding general_hitl nodes.
1771
- // If user wants approval, the agent should enable HITL flags on relevant agents.
1772
- // Compliance/Validation
1773
- if (keywords.includes("compliance") || keywords.includes("validate") || keywords.includes("rule") || keywords.includes("guardrail")) {
1774
- addIfNotPresent("response_validator");
1775
- }
1776
- // Entity extraction
1777
- if (keywords.includes("extract") || keywords.includes("entity") || keywords.includes("structure")) {
1778
- addIfNotPresent("entity_extraction_with_documents");
1779
- }
1780
- // Voice specific
1781
- if (keywords.includes("voice") || keywords.includes("phone") || keywords.includes("call")) {
1782
- suggestions[0] = getAgentByName("voice_trigger") || suggestions[0];
1783
- }
1784
- // Document processing
1785
- if (keywords.includes("document") || keywords.includes("upload") || keywords.includes("process") || keywords.includes("invoice") || keywords.includes("contract")) {
1786
- if (!added.has("chat_trigger")) {
1787
- suggestions[0] = getAgentByName("document_trigger") || suggestions[0];
1788
- }
1789
- addIfNotPresent("entity_extraction_with_documents");
1790
- }
1791
- // Default response if no response agent added
1792
- if (!added.has("respond_for_external_actions") && !added.has("call_llm")) {
1793
- addIfNotPresent("call_llm");
1794
- }
1795
- return suggestions;
1796
- }
1797
- export function validateWorkflowPrompt(prompt) {
1798
- const lowerPrompt = prompt.toLowerCase();
1799
- const issues = [];
1800
- const warnings = [];
1801
- // Check for categorizer without fallback
1802
- if ((lowerPrompt.includes("categoriz") || lowerPrompt.includes("classif") || lowerPrompt.includes("route")) &&
1803
- !lowerPrompt.includes("fallback")) {
1804
- issues.push("Missing Fallback category - every categorizer MUST have a Fallback");
1805
- }
1806
- // Check for HITL without both paths
1807
- if (lowerPrompt.includes("hitl") || lowerPrompt.includes("human collaboration") || lowerPrompt.includes("approval")) {
1808
- if (!lowerPrompt.includes("success") || !lowerPrompt.includes("fail")) {
1809
- issues.push("HITL node requires both success AND failure paths");
1810
- }
1811
- }
1812
- // Check for output mapping
1813
- if (!lowerPrompt.includes("workflow_output") && !lowerPrompt.includes("output")) {
1814
- warnings.push("No explicit WORKFLOW_OUTPUT mapping found - ensure all paths lead to output");
1815
- }
1816
- // Check for trigger
1817
- if (!lowerPrompt.includes("trigger") && !lowerPrompt.includes("chat") && !lowerPrompt.includes("voice") && !lowerPrompt.includes("document")) {
1818
- issues.push("No trigger type specified - include chat_trigger, voice_trigger, or document_trigger");
1819
- }
1820
- // Check for common type issues
1821
- if (lowerPrompt.includes("chat_conversation") && lowerPrompt.includes("search.query")) {
1822
- warnings.push("Potential type mismatch: chat_conversation → search.query. Use conversation_to_search_query first.");
1823
- }
1824
- // Check for persona type
1825
- if (!lowerPrompt.includes("voice ai") && !lowerPrompt.includes("chat ai") && !lowerPrompt.includes("dashboard ai") && !lowerPrompt.includes("persona type")) {
1826
- warnings.push("No persona type specified - should specify Voice AI, Chat AI, or Dashboard AI");
1827
- }
1828
- return {
1829
- valid: issues.length === 0,
1830
- issues,
1831
- warnings,
1832
- };
499
+ return { agent, score };
500
+ });
501
+ return scored
502
+ .filter(s => s.score > 0)
503
+ .sort((a, b) => b.score - a.score)
504
+ .map(s => s.agent);
1833
505
  }
1834
506
  // ─────────────────────────────────────────────────────────────────────────────
1835
507
  // Workflow Analysis Functions
@@ -2044,7 +716,7 @@ export { DEPRECATED_ACTIONS_WITH_REPLACEMENT, DEPRECATED_ACTIONS_NO_REPLACEMENT,
2044
716
  *
2045
717
  * SOURCE: ema-repos/ema/docs/workflow-architecture-handoff.md (lines 85-200, constraint checklist 153-167)
2046
718
  * STATUS: Can be auto-generated from source doc
2047
- * TODO: Add to catalog-sync.yml workflow for automatic updates
719
+ * SYNC: Candidate for catalog-sync.yml automation (see .context/core/guides/source-repos.md)
2048
720
  *
2049
721
  * See .context/core/guides/source-repos.md for sync details
2050
722
  */