Skip to content

Commit 10b26fd

Browse files
committed
refactor(path3): narrow bundled session runtime barrels
1 parent 4ae0a5d commit 10b26fd

11 files changed

Lines changed: 143 additions & 28 deletions

extensions/feishu/runtime-api.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,7 @@ export {
4343
filterSupplementalContextItems,
4444
resolveChannelContextVisibilityMode,
4545
} from "openclaw/plugin-sdk/context-visibility-runtime";
46-
export {
47-
loadSessionStore,
48-
resolveSessionStoreEntry,
49-
} from "openclaw/plugin-sdk/session-store-runtime";
46+
export { getSessionEntry } from "openclaw/plugin-sdk/session-store-runtime";
5047
export { readJsonFileWithFallback } from "openclaw/plugin-sdk/json-store";
5148
export { normalizeAgentId } from "openclaw/plugin-sdk/routing";
5249
export { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking";

extensions/feishu/src/bot-runtime-api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ export {
1010
filterSupplementalContextItems,
1111
normalizeAgentId,
1212
} from "../runtime-api.js";
13-
export { loadSessionStore, resolveSessionStoreEntry } from "../runtime-api.js";
13+
export { getSessionEntry } from "../runtime-api.js";

extensions/feishu/src/reasoning-preview.test.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ import { afterAll, beforeEach, describe, expect, it, vi } from "vitest";
33
import type { ClawdbotConfig } from "./bot-runtime-api.js";
44
import { resolveFeishuReasoningPreviewEnabled } from "./reasoning-preview.js";
55

6-
const { loadSessionStoreMock } = vi.hoisted(() => ({
7-
loadSessionStoreMock: vi.fn(),
6+
const { getSessionEntryMock } = vi.hoisted(() => ({
7+
getSessionEntryMock: vi.fn(),
88
}));
99

1010
vi.mock("./bot-runtime-api.js", async () => {
1111
const actual =
1212
await vi.importActual<typeof import("./bot-runtime-api.js")>("./bot-runtime-api.js");
1313
return {
1414
...actual,
15-
loadSessionStore: loadSessionStoreMock,
15+
getSessionEntry: getSessionEntryMock,
1616
};
1717
});
1818

@@ -29,9 +29,12 @@ describe("resolveFeishuReasoningPreviewEnabled", () => {
2929
});
3030

3131
it("enables previews only for stream reasoning sessions", () => {
32-
loadSessionStoreMock.mockReturnValue({
33-
"agent:main:feishu:dm:ou_sender_1": { reasoningLevel: "stream" },
34-
"agent:main:feishu:dm:ou_sender_2": { reasoningLevel: "on" },
32+
getSessionEntryMock.mockImplementation(({ sessionKey }) => {
33+
const entries = {
34+
"agent:main:feishu:dm:ou_sender_1": { reasoningLevel: "stream" },
35+
"agent:main:feishu:dm:ou_sender_2": { reasoningLevel: "on" },
36+
};
37+
return entries[sessionKey as keyof typeof entries];
3538
});
3639

3740
expect(
@@ -50,10 +53,15 @@ describe("resolveFeishuReasoningPreviewEnabled", () => {
5053
sessionKey: "agent:main:feishu:dm:ou_sender_2",
5154
}),
5255
).toBe(false);
56+
expect(getSessionEntryMock).toHaveBeenCalledWith({
57+
storePath: "/tmp/feishu-sessions.json",
58+
sessionKey: "agent:main:feishu:dm:ou_sender_1",
59+
readConsistency: "latest",
60+
});
5361
});
5462

5563
it("returns false for missing sessions or load failures", () => {
56-
loadSessionStoreMock.mockImplementationOnce(() => {
64+
getSessionEntryMock.mockImplementationOnce(() => {
5765
throw new Error("disk unavailable");
5866
});
5967

@@ -75,9 +83,12 @@ describe("resolveFeishuReasoningPreviewEnabled", () => {
7583
});
7684

7785
it("falls back to configured stream defaults", () => {
78-
loadSessionStoreMock.mockReturnValue({
79-
"agent:main:feishu:dm:ou_sender_1": {},
80-
"agent:main:feishu:dm:ou_sender_2": { reasoningLevel: "off" },
86+
getSessionEntryMock.mockImplementation(({ sessionKey }) => {
87+
const entries = {
88+
"agent:main:feishu:dm:ou_sender_1": {},
89+
"agent:main:feishu:dm:ou_sender_2": { reasoningLevel: "off" },
90+
};
91+
return entries[sessionKey as keyof typeof entries];
8192
});
8293

8394
const cfg: ClawdbotConfig = {

extensions/feishu/src/reasoning-preview.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Feishu plugin module implements reasoning preview behavior.
22
import { resolveFeishuConfigReasoningDefault } from "./agent-config.js";
3-
import { loadSessionStore, resolveSessionStoreEntry } from "./bot-runtime-api.js";
3+
import { getSessionEntry } from "./bot-runtime-api.js";
44
import type { ClawdbotConfig } from "./bot-runtime-api.js";
55

66
export function resolveFeishuReasoningPreviewEnabled(params: {
@@ -16,9 +16,11 @@ export function resolveFeishuReasoningPreviewEnabled(params: {
1616
}
1717

1818
try {
19-
const store = loadSessionStore(params.storePath, { skipCache: true });
20-
const level = resolveSessionStoreEntry({ store, sessionKey: params.sessionKey }).existing
21-
?.reasoningLevel;
19+
const level = getSessionEntry({
20+
storePath: params.storePath,
21+
sessionKey: params.sessionKey,
22+
readConsistency: "latest",
23+
})?.reasoningLevel;
2224
if (level === "on" || level === "stream" || level === "off") {
2325
return level === "stream";
2426
}

extensions/mattermost/runtime-api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export {
4646
warnMissingProviderGroupPolicyFallbackOnce,
4747
} from "openclaw/plugin-sdk/runtime-group-policy";
4848
export { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
49-
export { loadSessionStore, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
49+
export { resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
5050
export { formatInboundFromLabel } from "openclaw/plugin-sdk/channel-inbound";
5151
export { logInboundDrop } from "openclaw/plugin-sdk/channel-inbound";
5252
export { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";

extensions/mattermost/src/mattermost/model-picker.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,4 +214,66 @@ describe("Mattermost model picker", () => {
214214
fs.rmSync(testDir, { recursive: true, force: true });
215215
}
216216
});
217+
218+
it("resolves current and parent model overrides from targeted session entries", () => {
219+
const testDir = fs.mkdtempSync(path.join(os.tmpdir(), "mm-model-picker-"));
220+
try {
221+
const storePath = path.join(testDir, "{agentId}.json");
222+
const supportStorePath = path.join(testDir, "support.json");
223+
const parentSessionKey = "agent:support:mattermost:default:channel-1";
224+
const childSessionKey = `${parentSessionKey}:thread:root-1`;
225+
const directSessionKey = "agent:support:mattermost:default:direct-1";
226+
fs.writeFileSync(
227+
supportStorePath,
228+
JSON.stringify(
229+
{
230+
[parentSessionKey]: {
231+
providerOverride: "anthropic",
232+
modelOverride: "claude-sonnet-4-5",
233+
sessionId: "parent-session",
234+
},
235+
[childSessionKey]: {
236+
parentSessionKey,
237+
sessionId: "child-session",
238+
},
239+
[directSessionKey]: {
240+
providerOverride: "openai",
241+
modelOverride: "gpt-5",
242+
sessionId: "direct-session",
243+
},
244+
},
245+
null,
246+
2,
247+
),
248+
);
249+
const cfg: OpenClawConfig = {
250+
session: {
251+
store: storePath,
252+
},
253+
};
254+
255+
expect(
256+
resolveMattermostModelPickerCurrentModel({
257+
cfg,
258+
route: {
259+
agentId: "support",
260+
sessionKey: directSessionKey,
261+
},
262+
data,
263+
}),
264+
).toBe("openai/gpt-5");
265+
expect(
266+
resolveMattermostModelPickerCurrentModel({
267+
cfg,
268+
route: {
269+
agentId: "support",
270+
sessionKey: childSessionKey,
271+
},
272+
data,
273+
}),
274+
).toBe("anthropic/claude-sonnet-4-5");
275+
} finally {
276+
fs.rmSync(testDir, { recursive: true, force: true });
277+
}
278+
});
217279
});

extensions/mattermost/src/mattermost/model-picker.ts

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
88
import { parseStrictInteger } from "openclaw/plugin-sdk/number-runtime";
99
import { normalizeProviderId } from "openclaw/plugin-sdk/provider-model-shared";
10-
import { loadSessionStore, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
10+
import { parseThreadSessionSuffix } from "openclaw/plugin-sdk/routing";
11+
import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
1112
import {
1213
normalizeOptionalString,
1314
normalizeStringifiedOptionalString,
@@ -39,6 +40,8 @@ type MattermostModelPickerRenderedView = {
3940
buttons: MattermostInteractiveButtonInput[][];
4041
};
4142

43+
type MattermostModelPickerSessionEntry = ReturnType<typeof getSessionEntry>;
44+
4245
function splitModelRef(modelRef?: string | null): { provider: string; model: string } | null {
4346
const trimmed = normalizeOptionalString(modelRef);
4447
const match = trimmed?.match(/^([^/]+)\/(.+)$/u);
@@ -77,6 +80,24 @@ function normalizePage(value: number | undefined): number {
7780
return Math.max(1, Math.floor(value as number));
7881
}
7982

83+
function resolveMattermostModelPickerParentSessionKey(params: {
84+
sessionEntry: MattermostModelPickerSessionEntry;
85+
sessionKey: string;
86+
}): string | undefined {
87+
// Preserve inherited model overrides without exposing whole-store reads to the UI path.
88+
const persistedParent =
89+
typeof params.sessionEntry?.parentSessionKey === "string"
90+
? params.sessionEntry.parentSessionKey.trim()
91+
: "";
92+
if (persistedParent && persistedParent !== params.sessionKey) {
93+
return persistedParent;
94+
}
95+
const parsed = parseThreadSessionSuffix(params.sessionKey);
96+
return parsed.threadId && parsed.baseSessionKey && parsed.baseSessionKey !== params.sessionKey
97+
? parsed.baseSessionKey
98+
: undefined;
99+
}
100+
80101
function paginateItems<T>(items: T[], page?: number, pageSize = MODELS_PAGE_SIZE) {
81102
const totalPages = Math.max(1, Math.ceil(items.length / pageSize));
82103
const safePage = Math.max(1, Math.min(normalizePage(page), totalPages));
@@ -244,14 +265,31 @@ export function resolveMattermostModelPickerCurrentModel(params: {
244265
const storePath = resolveStorePath(params.cfg.session?.store, {
245266
agentId: params.route.agentId,
246267
});
247-
const sessionStore = params.skipCache
248-
? loadSessionStore(storePath, { skipCache: true })
249-
: loadSessionStore(storePath);
250-
const sessionEntry = sessionStore[params.route.sessionKey];
268+
const readOptions = {
269+
storePath,
270+
...(params.skipCache ? { readConsistency: "latest" as const } : {}),
271+
};
272+
const sessionEntry = getSessionEntry({
273+
...readOptions,
274+
sessionKey: params.route.sessionKey,
275+
});
276+
const parentSessionKey = resolveMattermostModelPickerParentSessionKey({
277+
sessionEntry,
278+
sessionKey: params.route.sessionKey,
279+
});
280+
const parentEntry = parentSessionKey
281+
? getSessionEntry({
282+
...readOptions,
283+
sessionKey: parentSessionKey,
284+
})
285+
: undefined;
251286
const override = resolveStoredModelOverride({
252287
sessionEntry,
253-
sessionStore,
288+
...(parentEntry && parentSessionKey
289+
? { sessionStore: { [parentSessionKey]: parentEntry } }
290+
: {}),
254291
sessionKey: params.route.sessionKey,
292+
parentSessionKey,
255293
defaultProvider: params.data.resolvedDefault.provider,
256294
});
257295
if (!override?.model) {

extensions/mattermost/src/runtime-api.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export {
3636
isTrustedProxyAddress,
3737
listSkillCommandsForAgents,
3838
loadOutboundMediaFromUrl,
39-
loadSessionStore,
4039
logInboundDrop,
4140
logTypingFailure,
4241
migrateBaseNameToDefaultAccount,

scripts/check-session-accessor-boundary.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,9 @@ export const migratedSessionAccessorFiles = new Set([
127127
export const migratedBundledPluginSessionAccessorFiles = new Set([
128128
"extensions/discord/src/monitor/native-command-model-picker-apply.ts",
129129
"extensions/discord/src/monitor/thread-session-close.ts",
130+
"extensions/feishu/src/reasoning-preview.ts",
130131
"extensions/memory-core/src/dreaming-narrative.ts",
132+
"extensions/mattermost/src/mattermost/model-picker.ts",
131133
"extensions/telegram/src/bot-handlers.runtime.ts",
132134
]);
133135

src/plugin-sdk/session-store-runtime.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Narrow session-store helpers for channel hot paths.
22

3+
import { resolveStorePath as resolveSessionStorePath } from "../config/sessions/paths.js";
34
import {
45
cleanupSessionLifecycleArtifacts as cleanupAccessorSessionLifecycleArtifacts,
56
listSessionEntries as listAccessorSessionEntries,
@@ -10,7 +11,6 @@ import {
1011
type SessionAccessScope,
1112
updateSessionEntry,
1213
} from "../config/sessions/session-accessor.js";
13-
import { resolveStorePath as resolveSessionStorePath } from "../config/sessions/paths.js";
1414
import { loadSessionStore as loadSessionStoreImpl } from "../config/sessions/store-load.js";
1515
import type { ResolvedSessionMaintenanceConfig } from "../config/sessions/store.js";
1616
import type { SessionEntry } from "../config/sessions/types.js";
@@ -19,6 +19,7 @@ type SessionStoreReadParams = {
1919
agentId?: string;
2020
env?: NodeJS.ProcessEnv;
2121
hydrateSkillPromptRefs?: boolean;
22+
readConsistency?: "latest";
2223
sessionKey: string;
2324
storePath?: string;
2425
};
@@ -89,6 +90,7 @@ function toSessionAccessScope(params: SessionStoreReadParams): SessionAccessScop
8990
...(params.hydrateSkillPromptRefs !== undefined
9091
? { hydrateSkillPromptRefs: params.hydrateSkillPromptRefs }
9192
: {}),
93+
...(params.readConsistency !== undefined ? { readConsistency: params.readConsistency } : {}),
9294
...(params.storePath !== undefined ? { storePath: params.storePath } : {}),
9395
};
9496
}

0 commit comments

Comments
 (0)