























@@ -33,6 +33,19 @@ type Registered = {
3333tools: unknown[];
3434service?: Parameters<OpenClawPluginApi["registerService"]>[0];
3535};
36+type MockCallSource = {
37+mock: {
38+calls: ArrayLike<ReadonlyArray<unknown>>;
39+};
40+};
41+type RespondCall = [
42+ok: boolean,
43+payload?: Record<string, unknown>,
44+error?: {
45+code?: unknown;
46+message?: unknown;
47+},
48+];
3649type RegisterVoiceCall = (api: Record<string, unknown>) => void;
3750type RegisterCliContext = {
3851program: Command;
@@ -141,6 +154,25 @@ function envRef(id: string) {
141154return { source: "env" as const, provider: "default", id };
142155}
143156157+function mockCall(source: MockCallSource, callIndex = 0): ReadonlyArray<unknown> {
158+const call = source.mock.calls[callIndex];
159+if (!call) {
160+throw new Error(`expected mock call ${callIndex}`);
161+}
162+return call;
163+}
164+165+function firstRespondCall(source: MockCallSource): RespondCall {
166+return mockCall(source) as unknown as RespondCall;
167+}
168+169+function firstRuntimeConfig(): VoiceCallRuntime["config"] | undefined {
170+const options = mockCall(vi.mocked(createVoiceCallRuntime))[0] as
171+| { config?: VoiceCallRuntime["config"] }
172+| undefined;
173+return options?.config;
174+}
175+144176function expectWarningIncludes(text: string): void {
145177expect(noopLogger.warn.mock.calls.some(([message]) => String(message).includes(text))).toBe(true);
146178}
@@ -326,9 +358,7 @@ describe("voice-call plugin", () => {
326358await service?.start(createServiceContext());
327359328360expect(createVoiceCallRuntime).toHaveBeenCalledTimes(1);
329-expect(
330-vi.mocked(createVoiceCallRuntime).mock.calls.at(0)?.[0]?.config.twilio?.authToken,
331-).toEqual(authToken);
361+expect(firstRuntimeConfig()?.twilio?.authToken).toEqual(authToken);
332362});
333363334364it("still reports missing provider setup when a command needs the runtime", async () => {
@@ -347,12 +377,10 @@ describe("voice-call plugin", () => {
347377await handler?.({ params: { message: "Hi", to: "+15550001234" }, respond });
348378349379expect(createVoiceCallRuntime).not.toHaveBeenCalled();
350-const [ok, payload, error] = respond.mock.calls.at(0) ?? [];
380+const [ok, payload, error] = firstRespondCall(respond);
351381expect(ok).toBe(false);
352382expect(payload).toBeUndefined();
353-expect(String((error as { message?: unknown } | undefined)?.message)).toContain(
354-"TWILIO_ACCOUNT_SID",
355-);
383+expect(String(error?.message)).toContain("TWILIO_ACCOUNT_SID");
356384});
357385358386it("initiates a call via voicecall.initiate", async () => {
@@ -366,9 +394,9 @@ describe("voice-call plugin", () => {
366394const respond = vi.fn();
367395await handler?.({ params: { message: "Hi" }, respond });
368396expect(runtimeStub.manager.initiateCall).toHaveBeenCalled();
369-const [ok, payload] = respond.mock.calls.at(0) ?? [];
397+const [ok, payload] = firstRespondCall(respond);
370398expect(ok).toBe(true);
371-expect(payload.callId).toBe("call-1");
399+expect(payload?.callId).toBe("call-1");
372400});
373401374402it("registers voice call gateway methods with least-privilege scopes", () => {
@@ -412,7 +440,7 @@ describe("voice-call plugin", () => {
412440message: "Hi",
413441mode: "conversation",
414442});
415-expect(respond.mock.calls.at(0)?.[0]).toBe(true);
443+expect(firstRespondCall(respond)[0]).toBe(true);
416444});
417445418446it("preserves explicit session keys on voicecall.start", async () => {
@@ -443,7 +471,7 @@ describe("voice-call plugin", () => {
443471requesterSessionKey: "agent:main:discord:channel:general",
444472},
445473);
446-expect(respond.mock.calls.at(0)?.[0]).toBe(true);
474+expect(firstRespondCall(respond)[0]).toBe(true);
447475});
448476449477it("returns call status", async () => {
@@ -456,9 +484,9 @@ describe("voice-call plugin", () => {
456484| undefined;
457485const respond = vi.fn();
458486await handler?.({ params: { callId: "call-1" }, respond });
459-const [ok, payload] = respond.mock.calls.at(0) ?? [];
487+const [ok, payload] = firstRespondCall(respond);
460488expect(ok).toBe(true);
461-expect(payload.found).toBe(true);
489+expect(payload?.found).toBe(true);
462490});
463491464492it("sends DTMF via voicecall.dtmf", async () => {
@@ -474,7 +502,7 @@ describe("voice-call plugin", () => {
474502await handler?.({ params: { callId: "call-1", digits: "ww123#" }, respond });
475503476504expect(runtimeStub.manager.sendDtmf).toHaveBeenCalledWith("call-1", "ww123#");
477-expect(respond.mock.calls.at(0)).toEqual([true, { success: true }]);
505+expect(firstRespondCall(respond)).toEqual([true, { success: true }]);
478506});
479507480508it("normalizes provider call ids before speaking", async () => {
@@ -497,7 +525,7 @@ describe("voice-call plugin", () => {
497525await handler?.({ params: { callId: "CA123", message: "hello" }, respond });
498526499527expect(runtimeStub.manager.speak).toHaveBeenCalledWith("call-1", "hello");
500-expect(respond.mock.calls.at(0)).toEqual([true, { success: true }]);
528+expect(firstRespondCall(respond)).toEqual([true, { success: true }]);
501529});
502530503531it("does not fall back to one-shot TwiML speak when realtime-only speech is requested", async () => {
@@ -518,7 +546,7 @@ describe("voice-call plugin", () => {
518546519547expect(runtimeStub.webhookServer.speakRealtime).toHaveBeenCalledWith("call-1", "hello");
520548expect(runtimeStub.manager.speak).not.toHaveBeenCalled();
521-expect(respond.mock.calls.at(0)).toEqual([
549+expect(firstRespondCall(respond)).toEqual([
522550true,
523551{ success: false, error: "No active realtime bridge for call" },
524552]);
@@ -547,11 +575,11 @@ describe("voice-call plugin", () => {
547575548576await handler?.({ params: { callId: "CA123", message: "hello" }, respond });
549577550-const [ok, , error] = respond.mock.calls.at(0) ?? [];
578+const [ok, , error] = firstRespondCall(respond);
551579expect(ok).toBe(false);
552-expect(error.message).toContain("call is not active");
553-expect(error.message).toContain("last state=completed");
554-expect(error.message).toContain("endReason=completed");
580+expect(error?.message).toContain("call is not active");
581+expect(error?.message).toContain("last state=completed");
582+expect(error?.message).toContain("endReason=completed");
555583expect(runtimeStub.manager.speak).not.toHaveBeenCalled();
556584});
557585@@ -579,7 +607,7 @@ describe("voice-call plugin", () => {
579607await handler?.({ params: { callId: "call-1" }, respond });
580608581609expect(vi.mocked(createVoiceCallRuntime)).toHaveBeenCalledTimes(1);
582-const runtimeConfig = vi.mocked(createVoiceCallRuntime).mock.calls.at(0)?.[0]?.config;
610+const runtimeConfig = firstRuntimeConfig();
583611expect(runtimeConfig?.enabled).toBe(true);
584612expect(runtimeConfig?.provider).toBe("mock");
585613expect(runtimeConfig?.fromNumber).toBe("+15550001234");
@@ -707,11 +735,11 @@ describe("voice-call plugin", () => {
707735708736await handler?.({ params: {}, respond });
709737710-const [ok, payload, error] = respond.mock.calls.at(0) ?? [];
738+const [ok, payload, error] = firstRespondCall(respond);
711739expect(ok).toBe(false);
712740expect(payload).toBeUndefined();
713-expect((error as { code?: unknown } | undefined)?.code).toBe("INVALID_REQUEST");
714-expect((error as { message?: unknown } | undefined)?.message).toBe("to required");
741+expect(error?.code).toBe("INVALID_REQUEST");
742+expect(error?.message).toBe("to required");
715743});
716744717745it("starts and polls delegated gateway continue operations", async () => {
@@ -791,7 +819,7 @@ describe("voice-call plugin", () => {
791819params: { callId: "call-1", message: "Hello" },
792820respond: startRespond,
793821});
794-const startPayload = startRespond.mock.calls.at(0)?.[1] as
822+const startPayload = firstRespondCall(startRespond)[1] as
795823| { operationId?: string; pollTimeoutMs?: number; status?: string }
796824| undefined;
797825expect(startPayload?.operationId).toMatch(
@@ -806,10 +834,9 @@ describe("voice-call plugin", () => {
806834params: { operationId: startPayload?.operationId },
807835respond: pendingRespond,
808836});
809-expect(pendingRespond.mock.calls.at(0)?.[0]).toBe(true);
810-expect((pendingRespond.mock.calls.at(0)?.[1] as { status?: unknown } | undefined)?.status).toBe(
811-"pending",
812-);
837+const pendingCall = firstRespondCall(pendingRespond);
838+expect(pendingCall[0]).toBe(true);
839+expect((pendingCall[1] as { status?: unknown } | undefined)?.status).toBe("pending");
813840814841finishContinue?.({ success: true, transcript: "gateway hello" });
815842await continuePromise;
@@ -820,10 +847,9 @@ describe("voice-call plugin", () => {
820847params: { operationId: startPayload?.operationId },
821848respond: completedRespond,
822849});
823-const completedPayload = completedRespond.mock.calls.at(0)?.[1] as
824-| { status?: unknown; result?: unknown }
825-| undefined;
826-expect(completedRespond.mock.calls.at(0)?.[0]).toBe(true);
850+const completedCall = firstRespondCall(completedRespond);
851+const completedPayload = completedCall[1] as { status?: unknown; result?: unknown } | undefined;
852+expect(completedCall[0]).toBe(true);
827853expect(completedPayload?.status).toBe("completed");
828854expect(completedPayload?.result).toEqual({ success: true, transcript: "gateway hello" });
829855});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。