@ema.co/mcp-toolkit 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +321 -0
  3. package/config.example.yaml +32 -0
  4. package/dist/cli/index.js +333 -0
  5. package/dist/config.js +136 -0
  6. package/dist/emaClient.js +398 -0
  7. package/dist/index.js +109 -0
  8. package/dist/mcp/handlers-consolidated.js +851 -0
  9. package/dist/mcp/index.js +15 -0
  10. package/dist/mcp/prompts.js +1753 -0
  11. package/dist/mcp/resources.js +624 -0
  12. package/dist/mcp/server.js +4723 -0
  13. package/dist/mcp/tools-consolidated.js +590 -0
  14. package/dist/mcp/tools-legacy.js +736 -0
  15. package/dist/models.js +8 -0
  16. package/dist/scheduler.js +21 -0
  17. package/dist/sdk/client.js +788 -0
  18. package/dist/sdk/config.js +136 -0
  19. package/dist/sdk/contracts.js +429 -0
  20. package/dist/sdk/generation-schema.js +189 -0
  21. package/dist/sdk/index.js +39 -0
  22. package/dist/sdk/knowledge.js +2780 -0
  23. package/dist/sdk/models.js +8 -0
  24. package/dist/sdk/state.js +88 -0
  25. package/dist/sdk/sync-options.js +216 -0
  26. package/dist/sdk/sync.js +220 -0
  27. package/dist/sdk/validation-rules.js +355 -0
  28. package/dist/sdk/workflow-generator.js +291 -0
  29. package/dist/sdk/workflow-intent.js +1585 -0
  30. package/dist/state.js +88 -0
  31. package/dist/sync.js +416 -0
  32. package/dist/syncOptions.js +216 -0
  33. package/dist/ui.js +334 -0
  34. package/docs/advisor-comms-assistant-fixes.md +175 -0
  35. package/docs/api-contracts.md +216 -0
  36. package/docs/auto-builder-analysis.md +271 -0
  37. package/docs/data-architecture.md +166 -0
  38. package/docs/ema-auto-builder-guide.html +394 -0
  39. package/docs/ema-user-guide.md +1121 -0
  40. package/docs/mcp-tools-guide.md +149 -0
  41. package/docs/naming-conventions.md +218 -0
  42. package/docs/tool-consolidation-proposal.md +427 -0
  43. package/package.json +98 -0
  44. package/resources/templates/chat-ai/README.md +119 -0
  45. package/resources/templates/chat-ai/persona-config.json +111 -0
  46. package/resources/templates/dashboard-ai/README.md +156 -0
  47. package/resources/templates/dashboard-ai/persona-config.json +180 -0
  48. package/resources/templates/voice-ai/README.md +123 -0
  49. package/resources/templates/voice-ai/persona-config.json +74 -0
  50. package/resources/templates/voice-ai/workflow-prompt.md +120 -0
