Skip to content

Commit 9aecae2

Browse files
committed
clawdbot-d02.1.9.1.1: route gateway session entry reads through seam
1 parent 897eb03 commit 9aecae2

3 files changed

Lines changed: 58 additions & 45 deletions

File tree

src/gateway/server-methods/sessions.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ import { compactEmbeddedAgentSession } from "../../agents/embedded-agent.js";
4747
import { clearSessionQueues } from "../../auto-reply/reply/queue/cleanup.js";
4848
import { normalizeReasoningLevel, normalizeThinkLevel } from "../../auto-reply/thinking.js";
4949
import {
50-
loadSessionStore,
5150
runSessionsCleanup,
5251
serializeSessionCleanupResult,
5352
resolveMainSessionKey,
@@ -259,6 +258,22 @@ function resolveGatewaySessionTargetFromKey(
259258
return { cfg, target, storePath: target.storePath };
260259
}
261260

261+
function loadSessionEntriesForTarget(params: {
262+
key: string;
263+
cfg: OpenClawConfig;
264+
agentId?: string;
265+
}) {
266+
const target = resolveGatewaySessionStoreTargetWithStore({
267+
cfg: params.cfg,
268+
key: params.key,
269+
clone: false,
270+
...(params.agentId ? { agentId: params.agentId } : {}),
271+
});
272+
const store = target.store;
273+
const entry = resolveFreshestSessionEntryFromStoreKeys(store, target.storeKeys);
274+
return { target, storePath: target.storePath, store, entry };
275+
}
276+
262277
function resolveOptionalInitialSessionMessage(params: {
263278
task?: unknown;
264279
message?: unknown;
@@ -1307,9 +1322,7 @@ export const sessionsHandlers: GatewayRequestHandlers = {
13071322
return;
13081323
}
13091324
const cfg = context.getRuntimeConfig();
1310-
const { target, storePath } = resolveGatewaySessionTargetFromKey(key, cfg);
1311-
const store = loadSessionStore(storePath);
1312-
const entry = resolveFreshestSessionEntryFromStoreKeys(store, target.storeKeys);
1325+
const { target, storePath, store, entry } = loadSessionEntriesForTarget({ key, cfg });
13131326
if (!entry) {
13141327
respond(true, { session: null }, undefined);
13151328
return;
@@ -2512,11 +2525,11 @@ export const sessionsHandlers: GatewayRequestHandlers = {
25122525
respond(false, undefined, requestedAgent.error);
25132526
return;
25142527
}
2515-
const { target, storePath } = resolveGatewaySessionTargetFromKey(key, cfg, {
2528+
const { storePath, entry } = loadSessionEntriesForTarget({
2529+
key,
2530+
cfg,
25162531
agentId: requestedAgent.agentId,
25172532
});
2518-
const store = loadSessionStore(storePath);
2519-
const entry = resolveFreshestSessionEntryFromStoreKeys(store, target.storeKeys);
25202533
if (!entry?.sessionId) {
25212534
respond(true, { messages: [] }, undefined);
25222535
return;

src/gateway/sessions-resolve.test.ts

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ import { ErrorCodes } from "../../packages/gateway-protocol/src/index.js";
33
import type { SessionEntry } from "../config/sessions/types.js";
44

55
const hoisted = vi.hoisted(() => ({
6-
loadSessionStoreMock: vi.fn(),
76
updateSessionStoreMock: vi.fn(),
87
listSessionsFromStoreMock: vi.fn(),
98
migrateAndPruneGatewaySessionStoreKeyMock: vi.fn(),
10-
resolveGatewaySessionStoreTargetMock: vi.fn(),
9+
resolveGatewaySessionStoreTargetWithStoreMock: vi.fn(),
1110
loadCombinedSessionStoreForGatewayMock: vi.fn(),
1211
listAgentIdsMock: vi.fn(),
1312
}));
@@ -27,7 +26,6 @@ vi.mock("../config/sessions.js", async () => {
2726
await vi.importActual<typeof import("../config/sessions.js")>("../config/sessions.js");
2827
return {
2928
...actual,
30-
loadSessionStore: hoisted.loadSessionStoreMock,
3129
updateSessionStore: hoisted.updateSessionStoreMock,
3230
};
3331
});
@@ -38,7 +36,8 @@ vi.mock("./session-utils.js", async () => {
3836
...actual,
3937
listSessionsFromStore: hoisted.listSessionsFromStoreMock,
4038
migrateAndPruneGatewaySessionStoreKey: hoisted.migrateAndPruneGatewaySessionStoreKeyMock,
41-
resolveGatewaySessionStoreTarget: hoisted.resolveGatewaySessionStoreTargetMock,
39+
resolveGatewaySessionStoreTargetWithStore:
40+
hoisted.resolveGatewaySessionStoreTargetWithStoreMock,
4241
loadCombinedSessionStoreForGateway: hoisted.loadCombinedSessionStoreForGatewayMock,
4342
};
4443
});
@@ -49,6 +48,7 @@ describe("resolveSessionKeyFromResolveParams", () => {
4948
const canonicalKey = "agent:main:canon";
5049
const legacyKey = "agent:main:legacy";
5150
const storePath = "/tmp/sessions.json";
51+
let targetStore: Record<string, SessionEntry>;
5252

5353
const expectResolveToCanonicalKey = async (
5454
p: Parameters<typeof resolveSessionKeyFromResolveParams>[0]["p"],
@@ -66,37 +66,33 @@ describe("resolveSessionKeyFromResolveParams", () => {
6666
};
6767

6868
beforeEach(() => {
69-
hoisted.loadSessionStoreMock.mockReset();
7069
hoisted.updateSessionStoreMock.mockReset();
7170
hoisted.listSessionsFromStoreMock.mockReset();
7271
hoisted.migrateAndPruneGatewaySessionStoreKeyMock.mockReset();
73-
hoisted.resolveGatewaySessionStoreTargetMock.mockReset();
72+
hoisted.resolveGatewaySessionStoreTargetWithStoreMock.mockReset();
7473
hoisted.loadCombinedSessionStoreForGatewayMock.mockReset();
7574
hoisted.listAgentIdsMock.mockReset();
75+
targetStore = {};
7676
// Default: all agents are known (main is always present).
7777
hoisted.listAgentIdsMock.mockReturnValue(["main"]);
78-
hoisted.resolveGatewaySessionStoreTargetMock.mockReturnValue({
78+
hoisted.resolveGatewaySessionStoreTargetWithStoreMock.mockImplementation(() => ({
7979
canonicalKey,
8080
storeKeys: [canonicalKey, legacyKey],
8181
storePath,
82-
});
82+
store: targetStore,
83+
}));
8384
hoisted.migrateAndPruneGatewaySessionStoreKeyMock.mockReturnValue({ primaryKey: canonicalKey });
8485
hoisted.updateSessionStoreMock.mockImplementation(
8586
async (_path: string, updater: (store: Record<string, SessionEntry>) => void) => {
86-
const store = hoisted.loadSessionStoreMock.mock.results[0]?.value as
87-
| Record<string, SessionEntry>
88-
| undefined;
89-
if (store) {
90-
updater(store);
91-
}
87+
updater(targetStore);
9288
},
9389
);
9490
});
9591

9692
it("hides canonical keys that fail the spawnedBy visibility filter", async () => {
97-
hoisted.loadSessionStoreMock.mockReturnValue({
93+
targetStore = {
9894
[canonicalKey]: { sessionId: "sess-1", updatedAt: 1 },
99-
});
95+
};
10096
hoisted.listSessionsFromStoreMock.mockReturnValue({ sessions: [] });
10197

10298
await expect(
@@ -129,7 +125,7 @@ describe("resolveSessionKeyFromResolveParams", () => {
129125
updatedAt: now - i,
130126
};
131127
}
132-
hoisted.loadSessionStoreMock.mockReturnValue(store);
128+
targetStore = store;
133129

134130
await expectResolveToCanonicalKey({ key: canonicalKey, spawnedBy: "controller-1" });
135131
});
@@ -138,7 +134,7 @@ describe("resolveSessionKeyFromResolveParams", () => {
138134
const store = {
139135
[legacyKey]: { sessionId: "sess-legacy", spawnedBy: "controller-1", updatedAt: Date.now() },
140136
} satisfies Record<string, SessionEntry>;
141-
hoisted.loadSessionStoreMock.mockImplementation(() => store);
137+
targetStore = store;
142138

143139
await expectResolveToCanonicalKey({ key: canonicalKey, spawnedBy: "controller-1" });
144140

@@ -150,13 +146,14 @@ describe("resolveSessionKeyFromResolveParams", () => {
150146

151147
it("rejects sessions belonging to a deleted agent (key-based lookup)", async () => {
152148
const deletedAgentKey = "agent:deleted-agent:main";
153-
hoisted.resolveGatewaySessionStoreTargetMock.mockReturnValue({
149+
targetStore = {
150+
[deletedAgentKey]: { sessionId: "sess-orphan", updatedAt: 1 },
151+
};
152+
hoisted.resolveGatewaySessionStoreTargetWithStoreMock.mockReturnValue({
154153
canonicalKey: deletedAgentKey,
155154
storeKeys: [deletedAgentKey],
156155
storePath,
157-
});
158-
hoisted.loadSessionStoreMock.mockReturnValue({
159-
[deletedAgentKey]: { sessionId: "sess-orphan", updatedAt: 1 },
156+
store: targetStore,
160157
});
161158
// "deleted-agent" is not in the known agents list.
162159
hoisted.listAgentIdsMock.mockReturnValue(["main"]);
@@ -177,13 +174,14 @@ describe("resolveSessionKeyFromResolveParams", () => {
177174

178175
it("rejects non-alias agent:main sessions when main is no longer configured", async () => {
179176
const staleMainKey = "agent:main:guildchat:direct:u1";
180-
hoisted.resolveGatewaySessionStoreTargetMock.mockReturnValue({
177+
targetStore = {
178+
[staleMainKey]: { sessionId: "sess-stale-main", updatedAt: 1 },
179+
};
180+
hoisted.resolveGatewaySessionStoreTargetWithStoreMock.mockReturnValue({
181181
canonicalKey: staleMainKey,
182182
storeKeys: [staleMainKey],
183183
storePath,
184-
});
185-
hoisted.loadSessionStoreMock.mockReturnValue({
186-
[staleMainKey]: { sessionId: "sess-stale-main", updatedAt: 1 },
184+
store: targetStore,
187185
});
188186
hoisted.listAgentIdsMock.mockReturnValue(["ops"]);
189187

src/gateway/sessions-resolve.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
errorShape,
66
type SessionsResolveParams,
77
} from "../../packages/gateway-protocol/src/index.js";
8-
import { loadSessionStore, updateSessionStore, type SessionEntry } from "../config/sessions.js";
8+
import { updateSessionStore, type SessionEntry } from "../config/sessions.js";
99
import type { OpenClawConfig } from "../config/types.openclaw.js";
1010
import { resolveSessionIdMatchSelection } from "../sessions/session-id-resolution.js";
1111
import { parseSessionLabel } from "../sessions/session-label.js";
@@ -15,7 +15,7 @@ import {
1515
loadCombinedSessionStoreForGateway,
1616
migrateAndPruneGatewaySessionStoreKey,
1717
resolveDeletedAgentIdFromSessionKey,
18-
resolveGatewaySessionStoreTarget,
18+
resolveGatewaySessionStoreTargetWithStore,
1919
} from "./session-utils.js";
2020

2121
export type SessionsResolveResult = { ok: true; key: string } | { ok: false; error: ErrorShape };
@@ -57,8 +57,7 @@ function validateSessionAgentExists(
5757
function isResolvedSessionKeyVisible(params: {
5858
cfg: OpenClawConfig;
5959
p: SessionsResolveParams;
60-
storePath: string;
61-
store: ReturnType<typeof loadSessionStore>;
60+
store: Record<string, SessionEntry>;
6261
key: string;
6362
}) {
6463
if (typeof params.p.spawnedBy !== "string" || params.p.spawnedBy.trim().length === 0) {
@@ -119,14 +118,13 @@ export async function resolveSessionKeyFromResolveParams(params: {
119118
}
120119

121120
if (hasKey) {
122-
const target = resolveGatewaySessionStoreTarget({ cfg, key });
123-
const store = loadSessionStore(target.storePath);
121+
const target = resolveGatewaySessionStoreTargetWithStore({ cfg, key, clone: false });
122+
const store = target.store;
124123
if (store[target.canonicalKey]) {
125124
if (
126125
!isResolvedSessionKeyVisible({
127126
cfg,
128127
p,
129-
storePath: target.storePath,
130128
store,
131129
key: target.canonicalKey,
132130
})
@@ -149,22 +147,26 @@ export async function resolveSessionKeyFromResolveParams(params: {
149147
s[primaryKey] = s[legacyKey];
150148
}
151149
});
150+
const refreshedTarget = resolveGatewaySessionStoreTargetWithStore({
151+
cfg,
152+
key: target.canonicalKey,
153+
clone: false,
154+
});
152155
if (
153156
!isResolvedSessionKeyVisible({
154157
cfg,
155158
p,
156-
storePath: target.storePath,
157-
store: loadSessionStore(target.storePath),
158-
key: target.canonicalKey,
159+
store: refreshedTarget.store,
160+
key: refreshedTarget.canonicalKey,
159161
})
160162
) {
161163
return noSessionFoundResult(key);
162164
}
163-
const agentCheckLegacy = validateSessionAgentExists(cfg, target.canonicalKey);
165+
const agentCheckLegacy = validateSessionAgentExists(cfg, refreshedTarget.canonicalKey);
164166
if (agentCheckLegacy) {
165167
return agentCheckLegacy;
166168
}
167-
return { ok: true, key: target.canonicalKey };
169+
return { ok: true, key: refreshedTarget.canonicalKey };
168170
}
169171

170172
if (hasSessionId) {

0 commit comments

Comments
 (0)