

























@@ -5,20 +5,9 @@ import { Command } from "commander";
55import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
66import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.ts";
77import type { OpenClawPluginApi } from "./api.js";
8+import type { VoiceCallRuntime } from "./runtime-entry.js";
899-let runtimeStub: {
10-config: { toNumber?: string };
11-manager: {
12-initiateCall: ReturnType<typeof vi.fn>;
13-continueCall: ReturnType<typeof vi.fn>;
14-speak: ReturnType<typeof vi.fn>;
15-sendDtmf: ReturnType<typeof vi.fn>;
16-endCall: ReturnType<typeof vi.fn>;
17-getCall: ReturnType<typeof vi.fn>;
18-getCallByProviderCallId: ReturnType<typeof vi.fn>;
19-};
20-stop: ReturnType<typeof vi.fn>;
21-};
10+let runtimeStub: VoiceCallRuntime;
22112312vi.mock("./runtime-entry.js", () => ({
2413createVoiceCallRuntime: vi.fn(async () => runtimeStub),
@@ -37,6 +26,7 @@ const noopLogger = {
3726type Registered = {
3827methods: Map<string, unknown>;
3928tools: unknown[];
29+service?: Parameters<OpenClawPluginApi["registerService"]>[0];
4030};
4131type RegisterVoiceCall = (api: Record<string, unknown>) => void;
4232type RegisterCliContext = {
@@ -57,9 +47,42 @@ function captureStdout() {
5747restore: () => writeSpy.mockRestore(),
5848};
5949}
50+51+function createRuntimeStub(callId = "call-1"): VoiceCallRuntime {
52+return {
53+config: { toNumber: "+15550001234" } as VoiceCallRuntime["config"],
54+provider: {} as VoiceCallRuntime["provider"],
55+manager: {
56+initiateCall: vi.fn(async () => ({ callId, success: true })),
57+continueCall: vi.fn(async () => ({
58+success: true,
59+transcript: "hello",
60+})),
61+speak: vi.fn(async () => ({ success: true })),
62+sendDtmf: vi.fn(async () => ({ success: true })),
63+endCall: vi.fn(async () => ({ success: true })),
64+getCall: vi.fn((id: string) => (id === callId ? { callId } : undefined)),
65+getCallByProviderCallId: vi.fn(() => undefined),
66+} as unknown as VoiceCallRuntime["manager"],
67+webhookServer: {} as VoiceCallRuntime["webhookServer"],
68+webhookUrl: "http://127.0.0.1:3334/voice/webhook",
69+publicUrl: null,
70+stop: vi.fn(async () => {}),
71+};
72+}
73+74+function createServiceContext(): Parameters<NonNullable<Registered["service"]>["start"]>[0] {
75+return {
76+config: {},
77+stateDir: os.tmpdir(),
78+logger: noopLogger,
79+} as Parameters<NonNullable<Registered["service"]>["start"]>[0];
80+}
81+6082function setup(config: Record<string, unknown>): Registered {
6183const methods = new Map<string, unknown>();
6284const tools: unknown[] = [];
85+let service: Registered["service"];
6386const api = createTestPluginApi({
6487id: "voice-call",
6588name: "Voice Call",
@@ -73,11 +96,13 @@ function setup(config: Record<string, unknown>): Registered {
7396registerGatewayMethod: (method: string, handler: unknown) => methods.set(method, handler),
7497registerTool: (tool: unknown) => tools.push(tool),
7598registerCli: () => {},
76-registerService: () => {},
99+registerService: (registeredService) => {
100+service = registeredService;
101+},
77102resolvePath: (p: string) => p,
78103});
79104plugin.register(api);
80-return { methods, tools };
105+return { methods, tools, service };
81106}
8210783108async function registerVoiceCallCli(program: Command) {
@@ -114,26 +139,60 @@ describe("voice-call plugin", () => {
114139noopLogger.warn.mockClear();
115140noopLogger.error.mockClear();
116141noopLogger.debug.mockClear();
117-vi.mocked(createVoiceCallRuntime).mockClear();
118-runtimeStub = {
119-config: { toNumber: "+15550001234" },
120-manager: {
121-initiateCall: vi.fn(async () => ({ callId: "call-1", success: true })),
122-continueCall: vi.fn(async () => ({
123-success: true,
124-transcript: "hello",
125-})),
126-speak: vi.fn(async () => ({ success: true })),
127-sendDtmf: vi.fn(async () => ({ success: true })),
128-endCall: vi.fn(async () => ({ success: true })),
129-getCall: vi.fn((id: string) => (id === "call-1" ? { callId: "call-1" } : undefined)),
130-getCallByProviderCallId: vi.fn(() => undefined),
131-},
132-stop: vi.fn(async () => {}),
133-};
142+runtimeStub = createRuntimeStub();
143+vi.mocked(createVoiceCallRuntime).mockReset();
144+vi.mocked(createVoiceCallRuntime).mockImplementation(async () => runtimeStub);
145+});
146+147+afterEach(() => {
148+vi.restoreAllMocks();
149+delete (globalThis as Record<PropertyKey, unknown>)[Symbol.for("openclaw.voice-call.runtime")];
150+delete (globalThis as Record<PropertyKey, unknown>)[
151+Symbol.for("openclaw.voice-call.runtimePromise")
152+];
153+delete (globalThis as Record<PropertyKey, unknown>)[
154+Symbol.for("openclaw.voice-call.runtimeStopPromise")
155+];
156+});
157+158+it("reuses a started runtime across plugin registration contexts", async () => {
159+const first = setup({ provider: "mock" });
160+const second = setup({ provider: "mock" });
161+162+await first.service?.start(createServiceContext());
163+const handler = second.methods.get("voicecall.initiate") as
164+| ((ctx: {
165+params: Record<string, unknown>;
166+respond: ReturnType<typeof vi.fn>;
167+}) => Promise<void>)
168+| undefined;
169+const respond = vi.fn();
170+await handler?.({ params: { message: "Hi" }, respond });
171+172+expect(createVoiceCallRuntime).toHaveBeenCalledTimes(1);
173+expect(runtimeStub.manager.initiateCall).toHaveBeenCalledTimes(1);
174+expect(respond).toHaveBeenCalledWith(true, { callId: "call-1", initiated: true });
134175});
135176136-afterEach(() => vi.restoreAllMocks());
177+it("creates a fresh shared runtime after service stop", async () => {
178+const first = setup({ provider: "mock" });
179+await first.service?.start(createServiceContext());
180+await first.service?.stop?.(createServiceContext());
181+182+runtimeStub = createRuntimeStub("call-2");
183+const second = setup({ provider: "mock" });
184+const handler = second.methods.get("voicecall.initiate") as
185+| ((ctx: {
186+params: Record<string, unknown>;
187+respond: ReturnType<typeof vi.fn>;
188+}) => Promise<void>)
189+| undefined;
190+const respond = vi.fn();
191+await handler?.({ params: { message: "Hi" }, respond });
192+193+expect(createVoiceCallRuntime).toHaveBeenCalledTimes(2);
194+expect(respond).toHaveBeenCalledWith(true, { callId: "call-2", initiated: true });
195+});
137196138197it("initiates a call via voicecall.initiate", async () => {
139198const { methods } = setup({ provider: "mock" });
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。