fix: restore notify init + Plivo numbers (#846) (thanks @vrknetha)
This commit is contained in:
@@ -20,6 +20,7 @@ import type { VoiceCallProvider } from "./providers/base.js";
|
||||
|
||||
class FakeProvider implements VoiceCallProvider {
|
||||
readonly name = "plivo" as const;
|
||||
readonly playTtsCalls: PlayTtsInput[] = [];
|
||||
|
||||
verifyWebhook(_ctx: WebhookContext): WebhookVerificationResult {
|
||||
return { ok: true };
|
||||
@@ -31,7 +32,9 @@ class FakeProvider implements VoiceCallProvider {
|
||||
return { providerCallId: "request-uuid", status: "initiated" };
|
||||
}
|
||||
async hangupCall(_input: HangupCallInput): Promise<void> {}
|
||||
async playTts(_input: PlayTtsInput): Promise<void> {}
|
||||
async playTts(input: PlayTtsInput): Promise<void> {
|
||||
this.playTtsCalls.push(input);
|
||||
}
|
||||
async startListening(_input: StartListeningInput): Promise<void> {}
|
||||
async stopListening(_input: StopListeningInput): Promise<void> {}
|
||||
}
|
||||
@@ -69,5 +72,37 @@ describe("CallManager", () => {
|
||||
expect(manager.getCallByProviderCallId("call-uuid")?.callId).toBe(callId);
|
||||
expect(manager.getCallByProviderCallId("request-uuid")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("speaks initial message on answered for notify mode (non-Twilio)", async () => {
|
||||
const config = VoiceCallConfigSchema.parse({
|
||||
enabled: true,
|
||||
provider: "plivo",
|
||||
fromNumber: "+15550000000",
|
||||
});
|
||||
|
||||
const storePath = path.join(os.tmpdir(), `clawdbot-voice-call-test-${Date.now()}`);
|
||||
const provider = new FakeProvider();
|
||||
const manager = new CallManager(config, storePath);
|
||||
manager.initialize(provider, "https://example.com/voice/webhook");
|
||||
|
||||
const { callId, success } = await manager.initiateCall(
|
||||
"+15550000002",
|
||||
undefined,
|
||||
{ message: "Hello there", mode: "notify" },
|
||||
);
|
||||
expect(success).toBe(true);
|
||||
|
||||
manager.processEvent({
|
||||
id: "evt-2",
|
||||
type: "call.answered",
|
||||
callId,
|
||||
providerCallId: "call-uuid",
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
expect(provider.playTtsCalls).toHaveLength(1);
|
||||
expect(provider.playTtsCalls[0]?.text).toBe("Hello there");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -672,41 +672,13 @@ export class CallManager {
|
||||
|
||||
if (!initialMessage) return;
|
||||
|
||||
// For outbound notify mode, we already use inline TwiML (provider-specific) to
|
||||
// deliver the message and hang up; do not double-speak.
|
||||
const mode = call.metadata?.mode as CallMode | undefined;
|
||||
if (call.direction === "outbound" && mode === "notify") return;
|
||||
|
||||
if (!this.provider || !call.providerCallId) return;
|
||||
|
||||
// Twilio has provider-specific state for speaking (<Say> fallback) and can
|
||||
// fail for inbound calls; keep existing Twilio behavior unchanged.
|
||||
if (this.provider.name === "twilio") return;
|
||||
|
||||
// Clear the initial message so it only plays once.
|
||||
if (call.metadata) {
|
||||
delete call.metadata.initialMessage;
|
||||
}
|
||||
this.persistCallRecord(call);
|
||||
|
||||
void this.provider
|
||||
.playTts({
|
||||
callId: call.callId,
|
||||
providerCallId: call.providerCallId,
|
||||
text: initialMessage,
|
||||
voice: this.config.tts.voice,
|
||||
})
|
||||
.then(() => {
|
||||
this.addTranscriptEntry(call, "bot", initialMessage);
|
||||
this.persistCallRecord(call);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.warn(
|
||||
`[voice-call] Failed to speak initial message on answered: ${
|
||||
err instanceof Error ? err.message : String(err)
|
||||
}`,
|
||||
);
|
||||
});
|
||||
void this.speakInitialMessage(call.providerCallId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -404,7 +404,7 @@ export class PlivoProvider implements VoiceCallProvider {
|
||||
private static normalizeNumber(numberOrSip: string): string {
|
||||
const trimmed = numberOrSip.trim();
|
||||
if (trimmed.toLowerCase().startsWith("sip:")) return trimmed;
|
||||
return trimmed.startsWith("+") ? trimmed.slice(1) : trimmed;
|
||||
return trimmed.replace(/[^\d+]/g, "");
|
||||
}
|
||||
|
||||
private static xmlEmpty(): string {
|
||||
|
||||
Reference in New Issue
Block a user