From b0b64da5f90ddf40a8b95f82a10379017113a3d7 Mon Sep 17 00:00:00 2001 From: kumarabhirup Date: Thu, 19 Feb 2026 21:51:41 -0800 Subject: [PATCH] skills: track workspaceDir in snapshot and fix path substitution for profile workspaces --- src/agents/skills/types.ts | 2 ++ src/agents/skills/workspace.ts | 21 +++++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/agents/skills/types.ts b/src/agents/skills/types.ts index 8d02ea6c64c..043f6f4be77 100644 --- a/src/agents/skills/types.ts +++ b/src/agents/skills/types.ts @@ -94,4 +94,6 @@ export type SkillSnapshot = { /** Skills with `inject: true` whose full content should be included in the system prompt. */ injectedSkills?: InjectedSkillContent[]; version?: number; + /** Workspace dir this snapshot was built for (used to invalidate on profile switch). */ + workspaceDir?: string; }; diff --git a/src/agents/skills/workspace.ts b/src/agents/skills/workspace.ts index 77ae2ce1a60..6ce5c859b2d 100644 --- a/src/agents/skills/workspace.ts +++ b/src/agents/skills/workspace.ts @@ -265,12 +265,28 @@ export function buildWorkspaceSkillSnapshot( const remoteNote = opts?.eligibility?.remote?.note?.trim(); const prompt = [remoteNote, formatSkillsForPrompt(resolvedSkills)].filter(Boolean).join("\n"); - // Read full content of injected skills, substituting workspace path placeholders + // Read full content of injected skills, substituting workspace path placeholders. + // We replace both the tilde form and the expanded default path to handle + // cases where the replacement target is a profile-specific workspace dir. + // + // Use regex with a negative lookahead so "~/.openclaw/workspace" doesn't + // match inside "~/.openclaw/workspace-", which would double the + // profile suffix (e.g. workspace-kumareth → workspace-kumareth-kumareth). + const defaultExpandedWorkspace = resolveUserPath("~/.openclaw/workspace"); + const escapeRegex = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const tildePattern = new RegExp(escapeRegex("~/.openclaw/workspace") + "(?![\\w-])", "g"); const injectedSkills: InjectedSkillContent[] = []; for (const entry of injectedEntries) { const rawContent = readSkillContent(entry.skill.filePath); if (rawContent) { - const content = rawContent.replaceAll("~/.openclaw/workspace", workspaceDir); + let content = rawContent.replace(tildePattern, workspaceDir); + if (workspaceDir !== defaultExpandedWorkspace) { + const expandedPattern = new RegExp( + escapeRegex(defaultExpandedWorkspace) + "(?![\\w-])", + "g", + ); + content = content.replace(expandedPattern, workspaceDir); + } injectedSkills.push({ name: entry.skill.name, content }); } } @@ -284,6 +300,7 @@ export function buildWorkspaceSkillSnapshot( resolvedSkills, injectedSkills: injectedSkills.length > 0 ? injectedSkills : undefined, version: opts?.snapshotVersion, + workspaceDir, }; }