@velaro/mcp-server 0.6.91 → 0.6.93
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 +2 -2
- package/server.js +328 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velaro/mcp-server",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.93",
|
|
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": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"node": ">=18.0.0"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
13
|
+
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
14
14
|
"qs": "^6.16.0"
|
|
15
15
|
},
|
|
16
16
|
"overrides": {
|
package/server.js
CHANGED
|
@@ -857,6 +857,46 @@ const TOOLS = [
|
|
|
857
857
|
description: 'Roll back an approved suggestion, restoring the original canned message text.',
|
|
858
858
|
inputSchema: { type: 'object', properties: { suggestionId: { type: 'number' }, notes: { type: 'string' } }, required: ['suggestionId'] },
|
|
859
859
|
},
|
|
860
|
+
{
|
|
861
|
+
name: 'chat_topics_get',
|
|
862
|
+
description: 'Chat Intelligence topics for this site: the active topic list (hint for the AI, not a rulebook), industry templates, groups, merge/rename mapping and limits. Intelligence Suite entitlement required.',
|
|
863
|
+
inputSchema: { type: 'object', properties: {} },
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
name: 'chat_topics_set',
|
|
867
|
+
description: 'Replace the active topic list (max 60 topics, 60 chars each, duplicates removed). Used for new classifications only; existing history is untouched. Optionally load a template first with industry.',
|
|
868
|
+
inputSchema: { type: 'object', properties: { topics: { type: 'array', items: { type: 'string' } }, industry: { type: 'string', description: 'Load a template instead: insurance, healthcare, retail, ecommerce, financial, saas, government, manufacturing' } } },
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
name: 'chat_topics_edit',
|
|
872
|
+
description: 'Merge, rename, retire or group topics. Applied when reports are read, so history follows the new names with zero AI cost. op = merge|rename|retire|group; from = topic names; to = target (merge/rename); group = group name (empty clears).',
|
|
873
|
+
inputSchema: { type: 'object', properties: { op: { type: 'string' }, from: { type: 'array', items: { type: 'string' } }, to: { type: 'string' }, group: { type: 'string' } }, required: ['op', 'from'] },
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
name: 'chat_topics_group_suggestions',
|
|
877
|
+
description: 'Suggested groups for the current topics, computed from topic names and counts with no AI call.',
|
|
878
|
+
inputSchema: { type: 'object', properties: { days: { type: 'number' } } },
|
|
879
|
+
},
|
|
880
|
+
{
|
|
881
|
+
name: 'chat_topics_suggestions',
|
|
882
|
+
description: 'Suggested topics: the short topic the AI proposed for chats that fit nothing on the list, with counts and example conversation ids.',
|
|
883
|
+
inputSchema: { type: 'object', properties: { days: { type: 'number' } } },
|
|
884
|
+
},
|
|
885
|
+
{
|
|
886
|
+
name: 'chat_topics_resolve_suggestion',
|
|
887
|
+
description: 'Accept (adds to the list), merge or rename into an existing topic (target), or dismiss a suggested topic. History follows with no AI cost.',
|
|
888
|
+
inputSchema: { type: 'object', properties: { suggestion: { type: 'string' }, action: { type: 'string', description: 'accept|merge|rename|dismiss' }, target: { type: 'string' } }, required: ['suggestion', 'action'] },
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
name: 'chat_topics_reclassify',
|
|
892
|
+
description: 'Optional: re-run the classifier on older chats. Call with confirm omitted/false to get the cost estimate (scope other = only unlisted chats, all = everything; hard cap 500). Resend with confirm=true to queue it. Never needed for merge/rename/group.',
|
|
893
|
+
inputSchema: { type: 'object', properties: { scope: { type: 'string' }, days: { type: 'number' }, max: { type: 'number' }, confirm: { type: 'boolean' } } },
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
name: 'chat_topics_analysis',
|
|
897
|
+
description: 'Optional one-call analysis (cheap model, aggregate counts only, PII redacted, cached, one run per day per site) that proposes merges/renames/groups. confirm omitted/false returns the estimate and any cached result; confirm=true runs it. Then call with proposalId + accept to apply or reject one proposal.',
|
|
898
|
+
inputSchema: { type: 'object', properties: { confirm: { type: 'boolean' }, proposalId: { type: 'string' }, accept: { type: 'boolean' } } },
|
|
899
|
+
},
|
|
860
900
|
{
|
|
861
901
|
name: 'chat_intelligence_recommendations',
|
|
862
902
|
description: 'List the active (non-dismissed) AI-generated improvement recommendations for this site — widget availability, bot gaps, missed-chat patterns, content-gap drafts. Each row includes id, type, priority, title, body, supportingDataBlob, plus lifecycle fields (appliedAt, appliedArtifactRef, rolledBackAt).',
|
|
@@ -1008,6 +1048,7 @@ Key node shapes:
|
|
|
1008
1048
|
propName:"myAnswerVar" } // variable that stores the result as JSON array string
|
|
1009
1049
|
• condition: { type:"action", actionType:"condition", conditionOptions:[...] }
|
|
1010
1050
|
• transferToAI: { type:"action", actionType:"transferToAI", aiConfigId:N }
|
|
1051
|
+
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.
|
|
1011
1052
|
|
|
1012
1053
|
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.`,
|
|
1013
1054
|
inputSchema: {
|
|
@@ -1021,6 +1062,20 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
1021
1062
|
},
|
|
1022
1063
|
},
|
|
1023
1064
|
|
|
1065
|
+
{
|
|
1066
|
+
name: 'ai_followups_site_get',
|
|
1067
|
+
description: "Read this account's default for AI follow-up suggestions (the AI Chatbot node's \"AI mode\": after each web-chat bot reply the bot offers 2-4 short tappable follow-up replies). A node's own suggestFollowUps setting wins over this default; platform default is off.",
|
|
1068
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
name: 'ai_followups_site_set',
|
|
1072
|
+
description: "Set this account's default for AI follow-up suggestions. enabled=true/false, or omit/null to clear (platform default: off). Per-node settings (workflow_update_node: suggestFollowUps) override this. Returns the value read back from the server.",
|
|
1073
|
+
inputSchema: {
|
|
1074
|
+
type: 'object',
|
|
1075
|
+
properties: { enabled: { type: ['boolean', 'null'], description: 'true = on, false = off, null/omitted = clear' } },
|
|
1076
|
+
required: [],
|
|
1077
|
+
},
|
|
1078
|
+
},
|
|
1024
1079
|
// ── Surveys ──────────────────────────────────────────────────────────────
|
|
1025
1080
|
{
|
|
1026
1081
|
name: 'survey_list',
|
|
@@ -1554,10 +1609,28 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
1554
1609
|
},
|
|
1555
1610
|
buttonLabel: { type: 'string', description: 'Optional action button label' },
|
|
1556
1611
|
buttonUrl: { type: 'string', description: 'Optional action button URL (required if buttonLabel is set)' },
|
|
1612
|
+
mentions: { type: 'array', description: 'People to @mention (real Teams notification). Velaro staff only. Uses the ops Teams sender instead of a webhook.', items: { type: 'object', properties: { user: { type: 'string', description: 'Entra object id or UPN, e.g. andrea@velaro.com' }, name: { type: 'string', description: 'Display name shown in the @mention' } }, required: ['user'] } },
|
|
1613
|
+
destination: { type: 'string', description: 'Named channel in the DevOps team instead of General: prs, workflows, deploys, alerts, qa, general. Velaro staff only; uses the ops Teams sender instead of a webhook.' },
|
|
1557
1614
|
},
|
|
1558
1615
|
required: ['title'],
|
|
1559
1616
|
},
|
|
1560
1617
|
},
|
|
1618
|
+
{
|
|
1619
|
+
name: 'teams_send_direct',
|
|
1620
|
+
description: 'Send a 1:1 Teams message (Adaptive Card) directly to any user in the Velaro tenant by UPN/email or Entra object id. Velaro staff only. Graph chat first, Velaro Bot proactive fallback.',
|
|
1621
|
+
inputSchema: {
|
|
1622
|
+
type: 'object',
|
|
1623
|
+
properties: {
|
|
1624
|
+
user: { type: 'string', description: 'UPN/email (alex@velaro.com) or Entra object id' },
|
|
1625
|
+
title: { type: 'string', description: 'Card title' },
|
|
1626
|
+
message: { type: 'string', description: 'Message text' },
|
|
1627
|
+
fields: { type: 'array', description: 'Optional fact rows', items: { type: 'object', properties: { label: { type: 'string' }, value: { type: 'string' } }, required: ['label', 'value'] } },
|
|
1628
|
+
buttonLabel: { type: 'string', description: 'Optional button label' },
|
|
1629
|
+
buttonUrl: { type: 'string', description: 'Optional button URL' },
|
|
1630
|
+
},
|
|
1631
|
+
required: ['user', 'title'],
|
|
1632
|
+
},
|
|
1633
|
+
},
|
|
1561
1634
|
|
|
1562
1635
|
// ── Slack ─────────────────────────────────────────────────────────────────
|
|
1563
1636
|
{
|
|
@@ -3017,7 +3090,7 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
3017
3090
|
inputSchema: {
|
|
3018
3091
|
type: 'object',
|
|
3019
3092
|
properties: {
|
|
3020
|
-
report: { type: 'string', enum: ['program_breakdown', 'agent_scorecard', 'qa_coverage', 'coaching_queue', 'sentiment_breakdown', 'topic_radar', 'weekly_brief', 'weekly_brief_send_test'] },
|
|
3093
|
+
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'] },
|
|
3021
3094
|
from: { type: 'string', description: 'yyyy-MM-dd start (default: 30 days ago).' },
|
|
3022
3095
|
to: { type: 'string', description: 'yyyy-MM-dd end, inclusive (default: today).' },
|
|
3023
3096
|
groupBy: { type: 'string', description: 'program_breakdown: website|team|platform|device|entrypage. sentiment_breakdown: website|team|agent|theme.' },
|
|
@@ -3027,6 +3100,14 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
3027
3100
|
perAgent: { type: 'number', description: 'coaching_queue: chats listed per agent (default 5).' },
|
|
3028
3101
|
limit: { type: 'number', description: 'topic_radar: max topics (default 25).' },
|
|
3029
3102
|
days: { type: 'number', description: 'weekly_brief: days to cover (default 7).' },
|
|
3103
|
+
windowDays: { type: 'number', description: 'repeat_contact: days a contact has to come back to count as a repeat (1-30, default 7).' },
|
|
3104
|
+
weeks: { type: 'number', description: 'demand_coverage: whole local weeks of history (1-12, default 6). from/to are IGNORED by demand_coverage.' },
|
|
3105
|
+
handleMinutes: { type: 'number', description: 'demand_coverage: assumed minutes per chat (1-120, default 10).' },
|
|
3106
|
+
maxConcurrent: { type: 'number', description: 'demand_coverage: assumed chats an agent handles at once (1-10, default 3).' },
|
|
3107
|
+
timeZone: { type: 'string', description: 'demand_coverage: IANA time zone (default America/Los_Angeles).' },
|
|
3108
|
+
lowScore: { type: 'number', description: 'bot_containment: QA score below which a handed-off chat counts as regret (1-100, default 60).' },
|
|
3109
|
+
includeDemo: { type: 'boolean', description: 'true = show only the seeded sample (demo) data. Default false.' },
|
|
3110
|
+
promiseId: { type: 'string', description: 'promise_dismiss: the promise id to dismiss (managers only).' },
|
|
3030
3111
|
},
|
|
3031
3112
|
required: ['report'],
|
|
3032
3113
|
},
|
|
@@ -3214,6 +3295,7 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
3214
3295
|
displayName: { type: 'string', description: 'Visitor-facing avatar name shown on the badge (e.g. "Moshky"). Empty string resets to "AI".' },
|
|
3215
3296
|
showBadge: { type: 'boolean', description: 'Show the name badge over the avatar video. Default true.' },
|
|
3216
3297
|
sizePx: { type: 'number', description: 'Max avatar panel width in px (160-640, square). Default 360.' },
|
|
3298
|
+
allowVisitorCollapse: { type: 'boolean', description: 'Let visitors hide or collapse the avatar panel (up/down arrow and X). Default true.' },
|
|
3217
3299
|
maxAvatarMinutesPerMonth: { type: 'number', description: 'Monthly avatar video minutes cap for the site. Whole number 1-100000; never unlimited (0/negative rejected).' },
|
|
3218
3300
|
maxAvatarTtsChars: { type: 'number', description: 'Monthly avatar TTS character cap (avatar_tts_chars) for the site. Whole number 1-100000000; never unlimited (0/negative rejected). Overage pricing is a platform-wide ladder, not per site.' },
|
|
3219
3301
|
},
|
|
@@ -3709,6 +3791,15 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
3709
3791
|
required: [],
|
|
3710
3792
|
},
|
|
3711
3793
|
},
|
|
3794
|
+
{
|
|
3795
|
+
name: 'ai_federation',
|
|
3796
|
+
description: 'SuperAdmin: Anthropic Workload Identity Federation for the AI gateway. action=status shows enabled, circuit breaker state, last success/failure reason, federated vs API-key fallback counts and token expiry. action=test does a real token exchange plus a 1-token Haiku message and reports which path (federated, apikey, apikey-fallback) served it. action=reset_breaker closes the breaker so the next call retries federation.',
|
|
3797
|
+
inputSchema: {
|
|
3798
|
+
type: 'object',
|
|
3799
|
+
properties: { action: { type: 'string', enum: ['status', 'test', 'reset_breaker'], description: 'Default status.' } },
|
|
3800
|
+
required: [],
|
|
3801
|
+
},
|
|
3802
|
+
},
|
|
3712
3803
|
{
|
|
3713
3804
|
name: 'status_incident_list',
|
|
3714
3805
|
description: 'Velaro staff only (SuperAdmin). Lists public status page incidents, including component overrides (IncidentType "Override").',
|
|
@@ -4939,6 +5030,53 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
4939
5030
|
description: 'List candidate applications submitted via the Recruiting Bot for this site (name, email, phone, role interest, work authorization, availability, submitted timestamp). Never contains EEOC self-ID data.',
|
|
4940
5031
|
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
4941
5032
|
},
|
|
5033
|
+
{
|
|
5034
|
+
name: 'recruiting_ats_failures',
|
|
5035
|
+
description: 'List candidate applications whose ATS write-back is Pending, Failed or Abandoned (status, attempts, last error, next retry). Requires EnableRecruitingBot.',
|
|
5036
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
5037
|
+
},
|
|
5038
|
+
{
|
|
5039
|
+
name: 'recruiting_ats_retry',
|
|
5040
|
+
description: 'Retry the ATS write-back for one candidate application right now (resets the attempt counter). Returns the new sync status.',
|
|
5041
|
+
inputSchema: { type: 'object', properties: { applicationId: { type: 'number' } }, required: ['applicationId'] },
|
|
5042
|
+
},
|
|
5043
|
+
{
|
|
5044
|
+
name: 'recruiting_apply_settings',
|
|
5045
|
+
description: 'Set public apply form options for this site (apply-if-present): publicApplyEnabled toggles the public apply endpoint; autoCreateCaseTypeId picks the case type auto-created per application (0 clears).',
|
|
5046
|
+
inputSchema: {
|
|
5047
|
+
type: 'object',
|
|
5048
|
+
properties: {
|
|
5049
|
+
publicApplyEnabled: { type: 'boolean' },
|
|
5050
|
+
autoCreateCaseTypeId: { type: 'number', description: 'Case type id; 0 clears the auto-create link' },
|
|
5051
|
+
defaultListingDays: { type: 'number', description: '1-365: how many days a new listing stays live when its feed gives no expiry. Requires the Job Board lifecycle package.' },
|
|
5052
|
+
},
|
|
5053
|
+
required: [],
|
|
5054
|
+
},
|
|
5055
|
+
},
|
|
5056
|
+
{
|
|
5057
|
+
name: 'recruiting_bot_settings',
|
|
5058
|
+
description: 'Get or set the recruiting bot customer controls (apply-if-present; no fields = read): pageContextEnabled (use the job from the host page Velaro context call), revealPostedDate, revealDescription, revealApplyLink, publicApplyEnabled, applyMode (both|link|inproduct), maxResults (1-10), greeting (max 500), tone (friendly|professional|concise).',
|
|
5059
|
+
inputSchema: {
|
|
5060
|
+
type: 'object',
|
|
5061
|
+
properties: {
|
|
5062
|
+
pageContextEnabled: { type: 'boolean' },
|
|
5063
|
+
revealPostedDate: { type: 'boolean' },
|
|
5064
|
+
revealDescription: { type: 'boolean' },
|
|
5065
|
+
revealApplyLink: { type: 'boolean' },
|
|
5066
|
+
publicApplyEnabled: { type: 'boolean' },
|
|
5067
|
+
applyMode: { type: 'string', enum: ['both', 'link', 'inproduct'] },
|
|
5068
|
+
maxResults: { type: 'number' },
|
|
5069
|
+
greeting: { type: 'string' },
|
|
5070
|
+
tone: { type: 'string', enum: ['friendly', 'professional', 'concise'] },
|
|
5071
|
+
},
|
|
5072
|
+
required: [],
|
|
5073
|
+
},
|
|
5074
|
+
},
|
|
5075
|
+
{
|
|
5076
|
+
name: 'recruiting_download_resume',
|
|
5077
|
+
description: 'Download a candidate application resume. Returns { fileName, contentType, sizeBytes, base64 }. Files over 5 MB are refused; use the CLI (velaro recruiting applications resume) for those.',
|
|
5078
|
+
inputSchema: { type: 'object', properties: { applicationId: { type: 'number' } }, required: ['applicationId'] },
|
|
5079
|
+
},
|
|
4942
5080
|
// Recruiting/Applications/ExportEeoc (voluntary self-ID CSV export) is intentionally NOT
|
|
4943
5081
|
// exposed as an MCP tool -- it returns sensitive EEOC/OFCCP self-identification PII in bulk.
|
|
4944
5082
|
// It is administrator-only and audit-logged server-side (403 for ordinary agents); the
|
|
@@ -5016,6 +5154,29 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
5016
5154
|
required: ['sourceId', 'title'],
|
|
5017
5155
|
},
|
|
5018
5156
|
},
|
|
5157
|
+
// -- Job Board listing lifecycle (package "job-board-lifecycle", default OFF: 403 for a site without it) --
|
|
5158
|
+
{
|
|
5159
|
+
name: 'jobboard_lifecycle_status',
|
|
5160
|
+
description: 'Does this site have the Job Board listing lifecycle package (close/renew, expiry, JSON-LD listing pages), and what is its default listing duration? Returns { entitled, defaultListingDays }.',
|
|
5161
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
5162
|
+
},
|
|
5163
|
+
{
|
|
5164
|
+
name: 'jobboard_close_listing',
|
|
5165
|
+
description: 'Close a job listing: it stops appearing in search and the public board, the public listing page answers 410, and applying to it answers 410. Idempotent. Requires the Job Board lifecycle package (403 otherwise); 404 for an id that is not on this site.',
|
|
5166
|
+
inputSchema: { type: 'object', properties: { listingId: { type: 'number' } }, required: ['listingId'] },
|
|
5167
|
+
},
|
|
5168
|
+
{
|
|
5169
|
+
name: 'jobboard_renew_listing',
|
|
5170
|
+
description: 'Renew a job listing: extends its expiry by `days` (default: the site\'s default listing days) from the later of its current expiry and now, increments its renewal count and republishes it if it was closed or expired. Requires the Job Board lifecycle package (403 otherwise).',
|
|
5171
|
+
inputSchema: {
|
|
5172
|
+
type: 'object',
|
|
5173
|
+
properties: {
|
|
5174
|
+
listingId: { type: 'number' },
|
|
5175
|
+
days: { type: 'number', description: '1-365; omit for the site default' },
|
|
5176
|
+
},
|
|
5177
|
+
required: ['listingId'],
|
|
5178
|
+
},
|
|
5179
|
+
},
|
|
5019
5180
|
{
|
|
5020
5181
|
name: 'jobboard_move_application_stage',
|
|
5021
5182
|
description: 'Move a candidate application to a new HR/agency-side pipeline stage on the applicant Kanban board (Applied, Screened, Interview, Offer, Hired, Rejected).',
|
|
@@ -5815,6 +5976,28 @@ async function handleTool(name, args) {
|
|
|
5815
5976
|
if (args.teamId != null) qs.set('teamId', String(args.teamId));
|
|
5816
5977
|
return await messagingGet(`/Reports/chat-intelligence/themes?${qs.toString()}`);
|
|
5817
5978
|
}
|
|
5979
|
+
case 'chat_topics_get':
|
|
5980
|
+
return await messagingGet('/ChatIntelligenceTopics');
|
|
5981
|
+
case 'chat_topics_set':
|
|
5982
|
+
return args.industry
|
|
5983
|
+
? await messagingPost('/ChatIntelligenceTopics/template', { industry: args.industry })
|
|
5984
|
+
: await messagingPut('/ChatIntelligenceTopics', { topics: args.topics ?? [] });
|
|
5985
|
+
case 'chat_topics_edit':
|
|
5986
|
+
return await messagingPost('/ChatIntelligenceTopics/mapping', { ops: [{ op: args.op, from: args.from ?? [], to: args.to ?? null, group: args.group ?? null }] });
|
|
5987
|
+
case 'chat_topics_group_suggestions':
|
|
5988
|
+
return await messagingGet(`/ChatIntelligenceTopics/group-suggestions?days=${args.days ?? 90}`);
|
|
5989
|
+
case 'chat_topics_suggestions':
|
|
5990
|
+
return await messagingGet(`/ChatIntelligenceTopics/suggestions?days=${args.days ?? 90}`);
|
|
5991
|
+
case 'chat_topics_resolve_suggestion':
|
|
5992
|
+
return await messagingPost('/ChatIntelligenceTopics/suggestions/resolve', { suggestion: args.suggestion, action: args.action, target: args.target ?? null });
|
|
5993
|
+
case 'chat_topics_reclassify':
|
|
5994
|
+
return args.confirm
|
|
5995
|
+
? await messagingPost('/ChatIntelligenceTopics/reclassify', { scope: args.scope ?? 'other', days: args.days ?? 90, max: args.max ?? null, confirm: true })
|
|
5996
|
+
: await messagingGet(`/ChatIntelligenceTopics/reclassify/estimate?scope=${args.scope ?? 'other'}&days=${args.days ?? 90}${args.max ? `&max=${args.max}` : ''}`);
|
|
5997
|
+
case 'chat_topics_analysis':
|
|
5998
|
+
if (args.proposalId) return await messagingPost('/ChatIntelligenceTopics/analysis/proposals', { id: args.proposalId, accept: args.accept !== false });
|
|
5999
|
+
if (args.confirm) return await messagingPost('/ChatIntelligenceTopics/analysis', { confirm: true });
|
|
6000
|
+
return { estimate: await messagingGet('/ChatIntelligenceTopics/analysis/estimate'), cached: await messagingGet('/ChatIntelligenceTopics/analysis') };
|
|
5818
6001
|
case 'canned_learning_suggestions':
|
|
5819
6002
|
return await messagingGet(`/CannedLearning/suggestions?state=${encodeURIComponent(args.state ?? 'pending_review')}`);
|
|
5820
6003
|
case 'canned_learning_generate':
|
|
@@ -5885,6 +6068,17 @@ async function handleTool(name, args) {
|
|
|
5885
6068
|
// [Authorize] does not accept vel_live_* service keys (only a handful of controllers opt
|
|
5886
6069
|
// into that manually) — these tools need VELARO_JWT, same requirement as the existing
|
|
5887
6070
|
// notifications_get/ai_config_staff_get tools they're modeled on.
|
|
6071
|
+
case 'ai_followups_site_get': {
|
|
6072
|
+
const r = await messagingGet('/SiteSettings/AiFollowUps');
|
|
6073
|
+
return JSON.stringify({ enabled: r?.enabled ?? null }, null, 2);
|
|
6074
|
+
}
|
|
6075
|
+
|
|
6076
|
+
case 'ai_followups_site_set': {
|
|
6077
|
+
await messagingPut('/SiteSettings/AiFollowUps', { enabled: typeof args.enabled === 'boolean' ? args.enabled : null });
|
|
6078
|
+
const r = await messagingGet('/SiteSettings/AiFollowUps');
|
|
6079
|
+
return JSON.stringify({ enabled: r?.enabled ?? null }, null, 2);
|
|
6080
|
+
}
|
|
6081
|
+
|
|
5888
6082
|
case 'survey_list': {
|
|
5889
6083
|
const surveys = (await messagingGet('/Surveys/list')) ?? [];
|
|
5890
6084
|
if (!surveys.length) return 'No surveys found.';
|
|
@@ -6077,7 +6271,7 @@ async function handleTool(name, args) {
|
|
|
6077
6271
|
return await rpc('ai_config_set_reply_format', args);
|
|
6078
6272
|
|
|
6079
6273
|
case 'site_identity_secret_status': {
|
|
6080
|
-
const settings = await api('GET', '/SiteSettings');
|
|
6274
|
+
const settings = await api('GET', '/api/SiteSettings');
|
|
6081
6275
|
const configured = settings?.identitySigningSecretConfigured ?? false;
|
|
6082
6276
|
const masked = settings?.identitySigningSecretMasked ?? null;
|
|
6083
6277
|
if (!configured) {
|
|
@@ -6117,7 +6311,7 @@ async function handleTool(name, args) {
|
|
|
6117
6311
|
}
|
|
6118
6312
|
|
|
6119
6313
|
case 'index_list': {
|
|
6120
|
-
const indexes = await messagingApi('/AzureIndexes/
|
|
6314
|
+
const indexes = await messagingApi('/AzureIndexes/List');
|
|
6121
6315
|
if (!Array.isArray(indexes) || !indexes.length) {
|
|
6122
6316
|
return 'No knowledge collections found for this site.';
|
|
6123
6317
|
}
|
|
@@ -6383,7 +6577,26 @@ async function handleTool(name, args) {
|
|
|
6383
6577
|
return await api('PUT', '/TeamsBot/ChannelConfig', body);
|
|
6384
6578
|
}
|
|
6385
6579
|
|
|
6580
|
+
case 'teams_send_direct': {
|
|
6581
|
+
const r = await api('POST', '/TeamsMessaging/Direct', {
|
|
6582
|
+
user: args.user, title: args.title, message: args.message || '',
|
|
6583
|
+
facts: (args.fields || []).map(f => ({ label: f.label, value: f.value })),
|
|
6584
|
+
actionUrl: args.buttonUrl, actionLabel: args.buttonLabel,
|
|
6585
|
+
});
|
|
6586
|
+
return r?.sent ? `Direct message sent to ${args.user} via ${r.path}.` : `Send failed: ${r?.error || 'unknown error'} ${(r?.warnings || []).join('; ')}`;
|
|
6587
|
+
}
|
|
6588
|
+
|
|
6386
6589
|
case 'teams_notify_card': {
|
|
6590
|
+
if ((args.mentions && args.mentions.length) || args.destination) {
|
|
6591
|
+
const r = await api('POST', '/TeamsMessaging/Card', {
|
|
6592
|
+
title: args.title,
|
|
6593
|
+
facts: (args.fields || []).map(f => ({ label: f.label, value: f.value })),
|
|
6594
|
+
actionUrl: args.buttonUrl, actionLabel: args.buttonLabel,
|
|
6595
|
+
destination: args.destination,
|
|
6596
|
+
mentions: (args.mentions || []).map(m => ({ user: m.user, name: m.name })),
|
|
6597
|
+
});
|
|
6598
|
+
return r?.sent ? `Card sent via ${r.path}${r.warnings?.length ? ' (' + r.warnings.join('; ') + ')' : ''}.` : `Send failed: ${r?.error || 'unknown error'}`;
|
|
6599
|
+
}
|
|
6387
6600
|
const webhooks = await api('GET', '/TeamsNotification');
|
|
6388
6601
|
if (!Array.isArray(webhooks) || !webhooks.length)
|
|
6389
6602
|
return 'No Teams webhooks configured. Go to Integrations → Microsoft Teams to add one, or call velaro teams create <name> <webhookUrl> via CLI.';
|
|
@@ -6449,7 +6662,8 @@ async function handleTool(name, args) {
|
|
|
6449
6662
|
}
|
|
6450
6663
|
|
|
6451
6664
|
case 'routing_delete': {
|
|
6452
|
-
|
|
6665
|
+
// RulesController.DeleteAsync takes the rule in the BODY (DELETE /Rules {id}); DELETE /rules/{id} never existed.
|
|
6666
|
+
await messagingRequest('DELETE', '/Rules', { id: args.id });
|
|
6453
6667
|
return `Deleted routing rule id=${args.id}`;
|
|
6454
6668
|
}
|
|
6455
6669
|
|
|
@@ -6988,7 +7202,7 @@ async function handleTool(name, args) {
|
|
|
6988
7202
|
|
|
6989
7203
|
// ── Layer 7: Admin Activity Log — recent channel/auth/integration events
|
|
6990
7204
|
// Useful for detecting send failures, auth events, channel errors not in integration logs.
|
|
6991
|
-
const activityLogP = safeJson(() =>
|
|
7205
|
+
const activityLogP = safeJson(() => messagingRequest('GET', '/AdminActivityLog?pageSize=10'));
|
|
6992
7206
|
|
|
6993
7207
|
const [health, ci, aiEx, velaroLogs, loggly, intLogPage, skillErr, activityLog] = await Promise.all([healthP, ciP, aiExP, velaroLoggerP, logglyP, intLogP, skillErrP, activityLogP]);
|
|
6994
7208
|
|
|
@@ -7113,7 +7327,7 @@ async function handleTool(name, args) {
|
|
|
7113
7327
|
const q = args.query ?? '';
|
|
7114
7328
|
const top = args.top ?? 5;
|
|
7115
7329
|
if (!q) return 'query is required.';
|
|
7116
|
-
const results = await
|
|
7330
|
+
const results = await messagingRequest('GET', `/AzureIndexes/content-search?q=${encodeURIComponent(q)}&top=${top}`);
|
|
7117
7331
|
if (results?.error) return `Index search error: ${results.error}`;
|
|
7118
7332
|
if (!Array.isArray(results) || !results.length) return `No results for "${q}". Index may be empty — trigger ingestion first.`;
|
|
7119
7333
|
const lines = [
|
|
@@ -7176,7 +7390,7 @@ async function handleTool(name, args) {
|
|
|
7176
7390
|
return JSON.stringify(r, null, 2);
|
|
7177
7391
|
}
|
|
7178
7392
|
case 'index_status': {
|
|
7179
|
-
const sources = await
|
|
7393
|
+
const sources = await messagingRequest('GET', '/AzureIndexes/IntegrationSources');
|
|
7180
7394
|
if (!Array.isArray(sources) || !sources.length) return 'No indexed sources found. Run index_ingest to populate.';
|
|
7181
7395
|
const lines = [
|
|
7182
7396
|
'=== Index Status ===',
|
|
@@ -7189,7 +7403,7 @@ async function handleTool(name, args) {
|
|
|
7189
7403
|
if (!args.jobId) return 'jobId is required.';
|
|
7190
7404
|
const body = { jobId: args.jobId };
|
|
7191
7405
|
if (args.indexName) body.indexName = args.indexName;
|
|
7192
|
-
const result = await
|
|
7406
|
+
const result = await messagingRequest('POST', '/AzureIndexes/IngestJobDirect', body);
|
|
7193
7407
|
if (result?.error) return `Ingestion error: ${result.error}`;
|
|
7194
7408
|
return `Ingestion started for job ${args.jobId}.\n Physical index: ${result?.physicalIndex ?? 'unknown'}\n\nEmbedding runs in the background (~1-2 min). Use index_status to verify chunks appear.`;
|
|
7195
7409
|
}
|
|
@@ -7214,7 +7428,7 @@ async function handleTool(name, args) {
|
|
|
7214
7428
|
}
|
|
7215
7429
|
|
|
7216
7430
|
case 'uploaded_files_list': {
|
|
7217
|
-
const files = await
|
|
7431
|
+
const files = await messagingRequest('GET', '/uploadedFiles/list/AI');
|
|
7218
7432
|
if (!Array.isArray(files) || !files.length) return 'No AI training files uploaded for this site.';
|
|
7219
7433
|
const lines = ['=== Uploaded AI Training Files ===',
|
|
7220
7434
|
...files.map(f => ` [id=${f.id}] ${f.fileName} size=${f.fileLength ? Math.round(f.fileLength/1024)+'KB' : '?'}`)];
|
|
@@ -7227,7 +7441,7 @@ async function handleTool(name, args) {
|
|
|
7227
7441
|
if (args.fileId) body.id = args.fileId;
|
|
7228
7442
|
if (args.fileName) body.fileName = args.fileName;
|
|
7229
7443
|
if (args.indexName) body.indexName = args.indexName;
|
|
7230
|
-
const result = await
|
|
7444
|
+
const result = await messagingRequest('POST', '/uploadedFiles/reindex', body);
|
|
7231
7445
|
if (result?.error) return `Reindex error: ${result.error}`;
|
|
7232
7446
|
return `✅ Re-indexed '${result.fileName}': ${result.chunksIndexed} chunks indexed.\n\nUse index_status to verify the chunks appear in the knowledge index.`;
|
|
7233
7447
|
}
|
|
@@ -8375,12 +8589,18 @@ async function handleTool(name, args) {
|
|
|
8375
8589
|
program_breakdown: 'program-breakdown', agent_scorecard: 'agent-scorecard', qa_coverage: 'qa-coverage',
|
|
8376
8590
|
coaching_queue: 'coaching-queue', sentiment_breakdown: 'sentiment-breakdown', topic_radar: 'topic-radar',
|
|
8377
8591
|
weekly_brief: 'weekly-brief', weekly_brief_send_test: 'weekly-brief/send-test',
|
|
8592
|
+
repeat_contact: 'repeat-contact', demand_coverage: 'demand-coverage', bot_containment: 'bot-containment',
|
|
8593
|
+
effort_index: 'effort-index', promise_ledger: 'promise-ledger',
|
|
8378
8594
|
};
|
|
8595
|
+
if (args.report === 'promise_dismiss') {
|
|
8596
|
+
if (!args.promiseId) throw new Error('promiseId is required for promise_dismiss');
|
|
8597
|
+
return JSON.stringify(await messagingPost('/api/enterprise-reports/promise-ledger/' + encodeURIComponent(String(args.promiseId)) + '/dismiss', {}), null, 2);
|
|
8598
|
+
}
|
|
8379
8599
|
const seg = ER_PATHS[args.report];
|
|
8380
8600
|
if (!seg) throw new Error('report must be one of: ' + Object.keys(ER_PATHS).join(', '));
|
|
8381
8601
|
if (args.report === 'weekly_brief_send_test') return JSON.stringify(await messagingPost('/api/enterprise-reports/' + seg, {}), null, 2);
|
|
8382
8602
|
const qs = new URLSearchParams();
|
|
8383
|
-
for (const k of ['from', 'to', 'groupBy', 'deploymentId', 'teamId', 'threshold', 'perAgent', 'limit', 'days']) {
|
|
8603
|
+
for (const k of ['from', 'to', 'groupBy', 'deploymentId', 'teamId', 'threshold', 'perAgent', 'limit', 'days', 'windowDays', 'weeks', 'handleMinutes', 'maxConcurrent', 'timeZone', 'lowScore', 'includeDemo']) {
|
|
8384
8604
|
if (args[k] != null && args[k] !== '') qs.set(k, String(args[k]));
|
|
8385
8605
|
}
|
|
8386
8606
|
const q = qs.toString();
|
|
@@ -8532,6 +8752,7 @@ async function handleTool(name, args) {
|
|
|
8532
8752
|
if (args.displayName !== undefined) body.displayName = args.displayName;
|
|
8533
8753
|
if (args.showBadge !== undefined) body.showBadge = args.showBadge;
|
|
8534
8754
|
if (args.sizePx !== undefined) body.sizePx = args.sizePx;
|
|
8755
|
+
if (args.allowVisitorCollapse !== undefined) body.allowVisitorCollapse = args.allowVisitorCollapse;
|
|
8535
8756
|
for (const [arg, max] of [['maxAvatarMinutesPerMonth', 100000], ['maxAvatarTtsChars', 100000000]]) {
|
|
8536
8757
|
if (args[arg] === undefined) continue;
|
|
8537
8758
|
if (!Number.isInteger(args[arg]) || args[arg] < 1 || args[arg] > max) throw new Error(`${arg} must be a whole number between 1 and ${max} (never unlimited).`);
|
|
@@ -8797,6 +9018,13 @@ async function handleTool(name, args) {
|
|
|
8797
9018
|
return JSON.stringify(data, null, 2);
|
|
8798
9019
|
}
|
|
8799
9020
|
|
|
9021
|
+
case 'ai_federation': {
|
|
9022
|
+
const action = args.action || 'status';
|
|
9023
|
+
if (action === 'test') return JSON.stringify(await api('POST', '/AiFederation/test', {}), null, 2);
|
|
9024
|
+
if (action === 'reset_breaker') return JSON.stringify(await api('POST', '/AiFederation/reset-breaker', {}), null, 2);
|
|
9025
|
+
return JSON.stringify(await api('GET', '/AiFederation/status'), null, 2);
|
|
9026
|
+
}
|
|
9027
|
+
|
|
8800
9028
|
case 'status_incident_list': {
|
|
8801
9029
|
const rows = await messagingRequest('GET', `/superadmin/status-incidents?filter=${encodeURIComponent(args.filter || 'active')}`);
|
|
8802
9030
|
return JSON.stringify(rows, null, 2);
|
|
@@ -9133,7 +9361,7 @@ async function handleTool(name, args) {
|
|
|
9133
9361
|
|
|
9134
9362
|
case 'magento_diagnose': {
|
|
9135
9363
|
if (!args.siteId) return 'siteId is required.';
|
|
9136
|
-
const r = await
|
|
9364
|
+
const r = await messagingRequest('GET', `/diagnostics/test-magento?siteId=${encodeURIComponent(args.siteId)}`);
|
|
9137
9365
|
return JSON.stringify(r, null, 2);
|
|
9138
9366
|
}
|
|
9139
9367
|
|
|
@@ -9141,18 +9369,18 @@ async function handleTool(name, args) {
|
|
|
9141
9369
|
case 'sap_diagnose': {
|
|
9142
9370
|
if (!args.siteId) return 'siteId is required.';
|
|
9143
9371
|
const deep = args.deep === false ? 'false' : 'true';
|
|
9144
|
-
const r = await
|
|
9372
|
+
const r = await messagingRequest('GET', `/diagnostics/test-sap?siteId=${encodeURIComponent(args.siteId)}&deep=${deep}`);
|
|
9145
9373
|
return JSON.stringify(r, null, 2);
|
|
9146
9374
|
}
|
|
9147
9375
|
|
|
9148
9376
|
case 'business_action_list': {
|
|
9149
|
-
const r = await
|
|
9377
|
+
const r = await messagingRequest('GET', '/BusinessActions');
|
|
9150
9378
|
return JSON.stringify(r, null, 2);
|
|
9151
9379
|
}
|
|
9152
9380
|
|
|
9153
9381
|
case 'business_action_route': {
|
|
9154
9382
|
if (!args.text) return 'text is required.';
|
|
9155
|
-
const r = await
|
|
9383
|
+
const r = await messagingRequest('POST', '/BusinessActions/Route', { text: args.text, max: args.max });
|
|
9156
9384
|
return JSON.stringify(r, null, 2);
|
|
9157
9385
|
}
|
|
9158
9386
|
|
|
@@ -9162,7 +9390,7 @@ async function handleTool(name, args) {
|
|
|
9162
9390
|
const qs = [];
|
|
9163
9391
|
if (args.idempotencyKey) qs.push(`idempotencyKey=${encodeURIComponent(args.idempotencyKey)}`);
|
|
9164
9392
|
if (args.conversationId) qs.push(`conversationId=${encodeURIComponent(args.conversationId)}`);
|
|
9165
|
-
const r = await
|
|
9393
|
+
const r = await messagingRequest('POST', `/BusinessActions/${encodeURIComponent(args.name)}/Execute${qs.length ? '?' + qs.join('&') : ''}`, args.request);
|
|
9166
9394
|
return JSON.stringify(r, null, 2);
|
|
9167
9395
|
}
|
|
9168
9396
|
|
|
@@ -9705,7 +9933,7 @@ async function handleTool(name, args) {
|
|
|
9705
9933
|
}
|
|
9706
9934
|
|
|
9707
9935
|
case 'billing_plan_status': {
|
|
9708
|
-
const s = await api('GET', '/BillingPayment/plan-status'
|
|
9936
|
+
const s = await api('GET', '/api/BillingPayment/plan-status');
|
|
9709
9937
|
const lines = [`Site ${s.siteId} — Subscription Status`];
|
|
9710
9938
|
if (s.subscription) {
|
|
9711
9939
|
lines.push(`Seats: ${s.subscription.maxLicensedSeats} | Conv/mo: ${s.subscription.maxConversationsPerMonth || '∞'} | Self-serve: ${s.subscription.enableSelfServeSeats ? 'YES' : 'no'}`);
|
|
@@ -10201,6 +10429,89 @@ async function handleTool(name, args) {
|
|
|
10201
10429
|
return JSON.stringify(listing, null, 2);
|
|
10202
10430
|
}
|
|
10203
10431
|
|
|
10432
|
+
case 'recruiting_ats_failures': {
|
|
10433
|
+
const rows = await messagingGet('/Recruiting/Applications/AtsFailures');
|
|
10434
|
+
return JSON.stringify(rows, null, 2);
|
|
10435
|
+
}
|
|
10436
|
+
case 'recruiting_ats_retry': {
|
|
10437
|
+
const id = Number(args.applicationId);
|
|
10438
|
+
if (!Number.isInteger(id) || id <= 0) throw new Error('applicationId must be a positive integer.');
|
|
10439
|
+
const r = await messagingPost(`/Recruiting/Applications/${id}/AtsRetry`, {});
|
|
10440
|
+
return JSON.stringify(r, null, 2);
|
|
10441
|
+
}
|
|
10442
|
+
case 'recruiting_apply_settings': {
|
|
10443
|
+
const body = {};
|
|
10444
|
+
if (args.publicApplyEnabled !== undefined) body.publicApplyEnabled = Boolean(args.publicApplyEnabled);
|
|
10445
|
+
if (args.autoCreateCaseTypeId !== undefined) {
|
|
10446
|
+
const n = Number(args.autoCreateCaseTypeId);
|
|
10447
|
+
if (!Number.isInteger(n) || n < 0) throw new Error('autoCreateCaseTypeId must be an integer >= 0 (0 clears).');
|
|
10448
|
+
body.autoCreateCaseTypeId = n;
|
|
10449
|
+
}
|
|
10450
|
+
if (args.defaultListingDays !== undefined) {
|
|
10451
|
+
const d = Number(args.defaultListingDays);
|
|
10452
|
+
if (!Number.isInteger(d) || d < 1 || d > 365) throw new Error('defaultListingDays must be an integer from 1 to 365.');
|
|
10453
|
+
body.defaultListingDays = d;
|
|
10454
|
+
}
|
|
10455
|
+
if (!Object.keys(body).length) throw new Error('Specify publicApplyEnabled, autoCreateCaseTypeId and/or defaultListingDays.');
|
|
10456
|
+
const r = await messagingPut('/Recruiting/ApplySettings', body);
|
|
10457
|
+
return JSON.stringify(r, null, 2);
|
|
10458
|
+
}
|
|
10459
|
+
case 'recruiting_bot_settings': {
|
|
10460
|
+
const fields = ['pageContextEnabled', 'revealPostedDate', 'revealDescription', 'revealApplyLink', 'publicApplyEnabled', 'applyMode', 'maxResults', 'greeting', 'tone'];
|
|
10461
|
+
const body = {};
|
|
10462
|
+
for (const f of fields) if (args[f] !== undefined) body[f] = args[f];
|
|
10463
|
+
if (body.applyMode !== undefined && !['both', 'link', 'inproduct'].includes(String(body.applyMode).toLowerCase())) throw new Error('applyMode must be both, link or inproduct.');
|
|
10464
|
+
if (body.tone !== undefined && !['friendly', 'professional', 'concise'].includes(String(body.tone).toLowerCase())) throw new Error('tone must be friendly, professional or concise.');
|
|
10465
|
+
if (body.maxResults !== undefined && !(Number.isInteger(Number(body.maxResults)) && Number(body.maxResults) >= 1 && Number(body.maxResults) <= 10)) throw new Error('maxResults must be an integer from 1 to 10.');
|
|
10466
|
+
const r = Object.keys(body).length ? await messagingPut('/Recruiting/BotSettings', body) : await messagingGet('/Recruiting/BotSettings');
|
|
10467
|
+
return JSON.stringify(r, null, 2);
|
|
10468
|
+
}
|
|
10469
|
+
case 'recruiting_download_resume': {
|
|
10470
|
+
const id = Number(args.applicationId);
|
|
10471
|
+
if (!Number.isInteger(id) || id <= 0) throw new Error('applicationId must be a positive integer.');
|
|
10472
|
+
if (!MESSAGING_API) throw new Error('VELARO_MESSAGING_API is not configured.');
|
|
10473
|
+
const res = await fetch(`${MESSAGING_API}/Recruiting/Applications/${id}/Resume`, {
|
|
10474
|
+
headers: { Authorization: authHeader() },
|
|
10475
|
+
signal: AbortSignal.timeout(30000),
|
|
10476
|
+
});
|
|
10477
|
+
if (!res.ok) {
|
|
10478
|
+
const text = await res.text().catch(() => '');
|
|
10479
|
+
throw new Error(`/Recruiting/Applications/${id}/Resume -> ${res.status}: ${text.slice(0, 300)}`);
|
|
10480
|
+
}
|
|
10481
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
10482
|
+
if (buf.length > 5 * 1024 * 1024) throw new Error(`Resume is ${buf.length} bytes (over the 5 MB MCP limit); use the CLI: velaro recruiting applications resume ${id}`);
|
|
10483
|
+
const cd = /filename\*?=(?:UTF-8'')?"?([^";]+)"?/i.exec(res.headers.get('content-disposition') || '');
|
|
10484
|
+
return JSON.stringify({
|
|
10485
|
+
fileName: cd ? decodeURIComponent(cd[1]) : `resume-${id}`,
|
|
10486
|
+
contentType: res.headers.get('content-type') || 'application/octet-stream',
|
|
10487
|
+
sizeBytes: buf.length,
|
|
10488
|
+
base64: buf.toString('base64'),
|
|
10489
|
+
}, null, 2);
|
|
10490
|
+
}
|
|
10491
|
+
|
|
10492
|
+
case 'jobboard_lifecycle_status': {
|
|
10493
|
+
const r = await messagingGet('/Recruiting/Lifecycle');
|
|
10494
|
+
return JSON.stringify(r, null, 2);
|
|
10495
|
+
}
|
|
10496
|
+
case 'jobboard_close_listing': {
|
|
10497
|
+
const id = Number(args.listingId);
|
|
10498
|
+
if (!Number.isInteger(id) || id <= 0) throw new Error('listingId must be a positive integer.');
|
|
10499
|
+
const r = await messagingPost(`/Recruiting/Jobs/${id}/close`, {});
|
|
10500
|
+
return JSON.stringify(r, null, 2);
|
|
10501
|
+
}
|
|
10502
|
+
case 'jobboard_renew_listing': {
|
|
10503
|
+
const id = Number(args.listingId);
|
|
10504
|
+
if (!Number.isInteger(id) || id <= 0) throw new Error('listingId must be a positive integer.');
|
|
10505
|
+
const body = {};
|
|
10506
|
+
if (args.days !== undefined) {
|
|
10507
|
+
const d = Number(args.days);
|
|
10508
|
+
if (!Number.isInteger(d) || d < 1 || d > 365) throw new Error('days must be an integer from 1 to 365.');
|
|
10509
|
+
body.days = d;
|
|
10510
|
+
}
|
|
10511
|
+
const r = await messagingPost(`/Recruiting/Jobs/${id}/renew`, body);
|
|
10512
|
+
return JSON.stringify(r, null, 2);
|
|
10513
|
+
}
|
|
10514
|
+
|
|
10204
10515
|
case 'jobboard_move_application_stage': {
|
|
10205
10516
|
const app = await messagingPut(`/Recruiting/Applications/${args.applicationId}/Stage`, { stage: args.stage });
|
|
10206
10517
|
return JSON.stringify(app, null, 2);
|