@velaro/mcp-server 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/server.js +359 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velaro/mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Velaro MCP server — connect Claude and other AI agents directly to your Velaro account: KB, workflows, bots, conversations, contacts, routing, and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/server.js
CHANGED
|
@@ -232,6 +232,65 @@ function execInsightsRange(days) {
|
|
|
232
232
|
|
|
233
233
|
const messagingDel = (path) => messagingRequest('DELETE', path);
|
|
234
234
|
|
|
235
|
+
// Visitor link helpers (mirrors cli/lib/visitor-link.js; keep in sync, covered by cli/lib/visitor-link.test.js)
|
|
236
|
+
|
|
237
|
+
const MAX_TTL_MINUTES = 1440;
|
|
238
|
+
|
|
239
|
+
function decodeLinkToken(token) {
|
|
240
|
+
const out = { ok: false, problems: [] };
|
|
241
|
+
if (typeof token !== 'string' || !token.trim()) { out.problems.push('empty token'); return out; }
|
|
242
|
+
const parts = token.trim().split('.');
|
|
243
|
+
if (parts.length !== 3 || parts[0] !== 'vlk1') { out.problems.push('not a vlk1 link token (expected vlk1.<payload>.<signature>)'); return out; }
|
|
244
|
+
const payload = Buffer.from(parts[1].replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8');
|
|
245
|
+
const f = payload.split('|');
|
|
246
|
+
if (f.length < 6) { out.problems.push('payload has fewer than 6 fields'); return out; }
|
|
247
|
+
const [siteId, visitorId, conversationBaseId, scope, iat, exp] = f;
|
|
248
|
+
const toMs = (v) => { const n = Number(v); return Number.isFinite(n) && n > 0 ? (n > 1e12 ? n : n * 1000) : null; };
|
|
249
|
+
const issuedMs = toMs(iat);
|
|
250
|
+
const expMs = toMs(exp);
|
|
251
|
+
Object.assign(out, {
|
|
252
|
+
ok: true,
|
|
253
|
+
siteId: Number(siteId) || null,
|
|
254
|
+
visitorId,
|
|
255
|
+
conversationBaseId: Number(conversationBaseId) || null,
|
|
256
|
+
scope,
|
|
257
|
+
issuedAt: issuedMs ? new Date(issuedMs).toISOString() : null,
|
|
258
|
+
expiresAt: expMs ? new Date(expMs).toISOString() : null,
|
|
259
|
+
signaturePresent: parts[2].length > 0,
|
|
260
|
+
});
|
|
261
|
+
if (scope !== 'link') out.problems.push(`unexpected scope "${scope}" (v1 only issues "link")`);
|
|
262
|
+
if (!out.signaturePresent) out.problems.push('signature missing');
|
|
263
|
+
if (!expMs) out.problems.push('expiry not parseable');
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function diagnoseLink({ token, siteId, settings, now = Date.now() }) {
|
|
268
|
+
const claims = token ? decodeLinkToken(token) : { ok: false, problems: [] };
|
|
269
|
+
const findings = [...claims.problems];
|
|
270
|
+
if (claims.ok) {
|
|
271
|
+
if (siteId && claims.siteId && Number(siteId) !== claims.siteId) findings.push(`token was minted for site ${claims.siteId}, not site ${siteId}: cross-site tokens always fail`);
|
|
272
|
+
if (claims.expiresAt && Date.parse(claims.expiresAt) <= now) findings.push(`token expired at ${claims.expiresAt}`);
|
|
273
|
+
if (!claims.conversationBaseId) findings.push('token carries no conversation id (visitor had not started a chat when it was minted)');
|
|
274
|
+
}
|
|
275
|
+
if (settings) {
|
|
276
|
+
if (!settings.enabled) findings.push('visitor linking is OFF for this site: mint returns linking_disabled');
|
|
277
|
+
if (settings.keyConfigured === false) findings.push('link signing key is not configured on the server: mint and resolve fail closed (503 key_not_configured)');
|
|
278
|
+
}
|
|
279
|
+
return { claims, settings: settings ?? null, findings, healthy: findings.length === 0 };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function validateSettingsArgs({ enabled, ttlMinutes }) {
|
|
283
|
+
if (enabled === undefined && ttlMinutes === undefined) throw new Error('Pass enabled and/or ttlMinutes.');
|
|
284
|
+
if (ttlMinutes !== undefined && (!Number.isInteger(ttlMinutes) || ttlMinutes < 1 || ttlMinutes > MAX_TTL_MINUTES)) {
|
|
285
|
+
throw new Error(`ttlMinutes must be a whole number from 1 to ${MAX_TTL_MINUTES}.`);
|
|
286
|
+
}
|
|
287
|
+
const body = {};
|
|
288
|
+
if (enabled !== undefined) body.enabled = !!enabled;
|
|
289
|
+
if (ttlMinutes !== undefined) body.ttlMinutes = ttlMinutes;
|
|
290
|
+
return body;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
|
|
235
294
|
// Skill-based routing CRUD helper for SkillRouting/{Skills|Assignments|Requirements}.
|
|
236
295
|
async function skillRoutingCrud(resource, args, fields) {
|
|
237
296
|
const body = Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== undefined));
|
|
@@ -823,6 +882,16 @@ const TOOLS = [
|
|
|
823
882
|
},
|
|
824
883
|
},
|
|
825
884
|
|
|
885
|
+
{
|
|
886
|
+
name: 'skill_usage_site',
|
|
887
|
+
description: 'Velaro staff only. Per-customer skill and integration usage for the month in one call: the billable counters (AI tool calls, MCP calls, API calls, workflow integration actions, workflow AI calls, bot turns) against the plan limits (same numbers as the customer Billing page), the effective usage-gate rollout mode per metric (Off/Observe/Enforce), calls by surface (MCP gateway, bot, workflow, REST business action, staff, copilot), and LLM cost split platform vs BYOK. BYOK removes LLM cost only; skill usage is counted and capped the same way. Lives in velaro-messaging - requires VELARO_MESSAGING_API.',
|
|
888
|
+
inputSchema: {
|
|
889
|
+
type: 'object',
|
|
890
|
+
properties: { siteId: { type: 'number', description: 'Target site ID (need not be your own)' } },
|
|
891
|
+
required: ['siteId'],
|
|
892
|
+
},
|
|
893
|
+
},
|
|
894
|
+
|
|
826
895
|
{
|
|
827
896
|
name: 'scraper_verify_token',
|
|
828
897
|
description: "Show the calling site's web-scraper verification header (name + value), the scraper IP, and ready-to-send instructions for allowlisting Velaro's crawler with the customer's web host/WAF. Wraps GET ScraperJobs/VerifyToken. Site comes from auth context, never a parameter.",
|
|
@@ -1041,6 +1110,18 @@ const TOOLS = [
|
|
|
1041
1110
|
},
|
|
1042
1111
|
},
|
|
1043
1112
|
|
|
1113
|
+
{
|
|
1114
|
+
name: 'csat_override_unapprove_candidate',
|
|
1115
|
+
description: 'Undo an approved Chat Intelligence improvement candidate: returns it to Pending, deactivates the promoted knowledge override and removes any vectorized copy. Requires site manager. Site comes from the key, never an argument.',
|
|
1116
|
+
inputSchema: {
|
|
1117
|
+
type: 'object',
|
|
1118
|
+
properties: {
|
|
1119
|
+
candidateId: { type: 'number', description: 'Id of the Approved override candidate' },
|
|
1120
|
+
},
|
|
1121
|
+
required: ['candidateId'],
|
|
1122
|
+
},
|
|
1123
|
+
},
|
|
1124
|
+
|
|
1044
1125
|
// ── Conversations ─────────────────────────────────────────────────────────
|
|
1045
1126
|
{
|
|
1046
1127
|
name: 'conversation_list',
|
|
@@ -1761,6 +1842,41 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
1761
1842
|
required: ['message'],
|
|
1762
1843
|
},
|
|
1763
1844
|
},
|
|
1845
|
+
{
|
|
1846
|
+
name: 'visitor_link_get_settings',
|
|
1847
|
+
description: 'Get the visitor-linking settings for this site: whether chat visitors can be linked to later form/survey submissions (default off), the link lifetime in minutes, and whether the signing key is configured. Site is resolved server-side.',
|
|
1848
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
1849
|
+
},
|
|
1850
|
+
{
|
|
1851
|
+
name: 'visitor_link_set_settings',
|
|
1852
|
+
description: 'Turn visitor linking on or off for this site and/or set the link lifetime (1-1440 minutes). Only fields you pass change. Links are only created for visitors who have chatted and, when the site uses a consent banner, granted functional storage.',
|
|
1853
|
+
inputSchema: {
|
|
1854
|
+
type: 'object',
|
|
1855
|
+
properties: {
|
|
1856
|
+
enabled: { type: 'boolean', description: 'Enable or disable visitor linking' },
|
|
1857
|
+
ttlMinutes: { type: 'number', description: 'Link lifetime in minutes, 1-1440' },
|
|
1858
|
+
},
|
|
1859
|
+
required: [],
|
|
1860
|
+
},
|
|
1861
|
+
},
|
|
1862
|
+
{
|
|
1863
|
+
name: 'visitor_link_diagnose',
|
|
1864
|
+
description: 'Explain why visitor linking is or is not working for this site. Reads the current settings and, if you pass a vlk1 link token, decodes its claims (site, conversation, expiry) and reports cross-site, expired, missing-conversation and key problems. The signature is not verified here; use visitor_link_resolve for the real server-side check.',
|
|
1865
|
+
inputSchema: {
|
|
1866
|
+
type: 'object',
|
|
1867
|
+
properties: { token: { type: 'string', description: 'Optional vlk1 link token from a form hidden field' } },
|
|
1868
|
+
required: [],
|
|
1869
|
+
},
|
|
1870
|
+
},
|
|
1871
|
+
{
|
|
1872
|
+
name: 'visitor_link_resolve',
|
|
1873
|
+
description: 'Exchange a vlk1 link token for the visitor id, conversation id and contact id (what your own server does when a form arrives). Cross-site and expired tokens fail.',
|
|
1874
|
+
inputSchema: {
|
|
1875
|
+
type: 'object',
|
|
1876
|
+
properties: { token: { type: 'string', description: 'vlk1 link token' } },
|
|
1877
|
+
required: ['token'],
|
|
1878
|
+
},
|
|
1879
|
+
},
|
|
1764
1880
|
{
|
|
1765
1881
|
name: 'teams_push_settings_get',
|
|
1766
1882
|
description: 'Get the Velaro Bot proactive-alert settings for this site: per-alert switches (new chat, missed chat, ticket created, ticket assigned, daily digest), the effective daily cap and the push count for today. Site is resolved server-side.',
|
|
@@ -2523,6 +2639,42 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2523
2639
|
required: ['siteId', 'indexName'],
|
|
2524
2640
|
},
|
|
2525
2641
|
},
|
|
2642
|
+
{
|
|
2643
|
+
name: 'knowledge_clinical_text_get',
|
|
2644
|
+
description: 'Read the Emergency and Safety Messages for a site: per-channel and per-language emergency/referral overrides (keys like chat, es, es.voice), the effective English wording, supportedLanguages, limits, and the read-only emergencyServiceMode. Customer: own site. Velaro staff: pass siteId. Wraps GET KnowledgeSetup/state (clinicalText).',
|
|
2645
|
+
inputSchema: {
|
|
2646
|
+
type: 'object',
|
|
2647
|
+
properties: { siteId: { type: 'number', description: 'Velaro staff only: target site ID. Omit for your own site.' } },
|
|
2648
|
+
},
|
|
2649
|
+
},
|
|
2650
|
+
{
|
|
2651
|
+
name: 'knowledge_clinical_text_set',
|
|
2652
|
+
description: 'Set the Emergency and Safety Messages. The server replaces the stored override set, so pass every override to keep (read first with knowledge_clinical_text_get). Keys: default, chat, voice, sms, whatsapp, email, a language code (es fr de pt it nl zh ja ko vi ru ar hi pl) or language.channel (es.voice). Each text 20-600 chars; emergency text must contain {emergencyNumber}; "hang up" only in voice wording. null value clears an entry. Wraps PUT KnowledgeSetup/clinical-text.',
|
|
2653
|
+
inputSchema: {
|
|
2654
|
+
type: 'object',
|
|
2655
|
+
properties: {
|
|
2656
|
+
siteId: { type: 'number', description: 'Velaro staff only: target site ID. Omit for your own site.' },
|
|
2657
|
+
emergency: { type: 'object', description: 'Map of key to emergency wording', additionalProperties: { type: ['string', 'null'] } },
|
|
2658
|
+
referral: { type: 'object', description: 'Map of key to referral wording', additionalProperties: { type: ['string', 'null'] } },
|
|
2659
|
+
emergencyNumber: { type: 'string', description: 'Replaces {emergencyNumber}; omit for the default' },
|
|
2660
|
+
resetToDefault: { type: 'boolean', description: 'Clear all custom wording' },
|
|
2661
|
+
},
|
|
2662
|
+
},
|
|
2663
|
+
},
|
|
2664
|
+
{
|
|
2665
|
+
name: 'knowledge_emergency_service_mode_set',
|
|
2666
|
+
description: 'Velaro staff only. Turn emergency-service mode on/off for a site that IS the emergency line: its own required text replaces the built-in emergency instruction (never the referral). Turning on needs confirm=true and text of 20-600 chars. Wraps PUT KnowledgeSetup/Staff/{siteId}/clinical-text (emergencyServiceMode); existing wording is left untouched.',
|
|
2667
|
+
inputSchema: {
|
|
2668
|
+
type: 'object',
|
|
2669
|
+
properties: {
|
|
2670
|
+
siteId: { type: 'number', description: 'Target site ID' },
|
|
2671
|
+
enabled: { type: 'boolean' },
|
|
2672
|
+
text: { type: 'string', description: 'Required emergency text (20-600 chars)' },
|
|
2673
|
+
confirm: { type: 'boolean', description: 'Must be true to turn the mode on' },
|
|
2674
|
+
},
|
|
2675
|
+
required: ['siteId'],
|
|
2676
|
+
},
|
|
2677
|
+
},
|
|
2526
2678
|
{
|
|
2527
2679
|
name: 'index_set_profile',
|
|
2528
2680
|
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).',
|
|
@@ -3194,6 +3346,50 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
3194
3346
|
required: ['id'],
|
|
3195
3347
|
},
|
|
3196
3348
|
},
|
|
3349
|
+
// ── Visitor Monitor (session-scoped: SiteId comes from the caller's JWT, never an arg) ──
|
|
3350
|
+
{
|
|
3351
|
+
name: 'visitor_monitor_active',
|
|
3352
|
+
description: 'List live visitors on this site (Visitor Monitor). Returns sessions (with region/city/ipTruncated when Store Location Detail is on), activeWindowMinutes, locationDetailEnabled, visitor limit and counts.',
|
|
3353
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
3354
|
+
},
|
|
3355
|
+
{
|
|
3356
|
+
name: 'visitor_monitor_preferences_get',
|
|
3357
|
+
description: 'Get the calling user\'s saved Visitor Monitor view (columns, groupBy, sortBy, sortDir, regionGrouping). Per user, follows them across browsers.',
|
|
3358
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
3359
|
+
},
|
|
3360
|
+
{
|
|
3361
|
+
name: 'visitor_monitor_preferences_set',
|
|
3362
|
+
description: 'Save the calling user\'s Visitor Monitor view. Replaces the saved view: send the full set of fields you want kept.',
|
|
3363
|
+
inputSchema: {
|
|
3364
|
+
type: 'object',
|
|
3365
|
+
properties: {
|
|
3366
|
+
columns: { type: 'array', items: { type: 'string' }, description: 'Ordered column keys, e.g. ["visitor","status","region","city"]' },
|
|
3367
|
+
groupBy: { type: 'string', description: 'Group the list by this key (e.g. "region", "city", "none")' },
|
|
3368
|
+
sortBy: { type: 'string', description: 'Sort key, e.g. "lastSeen"' },
|
|
3369
|
+
sortDir: { type: 'string', enum: ['asc', 'desc'], description: 'Sort direction' },
|
|
3370
|
+
regionGrouping: { type: 'string', description: 'Region grouping granularity: "region" or "city"' },
|
|
3371
|
+
},
|
|
3372
|
+
required: [],
|
|
3373
|
+
},
|
|
3374
|
+
},
|
|
3375
|
+
{
|
|
3376
|
+
name: 'visitor_monitor_settings_get',
|
|
3377
|
+
description: 'Get this site\'s Visitor Monitor settings: storeLocationDetail and activeWindowMinutes (how long a visitor stays listed after leaving; default 30).',
|
|
3378
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
3379
|
+
},
|
|
3380
|
+
{
|
|
3381
|
+
name: 'visitor_monitor_settings_set',
|
|
3382
|
+
description: 'Update this site\'s Visitor Monitor settings (partial update). activeWindowMinutes must be 5-240; above 30 and storeLocationDetail need the Visitor Insights Advanced plan flag, otherwise the API returns 403 feature_not_in_plan.',
|
|
3383
|
+
inputSchema: {
|
|
3384
|
+
type: 'object',
|
|
3385
|
+
properties: {
|
|
3386
|
+
storeLocationDetail: { type: 'boolean', description: 'Store visitor region/city and a truncated IP on new sessions (Advanced plan)' },
|
|
3387
|
+
activeWindowMinutes: { type: 'number', description: 'Minutes a visitor stays in the monitor after last activity (5-240; above 30 needs Advanced)' },
|
|
3388
|
+
},
|
|
3389
|
+
required: [],
|
|
3390
|
+
},
|
|
3391
|
+
},
|
|
3392
|
+
|
|
3197
3393
|
// ── Page Widget Containers ────────────────────────────────────────────────
|
|
3198
3394
|
{
|
|
3199
3395
|
name: 'widget_container_list',
|
|
@@ -3544,7 +3740,7 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
3544
3740
|
inputSchema: {
|
|
3545
3741
|
type: 'object',
|
|
3546
3742
|
properties: {
|
|
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'] },
|
|
3743
|
+
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', 'bonus_insights', 'operations_briefing', 'entitled'] },
|
|
3548
3744
|
from: { type: 'string', description: 'yyyy-MM-dd start (default: 30 days ago).' },
|
|
3549
3745
|
to: { type: 'string', description: 'yyyy-MM-dd end, inclusive (default: today).' },
|
|
3550
3746
|
groupBy: { type: 'string', description: 'program_breakdown: website|team|platform|device|entrypage. sentiment_breakdown: website|team|agent|theme.' },
|
|
@@ -3561,6 +3757,13 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
3561
3757
|
timeZone: { type: 'string', description: 'demand_coverage: IANA time zone (default America/Los_Angeles).' },
|
|
3562
3758
|
lowScore: { type: 'number', description: 'bot_containment: QA score below which a handed-off chat counts as regret (1-100, default 60).' },
|
|
3563
3759
|
includeDemo: { type: 'boolean', description: 'true = show only the seeded sample (demo) data. Default false.' },
|
|
3760
|
+
name: { type: 'string', description: 'bonus_insights: which analysis: coverage_gap|next_best_fix|escalation_risk|skill_gap_matrix|handle_quality_outliers|abandon_causes.' },
|
|
3761
|
+
narrative: { type: 'boolean', description: 'operations_briefing: include the cached AI narrative (default true).' },
|
|
3762
|
+
minutesPerReview: { type: 'number', description: 'bonus_insights: assumed minutes per manual review (0-240).' },
|
|
3763
|
+
hourlyCost: { type: 'number', description: 'bonus_insights: assumed reviewer hourly cost (0-1000).' },
|
|
3764
|
+
deflectionPct: { type: 'number', description: 'bonus_insights: assumed deflection percent (0-100).' },
|
|
3765
|
+
costPerChat: { type: 'number', description: 'bonus_insights: assumed cost per chat (0-1000).' },
|
|
3766
|
+
waitMinutes: { type: 'number', description: 'bonus_insights: wait minutes that counts as abandoned (1-1440).' },
|
|
3564
3767
|
promiseId: { type: 'string', description: 'promise_dismiss: the promise id to dismiss (managers only).' },
|
|
3565
3768
|
buildId: { type: 'string', description: 'year_in_review: the build id returned by a start call; when given, returns that build\'s status (queued|building|completed|failed) and download link instead of starting a new build.' },
|
|
3566
3769
|
},
|
|
@@ -4706,6 +4909,15 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
4706
4909
|
},
|
|
4707
4910
|
},
|
|
4708
4911
|
|
|
4912
|
+
{
|
|
4913
|
+
name: 'netsuite_rollback_auth',
|
|
4914
|
+
description: 'Undo a NetSuite connection-type switch: sets a connection that was moved to OAuth 2.0 / M2M back to the legacy OAuth 1.0a credentials. Refused (409) once the old credentials were cleared. Nothing is deleted. Administrator only, audited. Lives in velaro-messaging - requires VELARO_MESSAGING_API.',
|
|
4915
|
+
inputSchema: {
|
|
4916
|
+
type: 'object',
|
|
4917
|
+
properties: { configId: { type: 'number', description: 'NetSuite config ID (see netsuite_list_configs).' } },
|
|
4918
|
+
required: ['configId'],
|
|
4919
|
+
},
|
|
4920
|
+
},
|
|
4709
4921
|
{
|
|
4710
4922
|
name: 'netsuite_suiteapp_download',
|
|
4711
4923
|
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.',
|
|
@@ -4782,7 +4994,7 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
4782
4994
|
},
|
|
4783
4995
|
{
|
|
4784
4996
|
name: 'teams_phone_test_config',
|
|
4785
|
-
description: '
|
|
4997
|
+
description: 'Run the Teams Phone readiness checks and return each check (name, ok, message) plus callingReady. Surfaces what is missing (admin consent, resource account, Graph reachability) rather than only being discovered when a call is missed. Lives in velaro-messaging — requires VELARO_MESSAGING_API.',
|
|
4786
4998
|
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
4787
4999
|
},
|
|
4788
5000
|
|
|
@@ -5013,6 +5225,21 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
5013
5225
|
// Distinct from the reusable coupon-CODE catalog, which lives in velaro-messaging
|
|
5014
5226
|
// (superadmin/billing/coupons). Pass couponCode here only to record which catalog code a grant
|
|
5015
5227
|
// corresponds to.
|
|
5228
|
+
{
|
|
5229
|
+
name: 'sign_versions_list',
|
|
5230
|
+
description: 'Velaro Sign: list every uploaded document version of a contract (owner-only; signers never see old versions). Newest first with sha256, pages, uploader, note.',
|
|
5231
|
+
inputSchema: { type: 'object', properties: { contractId: { type: 'number', description: 'Contract ID' } }, required: ['contractId'] },
|
|
5232
|
+
},
|
|
5233
|
+
{
|
|
5234
|
+
name: 'sign_versions_diff',
|
|
5235
|
+
description: 'Velaro Sign: text diff between two document versions of a contract (changed blocks only: removed vs added lines). Owner-only.',
|
|
5236
|
+
inputSchema: { type: 'object', properties: { contractId: { type: 'number' }, from: { type: 'number', description: 'Older version number' }, to: { type: 'number', description: 'Newer version number' } }, required: ['contractId', 'from', 'to'] },
|
|
5237
|
+
},
|
|
5238
|
+
{
|
|
5239
|
+
name: 'sign_versions_upload',
|
|
5240
|
+
description: 'Velaro Sign: upload a new PDF version of a contract document from a local file path. Allowed only while the contract is a draft; after send/sign, void and re-send.',
|
|
5241
|
+
inputSchema: { type: 'object', properties: { contractId: { type: 'number' }, filePath: { type: 'string', description: 'Local path of the PDF' }, note: { type: 'string', description: 'Change note' } }, required: ['contractId', 'filePath'] },
|
|
5242
|
+
},
|
|
5016
5243
|
{
|
|
5017
5244
|
name: 'coupon_grant_list',
|
|
5018
5245
|
description: 'List per-account Stripe discount grants. Velaro billing staff only. Shows which accounts currently have a discount and its status.',
|
|
@@ -5138,6 +5365,10 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
5138
5365
|
required: ['id'],
|
|
5139
5366
|
},
|
|
5140
5367
|
},
|
|
5368
|
+
{
|
|
5369
|
+
name: 'usage_alerts',
|
|
5370
|
+
description: 'Plan-limit alerts for the authenticated site: every metered resource at or above its warning threshold (80%+), with used/limit, percent, level (warning | critical | limit) and whether overage is billed. Same rules as the usage-limit emails and the dashboard banner. Use to answer "are they near a limit?" or "why did they get a usage email?". Read-only. To raise a limit, a customer uses Billing > Contact Account Manager; staff use support_set_limit.',
|
|
5371
|
+
},
|
|
5141
5372
|
{
|
|
5142
5373
|
name: 'billing_notify_settings_get',
|
|
5143
5374
|
description: 'Billing notifications: the global settings (receipts.card, receipts.ach, receipts.other, invoices.bcc, digest.recipients). Staff only.',
|
|
@@ -6400,7 +6631,7 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
6400
6631
|
{"name":"skill_routing_assignments","description":"Skill-based routing: assign skills to agents with proficiency 1-5. action = list | create | update | delete.","inputSchema":{"type":"object","properties":{"action":{"type":"string"},"id":{"type":"number"},"workspaceUserId":{"type":"number"},"agentSkillId":{"type":"number"},"proficiency":{"type":"number"}},"required":["action"]}},
|
|
6401
6632
|
{"name":"skill_routing_requirements","description":"Skill-based routing: required/preferred skills per deployment and/or team. action = list | create | update | delete. Update is partial. Requirements are disabled by default; set isEnabled true to turn on.","inputSchema":{"type":"object","properties":{"action":{"type":"string"},"id":{"type":"number"},"deploymentId":{"type":"number"},"teamId":{"type":"number"},"requiredSkillIds":{"type":"array","items":{"type":"number"}},"preferredSkillIds":{"type":"array","items":{"type":"number"}},"overflowWaitSeconds":{"type":"number"},"isEnabled":{"type":"boolean"}},"required":["action"]}},
|
|
6402
6633
|
{"name":"skill_routing_simulate","description":"Dry-run skill routing for a team: shows which agents would be narrowed to and why. Never assigns a conversation or writes the decision log.","inputSchema":{"type":"object","properties":{"teamId":{"type":"number"},"waitedSeconds":{"type":"number"}}}},
|
|
6403
|
-
{"name":"qa_rubric","description":"Conversation Quality QA rubrics. action = list | get | default | create | update | delete. Pass rubric fields in \"rubric\". Requires EnableConversationQuality.","inputSchema":{"type":"object","properties":{"action":{"type":"string"},"id":{"type":"number"},"rubric":{"type":"object"}},"required":["action"]}},
|
|
6634
|
+
{"name":"qa_rubric","description":"Conversation Quality QA rubrics. action = list | get | default | starter (create the starter rubric if the site has none) | create | update | clone (inactive copy; optional rubric.name) | activate | deactivate | rescore (re-score recent chats against the active rubric; pass days 1-90 and limit 1-500) | delete. Pass rubric fields in \"rubric\". Weights are relative and shown as percents; max 20 criteria. Requires EnableConversationQuality and the Administrator role for writes.","inputSchema":{"type":"object","properties":{"action":{"type":"string"},"id":{"type":"number"},"rubric":{"type":"object"},"days":{"type":"number"},"limit":{"type":"number"}},"required":["action"]}},
|
|
6404
6635
|
{"name":"qa_low_scores","description":"Conversations scoring below the rubric threshold, for QA review.","inputSchema":{"type":"object","properties":{"threshold":{"type":"number"},"days":{"type":"number"},"agentId":{"type":"number"},"skip":{"type":"number"},"take":{"type":"number"}}}},
|
|
6405
6636
|
{"name":"ai_field_extraction_get_config","description":"Get the AI field extraction config (opt-in, default off; monthly cap). Requires the ai_field_extraction entitlement.","inputSchema":{"type":"object","properties":{}}},
|
|
6406
6637
|
{"name":"ai_field_extraction_update_config","description":"Update AI field extraction config (partial). enabled, monthlyCap (null = 500 default, hard ceiling 50000), createThemeTags.","inputSchema":{"type":"object","properties":{"enabled":{"type":"boolean"},"monthlyCap":{"type":"number"},"createThemeTags":{"type":"boolean"}}}},
|
|
@@ -6921,6 +7152,11 @@ async function handleTool(name, args) {
|
|
|
6921
7152
|
return JSON.stringify(result, null, 2);
|
|
6922
7153
|
}
|
|
6923
7154
|
|
|
7155
|
+
case 'skill_usage_site': {
|
|
7156
|
+
const usage = await messagingGet(`/SupportTools/sites/${args.siteId}/skill-usage`);
|
|
7157
|
+
return JSON.stringify(usage, null, 2);
|
|
7158
|
+
}
|
|
7159
|
+
|
|
6924
7160
|
case 'apple_health': {
|
|
6925
7161
|
const h = await messagingGet(`/SupportTools/sites/${args.siteId}/apple/health`);
|
|
6926
7162
|
return JSON.stringify(h, null, 2);
|
|
@@ -7030,6 +7266,12 @@ async function handleTool(name, args) {
|
|
|
7030
7266
|
{ dismissed: args.dismissed !== false }
|
|
7031
7267
|
);
|
|
7032
7268
|
|
|
7269
|
+
case 'csat_override_unapprove_candidate': {
|
|
7270
|
+
const cid = Number(args.candidateId);
|
|
7271
|
+
if (!Number.isInteger(cid) || cid <= 0) throw new Error('candidateId must be a positive integer');
|
|
7272
|
+
return await messagingPost(`/api/intelligence/csat-override-candidates/${cid}/unapprove`, {});
|
|
7273
|
+
}
|
|
7274
|
+
|
|
7033
7275
|
// ── Conversations ──────────────────────────────────────────────────────
|
|
7034
7276
|
case 'conversation_list':
|
|
7035
7277
|
return await rpc('conversation_list', args);
|
|
@@ -7584,6 +7826,20 @@ reply: ${r?.reply}`;
|
|
|
7584
7826
|
return `Sent to Teams webhook "${webhook.name}".`;
|
|
7585
7827
|
}
|
|
7586
7828
|
|
|
7829
|
+
case 'visitor_link_get_settings':
|
|
7830
|
+
return await messagingGet('/SiteSettings/visitor-linking');
|
|
7831
|
+
|
|
7832
|
+
case 'visitor_link_set_settings':
|
|
7833
|
+
return await messagingPut('/SiteSettings/visitor-linking', validateSettingsArgs({ enabled: args.enabled, ttlMinutes: args.ttlMinutes }));
|
|
7834
|
+
|
|
7835
|
+
case 'visitor_link_diagnose': {
|
|
7836
|
+
const settings = await messagingGet('/SiteSettings/visitor-linking');
|
|
7837
|
+
return diagnoseLink({ token: args.token, settings });
|
|
7838
|
+
}
|
|
7839
|
+
|
|
7840
|
+
case 'visitor_link_resolve':
|
|
7841
|
+
return await messagingPost('/api/v1/visitor-links/resolve', { token: args.token });
|
|
7842
|
+
|
|
7587
7843
|
case 'teams_push_settings_get':
|
|
7588
7844
|
return await api('GET', '/TeamsBot/PushSettings');
|
|
7589
7845
|
|
|
@@ -8584,6 +8840,34 @@ reply: ${r?.reply}`;
|
|
|
8584
8840
|
return `Index ready on site ${args.siteId}: ${idx?.name} (id=${idx?.id}, purpose=${idx?.purpose}, excludeFromAllSources=${!!idx?.excludeFromAllSources}).`;
|
|
8585
8841
|
}
|
|
8586
8842
|
|
|
8843
|
+
case 'knowledge_clinical_text_get': {
|
|
8844
|
+
const base = args.siteId ? `/KnowledgeSetup/Staff/${encodeURIComponent(args.siteId)}` : '/KnowledgeSetup';
|
|
8845
|
+
const st = await messagingRequest('GET', `${base}/state`);
|
|
8846
|
+
return JSON.stringify(st?.clinicalText ?? { error: 'state did not include clinicalText' }, null, 2);
|
|
8847
|
+
}
|
|
8848
|
+
|
|
8849
|
+
case 'knowledge_clinical_text_set': {
|
|
8850
|
+
const base = args.siteId ? `/KnowledgeSetup/Staff/${encodeURIComponent(args.siteId)}` : '/KnowledgeSetup';
|
|
8851
|
+
const body = args.resetToDefault ? { resetToDefault: true } : {};
|
|
8852
|
+
if (!args.resetToDefault) {
|
|
8853
|
+
if (args.emergency !== undefined) body.emergency = args.emergency;
|
|
8854
|
+
if (args.referral !== undefined) body.referral = args.referral;
|
|
8855
|
+
if (args.emergencyNumber !== undefined) body.emergencyNumber = args.emergencyNumber;
|
|
8856
|
+
}
|
|
8857
|
+
const r = await messagingRequest('PUT', `${base}/clinical-text`, body);
|
|
8858
|
+
return JSON.stringify(r, null, 2);
|
|
8859
|
+
}
|
|
8860
|
+
|
|
8861
|
+
case 'knowledge_emergency_service_mode_set': {
|
|
8862
|
+
if (!args.siteId) return 'siteId is required.';
|
|
8863
|
+
const mode = {};
|
|
8864
|
+
if (args.enabled !== undefined) mode.enabled = args.enabled;
|
|
8865
|
+
if (args.text !== undefined) mode.text = args.text;
|
|
8866
|
+
if (args.confirm) mode.confirm = true;
|
|
8867
|
+
const r = await messagingRequest('PUT', `/KnowledgeSetup/Staff/${encodeURIComponent(args.siteId)}/clinical-text`, { emergencyServiceMode: mode });
|
|
8868
|
+
return JSON.stringify(r, null, 2);
|
|
8869
|
+
}
|
|
8870
|
+
|
|
8587
8871
|
case 'index_set_profile': {
|
|
8588
8872
|
if (!args.siteId || !args.indexName) return 'siteId and indexName are required.';
|
|
8589
8873
|
const body = { indexName: args.indexName };
|
|
@@ -9476,6 +9760,25 @@ reply: ${r?.reply}`;
|
|
|
9476
9760
|
return `Widget ${args.id} ${args.isEnabled ? 'enabled' : 'disabled'}.`;
|
|
9477
9761
|
}
|
|
9478
9762
|
|
|
9763
|
+
// ── Visitor Monitor ───────────────────────────────────────────────────
|
|
9764
|
+
case 'visitor_monitor_active':
|
|
9765
|
+
return JSON.stringify(await messagingGet('/Visitor/active'), null, 2);
|
|
9766
|
+
case 'visitor_monitor_preferences_get':
|
|
9767
|
+
return JSON.stringify(await messagingGet('/Visitor/preferences'), null, 2);
|
|
9768
|
+
case 'visitor_monitor_preferences_set': {
|
|
9769
|
+
const body = {};
|
|
9770
|
+
for (const k of ['columns', 'groupBy', 'sortBy', 'sortDir', 'regionGrouping']) if (args[k] !== undefined) body[k] = args[k];
|
|
9771
|
+
return JSON.stringify(await messagingPut('/Visitor/preferences', body), null, 2);
|
|
9772
|
+
}
|
|
9773
|
+
case 'visitor_monitor_settings_get':
|
|
9774
|
+
return JSON.stringify(await messagingGet('/Visitor/settings'), null, 2);
|
|
9775
|
+
case 'visitor_monitor_settings_set': {
|
|
9776
|
+
const body = {};
|
|
9777
|
+
if (args.storeLocationDetail !== undefined) body.storeLocationDetail = args.storeLocationDetail;
|
|
9778
|
+
if (args.activeWindowMinutes !== undefined) body.activeWindowMinutes = args.activeWindowMinutes;
|
|
9779
|
+
return JSON.stringify(await messagingPut('/Visitor/settings', body), null, 2);
|
|
9780
|
+
}
|
|
9781
|
+
|
|
9479
9782
|
// ── Page Widget Containers ────────────────────────────────────────────────
|
|
9480
9783
|
case 'widget_container_list': {
|
|
9481
9784
|
const data = await api('GET', '/PageWidgetContainers');
|
|
@@ -9794,6 +10097,7 @@ reply: ${r?.reply}`;
|
|
|
9794
10097
|
weekly_brief: 'weekly-brief', weekly_brief_send_test: 'weekly-brief/send-test',
|
|
9795
10098
|
repeat_contact: 'repeat-contact', demand_coverage: 'demand-coverage', bot_containment: 'bot-containment',
|
|
9796
10099
|
effort_index: 'effort-index', promise_ledger: 'promise-ledger', new_agent_ramp: 'new-agent-ramp',
|
|
10100
|
+
bonus_insights: 'bonus', operations_briefing: 'operations-briefing', entitled: 'entitled',
|
|
9797
10101
|
};
|
|
9798
10102
|
if (args.report === 'promise_dismiss') {
|
|
9799
10103
|
if (!args.promiseId) throw new Error('promiseId is required for promise_dismiss');
|
|
@@ -9826,11 +10130,15 @@ reply: ${r?.reply}`;
|
|
|
9826
10130
|
if (f > t) throw new Error('from must be on or before to');
|
|
9827
10131
|
return JSON.stringify(await messagingPost('/api/enterprise-reports/year-in-review', { from: midnightIso(f), to: midnightIso(addDays(t, 1)) }), null, 2);
|
|
9828
10132
|
}
|
|
9829
|
-
|
|
10133
|
+
let seg = ER_PATHS[args.report];
|
|
9830
10134
|
if (!seg) throw new Error('report must be one of: ' + Object.keys(ER_PATHS).join(', ') + ', promise_dismiss, year_in_review');
|
|
10135
|
+
if (args.report === 'bonus_insights') {
|
|
10136
|
+
if (!args.name) throw new Error('name is required for bonus_insights (coverage_gap|next_best_fix|escalation_risk|skill_gap_matrix|handle_quality_outliers|abandon_causes)');
|
|
10137
|
+
seg = 'bonus/' + encodeURIComponent(String(args.name).replace(/-/g, '_'));
|
|
10138
|
+
}
|
|
9831
10139
|
if (args.report === 'weekly_brief_send_test') return JSON.stringify(await messagingPost('/api/enterprise-reports/' + seg, {}), null, 2);
|
|
9832
10140
|
const qs = new URLSearchParams();
|
|
9833
|
-
for (const k of ['from', 'to', 'groupBy', 'deploymentId', 'teamId', 'threshold', 'perAgent', 'limit', 'days', 'windowDays', 'weeks', 'handleMinutes', 'maxConcurrent', 'timeZone', 'lowScore', 'includeDemo']) {
|
|
10141
|
+
for (const k of ['from', 'to', 'groupBy', 'deploymentId', 'teamId', 'threshold', 'perAgent', 'limit', 'days', 'windowDays', 'weeks', 'handleMinutes', 'maxConcurrent', 'timeZone', 'lowScore', 'includeDemo', 'narrative', 'minutesPerReview', 'hourlyCost', 'deflectionPct', 'costPerChat', 'waitMinutes']) {
|
|
9834
10142
|
if (args[k] != null && args[k] !== '') qs.set(k, String(args[k]));
|
|
9835
10143
|
}
|
|
9836
10144
|
const q = qs.toString();
|
|
@@ -10791,6 +11099,10 @@ reply: ${r?.reply}`;
|
|
|
10791
11099
|
? `Embed key exists but the SuiteApp is not connected (last polled ${d.lastPolledAt}, outside the connected window).`
|
|
10792
11100
|
: 'Embed key exists but the SuiteApp has not polled yet.';
|
|
10793
11101
|
}
|
|
11102
|
+
case 'netsuite_rollback_auth': {
|
|
11103
|
+
const r = await messagingPost('/NetSuite/Auth/RollbackAuthType', { id: args.configId });
|
|
11104
|
+
return `Config ${r?.id ?? args.configId} switched back to ${r?.authType ?? 'oauth1'}. Run a connection test to confirm.`;
|
|
11105
|
+
}
|
|
10794
11106
|
|
|
10795
11107
|
case 'netsuite_m2m_init': {
|
|
10796
11108
|
const r = await messagingPost('/NetSuite/M2M/Init', { id: args.configId ?? null, company: args.company, displayName: args.displayName });
|
|
@@ -10837,7 +11149,12 @@ reply: ${r?.reply}`;
|
|
|
10837
11149
|
}
|
|
10838
11150
|
case 'teams_phone_test_config': {
|
|
10839
11151
|
const result = await messagingPost('/TeamsPhone/Config/Test', {});
|
|
10840
|
-
|
|
11152
|
+
const lines = [`${result.success ? 'Success' : 'Failed'}: ${result.message ?? '(no message)'}`];
|
|
11153
|
+
if (typeof result.callingReady === 'boolean') lines.push(`Inbound calling ready: ${result.callingReady}`);
|
|
11154
|
+
if (Array.isArray(result.checks)) {
|
|
11155
|
+
for (const ck of result.checks) lines.push(`${ck?.ok ? 'PASS' : 'FAIL'} ${ck?.name ?? ''}${ck?.message ? ': ' + ck.message : ''}`);
|
|
11156
|
+
}
|
|
11157
|
+
return lines.join(String.fromCharCode(10));
|
|
10841
11158
|
}
|
|
10842
11159
|
|
|
10843
11160
|
// ── Shopify write policy (velaro-messaging /api/Shopify/WritePolicy) ───────
|
|
@@ -11182,6 +11499,31 @@ ${JSON.stringify(result, null, 2)}`;
|
|
|
11182
11499
|
}
|
|
11183
11500
|
|
|
11184
11501
|
// ── Stripe discount grants ────────────────────────────────────────────────────
|
|
11502
|
+
case 'sign_versions_list': {
|
|
11503
|
+
const r = await api('GET', `/api/Contract/${args.contractId}/versions`);
|
|
11504
|
+
const rows = r.versions || [];
|
|
11505
|
+
if (!rows.length) return `Contract ${args.contractId}: no document versions yet.`;
|
|
11506
|
+
return rows.map(v => `v${v.versionNumber} ${String(v.uploadedAt||'').slice(0,19)} ${v.uploadedBy||'-'} ${v.fileName||'-'} ${v.pageCount??'?'}p sha256:${String(v.sha256||'').slice(0,12)}${v.note ? ' note: '+v.note : ''}`).join('\n');
|
|
11507
|
+
}
|
|
11508
|
+
|
|
11509
|
+
case 'sign_versions_diff': {
|
|
11510
|
+
const r = await api('GET', `/api/Contract/${args.contractId}/diff?from=${args.from}&to=${args.to}`);
|
|
11511
|
+
if (r.identical) return `v${r.from} and v${r.to} are byte-identical.`;
|
|
11512
|
+
return (r.blocks || []).map(b => [...(b.removed||[]).map(x => '- '+x), ...(b.added||[]).map(x => '+ '+x)].join('\n')).join('\n\n') || 'No text differences.';
|
|
11513
|
+
}
|
|
11514
|
+
|
|
11515
|
+
case 'sign_versions_upload': {
|
|
11516
|
+
const buf = _readFileSync(args.filePath);
|
|
11517
|
+
if (buf.subarray(0, 5).toString('latin1') !== '%PDF-') throw new Error('Only PDF files can be uploaded as a contract version.');
|
|
11518
|
+
const form = new FormData();
|
|
11519
|
+
form.append('file', new Blob([buf], { type: 'application/pdf' }), String(args.filePath).split(/[\/]/).pop());
|
|
11520
|
+
if (args.note) form.append('note', args.note);
|
|
11521
|
+
const res = await fetch(`${API_BASE}/api/Contract/${args.contractId}/document`, { method: 'POST', headers: { Authorization: authHeader() }, body: form, signal: AbortSignal.timeout(60000) });
|
|
11522
|
+
const t = await res.text();
|
|
11523
|
+
if (!res.ok) throw new Error(`Upload failed ${res.status}: ${t.slice(0,300)} (new versions only while draft; after send/sign void and re-send)`);
|
|
11524
|
+
return `Uploaded new version for contract ${args.contractId}.`;
|
|
11525
|
+
}
|
|
11526
|
+
|
|
11185
11527
|
case 'coupon_grant_list': {
|
|
11186
11528
|
const qs = new URLSearchParams();
|
|
11187
11529
|
if (args.siteId !== undefined) qs.set('siteId', String(args.siteId));
|
|
@@ -11284,6 +11626,11 @@ ${JSON.stringify(result, null, 2)}`;
|
|
|
11284
11626
|
return `✅ Billing contact ${args.id} removed.`;
|
|
11285
11627
|
}
|
|
11286
11628
|
|
|
11629
|
+
case 'usage_alerts': {
|
|
11630
|
+
const r = await messagingApi('/Usage/alerts');
|
|
11631
|
+
if (!r.alerts?.length) return 'No plan-limit alerts. Every metered resource is under its warning threshold.';
|
|
11632
|
+
return r.alerts.map(a => `[${a.level.toUpperCase()}] ${a.displayName}: ${a.current} of ${a.limit} (${a.percent}%)${a.softLimit ? ' - overage is billed' : ''}`).join(' | ');
|
|
11633
|
+
}
|
|
11287
11634
|
case 'billing_notify_settings_get': {
|
|
11288
11635
|
const all = await api('GET', '/api/BillingNotifications/settings');
|
|
11289
11636
|
return Object.entries(all).map(([k, v]) => `${k}: ${v ?? '(not set: code default)'}`).join('\n');
|
|
@@ -12350,7 +12697,12 @@ ${JSON.stringify(result, null, 2)}`;
|
|
|
12350
12697
|
if (a === 'create') return await messagingPost('/api/admin/QualityQa/rubrics', args.rubric ?? {});
|
|
12351
12698
|
if (a === 'update') return await messagingPut('/api/admin/QualityQa/rubrics/' + args.id, args.rubric ?? {});
|
|
12352
12699
|
if (a === 'delete') return await messagingDel('/api/admin/QualityQa/rubrics/' + args.id);
|
|
12353
|
-
|
|
12700
|
+
if (a === 'starter') return await messagingPost('/api/admin/QualityQa/rubrics/starter', {});
|
|
12701
|
+
if (a === 'clone') return await messagingPost('/api/admin/QualityQa/rubrics/' + args.id + '/clone', args.rubric ?? {});
|
|
12702
|
+
if (a === 'activate') return await messagingPut('/api/admin/QualityQa/rubrics/' + args.id, { isActive: true });
|
|
12703
|
+
if (a === 'deactivate') return await messagingPut('/api/admin/QualityQa/rubrics/' + args.id, { isActive: false });
|
|
12704
|
+
if (a === 'rescore') return await messagingPost('/api/admin/QualityQa/rescore?days=' + encodeURIComponent(args.days ?? 30) + '&limit=' + encodeURIComponent(args.limit ?? 200), {});
|
|
12705
|
+
throw new Error('action must be list|get|default|starter|create|update|clone|activate|deactivate|rescore|delete');
|
|
12354
12706
|
}
|
|
12355
12707
|
case 'qa_low_scores': {
|
|
12356
12708
|
const qs = new URLSearchParams({ days: String(args.days ?? 30), skip: String(args.skip ?? 0), take: String(args.take ?? 50) });
|