ACP: resolve diagnostics from configured session stores

This commit is contained in:
Gustavo Madeira Santana
2026-03-12 01:47:40 +00:00
parent aec492c491
commit 690bea5e3d
6 changed files with 165 additions and 89 deletions

View File

@@ -0,0 +1,69 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../config/config.js";
const hoisted = vi.hoisted(() => {
const resolveSessionStoreTargetsMock = vi.fn();
const loadSessionStoreMock = vi.fn();
return {
resolveSessionStoreTargetsMock,
loadSessionStoreMock,
};
});
vi.mock("../../config/sessions.js", async () => {
const actual = await vi.importActual<typeof import("../../config/sessions.js")>(
"../../config/sessions.js",
);
return {
...actual,
resolveSessionStoreTargets: (cfg: OpenClawConfig, opts: unknown) =>
hoisted.resolveSessionStoreTargetsMock(cfg, opts),
loadSessionStore: (storePath: string) => hoisted.loadSessionStoreMock(storePath),
};
});
const { listAcpSessionEntries } = await import("./session-meta.js");
describe("listAcpSessionEntries", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("reads ACP sessions from resolved configured store targets", async () => {
const cfg = {
session: {
store: "/custom/sessions/{agentId}.json",
},
} as OpenClawConfig;
hoisted.resolveSessionStoreTargetsMock.mockReturnValue([
{
agentId: "ops",
storePath: "/custom/sessions/ops.json",
},
]);
hoisted.loadSessionStoreMock.mockReturnValue({
"agent:ops:acp:s1": {
updatedAt: 123,
acp: {
backend: "acpx",
agent: "ops",
mode: "persistent",
state: "idle",
},
},
});
const entries = await listAcpSessionEntries({ cfg });
expect(hoisted.resolveSessionStoreTargetsMock).toHaveBeenCalledWith(cfg, { allAgents: true });
expect(hoisted.loadSessionStoreMock).toHaveBeenCalledWith("/custom/sessions/ops.json");
expect(entries).toEqual([
expect.objectContaining({
cfg,
storePath: "/custom/sessions/ops.json",
sessionKey: "agent:ops:acp:s1",
storeSessionKey: "agent:ops:acp:s1",
}),
]);
});
});

View File

