@ema.co/mcp-toolkit 2026.2.5 → 2026.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of @ema.co/mcp-toolkit might be problematic. Click here for more details.

Files changed (44) hide show
  1. package/.context/public/guides/dashboard-operations.md +63 -0
  2. package/.context/public/guides/workflow-builder-patterns.md +708 -0
  3. package/LICENSE +29 -21
  4. package/README.md +58 -35
  5. package/dist/mcp/domain/proto-constraints.js +284 -0
  6. package/dist/mcp/domain/structural-rules.js +8 -0
  7. package/dist/mcp/domain/validation-rules.js +102 -15
  8. package/dist/mcp/domain/workflow-graph-optimizer.js +235 -0
  9. package/dist/mcp/domain/workflow-graph-transforms.js +808 -0
  10. package/dist/mcp/domain/workflow-graph.js +376 -0
  11. package/dist/mcp/domain/workflow-optimizer.js +10 -4
  12. package/dist/mcp/guidance.js +45 -2
  13. package/dist/mcp/handlers/feedback/index.js +139 -0
  14. package/dist/mcp/handlers/feedback/store.js +262 -0
  15. package/dist/mcp/handlers/workflow/index.js +12 -11
  16. package/dist/mcp/handlers/workflow/optimize.js +73 -33
  17. package/dist/mcp/knowledge.js +87 -36
  18. package/dist/mcp/resources.js +393 -17
  19. package/dist/mcp/server.js +38 -4
  20. package/dist/mcp/tools.js +89 -2
  21. package/dist/sdk/generated/deprecated-actions.js +182 -96
  22. package/dist/sdk/generated/proto-fields.js +2 -1
  23. package/dist/sdk/generated/protos/service/agent_qa/v1/agent_qa_pb.js +460 -21
  24. package/dist/sdk/generated/protos/service/auth/v1/auth_pb.js +11 -1
  25. package/dist/sdk/generated/protos/service/dataingest/v1/dataingest_pb.js +173 -66
  26. package/dist/sdk/generated/protos/service/feedback/v1/feedback_pb.js +43 -1
  27. package/dist/sdk/generated/protos/service/llmservice/v1/llmservice_pb.js +26 -21
  28. package/dist/sdk/generated/protos/service/persona/v1/persona_config_pb.js +100 -89
  29. package/dist/sdk/generated/protos/service/persona/v1/persona_pb.js +126 -116
  30. package/dist/sdk/generated/protos/service/persona/v1/shared_widgets/widget_types_pb.js +33 -1
  31. package/dist/sdk/generated/protos/service/persona/v1/voicebot_widgets/widget_types_pb.js +60 -11
  32. package/dist/sdk/generated/protos/service/tenant/v1/tenant_pb.js +1 -1
  33. package/dist/sdk/generated/protos/service/user/v1/user_pb.js +1 -1
  34. package/dist/sdk/generated/protos/service/utils/v1/agent_qa_pb.js +35 -0
  35. package/dist/sdk/generated/protos/service/workflows/v1/action_registry_pb.js +1 -1
  36. package/dist/sdk/generated/protos/service/workflows/v1/action_type_pb.js +6 -1
  37. package/dist/sdk/generated/protos/service/workflows/v1/chatbot_pb.js +106 -11
  38. package/dist/sdk/generated/protos/service/workflows/v1/common_forms_pb.js +1 -1
  39. package/dist/sdk/generated/protos/service/workflows/v1/coordinator_pb.js +1 -1
  40. package/dist/sdk/generated/protos/service/workflows/v1/external_actions_pb.js +31 -1
  41. package/dist/sdk/generated/protos/service/workflows/v1/well_known_pb.js +5 -1
  42. package/dist/sdk/generated/protos/service/workflows/v1/workflow_pb.js +1 -1
  43. package/dist/sdk/generated/protos/util/tracking_metadata_pb.js +1 -1
  44. package/package.json +2 -2
