diff --git a/src/agents/pi-embedded-runner/compact.ts b/src/agents/pi-embedded-runner/compact.ts index 6c753e9d723..bd85515584c 100644 --- a/src/agents/pi-embedded-runner/compact.ts +++ b/src/agents/pi-embedded-runner/compact.ts @@ -520,18 +520,20 @@ export async function compactEmbeddedPiSessionDirect( let restoreSkillEnv: (() => void) | undefined; process.chdir(effectiveWorkspace); try { + const preferWorkspaceSkillPaths = effectiveWorkspace !== resolvedWorkspace; const { shouldLoadSkillEntries, skillEntries } = resolveEmbeddedRunSkillEntries({ workspaceDir: effectiveWorkspace, config: params.config, skillsSnapshot: params.skillsSnapshot, + preferWorkspaceEntries: preferWorkspaceSkillPaths, }); - restoreSkillEnv = params.skillsSnapshot - ? applySkillEnvOverridesFromSnapshot({ - snapshot: params.skillsSnapshot, + restoreSkillEnv = shouldLoadSkillEntries + ? applySkillEnvOverrides({ + skills: skillEntries ?? [], config: params.config, }) - : applySkillEnvOverrides({ - skills: skillEntries ?? [], + : applySkillEnvOverridesFromSnapshot({ + snapshot: params.skillsSnapshot, config: params.config, }); const skillsPrompt = resolveSkillsPromptForRun({ @@ -539,6 +541,7 @@ export async function compactEmbeddedPiSessionDirect( entries: shouldLoadSkillEntries ? skillEntries : undefined, config: params.config, workspaceDir: effectiveWorkspace, + preferEntries: preferWorkspaceSkillPaths, }); const sessionLabel = params.sessionKey ?? params.sessionId; diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index 71db23d0f5b..b11a8b1784f 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -1423,18 +1423,20 @@ export async function runEmbeddedAttempt( let restoreSkillEnv: (() => void) | undefined; process.chdir(effectiveWorkspace); try { + const preferWorkspaceSkillPaths = effectiveWorkspace !== resolvedWorkspace; const { shouldLoadSkillEntries, skillEntries } = resolveEmbeddedRunSkillEntries({ workspaceDir: effectiveWorkspace, config: params.config, skillsSnapshot: params.skillsSnapshot, + preferWorkspaceEntries: preferWorkspaceSkillPaths, }); - restoreSkillEnv = params.skillsSnapshot - ? applySkillEnvOverridesFromSnapshot({ - snapshot: params.skillsSnapshot, + restoreSkillEnv = shouldLoadSkillEntries + ? applySkillEnvOverrides({ + skills: skillEntries ?? [], config: params.config, }) - : applySkillEnvOverrides({ - skills: skillEntries ?? [], + : applySkillEnvOverridesFromSnapshot({ + snapshot: params.skillsSnapshot, config: params.config, }); @@ -1443,6 +1445,7 @@ export async function runEmbeddedAttempt( entries: shouldLoadSkillEntries ? skillEntries : undefined, config: params.config, workspaceDir: effectiveWorkspace, + preferEntries: preferWorkspaceSkillPaths, }); const sessionLabel = params.sessionKey ?? params.sessionId; diff --git a/src/agents/pi-embedded-runner/skills-runtime.test.ts b/src/agents/pi-embedded-runner/skills-runtime.test.ts index 516d96d8b8f..172247be152 100644 --- a/src/agents/pi-embedded-runner/skills-runtime.test.ts +++ b/src/agents/pi-embedded-runner/skills-runtime.test.ts @@ -67,4 +67,25 @@ describe("resolveEmbeddedRunSkillEntries", () => { }); expect(hoisted.loadWorkspaceSkillEntries).not.toHaveBeenCalled(); }); + + it("reloads skill entries when sandbox execution prefers workspace-local skill paths", () => { + const snapshot: SkillSnapshot = { + prompt: "skills prompt", + skills: [{ name: "diffs" }], + resolvedSkills: [], + }; + + const result = resolveEmbeddedRunSkillEntries({ + workspaceDir: "/tmp/workspace-sandbox", + config: {}, + skillsSnapshot: snapshot, + preferWorkspaceEntries: true, + }); + + expect(result.shouldLoadSkillEntries).toBe(true); + expect(hoisted.loadWorkspaceSkillEntries).toHaveBeenCalledTimes(1); + expect(hoisted.loadWorkspaceSkillEntries).toHaveBeenCalledWith("/tmp/workspace-sandbox", { + config: {}, + }); + }); }); diff --git a/src/agents/pi-embedded-runner/skills-runtime.ts b/src/agents/pi-embedded-runner/skills-runtime.ts index 3f3d138e6ae..1999b3a90eb 100644 --- a/src/agents/pi-embedded-runner/skills-runtime.ts +++ b/src/agents/pi-embedded-runner/skills-runtime.ts @@ -5,11 +5,15 @@ export function resolveEmbeddedRunSkillEntries(params: { workspaceDir: string; config?: OpenClawConfig; skillsSnapshot?: SkillSnapshot; + preferWorkspaceEntries?: boolean; }): { shouldLoadSkillEntries: boolean; skillEntries: SkillEntry[]; } { - const shouldLoadSkillEntries = !params.skillsSnapshot || !params.skillsSnapshot.resolvedSkills; + const shouldLoadSkillEntries = + params.preferWorkspaceEntries || + !params.skillsSnapshot || + !params.skillsSnapshot.resolvedSkills; return { shouldLoadSkillEntries, skillEntries: shouldLoadSkillEntries diff --git a/src/agents/skills.resolveskillspromptforrun.test.ts b/src/agents/skills.resolveskillspromptforrun.test.ts index 305e11f2f4e..19fa86d2aa4 100644 --- a/src/agents/skills.resolveskillspromptforrun.test.ts +++ b/src/agents/skills.resolveskillspromptforrun.test.ts @@ -29,4 +29,27 @@ describe("resolveSkillsPromptForRun", () => { expect(prompt).toContain(""); expect(prompt).toContain("/app/skills/demo-skill/SKILL.md"); }); + + it("prefers workspace-built prompt when sandbox execution overrides snapshot paths", () => { + const entry: SkillEntry = { + skill: { + name: "demo-skill", + description: "Demo", + filePath: "/sandbox/workspace/skills/demo-skill/SKILL.md", + baseDir: "/sandbox/workspace/skills/demo-skill", + source: "workspace", + disableModelInvocation: false, + }, + frontmatter: {}, + }; + const prompt = resolveSkillsPromptForRun({ + skillsSnapshot: { prompt: "SNAPSHOT", skills: [] }, + entries: [entry], + workspaceDir: "/sandbox/workspace", + preferEntries: true, + }); + + expect(prompt).toContain("/sandbox/workspace/skills/demo-skill/SKILL.md"); + expect(prompt).not.toBe("SNAPSHOT"); + }); }); diff --git a/src/agents/skills/workspace.ts b/src/agents/skills/workspace.ts index 80624a30139..56654d0c214 100644 --- a/src/agents/skills/workspace.ts +++ b/src/agents/skills/workspace.ts @@ -696,9 +696,10 @@ export function resolveSkillsPromptForRun(params: { entries?: SkillEntry[]; config?: OpenClawConfig; workspaceDir: string; + preferEntries?: boolean; }): string { const snapshotPrompt = params.skillsSnapshot?.prompt?.trim(); - if (snapshotPrompt) { + if (snapshotPrompt && !params.preferEntries) { return snapshotPrompt; } if (params.entries && params.entries.length > 0) {