@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.
- package/LICENSE +21 -0
- package/README.md +321 -0
- package/config.example.yaml +32 -0
- package/dist/cli/index.js +333 -0
- package/dist/config.js +136 -0
- package/dist/emaClient.js +398 -0
- package/dist/index.js +109 -0
- package/dist/mcp/handlers-consolidated.js +851 -0
- package/dist/mcp/index.js +15 -0
- package/dist/mcp/prompts.js +1753 -0
- package/dist/mcp/resources.js +624 -0
- package/dist/mcp/server.js +4723 -0
- package/dist/mcp/tools-consolidated.js +590 -0
- package/dist/mcp/tools-legacy.js +736 -0
- package/dist/models.js +8 -0
- package/dist/scheduler.js +21 -0
- package/dist/sdk/client.js +788 -0
- package/dist/sdk/config.js +136 -0
- package/dist/sdk/contracts.js +429 -0
- package/dist/sdk/generation-schema.js +189 -0
- package/dist/sdk/index.js +39 -0
- package/dist/sdk/knowledge.js +2780 -0
- package/dist/sdk/models.js +8 -0
- package/dist/sdk/state.js +88 -0
- package/dist/sdk/sync-options.js +216 -0
- package/dist/sdk/sync.js +220 -0
- package/dist/sdk/validation-rules.js +355 -0
- package/dist/sdk/workflow-generator.js +291 -0
- package/dist/sdk/workflow-intent.js +1585 -0
- package/dist/state.js +88 -0
- package/dist/sync.js +416 -0
- package/dist/syncOptions.js +216 -0
- package/dist/ui.js +334 -0
- package/docs/advisor-comms-assistant-fixes.md +175 -0
- package/docs/api-contracts.md +216 -0
- package/docs/auto-builder-analysis.md +271 -0
- package/docs/data-architecture.md +166 -0
- package/docs/ema-auto-builder-guide.html +394 -0
- package/docs/ema-user-guide.md +1121 -0
- package/docs/mcp-tools-guide.md +149 -0
- package/docs/naming-conventions.md +218 -0
- package/docs/tool-consolidation-proposal.md +427 -0
- package/package.json +98 -0
- package/resources/templates/chat-ai/README.md +119 -0
- package/resources/templates/chat-ai/persona-config.json +111 -0
- package/resources/templates/dashboard-ai/README.md +156 -0
- package/resources/templates/dashboard-ai/persona-config.json +180 -0
- package/resources/templates/voice-ai/README.md +123 -0
- package/resources/templates/voice-ai/persona-config.json +74 -0
- 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
|
+
}
|