From de96f5fed22f35e8ae6a1da874b37fbab2b118c2 Mon Sep 17 00:00:00 2001 From: Vignesh Natarajan Date: Sun, 22 Feb 2026 22:46:59 -0800 Subject: [PATCH] CLI/Sessions: honor default agent for implicit store path --- CHANGELOG.md | 1 + .../sessions.default-agent-store.test.ts | 72 +++++++++++++++++++ src/commands/sessions.ts | 4 +- 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 src/commands/sessions.default-agent-store.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 82e2d59da20..ad66f56bb6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -111,6 +111,7 @@ Docs: https://docs.openclaw.ai - Channels/Group policy: fail closed when `groupPolicy: "allowlist"` is set without explicit `groups`, honor account-level `groupPolicy` overrides, and enforce `groupPolicy: "disabled"` as a hard group block. (#22215) Thanks @etereo. - Telegram/Discord extensions: propagate trusted `mediaLocalRoots` through extension outbound `sendMedia` options so extension direct-send media paths honor agent-scoped local-media allowlists. (#20029, #21903, #23227) - Agents/Exec: honor explicit agent context when resolving `tools.exec` defaults for runs with opaque/non-agent session keys, so per-agent `host/security/ask` policies are applied consistently. (#11832) +- CLI/Sessions: resolve implicit session-store path templates with the configured default agent ID so named-agent setups do not silently read/write stale `agent:main` session/auth stores. (#22685) Thanks @sene1337. - Doctor/Security: add an explicit warning that `approvals.exec.enabled=false` disables forwarding only, while enforcement remains driven by host-local `exec-approvals.json` policy. (#15047) - Sandbox/Docker: default sandbox container user to the workspace owner `uid:gid` when `agents.*.sandbox.docker.user` is unset, fixing non-root gateway file-tool permissions under capability-dropped containers. (#20979) - Plugins/Media sandbox: propagate trusted `mediaLocalRoots` through plugin action dispatch (including Discord/Telegram action adapters) so plugin send paths enforce the same agent-scoped local-media sandbox roots as core outbound sends. (#20258, #22718) diff --git a/src/commands/sessions.default-agent-store.test.ts b/src/commands/sessions.default-agent-store.test.ts new file mode 100644 index 00000000000..604d6eb9fc2 --- /dev/null +++ b/src/commands/sessions.default-agent-store.test.ts @@ -0,0 +1,72 @@ +import { describe, expect, it, vi } from "vitest"; +import type { RuntimeEnv } from "../runtime.js"; + +const loadConfigMock = vi.hoisted(() => + vi.fn(() => ({ + agents: { + defaults: { + model: { primary: "pi:opus" }, + models: { "pi:opus": {} }, + contextTokens: 32000, + }, + list: [ + { id: "main", default: false }, + { id: "voice", default: true }, + ], + }, + session: { + store: "/tmp/sessions-{agentId}.json", + }, + })), +); + +const resolveStorePathMock = vi.hoisted(() => + vi.fn((_store: string | undefined, opts?: { agentId?: string }) => { + return `/tmp/sessions-${opts?.agentId ?? "missing"}.json`; + }), +); + +vi.mock("../config/config.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + loadConfig: loadConfigMock, + }; +}); + +vi.mock("../config/sessions.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + resolveStorePath: resolveStorePathMock, + loadSessionStore: vi.fn(() => ({})), + }; +}); + +import { sessionsCommand } from "./sessions.js"; + +function createRuntime(): { runtime: RuntimeEnv; logs: string[] } { + const logs: string[] = []; + return { + runtime: { + log: (msg: unknown) => logs.push(String(msg)), + error: vi.fn(), + exit: vi.fn(), + }, + logs, + }; +} + +describe("sessionsCommand default store agent selection", () => { + it("uses configured default agent id when resolving implicit session store path", async () => { + resolveStorePathMock.mockClear(); + const { runtime, logs } = createRuntime(); + + await sessionsCommand({}, runtime); + + expect(resolveStorePathMock).toHaveBeenCalledWith("/tmp/sessions-{agentId}.json", { + agentId: "voice", + }); + expect(logs[0]).toContain("Session store: /tmp/sessions-voice.json"); + }); +}); diff --git a/src/commands/sessions.ts b/src/commands/sessions.ts index 0a0934b82b8..53221559479 100644 --- a/src/commands/sessions.ts +++ b/src/commands/sessions.ts @@ -1,3 +1,4 @@ +import { resolveDefaultAgentId } from "../agents/agent-scope.js"; import { lookupContextTokens } from "../agents/context.js"; import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js"; import { resolveConfiguredModelRef } from "../agents/model-selection.js"; @@ -181,7 +182,8 @@ export async function sessionsCommand( lookupContextTokens(resolved.model) ?? DEFAULT_CONTEXT_TOKENS; const configModel = resolved.model ?? DEFAULT_MODEL; - const storePath = resolveStorePath(opts.store ?? cfg.session?.store); + const defaultAgentId = resolveDefaultAgentId(cfg); + const storePath = resolveStorePath(opts.store ?? cfg.session?.store, { agentId: defaultAgentId }); const store = loadSessionStore(storePath); let activeMinutes: number | undefined;