mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 13:50:49 +00:00
test(agents): share subagent persistence fixtures
This commit is contained in:
@@ -8,6 +8,10 @@ import {
|
||||
drainSessionStoreLockQueuesForTest,
|
||||
} from "../config/sessions/store.js";
|
||||
import { captureEnv } from "../test-utils/env.js";
|
||||
import {
|
||||
createSubagentRegistryTestDeps,
|
||||
writeSubagentSessionEntry,
|
||||
} from "./subagent-registry.persistence.test-support.js";
|
||||
|
||||
const hoisted = vi.hoisted(() => ({
|
||||
announceSpy: vi.fn(async () => true),
|
||||
@@ -71,22 +75,6 @@ describe("subagent registry persistence resume", () => {
|
||||
const envSnapshot = captureEnv(["OPENCLAW_STATE_DIR"]);
|
||||
let tempStateDir: string | null = null;
|
||||
|
||||
const resolveSessionStorePath = (stateDir: string, agentId: string) =>
|
||||
path.join(stateDir, "agents", agentId, "sessions", "sessions.json");
|
||||
|
||||
const readSessionStore = async (storePath: string) => {
|
||||
try {
|
||||
const raw = await fs.readFile(storePath, "utf8");
|
||||
const parsed = JSON.parse(raw) as unknown;
|
||||
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
||||
return parsed as Record<string, Record<string, unknown>>;
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return {} as Record<string, Record<string, unknown>>;
|
||||
};
|
||||
|
||||
const writeChildSessionEntry = async (params: {
|
||||
sessionKey: string;
|
||||
sessionId?: string;
|
||||
@@ -95,16 +83,14 @@ describe("subagent registry persistence resume", () => {
|
||||
if (!tempStateDir) {
|
||||
throw new Error("tempStateDir not initialized");
|
||||
}
|
||||
const storePath = resolveSessionStorePath(tempStateDir, "main");
|
||||
const store = await readSessionStore(storePath);
|
||||
store[params.sessionKey] = {
|
||||
...store[params.sessionKey],
|
||||
sessionId: params.sessionId ?? `sess-${Date.now()}`,
|
||||
updatedAt: params.updatedAt ?? Date.now(),
|
||||
};
|
||||
await fs.mkdir(path.dirname(storePath), { recursive: true });
|
||||
await fs.writeFile(storePath, `${JSON.stringify(store)}\n`, "utf8");
|
||||
return storePath;
|
||||
return await writeSubagentSessionEntry({
|
||||
stateDir: tempStateDir,
|
||||
agentId: "main",
|
||||
sessionKey: params.sessionKey,
|
||||
sessionId: params.sessionId,
|
||||
updatedAt: params.updatedAt,
|
||||
defaultSessionId: `sess-${Date.now()}`,
|
||||
});
|
||||
};
|
||||
|
||||
const flushQueuedRegistryWork = async () => {
|
||||
@@ -129,19 +115,10 @@ describe("subagent registry persistence resume", () => {
|
||||
endedAt: 222,
|
||||
});
|
||||
mod.__testing.setDepsForTest({
|
||||
callGateway: vi.mocked(callGatewayModule.callGateway),
|
||||
cleanupBrowserSessionsForLifecycleEnd: vi.fn(async () => {}),
|
||||
captureSubagentCompletionReply: vi.fn(async () => undefined),
|
||||
ensureContextEnginesInitialized: vi.fn(),
|
||||
ensureRuntimePluginsLoaded: vi.fn(),
|
||||
loadConfig: vi.fn(() => ({})),
|
||||
resolveAgentTimeoutMs: vi.fn(() => 100),
|
||||
resolveContextEngine: vi.fn(async () => ({
|
||||
info: { id: "test", name: "Test", version: "0.0.1" },
|
||||
ingest: vi.fn(async () => ({ ingested: false })),
|
||||
assemble: vi.fn(async ({ messages }) => ({ messages, estimatedTokens: 0 })),
|
||||
compact: vi.fn(async () => ({ ok: false, compacted: false })),
|
||||
})),
|
||||
...createSubagentRegistryTestDeps({
|
||||
callGateway: vi.mocked(callGatewayModule.callGateway),
|
||||
captureSubagentCompletionReply: vi.fn(async () => undefined),
|
||||
}),
|
||||
});
|
||||
mod.resetSubagentRegistryForTests({ persist: false });
|
||||
vi.mocked(agentEventsModule.onAgentEvent).mockReset();
|
||||
|
||||
74
src/agents/subagent-registry.persistence.test-support.ts
Normal file
74
src/agents/subagent-registry.persistence.test-support.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { vi } from "vitest";
|
||||
|
||||
type SessionStore = Record<string, Record<string, unknown>>;
|
||||
|
||||
export function resolveSubagentSessionStorePath(stateDir: string, agentId: string): string {
|
||||
return path.join(stateDir, "agents", agentId, "sessions", "sessions.json");
|
||||
}
|
||||
|
||||
export async function readSubagentSessionStore(storePath: string): Promise<SessionStore> {
|
||||
try {
|
||||
const raw = await fs.readFile(storePath, "utf8");
|
||||
const parsed = JSON.parse(raw) as unknown;
|
||||
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
||||
return parsed as SessionStore;
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
export async function writeSubagentSessionEntry(params: {
|
||||
stateDir: string;
|
||||
sessionKey: string;
|
||||
sessionId?: string;
|
||||
updatedAt?: number;
|
||||
agentId: string;
|
||||
defaultSessionId: string;
|
||||
}): Promise<string> {
|
||||
const storePath = resolveSubagentSessionStorePath(params.stateDir, params.agentId);
|
||||
const store = await readSubagentSessionStore(storePath);
|
||||
store[params.sessionKey] = {
|
||||
...store[params.sessionKey],
|
||||
sessionId: params.sessionId ?? params.defaultSessionId,
|
||||
updatedAt: params.updatedAt ?? Date.now(),
|
||||
};
|
||||
await fs.mkdir(path.dirname(storePath), { recursive: true });
|
||||
await fs.writeFile(storePath, `${JSON.stringify(store)}\n`, "utf8");
|
||||
return storePath;
|
||||
}
|
||||
|
||||
export async function removeSubagentSessionEntry(params: {
|
||||
stateDir: string;
|
||||
sessionKey: string;
|
||||
agentId: string;
|
||||
}): Promise<string> {
|
||||
const storePath = resolveSubagentSessionStorePath(params.stateDir, params.agentId);
|
||||
const store = await readSubagentSessionStore(storePath);
|
||||
delete store[params.sessionKey];
|
||||
await fs.mkdir(path.dirname(storePath), { recursive: true });
|
||||
await fs.writeFile(storePath, `${JSON.stringify(store)}\n`, "utf8");
|
||||
return storePath;
|
||||
}
|
||||
|
||||
export function createSubagentRegistryTestDeps(
|
||||
extra: Record<string, unknown> = {},
|
||||
): Record<string, unknown> {
|
||||
return {
|
||||
cleanupBrowserSessionsForLifecycleEnd: vi.fn(async () => {}),
|
||||
ensureContextEnginesInitialized: vi.fn(),
|
||||
ensureRuntimePluginsLoaded: vi.fn(),
|
||||
loadConfig: vi.fn(() => ({})),
|
||||
resolveAgentTimeoutMs: vi.fn(() => 100),
|
||||
resolveContextEngine: vi.fn(async () => ({
|
||||
info: { id: "test", name: "Test", version: "0.0.1" },
|
||||
ingest: vi.fn(async () => ({ ingested: false })),
|
||||
assemble: vi.fn(async ({ messages }) => ({ messages, estimatedTokens: 0 })),
|
||||
compact: vi.fn(async () => ({ ok: false, compacted: false })),
|
||||
})),
|
||||
...extra,
|
||||
};
|
||||
}
|
||||
@@ -22,6 +22,12 @@ import {
|
||||
registerSubagentRun,
|
||||
resetSubagentRegistryForTests,
|
||||
} from "./subagent-registry.js";
|
||||
import {
|
||||
createSubagentRegistryTestDeps,
|
||||
readSubagentSessionStore,
|
||||
removeSubagentSessionEntry,
|
||||
writeSubagentSessionEntry,
|
||||
} from "./subagent-registry.persistence.test-support.js";
|
||||
import {
|
||||
loadSubagentRegistryFromDisk,
|
||||
resolveSubagentRegistryPath,
|
||||
@@ -47,22 +53,6 @@ describe("subagent registry persistence", () => {
|
||||
return (match?.[1] ?? "main").trim().toLowerCase() || "main";
|
||||
};
|
||||
|
||||
const resolveSessionStorePath = (stateDir: string, agentId: string) =>
|
||||
path.join(stateDir, "agents", agentId, "sessions", "sessions.json");
|
||||
|
||||
const readSessionStore = async (storePath: string) => {
|
||||
try {
|
||||
const raw = await fs.readFile(storePath, "utf8");
|
||||
const parsed = JSON.parse(raw) as unknown;
|
||||
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
||||
return parsed as Record<string, Record<string, unknown>>;
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return {} as Record<string, Record<string, unknown>>;
|
||||
};
|
||||
|
||||
const writeChildSessionEntry = async (params: {
|
||||
sessionKey: string;
|
||||
sessionId?: string;
|
||||
@@ -72,16 +62,14 @@ describe("subagent registry persistence", () => {
|
||||
throw new Error("tempStateDir not initialized");
|
||||
}
|
||||
const agentId = resolveAgentIdFromSessionKey(params.sessionKey);
|
||||
const storePath = resolveSessionStorePath(tempStateDir, agentId);
|
||||
const store = await readSessionStore(storePath);
|
||||
store[params.sessionKey] = {
|
||||
...store[params.sessionKey],
|
||||
sessionId: params.sessionId ?? `sess-${agentId}-${Date.now()}`,
|
||||
updatedAt: params.updatedAt ?? Date.now(),
|
||||
};
|
||||
await fs.mkdir(path.dirname(storePath), { recursive: true });
|
||||
await fs.writeFile(storePath, `${JSON.stringify(store)}\n`, "utf8");
|
||||
return storePath;
|
||||
return await writeSubagentSessionEntry({
|
||||
stateDir: tempStateDir,
|
||||
agentId,
|
||||
sessionKey: params.sessionKey,
|
||||
sessionId: params.sessionId,
|
||||
updatedAt: params.updatedAt,
|
||||
defaultSessionId: `sess-${agentId}-${Date.now()}`,
|
||||
});
|
||||
};
|
||||
|
||||
const removeChildSessionEntry = async (sessionKey: string) => {
|
||||
@@ -89,12 +77,11 @@ describe("subagent registry persistence", () => {
|
||||
throw new Error("tempStateDir not initialized");
|
||||
}
|
||||
const agentId = resolveAgentIdFromSessionKey(sessionKey);
|
||||
const storePath = resolveSessionStorePath(tempStateDir, agentId);
|
||||
const store = await readSessionStore(storePath);
|
||||
delete store[sessionKey];
|
||||
await fs.mkdir(path.dirname(storePath), { recursive: true });
|
||||
await fs.writeFile(storePath, `${JSON.stringify(store)}\n`, "utf8");
|
||||
return storePath;
|
||||
return await removeSubagentSessionEntry({
|
||||
stateDir: tempStateDir,
|
||||
agentId,
|
||||
sessionKey,
|
||||
});
|
||||
};
|
||||
|
||||
const seedChildSessionsForPersistedRuns = async (persisted: Record<string, unknown>) => {
|
||||
@@ -181,17 +168,7 @@ describe("subagent registry persistence", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
__testing.setDepsForTest({
|
||||
cleanupBrowserSessionsForLifecycleEnd: vi.fn(async () => {}),
|
||||
ensureContextEnginesInitialized: vi.fn(),
|
||||
ensureRuntimePluginsLoaded: vi.fn(),
|
||||
loadConfig: vi.fn(() => ({})),
|
||||
resolveAgentTimeoutMs: vi.fn(() => 100),
|
||||
resolveContextEngine: vi.fn(async () => ({
|
||||
info: { id: "test", name: "Test", version: "0.0.1" },
|
||||
ingest: vi.fn(async () => ({ ingested: false })),
|
||||
assemble: vi.fn(async ({ messages }) => ({ messages, estimatedTokens: 0 })),
|
||||
compact: vi.fn(async () => ({ ok: false, compacted: false })),
|
||||
})),
|
||||
...createSubagentRegistryTestDeps(),
|
||||
runSubagentAnnounceFlow: announceSpy,
|
||||
});
|
||||
vi.mocked(callGateway).mockReset();
|
||||
@@ -245,7 +222,7 @@ describe("subagent registry persistence", () => {
|
||||
outcome: { status: "ok" },
|
||||
} as never);
|
||||
|
||||
const store = await readSessionStore(storePath);
|
||||
const store = await readSubagentSessionStore(storePath);
|
||||
const persisted = store["agent:main:subagent:timing"];
|
||||
expect(persisted?.endedAt).toBe(endedAt);
|
||||
expect(persisted?.runtimeMs).toBe(500);
|
||||
|
||||
Reference in New Issue
Block a user