






















@@ -4,6 +4,7 @@ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi }
44import { resolveEmbeddedRunSkillEntries } from "../../agents/pi-embedded-runner/skills-runtime.js";
55import { createCanonicalFixtureSkill } from "../../agents/skills.test-helpers.js";
66import type { Skill } from "../../agents/skills/skill-contract.js";
7+import { hydrateResolvedSkills } from "../../auto-reply/reply/session-updates.js";
78import { createSuiteTempRootTracker } from "../../test-helpers/temp-dir.js";
89import type { SessionEntry, SessionSkillSnapshot } from "./types.js";
910@@ -203,3 +204,69 @@ describe("embedded runner falls back to disk when resolvedSkills is absent", ()
203204expect(result.skillEntries).toEqual([]);
204205});
205206});
207+208+describe("hydrateResolvedSkills", () => {
209+it("returns the same snapshot when resolvedSkills is already populated", () => {
210+const snapshot: SessionSkillSnapshot = {
211+prompt: "p",
212+skills: [{ name: "x" }],
213+resolvedSkills: [makeFixtureSkill("x", 100)],
214+version: 1,
215+};
216+let buildCalls = 0;
217+const result = hydrateResolvedSkills(snapshot, () => {
218+buildCalls += 1;
219+return { prompt: "rebuilt", skills: [], resolvedSkills: [], version: 99 };
220+});
221+expect(result).toBe(snapshot);
222+expect(buildCalls).toBe(0);
223+});
224+225+it("rebuilds resolvedSkills only when missing and preserves persisted fields", () => {
226+// Simulates a cold session resume: the on-disk snapshot has no
227+// resolvedSkills, but consumers like prepareClaudeCliSkillsPlugin still
228+// need them. Hydration must not change prompt/skills/version, so the
229+// model's prompt-cache key stays stable across resume.
230+const stripped: SessionSkillSnapshot = {
231+prompt: "original-prompt",
232+skills: [{ name: "x" }],
233+skillFilter: ["x"],
234+version: 7,
235+};
236+const rebuiltSkills = [makeFixtureSkill("x", 200)];
237+let buildCalls = 0;
238+const result = hydrateResolvedSkills(stripped, () => {
239+buildCalls += 1;
240+return {
241+prompt: "DIFFERENT-PROMPT",
242+skills: [{ name: "y" }],
243+resolvedSkills: rebuiltSkills,
244+version: 99,
245+};
246+});
247+expect(buildCalls).toBe(1);
248+expect(result.prompt).toBe("original-prompt");
249+expect(result.skills).toEqual([{ name: "x" }]);
250+expect(result.skillFilter).toEqual(["x"]);
251+expect(result.version).toBe(7);
252+expect(result.resolvedSkills).toBe(rebuiltSkills);
253+});
254+255+it("hydrates an empty resolvedSkills array as if it were absent is NOT done — empty is treated as populated", () => {
256+// A resolvedSkills set explicitly to [] means the workspace genuinely had
257+// no skills, not that the field was stripped. Don't trigger a rebuild.
258+const snapshot: SessionSkillSnapshot = {
259+prompt: "",
260+skills: [],
261+resolvedSkills: [],
262+version: 1,
263+};
264+let buildCalls = 0;
265+const result = hydrateResolvedSkills(snapshot, () => {
266+buildCalls += 1;
267+return { prompt: "", skills: [], resolvedSkills: [makeFixtureSkill("x")], version: 1 };
268+});
269+expect(result).toBe(snapshot);
270+expect(buildCalls).toBe(0);
271+});
272+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。