collabSupport/services/voiceDiag/checks/executiveAssistant.js
jmcqueen 2eb31a2ddc Add /voicediag rules-engine command with 9 per-user calling checks
Introduces a new diagnostic command that walks a registry of check
modules against a store user's Webex Calling configuration and
surfaces per-issue adaptive-card remediation for the fixable ones.

Checks (services/voiceDiag/checks/): dnd, callForwarding, callWaiting,
callIntercept, voicemail, hoteling, executiveAssistant,
outgoingPermission, phoneOnline. Remediations offered for DND,
forwarding, waiting, and intercept.

Uses the /v1/people/{id}/features/* admin surface (spark-admin:people_read
+ spark-admin:people_write scopes we already hold) — the earlier
telephony/config/people/*/callSettings/* path scheme returns 404 from
the Webex gateway and is not a live surface. Runner distinguishes
routing-404s ("URL moved") from "not applicable" 404s ("no calling
license") via the response body.

Arg parser accepts detail/detailed/--detail/--detailed and normalises
macOS smart-dashes so --detailed doesn't die when auto-correct
turns it into an em-dash.

Wires a VOICEDIAG_ACTIONS dispatcher in index.js mirroring the IGMP
branch, and registers /voicediag in commands/registry.js. 170 tests
pass (52 new: 39 check + 12 renderer + 5 arg-normalization).

Docs updated in .env.example, services/phoneService.js:467, and a new
services/voiceDiag/README.md that includes a "how to add a check"
recipe plus a note on the earlier wrong URL scheme.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 18:51:31 -04:00

55 lines
2 KiB
JavaScript

// src/services/voiceDiag/checks/executiveAssistant.js
//
// Info-only check for the Executive / Executive Assistant feature.
// This isn't something a store line would normally be configured
// for; when the type is anything other than 'UNASSIGNED', calls can
// be filtered/screened through an assistant relationship in ways
// that look like "the store phone isn't behaving right". We surface
// the current type so an operator can decide whether to unassign it
// in Control Hub. No remediation button — this is intentional
// executive configuration, we don't auto-flip it.
//
// Endpoint: GET /v1/people/{personId}/features/executiveAssistant
// Scope: spark-admin:people_read
// Response shape: { type: 'UNASSIGNED' | 'EXECUTIVE' | 'EXECUTIVE_ASSISTANT' }
//
// NOTE: Webex has announced a path migration for this endpoint (see
// developer.webex.com changelog — /v1/telephony/config/people/{id}/executive
// is the new path). When that ships and starts returning 404 on the
// legacy path, swap the ENDPOINT constant. Do NOT eagerly switch
// before Cisco's cutover window closes — until then only the legacy
// path is live.
const ENDPOINT = (personId) =>
`people/${personId}/features/executiveAssistant`;
export const executiveAssistantCheck = {
id: 'executiveAssistant',
label: 'Executive / Assistant',
requires: ['personId'],
scope: 'spark-admin:people_read',
async run(ctx) {
const data = await ctx.webex.request('GET', ENDPOINT(ctx.personId));
const type = data?.type || 'UNASSIGNED';
if (type === 'UNASSIGNED') {
return {
status: 'ok',
message: 'No executive / assistant relationship (normal for a store line).',
details: { type },
remediation: null,
};
}
return {
status: 'warn',
message:
`This line is configured as **${type}** in the executive/assistant feature. ` +
`Verify this is intentional — otherwise calls may be routed through an ` +
`assistant relationship.`,
details: { type },
remediation: null,
};
},
};