@ema.co/mcp-toolkit 2026.2.5 → 2026.2.19
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.
- package/.context/public/guides/ema-user-guide.md +12 -16
- package/.context/public/guides/mcp-tools-guide.md +203 -334
- package/LICENSE +29 -21
- package/README.md +58 -35
- package/dist/mcp/domain/loop-detection.js +97 -0
- package/dist/mcp/domain/proto-constraints.js +284 -0
- package/dist/mcp/domain/structural-rules.js +12 -5
- package/dist/mcp/domain/validation-rules.js +107 -20
- package/dist/mcp/domain/workflow-graph-optimizer.js +235 -0
- package/dist/mcp/domain/workflow-graph-transforms.js +808 -0
- package/dist/mcp/domain/workflow-graph.js +374 -0
- package/dist/mcp/domain/workflow-optimizer.js +10 -4
- package/dist/mcp/guidance.js +54 -31
- package/dist/mcp/handlers/feedback/index.js +139 -0
- package/dist/mcp/handlers/feedback/store.js +262 -0
- package/dist/mcp/handlers/persona/index.js +237 -8
- package/dist/mcp/handlers/persona/schema.js +27 -0
- package/dist/mcp/handlers/reference/index.js +6 -4
- package/dist/mcp/handlers/workflow/index.js +25 -28
- package/dist/mcp/handlers/workflow/optimize.js +73 -33
- package/dist/mcp/handlers/workflow/validation.js +1 -1
- package/dist/mcp/knowledge-types.js +7 -0
- package/dist/mcp/knowledge.js +146 -834
- package/dist/mcp/resources.js +610 -18
- package/dist/mcp/server.js +233 -2156
- package/dist/mcp/tools.js +91 -5
- package/dist/sdk/generated/agent-catalog.js +615 -0
- package/dist/sdk/generated/deprecated-actions.js +182 -96
- package/dist/sdk/generated/proto-fields.js +2 -1
- package/dist/sdk/generated/protos/service/agent_qa/v1/agent_qa_pb.js +460 -21
- package/dist/sdk/generated/protos/service/auth/v1/auth_pb.js +11 -1
- package/dist/sdk/generated/protos/service/dataingest/v1/dataingest_pb.js +173 -66
- package/dist/sdk/generated/protos/service/feedback/v1/feedback_pb.js +43 -1
- package/dist/sdk/generated/protos/service/llmservice/v1/llmservice_pb.js +26 -21
- package/dist/sdk/generated/protos/service/persona/v1/persona_config_pb.js +100 -89
- package/dist/sdk/generated/protos/service/persona/v1/persona_pb.js +126 -116
- package/dist/sdk/generated/protos/service/persona/v1/shared_widgets/widget_types_pb.js +33 -1
- package/dist/sdk/generated/protos/service/persona/v1/voicebot_widgets/widget_types_pb.js +60 -11
- package/dist/sdk/generated/protos/service/tenant/v1/tenant_pb.js +1 -1
- package/dist/sdk/generated/protos/service/user/v1/user_pb.js +1 -1
- package/dist/sdk/generated/protos/service/utils/v1/agent_qa_pb.js +35 -0
- package/dist/sdk/generated/protos/service/workflows/v1/action_registry_pb.js +1 -1
- package/dist/sdk/generated/protos/service/workflows/v1/action_type_pb.js +6 -1
- package/dist/sdk/generated/protos/service/workflows/v1/chatbot_pb.js +106 -11
- package/dist/sdk/generated/protos/service/workflows/v1/common_forms_pb.js +1 -1
- package/dist/sdk/generated/protos/service/workflows/v1/coordinator_pb.js +1 -1
- package/dist/sdk/generated/protos/service/workflows/v1/external_actions_pb.js +31 -1
- package/dist/sdk/generated/protos/service/workflows/v1/well_known_pb.js +5 -1
- package/dist/sdk/generated/protos/service/workflows/v1/workflow_pb.js +1 -1
- package/dist/sdk/generated/protos/util/tracking_metadata_pb.js +1 -1
- package/dist/sdk/generated/widget-catalog.js +60 -0
- package/docs/README.md +17 -9
- package/package.json +2 -2
- package/.context/public/guides/dashboard-operations.md +0 -286
- package/.context/public/guides/email-patterns.md +0 -125
- package/dist/mcp/domain/intent-architect.js +0 -914
- package/dist/mcp/domain/quality-gates.js +0 -110
- package/dist/mcp/domain/workflow-execution-analyzer.js +0 -412
- package/dist/mcp/domain/workflow-intent.js +0 -1806
- package/dist/mcp/domain/workflow-merge.js +0 -449
- package/dist/mcp/domain/workflow-tracer.js +0 -648
- package/dist/mcp/domain/workflow-transformer.js +0 -742
- package/dist/mcp/handlers/persona/intent.js +0 -141
- package/dist/mcp/handlers/workflow/analyze.js +0 -119
- package/dist/mcp/handlers/workflow/compare.js +0 -70
- package/dist/mcp/handlers/workflow/generate.js +0 -384
- package/dist/mcp/handlers-consolidated.js +0 -333
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feedback Store - JSONL file-based storage for feedback and telemetry
|
|
3
|
+
*
|
|
4
|
+
* Provides append-only JSONL storage with rotation to prevent unbounded growth.
|
|
5
|
+
* Data is stored in `.feedback/` directory relative to the toolkit root.
|
|
6
|
+
*
|
|
7
|
+
* Two files:
|
|
8
|
+
* - feedback.jsonl - Explicit agent feedback (gaps, confusion, suggestions)
|
|
9
|
+
* - telemetry.jsonl - Passive telemetry (tool calls, resource fetches, errors)
|
|
10
|
+
*/
|
|
11
|
+
import { promises as fs } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { randomUUID } from "node:crypto";
|
|
14
|
+
import { getToolkitRoot } from "../../../sdk/paths.js";
|
|
15
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
16
|
+
// Constants
|
|
17
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
18
|
+
const FEEDBACK_DIR = ".feedback";
|
|
19
|
+
const FEEDBACK_FILE = "feedback.jsonl";
|
|
20
|
+
const TELEMETRY_FILE = "telemetry.jsonl";
|
|
21
|
+
const MAX_TELEMETRY_ENTRIES = 1000;
|
|
22
|
+
const MAX_FEEDBACK_ENTRIES = 500;
|
|
23
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
// Store
|
|
25
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* Get the feedback directory path, creating it if necessary.
|
|
28
|
+
*/
|
|
29
|
+
async function ensureFeedbackDir(rootOverride) {
|
|
30
|
+
const root = rootOverride ?? getToolkitRoot();
|
|
31
|
+
const dir = join(root, FEEDBACK_DIR);
|
|
32
|
+
await fs.mkdir(dir, { recursive: true });
|
|
33
|
+
return dir;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Append a single JSON line to a JSONL file.
|
|
37
|
+
*/
|
|
38
|
+
async function appendJsonl(filePath, entry) {
|
|
39
|
+
const line = JSON.stringify(entry) + "\n";
|
|
40
|
+
await fs.appendFile(filePath, line, "utf-8");
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Read all entries from a JSONL file.
|
|
44
|
+
* Returns empty array if file doesn't exist.
|
|
45
|
+
*/
|
|
46
|
+
async function readJsonl(filePath) {
|
|
47
|
+
try {
|
|
48
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
49
|
+
return content
|
|
50
|
+
.split("\n")
|
|
51
|
+
.filter((line) => line.trim().length > 0)
|
|
52
|
+
.map((line) => {
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(line);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Skip corrupted lines (e.g., from crash mid-write)
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
.filter((entry) => entry !== null);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
if (err.code === "ENOENT") {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Rotate a JSONL file by keeping only the last N entries.
|
|
72
|
+
*/
|
|
73
|
+
async function rotateJsonl(filePath, maxEntries) {
|
|
74
|
+
const entries = await readJsonl(filePath);
|
|
75
|
+
if (entries.length <= maxEntries)
|
|
76
|
+
return;
|
|
77
|
+
const kept = entries.slice(entries.length - maxEntries);
|
|
78
|
+
const content = kept.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
79
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
80
|
+
}
|
|
81
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
82
|
+
// Public API
|
|
83
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
84
|
+
/**
|
|
85
|
+
* Submit a feedback entry from an agent.
|
|
86
|
+
*/
|
|
87
|
+
export async function submitFeedback(entry, rootOverride) {
|
|
88
|
+
const dir = await ensureFeedbackDir(rootOverride);
|
|
89
|
+
const filePath = join(dir, FEEDBACK_FILE);
|
|
90
|
+
const full = {
|
|
91
|
+
id: randomUUID(),
|
|
92
|
+
ts: new Date().toISOString(),
|
|
93
|
+
...entry,
|
|
94
|
+
};
|
|
95
|
+
await appendJsonl(filePath, full);
|
|
96
|
+
// Log to stderr for visibility (stdout is the MCP stdio transport - never write there)
|
|
97
|
+
console.error(`[FEEDBACK] ${full.category}: ${full.message}`);
|
|
98
|
+
return full;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Record a telemetry event (passive, fire-and-forget).
|
|
102
|
+
*/
|
|
103
|
+
export async function recordTelemetry(entry, rootOverride) {
|
|
104
|
+
try {
|
|
105
|
+
const dir = await ensureFeedbackDir(rootOverride);
|
|
106
|
+
const filePath = join(dir, TELEMETRY_FILE);
|
|
107
|
+
const full = {
|
|
108
|
+
ts: new Date().toISOString(),
|
|
109
|
+
...entry,
|
|
110
|
+
};
|
|
111
|
+
await appendJsonl(filePath, full);
|
|
112
|
+
// Rotate periodically (check every 100 writes based on simple modulo of time)
|
|
113
|
+
// We use a lightweight check: rotate if file > MAX * 1.5 entries
|
|
114
|
+
const now = Date.now();
|
|
115
|
+
if (now % 100 < 5) {
|
|
116
|
+
await rotateJsonl(filePath, MAX_TELEMETRY_ENTRIES);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Telemetry is fire-and-forget; never block tool execution
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* List recent feedback entries.
|
|
125
|
+
*/
|
|
126
|
+
export async function listFeedback(options, rootOverride) {
|
|
127
|
+
const dir = await ensureFeedbackDir(rootOverride);
|
|
128
|
+
const filePath = join(dir, FEEDBACK_FILE);
|
|
129
|
+
let entries = await readJsonl(filePath);
|
|
130
|
+
if (options?.category) {
|
|
131
|
+
entries = entries.filter((e) => e.category === options.category);
|
|
132
|
+
}
|
|
133
|
+
const limit = options?.limit ?? 50;
|
|
134
|
+
return entries.slice(-limit);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* List recent telemetry entries.
|
|
138
|
+
*/
|
|
139
|
+
export async function listTelemetry(options, rootOverride) {
|
|
140
|
+
const dir = await ensureFeedbackDir(rootOverride);
|
|
141
|
+
const filePath = join(dir, TELEMETRY_FILE);
|
|
142
|
+
let entries = await readJsonl(filePath);
|
|
143
|
+
if (options?.type) {
|
|
144
|
+
entries = entries.filter((e) => e.type === options.type);
|
|
145
|
+
}
|
|
146
|
+
const limit = options?.limit ?? 100;
|
|
147
|
+
return entries.slice(-limit);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Analyze feedback and telemetry to produce actionable insights.
|
|
151
|
+
*/
|
|
152
|
+
export async function analyzeFeedback(rootOverride) {
|
|
153
|
+
const dir = await ensureFeedbackDir(rootOverride);
|
|
154
|
+
const feedback = await readJsonl(join(dir, FEEDBACK_FILE));
|
|
155
|
+
const telemetry = await readJsonl(join(dir, TELEMETRY_FILE));
|
|
156
|
+
// Category breakdown
|
|
157
|
+
const categoryBreakdown = {};
|
|
158
|
+
for (const entry of feedback) {
|
|
159
|
+
categoryBreakdown[entry.category] = (categoryBreakdown[entry.category] ?? 0) + 1;
|
|
160
|
+
}
|
|
161
|
+
// Hot spots - which tools/operations have the most issues
|
|
162
|
+
const issuesByTool = {};
|
|
163
|
+
const issuesByOperation = {};
|
|
164
|
+
const negativeFeedback = feedback.filter((e) => e.category !== "success");
|
|
165
|
+
for (const entry of negativeFeedback) {
|
|
166
|
+
if (entry.tool) {
|
|
167
|
+
issuesByTool[entry.tool] = (issuesByTool[entry.tool] ?? 0) + 1;
|
|
168
|
+
}
|
|
169
|
+
if (entry.operation) {
|
|
170
|
+
issuesByOperation[entry.operation] = (issuesByOperation[entry.operation] ?? 0) + 1;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Tool usage from telemetry
|
|
174
|
+
const toolUsage = {};
|
|
175
|
+
for (const entry of telemetry.filter((t) => t.type === "tool_call")) {
|
|
176
|
+
const key = entry.tool ?? "unknown";
|
|
177
|
+
if (!toolUsage[key]) {
|
|
178
|
+
toolUsage[key] = { total: 0, errors: 0 };
|
|
179
|
+
}
|
|
180
|
+
toolUsage[key].total++;
|
|
181
|
+
if (!entry.ok) {
|
|
182
|
+
toolUsage[key].errors++;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Resource usage from telemetry
|
|
186
|
+
const resourceUsage = {};
|
|
187
|
+
for (const entry of telemetry.filter((t) => t.type === "resource_fetch")) {
|
|
188
|
+
const uri = entry.resource_uri ?? "unknown";
|
|
189
|
+
resourceUsage[uri] = (resourceUsage[uri] ?? 0) + 1;
|
|
190
|
+
}
|
|
191
|
+
// Error patterns
|
|
192
|
+
const errorMessages = {};
|
|
193
|
+
for (const entry of telemetry.filter((t) => t.type === "error" && t.error_message)) {
|
|
194
|
+
const msg = entry.error_message;
|
|
195
|
+
// Normalize error messages by truncating at 100 chars
|
|
196
|
+
const normalized = msg.length > 100 ? msg.slice(0, 100) + "..." : msg;
|
|
197
|
+
errorMessages[normalized] = (errorMessages[normalized] ?? 0) + 1;
|
|
198
|
+
}
|
|
199
|
+
// High-severity items
|
|
200
|
+
const highSeverity = feedback.filter((e) => e.severity === "high");
|
|
201
|
+
// Actionable items
|
|
202
|
+
const actionableItems = [];
|
|
203
|
+
// Tools with high error rates
|
|
204
|
+
for (const [tool, usage] of Object.entries(toolUsage)) {
|
|
205
|
+
const errorRate = usage.errors / usage.total;
|
|
206
|
+
if (errorRate > 0.3 && usage.total >= 5) {
|
|
207
|
+
actionableItems.push(`Tool "${tool}" has ${Math.round(errorRate * 100)}% error rate (${usage.errors}/${usage.total}) - investigate error handling and documentation`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Gaps are high-priority feedback
|
|
211
|
+
const gaps = feedback.filter((e) => e.category === "gap");
|
|
212
|
+
for (const gap of gaps) {
|
|
213
|
+
actionableItems.push(`Documentation gap: ${gap.message}${gap.tool ? ` (tool: ${gap.tool})` : ""}`);
|
|
214
|
+
}
|
|
215
|
+
// Confusion items suggest unclear docs
|
|
216
|
+
const confusions = feedback.filter((e) => e.category === "confusion");
|
|
217
|
+
for (const confusion of confusions) {
|
|
218
|
+
actionableItems.push(`Unclear guidance: ${confusion.message}${confusion.tool ? ` (tool: ${confusion.tool})` : ""}`);
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
summary: {
|
|
222
|
+
total_feedback: feedback.length,
|
|
223
|
+
total_telemetry: telemetry.length,
|
|
224
|
+
feedback_period: feedback.length > 0
|
|
225
|
+
? { from: feedback[0].ts, to: feedback[feedback.length - 1].ts }
|
|
226
|
+
: null,
|
|
227
|
+
telemetry_period: telemetry.length > 0
|
|
228
|
+
? { from: telemetry[0].ts, to: telemetry[telemetry.length - 1].ts }
|
|
229
|
+
: null,
|
|
230
|
+
},
|
|
231
|
+
category_breakdown: categoryBreakdown,
|
|
232
|
+
hot_spots: {
|
|
233
|
+
by_tool: sortDescending(issuesByTool),
|
|
234
|
+
by_operation: sortDescending(issuesByOperation),
|
|
235
|
+
},
|
|
236
|
+
tool_usage: toolUsage,
|
|
237
|
+
resource_usage: sortDescending(resourceUsage),
|
|
238
|
+
error_patterns: sortDescending(errorMessages),
|
|
239
|
+
high_severity: highSeverity,
|
|
240
|
+
actionable_items: actionableItems,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Rotate both feedback and telemetry files to prevent unbounded growth.
|
|
245
|
+
*/
|
|
246
|
+
export async function rotateLogs(rootOverride) {
|
|
247
|
+
const dir = await ensureFeedbackDir(rootOverride);
|
|
248
|
+
await rotateJsonl(join(dir, FEEDBACK_FILE), MAX_FEEDBACK_ENTRIES);
|
|
249
|
+
await rotateJsonl(join(dir, TELEMETRY_FILE), MAX_TELEMETRY_ENTRIES);
|
|
250
|
+
const feedbackEntries = await readJsonl(join(dir, FEEDBACK_FILE));
|
|
251
|
+
const telemetryEntries = await readJsonl(join(dir, TELEMETRY_FILE));
|
|
252
|
+
return {
|
|
253
|
+
feedback_kept: feedbackEntries.length,
|
|
254
|
+
telemetry_kept: telemetryEntries.length,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
258
|
+
// Helpers
|
|
259
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
260
|
+
function sortDescending(record) {
|
|
261
|
+
return Object.fromEntries(Object.entries(record).sort((a, b) => b[1] - a[1]));
|
|
262
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Persona Handler - Dispatch Table
|
|
2
|
+
* Persona Handler - Dispatch Table + Top-Level Router
|
|
3
3
|
*
|
|
4
4
|
* This module exports persona mode handlers using a dispatch table
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* and provides the top-level `handlePersona` function that was previously
|
|
6
|
+
* (previously in the now-deleted handlers-consolidated.ts).
|
|
7
7
|
*
|
|
8
8
|
* Persona modes:
|
|
9
9
|
* - get: Fetch single persona (LLM then analyzes the data)
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* - update: Update persona config
|
|
14
14
|
* - delete: Delete persona (with confirmation)
|
|
15
15
|
* - create/clone: Create new persona from template or clone existing
|
|
16
|
-
* -
|
|
16
|
+
* - schema: Get persona input schema (dashboard columns, types)
|
|
17
17
|
*
|
|
18
18
|
* REMOVED (LLM does these):
|
|
19
19
|
* - analyze: LLM analyzes data from 'get'
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
* - snapshot/version_create, history/version_list, version_get
|
|
24
24
|
* - version_compare, restore/version_restore, version_policy
|
|
25
25
|
*/
|
|
26
|
+
import { resolvePersona } from "../utils.js";
|
|
27
|
+
import { checkDeprecatedParams, addDeprecationWarnings } from "../deprecation.js";
|
|
28
|
+
import { handleData } from "../data/index.js";
|
|
29
|
+
import { handleWorkflow } from "../workflow/index.js";
|
|
26
30
|
// Import mode handlers
|
|
27
31
|
import { handleGet } from "./get.js";
|
|
28
32
|
import { handleList } from "./list.js";
|
|
@@ -31,7 +35,7 @@ import { handleSanitize } from "./sanitize.js";
|
|
|
31
35
|
import { handleUpdate } from "./update.js";
|
|
32
36
|
import { handleDelete } from "./delete.js";
|
|
33
37
|
import { handleCreate } from "./create.js";
|
|
34
|
-
import {
|
|
38
|
+
import { handleSchema } from "./schema.js";
|
|
35
39
|
/**
|
|
36
40
|
* Dispatch table for persona modes
|
|
37
41
|
*
|
|
@@ -48,7 +52,7 @@ export const PERSONA_MODE_HANDLERS = {
|
|
|
48
52
|
delete: handleDelete,
|
|
49
53
|
create: handleCreate,
|
|
50
54
|
clone: handleCreate, // Clone uses same handler as create
|
|
51
|
-
|
|
55
|
+
schema: handleSchema,
|
|
52
56
|
};
|
|
53
57
|
/**
|
|
54
58
|
* Check if a mode has been extracted to this module
|
|
@@ -70,7 +74,232 @@ export { handleSanitize } from "./sanitize.js";
|
|
|
70
74
|
export { handleUpdate } from "./update.js";
|
|
71
75
|
export { handleDelete } from "./delete.js";
|
|
72
76
|
export { handleCreate } from "./create.js";
|
|
73
|
-
export {
|
|
74
|
-
// REMOVED: handleCompare, handleAnalyze - LLM does analysis/comparison
|
|
77
|
+
export { handleSchema } from "./schema.js";
|
|
75
78
|
// Version management
|
|
76
79
|
export { handleVersion, isVersionMode } from "./version.js";
|
|
80
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
81
|
+
// Top-level Persona Router
|
|
82
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
83
|
+
import { handleVersion } from "./version.js";
|
|
84
|
+
/**
|
|
85
|
+
* Top-level persona handler that routes to mode-specific handlers.
|
|
86
|
+
*
|
|
87
|
+
* Handles:
|
|
88
|
+
* - Data sub-resource routing (data={method:...})
|
|
89
|
+
* - Legacy workflow routing (optimize flag, workflow_def without update mode)
|
|
90
|
+
* - Mode resolution and dispatch
|
|
91
|
+
* - Version management modes
|
|
92
|
+
*/
|
|
93
|
+
export async function handlePersona(args, client, getTemplateId, createClientForEnv, versionContext) {
|
|
94
|
+
const deprecationWarnings = checkDeprecatedParams(args);
|
|
95
|
+
for (const warning of deprecationWarnings) {
|
|
96
|
+
console.warn(`[persona] Deprecation: ${warning}`);
|
|
97
|
+
}
|
|
98
|
+
const id = args.id;
|
|
99
|
+
const identifier = args.identifier;
|
|
100
|
+
const idOrName = id ?? identifier;
|
|
101
|
+
// Support both 'method' (new) and 'mode' (legacy) - method takes precedence
|
|
102
|
+
const method = args.method;
|
|
103
|
+
const mode = method ?? args.mode;
|
|
104
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
105
|
+
// DATA SUB-RESOURCE: Handle data={method:...} early
|
|
106
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
107
|
+
const dataArg = args.data;
|
|
108
|
+
if (dataArg && typeof dataArg === "object") {
|
|
109
|
+
if (!idOrName) {
|
|
110
|
+
return {
|
|
111
|
+
error: "Data operations require persona id",
|
|
112
|
+
hint: "Use persona(id=\"abc\", data={method:\"list\"})",
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const dataMethod = dataArg.method;
|
|
116
|
+
if (!dataMethod) {
|
|
117
|
+
return {
|
|
118
|
+
error: "Data operations require explicit method",
|
|
119
|
+
hint: "Use data={method:\"list|schema|upload|copy|delete|embed|search\", ...}",
|
|
120
|
+
valid_methods: ["list", "schema", "upload", "copy", "delete", "embed", "search"],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const methodToMode = {
|
|
124
|
+
"list": "list",
|
|
125
|
+
"schema": "schema",
|
|
126
|
+
"upload": "upload",
|
|
127
|
+
"copy": "dashboard_clone",
|
|
128
|
+
"delete": "delete",
|
|
129
|
+
"embed": "embed",
|
|
130
|
+
"search": "search",
|
|
131
|
+
};
|
|
132
|
+
const mappedMode = methodToMode[dataMethod];
|
|
133
|
+
if (!mappedMode) {
|
|
134
|
+
return {
|
|
135
|
+
error: `Unknown data method: ${dataMethod}`,
|
|
136
|
+
valid_methods: Object.keys(methodToMode),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const dataArgs = {
|
|
140
|
+
persona_id: idOrName,
|
|
141
|
+
mode: mappedMode,
|
|
142
|
+
};
|
|
143
|
+
if (dataArg.from)
|
|
144
|
+
dataArgs.source_persona_id = dataArg.from;
|
|
145
|
+
if (dataArg.sanitize !== undefined)
|
|
146
|
+
dataArgs.sanitize = dataArg.sanitize;
|
|
147
|
+
if (dataArg.path)
|
|
148
|
+
dataArgs.file_path = dataArg.path;
|
|
149
|
+
if (dataArg.content)
|
|
150
|
+
dataArgs.content = dataArg.content;
|
|
151
|
+
if (dataArg.file_id)
|
|
152
|
+
dataArgs.file_id = dataArg.file_id;
|
|
153
|
+
else if (dataArg.id)
|
|
154
|
+
dataArgs.file_id = dataArg.id;
|
|
155
|
+
if (dataArg.enabled !== undefined)
|
|
156
|
+
dataArgs.embed = dataArg.enabled;
|
|
157
|
+
if (dataArg.query)
|
|
158
|
+
dataArgs.query = dataArg.query;
|
|
159
|
+
const readFile = async (path) => {
|
|
160
|
+
const fs = await import("fs/promises");
|
|
161
|
+
return fs.readFile(path);
|
|
162
|
+
};
|
|
163
|
+
return handleData(dataArgs, client, readFile);
|
|
164
|
+
}
|
|
165
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
166
|
+
// ROUTING: Persona operations
|
|
167
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
168
|
+
// LLM-DRIVEN ARCHITECTURE:
|
|
169
|
+
// - persona tool: config changes only (name, description, widgets)
|
|
170
|
+
// - workflow tool: ALL workflow changes (get → modify → deploy)
|
|
171
|
+
// mode="update" → modular handleUpdate (config changes, workflow_spec for rewire/remove only)
|
|
172
|
+
if (mode === "update" && idOrName) {
|
|
173
|
+
return getPersonaModeHandler("update")(args, client);
|
|
174
|
+
}
|
|
175
|
+
// Legacy: optimize flag routes to workflow handler
|
|
176
|
+
const optimize = args.optimize;
|
|
177
|
+
const workflowDef = args.workflow_def ?? args.workflow;
|
|
178
|
+
if (optimize || (idOrName && workflowDef && mode !== "update")) {
|
|
179
|
+
const workflowArgs = {
|
|
180
|
+
...args,
|
|
181
|
+
persona_id: idOrName,
|
|
182
|
+
};
|
|
183
|
+
delete workflowArgs.id;
|
|
184
|
+
delete workflowArgs.identifier;
|
|
185
|
+
return handleWorkflow(workflowArgs, client, getTemplateId);
|
|
186
|
+
}
|
|
187
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
188
|
+
// Standard persona operations (get, list, compare, version management)
|
|
189
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
190
|
+
const fromParam = args.from;
|
|
191
|
+
const cloneFromParam = args.clone_from;
|
|
192
|
+
const templateIdParam = args.template_id;
|
|
193
|
+
const hasBase = fromParam || cloneFromParam || templateIdParam;
|
|
194
|
+
let effectiveMode = mode;
|
|
195
|
+
if (!effectiveMode) {
|
|
196
|
+
const availableMethods = ["list", "get", "create", "update", "delete", "analyze", "sanitize", "snapshot", "history", "restore", "compare"];
|
|
197
|
+
if (idOrName && args.proto_config) {
|
|
198
|
+
return {
|
|
199
|
+
error: "Explicit method required",
|
|
200
|
+
message: "You provided id and proto_config. Did you want to update this persona?",
|
|
201
|
+
suggested_method: "update",
|
|
202
|
+
hint: "Use method='update' to apply changes",
|
|
203
|
+
example: `persona(method="update", id="${idOrName}", config={...})`,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
else if (idOrName) {
|
|
207
|
+
return {
|
|
208
|
+
error: "Explicit method required",
|
|
209
|
+
message: "You provided a persona id/name. What operation would you like to perform?",
|
|
210
|
+
valid_methods: ["get", "update", "delete", "sanitize", "snapshot", "history", "restore"],
|
|
211
|
+
hint: "LLM does analysis/comparison. Use method='get' to fetch data, then reason about it.",
|
|
212
|
+
example: `persona(method="get", id="${idOrName}")`,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
else if (hasBase || (args.name && args.type)) {
|
|
216
|
+
return {
|
|
217
|
+
error: "Explicit method required",
|
|
218
|
+
message: "You provided creation parameters. Did you want to create a new persona?",
|
|
219
|
+
suggested_method: "create",
|
|
220
|
+
hint: "Use method='create'",
|
|
221
|
+
example: `persona(method="create", name="...", type="voice")`,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
return {
|
|
226
|
+
error: "Explicit method required",
|
|
227
|
+
message: "All persona operations require explicit method parameter",
|
|
228
|
+
valid_methods: availableMethods,
|
|
229
|
+
examples: [
|
|
230
|
+
'persona(method="list") - list all personas',
|
|
231
|
+
'persona(method="get", id="...") - get specific persona',
|
|
232
|
+
'persona(method="create", name="...", type="voice") - create new',
|
|
233
|
+
'persona(method="update", id="...", config={...}) - update config',
|
|
234
|
+
'persona(method="delete", id="...", confirm=true) - delete',
|
|
235
|
+
],
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
240
|
+
// Dispatch to handlers via dispatch table
|
|
241
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
242
|
+
if (hasExtractedHandler(effectiveMode)) {
|
|
243
|
+
const handler = getPersonaModeHandler(effectiveMode);
|
|
244
|
+
if (handler) {
|
|
245
|
+
let extraContext = undefined;
|
|
246
|
+
if (effectiveMode === "create" || effectiveMode === "clone") {
|
|
247
|
+
extraContext = getTemplateId;
|
|
248
|
+
}
|
|
249
|
+
else if (effectiveMode === "compare") {
|
|
250
|
+
extraContext = createClientForEnv;
|
|
251
|
+
}
|
|
252
|
+
const result = await handler(args, client, extraContext);
|
|
253
|
+
return addDeprecationWarnings(result, deprecationWarnings);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
switch (effectiveMode) {
|
|
257
|
+
// Fallback cases for modes already in dispatch table (should not be reached)
|
|
258
|
+
case "get":
|
|
259
|
+
case "list":
|
|
260
|
+
case "templates":
|
|
261
|
+
case "sanitize":
|
|
262
|
+
case "update":
|
|
263
|
+
case "delete": {
|
|
264
|
+
return { error: `Mode "${effectiveMode}" should be handled by extracted handler` };
|
|
265
|
+
}
|
|
266
|
+
// Clone and create - handled by dispatch table
|
|
267
|
+
case "clone":
|
|
268
|
+
case "create": {
|
|
269
|
+
return { error: `Mode "${effectiveMode}" should be handled by extracted handler` };
|
|
270
|
+
}
|
|
271
|
+
// Analyze and compare - LLM does this, not MCP
|
|
272
|
+
case "analyze":
|
|
273
|
+
case "compare": {
|
|
274
|
+
return {
|
|
275
|
+
error: `Method "${effectiveMode}" removed - LLM does analysis/comparison`,
|
|
276
|
+
hint: "Use method='get' to fetch persona data, then do your own analysis/comparison.",
|
|
277
|
+
example: `persona(method="get", id="...", include_workflow=true)`,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
// ─────────────── Version Management Modes ───────────────
|
|
281
|
+
case "snapshot":
|
|
282
|
+
case "history":
|
|
283
|
+
case "restore":
|
|
284
|
+
case "version_create":
|
|
285
|
+
case "version_list":
|
|
286
|
+
case "version_get":
|
|
287
|
+
case "version_compare":
|
|
288
|
+
case "version_restore":
|
|
289
|
+
case "version_policy": {
|
|
290
|
+
if (!idOrName) {
|
|
291
|
+
return { error: `id required for ${effectiveMode} mode` };
|
|
292
|
+
}
|
|
293
|
+
if (!versionContext) {
|
|
294
|
+
return { error: "Version tracking not configured. Provide workspaceRoot in context." };
|
|
295
|
+
}
|
|
296
|
+
const persona = await resolvePersona(client, idOrName);
|
|
297
|
+
if (!persona) {
|
|
298
|
+
return { error: `Persona not found: ${idOrName}` };
|
|
299
|
+
}
|
|
300
|
+
return handleVersion(effectiveMode, args, client, persona, versionContext);
|
|
301
|
+
}
|
|
302
|
+
default:
|
|
303
|
+
return { error: `Unknown mode: ${effectiveMode}` };
|
|
304
|
+
}
|
|
305
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema Handler - Get persona input schema (dashboard columns, types)
|
|
3
|
+
*/
|
|
4
|
+
import { resolvePersona } from "../utils.js";
|
|
5
|
+
export async function handleSchema(args, client) {
|
|
6
|
+
const id = (args.id ?? args.identifier);
|
|
7
|
+
if (!id) {
|
|
8
|
+
return { error: "id required for schema mode" };
|
|
9
|
+
}
|
|
10
|
+
const persona = await resolvePersona(client, id);
|
|
11
|
+
if (!persona) {
|
|
12
|
+
return { error: `Persona not found: ${id}` };
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const schema = await client.getDashboardSchema(persona.id, persona.id);
|
|
16
|
+
return {
|
|
17
|
+
mode: "schema",
|
|
18
|
+
persona_id: persona.id,
|
|
19
|
+
persona_name: persona.name,
|
|
20
|
+
schema: schema,
|
|
21
|
+
column_count: schema.columns?.length ?? 0,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
return { error: `Failed to get schema: ${error instanceof Error ? error.message : String(error)}` };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Unified reference tool for envs, actions, templates, patterns, concepts, guidance.
|
|
5
5
|
*/
|
|
6
6
|
import { errorResult } from "../types.js";
|
|
7
|
-
import { AGENT_CATALOG, WORKFLOW_PATTERNS, QUALIFYING_QUESTIONS, PLATFORM_CONCEPTS, WORKFLOW_EXECUTION_MODEL, COMMON_MISTAKES, DEBUG_CHECKLIST, GUIDANCE_TOPICS, getAgentByName, getWidgetsForPersonaType, checkTypeCompatibility, getQualifyingQuestionsByCategory, getRequiredQualifyingQuestions, getConceptByTerm, suggestAgentsForUseCase,
|
|
7
|
+
import { AGENT_CATALOG, WORKFLOW_PATTERNS, QUALIFYING_QUESTIONS, PLATFORM_CONCEPTS, WORKFLOW_EXECUTION_MODEL, COMMON_MISTAKES, DEBUG_CHECKLIST, GUIDANCE_TOPICS, getAgentByName, getWidgetsForPersonaType, checkTypeCompatibility, getQualifyingQuestionsByCategory, getRequiredQualifyingQuestions, getConceptByTerm, suggestAgentsForUseCase, } from "../../knowledge.js";
|
|
8
8
|
/**
|
|
9
9
|
* Handle reference tool requests - unified lookup for all reference data
|
|
10
10
|
*/
|
|
@@ -284,10 +284,12 @@ export async function handleReference(args, context) {
|
|
|
284
284
|
note: result?.note,
|
|
285
285
|
};
|
|
286
286
|
}
|
|
287
|
-
// Validate prompt
|
|
287
|
+
// Validate prompt (deprecated — LLM responsibility)
|
|
288
288
|
if (args.validate_prompt) {
|
|
289
|
-
|
|
290
|
-
|
|
289
|
+
return {
|
|
290
|
+
error: "Workflow prompt validation is the LLM's responsibility. Use workflow(mode='validate') for structural validation.",
|
|
291
|
+
_deprecated: true,
|
|
292
|
+
};
|
|
291
293
|
}
|
|
292
294
|
// ─────────────────────────────────────────────────────────────────────────
|
|
293
295
|
// type="demo_kits" or demo_kits=true - List demo kits
|