test(talk): isolate consult session storage (#110078)

This commit is contained in:
Peter Steinberger
2026-07-17 17:40:08 +01:00
committed by GitHub
parent 36ab06da22
commit b75b62fb1c
+39 -12
View File
@@ -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<ForkSessionEntryFromParentResult> => {
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");
});