fix(voice-call): harden inbound policy

This commit is contained in:
Peter Steinberger
2026-02-03 09:33:25 -08:00
parent fc40ba8e7e
commit f8dfd034f5
13 changed files with 328 additions and 33 deletions

View File

@@ -5,6 +5,7 @@ import os from "node:os";
import path from "node:path";
import type { CallMode, VoiceCallConfig } from "./config.js";
import type { VoiceCallProvider } from "./providers/base.js";
import { isAllowlistedCaller, normalizePhoneNumber } from "./allowlist.js";
import {
type CallId,
type CallRecord,
@@ -474,11 +475,12 @@ export class CallManager {
case "allowlist":
case "pairing": {
const normalized = from?.replace(/\D/g, "") || "";
const allowed = (allowFrom || []).some((num) => {
const normalizedAllow = num.replace(/\D/g, "");
return normalized.endsWith(normalizedAllow) || normalizedAllow.endsWith(normalized);
});
const normalized = normalizePhoneNumber(from);
if (!normalized) {
console.log("[voice-call] Inbound call rejected: missing caller ID");
return false;
}
const allowed = isAllowlistedCaller(normalized, allowFrom);
const status = allowed ? "accepted" : "rejected";
console.log(
`[voice-call] Inbound call ${status}: ${from} ${allowed ? "is in" : "not in"} allowlist`,
@@ -551,7 +553,7 @@ export class CallManager {
if (!call && event.direction === "inbound" && event.providerCallId) {
// Check if we should accept this inbound call
if (!this.shouldAcceptInbound(event.from)) {
// TODO: Could hang up the call here
void this.rejectInboundCall(event);
return;
}
@@ -653,6 +655,25 @@ export class CallManager {
this.persistCallRecord(call);
}
private async rejectInboundCall(event: NormalizedEvent): Promise<void> {
if (!this.provider || !event.providerCallId) {
return;
}
const callId = event.callId || event.providerCallId;
try {
await this.provider.hangupCall({
callId,
providerCallId: event.providerCallId,
reason: "hangup-bot",
});
} catch (err) {
console.warn(
`[voice-call] Failed to reject inbound call ${event.providerCallId}:`,
err instanceof Error ? err.message : err,
);
}
}
private maybeSpeakInitialMessageOnAnswered(call: CallRecord): void {
const initialMessage =
typeof call.metadata?.initialMessage === "string" ? call.metadata.initialMessage.trim() : "";