


























@@ -129,6 +129,21 @@ function createExternalProviderConfig(params: {
129129return config;
130130}
131131132+function firstCallParam(calls: unknown[][], label: string) {
133+const call = calls[0];
134+if (!call) {
135+throw new Error(`expected ${label} call`);
136+}
137+return call[0];
138+}
139+140+function requireRecord(value: unknown, label: string): Record<string, unknown> {
141+if (value === null || typeof value !== "object" || Array.isArray(value)) {
142+throw new Error(`expected ${label} to be a record`);
143+}
144+return value as Record<string, unknown>;
145+}
146+132147describe("createVoiceCallRuntime lifecycle", () => {
133148beforeEach(() => {
134149vi.clearAllMocks();
@@ -350,12 +365,18 @@ describe("createVoiceCallRuntime lifecycle", () => {
350365agentRuntime: agentRuntime as never,
351366});
352367353-expect(mocks.realtimeHandlerCtorArgs[0]?.[0]).toMatchObject({
354-tools: [
355-expect.objectContaining({ name: "openclaw_agent_consult" }),
356-expect.objectContaining({ name: "custom_tool" }),
357-],
358-});
368+const realtimeHandlerOptions = requireRecord(
369+mocks.realtimeHandlerCtorArgs[0]?.[0],
370+"realtime handler options",
371+);
372+const tools = realtimeHandlerOptions.tools;
373+if (!Array.isArray(tools)) {
374+throw new Error("expected realtime handler tools to be an array");
375+}
376+expect(tools.map((tool) => requireRecord(tool, "realtime tool").name)).toEqual([
377+"openclaw_agent_consult",
378+"custom_tool",
379+]);
359380const registeredToolHandler = mocks.realtimeHandlerRegisterToolHandler.mock.calls[0];
360381expect(registeredToolHandler?.[0]).toBe("openclaw_agent_consult");
361382expect(registeredToolHandler?.[1]).toBeTypeOf("function");
@@ -374,24 +395,28 @@ describe("createVoiceCallRuntime lifecycle", () => {
374395).resolves.toEqual({
375396text: "Use the shipment status.",
376397});
377-expect(runEmbeddedPiAgent).toHaveBeenCalledWith(
378-expect.objectContaining({
379-sessionKey: "voice:15550009999",
380-spawnedBy: "agent:main:discord:channel:general",
381-messageProvider: "voice",
382-lane: "voice",
383-provider: "openai",
384-model: "gpt-5.4",
385-toolsAllow: ["read", "web_search", "web_fetch", "x_search", "memory_search", "memory_get"],
386-extraSystemPrompt: expect.stringContaining("one or two bounded read-only queries"),
387-prompt: expect.stringContaining("Caller: Can you check shipment status?"),
388-}),
389-);
390-expect(runEmbeddedPiAgent).toHaveBeenCalledWith(
391-expect.objectContaining({
392-prompt: expect.stringContaining("Caller: Also check the ETA."),
393-}),
398+expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
399+const consultParams = requireRecord(
400+firstCallParam(runEmbeddedPiAgent.mock.calls as unknown[][], "embedded PI consult"),
401+"embedded PI consult params",
394402);
403+expect(consultParams.sessionKey).toBe("voice:15550009999");
404+expect(consultParams.spawnedBy).toBe("agent:main:discord:channel:general");
405+expect(consultParams.messageProvider).toBe("voice");
406+expect(consultParams.lane).toBe("voice");
407+expect(consultParams.provider).toBe("openai");
408+expect(consultParams.model).toBe("gpt-5.4");
409+expect(consultParams.toolsAllow).toEqual([
410+"read",
411+"web_search",
412+"web_fetch",
413+"x_search",
414+"memory_search",
415+"memory_get",
416+]);
417+expect(consultParams.extraSystemPrompt).toContain("one or two bounded read-only queries");
418+expect(consultParams.prompt).toContain("Caller: Can you check shipment status?");
419+expect(consultParams.prompt).toContain("Caller: Also check the ETA.");
395420});
396421397422it("uses persisted per-call session keys for realtime consults", async () => {
@@ -446,11 +471,12 @@ describe("createVoiceCallRuntime lifecycle", () => {
446471await expect(handler?.({ question: "What should I say?" }, "call-1")).resolves.toEqual({
447472text: "Per-call consult answer.",
448473});
449-expect(runEmbeddedPiAgent).toHaveBeenCalledWith(
450- expect.objectContaining({
451- sessionKey: "voice:call:call-1",
452-}),
474+expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
475+const consultParams = requireRecord(
476+firstCallParam(runEmbeddedPiAgent.mock.calls as unknown[][], "per-call embedded PI consult"),
477+"per-call embedded PI consult params",
453478);
479+expect(consultParams.sessionKey).toBe("voice:call:call-1");
454480});
455481456482it("answers realtime consults from fast memory context before starting the full agent", async () => {
@@ -511,11 +537,12 @@ describe("createVoiceCallRuntime lifecycle", () => {
511537context?: { partialUserTranscript?: string },
512538) => Promise<unknown>)
513539| undefined;
514-await expect(handler?.({ question: "Are the basement lights on?" }, "call-1")).resolves.toEqual(
515-{
516-text: expect.stringContaining("The caller's basement lights are on."),
517-},
540+const fastContextResult = await handler?.(
541+{ question: "Are the basement lights on?" },
542+"call-1",
518543);
544+const fastContextRecord = requireRecord(fastContextResult, "fast context result");
545+expect(fastContextRecord.text).toContain("The caller's basement lights are on.");
519546expect(mocks.resolveRealtimeFastContextConsult).toHaveBeenCalledWith({
520547cfg: {},
521548agentId: "main",
@@ -588,11 +615,15 @@ describe("createVoiceCallRuntime lifecycle", () => {
588615});
589616590617expect(agentRuntime.resolveThinkingDefault).not.toHaveBeenCalled();
591-expect(runEmbeddedPiAgent).toHaveBeenCalledWith(
592-expect.objectContaining({
593-thinkLevel: "low",
594-fastMode: true,
595-}),
618+expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
619+const consultParams = requireRecord(
620+firstCallParam(
621+runEmbeddedPiAgent.mock.calls as unknown[][],
622+"configured embedded PI consult",
623+),
624+"configured embedded PI consult params",
596625);
626+expect(consultParams.thinkLevel).toBe("low");
627+expect(consultParams.fastMode).toBe(true);
597628});
598629});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。