@ema.co/mcp-toolkit 2026.1.29-9 → 2026.2.5
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/mcp-tools-guide.md +5 -3
- package/README.md +15 -17
- package/dist/cli/index.js +1 -1
- package/dist/{sdk → mcp}/autobuilder.js +1 -1
- package/dist/mcp/domain/dashboard.js +224 -0
- package/dist/{sdk → mcp/domain}/generation-schema.js +1 -1
- package/dist/{sdk → mcp/domain}/intent-architect.js +3 -3
- package/dist/{sdk → mcp/domain}/structural-rules.js +43 -0
- package/dist/{sdk → mcp/domain}/validation-rules.js +154 -9
- package/dist/{sdk → mcp/domain}/workflow-def-schema.js +14 -30
- package/dist/{sdk → mcp/domain}/workflow-execution-analyzer.js +1 -1
- package/dist/{sdk → mcp/domain}/workflow-generator.js +1 -1
- package/dist/{sdk → mcp/domain}/workflow-transformer.js +93 -8
- package/dist/{sdk → mcp}/guidance.js +94 -121
- package/dist/mcp/handlers/action/index.js +1 -1
- package/dist/mcp/handlers/catalog/index.js +236 -0
- package/dist/mcp/handlers/data/dashboard-clone.js +41 -55
- package/dist/mcp/handlers/data/index.js +151 -61
- package/dist/mcp/handlers/demo/index.js +552 -0
- package/dist/mcp/handlers/deprecation.js +25 -0
- package/dist/mcp/handlers/env/config.js +296 -0
- package/dist/mcp/handlers/env/index.js +2 -2
- package/dist/mcp/handlers/index.js +1 -1
- package/dist/mcp/handlers/persona/create.js +28 -2
- package/dist/mcp/handlers/persona/delete.js +46 -1
- package/dist/mcp/handlers/persona/intent.js +1 -1
- package/dist/mcp/handlers/persona/update.js +87 -4
- package/dist/mcp/handlers/persona/version.js +2 -2
- package/dist/mcp/handlers/reference/index.js +1 -1
- package/dist/mcp/handlers/sync/direct.js +229 -0
- package/dist/mcp/handlers/template/index.js +1 -1
- package/dist/mcp/handlers/utils.js +1 -1
- package/dist/mcp/handlers/workflow/analyze.js +1 -1
- package/dist/mcp/handlers/workflow/deploy.js +105 -3
- package/dist/mcp/handlers/workflow/fix.js +588 -0
- package/dist/mcp/handlers/workflow/generate.js +7 -7
- package/dist/mcp/handlers/workflow/index.js +47 -45
- package/dist/mcp/handlers/workflow/utils.js +1 -1
- package/dist/mcp/handlers/workflow/validate.js +3 -3
- package/dist/mcp/handlers/workflow/validation.js +290 -56
- package/dist/mcp/handlers-consolidated.js +11 -28
- package/dist/{sdk → mcp}/knowledge.js +166 -43
- package/dist/mcp/prompts.js +7 -6
- package/dist/mcp/resources.js +618 -14
- package/dist/mcp/server.js +227 -1979
- package/dist/mcp/tools.js +43 -21
- package/dist/sdk/ema-client.js +237 -15
- package/dist/sdk/generated/api-client/index.js +1 -1
- package/dist/sdk/generated/api-client/sdk.gen.js +145 -102
- package/dist/sdk/generated/api-types.js +5 -3
- package/dist/sdk/generated/protos/service/agent_qa/v1/agent_qa_pb.js +260 -5
- package/dist/sdk/generated/protos/service/dataingest/v1/dataingest_pb.js +1 -1
- package/dist/sdk/generated/protos/service/document_store/v1/rfp_response_pb.js +71 -10
- package/dist/sdk/generated/protos/service/persona/v1/persona_config_pb.js +1 -1
- package/dist/sdk/generated/protos/service/persona/v1/voicebot_widgets/widget_types_pb.js +14 -9
- package/dist/sdk/generated/protos/service/search/v1/search_pb.js +34 -28
- package/dist/sdk/generated/protos/service/transform/v1/transform_pb.js +11 -5
- package/dist/sdk/generated/protos/service/workflows/v1/action_type_pb.js +1 -1
- package/dist/sdk/generated/protos/service/workflows/v1/dashboards_pb.js +1 -1
- package/dist/sdk/generated/protos/service/workflows/v1/rpc/workflow_rpc_pb.js +7 -7
- package/dist/sdk/generated/protos/service/workflows/v1/well_known_pb.js +1 -1
- package/dist/sdk/generated/protos/service/workflows/v1/workflow_pb.js +1 -1
- package/dist/sdk/index.js +29 -126
- package/dist/{sdk/sync.js → sync/sdk.js} +7 -3
- package/package.json +1 -1
- package/dist/mcp/handlers/workflow/modify.js +0 -528
- /package/dist/{sdk → mcp}/demo-generator.js +0 -0
- /package/dist/{sdk → mcp/domain}/action-schema-parser.js +0 -0
- /package/dist/{sdk → mcp/domain}/quality-gates.js +0 -0
- /package/dist/{sdk → mcp/domain}/sanitizer.js +0 -0
- /package/dist/{sdk → mcp/domain}/workflow-intent.js +0 -0
- /package/dist/{sdk → mcp/domain}/workflow-merge.js +0 -0
- /package/dist/{sdk → mcp/domain}/workflow-optimizer.js +0 -0
- /package/dist/{sdk → mcp/domain}/workflow-path-enumerator.js +0 -0
- /package/dist/{sdk → mcp/domain}/workflow-static-validator.js +0 -0
- /package/dist/{sdk → mcp/domain}/workflow-tracer.js +0 -0
- /package/dist/{sdk → mcp/domain}/workflow-validation-types.js +0 -0
- /package/dist/{sdk → mcp/domain}/workflow-validator.js +0 -0
- /package/dist/{sdk → sync}/sync-options.js +0 -0
- /package/dist/{sdk → sync}/version-policy.js +0 -0
- /package/dist/{sdk → sync}/version-storage.js +0 -0
- /package/dist/{sdk → sync}/version-tracking.js +0 -0
|
@@ -510,9 +510,11 @@ call_llm/v0 → Use call_llm/v2
|
|
|
510
510
|
|
|
511
511
|
| Scenario | Default | How to Add HITL |
|
|
512
512
|
|----------|---------|-----------------|
|
|
513
|
-
| Send email | ❌ No HITL |
|
|
514
|
-
| External API | ❌ No HITL |
|
|
515
|
-
| Create records | ❌ No HITL |
|
|
513
|
+
| Send email | ❌ No HITL | Get workflow → add general_hitl node before email → deploy |
|
|
514
|
+
| External API | ❌ No HITL | Get workflow → add general_hitl node → deploy |
|
|
515
|
+
| Create records | ❌ No HITL | Get workflow → add general_hitl node → deploy |
|
|
516
|
+
|
|
517
|
+
**Pattern:** `workflow(mode="get")` → modify workflow_def → `workflow(mode="deploy")`
|
|
516
518
|
|
|
517
519
|
## Auto Builder Prompt Length Limits
|
|
518
520
|
|
package/README.md
CHANGED
|
@@ -223,27 +223,25 @@ persona(input="IT helpdesk with KB search", type="chat", name="IT Support", prev
|
|
|
223
223
|
|
|
224
224
|
**Modify existing AI Employee (LLM-Driven):**
|
|
225
225
|
```typescript
|
|
226
|
-
// Step 1: Get workflow
|
|
227
|
-
|
|
228
|
-
// Returns:
|
|
229
|
-
|
|
230
|
-
// Step 2: Build and execute structured operations
|
|
231
|
-
persona(mode="modify", id="abc-123", operations=[
|
|
232
|
-
{ type: "insert", insert: { action_type: "hitl", insert_before: "send_email" }},
|
|
233
|
-
{ type: "remove", remove: { nodes: ["unused_node"] }}
|
|
234
|
-
], preview=true)
|
|
235
|
-
```
|
|
226
|
+
// Step 1: Get current workflow
|
|
227
|
+
workflow(mode="get", persona_id="abc-123")
|
|
228
|
+
// Returns: workflow_def, schema, guidance, fingerprint
|
|
236
229
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
230
|
+
// Step 2: LLM modifies the workflow_def JSON
|
|
231
|
+
// (add nodes, remove nodes, rewire connections)
|
|
232
|
+
|
|
233
|
+
// Step 3: Deploy modified workflow
|
|
234
|
+
// IMPORTANT: pass base_fingerprint from the get() response to prevent overwriting out-of-band changes
|
|
235
|
+
workflow(mode="deploy", persona_id="abc-123", base_fingerprint="<fingerprint>", workflow_def={...})
|
|
236
|
+
|
|
237
|
+
// If the workflow_def is large (near transport limits), deploy from a file instead:
|
|
238
|
+
// workflow(mode="deploy", persona_id="abc-123", base_fingerprint="<fingerprint>", workflow_def_path="/path/to/wf.json")
|
|
240
239
|
```
|
|
241
240
|
|
|
242
241
|
**Get reference info:**
|
|
243
242
|
```typescript
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
reference(type="patterns", pattern="intent-routing") // Get pattern
|
|
243
|
+
catalog(type="actions", id="send_email") // Get action details
|
|
244
|
+
catalog(type="templates") // List templates
|
|
247
245
|
```
|
|
248
246
|
|
|
249
247
|
## Dynamic Resources
|
|
@@ -276,7 +274,7 @@ The MCP is fully self-contained—no external configuration files needed.
|
|
|
276
274
|
- Read `ema://docs/usage-guide` (markdown) for documentation
|
|
277
275
|
- Read `ema://guidance/cursor-rule` (.mdc) for IDE integration
|
|
278
276
|
|
|
279
|
-
All guidance flows from a single source (`src/
|
|
277
|
+
All guidance flows from a single source (`src/mcp/guidance.ts`).
|
|
280
278
|
|
|
281
279
|
---
|
|
282
280
|
|
package/dist/cli/index.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { loadConfig } from "../sdk/config.js";
|
|
14
14
|
import { EmaClient } from "../sdk/client.js";
|
|
15
|
-
import { SyncSDK } from "../sdk
|
|
15
|
+
import { SyncSDK } from "../sync/sdk.js";
|
|
16
16
|
function printUsage() {
|
|
17
17
|
console.log(`
|
|
18
18
|
Ema Agent Sync CLI
|
|
@@ -274,7 +274,7 @@ export function generateAutobuilderPrompt(description, personaType) {
|
|
|
274
274
|
prompt += "- Use external_action_caller or send_email_agent as needed\n";
|
|
275
275
|
}
|
|
276
276
|
if (hasHITL) {
|
|
277
|
-
prompt += "-
|
|
277
|
+
prompt += "- Enable HITL flag on agents that need approval (do NOT add standalone general_hitl nodes)\n";
|
|
278
278
|
}
|
|
279
279
|
prompt += `- ${config.outputNote}\n`;
|
|
280
280
|
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard Domain Logic
|
|
3
|
+
*
|
|
4
|
+
* Business logic for dashboard operations including:
|
|
5
|
+
* - Sanitization of PII in column values
|
|
6
|
+
* - Nested data transformation with sanitization
|
|
7
|
+
* - Validation helpers
|
|
8
|
+
*
|
|
9
|
+
* Note: Pure format translation (without sanitization) is in SDK's columnValueToInput().
|
|
10
|
+
*/
|
|
11
|
+
import { columnValueToInput } from "../../sdk/ema-client.js";
|
|
12
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
13
|
+
// Sanitization
|
|
14
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
15
|
+
/**
|
|
16
|
+
* Sanitization session for PII replacement.
|
|
17
|
+
* Maintains consistent replacements across multiple values.
|
|
18
|
+
*/
|
|
19
|
+
export class SanitizationSession {
|
|
20
|
+
replacements = new Map();
|
|
21
|
+
counters = {};
|
|
22
|
+
/**
|
|
23
|
+
* Get or create a replacement for a detected PII value.
|
|
24
|
+
* Returns consistent replacements for the same value.
|
|
25
|
+
*/
|
|
26
|
+
getOrCreateReplacement(value, type) {
|
|
27
|
+
const existing = this.replacements.get(value);
|
|
28
|
+
if (existing)
|
|
29
|
+
return existing;
|
|
30
|
+
this.counters[type] = (this.counters[type] ?? 0) + 1;
|
|
31
|
+
const replacement = `[${type.toUpperCase()}_${this.counters[type]}]`;
|
|
32
|
+
this.replacements.set(value, replacement);
|
|
33
|
+
return replacement;
|
|
34
|
+
}
|
|
35
|
+
/** Get all replacements made in this session */
|
|
36
|
+
getReplacements() {
|
|
37
|
+
return new Map(this.replacements);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Detect PII patterns in text using regex.
|
|
42
|
+
*/
|
|
43
|
+
export function detectPiiPatterns(text) {
|
|
44
|
+
const detected = [];
|
|
45
|
+
// Email pattern
|
|
46
|
+
const emailRegex = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
47
|
+
for (const match of text.matchAll(emailRegex)) {
|
|
48
|
+
detected.push({ value: match[0], type: "email" });
|
|
49
|
+
}
|
|
50
|
+
// Phone pattern (various formats)
|
|
51
|
+
const phoneRegex = /\b(?:\+?1[-.\s]?)?\(?[0-9]{3}\)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{4}\b/g;
|
|
52
|
+
for (const match of text.matchAll(phoneRegex)) {
|
|
53
|
+
detected.push({ value: match[0], type: "phone" });
|
|
54
|
+
}
|
|
55
|
+
return detected;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Sanitize a string value by replacing detected PII.
|
|
59
|
+
*/
|
|
60
|
+
export function sanitizeString(value, session, additionalPatterns) {
|
|
61
|
+
let result = value;
|
|
62
|
+
// Pattern-based sanitization
|
|
63
|
+
const detected = detectPiiPatterns(result);
|
|
64
|
+
for (const entity of detected) {
|
|
65
|
+
const replacement = session.getOrCreateReplacement(entity.value, entity.type);
|
|
66
|
+
result = result.split(entity.value).join(replacement);
|
|
67
|
+
}
|
|
68
|
+
// User-provided patterns
|
|
69
|
+
if (additionalPatterns) {
|
|
70
|
+
for (const pattern of additionalPatterns) {
|
|
71
|
+
if (result.includes(pattern)) {
|
|
72
|
+
const replacement = session.getOrCreateReplacement(pattern, "custom");
|
|
73
|
+
result = result.split(pattern).join(replacement);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
80
|
+
// Column Value Conversion with Sanitization
|
|
81
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
82
|
+
/**
|
|
83
|
+
* Convert a column value to DashboardInput with optional sanitization.
|
|
84
|
+
*
|
|
85
|
+
* This wraps the SDK's pure conversion function and adds sanitization.
|
|
86
|
+
* Use this in MCP handlers when sanitization is needed.
|
|
87
|
+
*
|
|
88
|
+
* @param name - The column/field name
|
|
89
|
+
* @param value - The column value from API response
|
|
90
|
+
* @param session - Sanitization session (null to skip sanitization)
|
|
91
|
+
* @param additionalPatterns - Additional strings to sanitize
|
|
92
|
+
* @returns DashboardInput for upload, or null if value is empty/unsupported
|
|
93
|
+
*/
|
|
94
|
+
export function columnValueToInputWithSanitization(name, value, session, additionalPatterns) {
|
|
95
|
+
if (!value)
|
|
96
|
+
return null;
|
|
97
|
+
// If no sanitization needed, delegate to SDK
|
|
98
|
+
if (!session) {
|
|
99
|
+
return columnValueToInput(name, value);
|
|
100
|
+
}
|
|
101
|
+
// Handle string value with sanitization
|
|
102
|
+
if (value.stringValue !== undefined) {
|
|
103
|
+
const sanitized = sanitizeString(value.stringValue, session, additionalPatterns);
|
|
104
|
+
return { name, string_value: sanitized };
|
|
105
|
+
}
|
|
106
|
+
// Handle number value (no sanitization needed)
|
|
107
|
+
if (value.numberValue !== undefined) {
|
|
108
|
+
return { name, number_value: value.numberValue };
|
|
109
|
+
}
|
|
110
|
+
// Handle boolean value (no sanitization needed)
|
|
111
|
+
if (value.booleanValue !== undefined) {
|
|
112
|
+
return { name, boolean_value: value.booleanValue };
|
|
113
|
+
}
|
|
114
|
+
// Handle array value (recursively with sanitization)
|
|
115
|
+
if (value.arrayValue) {
|
|
116
|
+
const elements = value.arrayValue.arrayValues ?? [];
|
|
117
|
+
if (elements.length === 0)
|
|
118
|
+
return null;
|
|
119
|
+
const arrayInputs = [];
|
|
120
|
+
for (const elem of elements) {
|
|
121
|
+
const converted = columnValueToInputWithSanitization("element", elem, session, additionalPatterns);
|
|
122
|
+
if (converted)
|
|
123
|
+
arrayInputs.push(converted);
|
|
124
|
+
}
|
|
125
|
+
return arrayInputs.length > 0 ? { name, array_value: arrayInputs } : null;
|
|
126
|
+
}
|
|
127
|
+
// Handle object value (recursively with sanitization)
|
|
128
|
+
if (value.objectValue) {
|
|
129
|
+
const objectVals = value.objectValue.objectValues ?? {};
|
|
130
|
+
const keys = Object.keys(objectVals);
|
|
131
|
+
if (keys.length === 0)
|
|
132
|
+
return null;
|
|
133
|
+
const objectInput = {};
|
|
134
|
+
for (const key of keys) {
|
|
135
|
+
const converted = columnValueToInputWithSanitization(key, objectVals[key], session, additionalPatterns);
|
|
136
|
+
if (converted)
|
|
137
|
+
objectInput[key] = converted;
|
|
138
|
+
}
|
|
139
|
+
return Object.keys(objectInput).length > 0 ? { name, object_value: objectInput } : null;
|
|
140
|
+
}
|
|
141
|
+
// Unsupported types - delegate to SDK (will return null)
|
|
142
|
+
return columnValueToInput(name, value);
|
|
143
|
+
}
|
|
144
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
145
|
+
// Nested Data Validation
|
|
146
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
147
|
+
/** Maximum array elements to prevent oversized payloads */
|
|
148
|
+
export const MAX_ARRAY_ELEMENTS = 1000;
|
|
149
|
+
/** Maximum nesting depth for arrays/objects */
|
|
150
|
+
export const MAX_NESTING_DEPTH = 3;
|
|
151
|
+
/**
|
|
152
|
+
* Validate nested data structure.
|
|
153
|
+
* Returns errors/warnings for issues found.
|
|
154
|
+
*/
|
|
155
|
+
export function validateNestedData(value, schemaType, path = "", depth = 0) {
|
|
156
|
+
const errors = [];
|
|
157
|
+
// Check nesting depth
|
|
158
|
+
if (depth > MAX_NESTING_DEPTH) {
|
|
159
|
+
errors.push({
|
|
160
|
+
path,
|
|
161
|
+
message: `Nesting depth exceeds maximum of ${MAX_NESTING_DEPTH}`,
|
|
162
|
+
severity: "error",
|
|
163
|
+
});
|
|
164
|
+
return errors;
|
|
165
|
+
}
|
|
166
|
+
// Validate based on schema type
|
|
167
|
+
if (schemaType === "COLUMN_TYPE_ARRAY" || schemaType === "ARRAY") {
|
|
168
|
+
if (!Array.isArray(value)) {
|
|
169
|
+
errors.push({
|
|
170
|
+
path,
|
|
171
|
+
message: `Expected array for ARRAY column, got ${typeof value}`,
|
|
172
|
+
severity: "error",
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
else if (value.length > MAX_ARRAY_ELEMENTS) {
|
|
176
|
+
errors.push({
|
|
177
|
+
path,
|
|
178
|
+
message: `Array has ${value.length} elements, exceeds maximum of ${MAX_ARRAY_ELEMENTS}`,
|
|
179
|
+
severity: "error",
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else if (schemaType === "COLUMN_TYPE_OBJECT" || schemaType === "OBJECT") {
|
|
184
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
185
|
+
errors.push({
|
|
186
|
+
path,
|
|
187
|
+
message: `Expected object for OBJECT column, got ${Array.isArray(value) ? "array" : typeof value}`,
|
|
188
|
+
severity: "error",
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else if (schemaType === "COLUMN_TYPE_STRING" || schemaType === "STRING") {
|
|
193
|
+
if (typeof value !== "string") {
|
|
194
|
+
if (Array.isArray(value)) {
|
|
195
|
+
errors.push({
|
|
196
|
+
path,
|
|
197
|
+
message: `Column expects scalar STRING value, got array. Did you mean to use an ARRAY column?`,
|
|
198
|
+
severity: "error",
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
else if (typeof value === "object" && value !== null) {
|
|
202
|
+
errors.push({
|
|
203
|
+
path,
|
|
204
|
+
message: `Column expects scalar STRING value, got object. Did you mean to use an OBJECT column?`,
|
|
205
|
+
severity: "error",
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return errors;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Check if a value contains nested data (array or object).
|
|
214
|
+
*/
|
|
215
|
+
export function hasNestedData(value) {
|
|
216
|
+
if (Array.isArray(value))
|
|
217
|
+
return true;
|
|
218
|
+
if (typeof value === "object" && value !== null) {
|
|
219
|
+
// Check if it looks like a nested structure vs a simple object
|
|
220
|
+
const keys = Object.keys(value);
|
|
221
|
+
return keys.some(k => !["name", "string_value", "number_value", "boolean_value", "document_value"].includes(k));
|
|
222
|
+
}
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* The auto-builder sends ~70K tokens of documentation per request.
|
|
8
8
|
* This schema reduces that to ~5K tokens of actionable constraints.
|
|
9
9
|
*/
|
|
10
|
-
import { AGENT_CATALOG } from "
|
|
10
|
+
import { AGENT_CATALOG } from "../knowledge.js";
|
|
11
11
|
import { INPUT_SOURCE_RULES } from "./validation-rules.js";
|
|
12
12
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
13
13
|
// Schema Generation
|
|
@@ -34,7 +34,7 @@ import * as fs from "fs";
|
|
|
34
34
|
import * as path from "path";
|
|
35
35
|
import { fileURLToPath } from "node:url";
|
|
36
36
|
import { dirname } from "node:path";
|
|
37
|
-
import { getResourcePath } from "
|
|
37
|
+
import { getResourcePath } from "../../sdk/paths.js";
|
|
38
38
|
// ESM-compatible __dirname
|
|
39
39
|
const __filename = fileURLToPath(import.meta.url);
|
|
40
40
|
const __dirname = dirname(__filename);
|
|
@@ -54,8 +54,8 @@ export function loadGateConfig(forceReload = false) {
|
|
|
54
54
|
}
|
|
55
55
|
// Try multiple paths (works in both src and dist)
|
|
56
56
|
const possiblePaths = [
|
|
57
|
-
path.resolve(__dirname, "../../resources/config/gates.json"),
|
|
58
57
|
path.resolve(__dirname, "../../../resources/config/gates.json"),
|
|
58
|
+
path.resolve(__dirname, "../../../../resources/config/gates.json"),
|
|
59
59
|
getResourcePath("resources/config/gates.json"),
|
|
60
60
|
];
|
|
61
61
|
for (const configPath of possiblePaths) {
|
|
@@ -131,7 +131,7 @@ let _cachedPrompt = null;
|
|
|
131
131
|
export function getIntentArchitectPrompt() {
|
|
132
132
|
if (_cachedPrompt)
|
|
133
133
|
return _cachedPrompt;
|
|
134
|
-
const promptPath = path.join(__dirname, "
|
|
134
|
+
const promptPath = path.join(__dirname, "../../prompts/intent-architect.md");
|
|
135
135
|
try {
|
|
136
136
|
_cachedPrompt = fs.readFileSync(promptPath, "utf-8");
|
|
137
137
|
return _cachedPrompt;
|
|
@@ -129,6 +129,25 @@ export const STRUCTURAL_INVARIANTS = [
|
|
|
129
129
|
fix: "Use entity_extraction to extract email_address, then connect to to_email",
|
|
130
130
|
severity: "critical",
|
|
131
131
|
},
|
|
132
|
+
{
|
|
133
|
+
id: "inline_type_format",
|
|
134
|
+
name: "Inline Values Must Use Correct Type Format",
|
|
135
|
+
rule: "When providing inline values, the format must match the expected input type. " +
|
|
136
|
+
"stringValue → STRING, textWithSources → TEXT_WITH_SOURCES, enumValue → ENUM. " +
|
|
137
|
+
"These are NOT interchangeable - the Go workflow engine validates types at deploy time.",
|
|
138
|
+
violation: "Node input expects TEXT_WITH_SOURCES but inline uses { wellKnown: { stringValue: '...' } } (STRING type)",
|
|
139
|
+
fix: "Change to { wellKnown: { textWithSources: { text: '...', sources: [], resultConfidence: 0, toolSources: [], resultType: 0 } } }",
|
|
140
|
+
severity: "critical",
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: "named_inputs_binding_format",
|
|
144
|
+
name: "Named Inputs Must Use multiBinding Format",
|
|
145
|
+
rule: "The named_inputs field in workflow_def requires protobuf-compatible multiBinding.elements[].namedBinding structure. " +
|
|
146
|
+
"Plain objects or arrays will cause deploy failure.",
|
|
147
|
+
violation: "named_inputs: { key: value } or named_inputs: [{ name: 'key', value: ... }]",
|
|
148
|
+
fix: "Use: { multiBinding: { elements: [{ namedBinding: { name: 'key', value: <binding>, description: '', isOptional: false }, autoDetectedBinding: false }] }, autoDetectedBinding: false }",
|
|
149
|
+
severity: "critical",
|
|
150
|
+
},
|
|
132
151
|
{
|
|
133
152
|
id: "no_redundant_classifiers",
|
|
134
153
|
name: "No Redundant Classifiers",
|
|
@@ -313,6 +332,25 @@ BEFORE finalizing any workflow modification, verify these rules:
|
|
|
313
332
|
|
|
314
333
|
10. **Both Paths Required**: general_hitl needs handlers for both approval AND rejection.
|
|
315
334
|
|
|
335
|
+
### Raw workflow_def Format Rules (CRITICAL)
|
|
336
|
+
|
|
337
|
+
11. **Inline Type Format** (wrong format → HTTP 500 with no error details):
|
|
338
|
+
| Input Expects | Inline Format |
|
|
339
|
+
|---------------|---------------|
|
|
340
|
+
| STRING | \`{ wellKnown: { stringValue: "..." } }\` |
|
|
341
|
+
| TEXT_WITH_SOURCES | \`{ wellKnown: { textWithSources: { text: "...", sources: [], resultConfidence: 0, toolSources: [], resultType: 0 } } }\` |
|
|
342
|
+
| ENUM | \`{ enumValue: "CategoryName" }\` |
|
|
343
|
+
|
|
344
|
+
**WARNING**: STRING ≠ TEXT_WITH_SOURCES. Wrong format → HTTP 500 with no error details.
|
|
345
|
+
|
|
346
|
+
12. **named_inputs Format**: Must use \`multiBinding.elements[].namedBinding\` structure (protobuf JSON format). Plain objects or arrays cause deploy failure.
|
|
347
|
+
|
|
348
|
+
13. **Action Namespaces**: Must be \`["actions", "emainternal"]\`, not empty \`[]\`. Empty namespaces cause deploy failure.
|
|
349
|
+
|
|
350
|
+
14. **Categorizer typeArguments**: Categorizers require \`typeArguments.categories\` pointing to an enumType. Empty \`typeArguments: {}\` causes deploy failure.
|
|
351
|
+
|
|
352
|
+
15. **Dashboard Document Upload Order**: For dashboard personas, documents must be uploaded AFTER the persona is enabled. The workflow must be active to process input documents. Order: create → deploy workflow → enable → upload documents.
|
|
353
|
+
|
|
316
354
|
### Self-Check Checklist
|
|
317
355
|
|
|
318
356
|
After generating/modifying a workflow, verify:
|
|
@@ -325,6 +363,11 @@ After generating/modifying a workflow, verify:
|
|
|
325
363
|
- [ ] Response nodes are mutually exclusive (gated by runIf)
|
|
326
364
|
- [ ] Email recipients come from entity_extraction
|
|
327
365
|
- [ ] No circular dependencies
|
|
366
|
+
- [ ] Inline values use correct type format (stringValue vs textWithSources)
|
|
367
|
+
- [ ] named_inputs uses multiBinding.elements[].namedBinding format
|
|
368
|
+
- [ ] Action namespaces are ["actions", "emainternal"], not empty []
|
|
369
|
+
- [ ] Categorizers have typeArguments.categories pointing to enumType
|
|
370
|
+
- [ ] Dashboard personas: enable persona BEFORE uploading documents
|
|
328
371
|
`;
|
|
329
372
|
export const GRAPH_ANALYSIS_RULES = [
|
|
330
373
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -32,11 +32,17 @@ export const INPUT_SOURCE_RULES = [
|
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
actionPattern: "text_categorizer",
|
|
35
|
-
recommended: "user_query",
|
|
36
|
-
avoid: ["chat_conversation"],
|
|
37
|
-
reason: "text_categorizer
|
|
35
|
+
recommended: "named_inputs (v1) or user_query (v0 deprecated)",
|
|
36
|
+
avoid: ["chat_conversation", "stringValue for categorization_instructions"],
|
|
37
|
+
reason: "text_categorizer/v1 uses named_inputs (multiBinding format) for input context. " +
|
|
38
|
+
"categorization_instructions must be TEXT_WITH_SOURCES (textWithSources), not STRING (stringValue). " +
|
|
39
|
+
"v0 is DEPRECATED - use v1 with named_inputs. " +
|
|
40
|
+
"For conversation routing, use chat_categorizer instead.",
|
|
38
41
|
severity: "critical",
|
|
39
|
-
fix: "Use
|
|
42
|
+
fix: "Use text_categorizer/v1 with: named_inputs (multiBinding format), " +
|
|
43
|
+
"categorization_instructions (textWithSources inline), " +
|
|
44
|
+
"typeArguments.categories pointing to enumType, " +
|
|
45
|
+
"and ensure enumType has Fallback category",
|
|
40
46
|
},
|
|
41
47
|
{
|
|
42
48
|
actionPattern: "respond_with_sources",
|
|
@@ -126,12 +132,13 @@ export const ANTI_PATTERNS = [
|
|
|
126
132
|
{
|
|
127
133
|
id: "email-without-validation",
|
|
128
134
|
name: "Email Without Input Validation",
|
|
129
|
-
pattern: "send_email_agent without entity_extraction
|
|
135
|
+
pattern: "send_email_agent without entity_extraction to validate recipient data",
|
|
130
136
|
problem: "Sending emails without extracting and validating recipient data risks sending to wrong people or with wrong content. Emails are high-impact actions with external side effects.",
|
|
131
|
-
solution: "Always: 1) Extract required fields (email_address, subject) via entity_extraction, 2) Validate completeness via categorizer, 3) Ask user if missing
|
|
137
|
+
solution: "Always: 1) Extract required fields (email_address, subject) via entity_extraction, 2) Validate completeness via categorizer, 3) Ask user if missing. " +
|
|
138
|
+
"For approval: enable the HITL flag on send_email_agent (do NOT add a standalone general_hitl node).",
|
|
132
139
|
detection: {
|
|
133
|
-
issueType: "
|
|
134
|
-
condition: "send_email_agent without preceding entity_extraction
|
|
140
|
+
issueType: "incomplete_email_validation",
|
|
141
|
+
condition: "send_email_agent without preceding entity_extraction node to extract/validate recipient data",
|
|
135
142
|
},
|
|
136
143
|
severity: "critical",
|
|
137
144
|
},
|
|
@@ -188,12 +195,15 @@ export const ANTI_PATTERNS = [
|
|
|
188
195
|
name: "Incomplete HITL Paths",
|
|
189
196
|
pattern: "HITL with only success path",
|
|
190
197
|
problem: "Rejected requests have no handling, leaving users without response.",
|
|
191
|
-
solution: "ALWAYS implement both success AND failure paths
|
|
198
|
+
solution: "If workflow already has general_hitl node: ALWAYS implement both success AND failure paths. For NEW workflows: use HITL flag on the agent instead of standalone general_hitl nodes (see hitl-patterns guidance).",
|
|
192
199
|
detection: {
|
|
193
200
|
issueType: "incomplete_hitl",
|
|
194
201
|
condition: "HITL node missing 'hitl_status_HITL Success' or 'hitl_status_HITL Failure' edge (note: space, not underscore)",
|
|
195
202
|
},
|
|
196
203
|
severity: "critical",
|
|
204
|
+
// TODO: Revisit if general_hitl is re-enabled as a standalone node.
|
|
205
|
+
// Currently HITL is a flag on agents (send_email_agent, external_action_caller).
|
|
206
|
+
// This anti-pattern still fires for existing workflows that use general_hitl.
|
|
197
207
|
},
|
|
198
208
|
{
|
|
199
209
|
id: "orphan-nodes",
|
|
@@ -317,6 +327,83 @@ export const ANTI_PATTERNS = [
|
|
|
317
327
|
},
|
|
318
328
|
severity: "critical",
|
|
319
329
|
},
|
|
330
|
+
{
|
|
331
|
+
id: "type-mismatch-inline",
|
|
332
|
+
name: "Inline Type Mismatch",
|
|
333
|
+
pattern: "Using stringValue inline where TEXT_WITH_SOURCES input expected, or vice versa",
|
|
334
|
+
problem: "The Go workflow engine validates type compatibility at deploy time. " +
|
|
335
|
+
"stringValue (STRING type) is NOT interchangeable with textWithSources (TEXT_WITH_SOURCES type). " +
|
|
336
|
+
"Mismatches cause HTTP 500 with no error details.",
|
|
337
|
+
solution: "Match inline value format to expected input type:\n" +
|
|
338
|
+
"- STRING input → { inline: { wellKnown: { stringValue: '...' } } }\n" +
|
|
339
|
+
"- TEXT_WITH_SOURCES input → { inline: { wellKnown: { textWithSources: { text: '...', sources: [], resultConfidence: 0, toolSources: [], resultType: 0 } } } }\n" +
|
|
340
|
+
"- ENUM input → { inline: { enumValue: 'CategoryName' } }",
|
|
341
|
+
detection: {
|
|
342
|
+
issueType: "type_mismatch_inline",
|
|
343
|
+
condition: "Inline value format doesn't match expected input type",
|
|
344
|
+
},
|
|
345
|
+
severity: "critical",
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
id: "wrong-named-inputs-format",
|
|
349
|
+
name: "Wrong named_inputs Format",
|
|
350
|
+
pattern: "Using plain objects or arrays for named_inputs instead of multiBinding format",
|
|
351
|
+
problem: "named_inputs expects protobuf-compatible multiBinding.elements[].namedBinding structure. " +
|
|
352
|
+
"Plain JSON objects cause deploy failure (HTTP 500).",
|
|
353
|
+
solution: "Use the correct multiBinding format:\n" +
|
|
354
|
+
'{ "multiBinding": { "elements": [{ "namedBinding": { "name": "key", "value": <binding>, "description": "", "isOptional": false }, "autoDetectedBinding": false }] }, "autoDetectedBinding": false }',
|
|
355
|
+
detection: {
|
|
356
|
+
issueType: "wrong_named_inputs_format",
|
|
357
|
+
condition: "named_inputs value is not a multiBinding structure",
|
|
358
|
+
},
|
|
359
|
+
severity: "critical",
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
id: "categorizer-missing-type-arguments",
|
|
363
|
+
name: "Categorizer Missing typeArguments",
|
|
364
|
+
pattern: "text_categorizer or chat_categorizer with empty typeArguments ({})",
|
|
365
|
+
problem: "Categorizers require typeArguments.categories pointing to the enum type. " +
|
|
366
|
+
"Empty typeArguments causes deploy failure.",
|
|
367
|
+
solution: "Add typeArguments:\n" +
|
|
368
|
+
'{ "typeArguments": { "categories": { "enumType": { "name": { "name": "my_enum", "namespaces": [] } }, "isList": false } } }\n' +
|
|
369
|
+
"And ensure a matching enumType exists in workflow_def.enumTypes[].",
|
|
370
|
+
detection: {
|
|
371
|
+
issueType: "categorizer_missing_type_arguments",
|
|
372
|
+
condition: "Categorizer node has empty typeArguments or missing categories key",
|
|
373
|
+
},
|
|
374
|
+
severity: "critical",
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
id: "empty-action-namespaces",
|
|
378
|
+
name: "Empty Action Namespaces",
|
|
379
|
+
pattern: "Action with namespaces: [] instead of proper namespace path",
|
|
380
|
+
problem: "Actions require namespaces: ['actions', 'emainternal']. " +
|
|
381
|
+
"Empty namespaces cause deploy failure.",
|
|
382
|
+
solution: "Use the correct namespace format:\n" +
|
|
383
|
+
'{ "action": { "name": { "namespaces": ["actions", "emainternal"], "name": "call_llm" }, "version": "v2" } }',
|
|
384
|
+
detection: {
|
|
385
|
+
issueType: "empty_action_namespaces",
|
|
386
|
+
condition: "Action has empty namespaces array []",
|
|
387
|
+
},
|
|
388
|
+
severity: "critical",
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
id: "dashboard-upload-before-enable",
|
|
392
|
+
name: "Dashboard Documents Uploaded Before Persona Enabled",
|
|
393
|
+
pattern: "Uploading input documents to a dashboard persona before the persona is enabled",
|
|
394
|
+
problem: "For dashboard personas, the workflow must be active (persona enabled) before uploading input documents. " +
|
|
395
|
+
"If documents are uploaded before enabling, the workflow isn't active to process them and the upload will fail.",
|
|
396
|
+
solution: "Follow this order:\n" +
|
|
397
|
+
"1. Create persona\n" +
|
|
398
|
+
"2. Deploy workflow: workflow(mode='deploy', persona_id='...', workflow_def={...})\n" +
|
|
399
|
+
"3. Enable persona (activate it)\n" +
|
|
400
|
+
"4. Upload documents: persona(id='...', data={method:'upload', items:[...]})",
|
|
401
|
+
detection: {
|
|
402
|
+
issueType: "dashboard_upload_before_enable",
|
|
403
|
+
condition: "Dashboard persona receives document upload while persona is not enabled/active",
|
|
404
|
+
},
|
|
405
|
+
severity: "critical",
|
|
406
|
+
},
|
|
320
407
|
{
|
|
321
408
|
id: "dashboard-input-columns",
|
|
322
409
|
name: "Dashboard Input Columns From Trigger Outputs",
|
|
@@ -349,6 +436,64 @@ export const ANTI_PATTERNS = [
|
|
|
349
436
|
},
|
|
350
437
|
severity: "critical",
|
|
351
438
|
},
|
|
439
|
+
{
|
|
440
|
+
id: "chat-conversation-without-chat-response",
|
|
441
|
+
name: "Chat Conversation Not Wired to Chat Response Node",
|
|
442
|
+
pattern: "Chat workflow where chat_conversation is consumed by processing nodes but never wired to a chat-aware response node (respond_with_sources or respond_for_external_actions)",
|
|
443
|
+
problem: "When chat_conversation is processed by nodes like entity_extraction, call_llm, or search, " +
|
|
444
|
+
"but the final response is generated by a generic call_llm (not a chat-aware response node), " +
|
|
445
|
+
"the response node lacks conversation history context. This causes:\n" +
|
|
446
|
+
"1. Duplicate/repetitive responses — the LLM re-asks questions already answered in conversation\n" +
|
|
447
|
+
"2. Lost context — follow-up messages lose prior context\n" +
|
|
448
|
+
"3. Hallucinated greetings — the LLM generates its own greeting instead of continuing the conversation\n\n" +
|
|
449
|
+
"The chat-aware response nodes (respond_with_sources, respond_for_external_actions) are specifically " +
|
|
450
|
+
"designed to handle conversation history and produce contextually appropriate responses.",
|
|
451
|
+
solution: "Wire chat_conversation through to a chat-aware response node:\n" +
|
|
452
|
+
"• For search-based responses: use respond_with_sources (takes query + search_results)\n" +
|
|
453
|
+
"• For tool/action results: use respond_for_external_actions (takes query + external_action_result)\n" +
|
|
454
|
+
"• If using call_llm as responder: wire chat_conversation into named_inputs so the LLM sees full history\n\n" +
|
|
455
|
+
"Correct patterns:\n" +
|
|
456
|
+
" chat_trigger → search → respond_with_sources → WORKFLOW_OUTPUT\n" +
|
|
457
|
+
" chat_trigger → external_action_caller → respond_for_external_actions → WORKFLOW_OUTPUT\n" +
|
|
458
|
+
" chat_trigger → call_llm(named_inputs includes chat_conversation) → WORKFLOW_OUTPUT\n\n" +
|
|
459
|
+
"Anti-patterns:\n" +
|
|
460
|
+
" ❌ chat_trigger → search → call_llm (no conversation context) → WORKFLOW_OUTPUT\n" +
|
|
461
|
+
" ❌ chat_trigger → entity_extraction → call_llm (stateless) → WORKFLOW_OUTPUT",
|
|
462
|
+
detection: {
|
|
463
|
+
issueType: "chat_conversation_not_wired_to_response",
|
|
464
|
+
condition: "Chat workflow (chat_trigger) where terminal response node is call_llm without chat_conversation in named_inputs, " +
|
|
465
|
+
"and respond_with_sources / respond_for_external_actions are not used",
|
|
466
|
+
},
|
|
467
|
+
severity: "critical",
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
id: "entity-extraction-direct-to-email",
|
|
471
|
+
name: "Entity Extraction Wired Directly to Email Inputs",
|
|
472
|
+
pattern: "entity_extraction outputs connected directly to send_email_agent.email_to, email_subject, or email_body",
|
|
473
|
+
problem: "entity_extraction outputs are typed as WELL_KNOWN_TYPE_ANY (structured extraction results), " +
|
|
474
|
+
"but send_email_agent inputs (email_to, email_subject, email_body) require WELL_KNOWN_TYPE_TEXT_WITH_SOURCES. " +
|
|
475
|
+
"Direct wiring causes type mismatch errors or corrupted email fields.\n\n" +
|
|
476
|
+
"Additionally, entity_extraction outputs may contain multiple extracted values in a structured format — " +
|
|
477
|
+
"wiring the entire output to email_to would send an object/JSON as the recipient address, not a clean email string.",
|
|
478
|
+
solution: "Use intermediary nodes to extract and format individual fields:\n\n" +
|
|
479
|
+
"Pattern A — json_mapper + fixed_response (recommended for multiple fields):\n" +
|
|
480
|
+
" entity_extraction → json_mapper (extract 'to', 'subject', 'body') → fixed_response({{to}}) → send_email_agent.email_to\n" +
|
|
481
|
+
" Same json_mapper → fixed_response({{subject}}) → send_email_agent.email_subject\n" +
|
|
482
|
+
" Same json_mapper → fixed_response({{body}}) → send_email_agent.email_body\n\n" +
|
|
483
|
+
"Pattern B — custom_agent with output_fields (simpler for LLM-generated content):\n" +
|
|
484
|
+
" custom_agent(output_fields=[To,Subject,Body]) → send_email_agent\n" +
|
|
485
|
+
" (custom_agent with output_fields produces individual TEXT_WITH_SOURCES outputs)\n\n" +
|
|
486
|
+
"Pattern C — fixed_response with template variables (for simple extraction):\n" +
|
|
487
|
+
" entity_extraction → fixed_response(template='{{email_address}}', custom_data=entity_extraction) → send_email_agent.email_to\n\n" +
|
|
488
|
+
"Key principle: send_email_agent inputs need TEXT_WITH_SOURCES — always use fixed_response or " +
|
|
489
|
+
"custom_agent(output_fields) as the adapter between extraction results and email fields.",
|
|
490
|
+
detection: {
|
|
491
|
+
issueType: "entity_extraction_direct_to_email",
|
|
492
|
+
condition: "entity_extraction output connected directly to send_email_agent.email_to, email_subject, or email_body " +
|
|
493
|
+
"without an intermediary json_mapper/fixed_response/custom_agent node",
|
|
494
|
+
},
|
|
495
|
+
severity: "critical",
|
|
496
|
+
},
|
|
352
497
|
];
|
|
353
498
|
export const OPTIMIZATION_RULES = [
|
|
354
499
|
{
|