@@ -1,9 +1,11 @@
import path from "node:path";
import { resolveAgentSessionDirs } from "../../agents/session-dirs.js";
import type { OpenClawConfig } from "../../config/config.js";
import { loadConfig } from "../../config/config.js";
import { resolveStateDir } from "../../config/paths.js";
import { loadSessionStore, resolveStorePath, updateSessionStore } from "../../config/sessions.js";
import {
loadSessionStore,
resolveSessionStoreTargets,
resolveStorePath,
updateSessionStore,
} from "../../config/sessions.js";
import {
mergeSessionEntry,
type SessionAcpMeta,
@@ -90,12 +92,11 @@ export async function listAcpSessionEntries(params: {
cfg?: OpenClawConfig;
}): Promise<AcpSessionStoreEntry[]> {
const cfg = params.cfg ?? loadConfig();
const stateDir = resolveStateDir(process.env);
const sessionDirs = await resolveAgentSessionDirs(stateDir);
const storeTargets = resolveSessionStoreTargets(cfg, { allAgents: true });
const entries: AcpSessionStoreEntry[] = [];
for (const sessionsDir of sessionDirs) {
const storePath = path.join(sessionsDir, "sessions.json");
for (const target of storeTargets) {
const storePath = target.storePath;
let store: Record<string, SessionEntry>;
try {
store = loadSessionStore(storePath);

View File

@@ -1,11 +1,11 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { resolveSessionStoreTargets } from "./session-store-targets.js";
import { resolveSessionStoreTargets } from "../config/sessions/targets.js";
const resolveStorePathMock = vi.hoisted(() => vi.fn());
const resolveDefaultAgentIdMock = vi.hoisted(() => vi.fn());
const listAgentIdsMock = vi.hoisted(() => vi.fn());
vi.mock("../config/sessions.js", () => ({
vi.mock("../config/sessions/paths.js", () => ({
resolveStorePath: resolveStorePathMock,
}));

View File

@@ -1,84 +1,9 @@
import { listAgentIds, resolveDefaultAgentId } from "../agents/agent-scope.js";
import { resolveStorePath } from "../config/sessions.js";
export { resolveSessionStoreTargets } from "../config/sessions.js";
import { resolveSessionStoreTargets } from "../config/sessions.js";
import type { SessionStoreSelectionOptions, SessionStoreTarget } from "../config/sessions.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { normalizeAgentId } from "../routing/session-key.js";
import type { RuntimeEnv } from "../runtime.js";
export type SessionStoreSelectionOptions = {
store?: string;
agent?: string;
allAgents?: boolean;
};
export type SessionStoreTarget = {
agentId: string;
storePath: string;
};
function dedupeTargetsByStorePath(targets: SessionStoreTarget[]): SessionStoreTarget[] {
const deduped = new Map<string, SessionStoreTarget>();
for (const target of targets) {
if (!deduped.has(target.storePath)) {
deduped.set(target.storePath, target);
}
}
return [...deduped.values()];
}
export function resolveSessionStoreTargets(
cfg: OpenClawConfig,
opts: SessionStoreSelectionOptions,
): SessionStoreTarget[] {
const defaultAgentId = resolveDefaultAgentId(cfg);
const hasAgent = Boolean(opts.agent?.trim());
const allAgents = opts.allAgents === true;
if (hasAgent && allAgents) {
throw new Error("--agent and --all-agents cannot be used together");
}
if (opts.store && (hasAgent || allAgents)) {
throw new Error("--store cannot be combined with --agent or --all-agents");
}
if (opts.store) {
return [
{
agentId: defaultAgentId,
storePath: resolveStorePath(opts.store, { agentId: defaultAgentId }),
},
];
}
if (allAgents) {
const targets = listAgentIds(cfg).map((agentId) => ({
agentId,
storePath: resolveStorePath(cfg.session?.store, { agentId }),
}));
return dedupeTargetsByStorePath(targets);
}
if (hasAgent) {
const knownAgents = listAgentIds(cfg);
const requested = normalizeAgentId(opts.agent ?? "");
if (!knownAgents.includes(requested)) {
throw new Error(
`Unknown agent id "${opts.agent}". Use "openclaw agents list" to see configured agents.`,
);
}
return [
{
agentId: requested,
storePath: resolveStorePath(cfg.session?.store, { agentId: requested }),
},
];
}
return [
{
agentId: defaultAgentId,
storePath: resolveStorePath(cfg.session?.store, { agentId: defaultAgentId }),
},
];
}
export type { SessionStoreSelectionOptions, SessionStoreTarget } from "../config/sessions.js";
export function resolveSessionStoreTargetsOrExit(params: {
cfg: OpenClawConfig;

View File

@@ -11,3 +11,4 @@ export * from "./sessions/transcript.js";
export * from "./sessions/session-file.js";
export * from "./sessions/delivery-info.js";
export * from "./sessions/disk-budget.js";
export * from "./sessions/targets.js";

View File

@@ -0,0 +1,80 @@
import { listAgentIds, resolveDefaultAgentId } from "../../agents/agent-scope.js";
import { normalizeAgentId } from "../../routing/session-key.js";
import type { OpenClawConfig } from "../types.openclaw.js";
import { resolveStorePath } from "./paths.js";
export type SessionStoreSelectionOptions = {
store?: string;
agent?: string;
allAgents?: boolean;
};
export type SessionStoreTarget = {
agentId: string;
storePath: string;
};
function dedupeTargetsByStorePath(targets: SessionStoreTarget[]): SessionStoreTarget[] {
const deduped = new Map<string, SessionStoreTarget>();
for (const target of targets) {
if (!deduped.has(target.storePath)) {
deduped.set(target.storePath, target);
}
}
return [...deduped.values()];
}
export function resolveSessionStoreTargets(
cfg: OpenClawConfig,
opts: SessionStoreSelectionOptions,
): SessionStoreTarget[] {
const defaultAgentId = resolveDefaultAgentId(cfg);
const hasAgent = Boolean(opts.agent?.trim());
const allAgents = opts.allAgents === true;
if (hasAgent && allAgents) {
throw new Error("--agent and --all-agents cannot be used together");
}
if (opts.store && (hasAgent || allAgents)) {
throw new Error("--store cannot be combined with --agent or --all-agents");
}
if (opts.store) {
return [
{
agentId: defaultAgentId,
storePath: resolveStorePath(opts.store, { agentId: defaultAgentId }),
},
];
}
if (allAgents) {
const targets = listAgentIds(cfg).map((agentId) => ({
agentId,
storePath: resolveStorePath(cfg.session?.store, { agentId }),
}));
return dedupeTargetsByStorePath(targets);
}
if (hasAgent) {
const knownAgents = listAgentIds(cfg);
const requested = normalizeAgentId(opts.agent ?? "");
if (!knownAgents.includes(requested)) {
throw new Error(
`Unknown agent id "${opts.agent}". Use "openclaw agents list" to see configured agents.`,
);
}
return [
{
agentId: requested,
storePath: resolveStorePath(cfg.session?.store, { agentId: requested }),
},
];
}
return [
{
agentId: defaultAgentId,
storePath: resolveStorePath(cfg.session?.store, { agentId: defaultAgentId }),
},
];
}