@ema.co/mcp-toolkit 1.4.2 → 1.4.3
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/README.md +23 -1
- package/dist/mcp/handlers-consolidated.js +843 -116
- package/dist/mcp/prompts.js +134 -0
- package/dist/mcp/resources.js +4 -4
- package/dist/mcp/server.js +9 -215
- package/dist/mcp/tools-consolidated.js +71 -52
- package/dist/sdk/client.js +164 -20
- package/dist/sdk/contracts.js +1 -0
- package/docs/mcp-tools-guide.md +134 -17
- package/package.json +1 -1
package/dist/sdk/client.js
CHANGED
|
@@ -229,33 +229,46 @@ export class EmaClient {
|
|
|
229
229
|
return this.getPersonasForTenant();
|
|
230
230
|
}
|
|
231
231
|
/**
|
|
232
|
-
* Get a single persona by ID with full details including workflow_def.
|
|
232
|
+
* Get a single persona by ID with full details including workflow_def and proto_config.
|
|
233
233
|
* Uses /api/personas/{id} endpoint which returns the complete persona including workflow_def.
|
|
234
|
+
* Falls back to /api/ai_employee/get_ai_employee for additional data if needed.
|
|
234
235
|
*/
|
|
235
236
|
async getPersonaById(personaId) {
|
|
237
|
+
let persona = null;
|
|
236
238
|
// Primary: /api/personas/{id} - returns full persona with workflow_def
|
|
237
239
|
// Note: get_minimal_persona=false is REQUIRED to get workflow_def in response
|
|
238
240
|
try {
|
|
239
241
|
const resp = await this.requestWithRetries("GET", `/api/personas/${personaId}?get_minimal_persona=false`, {});
|
|
240
242
|
if (resp.ok) {
|
|
241
|
-
|
|
243
|
+
persona = (await resp.json());
|
|
242
244
|
}
|
|
243
245
|
}
|
|
244
246
|
catch {
|
|
245
247
|
// Fall through to fallback
|
|
246
248
|
}
|
|
247
249
|
// Fallback: /api/ai_employee/get_ai_employee endpoint
|
|
250
|
+
// Also used to supplement proto_config if missing from primary endpoint
|
|
248
251
|
try {
|
|
249
252
|
const resp = await this.requestWithRetries("GET", `/api/ai_employee/get_ai_employee?persona_id=${personaId}`, {});
|
|
250
253
|
if (resp.ok) {
|
|
251
254
|
const data = (await resp.json());
|
|
252
|
-
|
|
255
|
+
const fallbackPersona = data.persona ?? data.ai_employee ?? data.config ?? null;
|
|
256
|
+
if (!persona) {
|
|
257
|
+
// Primary failed, use fallback entirely
|
|
258
|
+
persona = fallbackPersona;
|
|
259
|
+
}
|
|
260
|
+
else if (fallbackPersona) {
|
|
261
|
+
// Merge proto_config from fallback if missing in primary
|
|
262
|
+
if (!persona.proto_config && fallbackPersona.proto_config) {
|
|
263
|
+
persona.proto_config = fallbackPersona.proto_config;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
253
266
|
}
|
|
254
267
|
}
|
|
255
268
|
catch {
|
|
256
|
-
//
|
|
269
|
+
// Fallback failed, continue with what we have
|
|
257
270
|
}
|
|
258
|
-
return
|
|
271
|
+
return persona;
|
|
259
272
|
}
|
|
260
273
|
/**
|
|
261
274
|
* Get workflow definition by workflow_id.
|
|
@@ -492,33 +505,164 @@ export class EmaClient {
|
|
|
492
505
|
clearTimeout(timeoutId);
|
|
493
506
|
}
|
|
494
507
|
}
|
|
508
|
+
/**
|
|
509
|
+
* Make a Connect-RPC style request with persona context.
|
|
510
|
+
* DataIngestService requires x-persona-id header.
|
|
511
|
+
*/
|
|
512
|
+
async connectRequest(path, body, personaId) {
|
|
513
|
+
const fullUrl = `${this.env.baseUrl.replace(/\/$/, "")}${path}`;
|
|
514
|
+
return fetch(fullUrl, {
|
|
515
|
+
method: "POST",
|
|
516
|
+
headers: {
|
|
517
|
+
"Authorization": `Bearer ${this.env.bearerToken}`,
|
|
518
|
+
"Content-Type": "application/json",
|
|
519
|
+
"x-persona-id": personaId,
|
|
520
|
+
"Connect-Protocol-Version": "1",
|
|
521
|
+
},
|
|
522
|
+
body: JSON.stringify(body),
|
|
523
|
+
});
|
|
524
|
+
}
|
|
495
525
|
/**
|
|
496
526
|
* List data source files for an AI Employee.
|
|
497
|
-
* Uses the DataIngestService gRPC endpoint.
|
|
527
|
+
* Uses the DataIngestService GetRootContentNodes gRPC endpoint.
|
|
528
|
+
*
|
|
529
|
+
* @param personaId - The persona ID
|
|
530
|
+
* @param opts - Optional parameters for pagination and filtering
|
|
498
531
|
*/
|
|
499
|
-
async listDataSourceFiles(personaId) {
|
|
500
|
-
|
|
532
|
+
async listDataSourceFiles(personaId, opts) {
|
|
533
|
+
const widgetName = opts?.widgetName ?? "fileUpload";
|
|
534
|
+
const page = opts?.page ?? 1;
|
|
535
|
+
const limit = opts?.limit ?? 50;
|
|
501
536
|
try {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
537
|
+
// Use Connect-RPC JSON encoding
|
|
538
|
+
const resp = await this.connectRequest("/dataingest.v1.DataIngestService/GetRootContentNodes", {
|
|
539
|
+
widgetName: widgetName,
|
|
540
|
+
includeFiles: true,
|
|
541
|
+
groupId: personaId,
|
|
542
|
+
groupType: "FILE_PICKER_GROUP_TYPE_PERSONA", // 2 in proto enum
|
|
543
|
+
page: page,
|
|
544
|
+
limit: limit,
|
|
545
|
+
}, personaId);
|
|
509
546
|
if (resp.ok) {
|
|
510
547
|
const data = await resp.json();
|
|
511
|
-
|
|
512
|
-
id: f.
|
|
513
|
-
filename: f.
|
|
514
|
-
status: f.status
|
|
548
|
+
const files = (data.data ?? []).map((f) => ({
|
|
549
|
+
id: f.contentNodeId ?? "",
|
|
550
|
+
filename: f.nodeName ?? "",
|
|
551
|
+
status: this.normalizeContentNodeStatus(f.status),
|
|
552
|
+
createdAt: f.createdAt,
|
|
553
|
+
updatedAt: f.updatedAt,
|
|
554
|
+
sourceType: f.sourceType,
|
|
515
555
|
}));
|
|
556
|
+
return {
|
|
557
|
+
files,
|
|
558
|
+
pagination: {
|
|
559
|
+
page: data.pagination?.page ?? page,
|
|
560
|
+
limit: data.pagination?.limit ?? limit,
|
|
561
|
+
total: data.pagination?.total ?? files.length,
|
|
562
|
+
totalPages: data.pagination?.totalPages ?? 1,
|
|
563
|
+
},
|
|
564
|
+
widgetName: data.meta?.widgetName ?? widgetName,
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
// If Connect-JSON fails, try to get files from persona's status_log
|
|
568
|
+
const persona = await this.getPersonaById(personaId);
|
|
569
|
+
if (persona) {
|
|
570
|
+
const statusLogFiles = this.extractFilesFromStatusLog(persona, widgetName);
|
|
571
|
+
if (statusLogFiles.length > 0) {
|
|
572
|
+
return {
|
|
573
|
+
files: statusLogFiles,
|
|
574
|
+
pagination: {
|
|
575
|
+
page: 1,
|
|
576
|
+
limit: statusLogFiles.length,
|
|
577
|
+
total: statusLogFiles.length,
|
|
578
|
+
totalPages: 1
|
|
579
|
+
},
|
|
580
|
+
widgetName,
|
|
581
|
+
source: "status_log",
|
|
582
|
+
};
|
|
583
|
+
}
|
|
516
584
|
}
|
|
517
585
|
}
|
|
518
586
|
catch {
|
|
519
587
|
// Fall through to return empty
|
|
520
588
|
}
|
|
521
|
-
return
|
|
589
|
+
return {
|
|
590
|
+
files: [],
|
|
591
|
+
pagination: { page: 1, limit: 0, total: 0, totalPages: 0 },
|
|
592
|
+
widgetName,
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Extract file list from persona's status_log.
|
|
597
|
+
* This is available when persona is fetched with get_minimal_persona=false.
|
|
598
|
+
*
|
|
599
|
+
* @param persona - The persona DTO with status_log
|
|
600
|
+
* @param widgetName - Widget name to extract files for (default: "fileUpload")
|
|
601
|
+
*/
|
|
602
|
+
extractFilesFromStatusLog(persona, widgetName = "fileUpload") {
|
|
603
|
+
const statusLog = persona.status_log;
|
|
604
|
+
if (!statusLog) {
|
|
605
|
+
return [];
|
|
606
|
+
}
|
|
607
|
+
const files = statusLog[widgetName];
|
|
608
|
+
if (!Array.isArray(files)) {
|
|
609
|
+
return [];
|
|
610
|
+
}
|
|
611
|
+
return files
|
|
612
|
+
.filter((f) => typeof f === "object" && f !== null)
|
|
613
|
+
.map((f) => ({
|
|
614
|
+
id: f.id ?? f.filename ?? "",
|
|
615
|
+
filename: f.filename ?? f.id ?? "",
|
|
616
|
+
status: f.status ?? "unknown",
|
|
617
|
+
tags: Array.isArray(f.tags) ? f.tags : undefined,
|
|
618
|
+
}))
|
|
619
|
+
.filter((f) => f.id !== "");
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Get data source aggregates (file counts by status).
|
|
623
|
+
*/
|
|
624
|
+
async getDataSourceAggregates(personaId, widgetName = "fileUpload") {
|
|
625
|
+
try {
|
|
626
|
+
const resp = await this.connectRequest("/dataingest.v1.DataIngestService/GetContentNodeAggregates", {
|
|
627
|
+
widgetName: widgetName,
|
|
628
|
+
includeFiles: true,
|
|
629
|
+
groupId: personaId,
|
|
630
|
+
groupType: "FILE_PICKER_GROUP_TYPE_PERSONA",
|
|
631
|
+
}, personaId);
|
|
632
|
+
if (resp.ok) {
|
|
633
|
+
const data = await resp.json();
|
|
634
|
+
const agg = data.widgetAggregates?.find(w => w.widgetName === widgetName);
|
|
635
|
+
const counts = agg?.totalCounts ?? {};
|
|
636
|
+
return {
|
|
637
|
+
total: (counts.pending ?? 0) + (counts.success ?? 0) + (counts.failed ?? 0),
|
|
638
|
+
pending: counts.pending ?? 0,
|
|
639
|
+
success: counts.success ?? 0,
|
|
640
|
+
failed: counts.failed ?? 0,
|
|
641
|
+
widgetName,
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
catch {
|
|
646
|
+
// Fall through
|
|
647
|
+
}
|
|
648
|
+
return { total: 0, pending: 0, success: 0, failed: 0, widgetName };
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Normalize content node status from proto enum to readable string.
|
|
652
|
+
*/
|
|
653
|
+
normalizeContentNodeStatus(status) {
|
|
654
|
+
if (!status)
|
|
655
|
+
return "unknown";
|
|
656
|
+
// Handle both enum name and human-readable formats
|
|
657
|
+
const statusMap = {
|
|
658
|
+
"CONTENT_NODE_STATUS_UNSPECIFIED": "unknown",
|
|
659
|
+
"CONTENT_NODE_STATUS_PENDING": "pending",
|
|
660
|
+
"CONTENT_NODE_STATUS_INDEXING": "indexing",
|
|
661
|
+
"CONTENT_NODE_STATUS_INDEXED": "indexed",
|
|
662
|
+
"CONTENT_NODE_STATUS_FAILED": "failed",
|
|
663
|
+
"CONTENT_NODE_STATUS_SUCCESS": "success",
|
|
664
|
+
};
|
|
665
|
+
return statusMap[status] ?? status.toLowerCase().replace("content_node_status_", "");
|
|
522
666
|
}
|
|
523
667
|
/**
|
|
524
668
|
* Detect MIME type from filename extension.
|
package/dist/sdk/contracts.js
CHANGED
|
@@ -153,6 +153,7 @@ export const CreatePersonaRequestSchema = z.object({
|
|
|
153
153
|
description: z.string().optional(),
|
|
154
154
|
template_id: z.string().optional(),
|
|
155
155
|
source_persona_id: z.string().optional(),
|
|
156
|
+
clone_data: z.boolean().optional(), // Clone knowledge base files when cloning from source_persona_id
|
|
156
157
|
proto_config: z.record(z.unknown()).optional(),
|
|
157
158
|
welcome_messages: WelcomeMessageSchema.optional(),
|
|
158
159
|
trigger_type: z.string().optional(),
|
package/docs/mcp-tools-guide.md
CHANGED
|
@@ -28,24 +28,40 @@ Some clients show a **prefixed name** (for example Cursor: `mcp_ema_workflow`).
|
|
|
28
28
|
|
|
29
29
|
## Start-here flows (LLM-friendly)
|
|
30
30
|
|
|
31
|
-
### Create a new AI Employee (
|
|
31
|
+
### Create a new AI Employee (greenfield)
|
|
32
32
|
|
|
33
33
|
1. **Requirements**: `template(questions=true)` (and `template(questions=true, category="Voice")` for voice)
|
|
34
34
|
2. **Pick agents/pattern**: `action(suggest="<use case>")`
|
|
35
35
|
3. **Get the pattern**: `template(pattern="<pattern>")`
|
|
36
|
-
4. **Generate
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
4. **Generate workflow** (preview by default):
|
|
37
|
+
```typescript
|
|
38
|
+
workflow(input="<requirements>", type="chat|voice|dashboard")
|
|
39
|
+
```
|
|
39
40
|
5. **Create persona** (if needed): `persona(mode="create", name="...", type="chat|voice|dashboard")`
|
|
40
|
-
6. **Deploy
|
|
41
|
+
6. **Deploy** (preview=false):
|
|
42
|
+
```typescript
|
|
43
|
+
workflow(input="<requirements>", persona_id="<id>", preview=false)
|
|
44
|
+
```
|
|
41
45
|
7. **Review**: `workflow(mode="analyze", persona_id="...")`
|
|
42
46
|
|
|
47
|
+
### Extend an existing AI Employee (brownfield)
|
|
48
|
+
|
|
49
|
+
**Combine multiple changes in one command!**
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// Preview changes (safe default)
|
|
53
|
+
workflow(mode="extend", persona_id="abc-123", input="add caller_type categorizer, add HITL before email")
|
|
54
|
+
|
|
55
|
+
// Deploy changes
|
|
56
|
+
workflow(mode="extend", persona_id="abc-123", input="...", preview=false)
|
|
57
|
+
```
|
|
58
|
+
|
|
43
59
|
### Review or debug an existing AI Employee
|
|
44
60
|
|
|
45
61
|
1. Fetch full workflow: `persona(id="<id-or-exact-name>", include_workflow=true)`
|
|
46
62
|
2. Analyze: `workflow(mode="analyze", persona_id="<persona_id>")`
|
|
47
|
-
3. Preview fixes: `workflow(mode="optimize", persona_id="<persona_id>"
|
|
48
|
-
4. Apply fixes: `workflow(mode="optimize", persona_id="<persona_id>")`
|
|
63
|
+
3. Preview fixes: `workflow(mode="optimize", persona_id="<persona_id>")`
|
|
64
|
+
4. Apply fixes: `workflow(mode="optimize", persona_id="<persona_id>", preview=false)`
|
|
49
65
|
|
|
50
66
|
### Upload / manage knowledge base documents
|
|
51
67
|
|
|
@@ -61,6 +77,28 @@ Some clients show a **prefixed name** (for example Cursor: `mcp_ema_workflow`).
|
|
|
61
77
|
- List all synced in env: `sync(mode="status", list_synced=true, env="dev")`
|
|
62
78
|
- Config: `sync(mode="config")`
|
|
63
79
|
|
|
80
|
+
### Version / snapshot AI Employees
|
|
81
|
+
|
|
82
|
+
Take snapshots before making changes, restore if something breaks.
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// Before a risky change - create a snapshot
|
|
86
|
+
persona(id="My Bot", mode="version_create", message="Before adding HITL")
|
|
87
|
+
|
|
88
|
+
// Make changes...
|
|
89
|
+
workflow(mode="extend", persona_id="abc", input="add HITL before email", preview=false)
|
|
90
|
+
|
|
91
|
+
// If something breaks - list versions and restore
|
|
92
|
+
persona(id="My Bot", mode="version_list")
|
|
93
|
+
persona(id="My Bot", mode="version_restore", version="v3")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Auto-snapshot on deploy:**
|
|
97
|
+
```typescript
|
|
98
|
+
persona(id="My Bot", mode="version_policy", auto_on_deploy=true)
|
|
99
|
+
// Now every deploy automatically creates a snapshot first
|
|
100
|
+
```
|
|
101
|
+
|
|
64
102
|
### Demo data → RAG-ready Markdown → upload
|
|
65
103
|
|
|
66
104
|
1. Get a template: `demo(mode="template", entity="customer")`
|
|
@@ -77,18 +115,55 @@ Some clients show a **prefixed name** (for example Cursor: `mcp_ema_workflow`).
|
|
|
77
115
|
- **Update**: `persona(id="<id>", mode="update", name="...", description="...", enabled=true|false)`
|
|
78
116
|
- **Compare**: `persona(id="<id>", mode="compare", compare_to="<id>", compare_env="dev")`
|
|
79
117
|
- **Templates**: `persona(templates=true)`
|
|
118
|
+
- **Version management**:
|
|
119
|
+
```typescript
|
|
120
|
+
persona(id="abc", mode="version_create", message="Before major update") // Create snapshot
|
|
121
|
+
persona(id="abc", mode="version_list") // List all versions
|
|
122
|
+
persona(id="abc", mode="version_get", version="v3") // Get specific version
|
|
123
|
+
persona(id="abc", mode="version_compare", v1="v2", v2="v3") // Compare versions
|
|
124
|
+
persona(id="abc", mode="version_restore", version="v2") // Restore to version
|
|
125
|
+
persona(id="abc", mode="version_policy", auto_on_deploy=true) // Auto-snapshot settings
|
|
126
|
+
```
|
|
80
127
|
|
|
81
128
|
### `workflow`
|
|
82
129
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
- **
|
|
130
|
+
All modification modes default to `preview=true` for safety. Use `preview=false` to deploy.
|
|
131
|
+
|
|
132
|
+
- **Generate (greenfield)**: Create new workflow
|
|
133
|
+
```typescript
|
|
134
|
+
workflow(input="IT helpdesk with KB search") // Preview
|
|
135
|
+
workflow(input="...", persona_id="abc", preview=false) // Generate AND deploy
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
- **Extend (brownfield)**: Modify existing workflow with natural language
|
|
139
|
+
```typescript
|
|
140
|
+
workflow(mode="extend", persona_id="abc", input="add caller_type categorizer")
|
|
141
|
+
workflow(mode="extend", persona_id="abc", input="add X, add Y, add Z") // Multiple changes!
|
|
142
|
+
workflow(mode="extend", persona_id="abc", input="...", preview=false) // Extend AND deploy
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
- **Optimize**: Fix issues in existing workflow
|
|
146
|
+
```typescript
|
|
147
|
+
workflow(mode="optimize", persona_id="abc") // Preview fixes
|
|
148
|
+
workflow(mode="optimize", persona_id="abc", preview=false) // Apply fixes
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
- **Analyze** (always read-only):
|
|
152
|
+
```typescript
|
|
153
|
+
workflow(mode="analyze", persona_id="abc")
|
|
154
|
+
workflow(mode="analyze", workflow_def={...})
|
|
155
|
+
workflow(mode="analyze", persona_id="abc", include=["issues", "fixes"])
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
- **Compare** (always read-only):
|
|
159
|
+
```typescript
|
|
160
|
+
workflow(mode="compare", persona_id="abc", compare_to="def")
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
- **Compile** (returns workflow_def from explicit node spec):
|
|
164
|
+
```typescript
|
|
165
|
+
workflow(mode="compile", name="...", description="...", nodes=[...])
|
|
166
|
+
```
|
|
92
167
|
|
|
93
168
|
### `action`
|
|
94
169
|
|
|
@@ -150,7 +225,49 @@ The `workflow(mode="optimize")` can automatically fix common issues:
|
|
|
150
225
|
| External API call | ❌ No HITL | Request explicitly: "require human approval" |
|
|
151
226
|
| Create/update records | ❌ No HITL | Request explicitly in prompt |
|
|
152
227
|
|
|
153
|
-
To add HITL after deployment: `workflow(
|
|
228
|
+
To add HITL after deployment: `workflow(persona_id="...", input="add human approval before email")`
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
## Brownfield Workflow Extension
|
|
232
|
+
|
|
233
|
+
Extend existing workflows with new capabilities while preserving existing logic.
|
|
234
|
+
|
|
235
|
+
### Usage
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
// Add new nodes/intents
|
|
239
|
+
workflow(persona_id="abc", input="add caller_type categorizer with Advisor and Client roles")
|
|
240
|
+
|
|
241
|
+
// Add enum options to existing categorizer
|
|
242
|
+
workflow(persona_id="abc", input="add Market Analysis intent to the categorizer")
|
|
243
|
+
|
|
244
|
+
// Add custom wiring
|
|
245
|
+
workflow(persona_id="abc", input="add send_email node connected to entity_extractor output")
|
|
246
|
+
|
|
247
|
+
// Merge a complete workflow_def (extend mode - preserves existing)
|
|
248
|
+
workflow(persona_id="abc", workflow_def={...}, merge_mode="extend")
|
|
249
|
+
|
|
250
|
+
// Replace with a new workflow (destructive)
|
|
251
|
+
workflow(persona_id="abc", workflow_def={...}, merge_mode="replace", force=true)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Merge Modes
|
|
255
|
+
|
|
256
|
+
| Mode | Behavior |
|
|
257
|
+
|------|----------|
|
|
258
|
+
| `extend` (default) | Add new nodes, preserve existing, merge enums |
|
|
259
|
+
| `replace` | Replace nodes not in incoming (destructive) |
|
|
260
|
+
|
|
261
|
+
### Capabilities
|
|
262
|
+
|
|
263
|
+
| Capability | Example |
|
|
264
|
+
|------------|---------|
|
|
265
|
+
| ✅ Add new nodes | `input="add password reset intent"` |
|
|
266
|
+
| ✅ Add enum options | `input="add Advisor/Client to caller_type"` |
|
|
267
|
+
| ✅ Add HITL gates | `input="add approval before sending emails"` |
|
|
268
|
+
| ✅ Modify node wiring | `input="wire entity_extraction to email handler"` |
|
|
269
|
+
| ✅ Preserve existing logic | Existing nodes/enums kept unless modified |
|
|
270
|
+
|
|
154
271
|
|
|
155
272
|
## Auto Builder Prompt Length Limits
|
|
156
273
|
|
package/package.json
CHANGED