Download src/agents/command/session.resolve-session-key.test.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 22.4 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/agents/command/session.resolve-session-key.test.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/agents/command/session.resolve-session-key.test.ts
-
curl -L -o session.resolve-session-key.test.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/agents/command/session.resolve-session-key.test.ts
22.4 kB
| // Covers cross-store session-key resolution for multi-agent session stores. | |
| import { beforeEach, describe, expect, it, vi } from "vitest"; | |
| import type { OpenClawConfig } from "../../config/config.js"; | |
| import { retainLegacyDefaultAgentId } from "../../config/legacy.default-agent-owner.js"; | |
| import { migratePersistedImplicitMainRoster } from "../../config/legacy.roster.js"; | |
| import type { SessionEntry } from "../../config/sessions/types.js"; | |
| const hoisted = vi.hoisted(() => ({ | |
| listSessionEntriesMock: vi.fn< | |
| (scope?: { agentId?: string; storePath?: string; clone?: boolean }) => Array<{ | |
| entry: SessionEntry; | |
| sessionKey: string; | |
| }> | |
| >(), | |
| loadExactSessionEntryMock: | |
| vi.fn< | |
| (scope: { | |
| storePath?: string; | |
| sessionKey: string; | |
| }) => { sessionKey: string; entry: SessionEntry } | undefined | |
| >(), | |
| listAgentIdsMock: vi.fn<() => string[]>(), | |
| })); | |
| vi.mock("../../config/sessions/session-accessor.js", () => ({ | |
| listSessionEntriesReadOnly: (scope?: { agentId?: string; storePath?: string; clone?: boolean }) => | |
| hoisted.listSessionEntriesMock(scope), | |
| loadExactSessionEntryReadOnly: hoisted.loadExactSessionEntryMock, | |
| })); | |
| vi.mock("../../config/sessions/paths.js", () => ({ | |
| resolveSessionStorePathCore: (store?: string, params?: { agentId?: string }) => | |
| store | |
| ? store.replace("{agentId}", params?.agentId ?? "main") | |
| : `/stores/${params?.agentId ?? "main"}.json`, | |
| })); | |
| vi.mock("../../config/sessions/main-session.js", () => ({ | |
| canonicalizeMainSessionAlias: ({ sessionKey }: { sessionKey: string }) => sessionKey, | |
| resolveAgentIdFromSessionKey: () => "main", | |
| resolveExplicitAgentSessionKey: () => undefined, | |
| })); | |
| vi.mock("../agent-scope.js", async () => ({ | |
| ...(await vi.importActual<typeof import("../agent-scope.js")>("../agent-scope.js")), | |
| listAgentIds: () => hoisted.listAgentIdsMock(), | |
| })); | |
| const { resolveSessionKeyForRequestCore, resolveStoredSessionKeyForSessionId } = | |
| await import("./session.js"); | |
| const resolveSessionKeyForRequest = resolveSessionKeyForRequestCore; | |
| function mockSessionStores(storesByPath: Record<string, Record<string, SessionEntry>>): void { | |
| hoisted.loadExactSessionEntryMock.mockImplementation(({ storePath, sessionKey }) => { | |
| const entry = storesByPath[storePath ?? ""]?.[sessionKey]; | |
| return entry ? { sessionKey, entry: structuredClone(entry) } : undefined; | |
| }); | |
| hoisted.listSessionEntriesMock.mockImplementation((scope) => | |
| Object.entries(storesByPath[scope?.storePath ?? ""] ?? {}).map(([sessionKey, entry]) => ({ | |
| sessionKey, | |
| entry, | |
| })), | |
| ); | |
| } | |
| function expectResolvedRequestSession(params: { | |
| sessionId: string; | |
| sessionKey: string; | |
| sessionStore: Record<string, SessionEntry>; | |
| storePath: string; | |
| }): void { | |
| const result = resolveSessionKeyForRequestCore({ | |
| cfg: { | |
| session: { | |
| store: "/stores/{agentId}.json", | |
| }, | |
| } satisfies OpenClawConfig, | |
| sessionId: params.sessionId, | |
| }); | |
| expect(result.sessionKey).toBe(params.sessionKey); | |
| expect(result.sessionEntry).toEqual(params.sessionStore[params.sessionKey]); | |
| expect(result.storePath).toBe(params.storePath); | |
| } | |
| describe("resolveSessionKeyForRequest", () => { | |
| beforeEach(() => { | |
| hoisted.listSessionEntriesMock.mockReset(); | |
| hoisted.loadExactSessionEntryMock.mockReset(); | |
| hoisted.listAgentIdsMock.mockReset(); | |
| hoisted.listAgentIdsMock.mockReturnValue(["main", "other"]); | |
| }); | |
| it("prefers the current store when equal duplicates exist across stores", () => { | |
| const mainStore = { | |
| "agent:main:main": { sessionId: "sid", updatedAt: 10 }, | |
| } satisfies Record<string, SessionEntry>; | |
| const otherStore = { | |
| "agent:other:main": { sessionId: "sid", updatedAt: 10 }, | |
| } satisfies Record<string, SessionEntry>; | |
| mockSessionStores({ | |
| "/stores/main.json": mainStore, | |
| "/stores/other.json": otherStore, | |
| }); | |
| expectResolvedRequestSession({ | |
| sessionId: "sid", | |
| sessionKey: "agent:main:main", | |
| sessionStore: mainStore, | |
| storePath: "/stores/main.json", | |
| }); | |
| }); | |
| it("keeps a cross-store structural winner over a newer local fuzzy duplicate", () => { | |
| // Structural keys beat fuzzy timestamp matches so ACP/subagent resumes do | |
| // not accidentally attach to a newer generic main-session duplicate. | |
| const mainStore = { | |
| "agent:main:main": { sessionId: "sid", updatedAt: 20 }, | |
| } satisfies Record<string, SessionEntry>; | |
| const otherStore = { | |
| "agent:other:acp:sid": { sessionId: "sid", updatedAt: 10 }, | |
| } satisfies Record<string, SessionEntry>; | |
| mockSessionStores({ | |
| "/stores/main.json": mainStore, | |
| "/stores/other.json": otherStore, | |
| }); | |
| expectResolvedRequestSession({ | |
| sessionId: "sid", | |
| sessionKey: "agent:other:acp:sid", | |
| sessionStore: otherStore, | |
| storePath: "/stores/other.json", | |
| }); | |
| }); | |
| it("scopes stored session-key lookup to the requested agent store", () => { | |
| const embeddedAgentStore = { | |
| "agent:embedded-agent:main": { sessionId: "other-session", updatedAt: 2 }, | |
| "agent:embedded-agent:work": { sessionId: "resume-agent-1", updatedAt: 1 }, | |
| } satisfies Record<string, SessionEntry>; | |
| mockSessionStores({ "/stores/embedded-agent.json": embeddedAgentStore }); | |
| const result = resolveStoredSessionKeyForSessionId({ | |
| cfg: { | |
| session: { | |
| store: "/stores/{agentId}.json", | |
| }, | |
| } satisfies OpenClawConfig, | |
| sessionId: "resume-agent-1", | |
| agentId: "embedded-agent", | |
| }); | |
| expect(result.sessionKey).toBe("agent:embedded-agent:work"); | |
| expect(result.sessionEntry).toEqual(embeddedAgentStore["agent:embedded-agent:work"]); | |
| expect(result.storePath).toBe("/stores/embedded-agent.json"); | |
| expect(hoisted.listSessionEntriesMock).toHaveBeenCalledTimes(1); | |
| }); | |
| it("assigns unscoped shared-store rows to the persisted owner regardless of scan order", () => { | |
| hoisted.listAgentIdsMock.mockReturnValue(["research", "ops"]); | |
| const sharedStore = { | |
| main: { sessionId: "ops-session", updatedAt: 10 }, | |
| } satisfies Record<string, SessionEntry>; | |
| mockSessionStores({ "/stores/shared.sqlite": sharedStore }); | |
| const result = resolveSessionKeyForRequest({ | |
| cfg: { | |
| session: { store: "/stores/shared.sqlite" }, | |
| agents: { | |
| ownership: "explicit", | |
| defaults: { sessionStore: { agentId: "ops" } }, | |
| entries: { research: {}, ops: {} }, | |
| }, | |
| } satisfies OpenClawConfig, | |
| sessionId: "ops-session", | |
| }); | |
| expect(result.agentId).toBe("ops"); | |
| expect(result.sessionKey).toBe("main"); | |
| expect(result.sessionEntry).toEqual(sharedStore.main); | |
| expect(result.storePath).toBe("/stores/shared.sqlite"); | |
| expect(hoisted.listSessionEntriesMock.mock.calls.map(([scope]) => scope?.agentId)).toEqual([ | |
| "research", | |
| "ops", | |
| ]); | |
| }); | |
| it("uses the persisted fixed-store owner for direct session-id lookup", () => { | |
| hoisted.listAgentIdsMock.mockReturnValue(["research", "ops"]); | |
| const sharedStore = { | |
| main: { sessionId: "ops-session", updatedAt: 10 }, | |
| } satisfies Record<string, SessionEntry>; | |
| mockSessionStores({ "/stores/shared.sqlite": sharedStore }); | |
| expect( | |
| resolveStoredSessionKeyForSessionId({ | |
| cfg: { | |
| session: { store: "/stores/shared.sqlite" }, | |
| agents: { | |
| ownership: "explicit", | |
| defaults: { sessionStore: { agentId: "ops" } }, | |
| entries: { research: {}, ops: {} }, | |
| }, | |
| }, | |
| sessionId: "ops-session", | |
| }), | |
| ).toMatchObject({ | |
| agentId: "ops", | |
| sessionKey: "main", | |
| sessionEntry: sharedStore.main, | |
| storePath: "/stores/shared.sqlite", | |
| }); | |
| }); | |
| it("rejects an explicit agent that conflicts with an unscoped direct session-id match", () => { | |
| const sharedStore = { | |
| main: { sessionId: "ops-session", updatedAt: 10 }, | |
| } satisfies Record<string, SessionEntry>; | |
| mockSessionStores({ "/stores/shared.sqlite": sharedStore }); | |
| const cfg = { | |
| session: { store: "/stores/shared.sqlite" }, | |
| agents: { | |
| ownership: "explicit", | |
| defaults: { sessionStore: { agentId: "ops" } }, | |
| entries: { research: {}, ops: {} }, | |
| }, | |
| } satisfies OpenClawConfig; | |
| expect(() => | |
| resolveStoredSessionKeyForSessionId({ | |
| cfg, | |
| sessionId: "ops-session", | |
| agentId: "research", | |
| }), | |
| ).toThrowError(expect.objectContaining({ code: "AGENT_SELECTION_REQUIRED" })); | |
| }); | |
| it("prefers the requested agent's scoped direct match over a newer foreign row", () => { | |
| const sharedStore = { | |
| "agent:ops:work": { sessionId: "shared-session", updatedAt: 20 }, | |
| "agent:research:work": { sessionId: "shared-session", updatedAt: 10 }, | |
| } satisfies Record<string, SessionEntry>; | |
| mockSessionStores({ "/stores/shared.sqlite": sharedStore }); | |
| expect( | |
| resolveStoredSessionKeyForSessionId({ | |
| cfg: { | |
| session: { store: "/stores/shared.sqlite" }, | |
| agents: { ownership: "explicit", entries: { research: {}, ops: {} } }, | |
| }, | |
| sessionId: "shared-session", | |
| agentId: "research", | |
| }), | |
| ).toMatchObject({ | |
| agentId: "research", | |
| sessionKey: "agent:research:work", | |
| sessionEntry: sharedStore["agent:research:work"], | |
| storePath: "/stores/shared.sqlite", | |
| }); | |
| }); | |
| it("rejects a direct session-id lookup with only foreign scoped matches", () => { | |
| mockSessionStores({ | |
| "/stores/shared.sqlite": { | |
| "agent:ops:work": { sessionId: "ops-session", updatedAt: 20 }, | |
| }, | |
| }); | |
| expect(() => | |
| resolveStoredSessionKeyForSessionId({ | |
| cfg: { | |
| session: { store: "/stores/shared.sqlite" }, | |
| agents: { ownership: "explicit", entries: { research: {}, ops: {} } }, | |
| }, | |
| sessionId: "ops-session", | |
| agentId: "research", | |
| }), | |
| ).toThrowError(expect.objectContaining({ code: "AGENT_SELECTION_REQUIRED" })); | |
| }); | |
| it("resolves a scoped direct session-id match despite a retired fixed-store owner", () => { | |
| const researchStore = { | |
| "agent:research:work": { sessionId: "research-session", updatedAt: 10 }, | |
| } satisfies Record<string, SessionEntry>; | |
| mockSessionStores({ "/stores/shared.sqlite": researchStore }); | |
| expect( | |
| resolveStoredSessionKeyForSessionId({ | |
| cfg: { | |
| session: { store: "/stores/shared.sqlite" }, | |
| agents: { | |
| ownership: "explicit", | |
| defaults: { sessionStore: { agentId: "ops" } }, | |
| entries: { research: {} }, | |
| }, | |
| }, | |
| sessionId: "research-session", | |
| }), | |
| ).toMatchObject({ | |
| agentId: "research", | |
| sessionKey: "agent:research:work", | |
| sessionEntry: researchStore["agent:research:work"], | |
| storePath: "/stores/shared.sqlite", | |
| }); | |
| }); | |
| it("does not reassign a retired sole agent's unscoped row to its replacement", () => { | |
| hoisted.listAgentIdsMock.mockReturnValue(["research"]); | |
| mockSessionStores({ | |
| "/stores/shared.sqlite": { | |
| main: { sessionId: "retired-session", updatedAt: 10 }, | |
| }, | |
| }); | |
| const cfg = { | |
| session: { store: "/stores/shared.sqlite" }, | |
| agents: { | |
| ownership: "explicit", | |
| defaults: { sessionStore: { agentId: "ops" } }, | |
| entries: { research: {} }, | |
| }, | |
| } satisfies OpenClawConfig; | |
| expect(() => resolveSessionKeyForRequest({ cfg, sessionId: "retired-session" })).toThrowError( | |
| expect.objectContaining({ code: "AGENT_SELECTION_REQUIRED" }), | |
| ); | |
| }); | |
| it("persists a legacy main fixed-store owner and fails closed after main is removed", () => { | |
| hoisted.listAgentIdsMock.mockReturnValue(["research"]); | |
| const sharedStore = { | |
| main: { sessionId: "legacy-main-session", updatedAt: 10 }, | |
| } satisfies Record<string, SessionEntry>; | |
| mockSessionStores({ "/stores/shared.sqlite": sharedStore }); | |
| const migrated = migratePersistedImplicitMainRoster({ | |
| session: { store: "/stores/shared.sqlite" }, | |
| agents: { entries: { main: { default: true }, research: {} } }, | |
| }).config as OpenClawConfig; | |
| expect(migrated.agents?.defaults?.sessionStore?.agentId).toBe("main"); | |
| const afterMainRemoval = { | |
| ...migrated, | |
| agents: { | |
| ...migrated.agents, | |
| ownership: "explicit" as const, | |
| entries: { research: {} }, | |
| }, | |
| } satisfies OpenClawConfig; | |
| expect(() => | |
| resolveSessionKeyForRequest({ cfg: afterMainRemoval, sessionId: "legacy-main-session" }), | |
| ).toThrowError(expect.objectContaining({ code: "AGENT_SELECTION_REQUIRED" })); | |
| }); | |
| it("resolves an unscoped fixed-store row while its persisted owner is configured", () => { | |
| hoisted.listAgentIdsMock.mockReturnValue(["ops"]); | |
| const sharedStore = { | |
| main: { sessionId: "ops-session", updatedAt: 10 }, | |
| } satisfies Record<string, SessionEntry>; | |
| mockSessionStores({ "/stores/shared.sqlite": sharedStore }); | |
| const result = resolveSessionKeyForRequest({ | |
| cfg: { | |
| session: { store: "/stores/shared.sqlite" }, | |
| agents: { | |
| ownership: "explicit", | |
| defaults: { sessionStore: { agentId: "ops" } }, | |
| entries: { ops: {} }, | |
| }, | |
| } satisfies OpenClawConfig, | |
| sessionId: "ops-session", | |
| }); | |
| expect(result.agentId).toBe("ops"); | |
| expect(result.sessionKey).toBe("main"); | |
| }); | |
| it("rejects an explicit agent that conflicts with an unscoped fixed-store owner", () => { | |
| hoisted.listAgentIdsMock.mockReturnValue(["ops", "research"]); | |
| const cfg = { | |
| session: { scope: "global", store: "/stores/shared.sqlite" }, | |
| agents: { | |
| ownership: "explicit", | |
| defaults: { sessionStore: { agentId: "ops" } }, | |
| entries: { ops: {}, research: {} }, | |
| }, | |
| } satisfies OpenClawConfig; | |
| expect(() => | |
| resolveSessionKeyForRequest({ cfg, agentId: "research", sessionKey: "global" }), | |
| ).toThrowError(expect.objectContaining({ code: "AGENT_SELECTION_REQUIRED" })); | |
| expect(hoisted.listSessionEntriesMock).not.toHaveBeenCalled(); | |
| mockSessionStores({ "/stores/shared.sqlite": {} }); | |
| expect( | |
| resolveSessionKeyForRequest({ cfg, agentId: "ops", sessionKey: "global" }), | |
| ).toMatchObject({ | |
| agentId: "ops", | |
| sessionKey: "global", | |
| storePath: "/stores/shared.sqlite", | |
| }); | |
| }); | |
| it("fails closed for an ownerless unscoped row during a cross-agent shared-store scan", () => { | |
| hoisted.listAgentIdsMock.mockReturnValue(["research", "ops"]); | |
| const sharedStore = { | |
| main: { sessionId: "ownerless-session", updatedAt: 10 }, | |
| } satisfies Record<string, SessionEntry>; | |
| mockSessionStores({ "/stores/shared.sqlite": sharedStore }); | |
| const cfg = { | |
| session: { store: "/stores/shared.sqlite" }, | |
| agents: { | |
| ownership: "explicit", | |
| entries: { research: {}, ops: {} }, | |
| }, | |
| } satisfies OpenClawConfig; | |
| expect(() => resolveSessionKeyForRequest({ cfg, sessionId: "ownerless-session" })).toThrowError( | |
| expect.objectContaining({ code: "AGENT_SELECTION_REQUIRED" }), | |
| ); | |
| expect(hoisted.listSessionEntriesMock.mock.calls.map(([scope]) => scope?.agentId)).toEqual([ | |
| "research", | |
| "ops", | |
| ]); | |
| }); | |
| it("does not assign an unowned bare key from a session-id scan anchor", () => { | |
| hoisted.listAgentIdsMock.mockReturnValue(["ops", "research"]); | |
| mockSessionStores({ "/stores/ops.json": {}, "/stores/research.json": {} }); | |
| const cfg = { | |
| session: { store: "/stores/{agentId}.json" }, | |
| agents: { | |
| ownership: "explicit", | |
| entries: { ops: {}, research: {} }, | |
| }, | |
| } satisfies OpenClawConfig; | |
| expect(() => | |
| resolveSessionKeyForRequest({ cfg, sessionKey: "global", sessionId: "missing-session" }), | |
| ).toThrowError(expect.objectContaining({ code: "AGENT_SELECTION_REQUIRED" })); | |
| }); | |
| it("resolves an unowned bare key's session id to the matching agent store", () => { | |
| hoisted.listAgentIdsMock.mockReturnValue(["ops", "research"]); | |
| const researchStore = { | |
| "agent:research:work": { sessionId: "research-session", updatedAt: 10 }, | |
| } satisfies Record<string, SessionEntry>; | |
| mockSessionStores({ | |
| "/stores/ops.json": {}, | |
| "/stores/research.json": researchStore, | |
| }); | |
| const cfg = { | |
| session: { store: "/stores/{agentId}.json" }, | |
| agents: { | |
| ownership: "explicit", | |
| entries: { ops: {}, research: {} }, | |
| }, | |
| } satisfies OpenClawConfig; | |
| expect( | |
| resolveSessionKeyForRequest({ | |
| cfg, | |
| sessionKey: "global", | |
| sessionId: "research-session", | |
| }), | |
| ).toMatchObject({ | |
| agentId: "research", | |
| sessionKey: "agent:research:work", | |
| sessionEntry: researchStore["agent:research:work"], | |
| storePath: "/stores/research.json", | |
| }); | |
| }); | |
| it("allows an agent-constrained lookup to own an unscoped shared-store row", () => { | |
| hoisted.listAgentIdsMock.mockReturnValue(["research", "ops"]); | |
| const sharedStore = { | |
| main: { sessionId: "ops-session", updatedAt: 10 }, | |
| } satisfies Record<string, SessionEntry>; | |
| mockSessionStores({ "/stores/shared.sqlite": sharedStore }); | |
| const result = resolveSessionKeyForRequest({ | |
| cfg: { | |
| session: { store: "/stores/shared.sqlite" }, | |
| agents: { | |
| ownership: "explicit", | |
| entries: { research: {}, ops: {} }, | |
| }, | |
| } satisfies OpenClawConfig, | |
| sessionId: "ops-session", | |
| agentId: "ops", | |
| }); | |
| expect(result.agentId).toBe("ops"); | |
| expect(result.sessionKey).toBe("main"); | |
| expect(result.sessionEntry).toEqual(sharedStore.main); | |
| expect(result.storePath).toBe("/stores/shared.sqlite"); | |
| expect(hoisted.listSessionEntriesMock).toHaveBeenCalledTimes(1); | |
| expect(hoisted.listSessionEntriesMock).toHaveBeenCalledWith( | |
| expect.objectContaining({ agentId: "ops" }), | |
| ); | |
| }); | |
| it("rejects an agent-constrained session id owned by another agent", () => { | |
| hoisted.listAgentIdsMock.mockReturnValue(["ops", "research"]); | |
| const sharedStore = { | |
| "agent:research:work": { sessionId: "duplicate-session", updatedAt: 20 }, | |
| "agent:ops:work": { sessionId: "duplicate-session", updatedAt: 10 }, | |
| } satisfies Record<string, SessionEntry>; | |
| mockSessionStores({ "/stores/shared.sqlite": sharedStore }); | |
| const cfg = { | |
| session: { store: "/stores/shared.sqlite" }, | |
| agents: { | |
| ownership: "explicit", | |
| entries: { ops: {}, research: {} }, | |
| }, | |
| } satisfies OpenClawConfig; | |
| expect( | |
| resolveSessionKeyForRequest({ cfg, agentId: "ops", sessionId: "duplicate-session" }), | |
| ).toMatchObject({ agentId: "ops", sessionKey: "agent:ops:work" }); | |
| mockSessionStores({ | |
| "/stores/shared.sqlite": { | |
| "agent:research:work": sharedStore["agent:research:work"], | |
| }, | |
| }); | |
| expect(() => | |
| resolveSessionKeyForRequest({ cfg, agentId: "ops", sessionId: "duplicate-session" }), | |
| ).toThrowError(expect.objectContaining({ code: "AGENT_SELECTION_REQUIRED" })); | |
| }); | |
| it.each(["legacy", "explicit"] as const)( | |
| "creates a missing session-id target under the %s roster's default owner", | |
| (ownership) => { | |
| hoisted.listAgentIdsMock.mockReturnValue(["ops", "research"]); | |
| mockSessionStores({}); | |
| const cfg = retainLegacyDefaultAgentId( | |
| { | |
| session: { store: "/stores/{agentId}.json" }, | |
| agents: { | |
| ...(ownership === "explicit" | |
| ? { ownership, defaults: { systemAgent: { agentId: "research" } } } | |
| : {}), | |
| entries: { ops: {}, research: {} }, | |
| }, | |
| }, | |
| "ops", | |
| ); | |
| const result = resolveSessionKeyForRequest({ cfg, sessionId: "new-session" }); | |
| const expectedAgentId = ownership === "explicit" ? "research" : "ops"; | |
| expect(result.agentId).toBe(expectedAgentId); | |
| expect(result.sessionKey).toBe(`agent:${expectedAgentId}:explicit:new-session`); | |
| expect(result.storePath).toBe(`/stores/${expectedAgentId}.json`); | |
| }, | |
| ); | |
| it.each([undefined, "ops"])( | |
| "fails closed when creating a session-id target without a designation (provenance: %s)", | |
| (retainedOwner) => { | |
| hoisted.listAgentIdsMock.mockReturnValue(["ops", "research"]); | |
| mockSessionStores({}); | |
| const cfg = retainLegacyDefaultAgentId( | |
| { | |
| session: { store: "/stores/{agentId}.json" }, | |
| agents: { | |
| ownership: "explicit", | |
| entries: { ops: {}, research: {} }, | |
| }, | |
| } satisfies OpenClawConfig, | |
| retainedOwner, | |
| ); | |
| expect(() => resolveSessionKeyForRequest({ cfg, sessionId: "new-session" })).toThrowError( | |
| expect.objectContaining({ code: "AGENT_SELECTION_REQUIRED" }), | |
| ); | |
| }, | |
| ); | |
| it("detaches the selected session entry from borrowed store rows", () => { | |
| const mainStore = { | |
| "agent:main:main": { sessionId: "sid", updatedAt: 10 }, | |
| } satisfies Record<string, SessionEntry>; | |
| const otherStore = { | |
| "agent:other:acp:sid": { | |
| sessionId: "sid", | |
| updatedAt: 20, | |
| skillsSnapshot: { prompt: "selected prompt", skills: [] }, | |
| }, | |
| } satisfies Record<string, SessionEntry>; | |
| mockSessionStores({ | |
| "/stores/main.json": mainStore, | |
| "/stores/other.json": otherStore, | |
| }); | |
| const result = resolveSessionKeyForRequestCore({ | |
| cfg: { | |
| session: { | |
| store: "/stores/{agentId}.json", | |
| }, | |
| } satisfies OpenClawConfig, | |
| sessionId: "sid", | |
| }); | |
| expect(result.sessionKey).toBe("agent:other:acp:sid"); | |
| expect(result.sessionEntry).toEqual(otherStore["agent:other:acp:sid"]); | |
| if (!result.sessionEntry?.skillsSnapshot) { | |
| throw new Error("expected the selected session's skills"); | |
| } | |
| result.sessionEntry.skillsSnapshot.prompt = "caller-owned edit"; | |
| expect(otherStore["agent:other:acp:sid"].skillsSnapshot.prompt).toBe("selected prompt"); | |
| expect(hoisted.listSessionEntriesMock).toHaveBeenCalledWith( | |
| expect.objectContaining({ | |
| storePath: "/stores/main.json", | |
| clone: false, | |
| }), | |
| ); | |
| expect(hoisted.listSessionEntriesMock).toHaveBeenCalledWith( | |
| expect.objectContaining({ | |
| storePath: "/stores/other.json", | |
| clone: false, | |
| }), | |
| ); | |
| }); | |
| }); | |