@@ -0,0 +1,376 @@
1
+ /**
2
+ * Shared Workflow Graph Representation
3
+ *
4
+ * Single source of truth for parsing workflow_def JSON into a typed graph.
5
+ * Replaces duplicated graph-building across workflow-execution-analyzer,
6
+ * workflow-optimizer (deprecated), and workflow-tracer.
7
+ *
8
+ * NOTE: `workflow-execution-analyzer.ts` still has its own `buildGraphMaps()`
9
+ * which serves validation (runtime checks). This graph serves optimization
10
+ * (static analysis). Future work: consolidate both into this module.
11
+ */
12
+ // ─────────────────────────────────────────────────────────────────────────────
13
+ // Constants
14
+ // ─────────────────────────────────────────────────────────────────────────────
15
+ const TRIGGER_TYPES = new Set([
16
+ "chat_trigger",
17
+ "voice_trigger",
18
+ "document_trigger",
19
+ ]);
20
+ // WARNING: These lists are hardcoded and may become stale as new action types
21
+ // are added to the platform. Future work: load side-effect classification from
22
+ // ActionRegistry metadata instead of maintaining a static list here.
23
+ // Unknown action types default to hasSideEffects=true (safe default).
24
+ const SIDE_EFFECT_FREE = new Set([
25
+ "search",
26
+ "call_llm",
27
+ "entity_extraction",
28
+ "respond_with_sources",
29
+ "respond_for_external_actions",
30
+ "conversation_to_search_query",
31
+ "combine_search_results",
32
+ "text_categorizer",
33
+ "chat_categorizer",
34
+ "response_validator",
35
+ "fixed_response",
36
+ "live_web_search",
37
+ "ai_web_search",
38
+ "json_mapper",
39
+ "custom_agent",
40
+ ]);
41
+ const SIDE_EFFECT_ACTIONS = new Set([
42
+ "send_email_agent",
43
+ "external_action_caller",
44
+ "general_hitl",
45
+ ]);
46
+ // ─────────────────────────────────────────────────────────────────────────────
47
+ // Main Entry Point
48
+ // ─────────────────────────────────────────────────────────────────────────────
49
+ /**
50
+ * Parse a workflow_def JSON into a typed WorkflowGraph.
51
+ *
52
+ * Handles the Ema platform action format:
53
+ * ```
54
+ * { actions: [{ name, action: { name: { namespaces, name }, version }, inputs, runIf, categories, displaySettings }] }
55
+ * ```
56
+ */
57
+ export function buildWorkflowGraph(workflowDef) {
58
+ const emptyGraph = {
59
+ nodes: new Map(),
60
+ edges: [],
61
+ forward: new Map(),
62
+ reverse: new Map(),
63
+ trigger: null,
64
+ outputMappings: [],
65
+ enumTypes: [],
66
+ };
67
+ if (!workflowDef || typeof workflowDef !== "object") {
68
+ return emptyGraph;
69
+ }
70
+ const actions = workflowDef.actions ?? [];
71
+ if (!Array.isArray(actions) || actions.length === 0) {
72
+ return emptyGraph;
73
+ }
74
+ const nodes = new Map();
75
+ const edges = [];
76
+ const forward = new Map();
77
+ const reverse = new Map();
78
+ let trigger = null;
79
+ // ── Pass 1: Build nodes ──────────────────────────────────────────────────
80
+ for (const action of actions) {
81
+ if (!action || typeof action !== "object")
82
+ continue;
83
+ const nodeId = action.name;
84
+ if (!nodeId)
85
+ continue;
86
+ const actionType = extractActionType(action);
87
+ const actionVersion = extractActionVersion(action);
88
+ const displayName = extractDisplayName(action);
89
+ const isTrigger = TRIGGER_TYPES.has(actionType);
90
+ const hasSideEffects = classifySideEffects(actionType);
91
+ // Categories can be string[] or { name: string, description?: string }[]
92
+ let categories;
93
+ const rawCategories = action.categories;
94
+ if (Array.isArray(rawCategories)) {
95
+ categories = rawCategories
96
+ .map((c) => typeof c === "string" ? c : c?.name)
97
+ .filter(Boolean);
98
+ if (categories.length === 0)
99
+ categories = undefined;
100
+ }
101
+ if (isTrigger && trigger === null) {
102
+ trigger = nodeId;
103
+ }
104
+ // Parse inputs
105
+ const inputsObj = action.inputs ?? {};
106
+ const inputRefs = new Map();
107
+ for (const [inputName, inputValue] of Object.entries(inputsObj)) {
108
+ if (!inputValue || typeof inputValue !== "object")
109
+ continue;
110
+ const binding = inputValue;
111
+ if (binding.actionOutput) {
112
+ const ao = binding.actionOutput;
113
+ inputRefs.set(inputName, {
114
+ type: "action_output",
115
+ sourceNode: ao.actionName,
116
+ sourceOutput: ao.output,
117
+ });
118
+ addEdge(edges, forward, reverse, ao.actionName, nodeId, ao.output, inputName);
119
+ }
120
+ else if (binding.multiBinding) {
121
+ inputRefs.set(inputName, { type: "multi_binding" });
122
+ // Parse elements for nested actionOutput refs
123
+ const mb = binding.multiBinding;
124
+ const elements = mb.elements ?? [];
125
+ for (const elem of elements) {
126
+ if (elem.namedBinding) {
127
+ const nb = elem.namedBinding;
128
+ const value = nb.value;
129
+ if (value?.actionOutput) {
130
+ const ao = value.actionOutput;
131
+ addEdge(edges, forward, reverse, ao.actionName, nodeId, ao.output, inputName);
132
+ }
133
+ }
134
+ else if (elem.actionOutput) {
135
+ const ao = elem.actionOutput;
136
+ addEdge(edges, forward, reverse, ao.actionName, nodeId, ao.output, inputName);
137
+ }
138
+ }
139
+ }
140
+ else if (binding.widgetConfig) {
141
+ inputRefs.set(inputName, { type: "widget" });
142
+ }
143
+ else if (binding.inline) {
144
+ inputRefs.set(inputName, { type: "inline" });
145
+ }
146
+ }
147
+ // Parse runIf
148
+ const runIf = parseRunIf(action.runIf);
149
+ if (runIf) {
150
+ // runIf creates a dependency edge from the source node
151
+ addEdge(edges, forward, reverse, runIf.sourceNode, nodeId, runIf.sourceOutput, "__runIf__");
152
+ }
153
+ forward.set(nodeId, forward.get(nodeId) ?? new Set());
154
+ reverse.set(nodeId, reverse.get(nodeId) ?? new Set());
155
+ nodes.set(nodeId, {
156
+ id: nodeId,
157
+ actionType,
158
+ actionVersion,
159
+ displayName,
160
+ depth: 0,
161
+ inputs: inputRefs,
162
+ rawAction: action,
163
+ runIf,
164
+ categories: Array.isArray(categories) ? categories : undefined,
165
+ isTrigger,
166
+ hasSideEffects,
167
+ });
168
+ }
169
+ // ── Pass 2: BFS depth from trigger ───────────────────────────────────────
170
+ if (trigger) {
171
+ const visited = new Set();
172
+ const queue = [{ node: trigger, depth: 0 }];
173
+ while (queue.length > 0) {
174
+ const { node, depth } = queue.shift();
175
+ if (visited.has(node))
176
+ continue;
177
+ visited.add(node);
178
+ const graphNode = nodes.get(node);
179
+ if (graphNode) {
180
+ graphNode.depth = depth;
181
+ }
182
+ for (const child of forward.get(node) ?? []) {
183
+ if (!visited.has(child)) {
184
+ queue.push({ node: child, depth: depth + 1 });
185
+ }
186
+ }
187
+ }
188
+ }
189
+ // ── Pass 3: Parse output mappings ────────────────────────────────────────
190
+ const outputMappings = [];
191
+ const results = workflowDef.results ?? {};
192
+ for (const resultDef of Object.values(results)) {
193
+ if (resultDef && typeof resultDef === "object") {
194
+ outputMappings.push({
195
+ nodeId: resultDef.actionName,
196
+ output: resultDef.outputName,
197
+ });
198
+ }
199
+ }
200
+ // ── Pass 4: Parse enumTypes ──────────────────────────────────────────────
201
+ const enumTypes = [];
202
+ const rawEnumTypes = workflowDef.enumTypes ?? [];
203
+ for (const et of rawEnumTypes) {
204
+ const name = extractEnumTypeName(et);
205
+ const options = (et.options ?? et.values ?? []);
206
+ if (name) {
207
+ enumTypes.push({
208
+ name,
209
+ values: options.filter(o => typeof o.name === "string").map(o => o.name),
210
+ });
211
+ }
212
+ }
213
+ return {
214
+ nodes,
215
+ edges,
216
+ forward,
217
+ reverse,
218
+ trigger,
219
+ outputMappings,
220
+ enumTypes,
221
+ };
222
+ }
223
+ // ─────────────────────────────────────────────────────────────────────────────
224
+ // Utility Functions
225
+ // ─────────────────────────────────────────────────────────────────────────────
226
+ /**
227
+ * BFS from trigger, returning the set of reachable node IDs.
228
+ */
229
+ export function getReachableNodes(graph) {
230
+ if (!graph.trigger)
231
+ return new Set();
232
+ const visited = new Set();
233
+ const queue = [graph.trigger];
234
+ while (queue.length > 0) {
235
+ const node = queue.shift();
236
+ if (visited.has(node))
237
+ continue;
238
+ visited.add(node);
239
+ for (const child of graph.forward.get(node) ?? []) {
240
+ if (!visited.has(child)) {
241
+ queue.push(child);
242
+ }
243
+ }
244
+ }
245
+ return visited;
246
+ }
247
+ /**
248
+ * Topological ordering via Kahn's algorithm.
249
+ * Returns node IDs in dependency order (parents before children).
250
+ */
251
+ export function getTopologicalOrder(graph) {
252
+ if (graph.nodes.size === 0)
253
+ return [];
254
+ // Calculate in-degrees
255
+ const inDegree = new Map();
256
+ for (const nodeId of graph.nodes.keys()) {
257
+ inDegree.set(nodeId, 0);
258
+ }
259
+ for (const edge of graph.edges) {
260
+ inDegree.set(edge.target, (inDegree.get(edge.target) ?? 0) + 1);
261
+ }
262
+ // Seed queue with zero-in-degree nodes
263
+ const queue = [];
264
+ for (const [nodeId, degree] of inDegree) {
265
+ if (degree === 0) {
266
+ queue.push(nodeId);
267
+ }
268
+ }
269
+ const result = [];
270
+ while (queue.length > 0) {
271
+ const node = queue.shift();
272
+ result.push(node);
273
+ for (const child of graph.forward.get(node) ?? []) {
274
+ const newDegree = (inDegree.get(child) ?? 1) - 1;
275
+ inDegree.set(child, newDegree);
276
+ if (newDegree === 0) {
277
+ queue.push(child);
278
+ }
279
+ }
280
+ }
281
+ return result;
282
+ }
283
+ /**
284
+ * Filter graph nodes by one or more action types.
285
+ */
286
+ export function getNodesByType(graph, ...types) {
287
+ const typeSet = new Set(types);
288
+ const result = [];
289
+ for (const node of graph.nodes.values()) {
290
+ if (typeSet.has(node.actionType)) {
291
+ result.push(node);
292
+ }
293
+ }
294
+ return result;
295
+ }
296
+ /**
297
+ * Get IDs of nodes that consume outputs from the given node.
298
+ */
299
+ export function getOutputConsumers(graph, nodeId) {
300
+ const children = graph.forward.get(nodeId);
301
+ if (!children)
302
+ return [];
303
+ return [...children];
304
+ }
305
+ // ─────────────────────────────────────────────────────────────────────────────
306
+ // Internal Helpers
307
+ // ─────────────────────────────────────────────────────────────────────────────
308
+ function extractActionType(action) {
309
+ const actionDef = action.action;
310
+ const actionName = actionDef?.name;
311
+ if (typeof actionName === "string")
312
+ return actionName;
313
+ if (actionName && typeof actionName === "object") {
314
+ return actionName.name ?? "unknown";
315
+ }
316
+ return "unknown";
317
+ }
318
+ function extractActionVersion(action) {
319
+ const actionDef = action.action;
320
+ return actionDef?.version ?? "";
321
+ }
322
+ function extractDisplayName(action) {
323
+ const ds = action.displaySettings;
324
+ return ds?.displayName ?? action.name ?? "";
325
+ }
326
+ function classifySideEffects(actionType) {
327
+ if (TRIGGER_TYPES.has(actionType))
328
+ return false;
329
+ if (SIDE_EFFECT_FREE.has(actionType))
330
+ return false;
331
+ if (SIDE_EFFECT_ACTIONS.has(actionType))
332
+ return true;
333
+ // Unknown → assume has side effects (safe default)
334
+ return true;
335
+ }
336
+ function parseRunIf(runIf) {
337
+ if (!runIf)
338
+ return undefined;
339
+ const lhs = runIf.lhs;
340
+ if (!lhs?.actionOutput)
341
+ return undefined;
342
+ const actionOutput = lhs.actionOutput;
343
+ const rhs = runIf.rhs;
344
+ const inlineRhs = rhs?.inline;
345
+ return {
346
+ sourceNode: actionOutput.actionName,
347
+ sourceOutput: actionOutput.output ?? "",
348
+ operator: runIf.operator ?? 0,
349
+ value: inlineRhs?.enumValue ?? "",
350
+ };
351
+ }
352
+ function extractEnumTypeName(et) {
353
+ const rawName = et.name;
354
+ if (typeof rawName === "string")
355
+ return rawName;
356
+ if (rawName && typeof rawName === "object") {
357
+ const nameObj = rawName;
358
+ if (typeof nameObj.name === "string")
359
+ return nameObj.name;
360
+ if (nameObj.name && typeof nameObj.name === "object") {
361
+ const nestedName = nameObj.name;
362
+ if (typeof nestedName.name === "string")
363
+ return nestedName.name;
364
+ }
365
+ }
366
+ return undefined;
367
+ }
368
+ function addEdge(edges, forward, reverse, source, target, sourceOutput, targetInput) {
369
+ edges.push({ source, target, sourceOutput, targetInput });
370
+ if (!forward.has(source))
371
+ forward.set(source, new Set());
372
+ forward.get(source).add(target);
373
+ if (!reverse.has(target))
374
+ reverse.set(target, new Set());
375
+ reverse.get(target).add(source);
376
+ }
@@ -1,9 +1,15 @@
1
1
  /**
2
- * Workflow Optimizer - Proactive Intelligence Engine
2
+ * @deprecated Use `workflow-graph-optimizer.ts` instead.
3
3
  *
4
- * Goes beyond issue detection to find optimization opportunities,
5
- * suggest improvements, and calculate workflow health scores.
6
- * This is the "brains" that makes workflows better.
4
+ * This file contains the legacy heuristic-based optimizer (health scores,
5
+ * recommendations, scoring). It is superseded by the deterministic graph
6
+ * optimizer which provides:
7
+ * - Shared typed graph (`workflow-graph.ts`)
8
+ * - Provably safe auto-fix transforms (`workflow-graph-transforms.ts`)
9
+ * - Convergence-based orchestrator (`workflow-graph-optimizer.ts`)
10
+ *
11
+ * Remaining consumer: `test/intelligence-layer.test.ts` (legacy tests).
12
+ * Remove this file once that test is migrated or deleted.
7
13
  */
