@ema.co/mcp-toolkit 2026.2.24 → 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/guidance.js +47 -0
- 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/persona/delete.js +7 -28
- package/dist/mcp/handlers/persona/update.js +41 -34
- 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/workflow/adapter.js +30 -46
- package/dist/mcp/handlers/workflow/deploy.js +3 -0
- package/dist/mcp/handlers/workflow/validation.js +21 -5
- package/dist/mcp/server.js +15 -5
- package/dist/mcp/tools.js +339 -5
- package/dist/sdk/client-adapter.js +92 -2
- package/dist/sdk/client.js +7 -0
- package/dist/sdk/ema-client.js +244 -27
- 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
package/dist/mcp/guidance.js
CHANGED
|
@@ -366,6 +366,44 @@ export const TOOL_GUIDANCE = {
|
|
|
366
366
|
],
|
|
367
367
|
applicableRules: [],
|
|
368
368
|
},
|
|
369
|
+
template: {
|
|
370
|
+
toolName: "template",
|
|
371
|
+
quickTip: "Manage persona templates (blueprints for AI Employees). List, create, update, delete templates.",
|
|
372
|
+
operations: [
|
|
373
|
+
{
|
|
374
|
+
name: "List templates",
|
|
375
|
+
description: "Browse available persona templates",
|
|
376
|
+
example: 'template(method="list")',
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
name: "Get template",
|
|
380
|
+
description: "Get full details for a template",
|
|
381
|
+
example: 'template(method="get", id="...")',
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
name: "Create template",
|
|
385
|
+
description: "Create a new persona template",
|
|
386
|
+
example: 'template(method="create", name="My Template", type="voice")',
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
name: "Delete template",
|
|
390
|
+
description: "Delete a persona template",
|
|
391
|
+
example: 'template(method="delete", id="...", confirm=true)',
|
|
392
|
+
},
|
|
393
|
+
],
|
|
394
|
+
nextSteps: {
|
|
395
|
+
list: "Get details: template(method='get', id='<id>')",
|
|
396
|
+
get: "Create persona from template: persona(method='create', from='<template_id>', name='...')",
|
|
397
|
+
create: "View template: template(method='get', id='<template_id>')",
|
|
398
|
+
update: "Verify changes: template(method='get', id='<template_id>')",
|
|
399
|
+
delete: "Template removed. List remaining: template(method='list')",
|
|
400
|
+
},
|
|
401
|
+
commonMistakes: [
|
|
402
|
+
"Deleting a template without confirm=true",
|
|
403
|
+
"Using template tool for read-only browsing when catalog tool would suffice",
|
|
404
|
+
],
|
|
405
|
+
applicableRules: [],
|
|
406
|
+
},
|
|
369
407
|
debug: {
|
|
370
408
|
toolName: "debug",
|
|
371
409
|
quickTip: "Inspect workflow executions and audit conversations. Follow the drill-down: conversations → detail → show_work → action_detail.",
|
|
@@ -486,6 +524,15 @@ Use the \`debug\` tool to inspect workflow runs and troubleshoot issues:
|
|
|
486
524
|
3. \`debug(method="action_detail", ...)\` → deep trace with LLM calls, inputs/outputs
|
|
487
525
|
Also available as persona sub-resource: \`persona(id="abc", debug={method:"conversations"})\`
|
|
488
526
|
|
|
527
|
+
## Template Management
|
|
528
|
+
Use the \`template\` tool to create, browse, update, and delete persona templates (blueprints for AI Employees):
|
|
529
|
+
- \`template(method="list")\` → browse available templates
|
|
530
|
+
- \`template(method="get", id="...")\` → get full template details
|
|
531
|
+
- \`template(method="create", name="...", type="chat", proto_config={...})\` → create new template
|
|
532
|
+
- \`template(method="update", id="...", name="...")\` → update template
|
|
533
|
+
- \`template(method="delete", id="...", confirm=true)\` → delete template
|
|
534
|
+
For read-only browsing, \`catalog(type="templates")\` also works.
|
|
535
|
+
|
|
489
536
|
## Resources
|
|
490
537
|
- \`ema://docs/usage-guide\` - Complete guide
|
|
491
538
|
- \`ema://docs/debugging-guide\` - Debugging workflow executions
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversation Adapter
|
|
3
|
+
*
|
|
4
|
+
* Thin adapter that resolves client from env and delegates to handleConversation.
|
|
5
|
+
* Same pattern as debug/adapter.ts.
|
|
6
|
+
*/
|
|
7
|
+
import { handleConversation } from "./index.js";
|
|
8
|
+
export async function handleConversationAdapter(args, createClient, getDefaultEnvName) {
|
|
9
|
+
const targetEnv = args.env ?? getDefaultEnvName();
|
|
10
|
+
// createClient() returns EmaClientAdapter at runtime (see env/config.ts)
|
|
11
|
+
const client = createClient(targetEnv);
|
|
12
|
+
return handleConversation(args, client);
|
|
13
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { formatCreateResponse } from "./formatters.js";
|
|
2
|
+
/**
|
|
3
|
+
* Create a new chat conversation for a persona.
|
|
4
|
+
* Returns the conversation ID and welcome message.
|
|
5
|
+
*/
|
|
6
|
+
export async function handleCreate(args, client) {
|
|
7
|
+
const personaId = args.persona_id;
|
|
8
|
+
if (!personaId) {
|
|
9
|
+
return {
|
|
10
|
+
error: "persona_id is required",
|
|
11
|
+
hint: 'conversation(method="create", persona_id="<uuid>")',
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
const result = await client.createConversation(personaId, {
|
|
15
|
+
userContext: args.user_context,
|
|
16
|
+
caller: args.caller ?? "mcp-toolkit",
|
|
17
|
+
});
|
|
18
|
+
return formatCreateResponse(result);
|
|
19
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export async function handleDelete(args, client) {
|
|
2
|
+
const conversationId = args.conversation_id;
|
|
3
|
+
if (!conversationId) {
|
|
4
|
+
return { error: "Missing required parameter: conversation_id" };
|
|
5
|
+
}
|
|
6
|
+
const confirm = args.confirm;
|
|
7
|
+
if (confirm !== true) {
|
|
8
|
+
return {
|
|
9
|
+
error: "Deletion requires confirm=true",
|
|
10
|
+
_tip: `To delete conversation ${conversationId}, call conversation(method='delete', conversation_id='${conversationId}', confirm=true)`,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
await client.deleteConversation(conversationId);
|
|
14
|
+
return {
|
|
15
|
+
success: true,
|
|
16
|
+
deleted_conversation_id: conversationId,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversation response formatters.
|
|
3
|
+
*
|
|
4
|
+
* Shapes raw API responses into agent-friendly output with _tip / _next_step hints.
|
|
5
|
+
*/
|
|
6
|
+
const MAX_MESSAGE_PREVIEW = 2000;
|
|
7
|
+
/**
|
|
8
|
+
* Truncate a string to max length with ellipsis.
|
|
9
|
+
*/
|
|
10
|
+
function truncate(value, max = MAX_MESSAGE_PREVIEW) {
|
|
11
|
+
const str = typeof value === "string" ? value : JSON.stringify(value) ?? "";
|
|
12
|
+
return str.length > max ? str.slice(0, max) + "..." : str;
|
|
13
|
+
}
|
|
14
|
+
export function formatCreateResponse(result) {
|
|
15
|
+
return {
|
|
16
|
+
conversation_id: result.conversationId,
|
|
17
|
+
welcome_message: result.message,
|
|
18
|
+
_next_step: `conversation(method="send", conversation_id="${result.conversationId}", text="your message here")`,
|
|
19
|
+
_tip: "Send a text message to start the conversation. The AI Employee will respond with its workflow output.",
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function formatSendResponse(result) {
|
|
23
|
+
const msg = result.message;
|
|
24
|
+
const msgType = msg?.type ?? "unknown";
|
|
25
|
+
const response = {
|
|
26
|
+
conversation_id: result.conversationId,
|
|
27
|
+
message: result.message,
|
|
28
|
+
snippets: result.snippets,
|
|
29
|
+
};
|
|
30
|
+
// Provide contextual next-step based on response message type
|
|
31
|
+
if (msgType.includes("BUTTONS")) {
|
|
32
|
+
response._tip = "The AI responded with buttons. Select one using the buttons parameter.";
|
|
33
|
+
response._next_step = `conversation(method="send", conversation_id="${result.conversationId}", buttons={selected_button:{label:"<button label>"}})`;
|
|
34
|
+
}
|
|
35
|
+
else if (msgType.includes("FORM")) {
|
|
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
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
response._tip = "Check message for the AI's reply. Use method='history' to review the full thread.";
|
|
41
|
+
response._next_step = `conversation(method="history", conversation_id="${result.conversationId}")`;
|
|
42
|
+
}
|
|
43
|
+
return response;
|
|
44
|
+
}
|
|
45
|
+
export function formatHistoryResponse(conversationId, result) {
|
|
46
|
+
const messages = result.messages.map((m) => {
|
|
47
|
+
const text = m.textMessage;
|
|
48
|
+
const contents = text?.contents;
|
|
49
|
+
return {
|
|
50
|
+
...m,
|
|
51
|
+
// Add a preview field for quick scanning
|
|
52
|
+
...(contents?.[0] && { _preview: truncate(contents[0], 200) }),
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
return {
|
|
56
|
+
conversation_id: conversationId,
|
|
57
|
+
message_count: messages.length,
|
|
58
|
+
messages,
|
|
59
|
+
_tip: "Messages are in chronological order. Use method='send' to continue the conversation.",
|
|
60
|
+
_next_step: `conversation(method="send", conversation_id="${conversationId}", text="your reply")`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { formatHistoryResponse } from "./formatters.js";
|
|
2
|
+
/**
|
|
3
|
+
* Retrieve the message history for a conversation.
|
|
4
|
+
*/
|
|
5
|
+
export async function handleHistory(args, client) {
|
|
6
|
+
const conversationId = args.conversation_id;
|
|
7
|
+
if (!conversationId) {
|
|
8
|
+
return {
|
|
9
|
+
error: "conversation_id is required",
|
|
10
|
+
hint: 'conversation(method="history", conversation_id="<uuid>")',
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
const result = await client.getConversationHistory(conversationId);
|
|
14
|
+
return formatHistoryResponse(conversationId, result);
|
|
15
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { handleCreate } from "./create.js";
|
|
2
|
+
import { handleSend } from "./send.js";
|
|
3
|
+
import { handleHistory } from "./history.js";
|
|
4
|
+
import { handleList } from "./list.js";
|
|
5
|
+
import { handleMessages } from "./messages.js";
|
|
6
|
+
import { handleDelete } from "./delete.js";
|
|
7
|
+
import { handleRename } from "./rename.js";
|
|
8
|
+
const METHOD_HANDLERS = {
|
|
9
|
+
create: handleCreate,
|
|
10
|
+
send: handleSend,
|
|
11
|
+
history: handleHistory,
|
|
12
|
+
list: handleList,
|
|
13
|
+
messages: handleMessages,
|
|
14
|
+
delete: handleDelete,
|
|
15
|
+
rename: handleRename,
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Top-level conversation handler.
|
|
19
|
+
* Routes to method-specific handlers based on the `method` parameter.
|
|
20
|
+
*/
|
|
21
|
+
export async function handleConversation(args, client) {
|
|
22
|
+
const method = args.method;
|
|
23
|
+
if (!method) {
|
|
24
|
+
return {
|
|
25
|
+
error: "method is required",
|
|
26
|
+
valid_methods: Object.keys(METHOD_HANDLERS),
|
|
27
|
+
examples: [
|
|
28
|
+
'conversation(method="create", persona_id="...") - start a live chat session',
|
|
29
|
+
'conversation(method="send", conversation_id="...", text="hello") - send a message',
|
|
30
|
+
'conversation(method="history", conversation_id="...") - view messages',
|
|
31
|
+
],
|
|
32
|
+
_tip: "Start with method='create' to open a new chat session with a persona.",
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const handler = METHOD_HANDLERS[method];
|
|
36
|
+
if (!handler) {
|
|
37
|
+
return {
|
|
38
|
+
error: `Unknown method: ${method}`,
|
|
39
|
+
valid_methods: Object.keys(METHOD_HANDLERS),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return handler(args, client);
|
|
43
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export async function handleList(args, client) {
|
|
2
|
+
const personaId = args.persona_id;
|
|
3
|
+
if (personaId) {
|
|
4
|
+
// List conversations for a specific persona
|
|
5
|
+
const limit = args.limit;
|
|
6
|
+
const paginationToken = args.pagination_token;
|
|
7
|
+
const result = await client.listConversationsForPersona(personaId, {
|
|
8
|
+
limit,
|
|
9
|
+
paginationToken,
|
|
10
|
+
});
|
|
11
|
+
const conversations = result.conversations ?? result;
|
|
12
|
+
const count = Array.isArray(conversations) ? conversations.length : 0;
|
|
13
|
+
return {
|
|
14
|
+
persona_id: personaId,
|
|
15
|
+
count,
|
|
16
|
+
conversations,
|
|
17
|
+
...(typeof result.pagination_token === "string" && {
|
|
18
|
+
pagination_token: result.pagination_token,
|
|
19
|
+
}),
|
|
20
|
+
_tip: count > 0
|
|
21
|
+
? "Use conversation(method='messages', conversation_id='<id>') to view messages."
|
|
22
|
+
: "No conversations found for this persona.",
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
// List all conversations for the current user
|
|
26
|
+
const conversations = await client.listConversations();
|
|
27
|
+
return {
|
|
28
|
+
count: conversations.length,
|
|
29
|
+
conversations: conversations.map((c) => ({
|
|
30
|
+
id: c.id,
|
|
31
|
+
display_name: c.display_name,
|
|
32
|
+
persona_id: c.persona_id,
|
|
33
|
+
persona_display_name: c.persona_display_name,
|
|
34
|
+
created_at: c.created_at,
|
|
35
|
+
updated_at: c.updated_at,
|
|
36
|
+
status: c.status,
|
|
37
|
+
})),
|
|
38
|
+
_tip: "Use conversation(method='messages', conversation_id='<id>') to view messages in a conversation.",
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export async function handleMessages(args, client) {
|
|
2
|
+
const conversationId = args.conversation_id;
|
|
3
|
+
if (!conversationId) {
|
|
4
|
+
return { error: "Missing required parameter: conversation_id" };
|
|
5
|
+
}
|
|
6
|
+
const messages = await client.getConversationMessages(conversationId);
|
|
7
|
+
return {
|
|
8
|
+
conversation_id: conversationId,
|
|
9
|
+
message_count: messages.length,
|
|
10
|
+
messages,
|
|
11
|
+
_tip: "Use conversation(method='send', conversation_id='...', text='...') to continue the conversation.",
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export async function handleRename(args, client) {
|
|
2
|
+
const conversationId = args.conversation_id;
|
|
3
|
+
if (!conversationId) {
|
|
4
|
+
return { error: "Missing required parameter: conversation_id" };
|
|
5
|
+
}
|
|
6
|
+
const displayName = args.display_name;
|
|
7
|
+
if (!displayName) {
|
|
8
|
+
return { error: "Missing required parameter: display_name" };
|
|
9
|
+
}
|
|
10
|
+
await client.updateConversationDisplayName(conversationId, displayName);
|
|
11
|
+
return {
|
|
12
|
+
success: true,
|
|
13
|
+
conversation_id: conversationId,
|
|
14
|
+
display_name: displayName,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { formatSendResponse } from "./formatters.js";
|
|
2
|
+
/**
|
|
3
|
+
* Build a ChatbotMessage proto-dict from convenience parameters.
|
|
4
|
+
*
|
|
5
|
+
* Supports three message types:
|
|
6
|
+
* - text: Simple text message or text HITL continuation
|
|
7
|
+
* - form: Form HITL continuation (filled fields or cancellation)
|
|
8
|
+
* - buttons: Button HITL continuation (selected button)
|
|
9
|
+
*
|
|
10
|
+
* If none of the convenience params are provided, falls back to `raw_message`
|
|
11
|
+
* which must be a pre-built ChatbotMessage dict with a valid `type` field.
|
|
12
|
+
*/
|
|
13
|
+
function buildChatbotMessage(args) {
|
|
14
|
+
const text = args.text;
|
|
15
|
+
const form = args.form;
|
|
16
|
+
const buttons = args.buttons;
|
|
17
|
+
const rawMessage = args.raw_message;
|
|
18
|
+
if (text !== undefined) {
|
|
19
|
+
return {
|
|
20
|
+
type: "MESSAGE_TYPE_TEXT",
|
|
21
|
+
textMessage: { contents: [text] },
|
|
22
|
+
isUserMessage: true,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (buttons !== undefined) {
|
|
26
|
+
return {
|
|
27
|
+
type: "MESSAGE_TYPE_BUTTONS",
|
|
28
|
+
buttonsMessage: {
|
|
29
|
+
selectedButton: buttons.selected_button ?? buttons.selectedButton,
|
|
30
|
+
},
|
|
31
|
+
isUserMessage: true,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (form !== undefined) {
|
|
35
|
+
return {
|
|
36
|
+
type: "MESSAGE_TYPE_FORM",
|
|
37
|
+
formMessage: {
|
|
38
|
+
fields: form.fields,
|
|
39
|
+
isCancelled: form.is_cancelled ?? form.isCancelled ?? false,
|
|
40
|
+
...(form.content !== undefined && { content: form.content }),
|
|
41
|
+
},
|
|
42
|
+
isUserMessage: true,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (rawMessage !== undefined) {
|
|
46
|
+
// Validate raw_message has a type field
|
|
47
|
+
if (!rawMessage.type || typeof rawMessage.type !== "string") {
|
|
48
|
+
return null; // Will trigger the "no content" error with a specific hint
|
|
49
|
+
}
|
|
50
|
+
return rawMessage;
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Send a message in an existing conversation.
|
|
56
|
+
*/
|
|
57
|
+
export async function handleSend(args, client) {
|
|
58
|
+
const conversationId = args.conversation_id;
|
|
59
|
+
if (!conversationId) {
|
|
60
|
+
return {
|
|
61
|
+
error: "conversation_id is required",
|
|
62
|
+
hint: 'conversation(method="send", conversation_id="<uuid>", text="hello")',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const message = buildChatbotMessage(args);
|
|
66
|
+
if (!message) {
|
|
67
|
+
// Distinguish between raw_message validation failure and no content at all
|
|
68
|
+
if (args.raw_message) {
|
|
69
|
+
return {
|
|
70
|
+
error: "raw_message must include a 'type' field (e.g. 'MESSAGE_TYPE_TEXT')",
|
|
71
|
+
hint: 'raw_message={type:"MESSAGE_TYPE_TEXT", textMessage:{contents:["hello"]}, isUserMessage:true}',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
error: "No message content provided. Supply one of: text, form, buttons, or raw_message",
|
|
76
|
+
hint: 'conversation(method="send", conversation_id="...", text="your message")',
|
|
77
|
+
examples: [
|
|
78
|
+
'text="hello" - send a text message',
|
|
79
|
+
'buttons={selected_button:{label:"Yes",description:"Confirm"}} - select a button',
|
|
80
|
+
'raw_message={type:"MESSAGE_TYPE_FORM", formMessage:{...}} - submit a form (echo back formMessage with values)',
|
|
81
|
+
'form={is_cancelled:true} - cancel a form',
|
|
82
|
+
],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const result = await client.sendMessage(conversationId, message, {
|
|
86
|
+
userContext: args.user_context,
|
|
87
|
+
originalMessageId: args.original_message_id,
|
|
88
|
+
});
|
|
89
|
+
return formatSendResponse(result);
|
|
90
|
+
}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* - regenerate: Modify document section with new query
|
|
19
19
|
* - replace: Replace entire document content (full replacement, not partial)
|
|
20
20
|
* - sanitize: Sanitize data items for a persona
|
|
21
|
+
* - continue: Respond to HITL pause on a dashboard row (text, button, or form)
|
|
21
22
|
*
|
|
22
23
|
* Note: Schema is now at persona level: persona(method="schema", id="...")
|
|
23
24
|
*/
|
|
@@ -335,7 +336,11 @@ export async function handleData(args, client, readFile) {
|
|
|
335
336
|
persona_id: personaId,
|
|
336
337
|
uploaded_rows: results.length,
|
|
337
338
|
row_ids: results.map(r => r.row_id),
|
|
338
|
-
_tip: "
|
|
339
|
+
_tip: "Rows created and workflow triggered. Poll for completion using method='result' with each row_id.",
|
|
340
|
+
_next_step: results.length === 1
|
|
341
|
+
? `persona(id="${personaId}", data={method:"result", row_id:"${results[0].row_id}"})`
|
|
342
|
+
: `Poll each row: ${results.map(r => `persona(id="${personaId}", data={method:"result", row_id:"${r.row_id}"})`).join(", ")}`,
|
|
343
|
+
_poll_interval_seconds: 5,
|
|
339
344
|
};
|
|
340
345
|
}
|
|
341
346
|
catch (error) {
|
|
@@ -459,10 +464,40 @@ export async function handleData(args, client, readFile) {
|
|
|
459
464
|
return { error: `Failed to replace document: ${error instanceof Error ? error.message : String(error)}` };
|
|
460
465
|
}
|
|
461
466
|
}
|
|
467
|
+
case "result": {
|
|
468
|
+
// Get dashboard row result/output
|
|
469
|
+
const rowId = (dataArgs?.row_id ?? dataArgs?.id);
|
|
470
|
+
if (!rowId) {
|
|
471
|
+
return errorResult("data.result requires row_id parameter", {
|
|
472
|
+
hint: "Get row_id from the upload response: persona(id='abc', data={method:'upload', items:[...]})",
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
const includeFileContents = (dataArgs?.include_file_contents ?? dataArgs?.include_files);
|
|
476
|
+
return handleDashboardResult(personaId, rowId, includeFileContents ?? false, client);
|
|
477
|
+
}
|
|
478
|
+
case "continue": {
|
|
479
|
+
// Respond to HITL pause on a dashboard row
|
|
480
|
+
const rowId = (dataArgs?.row_id ?? dataArgs?.id);
|
|
481
|
+
if (!rowId) {
|
|
482
|
+
return errorResult("data.continue requires row_id parameter", {
|
|
483
|
+
hint: "Get row_id from the result response when requires_human_input=true",
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
const text = dataArgs?.text;
|
|
487
|
+
const buttons = dataArgs?.buttons;
|
|
488
|
+
const form = dataArgs?.form;
|
|
489
|
+
const rawContinuation = dataArgs?.raw_continuation;
|
|
490
|
+
if (!text && !buttons && !form && !rawContinuation) {
|
|
491
|
+
return errorResult("data.continue requires one of: text, buttons, form, or raw_continuation", {
|
|
492
|
+
hint: "text='approved' for free-text, buttons={selected_button:{label:'Approve'}} for button selection, form={is_cancelled:true} for form cancellation, raw_continuation={formMessage:{...full form with values...}} for form submission",
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
return handleDashboardContinue(personaId, rowId, { text, buttons, form, raw: rawContinuation }, client);
|
|
496
|
+
}
|
|
462
497
|
default:
|
|
463
498
|
return {
|
|
464
499
|
error: `Unknown data method: ${method}`,
|
|
465
|
-
hint: "Valid methods: list, stats, templates, generate, get, copy, replicate, upload, delete,
|
|
500
|
+
hint: "Valid methods: list, stats, templates, generate, get, copy, replicate, upload, delete, embed, refresh, result, continue, regenerate, replace, sanitize",
|
|
466
501
|
};
|
|
467
502
|
}
|
|
468
503
|
}
|
|
@@ -684,6 +719,137 @@ async function handleDashboardRefresh(personaId, rowId, client) {
|
|
|
684
719
|
return { error: `Failed to refresh dashboard row: ${error instanceof Error ? error.message : String(error)}` };
|
|
685
720
|
}
|
|
686
721
|
}
|
|
722
|
+
/**
|
|
723
|
+
* Detect the type of continuation message (text, buttons, or form).
|
|
724
|
+
* Mirrors how chat's formatSendResponse detects MESSAGE_TYPE_BUTTONS/FORM/TEXT.
|
|
725
|
+
*/
|
|
726
|
+
function detectContinuationType(continuation) {
|
|
727
|
+
if (!continuation)
|
|
728
|
+
return "unknown";
|
|
729
|
+
// Check the oneof field that's populated in the ContinuationMessage proto
|
|
730
|
+
if (continuation.buttonsMessage)
|
|
731
|
+
return "buttons";
|
|
732
|
+
if (continuation.formMessage)
|
|
733
|
+
return "form";
|
|
734
|
+
if (continuation.textMessage)
|
|
735
|
+
return "text";
|
|
736
|
+
// Fallback: check for a `value` wrapper (some proto serializations)
|
|
737
|
+
const value = continuation.value;
|
|
738
|
+
if (value?.case === "buttonsMessage")
|
|
739
|
+
return "buttons";
|
|
740
|
+
if (value?.case === "formMessage")
|
|
741
|
+
return "form";
|
|
742
|
+
if (value?.case === "textMessage")
|
|
743
|
+
return "text";
|
|
744
|
+
return "unknown";
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Get the result/output of a dashboard row.
|
|
748
|
+
* Detects row state and provides polling/debugging guidance.
|
|
749
|
+
*/
|
|
750
|
+
async function handleDashboardResult(personaId, rowId, includeFileContents, client) {
|
|
751
|
+
try {
|
|
752
|
+
const result = await client.getDashboardRowResult(personaId, rowId, includeFileContents);
|
|
753
|
+
const row = result.row;
|
|
754
|
+
const state = row?.state ?? "unknown";
|
|
755
|
+
const isComplete = state === "DASHBOARD_ROW_STATE_SUCCESS";
|
|
756
|
+
const isFailed = state === "DASHBOARD_ROW_STATE_FAILED";
|
|
757
|
+
const isReviewing = state === "DASHBOARD_ROW_STATE_REVIEWING";
|
|
758
|
+
const isRunning = [
|
|
759
|
+
"DASHBOARD_ROW_STATE_RUNNING",
|
|
760
|
+
"DASHBOARD_ROW_STATE_QUEUED",
|
|
761
|
+
"DASHBOARD_ROW_STATE_INITIAL",
|
|
762
|
+
].includes(state);
|
|
763
|
+
const response = {
|
|
764
|
+
method: "result",
|
|
765
|
+
persona_id: personaId,
|
|
766
|
+
row_id: rowId,
|
|
767
|
+
state,
|
|
768
|
+
is_complete: isComplete,
|
|
769
|
+
row: result.row,
|
|
770
|
+
};
|
|
771
|
+
if (result.additional_column_details?.length) {
|
|
772
|
+
response.additional_column_details = result.additional_column_details;
|
|
773
|
+
}
|
|
774
|
+
if (isReviewing) {
|
|
775
|
+
// HITL — workflow is paused waiting for human input/approval
|
|
776
|
+
response.requires_human_input = true;
|
|
777
|
+
const continuation = (row?.continuationMessage ?? row?.continuation_message);
|
|
778
|
+
if (continuation) {
|
|
779
|
+
response.continuation_message = continuation;
|
|
780
|
+
}
|
|
781
|
+
// Detect continuation type for type-specific guidance (matches chat's formatSendResponse pattern)
|
|
782
|
+
const contType = detectContinuationType(continuation);
|
|
783
|
+
response.continuation_type = contType;
|
|
784
|
+
if (contType === "buttons") {
|
|
785
|
+
const buttonsMsg = continuation?.buttonsMessage;
|
|
786
|
+
const buttons = buttonsMsg?.buttons;
|
|
787
|
+
const labels = buttons?.map(b => b.label).filter(Boolean) ?? [];
|
|
788
|
+
response._tip = `HITL: The workflow is asking the user to select a button.${labels.length ? ` Options: ${labels.join(", ")}` : ""} Ask the user which to choose.`;
|
|
789
|
+
response._next_step = `persona(id="${personaId}", data={method:"continue", row_id:"${rowId}", buttons:{selected_button:{label:"<chosen label>"}}})`;
|
|
790
|
+
}
|
|
791
|
+
else if (contType === "form") {
|
|
792
|
+
const formMsg = continuation?.formMessage;
|
|
793
|
+
const isIntermediate = formMsg?.isIntermediate === true;
|
|
794
|
+
response._tip = `HITL: The workflow is asking the user to fill out a form.${isIntermediate ? " This is an INTERMEDIATE form — more fields will follow after submission." : ""} Review the fields in continuation_message.formMessage, ask the user for values, then echo back the full formMessage with values filled in using raw_continuation. Use form={is_cancelled:true} to cancel instead.`;
|
|
795
|
+
response._next_step = `persona(id="${personaId}", data={method:"continue", row_id:"${rowId}", raw_continuation:{formMessage:{...continuation_message.formMessage with field values filled in...}}})`;
|
|
796
|
+
response._form_value_guide = "Field values use wellKnown types: {wellKnown:{stringValue:'text'}} for strings, {wellKnown:{int64Value:'42'}} for ints (string-encoded), {wellKnown:{doubleValue:3.14}} for floats, {wellKnown:{boolValue:true}} for bools, {wellKnown:{dateValue:{year,month,day}}} for dates, {wellKnown:{datetimeValue:{utcTime:'ISO'}}} for datetimes. For struct dropdowns use Id as stringValue; for enum dropdowns use enumValue as stringValue.";
|
|
797
|
+
}
|
|
798
|
+
else {
|
|
799
|
+
response._tip = "HITL: Workflow is paused and waiting for human input. Review the continuation_message and ask the user what to do.";
|
|
800
|
+
response._next_step = `persona(id="${personaId}", data={method:"continue", row_id:"${rowId}", text:"user's response"})`;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
else if (isRunning) {
|
|
804
|
+
response._tip = "Row is still processing. Poll again in 5-10 seconds.";
|
|
805
|
+
response._next_step = `persona(id="${personaId}", data={method:"result", row_id:"${rowId}"})`;
|
|
806
|
+
response._poll_interval_seconds = 5;
|
|
807
|
+
}
|
|
808
|
+
else if (isFailed) {
|
|
809
|
+
response._tip = "Row processing failed. Check workflow execution for details.";
|
|
810
|
+
const workflowRunId = row?.workflowRunId ?? row?.workflow_run_id;
|
|
811
|
+
if (workflowRunId) {
|
|
812
|
+
response._next_step = `debug(method="show_work", persona_id="${personaId}", workflow_run_id="${workflowRunId}")`;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
else if (isComplete && !includeFileContents) {
|
|
816
|
+
response._tip = "Row complete. Add include_file_contents=true to get generated file contents.";
|
|
817
|
+
}
|
|
818
|
+
return response;
|
|
819
|
+
}
|
|
820
|
+
catch (error) {
|
|
821
|
+
return {
|
|
822
|
+
error: `Failed to get dashboard row result: ${error instanceof Error ? error.message : String(error)}`,
|
|
823
|
+
persona_id: personaId,
|
|
824
|
+
row_id: rowId,
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Continue a paused (HITL) dashboard row with a response.
|
|
830
|
+
* Sends text, button selection, or form data to resume the workflow.
|
|
831
|
+
*/
|
|
832
|
+
async function handleDashboardContinue(personaId, rowId, continuation, client) {
|
|
833
|
+
try {
|
|
834
|
+
await client.continueDashboardRow(rowId, continuation);
|
|
835
|
+
return {
|
|
836
|
+
method: "continue",
|
|
837
|
+
persona_id: personaId,
|
|
838
|
+
row_id: rowId,
|
|
839
|
+
sent: continuation.raw ? "raw" : continuation.text ? "text" : continuation.buttons ? "buttons" : "form",
|
|
840
|
+
_tip: "Continuation sent. The workflow is resuming. Poll for the result.",
|
|
841
|
+
_next_step: `persona(id="${personaId}", data={method:"result", row_id:"${rowId}"})`,
|
|
842
|
+
_poll_interval_seconds: 5,
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
catch (error) {
|
|
846
|
+
return {
|
|
847
|
+
error: `Failed to continue dashboard row: ${error instanceof Error ? error.message : String(error)}`,
|
|
848
|
+
persona_id: personaId,
|
|
849
|
+
row_id: rowId,
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
}
|
|
687
853
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
688
854
|
// Method inference and clarification when method is omitted
|
|
689
855
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -733,7 +899,7 @@ function inferMethodAndClarify(args, dataArgs) {
|
|
|
733
899
|
return {
|
|
734
900
|
status: "clarification_needed",
|
|
735
901
|
message: "What operation would you like to perform?",
|
|
736
|
-
options: ["list", "upload", "generate", "templates", "get", "delete", "copy", "replicate", "embedding", "sanitize", "refresh", "regenerate", "replace"],
|
|
902
|
+
options: ["list", "upload", "generate", "templates", "get", "delete", "copy", "replicate", "embedding", "sanitize", "refresh", "result", "continue", "regenerate", "replace"],
|
|
737
903
|
hint: "Specify method='...' for direct execution. Example: data(persona_id='abc', method='list')",
|
|
738
904
|
};
|
|
739
905
|
}
|