// src/services/voiceDiag/checks/callIntercept.js // // Detects whether Call Intercept is active. Intercept is a common // silent cause of "calls just don't come through" — when it's on, // incoming calls get an announcement instead of ringing the desk, // and callers hear a generic "the person you're trying to reach is // not available" prompt with no way to leave a message unless the // intercept was configured with a rerouting target. // // Endpoint: GET/PUT /v1/people/{personId}/features/intercept // NOTE the URL segment is `intercept`, not `callIntercept` // (the check id keeps the descriptive name for the UI). // Scope: spark-admin:people_read (GET) + spark-admin:people_write (PUT). // // Response shape (relevant fields): // { // enabled: true, // incoming: { // type: 'INTERCEPT_ALL' | 'ALLOW_ALL', // voicemailEnabled: true, // announcements: { // greeting: 'CUSTOM' | 'DEFAULT', // newNumber: { enabled: false, destination: '' }, // zeroTransfer: { enabled: false, destination: '' } // } // }, // outgoing: { type: 'INTERCEPT_ALL' | 'ALLOW_ALL', transferEnabled: false, destination: '' } // } // // Remediation: PUT `{enabled: false}` disables intercept in both // directions in one call. When the operator wants finer control // (e.g. only outgoing intercept off) they should do it in Control // Hub — the bot's role here is to unstick "no calls at all" stores. import { logger } from '../../../utils/logger.js'; import { describeRequester } from '../../../utils/requester.js'; const ENDPOINT = (personId) => `people/${personId}/features/intercept`; // Store-line standard: intercept off. Intercept blocks calls with an // announcement — never wanted on a live store line. export const CALL_INTERCEPT_STANDARDS = Object.freeze({ enabled: false, }); export const callInterceptCheck = { id: 'callIntercept', label: 'Call Intercept', requires: ['personId'], scope: 'spark-admin:people_read', standards: CALL_INTERCEPT_STANDARDS, async run(ctx) { const data = await ctx.webex.request('GET', ENDPOINT(ctx.personId)); const enabled = !!data?.enabled; const incomingType = data?.incoming?.type || null; const outgoingType = data?.outgoing?.type || null; if (enabled === CALL_INTERCEPT_STANDARDS.enabled) { return { status: 'ok', message: 'Call intercept is off.', details: { enabled, incomingType, outgoingType }, remediation: null, }; } return { status: 'error', message: 'Call intercept is ACTIVE — inbound and/or outbound calls are being blocked with an announcement.', details: { enabled, incomingType, outgoingType }, remediation: { action: 'disable_call_intercept', title: 'Disable Call Intercept', summary: `Turn call intercept off for ${ctx.personLabel} so calls resume.`, payload: { personId: ctx.personId, personLabel: ctx.personLabel, storeNum: ctx.storeNum, before: { enabled, incomingType, outgoingType }, }, }, }; }, remediations: { async disable_call_intercept(bot, data, requester) { const { personId, personLabel, storeNum, before } = data; logger( 'voicediag:audit', `CONFIRMED disable_call_intercept for ${personLabel} (person=${personId}, store=${storeNum}) ` + `by ${describeRequester(requester)} — was enabled=${before?.enabled ?? 'unknown'}, ` + `incoming=${before?.incomingType ?? 'unknown'}, outgoing=${before?.outgoingType ?? 'unknown'}`, ); try { const { default: webex } = await import('../../../integrations/webex/WebexClient.js'); await webex.request('PUT', ENDPOINT(personId), { ...CALL_INTERCEPT_STANDARDS }); } catch (err) { logger( 'voicediag:audit', `FAILED disable_call_intercept for ${personLabel}: ${err.message}`, 'error', ); await bot.say( 'markdown', `❌ Failed to disable call intercept for **${personLabel}**: ${err.message}`, ); return; } await bot.say( 'markdown', `✅ Call intercept disabled for **${personLabel}** (store ${storeNum}). ` + `Re-run \`/voicediag ${storeNum}\` to verify.`, ); logger( 'voicediag:audit', `COMPLETED disable_call_intercept for ${personLabel} (store ${storeNum})`, ); }, }, };