@velaro/mcp-server 0.6.103 → 1.0.0
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.
- package/README.md +6 -0
- package/log-search.js +252 -0
- package/messaging-base.js +21 -0
- package/package.json +6 -2
- package/server.js +782 -44
- package/server.json +2 -2
- package/tool-annotations.js +138 -0
- package/workflow-schedule.js +70 -0
package/server.js
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
* VELARO_MCP_MODE=http PORT=3000 node server.js
|
|
41
41
|
*/
|
|
42
42
|
|
|
43
|
-
import { describeRawMeta, fetchRetry429 } from './log-search.js';
|
|
43
|
+
import { buildRawLogQuery, describeRawMeta, fetchRetry429, resolveLogWindow } from './log-search.js';
|
|
44
44
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
45
45
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
46
46
|
import {
|
|
@@ -49,6 +49,9 @@ import {
|
|
|
49
49
|
GetPromptRequestSchema,
|
|
50
50
|
ListPromptsRequestSchema,
|
|
51
51
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
52
|
+
import { withAnnotations } from './tool-annotations.js';
|
|
53
|
+
import { resolveMessagingApi } from './messaging-base.js';
|
|
54
|
+
import { buildResolvePath, buildTimelinePath, buildOverrideBody, buildValidateBody, buildBannerPath, buildOverrideListPath, tolerant, VALIDATE_PATH, OVERRIDE_PATH } from './workflow-schedule.js';
|
|
52
55
|
|
|
53
56
|
// Default was 'https://help.velaro.com'. Renamed to the recognizable admin API hostname for
|
|
54
57
|
// clarity during the 2026-09-10 Donaldson bot Calendly status investigation
|
|
@@ -59,7 +62,7 @@ import {
|
|
|
59
62
|
// caused that investigation's status/reality contradiction, it was NOT this default resolving to a
|
|
60
63
|
// broken or different host; that theory is disproven. Left as a naming clarity improvement only.
|
|
61
64
|
const API_BASE = process.env.VELARO_ADMIN_API || 'https://api-admin-us-east.velaro.com';
|
|
62
|
-
const MESSAGING_API = process.env.VELARO_MESSAGING_API
|
|
65
|
+
const MESSAGING_API = resolveMessagingApi(process.env.VELARO_MESSAGING_API, API_BASE);
|
|
63
66
|
const MCP_KEY = process.env.VELARO_MCP_KEY;
|
|
64
67
|
const JWT = process.env.VELARO_JWT;
|
|
65
68
|
|
|
@@ -154,6 +157,25 @@ function extractLogEntries(response) {
|
|
|
154
157
|
return [];
|
|
155
158
|
}
|
|
156
159
|
|
|
160
|
+
/** Body for POST/PUT ApplePayPresetOrders: only passed fields; the server computes the total. */
|
|
161
|
+
function applePayPresetBody(args) {
|
|
162
|
+
const body = {};
|
|
163
|
+
for (const k of ['name', 'description', 'currencyCode', 'isActive', 'overrideRestrictedToManagers']) {
|
|
164
|
+
if (args[k] !== undefined) body[k] = args[k];
|
|
165
|
+
}
|
|
166
|
+
if (args.lineItems !== undefined) {
|
|
167
|
+
if (!Array.isArray(args.lineItems) || args.lineItems.length > 20) throw new Error('lineItems must be an array of at most 20 items');
|
|
168
|
+
body.lineItems = args.lineItems.map(li => ({ label: String(li?.label ?? ''), amount: Number(li?.amount) }));
|
|
169
|
+
}
|
|
170
|
+
return body;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function applePayPresetId(raw) {
|
|
174
|
+
const n = Number(raw);
|
|
175
|
+
if (!Number.isInteger(n) || n <= 0) throw new Error('id must be a positive whole number');
|
|
176
|
+
return n;
|
|
177
|
+
}
|
|
178
|
+
|
|
157
179
|
/**
|
|
158
180
|
* Body for PUT avatar/groups|workflows/{id}/override. Merge by default (only passed fields change, "" clears one);
|
|
159
181
|
* `replace: true` replaces the whole override. Mirrors cli/lib/commands/avatar.js.
|
|
@@ -320,7 +342,7 @@ async function gatewayInit() {
|
|
|
320
342
|
// Tools served by velaro-messaging's admin-tools gateway (McpAdminGatewayController, POST /mcp/v1/admin-tools/call,
|
|
321
343
|
// no session). The admin /mcp gateway only serves kb_* tools, so these used to fail there.
|
|
322
344
|
const MESSAGING_ADMIN_TOOLS = new Set([
|
|
323
|
-
'workflow_create', 'workflow_list', 'workflow_get', 'workflow_toggle',
|
|
345
|
+
'workflow_create', 'workflow_list', 'workflow_get', 'workflow_toggle', 'workflow_run_now',
|
|
324
346
|
'bot_create', 'bot_list', 'bot_get', 'bot_update_prompt', 'bot_set_knowledge',
|
|
325
347
|
'ai_config_attach_index', 'ai_config_set_reply_format', 'call_pop_settings_get', 'call_pop_settings_set',
|
|
326
348
|
'crm_mobile_fields_get', 'crm_mobile_fields_set',
|
|
@@ -488,7 +510,7 @@ const TOOLS = [
|
|
|
488
510
|
},
|
|
489
511
|
{
|
|
490
512
|
name: 'notifications_get',
|
|
491
|
-
description: "Get the calling agent's own notification/SMS alert settings (new conversation, missed chat, unassign warning, text-alert toggles, SMS phone number).",
|
|
513
|
+
description: "Get the calling agent's own notification/SMS alert settings (new conversation, missed chat, unassign warning, text-alert toggles, SMS phone number) plus alertRouting: which Channel (web, sms, whatsapp, appleMessages, email, phone) x Handling (mine, unassigned, bot, teammates) x Kind (conversations, tickets, calls, visitors) alerts fire, and ivrBotAlerts (opt-in for inbound IVR bot chats).",
|
|
492
514
|
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
493
515
|
},
|
|
494
516
|
{
|
|
@@ -508,6 +530,35 @@ const TOOLS = [
|
|
|
508
530
|
smsOnUnassignWarning: { type: 'boolean', description: 'Send an SMS on unassign warning' },
|
|
509
531
|
useProfilePhone: { type: 'boolean', description: 'Use the profile phone number for SMS alerts' },
|
|
510
532
|
smsAlertPhone: { type: 'string', description: 'Phone number for SMS alerts (E.164, e.g. +14155551234)' },
|
|
533
|
+
alertRouting: {
|
|
534
|
+
type: 'object',
|
|
535
|
+
description: 'Alert routing, partial update (omitted keys unchanged). An alert fires only if its channel, handling and kind are all on. Example: {"channels":{"sms":true,"web":false},"handling":{"mine":true,"unassigned":false},"kinds":{"tickets":false},"ivrBotAlerts":true}',
|
|
536
|
+
properties: {
|
|
537
|
+
channels: { type: 'object', description: 'Keys: web, sms, whatsapp, appleMessages, email, phone (booleans)' },
|
|
538
|
+
handling: { type: 'object', description: 'Keys: mine, unassigned, bot, teammates (booleans, multi-select)' },
|
|
539
|
+
kinds: { type: 'object', description: 'Keys: conversations, tickets, calls, visitors (booleans)' },
|
|
540
|
+
ivrBotAlerts: { type: 'boolean', description: 'Alert for inbound IVR / phone bot chats (default off)' },
|
|
541
|
+
},
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
name: 'signature_policy_get',
|
|
548
|
+
description: "Get the company email signature policy for the calling admin's site: the company-wide row (teamId 0), any per-team overrides, and the suggested legal disclaimer text. Modes: locked (users cannot use personal signatures), company-default (company signature is the default, personal allowed), personal-only (the default when nothing is set). The legal disclaimer is off unless turned on. Admin only.",
|
|
549
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
name: 'signature_policy_set',
|
|
553
|
+
description: "Update the company email signature policy (admin only). Only the fields passed are changed; the current row is read first and merged. teamId 0 (default) is the company-wide policy; a team id sets a per-team override. Pass mode 'default' or null to clear a value back to following the company-wide setting. Use signature_policy_get first.",
|
|
554
|
+
inputSchema: {
|
|
555
|
+
type: 'object',
|
|
556
|
+
properties: {
|
|
557
|
+
teamId: { type: 'integer', description: '0 = company-wide (default), otherwise the team id for a per-team override' },
|
|
558
|
+
mode: { type: 'string', enum: ['locked', 'company-default', 'personal-only', 'default'], description: 'locked | company-default | personal-only | default (follow company-wide)' },
|
|
559
|
+
corporateSignature: { type: ['string', 'null'], description: 'Company signature HTML, max 4000 characters. Empty or null clears it.' },
|
|
560
|
+
disclaimerEnabled: { type: ['boolean', 'null'], description: 'Turn the legal disclaimer on or off. null follows the company-wide choice. Off by default.' },
|
|
561
|
+
disclaimerText: { type: ['string', 'null'], description: 'Legal disclaimer text, max 2000 characters. Empty or null clears it.' },
|
|
511
562
|
},
|
|
512
563
|
},
|
|
513
564
|
},
|
|
@@ -1058,6 +1109,18 @@ const TOOLS = [
|
|
|
1058
1109
|
required: ['id', 'enabled'],
|
|
1059
1110
|
},
|
|
1060
1111
|
},
|
|
1112
|
+
{
|
|
1113
|
+
name: 'workflow_run_now',
|
|
1114
|
+
description: 'Queue an enabled workflow to run now, optionally seeded with a shallow JSON context object (max 50 keys, 4KB) that becomes the run\'s workflow attributes.',
|
|
1115
|
+
inputSchema: {
|
|
1116
|
+
type: 'object',
|
|
1117
|
+
properties: {
|
|
1118
|
+
id: { type: 'number', description: 'Workflow ID' },
|
|
1119
|
+
context: { type: 'object', description: 'Optional shallow object of values to seed the run, e.g. {"customerId":"123"}' },
|
|
1120
|
+
},
|
|
1121
|
+
required: ['id'],
|
|
1122
|
+
},
|
|
1123
|
+
},
|
|
1061
1124
|
{
|
|
1062
1125
|
name: 'workflow_create',
|
|
1063
1126
|
description: 'Create a new workflow with optional pre-built nodes. Use this to build a workflow for the user from a description. Pass initialNodesBlob as a JSON string of the nodes object (keys are string IDs "1","2","3"..., values are node objects). For integration AI workflows, use 3 nodes: trigger (type:"trigger", trigger:"conversation_opened", overrideRouting:true, channels:[], triggerExpression:null), sendMessage (type:"action", actionType:"sendMessage", text:"greeting", child:3), and the AI node (type:"action", actionType:"dynamicsAI"|"netsuiteAI"|"hubSpotAI"|"shopifyAI"|"bigCommerceAI"|"woocommerceAI", configId fields null, aiConfigId:null, mcpServerIds:[]). The workflow is created disabled — activate it separately with workflow_toggle.',
|
|
@@ -1071,6 +1134,66 @@ const TOOLS = [
|
|
|
1071
1134
|
required: ['name', 'trigger'],
|
|
1072
1135
|
},
|
|
1073
1136
|
},
|
|
1137
|
+
{
|
|
1138
|
+
name: 'workflow_schedule_resolve',
|
|
1139
|
+
description: 'Which workflow runs at a given date/time? Returns { mode, first (winner), gap, winnerOverrideId, active (ordered workflows with priority), trace } (workflow schedule priority; schedule-only preview, a workflow own trigger conditions are not evaluated). Site comes from auth. Answers "why did workflow X (not) fire at 6 PM Dec 25". Read-only.',
|
|
1140
|
+
inputSchema: { type: 'object', properties: {
|
|
1141
|
+
trigger: { type: 'string', description: 'Trigger type, e.g. "conversation_opened"' },
|
|
1142
|
+
channel: { type: 'string', description: 'Channel, e.g. "Chat", "Sms"' },
|
|
1143
|
+
at: { type: 'string', description: 'ISO date-time to evaluate (default now)' },
|
|
1144
|
+
tz: { type: 'string', description: 'IANA time zone, e.g. "America/Los_Angeles"' },
|
|
1145
|
+
}, required: ['trigger'] },
|
|
1146
|
+
},
|
|
1147
|
+
{
|
|
1148
|
+
name: 'workflow_schedule_timeline',
|
|
1149
|
+
description: 'Timeline of which workflow wins over a date range for a trigger/channel: returns { segments: [{ fromUtc, toUtc, workflowId, workflowName, gap }], gaps (count) }. Read-only.',
|
|
1150
|
+
inputSchema: { type: 'object', properties: {
|
|
1151
|
+
from: { type: 'string', description: 'ISO start with offset (default now)' },
|
|
1152
|
+
to: { type: 'string', description: 'ISO end with offset (default from + 14 days; range max 62 days)' },
|
|
1153
|
+
trigger: { type: 'string' },
|
|
1154
|
+
channel: { type: 'string' },
|
|
1155
|
+
}, required: ['trigger'] },
|
|
1156
|
+
},
|
|
1157
|
+
{
|
|
1158
|
+
name: 'workflow_schedule_validate',
|
|
1159
|
+
description: 'Dry-run one draft schedule override without saving: what would the next 400 days look like. Returns { ok, errors, warnings, gapCount }; same-priority overlap and a bad target block, gaps warn. Omit override to just list current gaps. Read-only.',
|
|
1160
|
+
inputSchema: { type: 'object', properties: {
|
|
1161
|
+
trigger: { type: 'string' },
|
|
1162
|
+
channel: { type: 'string' },
|
|
1163
|
+
override: { type: 'object', description: 'Draft override: { sharedScheduleId, name, startDate, endDate, action ("SwitchWorkflow" or 3), targetWorkflowId, recurringYearly, autoExpiresAt, isActive }' },
|
|
1164
|
+
}, required: ['trigger'] },
|
|
1165
|
+
},
|
|
1166
|
+
{
|
|
1167
|
+
name: 'workflow_schedule_banner',
|
|
1168
|
+
description: 'The first gap (no workflow scheduled to run) in the next 7 days for a trigger/channel: { hasGap, fromUtc, toUtc, message }. Read-only.',
|
|
1169
|
+
inputSchema: { type: 'object', properties: { trigger: { type: 'string' }, channel: { type: 'string' } } },
|
|
1170
|
+
},
|
|
1171
|
+
{
|
|
1172
|
+
name: 'workflow_schedule_override_list',
|
|
1173
|
+
description: 'List this site\'s schedule overrides, optionally only one action ("SwitchWorkflow" or 3). Read-only.',
|
|
1174
|
+
inputSchema: { type: 'object', properties: { action: { description: '"SwitchWorkflow" or an int action value' } } },
|
|
1175
|
+
},
|
|
1176
|
+
{
|
|
1177
|
+
name: 'workflow_schedule_override_delete',
|
|
1178
|
+
description: 'Delete a schedule override by id (site admins only). Writes: confirm with the user first.',
|
|
1179
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number' } }, required: ['id'] },
|
|
1180
|
+
},
|
|
1181
|
+
{
|
|
1182
|
+
name: 'workflow_schedule_override_save',
|
|
1183
|
+
description: 'Create (omit id) or update (id) a schedule override. action accepts "SwitchWorkflow" (3) with targetWorkflowId to run a different workflow on those dates, alongside the existing actions. Writes: confirm the dates and target with the user first.',
|
|
1184
|
+
inputSchema: { type: 'object', properties: {
|
|
1185
|
+
id: { type: 'number', description: 'Existing override id to update; omit to create' },
|
|
1186
|
+
sharedScheduleId: { type: 'number' },
|
|
1187
|
+
name: { type: 'string' },
|
|
1188
|
+
startDate: { type: 'string', description: 'YYYY-MM-DD' },
|
|
1189
|
+
endDate: { type: 'string', description: 'YYYY-MM-DD, optional' },
|
|
1190
|
+
recurringYearly: { type: 'boolean' },
|
|
1191
|
+
autoExpiresAt: { type: 'string', description: 'ISO date-time with offset, optional' },
|
|
1192
|
+
isActive: { type: 'boolean', description: 'Default true' },
|
|
1193
|
+
action: { description: '"SwitchWorkflow" or its int value 3 (or an existing action value)' },
|
|
1194
|
+
targetWorkflowId: { type: 'number', description: 'Workflow to run when action is SwitchWorkflow' },
|
|
1195
|
+
}, required: ['action'] },
|
|
1196
|
+
},
|
|
1074
1197
|
{
|
|
1075
1198
|
name: 'workflow_update_node',
|
|
1076
1199
|
description: `Update a single node in a workflow. Use workflow_get first to read current nodes, then pass the full updated node. The node must already exist.
|
|
@@ -1087,6 +1210,8 @@ Key node shapes:
|
|
|
1087
1210
|
propName:"myAnswerVar" } // variable that stores the result as JSON array string
|
|
1088
1211
|
• condition: { type:"action", actionType:"condition", conditionOptions:[...] }
|
|
1089
1212
|
• transferToAI: { type:"action", actionType:"transferToAI", aiConfigId:N }
|
|
1213
|
+
• sendSurvey: { type:"action", actionType:"sendSurvey", sendSurveyId:N, surveyDeliveryMode:"conversational"|"form", closeAfterSurvey:true|false, child:N }
|
|
1214
|
+
closeAfterSurvey (optional, default false): when true the chat closes once the visitor completes the survey (use for Unavailable/after-hours surveys that replace a live chat). Omit to keep the old behaviour.
|
|
1090
1215
|
AI follow-up suggestions ("AI mode"): add suggestFollowUps:true|false (omit = use the account default, set via ai_followups_site_set), suggestFollowUpCount:2-4, suggestFollowUpInstruction:"<=300 chars". After each web-chat bot reply the bot offers short tappable follow-up replies; typing stays available.
|
|
1091
1216
|
|
|
1092
1217
|
For multiSelect dynamic options: the variable named in dynamicOptionsVariable must contain a JSON array like [{"label":"Red","value":"red"},{"label":"Blue","value":"blue"}]. Populate it via an HTTP Request node before the ask node.`,
|
|
@@ -1471,6 +1596,18 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
1471
1596
|
required: ['id'],
|
|
1472
1597
|
},
|
|
1473
1598
|
},
|
|
1599
|
+
{
|
|
1600
|
+
name: 'copilot_studio_ask',
|
|
1601
|
+
description: 'Live diagnostic: ask a connected Copilot Studio / Azure AI agent a real question and classify the reply as answered, vendor_limit (usage limit / unavailable) or no_answer (fallback topic). Use to prove the agent actually answers, not just that credentials work.',
|
|
1602
|
+
inputSchema: {
|
|
1603
|
+
type: 'object',
|
|
1604
|
+
properties: {
|
|
1605
|
+
id: { type: 'number', description: 'Config ID from copilot_studio_list' },
|
|
1606
|
+
question: { type: 'string', description: 'A real data-bound question for the agent' },
|
|
1607
|
+
},
|
|
1608
|
+
required: ['id', 'question'],
|
|
1609
|
+
},
|
|
1610
|
+
},
|
|
1474
1611
|
|
|
1475
1612
|
// ── Monday.com field mapping / CRM search ────────────────────────────────
|
|
1476
1613
|
{
|
|
@@ -1661,6 +1798,11 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
1661
1798
|
welcomeMessage: { type: 'string', description: 'Sent to the Teams user when their message opens a new conversation. Use {{user.name}} for their display name. Max 1000 characters.' },
|
|
1662
1799
|
teamId: { type: 'number', description: 'Route new conversations to this team id (must be a real, non-deleted team on this site)' },
|
|
1663
1800
|
clearTeamId: { type: 'boolean', description: 'Clear the team override and fall back to the site default team' },
|
|
1801
|
+
prechatEnabled: { type: 'boolean', description: 'Ask the Teams user prechat questions before a conversation opens' },
|
|
1802
|
+
prechatFields: { type: 'array', description: 'Replaces the prechat questions (max 6). Key "email" is validated as an email.', items: { type: 'object', properties: { key: { type: 'string' }, label: { type: 'string' }, required: { type: 'boolean' } }, required: ['key', 'label'] } },
|
|
1803
|
+
quickReplies: { type: 'array', description: 'Replaces the quick-reply buttons (max 6, 40 chars each); empty array clears them', items: { type: 'string' } },
|
|
1804
|
+
postChatEnabled:{ type: 'boolean', description: 'Send postChatMessage when an agent closes the conversation' },
|
|
1805
|
+
postChatMessage:{ type: 'string', description: 'Post-chat text, {{user.name}} = first name, max 1000 chars; empty string clears it' },
|
|
1664
1806
|
},
|
|
1665
1807
|
required: [],
|
|
1666
1808
|
},
|
|
@@ -2381,6 +2523,24 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2381
2523
|
required: ['siteId', 'indexName'],
|
|
2382
2524
|
},
|
|
2383
2525
|
},
|
|
2526
|
+
{
|
|
2527
|
+
name: 'index_set_profile',
|
|
2528
|
+
description: 'Velaro staff only. Set the ingestion profile of ONE named index on ANY site (chunk strategy, chunk size/overlap, purpose, search size, excludeFromAllSources). Apply-if-present: only fields you pass change. Changing the profile versions the content hash so the next ingest re-chunks. Wraps PUT KnowledgeSetup/Staff/{siteId}/index-profile (velaro-messaging).',
|
|
2529
|
+
inputSchema: {
|
|
2530
|
+
type: 'object',
|
|
2531
|
+
properties: {
|
|
2532
|
+
siteId: { type: 'number', description: 'Target site ID' },
|
|
2533
|
+
indexName: { type: 'string', description: 'Index name on that site, e.g. "100140-troubleshooting"' },
|
|
2534
|
+
chunkStrategy: { type: 'string', enum: ['procedure', 'section'], description: 'Chunk strategy' },
|
|
2535
|
+
chunkSize: { type: 'number', description: 'Chunk size' },
|
|
2536
|
+
chunkOverlap: { type: 'number', description: 'Chunk overlap' },
|
|
2537
|
+
purpose: { type: 'string', enum: ['general', 'troubleshooting', 'catalog'], description: 'Index purpose' },
|
|
2538
|
+
searchSize: { type: 'number', description: 'Results per search' },
|
|
2539
|
+
excludeFromAllSources: { type: 'boolean', description: 'Keep out of the ["*"] all-sources search' },
|
|
2540
|
+
},
|
|
2541
|
+
required: ['siteId', 'indexName'],
|
|
2542
|
+
},
|
|
2543
|
+
},
|
|
2384
2544
|
{
|
|
2385
2545
|
name: 'uploaded_file_reindex',
|
|
2386
2546
|
description: 'Re-index an already-uploaded AI training file from blob storage — use when a file shows 0 chunks (e.g. PDF uploaded before PDF support was added). Reads from blob and re-runs full ingestion. Use index_status after ~30s to verify chunks appear.',
|
|
@@ -2626,6 +2786,29 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2626
2786
|
required: ['startDate', 'endDate'],
|
|
2627
2787
|
},
|
|
2628
2788
|
},
|
|
2789
|
+
{
|
|
2790
|
+
name: 'report_performance_by_country_get',
|
|
2791
|
+
description: 'Performance by Country report (US vs Canada vs Europe vs Other): engagements (handled, missed, abandoned), performance (first response, handle time, bot containment, transfers, service level) and scores (CSAT, sentiment) per country bucket and per agent. attribution: visitor (visitor GeoIP country, default) | agent (country on the agent profile) | group (the site saved group-to-bucket map).',
|
|
2792
|
+
inputSchema: {
|
|
2793
|
+
type: 'object',
|
|
2794
|
+
properties: {
|
|
2795
|
+
startDate: { type: 'string', description: 'ISO 8601 start. Default: 30 days ago' },
|
|
2796
|
+
endDate: { type: 'string', description: 'ISO 8601 end. Default: now' },
|
|
2797
|
+
attribution: { type: 'string', enum: ['visitor', 'agent', 'group'], description: 'Default: visitor' },
|
|
2798
|
+
channels: { type: 'array', items: { type: 'string' }, description: 'Optional ConversationSource filter, e.g. ["Web","TwilioSms"]. Default: all channels' },
|
|
2799
|
+
},
|
|
2800
|
+
},
|
|
2801
|
+
},
|
|
2802
|
+
{
|
|
2803
|
+
name: 'report_performance_by_country_set_groups',
|
|
2804
|
+
description: 'Read or set the per-site mapping of Velaro groups (teams) to Performance by Country buckets. With no map, returns the site groups and the current mapping. With map ({"<teamId>": "US|Canada|Europe|any label"}) it REPLACES the mapping; unmapped groups report as Other. Only the site own groups are kept.',
|
|
2805
|
+
inputSchema: {
|
|
2806
|
+
type: 'object',
|
|
2807
|
+
properties: {
|
|
2808
|
+
map: { type: 'object', description: 'teamId to bucket label. Omit to just read.', additionalProperties: { type: 'string' } },
|
|
2809
|
+
},
|
|
2810
|
+
},
|
|
2811
|
+
},
|
|
2629
2812
|
{
|
|
2630
2813
|
name: 'report_campaign_performance',
|
|
2631
2814
|
description: 'Email campaign performance: open rate, click rate, delivery rate, complaint rate per campaign + aggregate KPIs.',
|
|
@@ -2906,7 +3089,9 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2906
3089
|
platform: { type: 'string', enum: ['v10', 'v20', 'admin'], description: 'Filter by platform' },
|
|
2907
3090
|
environment: { type: 'string', enum: ['Production', 'Staging', 'Development'] },
|
|
2908
3091
|
correlationId: { type: 'string', description: 'Exact correlation/trace ID filter' },
|
|
2909
|
-
last: { type: 'string', description: 'Time window: 30m, 2h, 1d (default: 2h)' },
|
|
3092
|
+
last: { type: 'string', description: 'Time window ending at `to`/now: 30m, 2h, 1d (default: 2h)' },
|
|
3093
|
+
from: { type: 'string', description: 'Window start: ISO timestamp (2026-10-20T00:00:00Z) or relative (3h). Overrides `last`.' },
|
|
3094
|
+
to: { type: 'string', description: 'Window end: ISO timestamp or relative (1h = 1h ago). Default: now.' },
|
|
2910
3095
|
take: { type: 'number', description: 'Max results (default: 50, max: 500)' },
|
|
2911
3096
|
regex: { type: 'boolean', description: 'Treat q/logger as a regex pattern (default: false)' },
|
|
2912
3097
|
},
|
|
@@ -3359,7 +3544,7 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
3359
3544
|
inputSchema: {
|
|
3360
3545
|
type: 'object',
|
|
3361
3546
|
properties: {
|
|
3362
|
-
report: { type: 'string', enum: ['program_breakdown', 'agent_scorecard', 'qa_coverage', 'coaching_queue', 'sentiment_breakdown', 'topic_radar', 'weekly_brief', 'weekly_brief_send_test', 'repeat_contact', 'demand_coverage', 'bot_containment', 'effort_index', 'promise_ledger', 'promise_dismiss', 'year_in_review'] },
|
|
3547
|
+
report: { type: 'string', enum: ['program_breakdown', 'agent_scorecard', 'qa_coverage', 'coaching_queue', 'sentiment_breakdown', 'topic_radar', 'weekly_brief', 'weekly_brief_send_test', 'repeat_contact', 'demand_coverage', 'bot_containment', 'effort_index', 'promise_ledger', 'promise_dismiss', 'new_agent_ramp', 'year_in_review'] },
|
|
3363
3548
|
from: { type: 'string', description: 'yyyy-MM-dd start (default: 30 days ago).' },
|
|
3364
3549
|
to: { type: 'string', description: 'yyyy-MM-dd end, inclusive (default: today).' },
|
|
3365
3550
|
groupBy: { type: 'string', description: 'program_breakdown: website|team|platform|device|entrypage. sentiment_breakdown: website|team|agent|theme.' },
|
|
@@ -3384,16 +3569,17 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
3384
3569
|
},
|
|
3385
3570
|
{
|
|
3386
3571
|
name: 'report_backfill',
|
|
3387
|
-
description: 'Backfill report data for the caller site. Preview by default (reports "would fill N", writes nothing); apply=true writes. Idempotent. type "csat" fills missing survey ratings (SurveySubmission.Rating) from existing data; it does NOT touch ticket CSAT. Site managers only; scoped to the caller site.',
|
|
3572
|
+
description: 'Backfill report data for the caller site. Preview by default (reports "would fill N", writes nothing); apply=true writes. Idempotent. type "csat" fills missing survey ratings (SurveySubmission.Rating) from existing data; it does NOT touch ticket CSAT. type "csat-demo" adds tagged demo CSAT assigned to the demo agents (allow-listed demo sites only); type "csat-demo-undo" over the same from/to deletes exactly those tagged rows and nothing else. type "history-classification-sample" tags 20 recent conversations and reports the line-of-business and intent mix it found (the proposed tags); type "history-classification" then tags and sentiment-scores past resolved conversations in the range (up to 100 per run, run again to continue) so Enterprise Reports and Year in Review show classified history instead of Unclassified / Insufficient data. For both history types, apply=true is QUEUED (the server returns 202 with a runId and runs it in the background; one run per site at a time, a repeat call returns the active run); pass status=true to read the latest run (queued, running, completed with result, or failed). Site managers only; scoped to the caller site.',
|
|
3388
3573
|
inputSchema: {
|
|
3389
3574
|
type: 'object',
|
|
3390
3575
|
properties: {
|
|
3391
|
-
type: { type: 'string', description: 'Backfill type,
|
|
3392
|
-
from: { type: 'string', description: 'yyyy-MM-dd start.' },
|
|
3393
|
-
to: { type: 'string', description: 'yyyy-MM-dd end.' },
|
|
3394
|
-
apply: { type: 'boolean', description: 'true = write. Default false (preview only).' },
|
|
3576
|
+
type: { type: 'string', description: 'Backfill type: csat, csat-demo, csat-demo-undo, history-classification-sample or history-classification.' },
|
|
3577
|
+
from: { type: 'string', description: 'yyyy-MM-dd start (required unless status=true).' },
|
|
3578
|
+
to: { type: 'string', description: 'yyyy-MM-dd end (required unless status=true).' },
|
|
3579
|
+
apply: { type: 'boolean', description: 'true = write. Default false (preview only). For history types this queues a background run.' },
|
|
3580
|
+
status: { type: 'boolean', description: 'true = return the latest queued/running/finished apply run for this type instead of starting anything.' },
|
|
3395
3581
|
},
|
|
3396
|
-
required: ['type'
|
|
3582
|
+
required: ['type'],
|
|
3397
3583
|
},
|
|
3398
3584
|
},
|
|
3399
3585
|
// -- Staff: schedule adherence by site + role (SupportTools-style: explicit siteId, never auth-derived) --
|
|
@@ -3561,6 +3747,17 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
3561
3747
|
required: ['siteId'],
|
|
3562
3748
|
},
|
|
3563
3749
|
},
|
|
3750
|
+
{
|
|
3751
|
+
name: 'avatar_session_readiness',
|
|
3752
|
+
description: 'Velaro staff diagnostic: runs the real avatar session gates for a site (plan, config, provider key, billing authority, concurrency) without claiming a slot or calling the provider, so it costs nothing. Use when a visitor avatar session returns 503 or will not start. Calls velaro-messaging GET /avatar/diagnostics/session-readiness (staff only).',
|
|
3753
|
+
inputSchema: {
|
|
3754
|
+
type: 'object',
|
|
3755
|
+
properties: {
|
|
3756
|
+
siteId: { type: 'number', description: 'Site ID to check' },
|
|
3757
|
+
},
|
|
3758
|
+
required: ['siteId'],
|
|
3759
|
+
},
|
|
3760
|
+
},
|
|
3564
3761
|
{
|
|
3565
3762
|
name: 'avatar_set_config',
|
|
3566
3763
|
description: 'Set up or update the AI Video Avatar for a customer site (partial update — only send the fields you want to change). Velaro admin only.',
|
|
@@ -3573,11 +3770,13 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
3573
3770
|
avatarId: { type: 'string', description: 'Avatar ID. For provider "velaro" this is a Velaro Avatar library slug. Other providers: their own face/avatar ID. Omit for provider default.' },
|
|
3574
3771
|
voiceId: { type: 'string', description: 'ElevenLabs voice ID override. Omit to use the site default.' },
|
|
3575
3772
|
showCallMeOption: { type: 'boolean', description: 'Show the "Call me instead" option (requires EnableClickToCall).' },
|
|
3773
|
+
enabledOnAgentTransfer: { type: 'boolean', description: 'Keep the avatar running after a human agent joins the conversation (default off).' },
|
|
3576
3774
|
callbackConfirmWindowMinutes: { type: 'number', description: 'Minutes to confirm a callback request before it expires. Default 10.' },
|
|
3577
3775
|
displayName: { type: 'string', description: 'Visitor-facing avatar name shown on the badge (e.g. "Moshky"). Empty string resets to "AI".' },
|
|
3578
3776
|
showBadge: { type: 'boolean', description: 'Show the name badge over the avatar video. Default true.' },
|
|
3579
3777
|
sizePx: { type: 'number', description: 'Max avatar panel width in px (160-640, square). Default 360.' },
|
|
3580
3778
|
allowVisitorCollapse: { type: 'boolean', description: 'Let visitors hide or collapse the avatar panel (up/down arrow and X). Default true.' },
|
|
3779
|
+
heyGenEnabled: { type: 'boolean', description: 'Grant or revoke the HeyGen provider entitlement (EnableAvatarHeyGen) for this site. Off by default; HeyGen is hidden from the customer UI until granted.' },
|
|
3581
3780
|
motionLevel: { type: 'string', enum: ['', 'off', 'subtle', 'natural', 'expressive'], description: 'Body motion preset layered over the lip-sync (shoulders, breathing, head, brows). Empty string resets to the platform default (natural). Visitors with reduced-motion enabled always get a still avatar.' },
|
|
3582
3781
|
motionIntensityPercent: { type: 'integer', minimum: 25, maximum: 150, description: 'Body motion strength, whole percent 25-150 (default 100); multiplies every motion amplitude.' },
|
|
3583
3782
|
maxAvatarMinutesPerMonth: { type: 'number', description: 'Monthly avatar video minutes cap for the site. Whole number 1-100000; never unlimited (0/negative rejected).' },
|
|
@@ -3587,6 +3786,51 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
3587
3786
|
},
|
|
3588
3787
|
},
|
|
3589
3788
|
|
|
3789
|
+
// ── Apple Pay pre-canned (preset) orders ───────────────────────────────────
|
|
3790
|
+
{
|
|
3791
|
+
name: 'apple_pay_preset_list',
|
|
3792
|
+
description: 'List the Apple Pay pre-canned (preset) orders for the authenticated site: name, line items (label + amount), currency, server-computed total, active flag, and whether only managers may change amounts at send time. Use the id or name with the AI skill (preset_order_id / preset_order_name) or the composer.',
|
|
3793
|
+
inputSchema: { type: 'object', properties: { activeOnly: { type: 'boolean', description: 'Only return active presets (default false).' } } },
|
|
3794
|
+
},
|
|
3795
|
+
{
|
|
3796
|
+
name: 'apple_pay_preset_create',
|
|
3797
|
+
description: 'Create an Apple Pay pre-canned order (up to 20 line items). The total is computed by the server from the line items; never send one.',
|
|
3798
|
+
inputSchema: {
|
|
3799
|
+
type: 'object',
|
|
3800
|
+
properties: {
|
|
3801
|
+
name: { type: 'string', description: 'Preset name, e.g. "Haircut and tip".' },
|
|
3802
|
+
description: { type: 'string', description: 'Optional description.' },
|
|
3803
|
+
currencyCode: { type: 'string', description: 'ISO currency code, default USD.' },
|
|
3804
|
+
lineItems: { type: 'array', maxItems: 20, description: 'Line items.', items: { type: 'object', properties: { label: { type: 'string' }, amount: { type: 'number' } }, required: ['label', 'amount'] } },
|
|
3805
|
+
isActive: { type: 'boolean', description: 'Available to agents and the AI (default true).' },
|
|
3806
|
+
overrideRestrictedToManagers: { type: 'boolean', description: 'Only managers can change amounts at send time.' },
|
|
3807
|
+
},
|
|
3808
|
+
required: ['name', 'lineItems'],
|
|
3809
|
+
},
|
|
3810
|
+
},
|
|
3811
|
+
{
|
|
3812
|
+
name: 'apple_pay_preset_update',
|
|
3813
|
+
description: 'Update an Apple Pay preset order. Only the fields you pass change; passing lineItems replaces the whole list.',
|
|
3814
|
+
inputSchema: {
|
|
3815
|
+
type: 'object',
|
|
3816
|
+
properties: {
|
|
3817
|
+
id: { type: 'number', description: 'Preset order id (from apple_pay_preset_list).' },
|
|
3818
|
+
name: { type: 'string' },
|
|
3819
|
+
description: { type: 'string' },
|
|
3820
|
+
currencyCode: { type: 'string' },
|
|
3821
|
+
lineItems: { type: 'array', maxItems: 20, items: { type: 'object', properties: { label: { type: 'string' }, amount: { type: 'number' } }, required: ['label', 'amount'] } },
|
|
3822
|
+
isActive: { type: 'boolean' },
|
|
3823
|
+
overrideRestrictedToManagers: { type: 'boolean' },
|
|
3824
|
+
},
|
|
3825
|
+
required: ['id'],
|
|
3826
|
+
},
|
|
3827
|
+
},
|
|
3828
|
+
{
|
|
3829
|
+
name: 'apple_pay_preset_delete',
|
|
3830
|
+
description: 'Delete an Apple Pay preset order by id.',
|
|
3831
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number', description: 'Preset order id.' } }, required: ['id'] },
|
|
3832
|
+
},
|
|
3833
|
+
|
|
3590
3834
|
// ── Velaro Avatar (first-party provider) — library, personalities, site/group inheritance ──
|
|
3591
3835
|
{
|
|
3592
3836
|
name: 'avatar_list_library',
|
|
@@ -3675,6 +3919,31 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
3675
3919
|
description: 'Reset ALL group and workflow overrides for the calling site back to the site default.',
|
|
3676
3920
|
inputSchema: { type: 'object', properties: {} },
|
|
3677
3921
|
},
|
|
3922
|
+
{
|
|
3923
|
+
name: 'avatar_set_workflow_switch',
|
|
3924
|
+
description: 'Switch the AI avatar on, off, or back to the site rules for ONE workflow of the calling site. mode "on" shows the avatar, "off" hides it, "rules" clears the switch so the site rules decide. Only the switch changes; the rest of the workflow override is kept. Velaro admin or site auth.',
|
|
3925
|
+
inputSchema: {
|
|
3926
|
+
type: 'object',
|
|
3927
|
+
properties: {
|
|
3928
|
+
workflowId: { type: 'number', description: 'Workflow id (must belong to the calling site).' },
|
|
3929
|
+
mode: { type: 'string', enum: ['on', 'off', 'rules'], description: 'on | off | rules' },
|
|
3930
|
+
},
|
|
3931
|
+
required: ['workflowId', 'mode'],
|
|
3932
|
+
},
|
|
3933
|
+
},
|
|
3934
|
+
{
|
|
3935
|
+
name: 'avatar_bulk_set_switch',
|
|
3936
|
+
description: 'Set the AI avatar switch (on | off | rules) for many workflows and/or teams of the calling site in one call. Select rows with workflowIds and/or groupIds (at least one; max 500 rows). Each changed row is audited. Velaro admin or site auth.',
|
|
3937
|
+
inputSchema: {
|
|
3938
|
+
type: 'object',
|
|
3939
|
+
properties: {
|
|
3940
|
+
mode: { type: 'string', enum: ['on', 'off', 'rules'], description: 'on | off | rules' },
|
|
3941
|
+
workflowIds: { type: 'array', items: { type: 'number' }, description: 'Workflow ids of the calling site.' },
|
|
3942
|
+
groupIds: { type: 'array', items: { type: 'number' }, description: 'Team (group) ids of the calling site.' },
|
|
3943
|
+
},
|
|
3944
|
+
required: ['mode'],
|
|
3945
|
+
},
|
|
3946
|
+
},
|
|
3678
3947
|
|
|
3679
3948
|
// ── Calendly integration management (superadmin) ──────────────────────────
|
|
3680
3949
|
{
|
|
@@ -4028,6 +4297,26 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
4028
4297
|
required: ['shop'],
|
|
4029
4298
|
},
|
|
4030
4299
|
},
|
|
4300
|
+
{
|
|
4301
|
+
name: 'shopify_get_write_policy',
|
|
4302
|
+
description: 'Get the Shopify write-tier policy for the site: enableShopifyWrite (master toggle for the 11 Shopify write skills; read skills are unaffected), requireConfirmation, and the effective caps maxRefundAmount, maxDiscountPercent, maxDiscountFixedAmount (defaults filled in: 250 / 20 / 50). Site is resolved server-side from the caller\'s auth context. Lives in velaro-messaging (GET /api/Shopify/WritePolicy) - requires VELARO_MESSAGING_API.',
|
|
4303
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
4304
|
+
},
|
|
4305
|
+
{
|
|
4306
|
+
name: 'shopify_set_write_policy',
|
|
4307
|
+
description: 'Update the Shopify write-tier policy for the site (apply-if-present, partial: omitted fields keep their stored value). Pass null for a cap to return it to the default. enableShopifyWrite=false hides the 11 Shopify write skills (refund, discount, etc.) without touching the read skills. Validated server-side. Lives in velaro-messaging (PUT /api/Shopify/WritePolicy) - requires VELARO_MESSAGING_API.',
|
|
4308
|
+
inputSchema: {
|
|
4309
|
+
type: 'object',
|
|
4310
|
+
properties: {
|
|
4311
|
+
enableShopifyWrite: { type: 'boolean', description: 'Master toggle for the Shopify write skills.' },
|
|
4312
|
+
requireConfirmation: { type: 'boolean', description: 'Require customer confirmation before a write skill executes.' },
|
|
4313
|
+
maxRefundAmount: { type: ['number', 'null'], description: 'Max refund amount per call. null = default (250).' },
|
|
4314
|
+
maxDiscountPercent: { type: ['number', 'null'], description: 'Max percentage discount. null = default (20).' },
|
|
4315
|
+
maxDiscountFixedAmount: { type: ['number', 'null'], description: 'Max fixed-amount discount. null = default (50).' },
|
|
4316
|
+
},
|
|
4317
|
+
required: [],
|
|
4318
|
+
},
|
|
4319
|
+
},
|
|
4031
4320
|
{
|
|
4032
4321
|
name: 'appointments_manage',
|
|
4033
4322
|
description: 'Velaro Appointments — reschedules or cancels an existing appointment that was booked via appointments_book. Requires the provider slug and event_id from the original booking. Set intent="reschedule" with a new start (and optional duration) to move it, or intent="cancel" to remove it. Cancel is destructive and is additionally gated by the EnableDestructiveAiSkills subscription flag. Marketing page: /appointments.',
|
|
@@ -4220,8 +4509,9 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
4220
4509
|
properties: {
|
|
4221
4510
|
storeUrl: { type: 'string', description: 'Magento store base URL, e.g. "https://mystore.com"' },
|
|
4222
4511
|
accessToken: { type: 'string', description: 'Magento REST API bearer token (Admin Integration token)' },
|
|
4512
|
+
configId: { type: 'number', description: 'Test an already-connected store with its stored token instead (storeUrl/accessToken then not needed)' },
|
|
4223
4513
|
},
|
|
4224
|
-
required: [
|
|
4514
|
+
required: [],
|
|
4225
4515
|
},
|
|
4226
4516
|
},
|
|
4227
4517
|
{
|
|
@@ -4296,6 +4586,18 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
4296
4586
|
},
|
|
4297
4587
|
|
|
4298
4588
|
// ── Business actions + SAP (one definition, many surfaces) ─────────────────────
|
|
4589
|
+
{
|
|
4590
|
+
name: 'salesforce_diagnose',
|
|
4591
|
+
description: 'Run the Salesforce live diagnostic for a site: configuration completeness, OAuth token acquisition and, when deep, a read-only SOQL probe per object the skills use (contacts, leads, accounts, tasks, notes, email history, opportunities, cases, products, orders) with its package tier (Core/Sales/Service/Commerce). Never writes; write capabilities are never reported as verified; secrets are never returned. Calls messaging GET diagnostics/test-salesforce?siteId=<id>&deep=<bool>. Velaro staff only.',
|
|
4592
|
+
inputSchema: {
|
|
4593
|
+
type: 'object',
|
|
4594
|
+
properties: {
|
|
4595
|
+
siteId: { type: 'number', description: 'Site to diagnose (e.g. 1032)' },
|
|
4596
|
+
deep: { type: 'boolean', description: 'Probe every object (default true). false = configuration + token only.' },
|
|
4597
|
+
},
|
|
4598
|
+
required: ['siteId'],
|
|
4599
|
+
},
|
|
4600
|
+
},
|
|
4299
4601
|
{
|
|
4300
4602
|
name: 'sap_diagnose',
|
|
4301
4603
|
description: 'Run the SAP live diagnostic for a site: configuration completeness, authentication (real HTTP status) and, when deep, a per-capability read-only probe compared with what the capability matrix documents (S/4HANA on-prem/private/public, Business One, ECC via gateway). Never writes; a write capability is never reported as verified; secrets are never returned. Calls messaging GET diagnostics/test-sap?siteId=<id>&deep=<bool>. Velaro staff only.',
|
|
@@ -4404,6 +4706,35 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
4404
4706
|
},
|
|
4405
4707
|
},
|
|
4406
4708
|
|
|
4709
|
+
{
|
|
4710
|
+
name: 'netsuite_suiteapp_download',
|
|
4711
|
+
description: 'Download the NetSuite screen-pop SuiteApp (SDF project zip) to a local file. With includeKey=true (default) a new crm-embed-only key is minted and baked into the zip, so NO NetSuite API Secret and no key paste is needed. With includeKey=false the zip is key-less and the admin must create a NetSuite API Secret: type ID velaro_api_key in the form (NetSuite adds the prefix, giving custsecret_velaro_api_key). Lives in velaro-messaging - requires VELARO_MESSAGING_API.',
|
|
4712
|
+
inputSchema: { type: 'object', properties: { includeKey: { type: 'boolean', description: 'Default true.' }, outputPath: { type: 'string', description: 'Local file path to write (default: temp dir).' } }, required: [] },
|
|
4713
|
+
},
|
|
4714
|
+
{
|
|
4715
|
+
name: 'netsuite_config_delete',
|
|
4716
|
+
description: 'Delete a NetSuite connection (config) from the site. Removes the stored credentials; mappings/workflows referencing it will stop working. Destructive. Lives in velaro-messaging (DELETE Netsuite/Integrations/{id}) - requires VELARO_MESSAGING_API.',
|
|
4717
|
+
inputSchema: {
|
|
4718
|
+
type: 'object',
|
|
4719
|
+
properties: { configId: { type: 'number', description: 'NetSuite config ID (see netsuite_list_configs).' } },
|
|
4720
|
+
required: ['configId'],
|
|
4721
|
+
},
|
|
4722
|
+
},
|
|
4723
|
+
{
|
|
4724
|
+
name: 'netsuite_m2m_clear_legacy',
|
|
4725
|
+
description: 'Disconnect the old consumer-key/token credentials from a NetSuite config that already runs on a saved M2M certificate (explicit, never automatic; the connection row, mappings and workflows are untouched). Fails if M2M is not saved/active. Lives in velaro-messaging (POST NetSuite/M2M/ClearLegacy) - requires VELARO_MESSAGING_API.',
|
|
4726
|
+
inputSchema: {
|
|
4727
|
+
type: 'object',
|
|
4728
|
+
properties: { configId: { type: 'number', description: 'NetSuite config ID (see netsuite_list_configs).' } },
|
|
4729
|
+
required: ['configId'],
|
|
4730
|
+
},
|
|
4731
|
+
},
|
|
4732
|
+
{
|
|
4733
|
+
name: 'netsuite_embed_status',
|
|
4734
|
+
description: 'NetSuite SuiteApp call-pop (CRM embed) status for the site: is a crm_embed API key set, and the last heartbeat (when it last polled, connected or not) per key. Lives in velaro-messaging (GET api/v1/CrmEmbed/embed-status; fields hasEmbedKey, connected, lastPolledAt) - requires VELARO_MESSAGING_API.',
|
|
4735
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
4736
|
+
},
|
|
4737
|
+
|
|
4407
4738
|
{
|
|
4408
4739
|
name: 'netsuite_m2m_init',
|
|
4409
4740
|
description: 'Start NetSuite machine-to-machine (OAuth 2.0 Client Credentials) setup: generates the Velaro public certificate PEM to upload in NetSuite (Setup > Integration > Manage Authentication > OAuth 2.0 Client Credentials (M2M) Setup) and returns the steps. Omit configId for a new connection (company then required). Lives in velaro-messaging - requires VELARO_MESSAGING_API.',
|
|
@@ -4807,6 +5138,47 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
4807
5138
|
required: ['id'],
|
|
4808
5139
|
},
|
|
4809
5140
|
},
|
|
5141
|
+
{
|
|
5142
|
+
name: 'billing_notify_settings_get',
|
|
5143
|
+
description: 'Billing notifications: the global settings (receipts.card, receipts.ach, receipts.other, invoices.bcc, digest.recipients). Staff only.',
|
|
5144
|
+
inputSchema: { type: 'object', properties: {} },
|
|
5145
|
+
},
|
|
5146
|
+
{
|
|
5147
|
+
name: 'billing_notify_setting_set',
|
|
5148
|
+
description: 'Billing notifications: set one global setting. receipts.card|receipts.ach|receipts.other = never|send; invoices.bcc = comma-separated emails (blank = none); digest.recipients = comma-separated emails. Staff only.',
|
|
5149
|
+
inputSchema: {
|
|
5150
|
+
type: 'object',
|
|
5151
|
+
properties: {
|
|
5152
|
+
key: { type: 'string', enum: ['receipts.card', 'receipts.ach', 'receipts.other', 'invoices.bcc', 'digest.recipients'], description: 'Setting key' },
|
|
5153
|
+
value: { type: 'string', description: 'New value' },
|
|
5154
|
+
},
|
|
5155
|
+
required: ['key', 'value'],
|
|
5156
|
+
},
|
|
5157
|
+
},
|
|
5158
|
+
{
|
|
5159
|
+
name: 'billing_receipt_overrides_set',
|
|
5160
|
+
description: 'Billing notifications: set receipt overrides on one subscription (never|send per channel). Channels you omit keep their current override. Staff only.',
|
|
5161
|
+
inputSchema: {
|
|
5162
|
+
type: 'object',
|
|
5163
|
+
properties: {
|
|
5164
|
+
subscriptionId: { type: 'number', description: 'Billing subscription ID' },
|
|
5165
|
+
card: { type: 'string', enum: ['never', 'send'], description: 'Card receipts override' },
|
|
5166
|
+
ach: { type: 'string', enum: ['never', 'send'], description: 'ACH receipts override' },
|
|
5167
|
+
other: { type: 'string', enum: ['never', 'send'], description: 'Other (check, cash, wire) receipts override' },
|
|
5168
|
+
},
|
|
5169
|
+
required: ['subscriptionId'],
|
|
5170
|
+
},
|
|
5171
|
+
},
|
|
5172
|
+
{
|
|
5173
|
+
name: 'billing_digest_send_now',
|
|
5174
|
+
description: 'Billing notifications: send the daily billing digest now to digest.recipients (same email the 7am run sends). Staff only.',
|
|
5175
|
+
inputSchema: { type: 'object', properties: {} },
|
|
5176
|
+
},
|
|
5177
|
+
{
|
|
5178
|
+
name: 'billing_receipts_run_now',
|
|
5179
|
+
description: 'Billing notifications: decide pending payment receipts now instead of at :05. Returns the counts (sent, skipped, failed). Staff only.',
|
|
5180
|
+
inputSchema: { type: 'object', properties: {} },
|
|
5181
|
+
},
|
|
4810
5182
|
{
|
|
4811
5183
|
name: 'billing_plan_status',
|
|
4812
5184
|
description: 'Returns the current subscription state for the authenticated site: active Enable* feature flags, licensed seat count, self-serve seats toggle, and recent Stripe payment session history. Use this to answer "what plan are they on?" or "which features are active?"',
|
|
@@ -4897,6 +5269,11 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
4897
5269
|
description: 'Show the Enterprise Reports package (enterprise-internal, default OFF): every item with its feature key, what it builds on, and, when siteId is given, whether it is ON for that site. Superadmin only.',
|
|
4898
5270
|
inputSchema: { type: 'object', properties: { siteId: { type: 'number', description: 'Optional site ID to show which items are on' } } },
|
|
4899
5271
|
},
|
|
5272
|
+
{
|
|
5273
|
+
name: 'entitlement_report_packages',
|
|
5274
|
+
description: 'Show the five report category packages (Conversations & Channels, Workforce & Operations, Customer Experience, AI & Intelligence, Business & Executive) nested under the top Enterprise Reports package (default OFF): every report with its package slug and entitlement key and, when siteId is given, whether each report and each whole category is ON for that site. Superadmin only.',
|
|
5275
|
+
inputSchema: { type: 'object', properties: { siteId: { type: 'number', description: 'Optional site ID to show which reports are on' } } },
|
|
5276
|
+
},
|
|
4900
5277
|
{
|
|
4901
5278
|
name: 'entitlement_set_override',
|
|
4902
5279
|
description: 'Set a per-site feature override — enable or disable a specific feature for one site, independent of their plan. Superadmin only. Use entitlement_list_features to find valid featureKey values.',
|
|
@@ -4911,6 +5288,43 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
4911
5288
|
required: ['siteId', 'featureKey', 'value'],
|
|
4912
5289
|
},
|
|
4913
5290
|
},
|
|
5291
|
+
{
|
|
5292
|
+
name: 'entitlement_trial_get',
|
|
5293
|
+
description: 'Trial governance view for one site: trial status, days left, and the grant ledger (active/held/purchased/revoked). Superadmin only.',
|
|
5294
|
+
inputSchema: { type: 'object', properties: { siteId: { type: 'number', description: 'Site ID.' } }, required: ['siteId'] },
|
|
5295
|
+
},
|
|
5296
|
+
{
|
|
5297
|
+
name: 'entitlement_trial_recommend',
|
|
5298
|
+
description: 'Deterministic package recommendation for a trial site, from its recorded trial usage. Returns customer-safe text plus a staff-only rationale. Superadmin only.',
|
|
5299
|
+
inputSchema: { type: 'object', properties: { siteId: { type: 'number', description: 'Site ID.' } }, required: ['siteId'] },
|
|
5300
|
+
},
|
|
5301
|
+
{
|
|
5302
|
+
name: 'entitlement_trial_grant',
|
|
5303
|
+
description: 'Grant one feature to a trial site and record it in the trial ledger (who, when, why). Superadmin only; the site must be in a trial.',
|
|
5304
|
+
inputSchema: {
|
|
5305
|
+
type: 'object',
|
|
5306
|
+
properties: {
|
|
5307
|
+
siteId: { type: 'number', description: 'Site ID.' },
|
|
5308
|
+
featureKey: { type: 'string', description: 'Feature key, e.g. "EnableIvr".' },
|
|
5309
|
+
reason: { type: 'string', description: 'Why this feature is granted (required, recorded).' },
|
|
5310
|
+
},
|
|
5311
|
+
required: ['siteId', 'featureKey', 'reason'],
|
|
5312
|
+
},
|
|
5313
|
+
},
|
|
5314
|
+
{
|
|
5315
|
+
name: 'entitlement_trial_set_limit',
|
|
5316
|
+
description: 'Set a per-site trial cap (MaxIndexedChunkTotal, MaxKbArticles, MaxKnowledgeDocuments, MaxScraperPagesPerMonth). Whole number >= 0; audited. Superadmin only.',
|
|
5317
|
+
inputSchema: {
|
|
5318
|
+
type: 'object',
|
|
5319
|
+
properties: {
|
|
5320
|
+
siteId: { type: 'number', description: 'Site ID.' },
|
|
5321
|
+
key: { type: 'string', description: 'Cap name.' },
|
|
5322
|
+
value: { type: 'number', description: 'Whole number >= 0.' },
|
|
5323
|
+
reason: { type: 'string', description: 'Why (required, audited).' },
|
|
5324
|
+
},
|
|
5325
|
+
required: ['siteId', 'key', 'value', 'reason'],
|
|
5326
|
+
},
|
|
5327
|
+
},
|
|
4914
5328
|
{
|
|
4915
5329
|
name: 'entitlement_remove_override',
|
|
4916
5330
|
description: 'Remove a per-site feature override, reverting the site to its plan default. Superadmin only.',
|
|
@@ -5091,11 +5505,11 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
5091
5505
|
},
|
|
5092
5506
|
{
|
|
5093
5507
|
name: 'focus_update_triage_rules',
|
|
5094
|
-
description: 'Enable or disable one or more of the 5 fixed AI triage labels (urgent, needs_reply, offer, fyi, waiting) for this site. Apply-if-present -- only labels included are changed. Rejects any label name outside the fixed set. Disabling all 5 fails safe back to all-enabled server-side rather than breaking triage. Optionally set triageMode: "economy" classifies obvious messages (auto-replies, clear urgency) without an AI call, lowering AI usage; "off"
|
|
5508
|
+
description: 'Enable or disable one or more of the 5 fixed AI triage labels (urgent, needs_reply, offer, fyi, waiting) for this site. Apply-if-present -- only labels included are changed. Rejects any label name outside the fixed set. Disabling all 5 fails safe back to all-enabled server-side rather than breaking triage. Optionally set triageMode: "economy" classifies obvious messages (auto-replies, clear urgency) without an AI call, lowering AI usage; "off" makes this site always use the AI call (platform default is economy). Unspecified fields are preserved.',
|
|
5095
5509
|
inputSchema: {
|
|
5096
5510
|
type: 'object',
|
|
5097
5511
|
properties: {
|
|
5098
|
-
triageMode: { type: 'string', enum: ['off', 'economy'], description: 'Optional. "economy" skips the AI call for obvious messages; "off"
|
|
5512
|
+
triageMode: { type: 'string', enum: ['off', 'economy'], description: 'Optional. "economy" skips the AI call for obvious messages; "off" makes this site always use the AI call. Unset inherits the platform default, which is economy.' },
|
|
5099
5513
|
labels: {
|
|
5100
5514
|
type: 'array',
|
|
5101
5515
|
description: 'Labels to update.',
|
|
@@ -5201,7 +5615,7 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
5201
5615
|
type: 'object',
|
|
5202
5616
|
properties: {
|
|
5203
5617
|
start: { type: 'string', description: 'ISO 8601 start of range.' },
|
|
5204
|
-
end: { type: 'string', description: '
|
|
5618
|
+
end: { type: 'string', description: 'End of range. A plain YYYY-MM-DD date includes that whole day (the server end is exclusive, so it is sent as the start of the next day); a full ISO 8601 datetime is an exact exclusive instant. Must be at most 90 days after start.' },
|
|
5205
5619
|
},
|
|
5206
5620
|
required: ['start', 'end'],
|
|
5207
5621
|
},
|
|
@@ -5331,6 +5745,28 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
5331
5745
|
},
|
|
5332
5746
|
},
|
|
5333
5747
|
|
|
5748
|
+
// -- Apple Pay display settings --
|
|
5749
|
+
// Backend: velaro-messaging ApplePayConfigsController (GET/POST ApplePayConfig, PUT ApplePayConfig/{id}).
|
|
5750
|
+
// Display + test mode only; merchant id / certificate (which affect the charge) are intentionally not settable here.
|
|
5751
|
+
{
|
|
5752
|
+
name: 'apple_pay_get_config',
|
|
5753
|
+
description: 'Get this site\'s Apple Pay settings: enabled, test mode (sandbox: Velaro Stripe test account, no real money, real Wallet cards decline), business name shown on the Apple Pay sheet, whether a custom payment-bubble logo is set, and whether the site uses Velaro\'s platform merchant. id is 0 when the site has no row yet.',
|
|
5754
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
5755
|
+
},
|
|
5756
|
+
{
|
|
5757
|
+
name: 'apple_pay_update_config',
|
|
5758
|
+
description: 'Create or update this site\'s Apple Pay DISPLAY settings and test mode. Apply-if-present: omit a field to leave it unchanged. These never affect the charge (the merchant id and certificate do, and are managed by secure Velaro admin provisioning). Creates the row when the site has none.',
|
|
5759
|
+
inputSchema: {
|
|
5760
|
+
type: 'object',
|
|
5761
|
+
properties: {
|
|
5762
|
+
merchantDisplayName: { type: 'string', description: 'Business name shown on the Apple Pay sheet' },
|
|
5763
|
+
isTestMode: { type: 'boolean', description: 'Sandbox mode: Velaro Stripe test account, no real money moves, real Wallet cards decline' },
|
|
5764
|
+
bubbleLogoBase64: { type: 'string', description: 'PNG/JPEG base64 (max 500 KB) for the payment bubble. Empty string clears it back to the default image.' },
|
|
5765
|
+
},
|
|
5766
|
+
required: [],
|
|
5767
|
+
},
|
|
5768
|
+
},
|
|
5769
|
+
|
|
5334
5770
|
// -- Recruiting Bot --
|
|
5335
5771
|
// Backend: server/Velaro.Messaging/Controllers/RecruitingController.cs.
|
|
5336
5772
|
// [EnableRecruitingBot] gated server-side; siteId always derives from the
|
|
@@ -6036,6 +6472,14 @@ function parseBusinessHours(input) {
|
|
|
6036
6472
|
|
|
6037
6473
|
// ── Tool handlers ─────────────────────────────────────────────────────────────
|
|
6038
6474
|
|
|
6475
|
+
// Report end params are exclusive server-side: a plain YYYY-MM-DD end means "through that whole
|
|
6476
|
+
// day", so send the start of the next day (mirrors cli/lib/report-range.js exclusiveEndParam).
|
|
6477
|
+
function exclusiveEnd(raw) {
|
|
6478
|
+
if (raw == null || raw === '') return raw;
|
|
6479
|
+
const d = new Date(raw);
|
|
6480
|
+
if (Number.isNaN(d.getTime()) || !/^\d{4}-\d{2}-\d{2}$/.test(String(raw).trim())) return raw;
|
|
6481
|
+
return new Date(d.getTime() + 24 * 60 * 60 * 1000).toISOString();
|
|
6482
|
+
}
|
|
6039
6483
|
// -- Agent inactivity chain helpers (velaro-messaging PR #1471, v20 ONLY) ------------------------
|
|
6040
6484
|
// Terminal action ints are persisted by messaging (append-only enum). The API returns/accepts the NAME.
|
|
6041
6485
|
const TERMINAL_ACTION_NAMES = ['Survey','CreateTicket','SurveyAndCreateTicket','Queue','OfflineMessage','NoOp','EndChat','NotifyManager','RunWorkflow','ReturnToBot'];
|
|
@@ -6166,7 +6610,9 @@ async function handleTool(name, args) {
|
|
|
6166
6610
|
}
|
|
6167
6611
|
|
|
6168
6612
|
case 'notifications_get': {
|
|
6169
|
-
|
|
6613
|
+
const base = await messagingGet('/NotificationSettings');
|
|
6614
|
+
const routing = await messagingGet('/api/v1/alert-routing').catch(() => null);
|
|
6615
|
+
return JSON.stringify({ ...base, alertRouting: routing?.data ?? null }, null, 2);
|
|
6170
6616
|
}
|
|
6171
6617
|
|
|
6172
6618
|
case 'notifications_set': {
|
|
@@ -6180,7 +6626,47 @@ async function handleTool(name, args) {
|
|
|
6180
6626
|
if (args[f] !== undefined) current[f] = args[f];
|
|
6181
6627
|
}
|
|
6182
6628
|
await messagingPost('/NotificationSettings', current);
|
|
6183
|
-
|
|
6629
|
+
if (args.alertRouting && typeof args.alertRouting === 'object') {
|
|
6630
|
+
await messagingPut('/api/v1/alert-routing', args.alertRouting);
|
|
6631
|
+
}
|
|
6632
|
+
const routing = await messagingGet('/api/v1/alert-routing').catch(() => null);
|
|
6633
|
+
return JSON.stringify({ ...(await messagingGet('/NotificationSettings')), alertRouting: routing?.data ?? null }, null, 2);
|
|
6634
|
+
}
|
|
6635
|
+
|
|
6636
|
+
case 'signature_policy_get': {
|
|
6637
|
+
return JSON.stringify(await messagingGet('/Signatures/policy'), null, 2);
|
|
6638
|
+
}
|
|
6639
|
+
|
|
6640
|
+
case 'signature_policy_set': {
|
|
6641
|
+
// PUT replaces the row and silently turns an unknown mode into "follow company-wide", so validate and merge here.
|
|
6642
|
+
// Same rules as cli/lib/signature-policy.js (keep the two in step).
|
|
6643
|
+
const modes = { 'locked': 'admin_locked', 'company-default': 'company_default', 'personal-only': 'personal_only' };
|
|
6644
|
+
const teamId = args.teamId === undefined || args.teamId === null ? 0 : args.teamId;
|
|
6645
|
+
if (!Number.isInteger(teamId) || teamId < 0) throw new Error('teamId must be 0 (company-wide) or a team id');
|
|
6646
|
+
const data = await messagingGet('/Signatures/policy');
|
|
6647
|
+
const cur = (Array.isArray(data?.policies) ? data.policies : []).find((p) => p.teamId === teamId) ?? {};
|
|
6648
|
+
const body = {
|
|
6649
|
+
teamId,
|
|
6650
|
+
mode: cur.mode ?? null,
|
|
6651
|
+
corporateSignature: cur.corporateSignature || null,
|
|
6652
|
+
disclaimerEnabled: cur.disclaimerEnabled ?? null,
|
|
6653
|
+
disclaimerText: cur.disclaimerText || null,
|
|
6654
|
+
};
|
|
6655
|
+
if (args.mode !== undefined) {
|
|
6656
|
+
if (args.mode === null || args.mode === 'default') body.mode = null;
|
|
6657
|
+
else if (modes[args.mode]) body.mode = modes[args.mode];
|
|
6658
|
+
else throw new Error('mode must be locked, company-default, personal-only or default');
|
|
6659
|
+
}
|
|
6660
|
+
if (args.corporateSignature !== undefined) body.corporateSignature = args.corporateSignature || null;
|
|
6661
|
+
if (args.disclaimerEnabled !== undefined) {
|
|
6662
|
+
if (args.disclaimerEnabled !== null && typeof args.disclaimerEnabled !== 'boolean') throw new Error('disclaimerEnabled must be true, false or null');
|
|
6663
|
+
body.disclaimerEnabled = args.disclaimerEnabled;
|
|
6664
|
+
}
|
|
6665
|
+
if (args.disclaimerText !== undefined) body.disclaimerText = args.disclaimerText || null;
|
|
6666
|
+
if (body.corporateSignature && body.corporateSignature.length > 4000) throw new Error('corporateSignature is limited to 4000 characters');
|
|
6667
|
+
if (body.disclaimerText && body.disclaimerText.length > 2000) throw new Error('disclaimerText is limited to 2000 characters');
|
|
6668
|
+
await messagingPut('/Signatures/policy', body);
|
|
6669
|
+
return JSON.stringify(await messagingGet('/Signatures/policy'), null, 2);
|
|
6184
6670
|
}
|
|
6185
6671
|
|
|
6186
6672
|
case 'admin_startup_diagnostics': {
|
|
@@ -6469,7 +6955,7 @@ async function handleTool(name, args) {
|
|
|
6469
6955
|
case 'chat_intelligence_themes': {
|
|
6470
6956
|
const qs = new URLSearchParams();
|
|
6471
6957
|
if (args.startDate) qs.set('startDate', args.startDate);
|
|
6472
|
-
if (args.endDate) qs.set('endDate', args.endDate);
|
|
6958
|
+
if (args.endDate) qs.set('endDate', exclusiveEnd(args.endDate));
|
|
6473
6959
|
qs.set('groupBy', args.groupBy ?? 'month');
|
|
6474
6960
|
if (args.deploymentId != null) qs.set('deploymentId', String(args.deploymentId));
|
|
6475
6961
|
if (args.teamId != null) qs.set('teamId', String(args.teamId));
|
|
@@ -6555,6 +7041,29 @@ async function handleTool(name, args) {
|
|
|
6555
7041
|
return await rpc('conversation_search', args);
|
|
6556
7042
|
|
|
6557
7043
|
// ── Workflows ──────────────────────────────────────────────────────────
|
|
7044
|
+
case 'workflow_schedule_resolve':
|
|
7045
|
+
return await tolerant(() => messagingGet(buildResolvePath(args)));
|
|
7046
|
+
|
|
7047
|
+
case 'workflow_schedule_timeline':
|
|
7048
|
+
return await tolerant(() => messagingGet(buildTimelinePath(args)));
|
|
7049
|
+
|
|
7050
|
+
case 'workflow_schedule_validate':
|
|
7051
|
+
return await tolerant(() => messagingPost(VALIDATE_PATH, buildValidateBody(args)));
|
|
7052
|
+
|
|
7053
|
+
case 'workflow_schedule_banner':
|
|
7054
|
+
return await tolerant(() => messagingGet(buildBannerPath(args)));
|
|
7055
|
+
|
|
7056
|
+
case 'workflow_schedule_override_list':
|
|
7057
|
+
return await tolerant(() => messagingGet(buildOverrideListPath(args)));
|
|
7058
|
+
|
|
7059
|
+
case 'workflow_schedule_override_delete':
|
|
7060
|
+
return await tolerant(async () => { await messagingDel(`${OVERRIDE_PATH}/${args.id}`); return { success: true, deleted: args.id }; });
|
|
7061
|
+
|
|
7062
|
+
case 'workflow_schedule_override_save':
|
|
7063
|
+
return await tolerant(() => args.id
|
|
7064
|
+
? messagingPut(`${OVERRIDE_PATH}/${args.id}`, buildOverrideBody(args))
|
|
7065
|
+
: messagingPost(OVERRIDE_PATH, buildOverrideBody(args)));
|
|
7066
|
+
|
|
6558
7067
|
case 'workflow_list':
|
|
6559
7068
|
return await rpc('workflow_list', args);
|
|
6560
7069
|
|
|
@@ -6563,6 +7072,8 @@ async function handleTool(name, args) {
|
|
|
6563
7072
|
|
|
6564
7073
|
case 'workflow_toggle':
|
|
6565
7074
|
return await rpc('workflow_toggle', args);
|
|
7075
|
+
case 'workflow_run_now':
|
|
7076
|
+
return await rpc('workflow_run_now', args);
|
|
6566
7077
|
|
|
6567
7078
|
case 'workflow_create':
|
|
6568
7079
|
return await rpc('workflow_create', args);
|
|
@@ -6942,6 +7453,13 @@ async function handleTool(name, args) {
|
|
|
6942
7453
|
return `Connection test FAILED for agent ${args.id}: ${result?.error ?? 'unknown error'}`;
|
|
6943
7454
|
}
|
|
6944
7455
|
|
|
7456
|
+
case 'copilot_studio_ask': {
|
|
7457
|
+
if (!args.id || !args.question) return 'id and question are required.';
|
|
7458
|
+
const r = await api('POST', '/CopilotStudio/message', { configId: args.id, message: args.question });
|
|
7459
|
+
return `kind: ${r?.kind}
|
|
7460
|
+
reply: ${r?.reply}`;
|
|
7461
|
+
}
|
|
7462
|
+
|
|
6945
7463
|
// ── Monday.com field mapping / CRM search ──────────────────────────────
|
|
6946
7464
|
// Wired to MondayController.cs actions: GetBoardColumns, GetFieldMapping,
|
|
6947
7465
|
// UpdateFieldMapping, SearchContacts (routes under /Monday/...).
|
|
@@ -7094,7 +7612,7 @@ async function handleTool(name, args) {
|
|
|
7094
7612
|
|
|
7095
7613
|
case 'teams_channel_config_update': {
|
|
7096
7614
|
const body = {};
|
|
7097
|
-
for (const k of ['enabled','welcomeMessage','teamId','clearTeamId'])
|
|
7615
|
+
for (const k of ['enabled','welcomeMessage','teamId','clearTeamId','prechatEnabled','prechatFields','quickReplies','postChatEnabled','postChatMessage'])
|
|
7098
7616
|
if (args[k] !== undefined) body[k] = args[k];
|
|
7099
7617
|
return await api('PUT', '/TeamsBot/ChannelConfig', body);
|
|
7100
7618
|
}
|
|
@@ -8066,6 +8584,17 @@ async function handleTool(name, args) {
|
|
|
8066
8584
|
return `Index ready on site ${args.siteId}: ${idx?.name} (id=${idx?.id}, purpose=${idx?.purpose}, excludeFromAllSources=${!!idx?.excludeFromAllSources}).`;
|
|
8067
8585
|
}
|
|
8068
8586
|
|
|
8587
|
+
case 'index_set_profile': {
|
|
8588
|
+
if (!args.siteId || !args.indexName) return 'siteId and indexName are required.';
|
|
8589
|
+
const body = { indexName: args.indexName };
|
|
8590
|
+
for (const k of ['chunkStrategy', 'chunkSize', 'chunkOverlap', 'purpose', 'searchSize', 'excludeFromAllSources']) {
|
|
8591
|
+
if (args[k] !== undefined) body[k] = args[k];
|
|
8592
|
+
}
|
|
8593
|
+
const idx = await messagingRequest('PUT', `/KnowledgeSetup/Staff/${encodeURIComponent(args.siteId)}/index-profile`, body);
|
|
8594
|
+
if (idx?.error) return `Set index profile error: ${idx.error}`;
|
|
8595
|
+
return `Profile updated on site ${args.siteId}: ${idx?.name} (purpose=${idx?.purpose}, strategy=${idx?.chunkStrategy}, size=${idx?.chunkSize}, overlap=${idx?.chunkOverlap}, searchSize=${idx?.searchSize}, excludeFromAllSources=${!!idx?.excludeFromAllSources}). Re-ingest to apply to existing content.`;
|
|
8596
|
+
}
|
|
8597
|
+
|
|
8069
8598
|
case 'uploaded_files_list': {
|
|
8070
8599
|
const files = await messagingRequest('GET', '/uploadedFiles/list/AI');
|
|
8071
8600
|
if (!Array.isArray(files) || !files.length) return 'No AI training files uploaded for this site.';
|
|
@@ -8089,7 +8618,7 @@ async function handleTool(name, args) {
|
|
|
8089
8618
|
case 'service_level_report': {
|
|
8090
8619
|
const qs = [
|
|
8091
8620
|
args.startDate ? `startDate=${args.startDate}` : '',
|
|
8092
|
-
args.endDate ? `endDate=${args.endDate}` : '',
|
|
8621
|
+
args.endDate ? `endDate=${encodeURIComponent(exclusiveEnd(args.endDate))}` : '',
|
|
8093
8622
|
args.granularity ? `granularity=${args.granularity}` : '',
|
|
8094
8623
|
].filter(Boolean).join('&');
|
|
8095
8624
|
const r = await api('GET', `/ServiceLevel${qs ? '?' + qs : ''}`);
|
|
@@ -8139,7 +8668,7 @@ async function handleTool(name, args) {
|
|
|
8139
8668
|
case 'report_agent_utilization': {
|
|
8140
8669
|
const qs = [
|
|
8141
8670
|
args.startDate ? `startDate=${args.startDate}` : '',
|
|
8142
|
-
args.endDate ? `endDate=${args.endDate}` : '',
|
|
8671
|
+
args.endDate ? `endDate=${encodeURIComponent(exclusiveEnd(args.endDate))}` : '',
|
|
8143
8672
|
].filter(Boolean).join('&');
|
|
8144
8673
|
const rows = await api('GET', `/Reports/AgentUtilization${qs ? '?' + qs : ''}`);
|
|
8145
8674
|
if (!Array.isArray(rows) || !rows.length) return 'No agent utilization data for this period.';
|
|
@@ -8260,10 +8789,36 @@ async function handleTool(name, args) {
|
|
|
8260
8789
|
return `Export queued (id ${result.exportId}). ${result.message}`;
|
|
8261
8790
|
}
|
|
8262
8791
|
|
|
8792
|
+
case 'report_performance_by_country_get': {
|
|
8793
|
+
const end = args.endDate ? new Date(args.endDate) : new Date();
|
|
8794
|
+
const start = args.startDate ? new Date(args.startDate) : new Date(end.getTime() - 30 * 86400000);
|
|
8795
|
+
if (isNaN(start) || isNaN(end)) return 'Error: startDate/endDate must be valid ISO 8601 dates.';
|
|
8796
|
+
const r = await messagingPost('/reports/performance-by-country/query', {
|
|
8797
|
+
start: start.toISOString(), end: end.toISOString(),
|
|
8798
|
+
attribution: args.attribution || 'visitor', channels: Array.isArray(args.channels) ? args.channels : [],
|
|
8799
|
+
});
|
|
8800
|
+
const f = (v, s = '') => (v == null ? '-' : `${v}${s}`);
|
|
8801
|
+
const row = x => `${(x.agentName || x.bucket).padEnd(24)} eng ${x.engagements} | handled ${x.handled} | missed ${x.missed} | abandoned ${x.abandoned} | bot containment ${f(x.botContainmentPct, '%')} | transfers ${x.transfersToAgent} | 1st resp ${f(x.avgFirstResponseSeconds, 's')} | handle ${f(x.avgHandleTimeMinutes, 'm')} | SL ${f(x.serviceLevelPct, '%')} | CSAT ${f(x.csatAvg)} (${x.csatCount}) | positive ${f(x.sentimentPositivePct, '%')}`;
|
|
8802
|
+
return [`=== Performance by Country (${r.attribution}) ===`, ...(r.buckets || []).map(row), '', 'By agent:',
|
|
8803
|
+
...((r.agents || []).map(a => row(a) + (a.agentCountryNotSet ? ' [agent country not set]' : '')))].join('\n');
|
|
8804
|
+
}
|
|
8805
|
+
|
|
8806
|
+
case 'report_performance_by_country_set_groups': {
|
|
8807
|
+
const groups = await messagingGet('/reports/performance-by-country/groups');
|
|
8808
|
+
let current;
|
|
8809
|
+
if (args.map && typeof args.map === 'object') {
|
|
8810
|
+
current = await messagingPut('/reports/performance-by-country/group-map', { map: args.map });
|
|
8811
|
+
} else {
|
|
8812
|
+
current = await messagingGet('/reports/performance-by-country/group-map');
|
|
8813
|
+
}
|
|
8814
|
+
const map = current?.map ?? {};
|
|
8815
|
+
return ['Groups:', ...(Array.isArray(groups) ? groups : []).map(g => ` ${g.id} ${g.name} -> ${map[g.id] ?? '(unmapped, reports as Other)'}`)].join('\n');
|
|
8816
|
+
}
|
|
8817
|
+
|
|
8263
8818
|
case 'call_center_report': {
|
|
8264
8819
|
const qs = [
|
|
8265
8820
|
args.startDate ? `start=${args.startDate}` : '',
|
|
8266
|
-
args.endDate ? `end=${args.endDate}` : '',
|
|
8821
|
+
args.endDate ? `end=${encodeURIComponent(exclusiveEnd(args.endDate))}` : '',
|
|
8267
8822
|
].filter(Boolean).join('&');
|
|
8268
8823
|
const r = await api('GET', `/CallCenterAnalytics/Report${qs ? '?' + qs : ''}`);
|
|
8269
8824
|
const v = r.volume ?? {};
|
|
@@ -8301,7 +8856,7 @@ async function handleTool(name, args) {
|
|
|
8301
8856
|
case 'call_center_team_breakdown': {
|
|
8302
8857
|
const qs = [
|
|
8303
8858
|
args.startDate ? `start=${args.startDate}` : '',
|
|
8304
|
-
args.endDate ? `end=${args.endDate}` : '',
|
|
8859
|
+
args.endDate ? `end=${encodeURIComponent(exclusiveEnd(args.endDate))}` : '',
|
|
8305
8860
|
args.slaThresholdSeconds ? `slaThresholdSeconds=${args.slaThresholdSeconds}` : '',
|
|
8306
8861
|
].filter(Boolean).join('&');
|
|
8307
8862
|
let r;
|
|
@@ -8324,7 +8879,7 @@ async function handleTool(name, args) {
|
|
|
8324
8879
|
case 'call_center_channel_breakdown': {
|
|
8325
8880
|
const qs = [
|
|
8326
8881
|
args.startDate ? `start=${args.startDate}` : '',
|
|
8327
|
-
args.endDate ? `end=${args.endDate}` : '',
|
|
8882
|
+
args.endDate ? `end=${encodeURIComponent(exclusiveEnd(args.endDate))}` : '',
|
|
8328
8883
|
args.slaThresholdSeconds ? `slaThresholdSeconds=${args.slaThresholdSeconds}` : '',
|
|
8329
8884
|
].filter(Boolean).join('&');
|
|
8330
8885
|
let r;
|
|
@@ -8831,23 +9386,13 @@ async function handleTool(name, args) {
|
|
|
8831
9386
|
}
|
|
8832
9387
|
|
|
8833
9388
|
case 'velaro_logs_search': {
|
|
8834
|
-
const from
|
|
8835
|
-
const params =
|
|
8836
|
-
if (args.q) params.set('q', args.q);
|
|
8837
|
-
if (args.level) params.set('level', args.level.toUpperCase());
|
|
8838
|
-
if (args.logger) params.set('logger', args.logger);
|
|
8839
|
-
if (args.integration) params.set('integration', args.integration);
|
|
8840
|
-
if (args.siteId) params.set('siteId', String(args.siteId));
|
|
8841
|
-
if (args.source) params.set('source', args.source);
|
|
8842
|
-
if (args.platform) params.set('platform', args.platform);
|
|
8843
|
-
if (args.environment) params.set('environment', args.environment);
|
|
8844
|
-
if (args.correlationId) params.set('correlationId', args.correlationId);
|
|
8845
|
-
if (args.regex) params.set('regex', 'true');
|
|
9389
|
+
const { from, to } = resolveLogWindow(args);
|
|
9390
|
+
const params = buildRawLogQuery(args, from, to);
|
|
8846
9391
|
const resp = await messagingApi(`/superadmin/logs/search?${params}`, { timeoutMs: 40000 });
|
|
8847
9392
|
const entries = extractLogEntries(resp);
|
|
8848
9393
|
const meta = describeRawMeta(resp); // searchPath/scannedRows/truncated: a 0 or short list is never ambiguous
|
|
8849
9394
|
if (!entries.length) return `No results. ${meta}`;
|
|
8850
|
-
const lines = [`${entries.length} result(s) from ${from.toISOString().slice(0, 16)} UTC. ${meta}\n`];
|
|
9395
|
+
const lines = [`${entries.length} result(s) from ${from.toISOString().slice(0, 16)} to ${to.toISOString().slice(0, 16)} UTC. ${meta}\n`];
|
|
8851
9396
|
for (const e of entries) {
|
|
8852
9397
|
const ts = new Date(e.timestamp ?? e.LogTimestamp).toISOString().replace('T', ' ').slice(0, 19);
|
|
8853
9398
|
const lvl = (e.Level ?? e.level ?? '?').padEnd(5);
|
|
@@ -9248,7 +9793,7 @@ async function handleTool(name, args) {
|
|
|
9248
9793
|
coaching_queue: 'coaching-queue', sentiment_breakdown: 'sentiment-breakdown', topic_radar: 'topic-radar',
|
|
9249
9794
|
weekly_brief: 'weekly-brief', weekly_brief_send_test: 'weekly-brief/send-test',
|
|
9250
9795
|
repeat_contact: 'repeat-contact', demand_coverage: 'demand-coverage', bot_containment: 'bot-containment',
|
|
9251
|
-
effort_index: 'effort-index', promise_ledger: 'promise-ledger',
|
|
9796
|
+
effort_index: 'effort-index', promise_ledger: 'promise-ledger', new_agent_ramp: 'new-agent-ramp',
|
|
9252
9797
|
};
|
|
9253
9798
|
if (args.report === 'promise_dismiss') {
|
|
9254
9799
|
if (!args.promiseId) throw new Error('promiseId is required for promise_dismiss');
|
|
@@ -9325,8 +9870,11 @@ async function handleTool(name, args) {
|
|
|
9325
9870
|
}
|
|
9326
9871
|
|
|
9327
9872
|
case 'report_backfill': {
|
|
9873
|
+
if (args.status === true) return JSON.stringify(await messagingGet(`/Reports/backfill/${encodeURIComponent(args.type)}/status`), null, 2);
|
|
9874
|
+
if (!args.from || !args.to) throw new Error('from and to are required (yyyy-MM-dd) unless status=true');
|
|
9328
9875
|
const qs = new URLSearchParams({ from: String(args.from), to: String(args.to), apply: String(args.apply === true) });
|
|
9329
9876
|
const r = await messagingPost(`/Reports/backfill/${encodeURIComponent(args.type)}?${qs}`);
|
|
9877
|
+
if (args.apply === true && r && r.runId) return (r.alreadyRunning ? `A run is already ${r.status} for this site; not starting another.\n` : 'Backfill QUEUED (runs in the background; call again with status=true to read progress)\n') + JSON.stringify(r, null, 2);
|
|
9330
9878
|
return (args.apply === true ? 'Backfill APPLIED\n' : 'Backfill PREVIEW (nothing written; pass apply=true to write)\n') + JSON.stringify(r, null, 2);
|
|
9331
9879
|
}
|
|
9332
9880
|
|
|
@@ -9421,15 +9969,25 @@ async function handleTool(name, args) {
|
|
|
9421
9969
|
].join('\n');
|
|
9422
9970
|
}
|
|
9423
9971
|
|
|
9972
|
+
case 'avatar_session_readiness': {
|
|
9973
|
+
const data = await messagingRequest('GET', `/avatar/diagnostics/session-readiness?siteId=${encodeURIComponent(args.siteId)}`);
|
|
9974
|
+
return JSON.stringify(data, null, 2);
|
|
9975
|
+
}
|
|
9976
|
+
|
|
9424
9977
|
case 'avatar_get_config': {
|
|
9425
9978
|
const data = await api('GET', `/Avatar/superadmin/status?siteId=${args.siteId}`);
|
|
9426
|
-
|
|
9979
|
+
let heyGenLine = 'unknown';
|
|
9980
|
+
try { const ent = await api('GET', `/Entitlements/${args.siteId}`); heyGenLine = ent?.features?.EnableAvatarHeyGen ? 'yes' : 'no'; } catch { /* entitlement lookup is best-effort */ }
|
|
9981
|
+
if (!data.configured) return `Site ${args.siteId}: AI Video Avatar NOT configured (no saved config). HeyGen entitled: ${heyGenLine}`;
|
|
9427
9982
|
return [
|
|
9428
9983
|
`Site ${args.siteId}: AI Video Avatar ${data.enabled ? 'ENABLED' : 'configured but DISABLED'}`,
|
|
9429
9984
|
` Provider: ${data.providerSlug}`,
|
|
9430
9985
|
` Avatar ID: ${data.avatarId || '(provider default)'}`,
|
|
9986
|
+
` Face per provider: ${data.avatarIdByProvider && Object.keys(data.avatarIdByProvider).length ? JSON.stringify(data.avatarIdByProvider) : '(none saved)'} (read-only)`,
|
|
9987
|
+
` Keep running on agent join: ${data.enabledOnAgentTransfer ? 'yes' : 'no'}`,
|
|
9431
9988
|
` Voice ID: ${data.voiceId || '(site default)'}`,
|
|
9432
9989
|
` Enabled: ${data.enabled}`,
|
|
9990
|
+
` HeyGen entitled: ${heyGenLine}`,
|
|
9433
9991
|
` "Call me": ${data.showCallMeOption}`,
|
|
9434
9992
|
` Callback window: ${data.callbackConfirmWindowMinutes} min`,
|
|
9435
9993
|
` Display name: ${data.displayName || '(AI)'}`,
|
|
@@ -9446,12 +10004,17 @@ async function handleTool(name, args) {
|
|
|
9446
10004
|
if (args.voiceId !== undefined) body.voiceId = args.voiceId;
|
|
9447
10005
|
if (args.enabled !== undefined) body.enabled = args.enabled;
|
|
9448
10006
|
if (args.showCallMeOption !== undefined) body.showCallMeOption = args.showCallMeOption;
|
|
10007
|
+
if (args.enabledOnAgentTransfer !== undefined) body.enabledOnAgentTransfer = args.enabledOnAgentTransfer;
|
|
9449
10008
|
if (args.callbackConfirmWindowMinutes !== undefined) body.callbackConfirmWindowMinutes = args.callbackConfirmWindowMinutes;
|
|
9450
10009
|
if (args.displayName !== undefined) body.displayName = args.displayName;
|
|
9451
10010
|
if (args.showBadge !== undefined) body.showBadge = args.showBadge;
|
|
9452
10011
|
if (args.sizePx !== undefined) body.sizePx = args.sizePx;
|
|
9453
10012
|
if (args.allowVisitorCollapse !== undefined) body.allowVisitorCollapse = args.allowVisitorCollapse;
|
|
10013
|
+
if (args.heyGenEnabled !== undefined) {
|
|
10014
|
+
await api('POST', `/Entitlements/${args.siteId}/override`, { featureKey: 'EnableAvatarHeyGen', value: args.heyGenEnabled, reason: 'avatar_set_config heyGenEnabled' });
|
|
10015
|
+
}
|
|
9454
10016
|
if (args.motionLevel !== undefined) body.motionLevel = parseAvatarMotionLevel(args.motionLevel);
|
|
10017
|
+
if (args.heyGenEnabled !== undefined && !Object.keys(body).length && args.motionIntensityPercent === undefined && args.maxAvatarMinutesPerMonth === undefined && args.maxAvatarTtsChars === undefined) return `Site ${args.siteId}: EnableAvatarHeyGen=${args.heyGenEnabled}`;
|
|
9455
10018
|
if (args.motionIntensityPercent !== undefined) {
|
|
9456
10019
|
if (!Number.isInteger(args.motionIntensityPercent) || args.motionIntensityPercent < 25 || args.motionIntensityPercent > 150) throw new Error('motionIntensityPercent must be a whole number between 25 and 150.');
|
|
9457
10020
|
body.motionIntensityPercent = args.motionIntensityPercent;
|
|
@@ -9466,6 +10029,15 @@ async function handleTool(name, args) {
|
|
|
9466
10029
|
return `✓ AI Video Avatar updated for site ${args.siteId}. Provider: ${res.providerSlug}, enabled: ${res.enabled}, maxAvatarMinutesPerMonth: ${res.maxAvatarMinutesPerMonth ?? 'n/a'}, maxAvatarTtsChars: ${res.maxAvatarTtsChars ?? 'n/a'}`;
|
|
9467
10030
|
}
|
|
9468
10031
|
|
|
10032
|
+
case 'apple_pay_preset_list':
|
|
10033
|
+
return JSON.stringify(await messagingRequest('GET', `/ApplePayPresetOrders${args.activeOnly ? '?activeOnly=true' : ''}`), null, 2);
|
|
10034
|
+
case 'apple_pay_preset_create':
|
|
10035
|
+
return JSON.stringify(await messagingRequest('POST', '/ApplePayPresetOrders', applePayPresetBody(args)), null, 2);
|
|
10036
|
+
case 'apple_pay_preset_update':
|
|
10037
|
+
return JSON.stringify(await messagingRequest('PUT', `/ApplePayPresetOrders/${applePayPresetId(args.id)}`, applePayPresetBody(args)), null, 2);
|
|
10038
|
+
case 'apple_pay_preset_delete':
|
|
10039
|
+
return JSON.stringify(await messagingRequest('DELETE', `/ApplePayPresetOrders/${applePayPresetId(args.id)}`), null, 2);
|
|
10040
|
+
|
|
9469
10041
|
case 'avatar_list_library':
|
|
9470
10042
|
return JSON.stringify(await messagingRequest('GET', `/avatar/library${args.kind ? `?kind=${encodeURIComponent(args.kind)}` : ''}`), null, 2);
|
|
9471
10043
|
case 'avatar_list_personalities':
|
|
@@ -9485,6 +10057,22 @@ async function handleTool(name, args) {
|
|
|
9485
10057
|
return JSON.stringify(await messagingRequest('PUT', `/avatar/workflows/${Number(args.workflowId)}/override`, avatarOverrideBody(args)), null, 2);
|
|
9486
10058
|
case 'avatar_reset_workflow':
|
|
9487
10059
|
return JSON.stringify(await messagingRequest('DELETE', `/avatar/workflows/${Number(args.workflowId)}/override`), null, 2);
|
|
10060
|
+
case 'avatar_set_workflow_switch': {
|
|
10061
|
+
const mode = String(args.mode ?? '').trim().toLowerCase();
|
|
10062
|
+
if (!['on', 'off', 'rules'].includes(mode)) throw new Error('mode must be one of: on, off, rules');
|
|
10063
|
+
const id = Number(args.workflowId);
|
|
10064
|
+
if (!Number.isInteger(id) || id <= 0) throw new Error('workflowId must be a positive whole number');
|
|
10065
|
+
return JSON.stringify(await messagingRequest('PUT', `/avatar/workflows/${id}/enabled`, { mode }), null, 2);
|
|
10066
|
+
}
|
|
10067
|
+
case 'avatar_bulk_set_switch': {
|
|
10068
|
+
const mode = String(args.mode ?? '').trim().toLowerCase();
|
|
10069
|
+
if (!['on', 'off', 'rules'].includes(mode)) throw new Error('mode must be one of: on, off, rules');
|
|
10070
|
+
const workflowIds = (args.workflowIds ?? []).map(Number);
|
|
10071
|
+
const groupIds = (args.groupIds ?? []).map(Number);
|
|
10072
|
+
if (workflowIds.length + groupIds.length === 0) throw new Error('Pass workflowIds and/or groupIds');
|
|
10073
|
+
if (workflowIds.length + groupIds.length > 500) throw new Error('Too many rows in one call (max 500)');
|
|
10074
|
+
return JSON.stringify(await messagingRequest('PUT', '/avatar/overrides/enabled', { mode, workflowIds, groupIds }), null, 2);
|
|
10075
|
+
}
|
|
9488
10076
|
case 'avatar_reset_all_overrides':
|
|
9489
10077
|
return JSON.stringify(await messagingRequest('POST', '/avatar/group-overrides/reset-all', {}), null, 2);
|
|
9490
10078
|
|
|
@@ -10008,7 +10596,11 @@ async function handleTool(name, args) {
|
|
|
10008
10596
|
}
|
|
10009
10597
|
|
|
10010
10598
|
case 'magento_test_connection': {
|
|
10011
|
-
|
|
10599
|
+
if (!args.configId && (!args.storeUrl || !args.accessToken)) return 'Provide configId, or both storeUrl and accessToken.';
|
|
10600
|
+
const r = args.configId
|
|
10601
|
+
? await api('POST', `/Magento/Configs/${encodeURIComponent(args.configId)}/Test`)
|
|
10602
|
+
: await api('POST', '/Magento/Configs/Test', { storeUrl: args.storeUrl, accessToken: args.accessToken });
|
|
10603
|
+
if (r && r.valid === false) return `❌ Connection failed: ${r.error}`;
|
|
10012
10604
|
return `✅ Connected to "${r.storeName}" (${r.edition} ${r.version})`;
|
|
10013
10605
|
}
|
|
10014
10606
|
|
|
@@ -10074,6 +10666,13 @@ async function handleTool(name, args) {
|
|
|
10074
10666
|
}
|
|
10075
10667
|
|
|
10076
10668
|
// ── Business actions + SAP ────────────────────────────────────────────────
|
|
10669
|
+
case 'salesforce_diagnose': {
|
|
10670
|
+
if (!args.siteId) return 'siteId is required.';
|
|
10671
|
+
const deep = args.deep === false ? 'false' : 'true';
|
|
10672
|
+
const r = await messagingRequest('GET', `/diagnostics/test-salesforce?siteId=${encodeURIComponent(args.siteId)}&deep=${deep}`);
|
|
10673
|
+
return JSON.stringify(r, null, 2);
|
|
10674
|
+
}
|
|
10675
|
+
|
|
10077
10676
|
case 'sap_diagnose': {
|
|
10078
10677
|
if (!args.siteId) return 'siteId is required.';
|
|
10079
10678
|
const deep = args.deep === false ? 'false' : 'true';
|
|
@@ -10162,6 +10761,37 @@ async function handleTool(name, args) {
|
|
|
10162
10761
|
return `CTI token generated/rotated.\nDial URL: ${urls.dialUrlTemplate}\nScreen-pop URL: ${urls.screenPopUrl}`;
|
|
10163
10762
|
}
|
|
10164
10763
|
|
|
10764
|
+
case 'netsuite_suiteapp_download': {
|
|
10765
|
+
const includeKey = args.includeKey !== false;
|
|
10766
|
+
const r = await messagingGet(`/api/v1/CrmEmbed/suiteapp-package?includeKey=${includeKey}&format=json`);
|
|
10767
|
+
const d = r?.data ?? r;
|
|
10768
|
+
if (!d?.contentBase64) throw new Error('SuiteApp package was not returned.');
|
|
10769
|
+
const { writeFileSync } = await import('node:fs');
|
|
10770
|
+
const { tmpdir } = await import('node:os');
|
|
10771
|
+
const out = args.outputPath || _join(tmpdir(), d.fileName || 'velaro-netsuite-screenpop-suiteapp.zip');
|
|
10772
|
+
writeFileSync(out, Buffer.from(d.contentBase64, 'base64'));
|
|
10773
|
+
return `Saved ${out}. ` + (d.keyIncluded
|
|
10774
|
+
? 'Your Velaro key is already inside: no NetSuite API Secret and nothing to paste. Deploy with suitecloud (see SETUP.txt in the zip). Keep the zip private.'
|
|
10775
|
+
: `Key-less zip. In NetSuite create an API Secret: type ID ${d.secretFormId} (NetSuite adds the prefix, giving ${d.secretFullId}), paste your crm_embed_ key as Password.`);
|
|
10776
|
+
}
|
|
10777
|
+
case 'netsuite_config_delete': {
|
|
10778
|
+
await messagingDel(`/Netsuite/Integrations/${args.configId}`);
|
|
10779
|
+
return `Deleted NetSuite config ${args.configId} (a missing config is a no-op).`;
|
|
10780
|
+
}
|
|
10781
|
+
case 'netsuite_m2m_clear_legacy': {
|
|
10782
|
+
const r = await messagingPost('/NetSuite/M2M/ClearLegacy', { id: args.configId });
|
|
10783
|
+
return `Legacy consumer secret/token cleared for config ${r?.id ?? args.configId} (authType ${r?.authType ?? 'm2m'}).`;
|
|
10784
|
+
}
|
|
10785
|
+
case 'netsuite_embed_status': {
|
|
10786
|
+
const r = await messagingGet('/api/v1/CrmEmbed/embed-status');
|
|
10787
|
+
const d = r?.data ?? r;
|
|
10788
|
+
if (!d?.hasEmbedKey) return 'No embed key exists for this site yet. The SuiteApp cannot connect until one is created.';
|
|
10789
|
+
if (d.connected) return `SuiteApp connected. Last polled ${d.lastPolledAt}.`;
|
|
10790
|
+
return d.lastPolledAt
|
|
10791
|
+
? `Embed key exists but the SuiteApp is not connected (last polled ${d.lastPolledAt}, outside the connected window).`
|
|
10792
|
+
: 'Embed key exists but the SuiteApp has not polled yet.';
|
|
10793
|
+
}
|
|
10794
|
+
|
|
10165
10795
|
case 'netsuite_m2m_init': {
|
|
10166
10796
|
const r = await messagingPost('/NetSuite/M2M/Init', { id: args.configId ?? null, company: args.company, displayName: args.displayName });
|
|
10167
10797
|
return `Config ${r.id ?? args.configId ?? 'new'}. Upload this certificate in NetSuite (Setup > Integration > Manage Authentication > OAuth 2.0 Client Credentials (M2M) Setup):\n${r.certificatePem ?? '(none returned)'}\n${Array.isArray(r.steps) ? r.steps.map((x, i) => `${i + 1}. ${x}`).join('\n') : ''}`;
|
|
@@ -10210,6 +10840,20 @@ async function handleTool(name, args) {
|
|
|
10210
10840
|
return `${result.success ? 'Success' : 'Failed'}: ${result.message ?? '(no message)'}`;
|
|
10211
10841
|
}
|
|
10212
10842
|
|
|
10843
|
+
// ── Shopify write policy (velaro-messaging /api/Shopify/WritePolicy) ───────
|
|
10844
|
+
case 'shopify_get_write_policy': {
|
|
10845
|
+
return JSON.stringify(await messagingGet('/Shopify/WritePolicy'), null, 2);
|
|
10846
|
+
}
|
|
10847
|
+
case 'shopify_set_write_policy': {
|
|
10848
|
+
const body = {};
|
|
10849
|
+
for (const k of ['enableShopifyWrite', 'requireConfirmation', 'maxRefundAmount', 'maxDiscountPercent', 'maxDiscountFixedAmount']) {
|
|
10850
|
+
if (args[k] !== undefined) body[k] = args[k];
|
|
10851
|
+
}
|
|
10852
|
+
if (!Object.keys(body).length) throw new Error('Provide at least one field to change.');
|
|
10853
|
+
const result = await messagingPut('/Shopify/WritePolicy', body);
|
|
10854
|
+
return `Shopify write policy updated.
|
|
10855
|
+
${JSON.stringify(result, null, 2)}`;
|
|
10856
|
+
}
|
|
10213
10857
|
// ── Webflow (config CRUD) ──────────────────────────────────────────────────
|
|
10214
10858
|
case 'webflow_get_config': {
|
|
10215
10859
|
if (args.configId) {
|
|
@@ -10640,6 +11284,37 @@ async function handleTool(name, args) {
|
|
|
10640
11284
|
return `✅ Billing contact ${args.id} removed.`;
|
|
10641
11285
|
}
|
|
10642
11286
|
|
|
11287
|
+
case 'billing_notify_settings_get': {
|
|
11288
|
+
const all = await api('GET', '/api/BillingNotifications/settings');
|
|
11289
|
+
return Object.entries(all).map(([k, v]) => `${k}: ${v ?? '(not set: code default)'}`).join('\n');
|
|
11290
|
+
}
|
|
11291
|
+
|
|
11292
|
+
case 'billing_notify_setting_set': {
|
|
11293
|
+
await api('PUT', `/api/BillingNotifications/settings/${encodeURIComponent(args.key)}`, { value: args.value });
|
|
11294
|
+
return `✅ ${args.key} set to ${args.value}.`;
|
|
11295
|
+
}
|
|
11296
|
+
|
|
11297
|
+
case 'billing_receipt_overrides_set': {
|
|
11298
|
+
// The API replaces all three channels, so keep the ones the caller did not pass.
|
|
11299
|
+
const sub = await api('GET', `/api/BillingSubscription/${args.subscriptionId}`);
|
|
11300
|
+
const res = await api('PUT', `/api/BillingNotifications/subscriptions/${args.subscriptionId}/receipts`, {
|
|
11301
|
+
card: args.card ?? sub.receiptCardOverride ?? null,
|
|
11302
|
+
ach: args.ach ?? sub.receiptAchOverride ?? null,
|
|
11303
|
+
other: args.other ?? sub.receiptOtherOverride ?? null,
|
|
11304
|
+
});
|
|
11305
|
+
return `✅ Subscription ${res.id} receipts: card=${res.card ?? 'inherit'} ach=${res.ach ?? 'inherit'} other=${res.other ?? 'inherit'}.`;
|
|
11306
|
+
}
|
|
11307
|
+
|
|
11308
|
+
case 'billing_digest_send_now': {
|
|
11309
|
+
const res = await api('POST', '/api/BillingNotifications/digest/send-now', {});
|
|
11310
|
+
return `✅ Digest sent to ${res.recipients.join(', ')}: ${res.subject}`;
|
|
11311
|
+
}
|
|
11312
|
+
|
|
11313
|
+
case 'billing_receipts_run_now': {
|
|
11314
|
+
const res = await api('POST', '/api/BillingNotifications/receipts/run-now', {});
|
|
11315
|
+
return `✅ Receipts run: ${res.summary}`;
|
|
11316
|
+
}
|
|
11317
|
+
|
|
10643
11318
|
case 'billing_plan_status': {
|
|
10644
11319
|
const s = await api('GET', '/api/BillingPayment/plan-status');
|
|
10645
11320
|
const lines = [`Site ${s.siteId} — Subscription Status`];
|
|
@@ -10770,6 +11445,19 @@ async function handleTool(name, args) {
|
|
|
10770
11445
|
return lines.join(String.fromCharCode(10));
|
|
10771
11446
|
}
|
|
10772
11447
|
|
|
11448
|
+
case 'entitlement_report_packages': {
|
|
11449
|
+
const q = args && args.siteId ? `?siteId=${encodeURIComponent(args.siteId)}` : '';
|
|
11450
|
+
const pkg = await api('GET', `/Entitlements/report-packages${q}`);
|
|
11451
|
+
const lines = [`${pkg.top.name} (${pkg.top.slug}) - plan ${pkg.plan}, default OFF${pkg.siteId ? `, site ${pkg.siteId}${pkg.top.enabled ? ' [ALL ON]' : ''}` : ''}`, ` ${pkg.top.tagline}`];
|
|
11452
|
+
for (const c of pkg.categories) {
|
|
11453
|
+
lines.push('');
|
|
11454
|
+
lines.push(`${c.name} (${c.slug})${pkg.siteId ? ` [${c.enabled ? 'ON ' : 'off'}] ${c.enabledCount}/${c.reportCount} reports` : ` ${c.reportCount} reports`}`);
|
|
11455
|
+
lines.push(` ${c.tagline}`);
|
|
11456
|
+
for (const r of c.reports) lines.push(` ${pkg.siteId ? (r.enabled ? '[ON ] ' : '[off] ') : ''}${r.name} (${r.id}) - package ${r.packageSlug}, key ${r.packageKey}`);
|
|
11457
|
+
}
|
|
11458
|
+
return lines.join(String.fromCharCode(10));
|
|
11459
|
+
}
|
|
11460
|
+
|
|
10773
11461
|
case 'entitlement_list_features': {
|
|
10774
11462
|
const features = await api('GET', '/Entitlements/features');
|
|
10775
11463
|
const byCategory = {};
|
|
@@ -10798,6 +11486,32 @@ async function handleTool(name, args) {
|
|
|
10798
11486
|
return `✅ Site ${args.siteId}: ${args.featureKey} = ${args.value}${args.reason ? ` (${args.reason})` : ''}`;
|
|
10799
11487
|
}
|
|
10800
11488
|
|
|
11489
|
+
case 'entitlement_trial_get': {
|
|
11490
|
+
if (!args.siteId) throw new Error('siteId is required');
|
|
11491
|
+
return JSON.stringify(await api('GET', `/Entitlements/${args.siteId}/trial`), null, 2);
|
|
11492
|
+
}
|
|
11493
|
+
|
|
11494
|
+
case 'entitlement_trial_recommend': {
|
|
11495
|
+
if (!args.siteId) throw new Error('siteId is required');
|
|
11496
|
+
return JSON.stringify(await api('GET', `/Entitlements/${args.siteId}/trial/recommendation`), null, 2);
|
|
11497
|
+
}
|
|
11498
|
+
|
|
11499
|
+
case 'entitlement_trial_grant': {
|
|
11500
|
+
if (!args.siteId) throw new Error('siteId is required');
|
|
11501
|
+
if (!args.featureKey) throw new Error('featureKey is required');
|
|
11502
|
+
if (!args.reason) throw new Error('reason is required');
|
|
11503
|
+
const res = await api('POST', `/Entitlements/${args.siteId}/trial/grant`, { featureKey: args.featureKey, reason: args.reason });
|
|
11504
|
+
return `Site ${args.siteId}: ${res.featureKey} granted for the trial (${res.state}).`;
|
|
11505
|
+
}
|
|
11506
|
+
|
|
11507
|
+
case 'entitlement_trial_set_limit': {
|
|
11508
|
+
if (!args.siteId) throw new Error('siteId is required');
|
|
11509
|
+
if (!Number.isInteger(args.value) || args.value < 0) throw new Error('value must be a whole number >= 0');
|
|
11510
|
+
if (!args.key || !args.reason) throw new Error('key and reason are required');
|
|
11511
|
+
const res = await api('POST', `/Entitlements/${args.siteId}/trial/limit`, { key: args.key, value: args.value, reason: args.reason });
|
|
11512
|
+
return `Site ${args.siteId}: ${res.key} ${res.old} -> ${res.value}.`;
|
|
11513
|
+
}
|
|
11514
|
+
|
|
10801
11515
|
case 'entitlement_remove_override': {
|
|
10802
11516
|
if (!args.siteId) throw new Error('siteId is required');
|
|
10803
11517
|
if (!args.featureKey) throw new Error('featureKey is required');
|
|
@@ -10974,7 +11688,12 @@ async function handleTool(name, args) {
|
|
|
10974
11688
|
throw new Error('Date range too large. Worked Hours can be queried for at most 90 days at a time.');
|
|
10975
11689
|
}
|
|
10976
11690
|
}
|
|
10977
|
-
|
|
11691
|
+
// Server End is exclusive: a date-only end means "through the end of that day".
|
|
11692
|
+
let endValue = args.end;
|
|
11693
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(String(args.end).trim()) && !Number.isNaN(endMs)) {
|
|
11694
|
+
endValue = new Date(endMs + 24 * 60 * 60 * 1000).toISOString();
|
|
11695
|
+
}
|
|
11696
|
+
return await messagingPost('/Reports/worked-hours-breakdown', { Start: args.start, End: endValue });
|
|
10978
11697
|
}
|
|
10979
11698
|
|
|
10980
11699
|
// -- Pickup & Reminder Texting --
|
|
@@ -11022,6 +11741,25 @@ async function handleTool(name, args) {
|
|
|
11022
11741
|
return JSON.stringify(result, null, 2);
|
|
11023
11742
|
}
|
|
11024
11743
|
|
|
11744
|
+
case 'apple_pay_get_config': {
|
|
11745
|
+
const config = await messagingGet('/ApplePayConfig');
|
|
11746
|
+
return JSON.stringify(config, null, 2);
|
|
11747
|
+
}
|
|
11748
|
+
|
|
11749
|
+
case 'apple_pay_update_config': {
|
|
11750
|
+
// Field names must match ApplePayConfigUpdateRequest exactly (apply-if-present: undefined is dropped by JSON).
|
|
11751
|
+
const body = {
|
|
11752
|
+
merchantDisplayName: args.merchantDisplayName,
|
|
11753
|
+
isTestMode: args.isTestMode,
|
|
11754
|
+
bubbleLogoBase64: args.bubbleLogoBase64,
|
|
11755
|
+
};
|
|
11756
|
+
const existing = await messagingGet('/ApplePayConfig');
|
|
11757
|
+
const result = existing && existing.id > 0
|
|
11758
|
+
? await messagingPut(`/ApplePayConfig/${existing.id}`, body)
|
|
11759
|
+
: await messagingPost('/ApplePayConfig', body);
|
|
11760
|
+
return JSON.stringify(result, null, 2);
|
|
11761
|
+
}
|
|
11762
|
+
|
|
11025
11763
|
// -- Payment Recovery --
|
|
11026
11764
|
case 'payment_recovery_send': {
|
|
11027
11765
|
// Field names must match SendPaymentReminderRequest exactly.
|
|
@@ -11682,11 +12420,11 @@ const PROMPTS = [
|
|
|
11682
12420
|
];
|
|
11683
12421
|
|
|
11684
12422
|
const server = new Server(
|
|
11685
|
-
{ name: 'velaro', version:
|
|
12423
|
+
{ name: 'velaro', version: JSON.parse(_readFileSync(_join(_dirname(_ftu(import.meta.url)), 'package.json'), 'utf8')).version },
|
|
11686
12424
|
{ capabilities: { tools: {}, prompts: {} } }
|
|
11687
12425
|
);
|
|
11688
12426
|
|
|
11689
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
12427
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS.map(withAnnotations) }));
|
|
11690
12428
|
|
|
11691
12429
|
server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: PROMPTS }));
|
|
11692
12430
|
|