8
14
  // ─────────────────────────────────────────────────────────────────────────────
9
15
  // Main Entry Point
@@ -340,6 +340,37 @@ export const TOOL_GUIDANCE = {
340
340
  ],
341
341
  applicableRules: ["preview-before-deploy"],
342
342
  },
343
+ toolkit_feedback: {
344
+ toolName: "toolkit_feedback",
345
+ quickTip: "Submit feedback to help improve the MCP toolkit. Report gaps, confusion, successes, or suggestions.",
346
+ operations: [
347
+ {
348
+ name: "Submit feedback",
349
+ description: "Report an issue or positive experience",
350
+ example: 'toolkit_feedback(method="submit", category="gap", message="Missing docs on X")',
351
+ },
352
+ {
353
+ name: "List feedback",
354
+ description: "View recent feedback entries",
355
+ example: 'toolkit_feedback(method="list")',
356
+ },
357
+ {
358
+ name: "Analyze feedback",
359
+ description: "Aggregate feedback into actionable insights",
360
+ example: 'toolkit_feedback(method="analyze")',
361
+ },
362
+ ],
363
+ nextSteps: {
364
+ submit: "Continue with your task. Your feedback will be reviewed to improve the toolkit.",
365
+ list: "Review entries and use analyze for aggregated insights.",
366
+ analyze: "Address actionable_items by updating guidance, resources, or patterns documentation.",
367
+ },
368
+ commonMistakes: [
369
+ "Forgetting to include which tool/operation the feedback is about",
370
+ "Not describing what you were trying to accomplish (use context parameter)",
371
+ ],
372
+ applicableRules: [],
373
+ },
343
374
  };
