@ema.co/mcp-toolkit 2026.3.24 → 2026.3.25-2
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/dist/knowledge/guidance-cache.js +60 -3
- package/dist/knowledge/intelligence/staleness.js +4 -0
- package/dist/knowledge/search-client.js +89 -1
- package/dist/mcp/domain/generation-schema.js +244 -31
- package/dist/mcp/domain/workflow-def-validator.js +321 -1
- package/dist/mcp/domain/workflow-static-validator.js +1 -1
- package/dist/mcp/guidance/classify.js +74 -0
- package/dist/mcp/guidance/defaults.js +114 -0
- package/dist/mcp/guidance/middleware.js +193 -0
- package/dist/mcp/guidance/types.js +7 -0
- package/dist/mcp/guidance.js +12 -8
- package/dist/mcp/handlers/data/index.js +1 -1
- package/dist/mcp/handlers/debug/index.js +80 -7
- package/dist/mcp/handlers/env/index.js +4 -4
- package/dist/mcp/handlers/feedback/coalesce.js +4 -1
- package/dist/mcp/handlers/feedback/index.js +15 -3
- package/dist/mcp/handlers/feedback/store.js +32 -12
- package/dist/mcp/handlers/template/crud.js +26 -9
- package/dist/mcp/handlers/workflow/adapter.js +2 -2
- package/dist/mcp/handlers/workflow/deploy.js +15 -15
- package/dist/mcp/handlers/workflow/index.js +23 -11
- package/dist/mcp/handlers/workflow/validate.js +30 -1
- package/dist/mcp/handlers/workflow/validation.js +4 -4
- package/dist/mcp/server.js +57 -5
- package/dist/mcp/tools.js +18 -11
- package/dist/sdk/client.js +7 -7
- package/dist/sdk/ema-client.js +16 -1
- package/package.json +1 -1
|
@@ -33,7 +33,10 @@ const VALID_BINDING_CASES = new Set([
|
|
|
33
33
|
export function validateWorkflowDefStructure(workflowDef) {
|
|
34
34
|
const issues = [];
|
|
35
35
|
validateWorkflowName(workflowDef, issues);
|
|
36
|
+
validateEnumTypes(workflowDef, issues);
|
|
36
37
|
validateActions(workflowDef, issues);
|
|
38
|
+
validateTypeArguments(workflowDef, issues);
|
|
39
|
+
validateRunIfBindings(workflowDef, issues);
|
|
37
40
|
validateResults(workflowDef, issues);
|
|
38
41
|
validateNamedResults(workflowDef, issues);
|
|
39
42
|
return {
|
|
@@ -98,6 +101,205 @@ function validateWorkflowName(wf, issues) {
|
|
|
98
101
|
});
|
|
99
102
|
}
|
|
100
103
|
}
|
|
104
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
105
|
+
// enumTypes validation — empty namespaces here cause opaque 500 from the backend
|
|
106
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
107
|
+
function validateEnumTypes(wf, issues) {
|
|
108
|
+
const enumTypes = wf.enumTypes;
|
|
109
|
+
if (!Array.isArray(enumTypes) || enumTypes.length === 0)
|
|
110
|
+
return;
|
|
111
|
+
const enumNames = new Set();
|
|
112
|
+
for (let i = 0; i < enumTypes.length; i++) {
|
|
113
|
+
const et = enumTypes[i];
|
|
114
|
+
const prefix = `enumTypes[${i}]`;
|
|
115
|
+
const name = et.name;
|
|
116
|
+
if (!name || typeof name !== "object") {
|
|
117
|
+
issues.push({
|
|
118
|
+
path: `${prefix}.name`,
|
|
119
|
+
severity: "error",
|
|
120
|
+
message: "enumType missing 'name' (NamespacedName — requires { namespaces, name })",
|
|
121
|
+
proto_ref: "workflows.v1.EnumTypeDef.name",
|
|
122
|
+
});
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (!Array.isArray(name.namespaces) || name.namespaces.length === 0) {
|
|
126
|
+
issues.push({
|
|
127
|
+
path: `${prefix}.name.namespaces`,
|
|
128
|
+
severity: "error",
|
|
129
|
+
message: "enumType namespaces must be a non-empty array. Empty [] causes HTTP 500. " +
|
|
130
|
+
"Use the namespaces from the persona's workflowName, or leave namespaces from the template unchanged.",
|
|
131
|
+
proto_ref: "workflows.v1.NamespacedName.namespaces",
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (typeof name.name !== "string" || name.name.length === 0) {
|
|
135
|
+
issues.push({
|
|
136
|
+
path: `${prefix}.name.name`,
|
|
137
|
+
severity: "error",
|
|
138
|
+
message: "enumType name.name must be a non-empty string (e.g. 'intent_categories')",
|
|
139
|
+
proto_ref: "workflows.v1.NamespacedName.name",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
if (enumNames.has(name.name)) {
|
|
144
|
+
issues.push({
|
|
145
|
+
path: `${prefix}.name.name`,
|
|
146
|
+
severity: "error",
|
|
147
|
+
message: `Duplicate enumType name '${name.name}'`,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
enumNames.add(name.name);
|
|
151
|
+
}
|
|
152
|
+
// Validate options array
|
|
153
|
+
const options = et.options;
|
|
154
|
+
if (!Array.isArray(options) || options.length === 0) {
|
|
155
|
+
issues.push({
|
|
156
|
+
path: `${prefix}.options`,
|
|
157
|
+
severity: "warning",
|
|
158
|
+
message: "enumType has no options — categorizer will have nothing to route to",
|
|
159
|
+
proto_ref: "workflows.v1.EnumTypeDef.options",
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
const hasFallback = options.some((o) => typeof o.name === "string" && o.name.toLowerCase() === "fallback");
|
|
164
|
+
if (!hasFallback) {
|
|
165
|
+
issues.push({
|
|
166
|
+
path: `${prefix}.options`,
|
|
167
|
+
severity: "warning",
|
|
168
|
+
message: "enumType has no 'Fallback' option — categorizer should always include a Fallback category",
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
175
|
+
// typeArguments validation — categorizers need typeArguments.categories pointing
|
|
176
|
+
// to a valid enumType defined in enumTypes[]
|
|
177
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
178
|
+
function validateTypeArguments(wf, issues) {
|
|
179
|
+
const actions = wf.actions;
|
|
180
|
+
if (!Array.isArray(actions))
|
|
181
|
+
return;
|
|
182
|
+
// Collect defined enum type names
|
|
183
|
+
const enumTypes = wf.enumTypes;
|
|
184
|
+
const definedEnums = new Set();
|
|
185
|
+
if (Array.isArray(enumTypes)) {
|
|
186
|
+
for (const et of enumTypes) {
|
|
187
|
+
const name = et.name;
|
|
188
|
+
if (name && typeof name.name === "string") {
|
|
189
|
+
definedEnums.add(name.name);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
for (let i = 0; i < actions.length; i++) {
|
|
194
|
+
const action = actions[i];
|
|
195
|
+
const prefix = `actions[${i}]`;
|
|
196
|
+
const actionDef = action.action;
|
|
197
|
+
const actionName = actionDef?.name?.name;
|
|
198
|
+
// Check if this is a categorizer (needs typeArguments)
|
|
199
|
+
const isCategorizer = actionName === "chat_categorizer" || actionName === "text_categorizer";
|
|
200
|
+
const typeArgs = action.typeArguments;
|
|
201
|
+
if (isCategorizer && (!typeArgs || !typeArgs.categories)) {
|
|
202
|
+
issues.push({
|
|
203
|
+
path: `${prefix}.typeArguments`,
|
|
204
|
+
severity: "error",
|
|
205
|
+
message: `Categorizer '${action.name}' missing typeArguments.categories. ` +
|
|
206
|
+
"Must point to an enumType: { categories: { enumType: { name: { name: '<enum_name>', namespaces: [...] } } } }",
|
|
207
|
+
proto_ref: "workflows.v1.ActionInstance.type_arguments",
|
|
208
|
+
});
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (!typeArgs?.categories)
|
|
212
|
+
continue;
|
|
213
|
+
// Validate the enumType reference
|
|
214
|
+
const categories = typeArgs.categories;
|
|
215
|
+
const enumType = categories.enumType;
|
|
216
|
+
if (!enumType) {
|
|
217
|
+
issues.push({
|
|
218
|
+
path: `${prefix}.typeArguments.categories.enumType`,
|
|
219
|
+
severity: "error",
|
|
220
|
+
message: "typeArguments.categories missing enumType reference",
|
|
221
|
+
});
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const enumName = enumType.name;
|
|
225
|
+
if (!enumName || typeof enumName !== "object") {
|
|
226
|
+
issues.push({
|
|
227
|
+
path: `${prefix}.typeArguments.categories.enumType.name`,
|
|
228
|
+
severity: "error",
|
|
229
|
+
message: "enumType.name must be a NamespacedName object { name, namespaces }",
|
|
230
|
+
});
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
// Check namespaces match the enumType definition
|
|
234
|
+
if (!Array.isArray(enumName.namespaces) || enumName.namespaces.length === 0) {
|
|
235
|
+
issues.push({
|
|
236
|
+
path: `${prefix}.typeArguments.categories.enumType.name.namespaces`,
|
|
237
|
+
severity: "error",
|
|
238
|
+
message: "typeArguments enumType namespaces must be non-empty. Must match the namespaces in the enumTypes[] definition.",
|
|
239
|
+
proto_ref: "workflows.v1.NamespacedName.namespaces",
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
// Check the referenced enum exists
|
|
243
|
+
if (typeof enumName.name === "string" && definedEnums.size > 0 && !definedEnums.has(enumName.name)) {
|
|
244
|
+
issues.push({
|
|
245
|
+
path: `${prefix}.typeArguments.categories.enumType.name.name`,
|
|
246
|
+
severity: "error",
|
|
247
|
+
message: `typeArguments references enumType '${enumName.name}' but it's not defined in enumTypes[]. ` +
|
|
248
|
+
`Available: ${[...definedEnums].join(", ")}`,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
254
|
+
// runIf validation — conditional execution bindings must be well-formed
|
|
255
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
256
|
+
function validateRunIfBindings(wf, issues) {
|
|
257
|
+
const actions = wf.actions;
|
|
258
|
+
if (!Array.isArray(actions))
|
|
259
|
+
return;
|
|
260
|
+
for (let i = 0; i < actions.length; i++) {
|
|
261
|
+
const action = actions[i];
|
|
262
|
+
const runIf = action.runIf;
|
|
263
|
+
if (!runIf)
|
|
264
|
+
continue;
|
|
265
|
+
const prefix = `actions[${i}].runIf`;
|
|
266
|
+
// lhs must be a valid binding
|
|
267
|
+
const lhs = runIf.lhs;
|
|
268
|
+
if (!lhs || typeof lhs !== "object") {
|
|
269
|
+
issues.push({
|
|
270
|
+
path: `${prefix}.lhs`,
|
|
271
|
+
severity: "error",
|
|
272
|
+
message: "runIf.lhs must be a valid InputBinding (e.g., actionOutput referencing categorizer output)",
|
|
273
|
+
proto_ref: "workflows.v1.ConditionalInputBinding",
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
validateSingleBinding(lhs, `${prefix}.lhs`, issues);
|
|
278
|
+
}
|
|
279
|
+
// operator must be present
|
|
280
|
+
if (runIf.operator === undefined || runIf.operator === null) {
|
|
281
|
+
issues.push({
|
|
282
|
+
path: `${prefix}.operator`,
|
|
283
|
+
severity: "error",
|
|
284
|
+
message: "runIf.operator is required (1 = EQUAL, 2 = NOT_EQUAL)",
|
|
285
|
+
proto_ref: "workflows.v1.ConditionalInputBinding.operator",
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
// rhs must be a valid binding
|
|
289
|
+
const rhs = runIf.rhs;
|
|
290
|
+
if (!rhs || typeof rhs !== "object") {
|
|
291
|
+
issues.push({
|
|
292
|
+
path: `${prefix}.rhs`,
|
|
293
|
+
severity: "error",
|
|
294
|
+
message: "runIf.rhs must be a valid InputBinding (e.g., inline enumValue)",
|
|
295
|
+
proto_ref: "workflows.v1.ConditionalInputBinding",
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
validateSingleBinding(rhs, `${prefix}.rhs`, issues);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
101
303
|
function validateActions(wf, issues) {
|
|
102
304
|
const actions = wf.actions;
|
|
103
305
|
if (!Array.isArray(actions) || actions.length === 0) {
|
|
@@ -188,7 +390,43 @@ function validateInputBindings(action, prefix, _knownActions, issues) {
|
|
|
188
390
|
for (const [inputName, binding] of Object.entries(inputs)) {
|
|
189
391
|
if (!binding || typeof binding !== "object")
|
|
190
392
|
continue;
|
|
191
|
-
|
|
393
|
+
const bindingObj = binding;
|
|
394
|
+
validateSingleBinding(bindingObj, `${prefix}.inputs.${inputName}`, issues);
|
|
395
|
+
// named_inputs_* MUST use multiBinding with namedBinding elements
|
|
396
|
+
if (inputName.startsWith("named_inputs_")) {
|
|
397
|
+
if (!bindingObj.multiBinding) {
|
|
398
|
+
issues.push({
|
|
399
|
+
path: `${prefix}.inputs.${inputName}`,
|
|
400
|
+
severity: "error",
|
|
401
|
+
message: `Input '${inputName}' must use multiBinding format: ` +
|
|
402
|
+
`{ multiBinding: { elements: [{ namedBinding: { name: "...", binding: {...} } }] } }. ` +
|
|
403
|
+
"Search knowledge('named inputs format') for correct structure",
|
|
404
|
+
proto_ref: "workflows.v1.InputBinding.MultiBinding",
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
// Check each element uses namedBinding
|
|
409
|
+
const mb = bindingObj.multiBinding;
|
|
410
|
+
const elements = mb.elements;
|
|
411
|
+
if (Array.isArray(elements)) {
|
|
412
|
+
for (let j = 0; j < elements.length; j++) {
|
|
413
|
+
const el = elements[j];
|
|
414
|
+
if (el && typeof el === "object" && !el.namedBinding) {
|
|
415
|
+
issues.push({
|
|
416
|
+
path: `${prefix}.inputs.${inputName}.multiBinding.elements[${j}]`,
|
|
417
|
+
severity: "error",
|
|
418
|
+
message: "named_inputs elements must use namedBinding: { namedBinding: { name: '...', binding: {...} } }. " +
|
|
419
|
+
"Plain actionOutput or inline bindings inside multiBinding are invalid for named_inputs.",
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
// extraction_columns MUST use correct array format
|
|
427
|
+
if (inputName === "extraction_columns") {
|
|
428
|
+
validateExtractionColumnsFormat(bindingObj, `${prefix}.inputs.${inputName}`, issues);
|
|
429
|
+
}
|
|
192
430
|
}
|
|
193
431
|
}
|
|
194
432
|
function validateSingleBinding(binding, path, issues) {
|
|
@@ -462,3 +700,85 @@ function validateResultDef(value, path, issues) {
|
|
|
462
700
|
}
|
|
463
701
|
}
|
|
464
702
|
}
|
|
703
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
704
|
+
// extraction_columns format validation
|
|
705
|
+
// Must use: inline.array.values[{ wellKnown: { extractionColumn: { id, name, dataType } } }]
|
|
706
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
707
|
+
function validateExtractionColumnsFormat(binding, path, issues) {
|
|
708
|
+
const inline = binding.inline;
|
|
709
|
+
if (!inline)
|
|
710
|
+
return; // actionOutput wiring is valid too
|
|
711
|
+
const array = inline.array;
|
|
712
|
+
if (!array) {
|
|
713
|
+
issues.push({
|
|
714
|
+
path: `${path}.inline`,
|
|
715
|
+
severity: "error",
|
|
716
|
+
message: "extraction_columns must use inline.array format: { inline: { array: { values: [{ wellKnown: { extractionColumn: { id, name, dataType } } }] } } }. " +
|
|
717
|
+
"Search knowledge('extraction columns format') for correct structure",
|
|
718
|
+
});
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
const values = array.values;
|
|
722
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
723
|
+
issues.push({
|
|
724
|
+
path: `${path}.inline.array.values`,
|
|
725
|
+
severity: "warning",
|
|
726
|
+
message: "extraction_columns array is empty — no columns defined",
|
|
727
|
+
});
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
const seenIds = new Set();
|
|
731
|
+
for (let i = 0; i < values.length; i++) {
|
|
732
|
+
const v = values[i];
|
|
733
|
+
const vPath = `${path}.inline.array.values[${i}]`;
|
|
734
|
+
const wellKnown = v?.wellKnown;
|
|
735
|
+
if (!wellKnown) {
|
|
736
|
+
issues.push({
|
|
737
|
+
path: vPath,
|
|
738
|
+
severity: "error",
|
|
739
|
+
message: "extraction_columns values must be wrapped in wellKnown: { extractionColumn: { id, name, dataType } }",
|
|
740
|
+
});
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
const col = wellKnown.extractionColumn;
|
|
744
|
+
if (!col) {
|
|
745
|
+
issues.push({
|
|
746
|
+
path: `${vPath}.wellKnown`,
|
|
747
|
+
severity: "error",
|
|
748
|
+
message: "Missing extractionColumn inside wellKnown wrapper",
|
|
749
|
+
});
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
if (typeof col.id !== "string" || col.id.length === 0) {
|
|
753
|
+
issues.push({
|
|
754
|
+
path: `${vPath}.wellKnown.extractionColumn.id`,
|
|
755
|
+
severity: "error",
|
|
756
|
+
message: "extractionColumn requires 'id' (unique string identifier)",
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
if (seenIds.has(col.id)) {
|
|
761
|
+
issues.push({
|
|
762
|
+
path: `${vPath}.wellKnown.extractionColumn.id`,
|
|
763
|
+
severity: "error",
|
|
764
|
+
message: `Duplicate extraction column id '${col.id}'`,
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
seenIds.add(col.id);
|
|
768
|
+
}
|
|
769
|
+
if (typeof col.name !== "string" || col.name.length === 0) {
|
|
770
|
+
issues.push({
|
|
771
|
+
path: `${vPath}.wellKnown.extractionColumn.name`,
|
|
772
|
+
severity: "error",
|
|
773
|
+
message: "extractionColumn requires 'name' (display name)",
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
if (col.dataType === undefined || col.dataType === null) {
|
|
777
|
+
issues.push({
|
|
778
|
+
path: `${vPath}.wellKnown.extractionColumn.dataType`,
|
|
779
|
+
severity: "error",
|
|
780
|
+
message: "extractionColumn requires 'dataType' (1=STRING, 2=INT, 3=FLOAT, 4=BOOL, 5=DATE)",
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
@@ -327,7 +327,7 @@ function formatPathName(path) {
|
|
|
327
327
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
328
328
|
// Global Categorizer Validation (not path-dependent)
|
|
329
329
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
330
|
-
function validateAllCategorizers(workflow) {
|
|
330
|
+
export function validateAllCategorizers(workflow) {
|
|
331
331
|
const errors = [];
|
|
332
332
|
// Find all categorizer nodes in workflow
|
|
333
333
|
const categorizers = workflow.nodes.filter(node => node.actionType === "chat_categorizer" ||
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result Shape Classifier
|
|
3
|
+
*
|
|
4
|
+
* Inspects an MCP tool response and classifies it into a ResultShape.
|
|
5
|
+
* The shape drives which guidance atoms are resolved.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Classify an MCP tool response into a ResultShape.
|
|
9
|
+
*
|
|
10
|
+
* Handlers can override by setting `result._result_shape` directly.
|
|
11
|
+
*/
|
|
12
|
+
export function classifyResult(result, unfilteredCount) {
|
|
13
|
+
// Handler override
|
|
14
|
+
if (typeof result._result_shape === "string") {
|
|
15
|
+
return result._result_shape;
|
|
16
|
+
}
|
|
17
|
+
// Error shapes — check error field or status indicators
|
|
18
|
+
const error = result.error;
|
|
19
|
+
const status = result.status;
|
|
20
|
+
const apiStatus = result.api_status;
|
|
21
|
+
if (error || status === "failed") {
|
|
22
|
+
// Validation blocked by toolkit (not API)
|
|
23
|
+
if (typeof result.validation_failed === "string") {
|
|
24
|
+
return "error_validation";
|
|
25
|
+
}
|
|
26
|
+
// Deploy-specific failure — check before generic HTTP codes
|
|
27
|
+
// because deploy 400/500 needs deploy-specific guidance
|
|
28
|
+
if (result.mode === "deploy" || result.deployed === false) {
|
|
29
|
+
return "deploy_failed";
|
|
30
|
+
}
|
|
31
|
+
const errorStr = String(error ?? "").toLowerCase();
|
|
32
|
+
const code = apiStatus ?? extractStatusCode(errorStr);
|
|
33
|
+
if (code === 401 || code === 403)
|
|
34
|
+
return "error_401";
|
|
35
|
+
if (code === 404 || code === 422)
|
|
36
|
+
return "error_not_found";
|
|
37
|
+
if (code === 400)
|
|
38
|
+
return "error_400";
|
|
39
|
+
if (code === 500 || code === 502 || code === 503)
|
|
40
|
+
return "error_500";
|
|
41
|
+
return "error";
|
|
42
|
+
}
|
|
43
|
+
// Success shapes
|
|
44
|
+
if (result.success === true || result.persona_id) {
|
|
45
|
+
// Created entity
|
|
46
|
+
if (result.persona_id && !result.workflow_def) {
|
|
47
|
+
return "created";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (result.deployed === true || (result.mode === "deploy" && !error)) {
|
|
51
|
+
return "deployed";
|
|
52
|
+
}
|
|
53
|
+
// List shapes — check count
|
|
54
|
+
const count = typeof result.count === "number" ? result.count : undefined;
|
|
55
|
+
if (count !== undefined) {
|
|
56
|
+
if (count === 0) {
|
|
57
|
+
const unfiltered = unfilteredCount
|
|
58
|
+
?? (typeof result._unfiltered_count === "number" ? result._unfiltered_count : undefined);
|
|
59
|
+
return unfiltered && unfiltered > 0 ? "empty_filtered" : "empty_source";
|
|
60
|
+
}
|
|
61
|
+
return "success";
|
|
62
|
+
}
|
|
63
|
+
// Partial — success with warnings
|
|
64
|
+
if (result._warning && !error) {
|
|
65
|
+
return "partial";
|
|
66
|
+
}
|
|
67
|
+
return "success";
|
|
68
|
+
}
|
|
69
|
+
/** Extract HTTP status code from error message string. */
|
|
70
|
+
function extractStatusCode(errorStr) {
|
|
71
|
+
// Match patterns like "API error (400)" or "HTTP 500" or just "500"
|
|
72
|
+
const match = errorStr.match(/\b(4\d{2}|5\d{2})\b/);
|
|
73
|
+
return match ? parseInt(match[1], 10) : undefined;
|
|
74
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal Default Guidance — Fallback When DE Has No Match
|
|
3
|
+
*
|
|
4
|
+
* These are the last resort. DE-served atoms and agent-published solutions
|
|
5
|
+
* take priority. Defaults exist so no response ever has zero guidance.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Get default guidance hints for a result shape.
|
|
9
|
+
* Template variables ({tool}, {method}, etc.) are NOT resolved here —
|
|
10
|
+
* the middleware handles resolution after merging all sources.
|
|
11
|
+
*/
|
|
12
|
+
export function getDefaultGuidance(shape, ctx) {
|
|
13
|
+
switch (shape) {
|
|
14
|
+
case "empty_filtered":
|
|
15
|
+
return {
|
|
16
|
+
_warning: "Results exist but none match your filters.",
|
|
17
|
+
_tip: "Try without filters to see all, or use knowledge('{tool}') to search DE.",
|
|
18
|
+
_next_step: "{tool}(method='{method}')",
|
|
19
|
+
};
|
|
20
|
+
case "empty_source":
|
|
21
|
+
return {
|
|
22
|
+
_warning: "No results found.",
|
|
23
|
+
_tip: "Try knowledge('{tool}') to search DE, or check your profile/environment.",
|
|
24
|
+
_next_step: "knowledge('{tool}')",
|
|
25
|
+
};
|
|
26
|
+
case "created":
|
|
27
|
+
return {
|
|
28
|
+
_warning: "Entity created but may need additional configuration.",
|
|
29
|
+
_next_step: "workflow(mode='get', persona_id='{persona_id}') to get starter workflow, then build and deploy a complete workflow_def.",
|
|
30
|
+
};
|
|
31
|
+
case "deployed":
|
|
32
|
+
return {
|
|
33
|
+
_next_step: "Verify: workflow(mode='get', persona_id='{persona_id}') — confirm workflow is active.",
|
|
34
|
+
};
|
|
35
|
+
case "deploy_failed":
|
|
36
|
+
return {
|
|
37
|
+
_warning: "Deployment failed. Do NOT retry with the same workflow_def — fix the issue first. If the error is structural (not just a field fix), backtrack to design before rebuilding JSON.",
|
|
38
|
+
_likely_causes: [
|
|
39
|
+
"Type mismatch: input binding has incompatible type",
|
|
40
|
+
"Missing or invalid named_inputs format — see knowledge('named inputs format')",
|
|
41
|
+
"Empty namespaces on action names or enumTypes",
|
|
42
|
+
"namedResults producer missing actionName or outputName",
|
|
43
|
+
],
|
|
44
|
+
_debug_steps: [
|
|
45
|
+
"Read the error message — it names the specific field/action that failed",
|
|
46
|
+
"Search for the error: knowledge('{tool} {method} <error_keywords>') — other agents may have solved this",
|
|
47
|
+
"Compare your workflow_def against a working one: workflow(mode='get', persona_id='<similar_persona>')",
|
|
48
|
+
"Run workflow(mode='validate') to catch remaining issues before re-deploying",
|
|
49
|
+
"If the error is structural (wrong shape, not just a wrong value), go back to design — don't patch JSON",
|
|
50
|
+
],
|
|
51
|
+
_next_step: "knowledge('{tool} deploy <error_keywords>') to find solutions, then fix and workflow(mode='validate') before retrying.",
|
|
52
|
+
};
|
|
53
|
+
case "error_400":
|
|
54
|
+
return {
|
|
55
|
+
_warning: "Bad request — the API rejected the input.",
|
|
56
|
+
_likely_causes: [
|
|
57
|
+
"Invalid field format or missing required field",
|
|
58
|
+
"Input type mismatch (e.g., string where number expected)",
|
|
59
|
+
"Constraint violation (e.g., duplicate name, invalid enum value)",
|
|
60
|
+
],
|
|
61
|
+
_debug_steps: [
|
|
62
|
+
"Read the error message — it usually names the problematic field",
|
|
63
|
+
"Search for the error: knowledge('{tool} {method} <error_keywords>') — may find known solutions",
|
|
64
|
+
"Check field constraints: knowledge('field constraints')",
|
|
65
|
+
],
|
|
66
|
+
_next_step: "knowledge('{tool} {method} <error_keywords>') to find known solutions for this error.",
|
|
67
|
+
};
|
|
68
|
+
case "error_500":
|
|
69
|
+
return {
|
|
70
|
+
_warning: "Server error — the API gave no details. Do NOT guess — search knowledge and compare against a working workflow.",
|
|
71
|
+
_likely_causes: [
|
|
72
|
+
"Type mismatch in input bindings (API doesn't say which input)",
|
|
73
|
+
"Missing typeArguments on categorizer nodes",
|
|
74
|
+
"Invalid named_inputs or extraction_columns format",
|
|
75
|
+
"Empty namespaces array on action names or enumTypes",
|
|
76
|
+
"Deprecated action version",
|
|
77
|
+
],
|
|
78
|
+
_debug_steps: [
|
|
79
|
+
"Search for the error: knowledge('{tool} deploy 500 <error_keywords>') — other agents may have solved this",
|
|
80
|
+
"Compare against a WORKING workflow: workflow(mode='get', persona_id='<similar_persona>')",
|
|
81
|
+
"Check EACH input binding type — TEXT_WITH_SOURCES ≠ STRING ≠ JSON_VALUE",
|
|
82
|
+
"Verify enumTypes and action namespaces are non-empty arrays, not []",
|
|
83
|
+
"Check namedResults producers have both actionName AND outputName",
|
|
84
|
+
],
|
|
85
|
+
_next_step: "knowledge('{tool} deploy 500') to find known solutions, then compare against a working workflow_def.",
|
|
86
|
+
};
|
|
87
|
+
case "error_401":
|
|
88
|
+
return {
|
|
89
|
+
_warning: "Authentication failed.",
|
|
90
|
+
_next_step: "config(method='login') to re-authenticate.",
|
|
91
|
+
};
|
|
92
|
+
case "error_not_found":
|
|
93
|
+
return {
|
|
94
|
+
_warning: "Entity not found.",
|
|
95
|
+
_tip: "Check the ID is correct and you're using the right profile/environment.",
|
|
96
|
+
};
|
|
97
|
+
case "error_validation":
|
|
98
|
+
return {
|
|
99
|
+
_warning: "Validation blocked the operation before it reached the API.",
|
|
100
|
+
_debug_steps: [
|
|
101
|
+
"Read the validation errors — they describe exactly what to fix",
|
|
102
|
+
"Fix the issues and retry",
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
case "error":
|
|
106
|
+
return {
|
|
107
|
+
_tip: "If this error is unclear, use feedback(method='submit', category='error_unclear', message='...') to report it.",
|
|
108
|
+
};
|
|
109
|
+
case "partial":
|
|
110
|
+
case "success":
|
|
111
|
+
default:
|
|
112
|
+
return {};
|
|
113
|
+
}
|
|
114
|
+
}
|