@ema.co/mcp-toolkit 2026.2.27-1 → 2026.2.27-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.
Potentially problematic release.
This version of @ema.co/mcp-toolkit might be problematic. Click here for more details.
- package/dist/mcp/handlers/conversation/formatters.js +2 -2
- package/dist/mcp/handlers/conversation/send.js +1 -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/workflow/adapter.js +30 -46
- package/dist/mcp/handlers/workflow/validation.js +1 -1
- package/dist/mcp/tools.js +32 -5
- package/dist/sdk/client-adapter.js +9 -2
- package/dist/sdk/ema-client.js +53 -27
- package/dist/sdk/grpc-client.js +49 -4
- 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
|
@@ -33,8 +33,8 @@ export function formatSendResponse(result) {
|
|
|
33
33
|
response._next_step = `conversation(method="send", conversation_id="${result.conversationId}", buttons={selected_button:{label:"<button label>"}})`;
|
|
34
34
|
}
|
|
35
35
|
else if (msgType.includes("FORM")) {
|
|
36
|
-
response._tip = "The AI responded with a form.
|
|
37
|
-
response._next_step = `conversation(method="send", conversation_id="${result.conversationId}",
|
|
36
|
+
response._tip = "The AI responded with a form. Echo back the entire formMessage with values filled in using raw_message. The form param only works for cancellation.";
|
|
37
|
+
response._next_step = `conversation(method="send", conversation_id="${result.conversationId}", original_message_id="<bot_msg_id>", raw_message={type:"MESSAGE_TYPE_FORM", formMessage:{...formMessage from response with values filled...}, isUserMessage:true})`;
|
|
38
38
|
}
|
|
39
39
|
else {
|
|
40
40
|
response._tip = "Check message for the AI's reply. Use method='history' to review the full thread.";
|
|
@@ -77,7 +77,7 @@ export async function handleSend(args, client) {
|
|
|
77
77
|
examples: [
|
|
78
78
|
'text="hello" - send a text message',
|
|
79
79
|
'buttons={selected_button:{label:"Yes",description:"Confirm"}} - select a button',
|
|
80
|
-
'
|
|
80
|
+
'raw_message={type:"MESSAGE_TYPE_FORM", formMessage:{...}} - submit a form (echo back formMessage with values)',
|
|
81
81
|
'form={is_cancelled:true} - cancel a form',
|
|
82
82
|
],
|
|
83
83
|
};
|
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
* Deletes a persona with safety confirmation.
|
|
5
5
|
*/
|
|
6
6
|
import { resolvePersona, normalizeTriggerType } from "../utils.js";
|
|
7
|
-
import {
|
|
8
|
-
import { createVersionPolicyEngine } from "../../../sync/version-policy.js";
|
|
7
|
+
import { createCentralStorageEngine } from "../../../sync/central-factory.js";
|
|
9
8
|
/**
|
|
10
9
|
* Handle persona(mode="delete") - delete persona
|
|
11
10
|
*
|
|
@@ -65,47 +64,27 @@ export async function handleDelete(args, client) {
|
|
|
65
64
|
hint: "Use persona(method='delete', id='...', confirm=true) to proceed with deletion.",
|
|
66
65
|
};
|
|
67
66
|
}
|
|
68
|
-
//
|
|
67
|
+
// Best-effort: snapshot before any destructive change (via central DIS storage)
|
|
69
68
|
const force = args.force;
|
|
70
69
|
const targetEnv = args.env ?? "unknown";
|
|
71
70
|
if (fullPersona) {
|
|
72
71
|
try {
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
const snap = engine.forceCreateVersion(fullPersona, {
|
|
72
|
+
const { engine } = createCentralStorageEngine(client, fullPersona.id);
|
|
73
|
+
const snap = await engine.forceCreateVersion(fullPersona, {
|
|
76
74
|
environment: targetEnv,
|
|
77
75
|
tenant_id: targetEnv,
|
|
78
76
|
message: "Before delete",
|
|
79
77
|
created_by: "mcp-toolkit",
|
|
80
78
|
});
|
|
81
|
-
if (
|
|
82
|
-
if (!force) {
|
|
83
|
-
return {
|
|
84
|
-
error: "Failed to create pre-delete snapshot (required before delete)",
|
|
85
|
-
persona_id: persona.id,
|
|
86
|
-
details: snap.reason,
|
|
87
|
-
hint: "Fix snapshotting (workspace storage) and retry deletion. Use force=true to bypass.",
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
// force=true: proceed without snapshot (emergency only)
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
79
|
+
if (snap.created && snap.version) {
|
|
93
80
|
deletionSummary.version_snapshot = {
|
|
94
81
|
id: snap.version.id,
|
|
95
82
|
version_name: snap.version.version_name,
|
|
96
83
|
};
|
|
97
84
|
}
|
|
98
85
|
}
|
|
99
|
-
catch
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
error: "Failed to create pre-delete snapshot (required before delete)",
|
|
103
|
-
persona_id: persona.id,
|
|
104
|
-
details: e instanceof Error ? e.message : String(e),
|
|
105
|
-
hint: "Fix snapshotting (workspace storage) and retry deletion. Use force=true to bypass.",
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
// force=true: proceed without snapshot (emergency only)
|
|
86
|
+
catch {
|
|
87
|
+
// Best-effort: snapshot failure does not block the delete
|
|
109
88
|
}
|
|
110
89
|
}
|
|
111
90
|
// Perform deletion
|
|
@@ -16,8 +16,7 @@ import { PROJECT_TYPES } from "../../knowledge.js";
|
|
|
16
16
|
import { resolvePersona, validateWidgetsForApi } from "../utils.js";
|
|
17
17
|
import { validateSearchDataSourceConsistency, validationToHandlerResult } from "../workflow/validation.js";
|
|
18
18
|
import { checkRemovedParams } from "../deprecation.js";
|
|
19
|
-
import {
|
|
20
|
-
import { createVersionPolicyEngine } from "../../../sync/version-policy.js";
|
|
19
|
+
import { createCentralStorageEngine } from "../../../sync/central-factory.js";
|
|
21
20
|
import { fingerprintPersona } from "../../../sync.js";
|
|
22
21
|
/**
|
|
23
22
|
* Apply WorkflowSpec changes to an existing workflow_def.
|
|
@@ -234,41 +233,23 @@ export async function handleUpdate(args, client) {
|
|
|
234
233
|
hint: "Re-run persona(method='get') / workflow(mode='get') to fetch the latest state, re-apply your changes, then update again. Use force=true only if you intend to overwrite out-of-band changes.",
|
|
235
234
|
};
|
|
236
235
|
}
|
|
237
|
-
//
|
|
236
|
+
// Best-effort: snapshot before any update/change (via central DIS storage)
|
|
238
237
|
const targetEnv = args.env ?? "unknown";
|
|
239
238
|
let versionSnapshot;
|
|
240
239
|
try {
|
|
241
|
-
const
|
|
242
|
-
const
|
|
243
|
-
const snap = engine.forceCreateVersion(fullPersona, {
|
|
240
|
+
const { engine } = createCentralStorageEngine(client, fullPersona.id);
|
|
241
|
+
const snap = await engine.forceCreateVersion(fullPersona, {
|
|
244
242
|
environment: targetEnv,
|
|
245
243
|
tenant_id: targetEnv,
|
|
246
244
|
message: "Pre-update snapshot",
|
|
247
245
|
created_by: "mcp-toolkit",
|
|
248
246
|
});
|
|
249
|
-
if (
|
|
250
|
-
if (!force) {
|
|
251
|
-
return {
|
|
252
|
-
error: "Failed to create pre-update snapshot (required before update)",
|
|
253
|
-
persona_id: persona.id,
|
|
254
|
-
details: snap.reason,
|
|
255
|
-
hint: "Fix snapshotting (workspace storage) or retry with force=true for emergency override.",
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
else {
|
|
247
|
+
if (snap.created && snap.version) {
|
|
260
248
|
versionSnapshot = { id: snap.version.id, version_name: snap.version.version_name };
|
|
261
249
|
}
|
|
262
250
|
}
|
|
263
|
-
catch
|
|
264
|
-
|
|
265
|
-
return {
|
|
266
|
-
error: "Failed to create pre-update snapshot (required before update)",
|
|
267
|
-
persona_id: persona.id,
|
|
268
|
-
details: e instanceof Error ? e.message : String(e),
|
|
269
|
-
hint: "Retry after fixing local workspace write access, or use force=true for emergency override.",
|
|
270
|
-
};
|
|
271
|
-
}
|
|
251
|
+
catch {
|
|
252
|
+
// Best-effort: snapshot failure does not block the update
|
|
272
253
|
}
|
|
273
254
|
const existingProtoConfig = (fullPersona?.proto_config ?? persona.proto_config ?? {});
|
|
274
255
|
const existingWorkflow = fullPersona
|
|
@@ -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,
|
|
@@ -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",
|
|
@@ -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
|
}
|
package/dist/mcp/tools.js
CHANGED
|
@@ -53,7 +53,7 @@ export function generateTools(envNames, defaultEnv) {
|
|
|
53
53
|
**IMPORTANT (strict)**:
|
|
54
54
|
- Always fetch latest state immediately before changing: \`persona(method="get", id="abc", include_fingerprint=true)\`
|
|
55
55
|
- Pass \`base_fingerprint\` to update to prevent overwriting out-of-band changes
|
|
56
|
-
- The toolkit snapshots the persona
|
|
56
|
+
- The toolkit automatically snapshots the persona before applying updates
|
|
57
57
|
|
|
58
58
|
**For workflow changes**: Use the workflow tool:
|
|
59
59
|
1. \`workflow(mode="get", persona_id="abc")\` - get current workflow_def
|
|
@@ -830,8 +830,35 @@ When the bot responds with a continuation (form, buttons, or text prompt), respo
|
|
|
830
830
|
|
|
831
831
|
- **Text**: \`conversation(method="send", conversation_id="...", text="user reply")\`
|
|
832
832
|
- **Buttons**: \`conversation(method="send", conversation_id="...", buttons={selected_button:{label:"Yes", description:"Confirm"}})\`
|
|
833
|
-
- **Form submit**: \`conversation(method="send", conversation_id="...", form={fields:[{name:"email", value:{stringValue:"a@b.com"}}]})\`
|
|
834
833
|
- **Form cancel**: \`conversation(method="send", conversation_id="...", form={is_cancelled:true})\`
|
|
834
|
+
- **Form submit**: Use \`raw_message\` — the simplified \`form\` param does NOT work for form submissions because the backend expects the full form structure echoed back.
|
|
835
|
+
|
|
836
|
+
### Form HITL (CRITICAL — use raw_message)
|
|
837
|
+
Forms require echoing back the **entire formMessage** from the bot's response with your values filled in:
|
|
838
|
+
|
|
839
|
+
\`\`\`
|
|
840
|
+
conversation(method="send", conversation_id="...", original_message_id="<id from bot message>", raw_message={
|
|
841
|
+
type: "MESSAGE_TYPE_FORM",
|
|
842
|
+
formMessage: { ...entire formMessage from bot response with field values filled in... },
|
|
843
|
+
isUserMessage: true
|
|
844
|
+
})
|
|
845
|
+
\`\`\`
|
|
846
|
+
|
|
847
|
+
**Field value patterns by wellKnown type (from WellKnownValue proto):**
|
|
848
|
+
- **String** (\`WELL_KNOWN_TYPE_STRING\`): \`value: {wellKnown: {stringValue: "text"}}\`
|
|
849
|
+
- **Int** (\`WELL_KNOWN_TYPE_INT\`): \`value: {wellKnown: {int64Value: "42"}}\` (string-encoded integer)
|
|
850
|
+
- **Float** (\`WELL_KNOWN_TYPE_FLOAT\`): \`value: {wellKnown: {doubleValue: 3.14}}\`
|
|
851
|
+
- **Boolean** (\`WELL_KNOWN_TYPE_BOOL\`): \`value: {wellKnown: {boolValue: true}}\`
|
|
852
|
+
- **Date** (\`WELL_KNOWN_TYPE_DATE\`): \`value: {wellKnown: {dateValue: {year: 2026, month: 3, day: 2}}}\`
|
|
853
|
+
- **DateTime** (\`WELL_KNOWN_TYPE_DATETIME\`): \`value: {wellKnown: {datetimeValue: {utcTime: "2026-03-02T09:00:00Z"}}}\`
|
|
854
|
+
|
|
855
|
+
**Dropdown fields:** Check \`inputValidation.validValues\` to determine format:
|
|
856
|
+
- Struct options \`{wellKnown: {structValue: {Name:"Engineering", Id:"E"}}}\` → use the **Id** as stringValue: \`value: {wellKnown: {stringValue: "E"}}\`
|
|
857
|
+
- Enum options \`{enumValue: "Sick Leave"}\` → use the enum value as stringValue: \`value: {wellKnown: {stringValue: "Sick Leave"}}\`
|
|
858
|
+
|
|
859
|
+
**Intermediate forms**: If the response has \`isIntermediate: true\`, the bot will return another form with more fields after you submit. Previously filled fields will have \`editDisabled: true\`. The final form (no \`isIntermediate\`) submits all fields at once.
|
|
860
|
+
|
|
861
|
+
**Async responses**: Some workflows (e.g. agentic search) run long tasks after HITL exits. If \`send\` times out, poll with \`conversation(method="history")\` until the response appears with \`processing_status: "completed"\`.
|
|
835
862
|
|
|
836
863
|
## History
|
|
837
864
|
- \`conversation(method="history", conversation_id="...")\` - get all messages in the conversation
|
|
@@ -850,7 +877,7 @@ When the bot responds with a continuation (form, buttons, or text prompt), respo
|
|
|
850
877
|
- \`conversation(method="delete", conversation_id="...", confirm=true)\` - delete a conversation
|
|
851
878
|
|
|
852
879
|
## Raw Message
|
|
853
|
-
- \`conversation(method="send", conversation_id="...", raw_message={
|
|
880
|
+
- \`conversation(method="send", conversation_id="...", raw_message={...})\` - send a pre-built ChatbotMessage dict. **Required for form HITL submissions.**`,
|
|
854
881
|
inputSchema: {
|
|
855
882
|
type: "object",
|
|
856
883
|
properties: {
|
|
@@ -888,7 +915,7 @@ When the bot responds with a continuation (form, buttons, or text prompt), respo
|
|
|
888
915
|
},
|
|
889
916
|
form: {
|
|
890
917
|
type: "object",
|
|
891
|
-
description: "Form
|
|
918
|
+
description: "Form HITL cancellation only (for method=send). Set is_cancelled=true to cancel a form. For form SUBMISSIONS, use raw_message instead — the backend requires the full formMessage echoed back.",
|
|
892
919
|
properties: {
|
|
893
920
|
fields: {
|
|
894
921
|
type: "array",
|
|
@@ -914,7 +941,7 @@ When the bot responds with a continuation (form, buttons, or text prompt), respo
|
|
|
914
941
|
},
|
|
915
942
|
raw_message: {
|
|
916
943
|
type: "object",
|
|
917
|
-
description: "Pre-built ChatbotMessage dict (for method=send). Use
|
|
944
|
+
description: "Pre-built ChatbotMessage dict (for method=send). REQUIRED for form HITL submissions — echo back the full formMessage from the bot's response with values filled in. Use correct wellKnown value types: stringValue (strings), int64Value (ints, string-encoded), doubleValue (floats), boolValue (bools), dateValue {year,month,day} (dates), datetimeValue {utcTime} (datetimes). For struct dropdowns use the Id as stringValue; for enum dropdowns use the enumValue as stringValue.",
|
|
918
945
|
},
|
|
919
946
|
original_message_id: {
|
|
920
947
|
type: "string",
|
|
@@ -345,8 +345,15 @@ export class EmaClientAdapter {
|
|
|
345
345
|
/**
|
|
346
346
|
* Delete a data source file
|
|
347
347
|
*/
|
|
348
|
-
async deleteDataSource(personaId, fileId) {
|
|
349
|
-
return this.client.deleteDataSource(personaId, fileId);
|
|
348
|
+
async deleteDataSource(personaId, fileId, opts) {
|
|
349
|
+
return this.client.deleteDataSource(personaId, fileId, opts);
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Download a file's content by UploadedFile.Id.
|
|
353
|
+
* Uses GetSignedUrl gRPC → HTTP fetch.
|
|
354
|
+
*/
|
|
355
|
+
async downloadFile(fileId, personaId) {
|
|
356
|
+
return this.client.downloadFile(fileId, personaId);
|
|
350
357
|
}
|
|
351
358
|
/**
|
|
352
359
|
* List data source files
|
package/dist/sdk/ema-client.js
CHANGED
|
@@ -406,6 +406,50 @@ export class EmaClientV2 {
|
|
|
406
406
|
async getDataSourceAggregates(personaId, widgetName) {
|
|
407
407
|
return this.grpcClient.getContentNodeAggregates(personaId, widgetName);
|
|
408
408
|
}
|
|
409
|
+
/**
|
|
410
|
+
* Download a file's content by its UploadedFile.Id.
|
|
411
|
+
* Uses GetSignedUrl gRPC → HTTP fetch on the signed URL.
|
|
412
|
+
*
|
|
413
|
+
* @param fileId - The UploadedFile.Id (from ContentNodeResponse.id)
|
|
414
|
+
* @returns Raw file content as Buffer
|
|
415
|
+
* @throws If file is deleted or signed URL fetch fails
|
|
416
|
+
*/
|
|
417
|
+
async downloadFile(fileId, personaId) {
|
|
418
|
+
const signedUrlResponse = await this.grpcClient.getSignedUrl(fileId, personaId);
|
|
419
|
+
if (signedUrlResponse.isDeleted) {
|
|
420
|
+
throw new EmaApiError({
|
|
421
|
+
statusCode: 410,
|
|
422
|
+
body: `File ${fileId} has been deleted`,
|
|
423
|
+
message: `downloadFile: file is deleted (${this.env.name})`,
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
if (!signedUrlResponse.signedUrl) {
|
|
427
|
+
throw new EmaApiError({
|
|
428
|
+
statusCode: 404,
|
|
429
|
+
body: `No signed URL returned for file ${fileId}`,
|
|
430
|
+
message: `downloadFile: no signed URL (${this.env.name})`,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
const controller = new AbortController();
|
|
434
|
+
const timeoutId = setTimeout(() => controller.abort(), 30_000);
|
|
435
|
+
try {
|
|
436
|
+
const resp = await fetch(signedUrlResponse.signedUrl, {
|
|
437
|
+
signal: controller.signal,
|
|
438
|
+
});
|
|
439
|
+
if (!resp.ok) {
|
|
440
|
+
throw new EmaApiError({
|
|
441
|
+
statusCode: resp.status,
|
|
442
|
+
body: await resp.text(),
|
|
443
|
+
message: `downloadFile: fetch failed (${this.env.name})`,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
const arrayBuffer = await resp.arrayBuffer();
|
|
447
|
+
return Buffer.from(arrayBuffer);
|
|
448
|
+
}
|
|
449
|
+
finally {
|
|
450
|
+
clearTimeout(timeoutId);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
409
453
|
/**
|
|
410
454
|
* Replicate data between personas
|
|
411
455
|
*/
|
|
@@ -720,34 +764,16 @@ export class EmaClientV2 {
|
|
|
720
764
|
}
|
|
721
765
|
}
|
|
722
766
|
/**
|
|
723
|
-
* Delete
|
|
724
|
-
*
|
|
767
|
+
* Delete data source file(s) from an AI Employee's knowledge base.
|
|
768
|
+
* Uses gRPC DeleteFilesForGroup — the ONLY deletion method DIS exposes.
|
|
769
|
+
*
|
|
770
|
+
* @param personaId - The persona owning the files
|
|
771
|
+
* @param fileId - Single file ID to delete
|
|
772
|
+
* @param opts - Optional widget name scope
|
|
725
773
|
*/
|
|
726
|
-
async deleteDataSource(personaId, fileId) {
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
try {
|
|
730
|
-
const resp = await fetch(`${this.env.baseUrl.replace(/\/$/, "")}/api/v2/upload/files/${fileId}?persona_id=${personaId}`, {
|
|
731
|
-
method: "DELETE",
|
|
732
|
-
headers: {
|
|
733
|
-
Authorization: `Bearer ${this.env.bearerToken}`,
|
|
734
|
-
"X-Persona-Id": personaId,
|
|
735
|
-
},
|
|
736
|
-
signal: controller.signal,
|
|
737
|
-
});
|
|
738
|
-
if (!resp.ok && resp.status !== 404) {
|
|
739
|
-
const errorBody = await resp.text();
|
|
740
|
-
throw new EmaApiError({
|
|
741
|
-
statusCode: resp.status,
|
|
742
|
-
body: errorBody,
|
|
743
|
-
message: `deleteDataSource failed (${this.env.name})`,
|
|
744
|
-
});
|
|
745
|
-
}
|
|
746
|
-
return { success: true, fileId };
|
|
747
|
-
}
|
|
748
|
-
finally {
|
|
749
|
-
clearTimeout(timeoutId);
|
|
750
|
-
}
|
|
774
|
+
async deleteDataSource(personaId, fileId, opts) {
|
|
775
|
+
await this.grpcClient.deleteFilesForGroup(personaId, [fileId], opts?.widgetName);
|
|
776
|
+
return { success: true, fileId };
|
|
751
777
|
}
|
|
752
778
|
/**
|
|
753
779
|
* Delete a persona
|