@velaro/mcp-server 0.6.103 → 0.6.104
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 +5 -2
- package/server.js +489 -43
- package/tool-annotations.js +138 -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,8 @@ 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';
|
|
52
54
|
|
|
53
55
|
// Default was 'https://help.velaro.com'. Renamed to the recognizable admin API hostname for
|
|
54
56
|
// clarity during the 2026-09-10 Donaldson bot Calendly status investigation
|
|
@@ -59,7 +61,7 @@ import {
|
|
|
59
61
|
// caused that investigation's status/reality contradiction, it was NOT this default resolving to a
|
|
60
62
|
// broken or different host; that theory is disproven. Left as a naming clarity improvement only.
|
|
61
63
|
const API_BASE = process.env.VELARO_ADMIN_API || 'https://api-admin-us-east.velaro.com';
|
|
62
|
-
const MESSAGING_API = process.env.VELARO_MESSAGING_API
|
|
64
|
+
const MESSAGING_API = resolveMessagingApi(process.env.VELARO_MESSAGING_API, API_BASE);
|
|
63
65
|
const MCP_KEY = process.env.VELARO_MCP_KEY;
|
|
64
66
|
const JWT = process.env.VELARO_JWT;
|
|
65
67
|
|
|
@@ -320,7 +322,7 @@ async function gatewayInit() {
|
|
|
320
322
|
// Tools served by velaro-messaging's admin-tools gateway (McpAdminGatewayController, POST /mcp/v1/admin-tools/call,
|
|
321
323
|
// no session). The admin /mcp gateway only serves kb_* tools, so these used to fail there.
|
|
322
324
|
const MESSAGING_ADMIN_TOOLS = new Set([
|
|
323
|
-
'workflow_create', 'workflow_list', 'workflow_get', 'workflow_toggle',
|
|
325
|
+
'workflow_create', 'workflow_list', 'workflow_get', 'workflow_toggle', 'workflow_run_now',
|
|
324
326
|
'bot_create', 'bot_list', 'bot_get', 'bot_update_prompt', 'bot_set_knowledge',
|
|
325
327
|
'ai_config_attach_index', 'ai_config_set_reply_format', 'call_pop_settings_get', 'call_pop_settings_set',
|
|
326
328
|
'crm_mobile_fields_get', 'crm_mobile_fields_set',
|
|
@@ -488,7 +490,7 @@ const TOOLS = [
|
|
|
488
490
|
},
|
|
489
491
|
{
|
|
490
492
|
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).",
|
|
493
|
+
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
494
|
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
493
495
|
},
|
|
494
496
|
{
|
|
@@ -508,6 +510,35 @@ const TOOLS = [
|
|
|
508
510
|
smsOnUnassignWarning: { type: 'boolean', description: 'Send an SMS on unassign warning' },
|
|
509
511
|
useProfilePhone: { type: 'boolean', description: 'Use the profile phone number for SMS alerts' },
|
|
510
512
|
smsAlertPhone: { type: 'string', description: 'Phone number for SMS alerts (E.164, e.g. +14155551234)' },
|
|
513
|
+
alertRouting: {
|
|
514
|
+
type: 'object',
|
|
515
|
+
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}',
|
|
516
|
+
properties: {
|
|
517
|
+
channels: { type: 'object', description: 'Keys: web, sms, whatsapp, appleMessages, email, phone (booleans)' },
|
|
518
|
+
handling: { type: 'object', description: 'Keys: mine, unassigned, bot, teammates (booleans, multi-select)' },
|
|
519
|
+
kinds: { type: 'object', description: 'Keys: conversations, tickets, calls, visitors (booleans)' },
|
|
520
|
+
ivrBotAlerts: { type: 'boolean', description: 'Alert for inbound IVR / phone bot chats (default off)' },
|
|
521
|
+
},
|
|
522
|
+
},
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
name: 'signature_policy_get',
|
|
528
|
+
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.",
|
|
529
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
name: 'signature_policy_set',
|
|
533
|
+
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.",
|
|
534
|
+
inputSchema: {
|
|
535
|
+
type: 'object',
|
|
536
|
+
properties: {
|
|
537
|
+
teamId: { type: 'integer', description: '0 = company-wide (default), otherwise the team id for a per-team override' },
|
|
538
|
+
mode: { type: 'string', enum: ['locked', 'company-default', 'personal-only', 'default'], description: 'locked | company-default | personal-only | default (follow company-wide)' },
|
|
539
|
+
corporateSignature: { type: ['string', 'null'], description: 'Company signature HTML, max 4000 characters. Empty or null clears it.' },
|
|
540
|
+
disclaimerEnabled: { type: ['boolean', 'null'], description: 'Turn the legal disclaimer on or off. null follows the company-wide choice. Off by default.' },
|
|
541
|
+
disclaimerText: { type: ['string', 'null'], description: 'Legal disclaimer text, max 2000 characters. Empty or null clears it.' },
|
|
511
542
|
},
|
|
512
543
|
},
|
|
513
544
|
},
|
|
@@ -1058,6 +1089,18 @@ const TOOLS = [
|
|
|
1058
1089
|
required: ['id', 'enabled'],
|
|
1059
1090
|
},
|
|
1060
1091
|
},
|
|
1092
|
+
{
|
|
1093
|
+
name: 'workflow_run_now',
|
|
1094
|
+
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.',
|
|
1095
|
+
inputSchema: {
|
|
1096
|
+
type: 'object',
|
|
1097
|
+
properties: {
|
|
1098
|
+
id: { type: 'number', description: 'Workflow ID' },
|
|
1099
|
+
context: { type: 'object', description: 'Optional shallow object of values to seed the run, e.g. {"customerId":"123"}' },
|
|
1100
|
+
},
|
|
1101
|
+
required: ['id'],
|
|
1102
|
+
},
|
|
1103
|
+
},
|
|
1061
1104
|
{
|
|
1062
1105
|
name: 'workflow_create',
|
|
1063
1106
|
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.',
|
|
@@ -1087,6 +1130,8 @@ Key node shapes:
|
|
|
1087
1130
|
propName:"myAnswerVar" } // variable that stores the result as JSON array string
|
|
1088
1131
|
• condition: { type:"action", actionType:"condition", conditionOptions:[...] }
|
|
1089
1132
|
• transferToAI: { type:"action", actionType:"transferToAI", aiConfigId:N }
|
|
1133
|
+
• sendSurvey: { type:"action", actionType:"sendSurvey", sendSurveyId:N, surveyDeliveryMode:"conversational"|"form", closeAfterSurvey:true|false, child:N }
|
|
1134
|
+
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
1135
|
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
1136
|
|
|
1092
1137
|
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 +1516,18 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
1471
1516
|
required: ['id'],
|
|
1472
1517
|
},
|
|
1473
1518
|
},
|
|
1519
|
+
{
|
|
1520
|
+
name: 'copilot_studio_ask',
|
|
1521
|
+
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.',
|
|
1522
|
+
inputSchema: {
|
|
1523
|
+
type: 'object',
|
|
1524
|
+
properties: {
|
|
1525
|
+
id: { type: 'number', description: 'Config ID from copilot_studio_list' },
|
|
1526
|
+
question: { type: 'string', description: 'A real data-bound question for the agent' },
|
|
1527
|
+
},
|
|
1528
|
+
required: ['id', 'question'],
|
|
1529
|
+
},
|
|
1530
|
+
},
|
|
1474
1531
|
|
|
1475
1532
|
// ── Monday.com field mapping / CRM search ────────────────────────────────
|
|
1476
1533
|
{
|
|
@@ -1661,6 +1718,11 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
1661
1718
|
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
1719
|
teamId: { type: 'number', description: 'Route new conversations to this team id (must be a real, non-deleted team on this site)' },
|
|
1663
1720
|
clearTeamId: { type: 'boolean', description: 'Clear the team override and fall back to the site default team' },
|
|
1721
|
+
prechatEnabled: { type: 'boolean', description: 'Ask the Teams user prechat questions before a conversation opens' },
|
|
1722
|
+
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'] } },
|
|
1723
|
+
quickReplies: { type: 'array', description: 'Replaces the quick-reply buttons (max 6, 40 chars each); empty array clears them', items: { type: 'string' } },
|
|
1724
|
+
postChatEnabled:{ type: 'boolean', description: 'Send postChatMessage when an agent closes the conversation' },
|
|
1725
|
+
postChatMessage:{ type: 'string', description: 'Post-chat text, {{user.name}} = first name, max 1000 chars; empty string clears it' },
|
|
1664
1726
|
},
|
|
1665
1727
|
required: [],
|
|
1666
1728
|
},
|
|
@@ -2381,6 +2443,24 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2381
2443
|
required: ['siteId', 'indexName'],
|
|
2382
2444
|
},
|
|
2383
2445
|
},
|
|
2446
|
+
{
|
|
2447
|
+
name: 'index_set_profile',
|
|
2448
|
+
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).',
|
|
2449
|
+
inputSchema: {
|
|
2450
|
+
type: 'object',
|
|
2451
|
+
properties: {
|
|
2452
|
+
siteId: { type: 'number', description: 'Target site ID' },
|
|
2453
|
+
indexName: { type: 'string', description: 'Index name on that site, e.g. "100140-troubleshooting"' },
|
|
2454
|
+
chunkStrategy: { type: 'string', enum: ['procedure', 'section'], description: 'Chunk strategy' },
|
|
2455
|
+
chunkSize: { type: 'number', description: 'Chunk size' },
|
|
2456
|
+
chunkOverlap: { type: 'number', description: 'Chunk overlap' },
|
|
2457
|
+
purpose: { type: 'string', enum: ['general', 'troubleshooting', 'catalog'], description: 'Index purpose' },
|
|
2458
|
+
searchSize: { type: 'number', description: 'Results per search' },
|
|
2459
|
+
excludeFromAllSources: { type: 'boolean', description: 'Keep out of the ["*"] all-sources search' },
|
|
2460
|
+
},
|
|
2461
|
+
required: ['siteId', 'indexName'],
|
|
2462
|
+
},
|
|
2463
|
+
},
|
|
2384
2464
|
{
|
|
2385
2465
|
name: 'uploaded_file_reindex',
|
|
2386
2466
|
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 +2706,29 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2626
2706
|
required: ['startDate', 'endDate'],
|
|
2627
2707
|
},
|
|
2628
2708
|
},
|
|
2709
|
+
{
|
|
2710
|
+
name: 'report_performance_by_country_get',
|
|
2711
|
+
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).',
|
|
2712
|
+
inputSchema: {
|
|
2713
|
+
type: 'object',
|
|
2714
|
+
properties: {
|
|
2715
|
+
startDate: { type: 'string', description: 'ISO 8601 start. Default: 30 days ago' },
|
|
2716
|
+
endDate: { type: 'string', description: 'ISO 8601 end. Default: now' },
|
|
2717
|
+
attribution: { type: 'string', enum: ['visitor', 'agent', 'group'], description: 'Default: visitor' },
|
|
2718
|
+
channels: { type: 'array', items: { type: 'string' }, description: 'Optional ConversationSource filter, e.g. ["Web","TwilioSms"]. Default: all channels' },
|
|
2719
|
+
},
|
|
2720
|
+
},
|
|
2721
|
+
},
|
|
2722
|
+
{
|
|
2723
|
+
name: 'report_performance_by_country_set_groups',
|
|
2724
|
+
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.',
|
|
2725
|
+
inputSchema: {
|
|
2726
|
+
type: 'object',
|
|
2727
|
+
properties: {
|
|
2728
|
+
map: { type: 'object', description: 'teamId to bucket label. Omit to just read.', additionalProperties: { type: 'string' } },
|
|
2729
|
+
},
|
|
2730
|
+
},
|
|
2731
|
+
},
|
|
2629
2732
|
{
|
|
2630
2733
|
name: 'report_campaign_performance',
|
|
2631
2734
|
description: 'Email campaign performance: open rate, click rate, delivery rate, complaint rate per campaign + aggregate KPIs.',
|
|
@@ -2906,7 +3009,9 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2906
3009
|
platform: { type: 'string', enum: ['v10', 'v20', 'admin'], description: 'Filter by platform' },
|
|
2907
3010
|
environment: { type: 'string', enum: ['Production', 'Staging', 'Development'] },
|
|
2908
3011
|
correlationId: { type: 'string', description: 'Exact correlation/trace ID filter' },
|
|
2909
|
-
last: { type: 'string', description: 'Time window: 30m, 2h, 1d (default: 2h)' },
|
|
3012
|
+
last: { type: 'string', description: 'Time window ending at `to`/now: 30m, 2h, 1d (default: 2h)' },
|
|
3013
|
+
from: { type: 'string', description: 'Window start: ISO timestamp (2026-10-20T00:00:00Z) or relative (3h). Overrides `last`.' },
|
|
3014
|
+
to: { type: 'string', description: 'Window end: ISO timestamp or relative (1h = 1h ago). Default: now.' },
|
|
2910
3015
|
take: { type: 'number', description: 'Max results (default: 50, max: 500)' },
|
|
2911
3016
|
regex: { type: 'boolean', description: 'Treat q/logger as a regex pattern (default: false)' },
|
|
2912
3017
|
},
|
|
@@ -3359,7 +3464,7 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
3359
3464
|
inputSchema: {
|
|
3360
3465
|
type: 'object',
|
|
3361
3466
|
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'] },
|
|
3467
|
+
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
3468
|
from: { type: 'string', description: 'yyyy-MM-dd start (default: 30 days ago).' },
|
|
3364
3469
|
to: { type: 'string', description: 'yyyy-MM-dd end, inclusive (default: today).' },
|
|
3365
3470
|
groupBy: { type: 'string', description: 'program_breakdown: website|team|platform|device|entrypage. sentiment_breakdown: website|team|agent|theme.' },
|
|
@@ -3384,16 +3489,17 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
3384
3489
|
},
|
|
3385
3490
|
{
|
|
3386
3491
|
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.',
|
|
3492
|
+
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
3493
|
inputSchema: {
|
|
3389
3494
|
type: 'object',
|
|
3390
3495
|
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).' },
|
|
3496
|
+
type: { type: 'string', description: 'Backfill type: csat, csat-demo, csat-demo-undo, history-classification-sample or history-classification.' },
|
|
3497
|
+
from: { type: 'string', description: 'yyyy-MM-dd start (required unless status=true).' },
|
|
3498
|
+
to: { type: 'string', description: 'yyyy-MM-dd end (required unless status=true).' },
|
|
3499
|
+
apply: { type: 'boolean', description: 'true = write. Default false (preview only). For history types this queues a background run.' },
|
|
3500
|
+
status: { type: 'boolean', description: 'true = return the latest queued/running/finished apply run for this type instead of starting anything.' },
|
|
3395
3501
|
},
|
|
3396
|
-
required: ['type'
|
|
3502
|
+
required: ['type'],
|
|
3397
3503
|
},
|
|
3398
3504
|
},
|
|
3399
3505
|
// -- Staff: schedule adherence by site + role (SupportTools-style: explicit siteId, never auth-derived) --
|
|
@@ -3561,6 +3667,17 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
3561
3667
|
required: ['siteId'],
|
|
3562
3668
|
},
|
|
3563
3669
|
},
|
|
3670
|
+
{
|
|
3671
|
+
name: 'avatar_session_readiness',
|
|
3672
|
+
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).',
|
|
3673
|
+
inputSchema: {
|
|
3674
|
+
type: 'object',
|
|
3675
|
+
properties: {
|
|
3676
|
+
siteId: { type: 'number', description: 'Site ID to check' },
|
|
3677
|
+
},
|
|
3678
|
+
required: ['siteId'],
|
|
3679
|
+
},
|
|
3680
|
+
},
|
|
3564
3681
|
{
|
|
3565
3682
|
name: 'avatar_set_config',
|
|
3566
3683
|
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 +3690,13 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
3573
3690
|
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
3691
|
voiceId: { type: 'string', description: 'ElevenLabs voice ID override. Omit to use the site default.' },
|
|
3575
3692
|
showCallMeOption: { type: 'boolean', description: 'Show the "Call me instead" option (requires EnableClickToCall).' },
|
|
3693
|
+
enabledOnAgentTransfer: { type: 'boolean', description: 'Keep the avatar running after a human agent joins the conversation (default off).' },
|
|
3576
3694
|
callbackConfirmWindowMinutes: { type: 'number', description: 'Minutes to confirm a callback request before it expires. Default 10.' },
|
|
3577
3695
|
displayName: { type: 'string', description: 'Visitor-facing avatar name shown on the badge (e.g. "Moshky"). Empty string resets to "AI".' },
|
|
3578
3696
|
showBadge: { type: 'boolean', description: 'Show the name badge over the avatar video. Default true.' },
|
|
3579
3697
|
sizePx: { type: 'number', description: 'Max avatar panel width in px (160-640, square). Default 360.' },
|
|
3580
3698
|
allowVisitorCollapse: { type: 'boolean', description: 'Let visitors hide or collapse the avatar panel (up/down arrow and X). Default true.' },
|
|
3699
|
+
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
3700
|
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
3701
|
motionIntensityPercent: { type: 'integer', minimum: 25, maximum: 150, description: 'Body motion strength, whole percent 25-150 (default 100); multiplies every motion amplitude.' },
|
|
3583
3702
|
maxAvatarMinutesPerMonth: { type: 'number', description: 'Monthly avatar video minutes cap for the site. Whole number 1-100000; never unlimited (0/negative rejected).' },
|
|
@@ -3675,6 +3794,31 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
3675
3794
|
description: 'Reset ALL group and workflow overrides for the calling site back to the site default.',
|
|
3676
3795
|
inputSchema: { type: 'object', properties: {} },
|
|
3677
3796
|
},
|
|
3797
|
+
{
|
|
3798
|
+
name: 'avatar_set_workflow_switch',
|
|
3799
|
+
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.',
|
|
3800
|
+
inputSchema: {
|
|
3801
|
+
type: 'object',
|
|
3802
|
+
properties: {
|
|
3803
|
+
workflowId: { type: 'number', description: 'Workflow id (must belong to the calling site).' },
|
|
3804
|
+
mode: { type: 'string', enum: ['on', 'off', 'rules'], description: 'on | off | rules' },
|
|
3805
|
+
},
|
|
3806
|
+
required: ['workflowId', 'mode'],
|
|
3807
|
+
},
|
|
3808
|
+
},
|
|
3809
|
+
{
|
|
3810
|
+
name: 'avatar_bulk_set_switch',
|
|
3811
|
+
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.',
|
|
3812
|
+
inputSchema: {
|
|
3813
|
+
type: 'object',
|
|
3814
|
+
properties: {
|
|
3815
|
+
mode: { type: 'string', enum: ['on', 'off', 'rules'], description: 'on | off | rules' },
|
|
3816
|
+
workflowIds: { type: 'array', items: { type: 'number' }, description: 'Workflow ids of the calling site.' },
|
|
3817
|
+
groupIds: { type: 'array', items: { type: 'number' }, description: 'Team (group) ids of the calling site.' },
|
|
3818
|
+
},
|
|
3819
|
+
required: ['mode'],
|
|
3820
|
+
},
|
|
3821
|
+
},
|
|
3678
3822
|
|
|
3679
3823
|
// ── Calendly integration management (superadmin) ──────────────────────────
|
|
3680
3824
|
{
|
|
@@ -4028,6 +4172,26 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
4028
4172
|
required: ['shop'],
|
|
4029
4173
|
},
|
|
4030
4174
|
},
|
|
4175
|
+
{
|
|
4176
|
+
name: 'shopify_get_write_policy',
|
|
4177
|
+
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.',
|
|
4178
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
4179
|
+
},
|
|
4180
|
+
{
|
|
4181
|
+
name: 'shopify_set_write_policy',
|
|
4182
|
+
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.',
|
|
4183
|
+
inputSchema: {
|
|
4184
|
+
type: 'object',
|
|
4185
|
+
properties: {
|
|
4186
|
+
enableShopifyWrite: { type: 'boolean', description: 'Master toggle for the Shopify write skills.' },
|
|
4187
|
+
requireConfirmation: { type: 'boolean', description: 'Require customer confirmation before a write skill executes.' },
|
|
4188
|
+
maxRefundAmount: { type: ['number', 'null'], description: 'Max refund amount per call. null = default (250).' },
|
|
4189
|
+
maxDiscountPercent: { type: ['number', 'null'], description: 'Max percentage discount. null = default (20).' },
|
|
4190
|
+
maxDiscountFixedAmount: { type: ['number', 'null'], description: 'Max fixed-amount discount. null = default (50).' },
|
|
4191
|
+
},
|
|
4192
|
+
required: [],
|
|
4193
|
+
},
|
|
4194
|
+
},
|
|
4031
4195
|
{
|
|
4032
4196
|
name: 'appointments_manage',
|
|
4033
4197
|
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 +4384,9 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
4220
4384
|
properties: {
|
|
4221
4385
|
storeUrl: { type: 'string', description: 'Magento store base URL, e.g. "https://mystore.com"' },
|
|
4222
4386
|
accessToken: { type: 'string', description: 'Magento REST API bearer token (Admin Integration token)' },
|
|
4387
|
+
configId: { type: 'number', description: 'Test an already-connected store with its stored token instead (storeUrl/accessToken then not needed)' },
|
|
4223
4388
|
},
|
|
4224
|
-
required: [
|
|
4389
|
+
required: [],
|
|
4225
4390
|
},
|
|
4226
4391
|
},
|
|
4227
4392
|
{
|
|
@@ -4296,6 +4461,18 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
4296
4461
|
},
|
|
4297
4462
|
|
|
4298
4463
|
// ── Business actions + SAP (one definition, many surfaces) ─────────────────────
|
|
4464
|
+
{
|
|
4465
|
+
name: 'salesforce_diagnose',
|
|
4466
|
+
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.',
|
|
4467
|
+
inputSchema: {
|
|
4468
|
+
type: 'object',
|
|
4469
|
+
properties: {
|
|
4470
|
+
siteId: { type: 'number', description: 'Site to diagnose (e.g. 1032)' },
|
|
4471
|
+
deep: { type: 'boolean', description: 'Probe every object (default true). false = configuration + token only.' },
|
|
4472
|
+
},
|
|
4473
|
+
required: ['siteId'],
|
|
4474
|
+
},
|
|
4475
|
+
},
|
|
4299
4476
|
{
|
|
4300
4477
|
name: 'sap_diagnose',
|
|
4301
4478
|
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 +4581,35 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
4404
4581
|
},
|
|
4405
4582
|
},
|
|
4406
4583
|
|
|
4584
|
+
{
|
|
4585
|
+
name: 'netsuite_suiteapp_download',
|
|
4586
|
+
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.',
|
|
4587
|
+
inputSchema: { type: 'object', properties: { includeKey: { type: 'boolean', description: 'Default true.' }, outputPath: { type: 'string', description: 'Local file path to write (default: temp dir).' } }, required: [] },
|
|
4588
|
+
},
|
|
4589
|
+
{
|
|
4590
|
+
name: 'netsuite_config_delete',
|
|
4591
|
+
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.',
|
|
4592
|
+
inputSchema: {
|
|
4593
|
+
type: 'object',
|
|
4594
|
+
properties: { configId: { type: 'number', description: 'NetSuite config ID (see netsuite_list_configs).' } },
|
|
4595
|
+
required: ['configId'],
|
|
4596
|
+
},
|
|
4597
|
+
},
|
|
4598
|
+
{
|
|
4599
|
+
name: 'netsuite_m2m_clear_legacy',
|
|
4600
|
+
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.',
|
|
4601
|
+
inputSchema: {
|
|
4602
|
+
type: 'object',
|
|
4603
|
+
properties: { configId: { type: 'number', description: 'NetSuite config ID (see netsuite_list_configs).' } },
|
|
4604
|
+
required: ['configId'],
|
|
4605
|
+
},
|
|
4606
|
+
},
|
|
4607
|
+
{
|
|
4608
|
+
name: 'netsuite_embed_status',
|
|
4609
|
+
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.',
|
|
4610
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
4611
|
+
},
|
|
4612
|
+
|
|
4407
4613
|
{
|
|
4408
4614
|
name: 'netsuite_m2m_init',
|
|
4409
4615
|
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.',
|
|
@@ -4897,6 +5103,11 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
4897
5103
|
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
5104
|
inputSchema: { type: 'object', properties: { siteId: { type: 'number', description: 'Optional site ID to show which items are on' } } },
|
|
4899
5105
|
},
|
|
5106
|
+
{
|
|
5107
|
+
name: 'entitlement_report_packages',
|
|
5108
|
+
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.',
|
|
5109
|
+
inputSchema: { type: 'object', properties: { siteId: { type: 'number', description: 'Optional site ID to show which reports are on' } } },
|
|
5110
|
+
},
|
|
4900
5111
|
{
|
|
4901
5112
|
name: 'entitlement_set_override',
|
|
4902
5113
|
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.',
|
|
@@ -5091,11 +5302,11 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
5091
5302
|
},
|
|
5092
5303
|
{
|
|
5093
5304
|
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"
|
|
5305
|
+
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
5306
|
inputSchema: {
|
|
5096
5307
|
type: 'object',
|
|
5097
5308
|
properties: {
|
|
5098
|
-
triageMode: { type: 'string', enum: ['off', 'economy'], description: 'Optional. "economy" skips the AI call for obvious messages; "off"
|
|
5309
|
+
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
5310
|
labels: {
|
|
5100
5311
|
type: 'array',
|
|
5101
5312
|
description: 'Labels to update.',
|
|
@@ -5201,7 +5412,7 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
5201
5412
|
type: 'object',
|
|
5202
5413
|
properties: {
|
|
5203
5414
|
start: { type: 'string', description: 'ISO 8601 start of range.' },
|
|
5204
|
-
end: { type: 'string', description: '
|
|
5415
|
+
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
5416
|
},
|
|
5206
5417
|
required: ['start', 'end'],
|
|
5207
5418
|
},
|
|
@@ -5331,6 +5542,28 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
5331
5542
|
},
|
|
5332
5543
|
},
|
|
5333
5544
|
|
|
5545
|
+
// -- Apple Pay display settings --
|
|
5546
|
+
// Backend: velaro-messaging ApplePayConfigsController (GET/POST ApplePayConfig, PUT ApplePayConfig/{id}).
|
|
5547
|
+
// Display + test mode only; merchant id / certificate (which affect the charge) are intentionally not settable here.
|
|
5548
|
+
{
|
|
5549
|
+
name: 'apple_pay_get_config',
|
|
5550
|
+
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.',
|
|
5551
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
5552
|
+
},
|
|
5553
|
+
{
|
|
5554
|
+
name: 'apple_pay_update_config',
|
|
5555
|
+
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.',
|
|
5556
|
+
inputSchema: {
|
|
5557
|
+
type: 'object',
|
|
5558
|
+
properties: {
|
|
5559
|
+
merchantDisplayName: { type: 'string', description: 'Business name shown on the Apple Pay sheet' },
|
|
5560
|
+
isTestMode: { type: 'boolean', description: 'Sandbox mode: Velaro Stripe test account, no real money moves, real Wallet cards decline' },
|
|
5561
|
+
bubbleLogoBase64: { type: 'string', description: 'PNG/JPEG base64 (max 500 KB) for the payment bubble. Empty string clears it back to the default image.' },
|
|
5562
|
+
},
|
|
5563
|
+
required: [],
|
|
5564
|
+
},
|
|
5565
|
+
},
|
|
5566
|
+
|
|
5334
5567
|
// -- Recruiting Bot --
|
|
5335
5568
|
// Backend: server/Velaro.Messaging/Controllers/RecruitingController.cs.
|
|
5336
5569
|
// [EnableRecruitingBot] gated server-side; siteId always derives from the
|
|
@@ -6036,6 +6269,14 @@ function parseBusinessHours(input) {
|
|
|
6036
6269
|
|
|
6037
6270
|
// ── Tool handlers ─────────────────────────────────────────────────────────────
|
|
6038
6271
|
|
|
6272
|
+
// Report end params are exclusive server-side: a plain YYYY-MM-DD end means "through that whole
|
|
6273
|
+
// day", so send the start of the next day (mirrors cli/lib/report-range.js exclusiveEndParam).
|
|
6274
|
+
function exclusiveEnd(raw) {
|
|
6275
|
+
if (raw == null || raw === '') return raw;
|
|
6276
|
+
const d = new Date(raw);
|
|
6277
|
+
if (Number.isNaN(d.getTime()) || !/^\d{4}-\d{2}-\d{2}$/.test(String(raw).trim())) return raw;
|
|
6278
|
+
return new Date(d.getTime() + 24 * 60 * 60 * 1000).toISOString();
|
|
6279
|
+
}
|
|
6039
6280
|
// -- Agent inactivity chain helpers (velaro-messaging PR #1471, v20 ONLY) ------------------------
|
|
6040
6281
|
// Terminal action ints are persisted by messaging (append-only enum). The API returns/accepts the NAME.
|
|
6041
6282
|
const TERMINAL_ACTION_NAMES = ['Survey','CreateTicket','SurveyAndCreateTicket','Queue','OfflineMessage','NoOp','EndChat','NotifyManager','RunWorkflow','ReturnToBot'];
|
|
@@ -6166,7 +6407,9 @@ async function handleTool(name, args) {
|
|
|
6166
6407
|
}
|
|
6167
6408
|
|
|
6168
6409
|
case 'notifications_get': {
|
|
6169
|
-
|
|
6410
|
+
const base = await messagingGet('/NotificationSettings');
|
|
6411
|
+
const routing = await messagingGet('/api/v1/alert-routing').catch(() => null);
|
|
6412
|
+
return JSON.stringify({ ...base, alertRouting: routing?.data ?? null }, null, 2);
|
|
6170
6413
|
}
|
|
6171
6414
|
|
|
6172
6415
|
case 'notifications_set': {
|
|
@@ -6180,7 +6423,47 @@ async function handleTool(name, args) {
|
|
|
6180
6423
|
if (args[f] !== undefined) current[f] = args[f];
|
|
6181
6424
|
}
|
|
6182
6425
|
await messagingPost('/NotificationSettings', current);
|
|
6183
|
-
|
|
6426
|
+
if (args.alertRouting && typeof args.alertRouting === 'object') {
|
|
6427
|
+
await messagingPut('/api/v1/alert-routing', args.alertRouting);
|
|
6428
|
+
}
|
|
6429
|
+
const routing = await messagingGet('/api/v1/alert-routing').catch(() => null);
|
|
6430
|
+
return JSON.stringify({ ...(await messagingGet('/NotificationSettings')), alertRouting: routing?.data ?? null }, null, 2);
|
|
6431
|
+
}
|
|
6432
|
+
|
|
6433
|
+
case 'signature_policy_get': {
|
|
6434
|
+
return JSON.stringify(await messagingGet('/Signatures/policy'), null, 2);
|
|
6435
|
+
}
|
|
6436
|
+
|
|
6437
|
+
case 'signature_policy_set': {
|
|
6438
|
+
// PUT replaces the row and silently turns an unknown mode into "follow company-wide", so validate and merge here.
|
|
6439
|
+
// Same rules as cli/lib/signature-policy.js (keep the two in step).
|
|
6440
|
+
const modes = { 'locked': 'admin_locked', 'company-default': 'company_default', 'personal-only': 'personal_only' };
|
|
6441
|
+
const teamId = args.teamId === undefined || args.teamId === null ? 0 : args.teamId;
|
|
6442
|
+
if (!Number.isInteger(teamId) || teamId < 0) throw new Error('teamId must be 0 (company-wide) or a team id');
|
|
6443
|
+
const data = await messagingGet('/Signatures/policy');
|
|
6444
|
+
const cur = (Array.isArray(data?.policies) ? data.policies : []).find((p) => p.teamId === teamId) ?? {};
|
|
6445
|
+
const body = {
|
|
6446
|
+
teamId,
|
|
6447
|
+
mode: cur.mode ?? null,
|
|
6448
|
+
corporateSignature: cur.corporateSignature || null,
|
|
6449
|
+
disclaimerEnabled: cur.disclaimerEnabled ?? null,
|
|
6450
|
+
disclaimerText: cur.disclaimerText || null,
|
|
6451
|
+
};
|
|
6452
|
+
if (args.mode !== undefined) {
|
|
6453
|
+
if (args.mode === null || args.mode === 'default') body.mode = null;
|
|
6454
|
+
else if (modes[args.mode]) body.mode = modes[args.mode];
|
|
6455
|
+
else throw new Error('mode must be locked, company-default, personal-only or default');
|
|
6456
|
+
}
|
|
6457
|
+
if (args.corporateSignature !== undefined) body.corporateSignature = args.corporateSignature || null;
|
|
6458
|
+
if (args.disclaimerEnabled !== undefined) {
|
|
6459
|
+
if (args.disclaimerEnabled !== null && typeof args.disclaimerEnabled !== 'boolean') throw new Error('disclaimerEnabled must be true, false or null');
|
|
6460
|
+
body.disclaimerEnabled = args.disclaimerEnabled;
|
|
6461
|
+
}
|
|
6462
|
+
if (args.disclaimerText !== undefined) body.disclaimerText = args.disclaimerText || null;
|
|
6463
|
+
if (body.corporateSignature && body.corporateSignature.length > 4000) throw new Error('corporateSignature is limited to 4000 characters');
|
|
6464
|
+
if (body.disclaimerText && body.disclaimerText.length > 2000) throw new Error('disclaimerText is limited to 2000 characters');
|
|
6465
|
+
await messagingPut('/Signatures/policy', body);
|
|
6466
|
+
return JSON.stringify(await messagingGet('/Signatures/policy'), null, 2);
|
|
6184
6467
|
}
|
|
6185
6468
|
|
|
6186
6469
|
case 'admin_startup_diagnostics': {
|
|
@@ -6469,7 +6752,7 @@ async function handleTool(name, args) {
|
|
|
6469
6752
|
case 'chat_intelligence_themes': {
|
|
6470
6753
|
const qs = new URLSearchParams();
|
|
6471
6754
|
if (args.startDate) qs.set('startDate', args.startDate);
|
|
6472
|
-
if (args.endDate) qs.set('endDate', args.endDate);
|
|
6755
|
+
if (args.endDate) qs.set('endDate', exclusiveEnd(args.endDate));
|
|
6473
6756
|
qs.set('groupBy', args.groupBy ?? 'month');
|
|
6474
6757
|
if (args.deploymentId != null) qs.set('deploymentId', String(args.deploymentId));
|
|
6475
6758
|
if (args.teamId != null) qs.set('teamId', String(args.teamId));
|
|
@@ -6563,6 +6846,8 @@ async function handleTool(name, args) {
|
|
|
6563
6846
|
|
|
6564
6847
|
case 'workflow_toggle':
|
|
6565
6848
|
return await rpc('workflow_toggle', args);
|
|
6849
|
+
case 'workflow_run_now':
|
|
6850
|
+
return await rpc('workflow_run_now', args);
|
|
6566
6851
|
|
|
6567
6852
|
case 'workflow_create':
|
|
6568
6853
|
return await rpc('workflow_create', args);
|
|
@@ -6942,6 +7227,13 @@ async function handleTool(name, args) {
|
|
|
6942
7227
|
return `Connection test FAILED for agent ${args.id}: ${result?.error ?? 'unknown error'}`;
|
|
6943
7228
|
}
|
|
6944
7229
|
|
|
7230
|
+
case 'copilot_studio_ask': {
|
|
7231
|
+
if (!args.id || !args.question) return 'id and question are required.';
|
|
7232
|
+
const r = await api('POST', '/CopilotStudio/message', { configId: args.id, message: args.question });
|
|
7233
|
+
return `kind: ${r?.kind}
|
|
7234
|
+
reply: ${r?.reply}`;
|
|
7235
|
+
}
|
|
7236
|
+
|
|
6945
7237
|
// ── Monday.com field mapping / CRM search ──────────────────────────────
|
|
6946
7238
|
// Wired to MondayController.cs actions: GetBoardColumns, GetFieldMapping,
|
|
6947
7239
|
// UpdateFieldMapping, SearchContacts (routes under /Monday/...).
|
|
@@ -7094,7 +7386,7 @@ async function handleTool(name, args) {
|
|
|
7094
7386
|
|
|
7095
7387
|
case 'teams_channel_config_update': {
|
|
7096
7388
|
const body = {};
|
|
7097
|
-
for (const k of ['enabled','welcomeMessage','teamId','clearTeamId'])
|
|
7389
|
+
for (const k of ['enabled','welcomeMessage','teamId','clearTeamId','prechatEnabled','prechatFields','quickReplies','postChatEnabled','postChatMessage'])
|
|
7098
7390
|
if (args[k] !== undefined) body[k] = args[k];
|
|
7099
7391
|
return await api('PUT', '/TeamsBot/ChannelConfig', body);
|
|
7100
7392
|
}
|
|
@@ -8066,6 +8358,17 @@ async function handleTool(name, args) {
|
|
|
8066
8358
|
return `Index ready on site ${args.siteId}: ${idx?.name} (id=${idx?.id}, purpose=${idx?.purpose}, excludeFromAllSources=${!!idx?.excludeFromAllSources}).`;
|
|
8067
8359
|
}
|
|
8068
8360
|
|
|
8361
|
+
case 'index_set_profile': {
|
|
8362
|
+
if (!args.siteId || !args.indexName) return 'siteId and indexName are required.';
|
|
8363
|
+
const body = { indexName: args.indexName };
|
|
8364
|
+
for (const k of ['chunkStrategy', 'chunkSize', 'chunkOverlap', 'purpose', 'searchSize', 'excludeFromAllSources']) {
|
|
8365
|
+
if (args[k] !== undefined) body[k] = args[k];
|
|
8366
|
+
}
|
|
8367
|
+
const idx = await messagingRequest('PUT', `/KnowledgeSetup/Staff/${encodeURIComponent(args.siteId)}/index-profile`, body);
|
|
8368
|
+
if (idx?.error) return `Set index profile error: ${idx.error}`;
|
|
8369
|
+
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.`;
|
|
8370
|
+
}
|
|
8371
|
+
|
|
8069
8372
|
case 'uploaded_files_list': {
|
|
8070
8373
|
const files = await messagingRequest('GET', '/uploadedFiles/list/AI');
|
|
8071
8374
|
if (!Array.isArray(files) || !files.length) return 'No AI training files uploaded for this site.';
|
|
@@ -8089,7 +8392,7 @@ async function handleTool(name, args) {
|
|
|
8089
8392
|
case 'service_level_report': {
|
|
8090
8393
|
const qs = [
|
|
8091
8394
|
args.startDate ? `startDate=${args.startDate}` : '',
|
|
8092
|
-
args.endDate ? `endDate=${args.endDate}` : '',
|
|
8395
|
+
args.endDate ? `endDate=${encodeURIComponent(exclusiveEnd(args.endDate))}` : '',
|
|
8093
8396
|
args.granularity ? `granularity=${args.granularity}` : '',
|
|
8094
8397
|
].filter(Boolean).join('&');
|
|
8095
8398
|
const r = await api('GET', `/ServiceLevel${qs ? '?' + qs : ''}`);
|
|
@@ -8139,7 +8442,7 @@ async function handleTool(name, args) {
|
|
|
8139
8442
|
case 'report_agent_utilization': {
|
|
8140
8443
|
const qs = [
|
|
8141
8444
|
args.startDate ? `startDate=${args.startDate}` : '',
|
|
8142
|
-
args.endDate ? `endDate=${args.endDate}` : '',
|
|
8445
|
+
args.endDate ? `endDate=${encodeURIComponent(exclusiveEnd(args.endDate))}` : '',
|
|
8143
8446
|
].filter(Boolean).join('&');
|
|
8144
8447
|
const rows = await api('GET', `/Reports/AgentUtilization${qs ? '?' + qs : ''}`);
|
|
8145
8448
|
if (!Array.isArray(rows) || !rows.length) return 'No agent utilization data for this period.';
|
|
@@ -8260,10 +8563,36 @@ async function handleTool(name, args) {
|
|
|
8260
8563
|
return `Export queued (id ${result.exportId}). ${result.message}`;
|
|
8261
8564
|
}
|
|
8262
8565
|
|
|
8566
|
+
case 'report_performance_by_country_get': {
|
|
8567
|
+
const end = args.endDate ? new Date(args.endDate) : new Date();
|
|
8568
|
+
const start = args.startDate ? new Date(args.startDate) : new Date(end.getTime() - 30 * 86400000);
|
|
8569
|
+
if (isNaN(start) || isNaN(end)) return 'Error: startDate/endDate must be valid ISO 8601 dates.';
|
|
8570
|
+
const r = await messagingPost('/reports/performance-by-country/query', {
|
|
8571
|
+
start: start.toISOString(), end: end.toISOString(),
|
|
8572
|
+
attribution: args.attribution || 'visitor', channels: Array.isArray(args.channels) ? args.channels : [],
|
|
8573
|
+
});
|
|
8574
|
+
const f = (v, s = '') => (v == null ? '-' : `${v}${s}`);
|
|
8575
|
+
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, '%')}`;
|
|
8576
|
+
return [`=== Performance by Country (${r.attribution}) ===`, ...(r.buckets || []).map(row), '', 'By agent:',
|
|
8577
|
+
...((r.agents || []).map(a => row(a) + (a.agentCountryNotSet ? ' [agent country not set]' : '')))].join('\n');
|
|
8578
|
+
}
|
|
8579
|
+
|
|
8580
|
+
case 'report_performance_by_country_set_groups': {
|
|
8581
|
+
const groups = await messagingGet('/reports/performance-by-country/groups');
|
|
8582
|
+
let current;
|
|
8583
|
+
if (args.map && typeof args.map === 'object') {
|
|
8584
|
+
current = await messagingPut('/reports/performance-by-country/group-map', { map: args.map });
|
|
8585
|
+
} else {
|
|
8586
|
+
current = await messagingGet('/reports/performance-by-country/group-map');
|
|
8587
|
+
}
|
|
8588
|
+
const map = current?.map ?? {};
|
|
8589
|
+
return ['Groups:', ...(Array.isArray(groups) ? groups : []).map(g => ` ${g.id} ${g.name} -> ${map[g.id] ?? '(unmapped, reports as Other)'}`)].join('\n');
|
|
8590
|
+
}
|
|
8591
|
+
|
|
8263
8592
|
case 'call_center_report': {
|
|
8264
8593
|
const qs = [
|
|
8265
8594
|
args.startDate ? `start=${args.startDate}` : '',
|
|
8266
|
-
args.endDate ? `end=${args.endDate}` : '',
|
|
8595
|
+
args.endDate ? `end=${encodeURIComponent(exclusiveEnd(args.endDate))}` : '',
|
|
8267
8596
|
].filter(Boolean).join('&');
|
|
8268
8597
|
const r = await api('GET', `/CallCenterAnalytics/Report${qs ? '?' + qs : ''}`);
|
|
8269
8598
|
const v = r.volume ?? {};
|
|
@@ -8301,7 +8630,7 @@ async function handleTool(name, args) {
|
|
|
8301
8630
|
case 'call_center_team_breakdown': {
|
|
8302
8631
|
const qs = [
|
|
8303
8632
|
args.startDate ? `start=${args.startDate}` : '',
|
|
8304
|
-
args.endDate ? `end=${args.endDate}` : '',
|
|
8633
|
+
args.endDate ? `end=${encodeURIComponent(exclusiveEnd(args.endDate))}` : '',
|
|
8305
8634
|
args.slaThresholdSeconds ? `slaThresholdSeconds=${args.slaThresholdSeconds}` : '',
|
|
8306
8635
|
].filter(Boolean).join('&');
|
|
8307
8636
|
let r;
|
|
@@ -8324,7 +8653,7 @@ async function handleTool(name, args) {
|
|
|
8324
8653
|
case 'call_center_channel_breakdown': {
|
|
8325
8654
|
const qs = [
|
|
8326
8655
|
args.startDate ? `start=${args.startDate}` : '',
|
|
8327
|
-
args.endDate ? `end=${args.endDate}` : '',
|
|
8656
|
+
args.endDate ? `end=${encodeURIComponent(exclusiveEnd(args.endDate))}` : '',
|
|
8328
8657
|
args.slaThresholdSeconds ? `slaThresholdSeconds=${args.slaThresholdSeconds}` : '',
|
|
8329
8658
|
].filter(Boolean).join('&');
|
|
8330
8659
|
let r;
|
|
@@ -8831,23 +9160,13 @@ async function handleTool(name, args) {
|
|
|
8831
9160
|
}
|
|
8832
9161
|
|
|
8833
9162
|
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');
|
|
9163
|
+
const { from, to } = resolveLogWindow(args);
|
|
9164
|
+
const params = buildRawLogQuery(args, from, to);
|
|
8846
9165
|
const resp = await messagingApi(`/superadmin/logs/search?${params}`, { timeoutMs: 40000 });
|
|
8847
9166
|
const entries = extractLogEntries(resp);
|
|
8848
9167
|
const meta = describeRawMeta(resp); // searchPath/scannedRows/truncated: a 0 or short list is never ambiguous
|
|
8849
9168
|
if (!entries.length) return `No results. ${meta}`;
|
|
8850
|
-
const lines = [`${entries.length} result(s) from ${from.toISOString().slice(0, 16)} UTC. ${meta}\n`];
|
|
9169
|
+
const lines = [`${entries.length} result(s) from ${from.toISOString().slice(0, 16)} to ${to.toISOString().slice(0, 16)} UTC. ${meta}\n`];
|
|
8851
9170
|
for (const e of entries) {
|
|
8852
9171
|
const ts = new Date(e.timestamp ?? e.LogTimestamp).toISOString().replace('T', ' ').slice(0, 19);
|
|
8853
9172
|
const lvl = (e.Level ?? e.level ?? '?').padEnd(5);
|
|
@@ -9248,7 +9567,7 @@ async function handleTool(name, args) {
|
|
|
9248
9567
|
coaching_queue: 'coaching-queue', sentiment_breakdown: 'sentiment-breakdown', topic_radar: 'topic-radar',
|
|
9249
9568
|
weekly_brief: 'weekly-brief', weekly_brief_send_test: 'weekly-brief/send-test',
|
|
9250
9569
|
repeat_contact: 'repeat-contact', demand_coverage: 'demand-coverage', bot_containment: 'bot-containment',
|
|
9251
|
-
effort_index: 'effort-index', promise_ledger: 'promise-ledger',
|
|
9570
|
+
effort_index: 'effort-index', promise_ledger: 'promise-ledger', new_agent_ramp: 'new-agent-ramp',
|
|
9252
9571
|
};
|
|
9253
9572
|
if (args.report === 'promise_dismiss') {
|
|
9254
9573
|
if (!args.promiseId) throw new Error('promiseId is required for promise_dismiss');
|
|
@@ -9325,8 +9644,11 @@ async function handleTool(name, args) {
|
|
|
9325
9644
|
}
|
|
9326
9645
|
|
|
9327
9646
|
case 'report_backfill': {
|
|
9647
|
+
if (args.status === true) return JSON.stringify(await messagingGet(`/Reports/backfill/${encodeURIComponent(args.type)}/status`), null, 2);
|
|
9648
|
+
if (!args.from || !args.to) throw new Error('from and to are required (yyyy-MM-dd) unless status=true');
|
|
9328
9649
|
const qs = new URLSearchParams({ from: String(args.from), to: String(args.to), apply: String(args.apply === true) });
|
|
9329
9650
|
const r = await messagingPost(`/Reports/backfill/${encodeURIComponent(args.type)}?${qs}`);
|
|
9651
|
+
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
9652
|
return (args.apply === true ? 'Backfill APPLIED\n' : 'Backfill PREVIEW (nothing written; pass apply=true to write)\n') + JSON.stringify(r, null, 2);
|
|
9331
9653
|
}
|
|
9332
9654
|
|
|
@@ -9421,15 +9743,25 @@ async function handleTool(name, args) {
|
|
|
9421
9743
|
].join('\n');
|
|
9422
9744
|
}
|
|
9423
9745
|
|
|
9746
|
+
case 'avatar_session_readiness': {
|
|
9747
|
+
const data = await messagingRequest('GET', `/avatar/diagnostics/session-readiness?siteId=${encodeURIComponent(args.siteId)}`);
|
|
9748
|
+
return JSON.stringify(data, null, 2);
|
|
9749
|
+
}
|
|
9750
|
+
|
|
9424
9751
|
case 'avatar_get_config': {
|
|
9425
9752
|
const data = await api('GET', `/Avatar/superadmin/status?siteId=${args.siteId}`);
|
|
9426
|
-
|
|
9753
|
+
let heyGenLine = 'unknown';
|
|
9754
|
+
try { const ent = await api('GET', `/Entitlements/${args.siteId}`); heyGenLine = ent?.features?.EnableAvatarHeyGen ? 'yes' : 'no'; } catch { /* entitlement lookup is best-effort */ }
|
|
9755
|
+
if (!data.configured) return `Site ${args.siteId}: AI Video Avatar NOT configured (no saved config). HeyGen entitled: ${heyGenLine}`;
|
|
9427
9756
|
return [
|
|
9428
9757
|
`Site ${args.siteId}: AI Video Avatar ${data.enabled ? 'ENABLED' : 'configured but DISABLED'}`,
|
|
9429
9758
|
` Provider: ${data.providerSlug}`,
|
|
9430
9759
|
` Avatar ID: ${data.avatarId || '(provider default)'}`,
|
|
9760
|
+
` Face per provider: ${data.avatarIdByProvider && Object.keys(data.avatarIdByProvider).length ? JSON.stringify(data.avatarIdByProvider) : '(none saved)'} (read-only)`,
|
|
9761
|
+
` Keep running on agent join: ${data.enabledOnAgentTransfer ? 'yes' : 'no'}`,
|
|
9431
9762
|
` Voice ID: ${data.voiceId || '(site default)'}`,
|
|
9432
9763
|
` Enabled: ${data.enabled}`,
|
|
9764
|
+
` HeyGen entitled: ${heyGenLine}`,
|
|
9433
9765
|
` "Call me": ${data.showCallMeOption}`,
|
|
9434
9766
|
` Callback window: ${data.callbackConfirmWindowMinutes} min`,
|
|
9435
9767
|
` Display name: ${data.displayName || '(AI)'}`,
|
|
@@ -9446,12 +9778,17 @@ async function handleTool(name, args) {
|
|
|
9446
9778
|
if (args.voiceId !== undefined) body.voiceId = args.voiceId;
|
|
9447
9779
|
if (args.enabled !== undefined) body.enabled = args.enabled;
|
|
9448
9780
|
if (args.showCallMeOption !== undefined) body.showCallMeOption = args.showCallMeOption;
|
|
9781
|
+
if (args.enabledOnAgentTransfer !== undefined) body.enabledOnAgentTransfer = args.enabledOnAgentTransfer;
|
|
9449
9782
|
if (args.callbackConfirmWindowMinutes !== undefined) body.callbackConfirmWindowMinutes = args.callbackConfirmWindowMinutes;
|
|
9450
9783
|
if (args.displayName !== undefined) body.displayName = args.displayName;
|
|
9451
9784
|
if (args.showBadge !== undefined) body.showBadge = args.showBadge;
|
|
9452
9785
|
if (args.sizePx !== undefined) body.sizePx = args.sizePx;
|
|
9453
9786
|
if (args.allowVisitorCollapse !== undefined) body.allowVisitorCollapse = args.allowVisitorCollapse;
|
|
9787
|
+
if (args.heyGenEnabled !== undefined) {
|
|
9788
|
+
await api('POST', `/Entitlements/${args.siteId}/override`, { featureKey: 'EnableAvatarHeyGen', value: args.heyGenEnabled, reason: 'avatar_set_config heyGenEnabled' });
|
|
9789
|
+
}
|
|
9454
9790
|
if (args.motionLevel !== undefined) body.motionLevel = parseAvatarMotionLevel(args.motionLevel);
|
|
9791
|
+
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
9792
|
if (args.motionIntensityPercent !== undefined) {
|
|
9456
9793
|
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
9794
|
body.motionIntensityPercent = args.motionIntensityPercent;
|
|
@@ -9485,6 +9822,22 @@ async function handleTool(name, args) {
|
|
|
9485
9822
|
return JSON.stringify(await messagingRequest('PUT', `/avatar/workflows/${Number(args.workflowId)}/override`, avatarOverrideBody(args)), null, 2);
|
|
9486
9823
|
case 'avatar_reset_workflow':
|
|
9487
9824
|
return JSON.stringify(await messagingRequest('DELETE', `/avatar/workflows/${Number(args.workflowId)}/override`), null, 2);
|
|
9825
|
+
case 'avatar_set_workflow_switch': {
|
|
9826
|
+
const mode = String(args.mode ?? '').trim().toLowerCase();
|
|
9827
|
+
if (!['on', 'off', 'rules'].includes(mode)) throw new Error('mode must be one of: on, off, rules');
|
|
9828
|
+
const id = Number(args.workflowId);
|
|
9829
|
+
if (!Number.isInteger(id) || id <= 0) throw new Error('workflowId must be a positive whole number');
|
|
9830
|
+
return JSON.stringify(await messagingRequest('PUT', `/avatar/workflows/${id}/enabled`, { mode }), null, 2);
|
|
9831
|
+
}
|
|
9832
|
+
case 'avatar_bulk_set_switch': {
|
|
9833
|
+
const mode = String(args.mode ?? '').trim().toLowerCase();
|
|
9834
|
+
if (!['on', 'off', 'rules'].includes(mode)) throw new Error('mode must be one of: on, off, rules');
|
|
9835
|
+
const workflowIds = (args.workflowIds ?? []).map(Number);
|
|
9836
|
+
const groupIds = (args.groupIds ?? []).map(Number);
|
|
9837
|
+
if (workflowIds.length + groupIds.length === 0) throw new Error('Pass workflowIds and/or groupIds');
|
|
9838
|
+
if (workflowIds.length + groupIds.length > 500) throw new Error('Too many rows in one call (max 500)');
|
|
9839
|
+
return JSON.stringify(await messagingRequest('PUT', '/avatar/overrides/enabled', { mode, workflowIds, groupIds }), null, 2);
|
|
9840
|
+
}
|
|
9488
9841
|
case 'avatar_reset_all_overrides':
|
|
9489
9842
|
return JSON.stringify(await messagingRequest('POST', '/avatar/group-overrides/reset-all', {}), null, 2);
|
|
9490
9843
|
|
|
@@ -10008,7 +10361,11 @@ async function handleTool(name, args) {
|
|
|
10008
10361
|
}
|
|
10009
10362
|
|
|
10010
10363
|
case 'magento_test_connection': {
|
|
10011
|
-
|
|
10364
|
+
if (!args.configId && (!args.storeUrl || !args.accessToken)) return 'Provide configId, or both storeUrl and accessToken.';
|
|
10365
|
+
const r = args.configId
|
|
10366
|
+
? await api('POST', `/Magento/Configs/${encodeURIComponent(args.configId)}/Test`)
|
|
10367
|
+
: await api('POST', '/Magento/Configs/Test', { storeUrl: args.storeUrl, accessToken: args.accessToken });
|
|
10368
|
+
if (r && r.valid === false) return `❌ Connection failed: ${r.error}`;
|
|
10012
10369
|
return `✅ Connected to "${r.storeName}" (${r.edition} ${r.version})`;
|
|
10013
10370
|
}
|
|
10014
10371
|
|
|
@@ -10074,6 +10431,13 @@ async function handleTool(name, args) {
|
|
|
10074
10431
|
}
|
|
10075
10432
|
|
|
10076
10433
|
// ── Business actions + SAP ────────────────────────────────────────────────
|
|
10434
|
+
case 'salesforce_diagnose': {
|
|
10435
|
+
if (!args.siteId) return 'siteId is required.';
|
|
10436
|
+
const deep = args.deep === false ? 'false' : 'true';
|
|
10437
|
+
const r = await messagingRequest('GET', `/diagnostics/test-salesforce?siteId=${encodeURIComponent(args.siteId)}&deep=${deep}`);
|
|
10438
|
+
return JSON.stringify(r, null, 2);
|
|
10439
|
+
}
|
|
10440
|
+
|
|
10077
10441
|
case 'sap_diagnose': {
|
|
10078
10442
|
if (!args.siteId) return 'siteId is required.';
|
|
10079
10443
|
const deep = args.deep === false ? 'false' : 'true';
|
|
@@ -10162,6 +10526,37 @@ async function handleTool(name, args) {
|
|
|
10162
10526
|
return `CTI token generated/rotated.\nDial URL: ${urls.dialUrlTemplate}\nScreen-pop URL: ${urls.screenPopUrl}`;
|
|
10163
10527
|
}
|
|
10164
10528
|
|
|
10529
|
+
case 'netsuite_suiteapp_download': {
|
|
10530
|
+
const includeKey = args.includeKey !== false;
|
|
10531
|
+
const r = await messagingGet(`/api/v1/CrmEmbed/suiteapp-package?includeKey=${includeKey}&format=json`);
|
|
10532
|
+
const d = r?.data ?? r;
|
|
10533
|
+
if (!d?.contentBase64) throw new Error('SuiteApp package was not returned.');
|
|
10534
|
+
const { writeFileSync } = await import('node:fs');
|
|
10535
|
+
const { tmpdir } = await import('node:os');
|
|
10536
|
+
const out = args.outputPath || _join(tmpdir(), d.fileName || 'velaro-netsuite-screenpop-suiteapp.zip');
|
|
10537
|
+
writeFileSync(out, Buffer.from(d.contentBase64, 'base64'));
|
|
10538
|
+
return `Saved ${out}. ` + (d.keyIncluded
|
|
10539
|
+
? '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.'
|
|
10540
|
+
: `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.`);
|
|
10541
|
+
}
|
|
10542
|
+
case 'netsuite_config_delete': {
|
|
10543
|
+
await messagingDel(`/Netsuite/Integrations/${args.configId}`);
|
|
10544
|
+
return `Deleted NetSuite config ${args.configId} (a missing config is a no-op).`;
|
|
10545
|
+
}
|
|
10546
|
+
case 'netsuite_m2m_clear_legacy': {
|
|
10547
|
+
const r = await messagingPost('/NetSuite/M2M/ClearLegacy', { id: args.configId });
|
|
10548
|
+
return `Legacy consumer secret/token cleared for config ${r?.id ?? args.configId} (authType ${r?.authType ?? 'm2m'}).`;
|
|
10549
|
+
}
|
|
10550
|
+
case 'netsuite_embed_status': {
|
|
10551
|
+
const r = await messagingGet('/api/v1/CrmEmbed/embed-status');
|
|
10552
|
+
const d = r?.data ?? r;
|
|
10553
|
+
if (!d?.hasEmbedKey) return 'No embed key exists for this site yet. The SuiteApp cannot connect until one is created.';
|
|
10554
|
+
if (d.connected) return `SuiteApp connected. Last polled ${d.lastPolledAt}.`;
|
|
10555
|
+
return d.lastPolledAt
|
|
10556
|
+
? `Embed key exists but the SuiteApp is not connected (last polled ${d.lastPolledAt}, outside the connected window).`
|
|
10557
|
+
: 'Embed key exists but the SuiteApp has not polled yet.';
|
|
10558
|
+
}
|
|
10559
|
+
|
|
10165
10560
|
case 'netsuite_m2m_init': {
|
|
10166
10561
|
const r = await messagingPost('/NetSuite/M2M/Init', { id: args.configId ?? null, company: args.company, displayName: args.displayName });
|
|
10167
10562
|
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 +10605,20 @@ async function handleTool(name, args) {
|
|
|
10210
10605
|
return `${result.success ? 'Success' : 'Failed'}: ${result.message ?? '(no message)'}`;
|
|
10211
10606
|
}
|
|
10212
10607
|
|
|
10608
|
+
// ── Shopify write policy (velaro-messaging /api/Shopify/WritePolicy) ───────
|
|
10609
|
+
case 'shopify_get_write_policy': {
|
|
10610
|
+
return JSON.stringify(await messagingGet('/Shopify/WritePolicy'), null, 2);
|
|
10611
|
+
}
|
|
10612
|
+
case 'shopify_set_write_policy': {
|
|
10613
|
+
const body = {};
|
|
10614
|
+
for (const k of ['enableShopifyWrite', 'requireConfirmation', 'maxRefundAmount', 'maxDiscountPercent', 'maxDiscountFixedAmount']) {
|
|
10615
|
+
if (args[k] !== undefined) body[k] = args[k];
|
|
10616
|
+
}
|
|
10617
|
+
if (!Object.keys(body).length) throw new Error('Provide at least one field to change.');
|
|
10618
|
+
const result = await messagingPut('/Shopify/WritePolicy', body);
|
|
10619
|
+
return `Shopify write policy updated.
|
|
10620
|
+
${JSON.stringify(result, null, 2)}`;
|
|
10621
|
+
}
|
|
10213
10622
|
// ── Webflow (config CRUD) ──────────────────────────────────────────────────
|
|
10214
10623
|
case 'webflow_get_config': {
|
|
10215
10624
|
if (args.configId) {
|
|
@@ -10770,6 +11179,19 @@ async function handleTool(name, args) {
|
|
|
10770
11179
|
return lines.join(String.fromCharCode(10));
|
|
10771
11180
|
}
|
|
10772
11181
|
|
|
11182
|
+
case 'entitlement_report_packages': {
|
|
11183
|
+
const q = args && args.siteId ? `?siteId=${encodeURIComponent(args.siteId)}` : '';
|
|
11184
|
+
const pkg = await api('GET', `/Entitlements/report-packages${q}`);
|
|
11185
|
+
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}`];
|
|
11186
|
+
for (const c of pkg.categories) {
|
|
11187
|
+
lines.push('');
|
|
11188
|
+
lines.push(`${c.name} (${c.slug})${pkg.siteId ? ` [${c.enabled ? 'ON ' : 'off'}] ${c.enabledCount}/${c.reportCount} reports` : ` ${c.reportCount} reports`}`);
|
|
11189
|
+
lines.push(` ${c.tagline}`);
|
|
11190
|
+
for (const r of c.reports) lines.push(` ${pkg.siteId ? (r.enabled ? '[ON ] ' : '[off] ') : ''}${r.name} (${r.id}) - package ${r.packageSlug}, key ${r.packageKey}`);
|
|
11191
|
+
}
|
|
11192
|
+
return lines.join(String.fromCharCode(10));
|
|
11193
|
+
}
|
|
11194
|
+
|
|
10773
11195
|
case 'entitlement_list_features': {
|
|
10774
11196
|
const features = await api('GET', '/Entitlements/features');
|
|
10775
11197
|
const byCategory = {};
|
|
@@ -10974,7 +11396,12 @@ async function handleTool(name, args) {
|
|
|
10974
11396
|
throw new Error('Date range too large. Worked Hours can be queried for at most 90 days at a time.');
|
|
10975
11397
|
}
|
|
10976
11398
|
}
|
|
10977
|
-
|
|
11399
|
+
// Server End is exclusive: a date-only end means "through the end of that day".
|
|
11400
|
+
let endValue = args.end;
|
|
11401
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(String(args.end).trim()) && !Number.isNaN(endMs)) {
|
|
11402
|
+
endValue = new Date(endMs + 24 * 60 * 60 * 1000).toISOString();
|
|
11403
|
+
}
|
|
11404
|
+
return await messagingPost('/Reports/worked-hours-breakdown', { Start: args.start, End: endValue });
|
|
10978
11405
|
}
|
|
10979
11406
|
|
|
10980
11407
|
// -- Pickup & Reminder Texting --
|
|
@@ -11022,6 +11449,25 @@ async function handleTool(name, args) {
|
|
|
11022
11449
|
return JSON.stringify(result, null, 2);
|
|
11023
11450
|
}
|
|
11024
11451
|
|
|
11452
|
+
case 'apple_pay_get_config': {
|
|
11453
|
+
const config = await messagingGet('/ApplePayConfig');
|
|
11454
|
+
return JSON.stringify(config, null, 2);
|
|
11455
|
+
}
|
|
11456
|
+
|
|
11457
|
+
case 'apple_pay_update_config': {
|
|
11458
|
+
// Field names must match ApplePayConfigUpdateRequest exactly (apply-if-present: undefined is dropped by JSON).
|
|
11459
|
+
const body = {
|
|
11460
|
+
merchantDisplayName: args.merchantDisplayName,
|
|
11461
|
+
isTestMode: args.isTestMode,
|
|
11462
|
+
bubbleLogoBase64: args.bubbleLogoBase64,
|
|
11463
|
+
};
|
|
11464
|
+
const existing = await messagingGet('/ApplePayConfig');
|
|
11465
|
+
const result = existing && existing.id > 0
|
|
11466
|
+
? await messagingPut(`/ApplePayConfig/${existing.id}`, body)
|
|
11467
|
+
: await messagingPost('/ApplePayConfig', body);
|
|
11468
|
+
return JSON.stringify(result, null, 2);
|
|
11469
|
+
}
|
|
11470
|
+
|
|
11025
11471
|
// -- Payment Recovery --
|
|
11026
11472
|
case 'payment_recovery_send': {
|
|
11027
11473
|
// Field names must match SendPaymentReminderRequest exactly.
|
|
@@ -11686,7 +12132,7 @@ const server = new Server(
|
|
|
11686
12132
|
{ capabilities: { tools: {}, prompts: {} } }
|
|
11687
12133
|
);
|
|
11688
12134
|
|
|
11689
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
12135
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS.map(withAnnotations) }));
|
|
11690
12136
|
|
|
11691
12137
|
server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: PROMPTS }));
|
|
11692
12138
|
|