

























@@ -4436,113 +4436,122 @@ describe("google-meet plugin", () => {
44364436});
4437443744384438it("defaults Chrome command-pair realtime to agent-driven talk-back", async () => {
4439-let callbacks: Parameters<RealtimeVoiceProviderPlugin["createBridge"]>[0] | undefined;
4440-const sendUserMessage = vi.fn();
4441-const bridge = {
4442-connect: vi.fn(async () => {}),
4443-sendAudio: vi.fn(),
4444- sendUserMessage,
4445-setMediaTimestamp: vi.fn(),
4446-submitToolResult: vi.fn(),
4447-acknowledgeMark: vi.fn(),
4448-close: vi.fn(),
4449-triggerGreeting: vi.fn(),
4450-isConnected: vi.fn(() => true),
4451-};
4452-const provider: RealtimeVoiceProviderPlugin = {
4453-id: "openai",
4454-label: "OpenAI",
4455-defaultModel: "gpt-realtime-2",
4456-autoSelectOrder: 1,
4457-resolveConfig: ({ rawConfig }) => rawConfig,
4458-isConfigured: () => true,
4459-createBridge: (req) => {
4460-callbacks = req;
4461-return bridge;
4462-},
4463-};
4464-const inputStdout = new PassThrough();
4465-const makeProcess = (stdio: {
4466-stdin?: { write(chunk: unknown): unknown } | null;
4467-stdout?: { on(event: "data", listener: (chunk: unknown) => void): unknown } | null;
4468-}): TestBridgeProcess => {
4469-const proc = new EventEmitter() as unknown as TestBridgeProcess;
4470-proc.stdin = stdio.stdin;
4471-proc.stdout = stdio.stdout;
4472-proc.stderr = new PassThrough();
4473-proc.killed = false;
4474-proc.kill = vi.fn(() => {
4475-proc.killed = true;
4476-return true;
4439+vi.useFakeTimers();
4440+try {
4441+let callbacks: Parameters<RealtimeVoiceProviderPlugin["createBridge"]>[0] | undefined;
4442+const sendUserMessage = vi.fn();
4443+const bridge = {
4444+connect: vi.fn(async () => {}),
4445+sendAudio: vi.fn(),
4446+ sendUserMessage,
4447+setMediaTimestamp: vi.fn(),
4448+submitToolResult: vi.fn(),
4449+acknowledgeMark: vi.fn(),
4450+close: vi.fn(),
4451+triggerGreeting: vi.fn(),
4452+isConnected: vi.fn(() => true),
4453+};
4454+const provider: RealtimeVoiceProviderPlugin = {
4455+id: "openai",
4456+label: "OpenAI",
4457+defaultModel: "gpt-realtime-2",
4458+autoSelectOrder: 1,
4459+resolveConfig: ({ rawConfig }) => rawConfig,
4460+isConfigured: () => true,
4461+createBridge: (req) => {
4462+callbacks = req;
4463+return bridge;
4464+},
4465+};
4466+const inputStdout = new PassThrough();
4467+const makeProcess = (stdio: {
4468+stdin?: { write(chunk: unknown): unknown } | null;
4469+stdout?: { on(event: "data", listener: (chunk: unknown) => void): unknown } | null;
4470+}): TestBridgeProcess => {
4471+const proc = new EventEmitter() as unknown as TestBridgeProcess;
4472+proc.stdin = stdio.stdin;
4473+proc.stdout = stdio.stdout;
4474+proc.stderr = new PassThrough();
4475+proc.killed = false;
4476+proc.kill = vi.fn(() => {
4477+proc.killed = true;
4478+return true;
4479+});
4480+return proc;
4481+};
4482+const outputProcess = makeProcess({
4483+stdin: new Writable({
4484+write(_chunk, _encoding, done) {
4485+done();
4486+},
4487+}),
4488+stdout: null,
44774489});
4478-return proc;
4479-};
4480-const outputProcess = makeProcess({
4481-stdin: new Writable({
4482-write(_chunk, _encoding, done) {
4483-done();
4490+const inputProcess = makeProcess({ stdout: inputStdout, stdin: null });
4491+const spawnMock = vi
4492+.fn()
4493+.mockReturnValueOnce(outputProcess)
4494+.mockReturnValueOnce(inputProcess);
4495+const sessionStore: Record<string, unknown> = {};
4496+const runtime = {
4497+agent: {
4498+resolveAgentDir: vi.fn(() => "/tmp/agent"),
4499+resolveAgentWorkspaceDir: vi.fn(() => "/tmp/workspace"),
4500+ensureAgentWorkspace: vi.fn(async () => {}),
4501+session: createMockSessionRuntime(sessionStore),
4502+runEmbeddedPiAgent: vi.fn(async (_request: unknown) => ({
4503+payloads: [{ text: "The launch is still on track." }],
4504+meta: {},
4505+})),
4506+resolveAgentTimeoutMs: vi.fn(() => 1000),
44844507},
4485-}),
4486-stdout: null,
4487-});
4488-const inputProcess = makeProcess({ stdout: inputStdout, stdin: null });
4489-const spawnMock = vi.fn().mockReturnValueOnce(outputProcess).mockReturnValueOnce(inputProcess);
4490-const sessionStore: Record<string, unknown> = {};
4491-const runtime = {
4492-agent: {
4493-resolveAgentDir: vi.fn(() => "/tmp/agent"),
4494-resolveAgentWorkspaceDir: vi.fn(() => "/tmp/workspace"),
4495-ensureAgentWorkspace: vi.fn(async () => {}),
4496-session: createMockSessionRuntime(sessionStore),
4497-runEmbeddedPiAgent: vi.fn(async (_request: unknown) => ({
4498-payloads: [{ text: "The launch is still on track." }],
4499-meta: {},
4500-})),
4501-resolveAgentTimeoutMs: vi.fn(() => 1000),
4502-},
4503-};
4504-4505-const handle = await startCommandRealtimeAudioBridge({
4506-config: resolveGoogleMeetConfig({ realtime: { provider: "openai", agentId: "jay" } }),
4507-fullConfig: {} as never,
4508-runtime: runtime as never,
4509-meetingSessionId: "meet-1",
4510-inputCommand: ["capture-meet"],
4511-outputCommand: ["play-meet"],
4512-logger: noopLogger,
4513-providers: [provider],
4514-spawn: spawnMock,
4515-});
4508+};
451645094517-if (!callbacks) {
4518-throw new Error("Expected realtime bridge callbacks");
4519-}
4520-expect(callbacks.autoRespondToAudio).toBe(false);
4521-expect(callbacks.tools).toStrictEqual([]);
4522-callbacks?.onTranscript?.("user", "Are we still on track?", true);
4523-callbacks?.onTranscript?.("user", "Please include launch blockers.", true);
4510+const handle = await startCommandRealtimeAudioBridge({
4511+config: resolveGoogleMeetConfig({ realtime: { provider: "openai", agentId: "jay" } }),
4512+fullConfig: {} as never,
4513+runtime: runtime as never,
4514+meetingSessionId: "meet-1",
4515+inputCommand: ["capture-meet"],
4516+outputCommand: ["play-meet"],
4517+logger: noopLogger,
4518+providers: [provider],
4519+spawn: spawnMock,
4520+});
452445214525-await vi.waitFor(() => {
4526-expect(runtime.agent.runEmbeddedPiAgent).toHaveBeenCalledTimes(1);
4527-});
4528-const consultArgs = requireRecord(
4529-(runtime.agent.runEmbeddedPiAgent.mock.calls as unknown[][])[0]?.[0],
4530-"default talk-back agent request",
4531-);
4532-expect(consultArgs.agentId).toBe("jay");
4533-expect(consultArgs.spawnedBy).toBe("agent:jay:main");
4534-expect(consultArgs.sessionKey).toBe("agent:jay:subagent:google-meet:meet-1");
4535-expect(consultArgs.sandboxSessionKey).toBe("agent:jay:subagent:google-meet:meet-1");
4536-expect(JSON.stringify(consultArgs)).toContain(
4537-"Are we still on track?\\nPlease include launch blockers.",
4538-);
4539-expect(sendUserMessage).toHaveBeenCalledTimes(1);
4540-const sentUserMessage = mockCallArg(sendUserMessage, 0) as string;
4541-expect(typeof sentUserMessage).toBe("string");
4542-expect(sentUserMessage).toContain(JSON.stringify("The launch is still on track."));
4543-expect(sessionStore).toHaveProperty("agent:jay:subagent:google-meet:meet-1");
4522+if (!callbacks) {
4523+throw new Error("Expected realtime bridge callbacks");
4524+}
4525+expect(callbacks.autoRespondToAudio).toBe(false);
4526+expect(callbacks.tools).toStrictEqual([]);
4527+callbacks?.onTranscript?.("user", "Are we still on track?", true);
4528+callbacks?.onTranscript?.("user", "Please include launch blockers.", true);
4529+4530+await vi.advanceTimersByTimeAsync(GOOGLE_MEET_AGENT_TRANSCRIPT_DEBOUNCE_MS);
4531+await vi.waitFor(() => {
4532+expect(runtime.agent.runEmbeddedPiAgent).toHaveBeenCalledTimes(1);
4533+});
4534+const consultArgs = requireRecord(
4535+(runtime.agent.runEmbeddedPiAgent.mock.calls as unknown[][])[0]?.[0],
4536+"default talk-back agent request",
4537+);
4538+expect(consultArgs.agentId).toBe("jay");
4539+expect(consultArgs.spawnedBy).toBe("agent:jay:main");
4540+expect(consultArgs.sessionKey).toBe("agent:jay:subagent:google-meet:meet-1");
4541+expect(consultArgs.sandboxSessionKey).toBe("agent:jay:subagent:google-meet:meet-1");
4542+expect(JSON.stringify(consultArgs)).toContain(
4543+"Are we still on track?\\nPlease include launch blockers.",
4544+);
4545+expect(sendUserMessage).toHaveBeenCalledTimes(1);
4546+const sentUserMessage = mockCallArg(sendUserMessage, 0) as string;
4547+expect(typeof sentUserMessage).toBe("string");
4548+expect(sentUserMessage).toContain(JSON.stringify("The launch is still on track."));
4549+expect(sessionStore).toHaveProperty("agent:jay:subagent:google-meet:meet-1");
454445504545-await handle.stop();
4551+await handle.stop();
4552+} finally {
4553+vi.useRealTimers();
4554+}
45464555});
4547455645484557it("tracks queued playback time when suppressing realtime input echo", () => {
@@ -4908,6 +4917,7 @@ describe("google-meet plugin", () => {
49084917});
4909491849104919it("keeps paired-node realtime audio alive after transient input pull failures", async () => {
4920+vi.useFakeTimers();
49114921const sendAudio = vi.fn();
49124922const bridge = {
49134923connect: vi.fn(async () => {}),
@@ -4953,33 +4963,40 @@ describe("google-meet plugin", () => {
49534963}),
49544964},
49554965};
4966+let handle: Awaited<ReturnType<typeof startNodeRealtimeAudioBridge>> | undefined;
495649674957-const handle = await startNodeRealtimeAudioBridge({
4958-config: resolveGoogleMeetConfig({
4959-realtime: { provider: "openai", model: "gpt-realtime" },
4960-}),
4961-fullConfig: {} as never,
4962-runtime: runtime as never,
4963-meetingSessionId: "meet-1",
4964-nodeId: "node-1",
4965-bridgeId: "bridge-1",
4966-logger: noopLogger,
4967-providers: [provider],
4968-});
4968+try {
4969+handle = await startNodeRealtimeAudioBridge({
4970+config: resolveGoogleMeetConfig({
4971+realtime: { provider: "openai", model: "gpt-realtime" },
4972+}),
4973+fullConfig: {} as never,
4974+runtime: runtime as never,
4975+meetingSessionId: "meet-1",
4976+nodeId: "node-1",
4977+bridgeId: "bridge-1",
4978+logger: noopLogger,
4979+providers: [provider],
4980+});
496949814970-await vi.waitFor(() => {
4971-expect(sendAudio).toHaveBeenCalledWith(Buffer.from([5, 4, 3]));
4972-});
4973-expect(bridge.close).not.toHaveBeenCalled();
4974-const health = handle.getHealth();
4975-expect(health.audioInputActive).toBe(true);
4976-expect(health.lastInputBytes).toBe(3);
4977-expect(health.consecutiveInputErrors).toBe(0);
4982+await vi.advanceTimersByTimeAsync(250);
4983+await vi.waitFor(() => {
4984+expect(sendAudio).toHaveBeenCalledWith(Buffer.from([5, 4, 3]));
4985+});
4986+expect(bridge.close).not.toHaveBeenCalled();
4987+const health = handle.getHealth();
4988+expect(health.audioInputActive).toBe(true);
4989+expect(health.lastInputBytes).toBe(3);
4990+expect(health.consecutiveInputErrors).toBe(0);
497849914979-await vi.waitFor(() => {
4980-expect(idlePullStarted).toBe(true);
4981-});
4982-await handle.stop();
4992+await vi.waitFor(() => {
4993+expect(idlePullStarted).toBe(true);
4994+});
4995+await handle.stop();
4996+} finally {
4997+await handle?.stop();
4998+vi.useRealTimers();
4999+}
49835000});
4984500149855002it("stops paired-node realtime audio after repeated input pull failures", async () => {
@@ -5027,6 +5044,7 @@ describe("google-meet plugin", () => {
50275044providers: [provider],
50285045});
502950465047+await vi.advanceTimersByTimeAsync(1_000);
50305048await vi.waitFor(
50315049() => {
50325050expect(bridge.close).toHaveBeenCalled();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。