From b75b62fb1c246b68926b54526ec68d580625f4ff Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 17 Jul 2026 17:40:08 +0100 Subject: [PATCH] test(talk): isolate consult session storage (#110078) --- src/talk/agent-consult-runtime.test.ts | 51 ++++++++++++++++++++------ 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/src/talk/agent-consult-runtime.test.ts b/src/talk/agent-consult-runtime.test.ts index ee9e8b41ac70..97e872569836 100644 --- a/src/talk/agent-consult-runtime.test.ts +++ b/src/talk/agent-consult-runtime.test.ts @@ -1,8 +1,12 @@ -import { afterEach, describe, expect, it, vi } from "vitest"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { RunEmbeddedAgentParams } from "../agents/embedded-agent-runner/run/params.js"; import type { SessionEntry } from "../config/sessions/types.js"; import { MODEL_SELECTION_LOCKED_MESSAGE } from "../sessions/model-overrides.js"; import { runExclusiveSessionLifecycleMutation } from "../sessions/session-lifecycle-admission.js"; +import { closeOpenClawAgentDatabaseByPath } from "../state/openclaw-agent-db.js"; import { consultRealtimeVoiceAgent } from "./agent-consult-runtime.js"; import { REALTIME_VOICE_AGENT_CONSULT_TOOL } from "./agent-consult-tool.js"; import { @@ -30,6 +34,15 @@ vi.mock("../auto-reply/reply/session-fork.js", async (importOriginal) => { }; }); +let testTempDir: string | undefined; + +function testTempPath(name: string): string { + if (!testTempDir) { + throw new Error("Expected an isolated consult runtime test directory"); + } + return path.join(testTempDir, name); +} + function createAgentRuntime(payloads: unknown[] = [{ text: "Speak this." }]) { const sessionStore: Record< string, @@ -98,12 +111,12 @@ function createAgentRuntime(payloads: unknown[] = [{ text: "Speak this." }]) { ); return { runtime: { - resolveAgentDir: vi.fn(() => "/tmp/agent"), - resolveAgentWorkspaceDir: vi.fn(() => "/tmp/workspace"), + resolveAgentDir: vi.fn(() => testTempPath("agent")), + resolveAgentWorkspaceDir: vi.fn(() => testTempPath("workspace")), ensureAgentWorkspace: vi.fn(async () => {}), resolveAgentTimeoutMs: vi.fn(() => 30_000), session: { - resolveStorePath: vi.fn(() => "/tmp/sessions.json"), + resolveStorePath: vi.fn(() => testTempPath("sessions.json")), loadSessionStore: vi.fn(() => sessionStore), saveSessionStore: vi.fn(async () => {}), updateSessionStore, @@ -112,7 +125,7 @@ function createAgentRuntime(payloads: unknown[] = [{ text: "Speak this." }]) { upsertSessionEntry, resolveSessionFilePath: vi.fn( (_sessionId: string, entry?: { sessionFile?: string }) => - entry?.sessionFile ?? "/tmp/session.json", + entry?.sessionFile ?? testTempPath("session.json"), ), }, runEmbeddedAgent, @@ -155,7 +168,15 @@ function createDeferred() { } describe("realtime voice agent consult runtime", () => { - afterEach(() => { + beforeEach(async () => { + // macOS aliases its temp directory through /var; canonical paths keep the + // SQLite cache key and cleanup target aligned. + testTempDir = await fs.realpath( + await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-talk-consult-")), + ); + }); + + afterEach(async () => { sessionForkMocks.forkSessionEntryFromParent.mockReset(); const defaultForkSessionEntryFromParent = sessionForkMocks.defaultForkSessionEntryFromParent; if (!defaultForkSessionEntryFromParent) { @@ -164,6 +185,12 @@ describe("realtime voice agent consult runtime", () => { sessionForkMocks.forkSessionEntryFromParent.mockImplementation( defaultForkSessionEntryFromParent, ); + const tempDir = testTempDir; + testTempDir = undefined; + if (tempDir) { + closeOpenClawAgentDatabaseByPath(path.join(tempDir, "openclaw-agent.sqlite")); + await fs.rm(tempDir, { recursive: true, force: true }); + } }); it("exposes the shared consult tool based on policy", () => { @@ -346,7 +373,7 @@ describe("realtime voice agent consult runtime", () => { const mutationStarted = createDeferred(); const releaseMutation = createDeferred(); const mutation = runExclusiveSessionLifecycleMutation({ - scope: "/tmp/sessions.json", + scope: testTempPath("sessions.json"), identities: [sessionKey, "active-session"], run: async () => { mutationStarted.resolve(); @@ -438,7 +465,7 @@ describe("realtime voice agent consult runtime", () => { const { runtime, runEmbeddedAgent, sessionStore } = createAgentRuntime(); sessionStore["agent:main:main"] = { sessionId: "parent-session", - sessionFile: "/tmp/parent.jsonl", + sessionFile: testTempPath("parent.jsonl"), totalTokens: 100, updatedAt: 1, }; @@ -454,7 +481,7 @@ describe("realtime voice agent consult runtime", () => { ): Promise => { const fork = { sessionId: "forked-session", - sessionFile: "/tmp/forked.jsonl", + sessionFile: testTempPath("forked.jsonl"), }; const parentEntry = sessionStore["agent:main:main"]; if (!parentEntry?.sessionId) { @@ -521,7 +548,7 @@ describe("realtime voice agent consult runtime", () => { } expect(forkedEntry).toStrictEqual({ sessionId: "forked-session", - sessionFile: "/tmp/forked.jsonl", + sessionFile: testTempPath("forked.jsonl"), spawnedBy: "agent:main:main", forkedFromParent: true, updatedAt: forkedEntry.updatedAt, @@ -534,7 +561,7 @@ describe("realtime voice agent consult runtime", () => { agentId: "main", sessionId: "forked-session", sessionKey: "agent:main:subagent:google-meet:meet-1", - storePath: "/tmp/sessions.json", + storePath: testTempPath("sessions.json"), }); expect(call.spawnedBy).toBe("agent:main:main"); }); @@ -593,7 +620,7 @@ describe("realtime voice agent consult runtime", () => { agentId: "main", sessionId: call.sessionId, sessionKey: "agent:main:subagent:google-meet:meet-1", - storePath: "/tmp/sessions.json", + storePath: testTempPath("sessions.json"), }); expect(call.spawnedBy).toBe("agent:main:main"); });