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>
74 lines
2.8 KiB
JavaScript
74 lines
2.8 KiB
JavaScript
// src/services/voiceDiag/checks/index.js
|
|
//
|
|
// Ordered registry of every /voicediag check. Order matters — the
|
|
// renderer walks this list to produce the output, so put the checks a
|
|
// human operator would notice first (DND, forwarding) at the top,
|
|
// then the deeper feature-config ones, then finally the online-device
|
|
// summary which is often the last thing they want to see.
|
|
//
|
|
// Adding a new check
|
|
// 1. Drop a new file next to this one (e.g. `myNewCheck.js`) that
|
|
// exports the check descriptor as its default export or a named
|
|
// export.
|
|
// 2. Add it to CHECKS below in the position you want it to render.
|
|
// 3. If it exports a `remediations: {actionId: handler}` map, the
|
|
// voiceDiagService remediation registry will pick it up
|
|
// automatically. Action ids must be globally unique across all
|
|
// checks — a collision throws at startup.
|
|
//
|
|
// Every check must export an object matching:
|
|
//
|
|
// {
|
|
// id: string, // stable, snake_case, globally unique
|
|
// label: string, // human-readable heading
|
|
// requires: string[], // subset of ['personId','phoneStatus',
|
|
// // 'telephonyProfile'] — runner skips
|
|
// // the check if any are missing on ctx
|
|
// scope: string, // primary Webex scope needed; surfaced
|
|
// // in the skipped message on 401/403/404
|
|
// run: async (ctx) => CheckResult,
|
|
// remediations?: { [actionId: string]: async (bot, data, requester) => void }
|
|
// }
|
|
|
|
import { dndCheck } from './dnd.js';
|
|
import { callForwardingCheck } from './callForwarding.js';
|
|
import { callWaitingCheck } from './callWaiting.js';
|
|
import { voicemailCheck } from './voicemail.js';
|
|
import { callInterceptCheck } from './callIntercept.js';
|
|
import { hotelingCheck } from './hoteling.js';
|
|
import { executiveAssistantCheck } from './executiveAssistant.js';
|
|
import { outgoingPermissionCheck } from './outgoingPermission.js';
|
|
import { phoneOnlineCheck } from './phoneOnline.js';
|
|
|
|
export const CHECKS = [
|
|
dndCheck,
|
|
callForwardingCheck,
|
|
callInterceptCheck,
|
|
callWaitingCheck,
|
|
voicemailCheck,
|
|
hotelingCheck,
|
|
executiveAssistantCheck,
|
|
outgoingPermissionCheck,
|
|
phoneOnlineCheck,
|
|
];
|
|
|
|
const _byId = new Map();
|
|
for (const c of CHECKS) {
|
|
if (!c || typeof c !== 'object') {
|
|
throw new Error('voicediag: encountered non-object check in registry');
|
|
}
|
|
if (!c.id || typeof c.id !== 'string') {
|
|
throw new Error('voicediag: check is missing required "id" string');
|
|
}
|
|
const key = c.id.toLowerCase();
|
|
if (_byId.has(key)) {
|
|
throw new Error(`voicediag: duplicate check id "${c.id}"`);
|
|
}
|
|
_byId.set(key, c);
|
|
}
|
|
|
|
/** Case-insensitive lookup so `--only DND` and `--only dnd` both work. */
|
|
export function getCheckById(id) {
|
|
if (!id) return null;
|
|
return _byId.get(String(id).toLowerCase().trim()) || null;
|
|
}
|