






















@@ -1,4 +1,24 @@
11import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2+import type { SessionEntry } from "../../config/sessions.js";
3+4+const TEST_WORKSPACE_DIR = "/tmp/workspace";
5+type TestSkillSnapshot = NonNullable<SessionEntry["skillsSnapshot"]>;
6+7+function strippedSnapshot(skillName = "test"): TestSkillSnapshot {
8+return {
9+prompt: "skills prompt",
10+skills: [{ name: skillName }],
11+version: 0,
12+};
13+}
14+15+function testSessionEntry(sessionId: string, skillsSnapshot: TestSkillSnapshot): SessionEntry {
16+return {
17+ sessionId,
18+updatedAt: Date.now(),
19+ skillsSnapshot,
20+};
21+}
222323const {
424 buildWorkspaceSkillSnapshotMock,
@@ -10,7 +30,11 @@ const {
1030 resolveSessionAgentIdMock,
1131 resolveAgentIdFromSessionKeyMock,
1232} = vi.hoisted(() => ({
13-buildWorkspaceSkillSnapshotMock: vi.fn(() => ({ prompt: "", skills: [], resolvedSkills: [] })),
33+buildWorkspaceSkillSnapshotMock: vi.fn((..._args: unknown[]) => ({
34+prompt: "",
35+skills: [] as unknown[],
36+resolvedSkills: [] as unknown[],
37+})),
1438ensureSkillsWatcherMock: vi.fn(),
1539getSkillsSnapshotVersionMock: vi.fn(() => 0),
1640shouldRefreshSnapshotForVersionMock: vi.fn(() => false),
@@ -55,11 +79,13 @@ vi.mock("../../routing/session-key.js", () => ({
5579resolveAgentIdFromSessionKey: resolveAgentIdFromSessionKeyMock,
5680}));
578158-const { ensureSkillSnapshot } = await import("./session-updates.js");
82+const { ensureSkillSnapshot, __testing_resetResolvedSkillsCache } =
83+await import("./session-updates.js");
59846085describe("ensureSkillSnapshot", () => {
6186beforeEach(() => {
6287vi.clearAllMocks();
88+__testing_resetResolvedSkillsCache();
6389buildWorkspaceSkillSnapshotMock.mockReturnValue({ prompt: "", skills: [], resolvedSkills: [] });
6490getSkillsSnapshotVersionMock.mockReturnValue(0);
6591shouldRefreshSnapshotForVersionMock.mockReturnValue(false);
@@ -83,7 +109,7 @@ describe("ensureSkillSnapshot", () => {
83109await ensureSkillSnapshot({
84110sessionKey: "main",
85111isFirstTurnInSession: false,
86-workspaceDir: "/tmp/workspace",
112+workspaceDir: TEST_WORKSPACE_DIR,
87113cfg: {
88114agents: {
89115list: [{ id: "writer", default: true }],
@@ -102,8 +128,143 @@ describe("ensureSkillSnapshot", () => {
102128expect(buildWorkspaceSkillSnapshotMock).toHaveBeenCalledTimes(1);
103129const [[workspaceDir, snapshotParams]] = buildWorkspaceSkillSnapshotMock.mock
104130.calls as unknown as Array<[string, { agentId?: string }]>;
105-expect(workspaceDir).toBe("/tmp/workspace");
131+expect(workspaceDir).toBe(TEST_WORKSPACE_DIR);
106132expect(snapshotParams.agentId).toBe("writer");
107133expect(resolveAgentIdFromSessionKeyMock).not.toHaveBeenCalled();
108134});
135+136+it("reuses cached resolvedSkills across calls with same workspaceDir/version/filter", async () => {
137+vi.stubEnv("OPENCLAW_TEST_FAST", "0");
138+139+const sessionStore: Record<string, SessionEntry> = {};
140+const sessionKey = "main";
141+const snapshot = strippedSnapshot();
142+const sessionEntry = testSessionEntry("sess-1", snapshot);
143+144+await ensureSkillSnapshot({
145+ sessionEntry,
146+ sessionStore,
147+ sessionKey,
148+isFirstTurnInSession: true,
149+workspaceDir: TEST_WORKSPACE_DIR,
150+cfg: {},
151+});
152+expect(buildWorkspaceSkillSnapshotMock).toHaveBeenCalledTimes(1);
153+154+const sessionEntry2 = testSessionEntry("sess-2", { ...snapshot });
155+await ensureSkillSnapshot({
156+sessionEntry: sessionEntry2,
157+sessionStore: {},
158+sessionKey: "other",
159+isFirstTurnInSession: false,
160+workspaceDir: TEST_WORKSPACE_DIR,
161+cfg: {},
162+});
163+expect(buildWorkspaceSkillSnapshotMock).toHaveBeenCalledTimes(1);
164+});
165+166+it("invalidates cache when skillFilter changes", async () => {
167+vi.stubEnv("OPENCLAW_TEST_FAST", "0");
168+169+const sessionStore: Record<string, SessionEntry> = {};
170+const sessionKey = "main";
171+const snapshot = strippedSnapshot();
172+const sessionEntry = testSessionEntry("sess-1", snapshot);
173+174+await ensureSkillSnapshot({
175+ sessionEntry,
176+ sessionStore,
177+ sessionKey,
178+isFirstTurnInSession: true,
179+workspaceDir: TEST_WORKSPACE_DIR,
180+cfg: {},
181+});
182+expect(buildWorkspaceSkillSnapshotMock).toHaveBeenCalledTimes(1);
183+184+const sessionEntry2 = testSessionEntry("sess-2", {
185+ ...snapshot,
186+skillFilter: ["old-filter"],
187+});
188+await ensureSkillSnapshot({
189+sessionEntry: sessionEntry2,
190+sessionStore: {},
191+sessionKey: "other",
192+isFirstTurnInSession: false,
193+workspaceDir: TEST_WORKSPACE_DIR,
194+skillFilter: ["new-filter"],
195+cfg: {},
196+});
197+expect(buildWorkspaceSkillSnapshotMock).toHaveBeenCalledTimes(2);
198+});
199+200+it("invalidates cache when non-skills config gates change", async () => {
201+vi.stubEnv("OPENCLAW_TEST_FAST", "0");
202+203+buildWorkspaceSkillSnapshotMock.mockImplementation((_workspaceDir, opts) => {
204+const config = (opts as { config?: { channels?: { discord?: { token?: string } } } }).config;
205+return {
206+prompt: "",
207+skills: [],
208+resolvedSkills: config?.channels?.discord?.token ? [{ name: "discord" }] : [],
209+};
210+});
211+212+const snapshot = strippedSnapshot("discord");
213+214+const first = await ensureSkillSnapshot({
215+sessionEntry: testSessionEntry("sess-1", snapshot),
216+sessionStore: {},
217+sessionKey: "main",
218+isFirstTurnInSession: true,
219+workspaceDir: TEST_WORKSPACE_DIR,
220+cfg: { channels: { discord: { token: "enabled" } } },
221+});
222+223+expect(first.skillsSnapshot?.resolvedSkills).toEqual([{ name: "discord" }]);
224+expect(buildWorkspaceSkillSnapshotMock).toHaveBeenCalledTimes(1);
225+226+const second = await ensureSkillSnapshot({
227+sessionEntry: testSessionEntry("sess-2", { ...snapshot }),
228+sessionStore: {},
229+sessionKey: "other",
230+isFirstTurnInSession: false,
231+workspaceDir: TEST_WORKSPACE_DIR,
232+cfg: { channels: { discord: {} } },
233+});
234+235+expect(second.skillsSnapshot?.resolvedSkills).toEqual([]);
236+expect(buildWorkspaceSkillSnapshotMock).toHaveBeenCalledTimes(2);
237+});
238+239+it("redacts secret values in the cache key while preserving eligibility presence", async () => {
240+vi.stubEnv("OPENCLAW_TEST_FAST", "0");
241+242+buildWorkspaceSkillSnapshotMock.mockReturnValue({
243+prompt: "",
244+skills: [],
245+resolvedSkills: [{ name: "discord" }],
246+});
247+248+const snapshot = strippedSnapshot("discord");
249+250+await ensureSkillSnapshot({
251+sessionEntry: testSessionEntry("sess-1", snapshot),
252+sessionStore: {},
253+sessionKey: "main",
254+isFirstTurnInSession: true,
255+workspaceDir: TEST_WORKSPACE_DIR,
256+cfg: { channels: { discord: { token: "first-secret" } } },
257+});
258+259+await ensureSkillSnapshot({
260+sessionEntry: testSessionEntry("sess-2", { ...snapshot }),
261+sessionStore: {},
262+sessionKey: "other",
263+isFirstTurnInSession: false,
264+workspaceDir: TEST_WORKSPACE_DIR,
265+cfg: { channels: { discord: { token: "rotated-secret" } } },
266+});
267+268+expect(buildWorkspaceSkillSnapshotMock).toHaveBeenCalledTimes(1);
269+});
109270});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。