@@ -0,0 +1,851 @@
1
+ /**
2
+ * Consolidated Tool Handlers
3
+ *
4
+ * Each handler dispatches based on mode/flags following Unix CLI patterns.
5
+ */
6
+ import { fingerprintPersona } from "../sync.js";
7
+ import { AGENT_CATALOG, WORKFLOW_PATTERNS, QUALIFYING_QUESTIONS, PLATFORM_CONCEPTS, WORKFLOW_EXECUTION_MODEL, COMMON_MISTAKES, DEBUG_CHECKLIST, GUIDANCE_TOPICS, VOICE_PERSONA_TEMPLATE, getAgentByName, getWidgetsForPersonaType, checkTypeCompatibility, getQualifyingQuestionsByCategory, getRequiredQualifyingQuestions, getConceptByTerm, suggestAgentsForUseCase, validateWorkflowPrompt, detectWorkflowIssues, validateWorkflowConnections, suggestWorkflowFixes, } from "../sdk/knowledge.js";
8
+ import { compileWorkflow } from "../sdk/workflow-generator.js";
9
+ import { parseInput, intentToSpec } from "../sdk/workflow-intent.js";
10
+ // ═══════════════════════════════════════════════════════════════════════════
11
+ // ENV Handler
12
+ // ═══════════════════════════════════════════════════════════════════════════
13
+ export async function handleEnv(_args, getEnvironments) {
14
+ const envs = getEnvironments();
15
+ return {
16
+ environments: envs.map(e => ({
17
+ name: e.name,
18
+ default: e.isDefault,
19
+ })),
20
+ count: envs.length,
21
+ };
22
+ }
23
+ // ═══════════════════════════════════════════════════════════════════════════
24
+ // PERSONA Handler
25
+ // ═══════════════════════════════════════════════════════════════════════════
26
+ export async function handlePersona(args, client, getTemplateId, createClientForEnv) {
27
+ const id = args.id;
28
+ const identifier = args.identifier; // deprecated alias
29
+ const idOrName = id ?? identifier;
30
+ const mode = args.mode;
31
+ // Determine effective mode
32
+ let effectiveMode = mode;
33
+ if (!effectiveMode) {
34
+ if (args.templates)
35
+ effectiveMode = "templates";
36
+ else if (args.all || args.query || args.status || args.trigger_type)
37
+ effectiveMode = "list";
38
+ else if (idOrName)
39
+ effectiveMode = "get";
40
+ else
41
+ effectiveMode = "list"; // Default to list all
42
+ }
43
+ switch (effectiveMode) {
44
+ case "get": {
45
+ if (!idOrName) {
46
+ return { error: "id required for get mode" };
47
+ }
48
+ const persona = await resolvePersona(client, idOrName);
49
+ if (!persona) {
50
+ return { error: `Persona not found: ${idOrName}` };
51
+ }
52
+ const result = {
53
+ id: persona.id,
54
+ name: persona.name,
55
+ description: persona.description,
56
+ status: persona.status,
57
+ template_id: persona.template_id,
58
+ trigger_type: persona.trigger_type,
59
+ };
60
+ if (args.include_workflow && persona.workflow_def) {
61
+ result.workflow_def = persona.workflow_def;
62
+ }
63
+ if (args.include_fingerprint) {
64
+ result.fingerprint = fingerprintPersona(persona);
65
+ }
66
+ return result;
67
+ }
68
+ case "list": {
69
+ let personas = await client.getPersonasForTenant();
70
+ // Apply filters
71
+ if (args.query) {
72
+ const q = args.query.toLowerCase();
73
+ personas = personas.filter(p => p.name?.toLowerCase().includes(q));
74
+ }
75
+ if (args.status) {
76
+ personas = personas.filter(p => p.status === args.status);
77
+ }
78
+ if (args.trigger_type) {
79
+ personas = personas.filter(p => p.trigger_type === args.trigger_type);
80
+ }
81
+ // Apply limit
82
+ const limit = args.limit || 50;
83
+ personas = personas.slice(0, limit);
84
+ return {
85
+ count: personas.length,
86
+ personas: personas.map(p => ({
87
+ id: p.id,
88
+ name: p.name,
89
+ status: p.status,
90
+ trigger_type: p.trigger_type,
91
+ })),
92
+ };
93
+ }
94
+ case "create": {
95
+ const name = args.name;
96
+ if (!name) {
97
+ return { error: "name required for create mode" };
98
+ }
99
+ let templateId = args.template_id;
100
+ if (!templateId && args.type) {
101
+ templateId = getTemplateId(args.type);
102
+ }
103
+ const result = await client.createAiEmployee({
104
+ name,
105
+ description: args.description,
106
+ template_id: templateId,
107
+ source_persona_id: args.clone_from,
108
+ });
109
+ return {
110
+ success: true,
111
+ persona_id: result.persona_id,
112
+ name,
113
+ };
114
+ }
115
+ case "update": {
116
+ if (!idOrName) {
117
+ return { error: "id required for update mode" };
118
+ }
119
+ const persona = await resolvePersona(client, idOrName);
120
+ if (!persona) {
121
+ return { error: `Persona not found: ${idOrName}` };
122
+ }
123
+ await client.updateAiEmployee({
124
+ persona_id: persona.id,
125
+ name: args.name,
126
+ description: args.description,
127
+ proto_config: persona.proto_config ?? {},
128
+ enabled_by_user: typeof args.enabled === "boolean" ? args.enabled : undefined,
129
+ });
130
+ return {
131
+ success: true,
132
+ persona_id: persona.id,
133
+ updated_fields: Object.keys(args).filter(k => !["id", "identifier", "mode", "env"].includes(k)),
134
+ };
135
+ }
136
+ case "compare": {
137
+ if (!idOrName || !args.compare_to) {
138
+ return { error: "id and compare_to required for compare mode" };
139
+ }
140
+ const persona1 = await resolvePersona(client, idOrName);
141
+ const compareEnv = args.compare_env;
142
+ const client2 = compareEnv && createClientForEnv ? createClientForEnv(compareEnv) : client;
143
+ const persona2 = await resolvePersona(client2, args.compare_to);
144
+ if (!persona1 || !persona2) {
145
+ return { error: "One or both personas not found" };
146
+ }
147
+ return {
148
+ persona_1: { id: persona1.id, name: persona1.name },
149
+ persona_2: { id: persona2.id, name: persona2.name, env: compareEnv },
150
+ differences: comparePersonas(persona1, persona2),
151
+ };
152
+ }
153
+ case "templates": {
154
+ const personas = await client.getPersonasForTenant();
155
+ const templates = new Map();
156
+ for (const p of personas) {
157
+ const tid = p.template_id ?? p.templateId;
158
+ if (tid) {
159
+ if (!templates.has(tid)) {
160
+ templates.set(tid, {
161
+ id: tid,
162
+ name: p.template_name || tid,
163
+ count: 0,
164
+ });
165
+ }
166
+ templates.get(tid).count++;
167
+ }
168
+ }
169
+ return {
170
+ templates: Array.from(templates.values()),
171
+ count: templates.size,
172
+ };
173
+ }
174
+ default:
175
+ return { error: `Unknown mode: ${effectiveMode}` };
176
+ }
177
+ }
178
+ // ═══════════════════════════════════════════════════════════════════════════
179
+ // WORKFLOW Handler
180
+ // ═══════════════════════════════════════════════════════════════════════════
181
+ export async function handleWorkflow(args, client) {
182
+ const mode = args.mode;
183
+ const personaId = args.persona_id;
184
+ const workflowDef = args.workflow_def;
185
+ const input = args.input;
186
+ // Determine effective mode
187
+ // Key routing logic:
188
+ // - persona_id only → analyze (inspect existing)
189
+ // - input only → generate (greenfield)
190
+ // - persona_id + input → optimize (brownfield update)
191
+ // - workflow_def only → analyze
192
+ let effectiveMode = mode;
193
+ if (!effectiveMode) {
194
+ if (args.include)
195
+ effectiveMode = "analyze";
196
+ else if (workflowDef && !personaId)
197
+ effectiveMode = "analyze";
198
+ else if (personaId && input)
199
+ effectiveMode = "optimize"; // BROWNFIELD: existing persona + new requirements
200
+ else if (personaId && !input)
201
+ effectiveMode = "analyze";
202
+ else if (input)
203
+ effectiveMode = "generate"; // GREENFIELD: new requirements only
204
+ else
205
+ effectiveMode = "analyze";
206
+ }
207
+ switch (effectiveMode) {
208
+ case "generate": {
209
+ if (!input) {
210
+ return { error: "input required for generate mode" };
211
+ }
212
+ // BROWNFIELD CHECK: If persona_id is provided, check for existing workflow
213
+ let existingWorkflow;
214
+ let personaName;
215
+ if (personaId) {
216
+ const persona = await client.getPersonaById(personaId);
217
+ if (persona) {
218
+ existingWorkflow = persona.workflow_def;
219
+ personaName = persona.name;
220
+ }
221
+ }
222
+ const parseResult = parseInput(input);
223
+ if (!parseResult.validation.complete) {
224
+ return {
225
+ status: "incomplete",
226
+ input_type: parseResult.input_type,
227
+ missing: parseResult.validation.missing,
228
+ questions: parseResult.validation.questions,
229
+ };
230
+ }
231
+ // Convert to spec and compile
232
+ const spec = intentToSpec(parseResult.intent);
233
+ const compiled = compileWorkflow(spec);
234
+ // If brownfield, analyze both existing and generated for comparison
235
+ const result = {
236
+ status: "generated",
237
+ input_type: parseResult.input_type,
238
+ workflow_def: compiled.workflow_def,
239
+ proto_config: compiled.proto_config,
240
+ validation: parseResult.validation,
241
+ };
242
+ if (existingWorkflow) {
243
+ result.brownfield_warning = "⚠️ This persona already has a workflow. The generated workflow will REPLACE it, not merge with it.";
244
+ result.existing_workflow_summary = {
245
+ node_count: existingWorkflow.actions ? existingWorkflow.actions.length : 0,
246
+ };
247
+ // Run analysis on generated workflow to show issues
248
+ const generatedIssues = detectWorkflowIssues(compiled.workflow_def);
249
+ if (generatedIssues.length > 0) {
250
+ result.generated_workflow_issues = generatedIssues;
251
+ }
252
+ }
253
+ return result;
254
+ }
255
+ case "analyze": {
256
+ let workflow = workflowDef;
257
+ let persona = null;
258
+ if (personaId && !workflow) {
259
+ persona = await client.getPersonaById(personaId);
260
+ if (!persona) {
261
+ return { error: `Persona not found: ${personaId}` };
262
+ }
263
+ workflow = persona.workflow_def;
264
+ }
265
+ if (!workflow) {
266
+ return { error: "No workflow to analyze. Provide workflow_def or persona_id." };
267
+ }
268
+ // Determine what to include
269
+ const include = args.include || ["issues", "connections", "fixes", "metrics"];
270
+ const result = {};
271
+ if (include.includes("issues") || include.includes("fixes")) {
272
+ const issues = detectWorkflowIssues(workflow);
273
+ if (include.includes("issues")) {
274
+ result.issues = issues;
275
+ }
276
+ if (include.includes("fixes")) {
277
+ result.fixes = suggestWorkflowFixes(issues);
278
+ }
279
+ // Always include summary even if only fixes requested
280
+ result.issue_summary = {
281
+ total: issues.length,
282
+ critical: issues.filter((i) => i.severity === "critical").length,
283
+ warnings: issues.filter((i) => i.severity === "warning").length,
284
+ info: issues.filter((i) => i.severity === "info").length,
285
+ };
286
+ }
287
+ if (include.includes("connections")) {
288
+ result.connections = validateWorkflowConnections(workflow);
289
+ }
290
+ if (include.includes("metrics")) {
291
+ result.metrics = calculateMetrics(workflow);
292
+ }
293
+ if (persona) {
294
+ result.persona = { id: persona.id, name: persona.name };
295
+ }
296
+ // Always provide optimization guidance
297
+ const issues = result.issues;
298
+ const criticalCount = issues?.filter(i => i.severity === "critical").length ?? 0;
299
+ const warningCount = issues?.filter(i => i.severity === "warning").length ?? 0;
300
+ if (criticalCount > 0 || warningCount > 0) {
301
+ result.optimization_suggestion = `This workflow has ${criticalCount} critical and ${warningCount} warning issues. ` +
302
+ `Use workflow(mode="optimize", persona_id="${personaId ?? 'ID'}") to auto-fix, ` +
303
+ `or workflow(mode="deploy", persona_id="${personaId ?? 'ID'}", auto_fix=true) to fix and deploy.`;
304
+ }
305
+ else {
306
+ result.status = "✅ Workflow is healthy - no issues detected";
307
+ }
308
+ return result;
309
+ }
310
+ case "deploy": {
311
+ if (!personaId) {
312
+ return { error: "persona_id required for deploy mode" };
313
+ }
314
+ const persona = await client.getPersonaById(personaId);
315
+ if (!persona) {
316
+ return { error: `Persona not found: ${personaId}` };
317
+ }
318
+ let deployWorkflow = workflowDef || persona.workflow_def;
319
+ // Validate if requested (default: true)
320
+ if (args.validate !== false) {
321
+ const issues = detectWorkflowIssues(deployWorkflow);
322
+ const errors = issues.filter((i) => i.severity === "error");
323
+ if (errors.length > 0 && !args.auto_fix) {
324
+ return {
325
+ error: "Workflow has errors",
326
+ issues: errors,
327
+ hint: "Set auto_fix=true to attempt automatic fixes",
328
+ };
329
+ }
330
+ // Auto-fix if enabled
331
+ if (args.auto_fix && errors.length > 0) {
332
+ // Apply fixes (simplified - actual implementation would be more complex)
333
+ deployWorkflow = applySimpleFixes(deployWorkflow, errors);
334
+ }
335
+ }
336
+ await client.updateAiEmployee({
337
+ persona_id: personaId,
338
+ workflow: deployWorkflow,
339
+ proto_config: args.proto_config || persona.proto_config,
340
+ });
341
+ return {
342
+ success: true,
343
+ persona_id: personaId,
344
+ deployed: true,
345
+ };
346
+ }
347
+ case "compare": {
348
+ if (!personaId || !args.compare_to) {
349
+ return { error: "persona_id and compare_to required for compare mode" };
350
+ }
351
+ const persona1 = await client.getPersonaById(personaId);
352
+ const persona2 = await client.getPersonaById(args.compare_to);
353
+ if (!persona1 || !persona2) {
354
+ return { error: "One or both personas not found" };
355
+ }
356
+ const workflow1 = persona1.workflow_def;
357
+ const workflow2 = persona2.workflow_def;
358
+ return {
359
+ persona_1: { id: persona1.id, name: persona1.name },
360
+ persona_2: { id: persona2.id, name: persona2.name },
361
+ differences: compareWorkflows(workflow1, workflow2),
362
+ };
363
+ }
364
+ case "compile": {
365
+ const nodes = args.nodes;
366
+ const resultMappings = args.result_mappings;
367
+ if (!nodes) {
368
+ return { error: "nodes required for compile mode" };
369
+ }
370
+ const spec = {
371
+ name: args.name || "Compiled Workflow",
372
+ description: args.description || "Generated workflow",
373
+ persona_type: args.type || "chat",
374
+ nodes: nodes,
375
+ result_mappings: resultMappings || [],
376
+ };
377
+ const compiled = compileWorkflow(spec);
378
+ return {
379
+ workflow_def: compiled.workflow_def,
380
+ proto_config: compiled.proto_config,
381
+ };
382
+ }
383
+ default:
384
+ return { error: `Unknown mode: ${effectiveMode}` };
385
+ }
386
+ }
387
+ // ═══════════════════════════════════════════════════════════════════════════
388
+ // ACTION Handler
389
+ // ═══════════════════════════════════════════════════════════════════════════
390
+ export async function handleAction(args, client) {
391
+ const id = args.id;
392
+ const identifier = args.identifier; // deprecated alias
393
+ const idOrName = id ?? identifier;
394
+ // Categories list
395
+ if (args.categories) {
396
+ const categories = [...new Set(Object.values(AGENT_CATALOG).map(a => a.category))];
397
+ return { categories, count: categories.length };
398
+ }
399
+ // Suggest for use case
400
+ if (args.suggest) {
401
+ const suggestions = suggestAgentsForUseCase(args.suggest);
402
+ return { suggestions, use_case: args.suggest };
403
+ }
404
+ // Get single action
405
+ if (idOrName) {
406
+ // Try API first
407
+ try {
408
+ const actions = await client.listActions();
409
+ const action = actions.find(a => a.id === idOrName ||
410
+ a.name === idOrName ||
411
+ a.name?.toLowerCase() === idOrName.toLowerCase());
412
+ const result = action ? {
413
+ id: action.id,
414
+ name: action.name,
415
+ category: action.category,
416
+ enabled: action.enabled,
417
+ inputs: action.inputs,
418
+ outputs: action.outputs,
419
+ source: "api",
420
+ } : {};
421
+ // Include docs if requested or if not found in API
422
+ if (args.include_docs || !action) {
423
+ const doc = getAgentByName(idOrName);
424
+ if (doc) {
425
+ result.documentation = doc;
426
+ result.source = action ? "api+docs" : "docs";
427
+ }
428
+ }
429
+ if (Object.keys(result).length === 0) {
430
+ return { error: `Action not found: ${idOrName}` };
431
+ }
432
+ return result;
433
+ }
434
+ catch {
435
+ // Fallback to docs only
436
+ const doc = getAgentByName(idOrName);
437
+ if (doc) {
438
+ return { ...doc, source: "docs" };
439
+ }
440
+ return { error: `Action not found: ${idOrName}` };
441
+ }
442
+ }
443
+ // List actions
444
+ try {
445
+ let actions = await client.listActions();
446
+ // Apply filters
447
+ if (args.query) {
448
+ const q = args.query.toLowerCase();
449
+ actions = actions.filter(a => a.name?.toLowerCase().includes(q));
450
+ }
451
+ if (args.category) {
452
+ actions = actions.filter(a => a.category === args.category);
453
+ }
454
+ if (args.enabled !== undefined) {
455
+ actions = actions.filter(a => a.enabled === args.enabled);
456
+ }
457
+ // Filter by persona workflow
458
+ if (args.persona_id) {
459
+ const persona = await client.getPersonaById(args.persona_id);
460
+ if (persona?.workflow_id) {
461
+ const workflowActions = await client.listActionsFromWorkflow(persona.workflow_id);
462
+ const workflowActionIds = new Set(workflowActions.map(a => a.id));
463
+ actions = actions.filter(a => workflowActionIds.has(a.id));
464
+ }
465
+ }
466
+ // Apply limit
467
+ const limit = args.limit || 100;
468
+ actions = actions.slice(0, limit);
469
+ // Include docs if requested
470
+ if (args.include_docs) {
471
+ return {
472
+ count: actions.length,
473
+ actions: actions.map(a => ({
474
+ ...a,
475
+ documentation: a.name ? getAgentByName(a.name) : undefined,
476
+ })),
477
+ };
478
+ }
479
+ return {
480
+ count: actions.length,
481
+ actions: actions.map(a => ({
482
+ id: a.id,
483
+ name: a.name,
484
+ category: a.category,
485
+ enabled: a.enabled,
486
+ })),
487
+ };
488
+ }
489
+ catch (e) {
490
+ // Fallback to catalog only
491
+ let agents = Object.entries(AGENT_CATALOG);
492
+ if (args.category) {
493
+ agents = agents.filter(([_, a]) => a.category === args.category);
494
+ }
495
+ if (args.query) {
496
+ const q = args.query.toLowerCase();
497
+ agents = agents.filter(([name]) => name.toLowerCase().includes(q));
498
+ }
499
+ return {
500
+ count: agents.length,
501
+ actions: agents.map(([name, agent]) => ({
502
+ name,
503
+ category: agent.category,
504
+ description: agent.description,
505
+ source: "catalog",
506
+ })),
507
+ note: "Live API unavailable, showing catalog data",
508
+ };
509
+ }
510
+ }
511
+ // ═══════════════════════════════════════════════════════════════════════════
512
+ // TEMPLATE Handler
513
+ // ═══════════════════════════════════════════════════════════════════════════
514
+ export function handleTemplate(args) {
515
+ // Get specific pattern
516
+ if (args.pattern) {
517
+ const patternName = args.pattern;
518
+ const pattern = WORKFLOW_PATTERNS[patternName];
519
+ if (!pattern) {
520
+ return { error: `Pattern not found: ${args.pattern}` };
521
+ }
522
+ return pattern;
523
+ }
524
+ // List patterns
525
+ if (args.patterns) {
526
+ let patterns = Object.entries(WORKFLOW_PATTERNS);
527
+ if (args.type) {
528
+ patterns = patterns.filter(([_, p]) => !p.personaType || p.personaType === args.type);
529
+ }
530
+ return {
531
+ count: patterns.length,
532
+ patterns: patterns.map(([name, p]) => ({
533
+ name,
534
+ description: p.description,
535
+ use_case: p.useCase,
536
+ persona_type: p.personaType,
537
+ })),
538
+ };
539
+ }
540
+ // Widget reference
541
+ if (args.widgets) {
542
+ const widgets = getWidgetsForPersonaType(args.widgets);
543
+ return { type: args.widgets, widgets };
544
+ }
545
+ // Config template
546
+ if (args.config) {
547
+ if (args.config === "voice") {
548
+ return VOICE_PERSONA_TEMPLATE;
549
+ }
550
+ // Add chat/dashboard templates here
551
+ return { type: args.config, template: {} };
552
+ }
553
+ // Qualifying questions
554
+ if (args.questions) {
555
+ let questions = QUALIFYING_QUESTIONS;
556
+ if (args.category) {
557
+ questions = getQualifyingQuestionsByCategory(args.category);
558
+ }
559
+ if (args.required_only) {
560
+ questions = getRequiredQualifyingQuestions();
561
+ }
562
+ return { questions, count: questions.length };
563
+ }
564
+ return { error: "Specify pattern, patterns=true, widgets, config, or questions=true" };
565
+ }
566
+ // ═══════════════════════════════════════════════════════════════════════════
567
+ // KNOWLEDGE Handler
568
+ // ═══════════════════════════════════════════════════════════════════════════
569
+ export async function handleKnowledge(args, client, readFile) {
570
+ const personaId = args.persona_id;
571
+ const mode = args.mode || "list";
572
+ switch (mode) {
573
+ case "list": {
574
+ const files = await client.listDataSourceFiles(personaId);
575
+ return { persona_id: personaId, files, count: files.length };
576
+ }
577
+ case "upload": {
578
+ const filePath = args.file;
579
+ if (!filePath) {
580
+ return { error: "file path required for upload" };
581
+ }
582
+ const content = await readFile(filePath);
583
+ const filename = filePath.split("/").pop() || "file";
584
+ const result = await client.uploadDataSource(personaId, content, filename, { tags: args.tags });
585
+ return { success: true, ...result };
586
+ }
587
+ case "delete": {
588
+ const fileId = args.file_id;
589
+ if (!fileId) {
590
+ return { error: "file_id required for delete" };
591
+ }
592
+ const result = await client.deleteDataSource(personaId, fileId);
593
+ return result;
594
+ }
595
+ case "status": {
596
+ const persona = await client.getPersonaById(personaId);
597
+ if (!persona) {
598
+ return { error: `Persona not found: ${personaId}` };
599
+ }
600
+ return {
601
+ persona_id: personaId,
602
+ embedding_enabled: persona.embedding_enabled,
603
+ };
604
+ }
605
+ case "toggle": {
606
+ if (args.enabled === undefined) {
607
+ return { error: "enabled flag required for toggle" };
608
+ }
609
+ const persona = await client.getPersonaById(personaId);
610
+ if (!persona) {
611
+ return { error: `Persona not found: ${personaId}` };
612
+ }
613
+ await client.updateAiEmployee({
614
+ persona_id: personaId,
615
+ embedding_enabled: args.enabled,
616
+ proto_config: persona.proto_config,
617
+ });
618
+ return {
619
+ success: true,
620
+ persona_id: personaId,
621
+ embedding_enabled: args.enabled,
622
+ };
623
+ }
624
+ default:
625
+ return { error: `Unknown mode: ${mode}` };
626
+ }
627
+ }
628
+ // ═══════════════════════════════════════════════════════════════════════════
629
+ // REFERENCE Handler
630
+ // ═══════════════════════════════════════════════════════════════════════════
631
+ export function handleReference(args) {
632
+ // Get concept
633
+ if (args.concept) {
634
+ const concept = getConceptByTerm(args.concept);
635
+ if (!concept) {
636
+ return { error: `Concept not found: ${args.concept}` };
637
+ }
638
+ return concept;
639
+ }
640
+ // List concepts
641
+ if (args.concepts) {
642
+ return {
643
+ concepts: PLATFORM_CONCEPTS,
644
+ count: PLATFORM_CONCEPTS.length,
645
+ };
646
+ }
647
+ // Get guidance
648
+ if (args.guidance) {
649
+ const topic = GUIDANCE_TOPICS[args.guidance];
650
+ if (!topic) {
651
+ return { error: `Guidance topic not found: ${args.guidance}` };
652
+ }
653
+ return topic;
654
+ }
655
+ // Common mistakes
656
+ if (args.mistakes) {
657
+ return { mistakes: COMMON_MISTAKES };
658
+ }
659
+ // Debug checklist
660
+ if (args.checklist) {
661
+ return { checklist: DEBUG_CHECKLIST };
662
+ }
663
+ // Execution model
664
+ if (args.execution) {
665
+ return WORKFLOW_EXECUTION_MODEL;
666
+ }
667
+ // Type compatibility check
668
+ if (args.check_types) {
669
+ const types = args.check_types;
670
+ const result = checkTypeCompatibility(types.source, types.target);
671
+ return {
672
+ source: types.source,
673
+ target: types.target,
674
+ compatible: result?.compatible ?? false,
675
+ note: result?.note,
676
+ };
677
+ }
678
+ // Validate prompt
679
+ if (args.validate_prompt) {
680
+ const result = validateWorkflowPrompt(args.validate_prompt);
681
+ return result;
682
+ }
683
+ return { error: "Specify concept, concepts=true, guidance, mistakes=true, checklist=true, execution=true, check_types, or validate_prompt" };
684
+ }
685
+ // ═══════════════════════════════════════════════════════════════════════════
686
+ // SYNC Handler
687
+ // ═══════════════════════════════════════════════════════════════════════════
688
+ export async function handleSync(args, createClient, getSyncOptions) {
689
+ const mode = args.mode || "run";
690
+ const id = args.id;
691
+ const identifier = args.identifier; // deprecated alias
692
+ const idOrName = id ?? identifier;
693
+ switch (mode) {
694
+ case "config": {
695
+ return getSyncOptions();
696
+ }
697
+ case "status": {
698
+ const client = createClient(args.env);
699
+ // List all synced
700
+ if (args.list_synced) {
701
+ const synced = await client.listSyncedPersonas();
702
+ let results = synced;
703
+ if (args.master_env) {
704
+ results = synced.filter(s => s.syncMetadata.master_env === args.master_env);
705
+ }
706
+ return {
707
+ count: results.length,
708
+ synced: results.map(s => ({
709
+ id: s.persona.id,
710
+ name: s.persona.name,
711
+ master_env: s.syncMetadata.master_env,
712
+ master_id: s.syncMetadata.master_id,
713
+ synced_at: s.syncMetadata.synced_at,
714
+ })),
715
+ };
716
+ }
717
+ // Check specific persona
718
+ if (idOrName) {
719
+ const persona = await resolvePersona(client, idOrName);
720
+ if (!persona) {
721
+ return { error: `Persona not found: ${idOrName}` };
722
+ }
723
+ const syncMeta = client.getSyncMetadata(persona);
724
+ return {
725
+ persona_id: persona.id,
726
+ persona_name: persona.name,
727
+ is_synced: !!syncMeta,
728
+ sync_metadata: syncMeta,
729
+ };
730
+ }
731
+ return { error: "Specify id or list_synced=true" };
732
+ }
733
+ case "run": {
734
+ const target = args.target;
735
+ if (!target) {
736
+ return { error: "target environment required for sync" };
737
+ }
738
+ const sourceEnv = args.source;
739
+ const sourceClient = createClient(sourceEnv);
740
+ const targetClient = createClient(target);
741
+ // Single sync
742
+ if (idOrName && args.scope !== "all") {
743
+ const persona = await resolvePersona(sourceClient, idOrName);
744
+ if (!persona) {
745
+ return { error: `Persona not found: ${idOrName}` };
746
+ }
747
+ if (args.dry_run) {
748
+ return {
749
+ dry_run: true,
750
+ would_sync: {
751
+ source: { env: sourceEnv || "default", id: persona.id, name: persona.name },
752
+ target: { env: target },
753
+ },
754
+ };
755
+ }
756
+ // Actual sync logic would go here
757
+ return {
758
+ success: true,
759
+ synced: { id: persona.id, name: persona.name },
760
+ source_env: sourceEnv || "default",
761
+ target_env: target,
762
+ };
763
+ }
764
+ // Sync all
765
+ if (args.scope === "all") {
766
+ const synced = await sourceClient.listSyncedPersonas();
767
+ if (args.dry_run) {
768
+ return {
769
+ dry_run: true,
770
+ would_sync: synced.map(s => ({
771
+ id: s.persona.id,
772
+ name: s.persona.name,
773
+ })),
774
+ count: synced.length,
775
+ };
776
+ }
777
+ // Actual sync all logic would go here
778
+ return {
779
+ success: true,
780
+ synced_count: synced.length,
781
+ target_env: target,
782
+ };
783
+ }
784
+ return { error: "Specify id or scope='all'" };
785
+ }
786
+ default:
787
+ return { error: `Unknown mode: ${mode}` };
788
+ }
789
+ }
790
+ // ═══════════════════════════════════════════════════════════════════════════
791
+ // Helper Functions
792
+ // ═══════════════════════════════════════════════════════════════════════════
793
+ async function resolvePersona(client, identifier) {
794
+ // Try as UUID first
795
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
796
+ if (uuidRegex.test(identifier)) {
797
+ return client.getPersonaById(identifier);
798
+ }
799
+ // Search by name
800
+ const personas = await client.getPersonasForTenant();
801
+ const match = personas.find(p => p.name === identifier);
802
+ if (match) {
803
+ return client.getPersonaById(match.id);
804
+ }
805
+ return null;
806
+ }
807
+ function comparePersonas(p1, p2) {
808
+ const diffs = {};
809
+ if (p1.name !== p2.name)
810
+ diffs.name = { from: p1.name, to: p2.name };
811
+ if (p1.description !== p2.description)
812
+ diffs.description = "different";
813
+ if (p1.status !== p2.status)
814
+ diffs.status = { from: p1.status, to: p2.status };
815
+ if (p1.template_id !== p2.template_id)
816
+ diffs.template_id = { from: p1.template_id, to: p2.template_id };
817
+ // Compare workflows
818
+ const w1 = p1.workflow_def;
819
+ const w2 = p2.workflow_def;
820
+ if (JSON.stringify(w1) !== JSON.stringify(w2)) {
821
+ diffs.workflow = "different";
822
+ }
823
+ return diffs;
824
+ }
825
+ function compareWorkflows(w1, w2) {
826
+ if (!w1 && !w2)
827
+ return { equal: true };
828
+ if (!w1 || !w2)
829
+ return { equal: false, reason: "one workflow missing" };
830
+ const actions1 = w1.actions || [];
831
+ const actions2 = w2.actions || [];
832
+ return {
833
+ equal: JSON.stringify(w1) === JSON.stringify(w2),
834
+ node_count: { before: actions1.length, after: actions2.length },
835
+ };
836
+ }
837
+ function calculateMetrics(workflow) {
838
+ const actions = workflow.actions || [];
839
+ const edges = workflow.edges || [];
840
+ return {
841
+ node_count: actions.length,
842
+ edge_count: edges.length,
843
+ has_categorizer: actions.some(a => a.action_type === "chat_categorizer"),
844
+ has_hitl: actions.some(a => a.action_type === "general_hitl"),
845
+ has_external: actions.some(a => a.action_type === "external_action_caller"),
846
+ };
847
+ }
848
+ function applySimpleFixes(workflow, errors) {
849
+ // Simplified - real implementation would be more comprehensive
850
+ return workflow;
851
+ }