344
375
  // ─────────────────────────────────────────────────────────────────────────────
345
376
  // Contextual Tips (for tool responses)
@@ -385,10 +416,13 @@ export const CONTEXTUAL_TIPS = [
385
416
  * Generate server instructions from rules.
386
417
  * These are injected into the MCP init response and should be concise.
387
418
  */
388
- export function generateServerInstructions() {
419
+ export function generateServerInstructions(buildInfo) {
389
420
  const critical = GUIDANCE_RULES.filter((r) => r.level === "critical");
390
421
  const important = GUIDANCE_RULES.filter((r) => r.level === "important");
391
- return `# Ema MCP Toolkit
422
+ const versionLine = buildInfo
423
+ ? `\n> Build: ${buildInfo.version}${buildInfo.commit ? ` (${buildInfo.commit})` : ""}\n`
424
+ : "";
425
+ return `# Ema MCP Toolkit${versionLine}
392
426
 
393
427
  ## Workflow Pattern
394
428
  1. **Discover**: persona() to list AI Employees
@@ -416,6 +450,15 @@ The LLM generates the full workflow_def. MCP provides data and executes.
416
450
  - \`ema://catalog/agents-summary\` - Action catalog
417
451
  - \`ema://rules/anti-patterns\` - Common mistakes
418
452
 
453
+ ## Feedback Welcome
454
+ Help us improve! Use \`toolkit_feedback\` to report issues or successes:
455
+ - **Missing docs**: \`toolkit_feedback(method="submit", category="gap", message="...")\`
456
+ - **Confusing guidance**: \`toolkit_feedback(method="submit", category="confusion", message="...")\`
457
+ - **Something worked well**: \`toolkit_feedback(method="submit", category="success", message="...")\`
458
+ - **Unclear error**: \`toolkit_feedback(method="submit", category="error_unclear", tool="...", message="...")\`
459
+
460
+ Your feedback is automatically collected and analyzed to improve the toolkit for all agents.
461
+
419
462
  ## Need Help?
420
463
  Use the \`toolkit_onboard\` prompt for comprehensive guidance.
421
464
  `;
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Feedback Handler - Agent feedback collection and analysis
3
+ *
4
+ * Enables a self-improving loop where agents that USE the MCP toolkit
5
+ * can report gaps, confusion, successes, and suggestions.
6
+ *
7
+ * Methods:
8
+ * - submit - Agent reports feedback
9
+ * - list - View recent feedback entries
10
+ * - analyze - Aggregate feedback + telemetry into actionable insights
11
+ * - rotate - Manually trigger log rotation
12
+ */
13
+ import { errorResult } from "../types.js";
14
+ import { submitFeedback, listFeedback, listTelemetry, analyzeFeedback, rotateLogs, } from "./store.js";
15
+ const VALID_CATEGORIES = [
16
+ "gap",
17
+ "confusion",
18
+ "success",
19
+ "error_unclear",
20
+ "suggestion",
21
+ ];
22
+ const VALID_SEVERITIES = ["low", "medium", "high"];
23
+ /** Max lengths to prevent oversized entries from consuming disk/memory */
24
+ const MAX_MESSAGE_LENGTH = 2000;
25
+ const MAX_CONTEXT_LENGTH = 1000;
26
+ const MAX_FIELD_LENGTH = 100;
27
+ /** Truncate a string to maxLen, appending "..." if truncated */
28
+ function truncate(value, maxLen) {
29
+ return value.length > maxLen ? value.slice(0, maxLen) + "..." : value;
30
+ }
31
+ /**
32
+ * Handle toolkit_feedback tool requests.
33
+ */
34
+ export async function handleFeedback(args) {
35
+ const method = args.method ? String(args.method) : "submit";
36
+ switch (method) {
37
+ case "submit":
38
+ return handleSubmit(args);
39
+ case "list":
40
+ return handleList(args);
41
+ case "analyze":
42
+ return handleAnalyze();
43
+ case "rotate":
44
+ return handleRotate();
45
+ default:
46
+ return errorResult(`Unknown feedback method: ${method}`, {
47
+ hint: "Available methods: submit, list, analyze, rotate",
48
+ });
49
+ }
50
+ }
51
+ // ─────────────────────────────────────────────────────────────────────────────
52
+ // Method handlers
53
+ // ─────────────────────────────────────────────────────────────────────────────
54
+ async function handleSubmit(args) {
55
+ const category = args.category ? String(args.category) : undefined;
56
+ const message = args.message ? String(args.message) : undefined;
57
+ if (!category) {
58
+ return errorResult("Missing required parameter: category", {
59
+ hint: `Valid categories: ${VALID_CATEGORIES.join(", ")}`,
60
+ });
61
+ }
62
+ if (!VALID_CATEGORIES.includes(category)) {
63
+ return errorResult(`Invalid category: ${category}`, {
64
+ hint: `Valid categories: ${VALID_CATEGORIES.join(", ")}`,
65
+ });
66
+ }
67
+ if (!message) {
68
+ return errorResult("Missing required parameter: message", {
69
+ hint: "Describe what you encountered, what was confusing, or what's missing.",
70
+ });
71
+ }
72
+ const severity = args.severity ? String(args.severity) : undefined;
73
+ if (severity && !VALID_SEVERITIES.includes(severity)) {
74
+ return errorResult(`Invalid severity: ${severity}`, {
75
+ hint: `Valid severities: ${VALID_SEVERITIES.join(", ")}`,
76
+ });
77
+ }
78
+ const entry = await submitFeedback({
79
+ category: category,
80
+ message: truncate(message, MAX_MESSAGE_LENGTH),
81
+ tool: args.tool ? truncate(String(args.tool), MAX_FIELD_LENGTH) : undefined,
82
+ operation: args.operation ? truncate(String(args.operation), MAX_FIELD_LENGTH) : undefined,
83
+ context: args.context ? truncate(String(args.context), MAX_CONTEXT_LENGTH) : undefined,
84
+ severity: severity,
85
+ });
86
+ return {
87
+ success: true,
88
+ feedback_id: entry.id,
89
+ message: "Feedback recorded. Thank you for helping improve the toolkit!",
90
+ _tip: "Your feedback helps us identify documentation gaps and improve guidance for all agents.",
91
+ };
92
+ }
93
+ async function handleList(args) {
94
+ const categoryStr = args.category ? String(args.category) : undefined;
95
+ if (categoryStr && !VALID_CATEGORIES.includes(categoryStr)) {
96
+ return errorResult(`Invalid category filter: ${categoryStr}`, {
97
+ hint: `Valid categories: ${VALID_CATEGORIES.join(", ")}`,
98
+ });
99
+ }
100
+ const category = categoryStr;
101
+ const limit = args.limit ? Number(args.limit) : 50;
102
+ const includeTelemetry = args.include_telemetry === true;
103
+ const feedback = await listFeedback({ limit, category });
104
+ const result = {
105
+ count: feedback.length,
106
+ feedback,
107
+ };
108
+ if (includeTelemetry) {
109
+ const telemetry = await listTelemetry({ limit });
110
+ result.telemetry = {
111
+ count: telemetry.length,
112
+ entries: telemetry,
113
+ };
114
+ }
115
+ if (feedback.length === 0) {
116
+ result._tip = "No feedback recorded yet. Agents can submit feedback using toolkit_feedback(method='submit', category='...', message='...').";
117
+ }
118
+ return result;
119
+ }
120
+ async function handleAnalyze() {
121
+ const analysis = await analyzeFeedback();
122
+ return {
123
+ ...analysis,
124
+ _tip: analysis.actionable_items.length > 0
125
+ ? `Found ${analysis.actionable_items.length} actionable item(s). Review and address these to improve agent experience.`
126
+ : "No actionable items found. Feedback data may be limited - encourage agents to submit feedback.",
127
+ _next_step: analysis.actionable_items.length > 0
128
+ ? "Review actionable_items and update guidance.ts, resources.ts, or workflow-builder-patterns.md accordingly."
129
+ : "Continue collecting feedback. Use toolkit_feedback(method='list') to review individual entries.",
130
+ };
131
+ }
132
+ async function handleRotate() {
133
+ const result = await rotateLogs();
134
+ return {
135
+ success: true,
136
+ ...result,
137
+ message: "Log files rotated successfully.",
138
+ };
139
+ }