@ema.co/mcp-toolkit 2026.2.27 → 2026.2.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @ema.co/mcp-toolkit might be problematic. Click here for more details.
- package/.context/public/guides/ema-user-guide.md +7 -6
- package/.context/public/guides/mcp-tools-guide.md +46 -23
- package/dist/config/index.js +11 -0
- package/dist/config/workflow-patterns.js +361 -0
- package/dist/mcp/autobuilder.js +2 -2
- package/dist/mcp/domain/generation-schema.js +15 -9
- package/dist/mcp/domain/structural-rules.js +3 -3
- package/dist/mcp/domain/validation-rules.js +20 -27
- package/dist/mcp/domain/workflow-generator.js +3 -3
- package/dist/mcp/domain/workflow-graph.js +1 -1
- package/dist/mcp/guidance.js +60 -1
- package/dist/mcp/handlers/conversation/adapter.js +13 -0
- package/dist/mcp/handlers/conversation/create.js +19 -0
- package/dist/mcp/handlers/conversation/delete.js +18 -0
- package/dist/mcp/handlers/conversation/formatters.js +62 -0
- package/dist/mcp/handlers/conversation/history.js +15 -0
- package/dist/mcp/handlers/conversation/index.js +43 -0
- package/dist/mcp/handlers/conversation/list.js +40 -0
- package/dist/mcp/handlers/conversation/messages.js +13 -0
- package/dist/mcp/handlers/conversation/rename.js +16 -0
- package/dist/mcp/handlers/conversation/send.js +90 -0
- package/dist/mcp/handlers/data/index.js +169 -3
- package/dist/mcp/handlers/feedback/client-id.js +49 -0
- package/dist/mcp/handlers/feedback/coalesce.js +167 -0
- package/dist/mcp/handlers/feedback/index.js +42 -1
- package/dist/mcp/handlers/feedback/outbox.js +301 -0
- package/dist/mcp/handlers/feedback/probes.js +127 -0
- package/dist/mcp/handlers/feedback/remote-store.js +59 -0
- package/dist/mcp/handlers/feedback/store.js +13 -1
- package/dist/mcp/handlers/persona/delete.js +7 -28
- package/dist/mcp/handlers/persona/update.js +7 -26
- package/dist/mcp/handlers/persona/version.js +30 -15
- package/dist/mcp/handlers/template/adapter.js +23 -0
- package/dist/mcp/handlers/template/crud.js +174 -0
- package/dist/mcp/handlers/template/index.js +6 -7
- package/dist/mcp/handlers/workflow/adapter.js +30 -46
- package/dist/mcp/handlers/workflow/index.js +2 -2
- package/dist/mcp/handlers/workflow/validation.js +2 -2
- package/dist/mcp/knowledge-guidance-topics.js +90 -53
- package/dist/mcp/knowledge.js +7 -357
- package/dist/mcp/prompts.js +5 -5
- package/dist/mcp/resources-dynamic.js +46 -38
- package/dist/mcp/resources-validation.js +5 -5
- package/dist/mcp/server.js +38 -5
- package/dist/mcp/tools.js +340 -8
- package/dist/sdk/client-adapter.js +90 -2
- package/dist/sdk/client.js +7 -0
- package/dist/sdk/ema-client.js +242 -27
- package/dist/sdk/generated/agent-catalog.js +96 -39
- package/dist/sdk/generated/deprecated-actions.js +1 -1
- package/dist/sdk/grpc-client.js +67 -5
- package/dist/sync/central-factory.js +86 -0
- package/dist/sync/central-version-storage.js +387 -0
- package/dist/sync/dis-port.js +75 -0
- package/dist/sync/version-policy.js +29 -31
- package/dist/sync/version-storage-interface.js +11 -0
- package/dist/sync/version-storage.js +22 -22
- package/package.json +2 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Version Management Handlers
|
|
3
3
|
*
|
|
4
|
-
* Provides
|
|
5
|
-
*
|
|
4
|
+
* Provides version tracking for personas (snapshot, history, restore).
|
|
5
|
+
* Uses IVersionStorage — can be local (.ema-versions/) or central (DIS).
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { createCentralStorageEngine, migrateLocalToCentral } from "../../../sync/central-factory.js";
|
|
8
|
+
import { VersionStorage } from "../../../sync/version-storage.js";
|
|
9
9
|
/**
|
|
10
10
|
* Check if a mode is a version management mode.
|
|
11
11
|
*/
|
|
@@ -32,20 +32,27 @@ export function isVersionMode(mode) {
|
|
|
32
32
|
* @param versionContext - Workspace and environment context
|
|
33
33
|
*/
|
|
34
34
|
export async function handleVersion(mode, args, client, persona, versionContext) {
|
|
35
|
-
const storage =
|
|
36
|
-
const engine = createVersionPolicyEngine(storage);
|
|
35
|
+
const { storage, engine } = createCentralStorageEngine(client, persona.id);
|
|
37
36
|
switch (mode) {
|
|
38
37
|
// ─────────────────────────────────────────────────────────────────────────
|
|
39
38
|
// snapshot / version_create - Create a new version snapshot
|
|
40
39
|
// ─────────────────────────────────────────────────────────────────────────
|
|
41
40
|
case "snapshot":
|
|
42
41
|
case "version_create": {
|
|
42
|
+
// Lazy migration: seed central from local on first snapshot
|
|
43
|
+
try {
|
|
44
|
+
const localStorage = new VersionStorage(versionContext.workspaceRoot);
|
|
45
|
+
await migrateLocalToCentral(localStorage, storage, persona.id);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Best-effort: migration failure should not block snapshot creation
|
|
49
|
+
}
|
|
43
50
|
// Fetch full persona with workflow
|
|
44
51
|
const fullPersona = await client.getPersonaById(persona.id);
|
|
45
52
|
if (!fullPersona) {
|
|
46
53
|
return { error: `Could not fetch full persona: ${persona.id}` };
|
|
47
54
|
}
|
|
48
|
-
const result = engine.forceCreateVersion(fullPersona, {
|
|
55
|
+
const result = await engine.forceCreateVersion(fullPersona, {
|
|
49
56
|
environment: versionContext.environment,
|
|
50
57
|
tenant_id: versionContext.tenant_id,
|
|
51
58
|
message: args.message,
|
|
@@ -73,7 +80,15 @@ export async function handleVersion(mode, args, client, persona, versionContext)
|
|
|
73
80
|
// ─────────────────────────────────────────────────────────────────────────
|
|
74
81
|
case "history":
|
|
75
82
|
case "version_list": {
|
|
76
|
-
|
|
83
|
+
// Lazy migration: seed central from local so history shows existing versions
|
|
84
|
+
try {
|
|
85
|
+
const localStorage = new VersionStorage(versionContext.workspaceRoot);
|
|
86
|
+
await migrateLocalToCentral(localStorage, storage, persona.id);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Best-effort
|
|
90
|
+
}
|
|
91
|
+
const versions = await engine.listVersions(persona.id, {
|
|
77
92
|
limit: args.limit,
|
|
78
93
|
});
|
|
79
94
|
return {
|
|
@@ -88,7 +103,7 @@ export async function handleVersion(mode, args, client, persona, versionContext)
|
|
|
88
103
|
// ─────────────────────────────────────────────────────────────────────────
|
|
89
104
|
case "version_get": {
|
|
90
105
|
const versionId = args.version ?? "latest";
|
|
91
|
-
const version = engine.getVersion(persona.id, versionId);
|
|
106
|
+
const version = await engine.getVersion(persona.id, versionId);
|
|
92
107
|
if (!version) {
|
|
93
108
|
return { error: `Version not found: ${versionId}` };
|
|
94
109
|
}
|
|
@@ -126,7 +141,7 @@ export async function handleVersion(mode, args, client, persona, versionContext)
|
|
|
126
141
|
if (!v1 || !v2) {
|
|
127
142
|
return { error: "v1 and v2 required for version_compare mode" };
|
|
128
143
|
}
|
|
129
|
-
const result = engine.compareVersions(persona.id, v1, v2);
|
|
144
|
+
const result = await engine.compareVersions(persona.id, v1, v2);
|
|
130
145
|
if (!result.success) {
|
|
131
146
|
return { error: result.error };
|
|
132
147
|
}
|
|
@@ -152,14 +167,14 @@ export async function handleVersion(mode, args, client, persona, versionContext)
|
|
|
152
167
|
if (!versionId) {
|
|
153
168
|
return { error: "version required for restore mode" };
|
|
154
169
|
}
|
|
155
|
-
const restoreData = engine.getRestoreData(persona.id, versionId);
|
|
170
|
+
const restoreData = await engine.getRestoreData(persona.id, versionId);
|
|
156
171
|
if (!restoreData.success || !restoreData.restore_payload) {
|
|
157
172
|
return { error: restoreData.error ?? "Failed to get restore data" };
|
|
158
173
|
}
|
|
159
174
|
// Create a version snapshot before restoring (audit trail)
|
|
160
175
|
const fullPersona = await client.getPersonaById(persona.id);
|
|
161
176
|
if (fullPersona) {
|
|
162
|
-
engine.forceCreateVersion(fullPersona, {
|
|
177
|
+
await engine.forceCreateVersion(fullPersona, {
|
|
163
178
|
environment: versionContext.environment,
|
|
164
179
|
tenant_id: versionContext.tenant_id,
|
|
165
180
|
message: `Before restore to ${restoreData.version?.version_name}`,
|
|
@@ -180,7 +195,7 @@ export async function handleVersion(mode, args, client, persona, versionContext)
|
|
|
180
195
|
// Create post-restore version
|
|
181
196
|
const restoredPersona = await client.getPersonaById(persona.id);
|
|
182
197
|
if (restoredPersona) {
|
|
183
|
-
engine.forceCreateVersion(restoredPersona, {
|
|
198
|
+
await engine.forceCreateVersion(restoredPersona, {
|
|
184
199
|
environment: versionContext.environment,
|
|
185
200
|
tenant_id: versionContext.tenant_id,
|
|
186
201
|
message: `Restored to ${restoreData.version?.version_name}`,
|
|
@@ -207,7 +222,7 @@ export async function handleVersion(mode, args, client, persona, versionContext)
|
|
|
207
222
|
args.auto_on_sync !== undefined ||
|
|
208
223
|
args.max_versions !== undefined;
|
|
209
224
|
if (hasUpdates) {
|
|
210
|
-
const updated = engine.updatePolicy(persona.id, {
|
|
225
|
+
const updated = await engine.updatePolicy(persona.id, {
|
|
211
226
|
auto_version_on_deploy: args.auto_on_deploy,
|
|
212
227
|
auto_version_on_sync: args.auto_on_sync,
|
|
213
228
|
max_versions: args.max_versions,
|
|
@@ -221,7 +236,7 @@ export async function handleVersion(mode, args, client, persona, versionContext)
|
|
|
221
236
|
};
|
|
222
237
|
}
|
|
223
238
|
// Just get current policy
|
|
224
|
-
const policy = engine.getPolicy(persona.id);
|
|
239
|
+
const policy = await engine.getPolicy(persona.id);
|
|
225
240
|
return {
|
|
226
241
|
persona_id: persona.id,
|
|
227
242
|
persona_name: persona.name,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Adapter
|
|
3
|
+
*
|
|
4
|
+
* Routes CRUD methods (list/get/create/update/delete) to the new crud handler.
|
|
5
|
+
* Non-CRUD calls (patterns, widgets, config, questions) fall through to the
|
|
6
|
+
* existing handleTemplate handler for backwards compatibility.
|
|
7
|
+
*
|
|
8
|
+
* Same pattern as debug/adapter.ts.
|
|
9
|
+
*/
|
|
10
|
+
import { handleTemplateCrud } from "./crud.js";
|
|
11
|
+
import { handleTemplate } from "./index.js";
|
|
12
|
+
const CRUD_METHODS = new Set(["list", "get", "create", "update", "delete"]);
|
|
13
|
+
export async function handleTemplateAdapter(args, createClient, getDefaultEnvName) {
|
|
14
|
+
const method = args.method;
|
|
15
|
+
// Route CRUD methods to the new handler
|
|
16
|
+
if (method && CRUD_METHODS.has(method)) {
|
|
17
|
+
const targetEnv = args.env ?? getDefaultEnvName();
|
|
18
|
+
const client = createClient(targetEnv);
|
|
19
|
+
return handleTemplateCrud(args, client);
|
|
20
|
+
}
|
|
21
|
+
// Legacy calls (patterns, widgets, config, questions) go to existing handler
|
|
22
|
+
return handleTemplate(args);
|
|
23
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template CRUD Handler — Method Dispatch
|
|
3
|
+
*
|
|
4
|
+
* Routes template CRUD methods to the appropriate SDK calls.
|
|
5
|
+
* Follows the handler pattern from debug/index.ts.
|
|
6
|
+
*/
|
|
7
|
+
const METHODS = ["list", "get", "create", "update", "delete"];
|
|
8
|
+
/**
|
|
9
|
+
* Map friendly type names to PersonaTriggerTypeEnum values.
|
|
10
|
+
* The backend DB rejects "UNSPECIFIED" (proto default 0), so a valid
|
|
11
|
+
* trigger_type must always be provided on create.
|
|
12
|
+
*/
|
|
13
|
+
const TRIGGER_TYPE_MAP = {
|
|
14
|
+
chat: 1,
|
|
15
|
+
chatbot: 1,
|
|
16
|
+
dashboard: 2,
|
|
17
|
+
extraction: 2,
|
|
18
|
+
agent_assist: 3,
|
|
19
|
+
conversational: 4,
|
|
20
|
+
document: 5,
|
|
21
|
+
email: 6,
|
|
22
|
+
agent_qa: 7,
|
|
23
|
+
voice: 1, // voice personas use CHATBOT trigger type
|
|
24
|
+
};
|
|
25
|
+
function resolveTriggerType(type) {
|
|
26
|
+
if (!type)
|
|
27
|
+
return 1; // default to CHATBOT
|
|
28
|
+
const asNumber = Number(type);
|
|
29
|
+
if (!isNaN(asNumber) && asNumber >= 1 && asNumber <= 7)
|
|
30
|
+
return asNumber;
|
|
31
|
+
return TRIGGER_TYPE_MAP[type.toLowerCase()] ?? 1;
|
|
32
|
+
}
|
|
33
|
+
export async function handleTemplateCrud(args, client) {
|
|
34
|
+
const method = args.method;
|
|
35
|
+
if (!method) {
|
|
36
|
+
return {
|
|
37
|
+
error: "Missing required parameter: method",
|
|
38
|
+
available_methods: [...METHODS],
|
|
39
|
+
_tip: "Start with template(method='list') to browse available templates.",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
switch (method) {
|
|
43
|
+
case "list":
|
|
44
|
+
return handleList(args, client);
|
|
45
|
+
case "get":
|
|
46
|
+
return handleGet(args, client);
|
|
47
|
+
case "create":
|
|
48
|
+
return handleCreate(args, client);
|
|
49
|
+
case "update":
|
|
50
|
+
return handleUpdate(args, client);
|
|
51
|
+
case "delete":
|
|
52
|
+
return handleDelete(args, client);
|
|
53
|
+
default:
|
|
54
|
+
return {
|
|
55
|
+
error: `Unknown method: ${method}`,
|
|
56
|
+
available_methods: [...METHODS],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
61
|
+
// Method handlers
|
|
62
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
63
|
+
async function handleList(args, client) {
|
|
64
|
+
const templates = await client.getPersonaTemplates();
|
|
65
|
+
// Apply optional filters
|
|
66
|
+
let filtered = templates;
|
|
67
|
+
const query = args.query;
|
|
68
|
+
if (query) {
|
|
69
|
+
const q = query.toLowerCase();
|
|
70
|
+
filtered = filtered.filter((t) => t.name?.toLowerCase().includes(q) ||
|
|
71
|
+
t.description?.toLowerCase().includes(q));
|
|
72
|
+
}
|
|
73
|
+
const type = args.type;
|
|
74
|
+
if (type) {
|
|
75
|
+
// Map friendly type names to trigger_type values
|
|
76
|
+
const typeMap = {
|
|
77
|
+
voice: "VOICEBOT_AI_EMPLOYEE",
|
|
78
|
+
chat: "CHATBOT",
|
|
79
|
+
dashboard: "DOCUMENT_TRIGGER",
|
|
80
|
+
};
|
|
81
|
+
const triggerType = typeMap[type.toLowerCase()] ?? type;
|
|
82
|
+
filtered = filtered.filter((t) => t.trigger_type === triggerType);
|
|
83
|
+
}
|
|
84
|
+
const category = args.category;
|
|
85
|
+
if (category) {
|
|
86
|
+
const c = category.toLowerCase();
|
|
87
|
+
filtered = filtered.filter((t) => t.category?.toLowerCase() === c);
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
count: filtered.length,
|
|
91
|
+
templates: filtered.map((t) => ({
|
|
92
|
+
id: t.id,
|
|
93
|
+
name: t.name,
|
|
94
|
+
description: t.description,
|
|
95
|
+
category: t.category,
|
|
96
|
+
trigger_type: t.trigger_type,
|
|
97
|
+
has_project_template: t.has_project_template,
|
|
98
|
+
})),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
async function handleGet(args, client) {
|
|
102
|
+
const id = args.id;
|
|
103
|
+
if (!id) {
|
|
104
|
+
return { error: "Missing required parameter: id" };
|
|
105
|
+
}
|
|
106
|
+
const template = await client.getPersonaTemplateById(id);
|
|
107
|
+
if (!template) {
|
|
108
|
+
return { error: `Template not found: ${id}` };
|
|
109
|
+
}
|
|
110
|
+
return template;
|
|
111
|
+
}
|
|
112
|
+
async function handleCreate(args, client) {
|
|
113
|
+
const name = args.name;
|
|
114
|
+
if (!name) {
|
|
115
|
+
return { error: "Missing required parameter: name" };
|
|
116
|
+
}
|
|
117
|
+
// Resolve trigger_type: the backend DB rejects proto default "UNSPECIFIED",
|
|
118
|
+
// so we must always provide a valid numeric trigger_type.
|
|
119
|
+
const triggerType = resolveTriggerType(args.type);
|
|
120
|
+
const result = await client.createPersonaTemplate({
|
|
121
|
+
name,
|
|
122
|
+
description: args.description,
|
|
123
|
+
proto_config: args.proto_config,
|
|
124
|
+
trigger_type: String(triggerType),
|
|
125
|
+
category: args.category,
|
|
126
|
+
about_template: args.about_template,
|
|
127
|
+
workflow_def: args.workflow_def,
|
|
128
|
+
source_template_id: args.from,
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
success: true,
|
|
132
|
+
template_id: result.template_id,
|
|
133
|
+
name,
|
|
134
|
+
_tip: `Template created. Use template(method='get', id='${result.template_id}') to view it.`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
async function handleUpdate(args, client) {
|
|
138
|
+
const id = args.id;
|
|
139
|
+
if (!id) {
|
|
140
|
+
return { error: "Missing required parameter: id" };
|
|
141
|
+
}
|
|
142
|
+
const result = await client.updatePersonaTemplate({
|
|
143
|
+
template_id: id,
|
|
144
|
+
name: args.name,
|
|
145
|
+
description: args.description,
|
|
146
|
+
proto_config: args.proto_config,
|
|
147
|
+
category: args.category,
|
|
148
|
+
about_template: args.about_template,
|
|
149
|
+
workflow_def: args.workflow_def,
|
|
150
|
+
});
|
|
151
|
+
return {
|
|
152
|
+
success: true,
|
|
153
|
+
template_id: id,
|
|
154
|
+
...result,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
async function handleDelete(args, client) {
|
|
158
|
+
const id = args.id;
|
|
159
|
+
if (!id) {
|
|
160
|
+
return { error: "Missing required parameter: id" };
|
|
161
|
+
}
|
|
162
|
+
const confirm = args.confirm;
|
|
163
|
+
if (confirm !== true) {
|
|
164
|
+
return {
|
|
165
|
+
error: "Deletion requires confirm=true",
|
|
166
|
+
_tip: `To delete template ${id}, call template(method='delete', id='${id}', confirm=true)`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
await client.deletePersonaTemplate(id);
|
|
170
|
+
return {
|
|
171
|
+
success: true,
|
|
172
|
+
deleted_template_id: id,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
@@ -15,8 +15,7 @@ import { getTemplateFallback, getTemplateFieldDocs } from "../../../sdk/generate
|
|
|
15
15
|
export function handleTemplate(args) {
|
|
16
16
|
// Get specific pattern
|
|
17
17
|
if (args.pattern) {
|
|
18
|
-
const
|
|
19
|
-
const pattern = WORKFLOW_PATTERNS[patternName];
|
|
18
|
+
const pattern = WORKFLOW_PATTERNS.find(p => p.name === args.pattern);
|
|
20
19
|
if (!pattern) {
|
|
21
20
|
return { error: `Pattern not found: ${args.pattern}` };
|
|
22
21
|
}
|
|
@@ -24,14 +23,14 @@ export function handleTemplate(args) {
|
|
|
24
23
|
}
|
|
25
24
|
// List patterns
|
|
26
25
|
if (args.patterns) {
|
|
27
|
-
let
|
|
26
|
+
let filtered = WORKFLOW_PATTERNS;
|
|
28
27
|
if (args.type) {
|
|
29
|
-
|
|
28
|
+
filtered = filtered.filter(p => !p.personaType || p.personaType === args.type);
|
|
30
29
|
}
|
|
31
30
|
return {
|
|
32
|
-
count:
|
|
33
|
-
patterns:
|
|
34
|
-
name,
|
|
31
|
+
count: filtered.length,
|
|
32
|
+
patterns: filtered.map(p => ({
|
|
33
|
+
name: p.name,
|
|
35
34
|
description: p.description,
|
|
36
35
|
use_case: p.useCase,
|
|
37
36
|
persona_type: p.personaType,
|
|
@@ -7,8 +7,7 @@
|
|
|
7
7
|
* Extracted from server.ts to keep the dispatch table thin.
|
|
8
8
|
*/
|
|
9
9
|
import { fingerprintPersona } from "../../../sync.js";
|
|
10
|
-
import {
|
|
11
|
-
import { createVersionPolicyEngine } from "../../../sync/version-policy.js";
|
|
10
|
+
import { createCentralStorageEngine } from "../../../sync/central-factory.js";
|
|
12
11
|
import { handleWorkflow } from "./index.js";
|
|
13
12
|
import { handleWorkflowOptimize } from "./optimize.js";
|
|
14
13
|
export async function handleWorkflowAdapter(args, createClient, getDefaultEnvName) {
|
|
@@ -84,60 +83,45 @@ export async function handleWorkflowAdapter(args, createClient, getDefaultEnvNam
|
|
|
84
83
|
return { error: 'persona_id is required for workflow(mode="deploy")' };
|
|
85
84
|
}
|
|
86
85
|
const targetEnv = normalizedArgs.env ?? getDefaultEnvName();
|
|
86
|
+
// Fetch persona and validate fingerprint (blocking — these are safety checks)
|
|
87
|
+
const personaBefore = await client.getPersonaById(personaId);
|
|
88
|
+
if (!personaBefore) {
|
|
89
|
+
return { error: `Persona not found: ${personaId}` };
|
|
90
|
+
}
|
|
91
|
+
const currentFp = fingerprintPersona(personaBefore);
|
|
92
|
+
if (!force && !baseFingerprint) {
|
|
93
|
+
return {
|
|
94
|
+
error: "base_fingerprint is required for workflow deploy (stale-state protection)",
|
|
95
|
+
persona_id: personaId,
|
|
96
|
+
current_fingerprint: currentFp,
|
|
97
|
+
hint: "Run workflow(mode='get', persona_id='...') immediately before deploying and pass fingerprint as base_fingerprint. Use force=true only for emergency overrides.",
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (!force && baseFingerprint && baseFingerprint !== currentFp) {
|
|
101
|
+
return {
|
|
102
|
+
error: "Persona changed since you last fetched it (fingerprint mismatch)",
|
|
103
|
+
persona_id: personaId,
|
|
104
|
+
base_fingerprint: baseFingerprint,
|
|
105
|
+
current_fingerprint: currentFp,
|
|
106
|
+
hint: "Re-run workflow(mode='get') to fetch the latest workflow_def, re-apply your edits, then deploy again. Use force=true only if you intend to overwrite out-of-band changes.",
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// Best-effort: snapshot before any destructive change (via central DIS storage)
|
|
87
110
|
let versionCreated;
|
|
88
111
|
try {
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
return { error: `Persona not found: ${personaId}` };
|
|
92
|
-
}
|
|
93
|
-
const currentFp = fingerprintPersona(personaBefore);
|
|
94
|
-
if (!force && !baseFingerprint) {
|
|
95
|
-
return {
|
|
96
|
-
error: "base_fingerprint is required for workflow deploy (stale-state protection)",
|
|
97
|
-
persona_id: personaId,
|
|
98
|
-
current_fingerprint: currentFp,
|
|
99
|
-
hint: "Run workflow(mode='get', persona_id='...') immediately before deploying and pass fingerprint as base_fingerprint. Use force=true only for emergency overrides.",
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
if (!force && baseFingerprint && baseFingerprint !== currentFp) {
|
|
103
|
-
return {
|
|
104
|
-
error: "Persona changed since you last fetched it (fingerprint mismatch)",
|
|
105
|
-
persona_id: personaId,
|
|
106
|
-
base_fingerprint: baseFingerprint,
|
|
107
|
-
current_fingerprint: currentFp,
|
|
108
|
-
hint: "Re-run workflow(mode='get') to fetch the latest workflow_def, re-apply your edits, then deploy again. Use force=true only if you intend to overwrite out-of-band changes.",
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
const storage = createVersionStorage(process.cwd());
|
|
112
|
-
const engine = createVersionPolicyEngine(storage);
|
|
113
|
-
const snap = engine.forceCreateVersion(personaBefore, {
|
|
112
|
+
const { engine } = createCentralStorageEngine(client, personaId);
|
|
113
|
+
const snap = await engine.forceCreateVersion(personaBefore, {
|
|
114
114
|
environment: targetEnv,
|
|
115
115
|
tenant_id: targetEnv,
|
|
116
116
|
message: "Pre-deploy snapshot",
|
|
117
117
|
created_by: "mcp-toolkit",
|
|
118
118
|
});
|
|
119
|
-
if (
|
|
120
|
-
if (!force) {
|
|
121
|
-
return {
|
|
122
|
-
error: "Failed to create pre-deploy snapshot (required before deploy)",
|
|
123
|
-
persona_id: personaId,
|
|
124
|
-
details: snap.reason,
|
|
125
|
-
hint: "Fix snapshotting (workspace storage) or retry with force=true for emergency override.",
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
119
|
+
if (snap.created && snap.version) {
|
|
130
120
|
versionCreated = { id: snap.version.id, version_name: snap.version.version_name };
|
|
131
121
|
}
|
|
132
122
|
}
|
|
133
|
-
catch
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
error: "Failed to create pre-deploy snapshot (required before deploy)",
|
|
137
|
-
persona_id: personaId,
|
|
138
|
-
hint: "Retry after fixing local workspace write access, or use force=true for emergency override.",
|
|
139
|
-
};
|
|
140
|
-
}
|
|
123
|
+
catch {
|
|
124
|
+
// Best-effort: snapshot failure does not block the deploy
|
|
141
125
|
}
|
|
142
126
|
const deployResult = await handleWorkflow({
|
|
143
127
|
mode: "deploy",
|
|
@@ -43,7 +43,7 @@ export const DEPRECATED_ACTIONS_FALLBACK = {
|
|
|
43
43
|
"custom_agent/v0": { replacement: "custom_agent/v1" },
|
|
44
44
|
"json_mapper/v0": { replacement: "json_mapper/v1" },
|
|
45
45
|
"text_categorizer/v0": { replacement: "text_categorizer/v1" },
|
|
46
|
-
"respond_with_sources/v0": { replacement: "
|
|
46
|
+
"respond_with_sources/v0": { replacement: "call_llm with named_inputs", notes: "For KB Q&A use call_llm; respond_for_external_actions is ONLY for external_action_caller results" },
|
|
47
47
|
};
|
|
48
48
|
/**
|
|
49
49
|
* Get deprecated actions from API (with fallback)
|
|
@@ -183,7 +183,7 @@ async function handleWorkflowGet(args, client) {
|
|
|
183
183
|
flow_pattern: "trigger → categorizer (optional) → processing → response → WORKFLOW_OUTPUT",
|
|
184
184
|
categorizer: "For multi-intent workflows, use chat_categorizer early with a Fallback category",
|
|
185
185
|
fallback: "Every categorizer MUST have a Fallback category for unmatched intents",
|
|
186
|
-
hitl: "
|
|
186
|
+
hitl: "Two HITL mechanisms: 1) general_hitl (Human Collaboration Agent) for rich dialogue/forms, 2) disable_human_interaction: false flag on send_email_agent or entity_extraction_with_documents for simple approval",
|
|
187
187
|
search: "Use search/v2 with datastore_configs. Wire query from trigger output.",
|
|
188
188
|
},
|
|
189
189
|
// LLM-driven analysis pattern: MCP provides data, LLM does the thinking
|
|
@@ -167,7 +167,7 @@ const KNOWLEDGE_SEARCH_TYPES = [
|
|
|
167
167
|
"search_datastore",
|
|
168
168
|
"knowledge_search",
|
|
169
169
|
"semantic_search",
|
|
170
|
-
"
|
|
170
|
+
"combine_and_rerank_search_results",
|
|
171
171
|
];
|
|
172
172
|
/**
|
|
173
173
|
* Known web search action types (excluded from knowledge search detection).
|
|
@@ -222,7 +222,7 @@ export function getSearchNodeWidgetNames(workflowDef) {
|
|
|
222
222
|
const elements = dc?.multiBinding?.elements ?? [];
|
|
223
223
|
for (const el of elements) {
|
|
224
224
|
const name = el.widgetConfig?.widgetName;
|
|
225
|
-
if (typeof name === "string" && name.trim().length > 0) {
|
|
225
|
+
if (typeof name === "string" && name.trim().length > 0 && !name.startsWith("_")) {
|
|
226
226
|
widgetNames.add(name.trim());
|
|
227
227
|
}
|
|
228
228
|
}
|