@@ -13,7 +13,7 @@ import { walkDirectorySync } from "../../infra/fs-safe.js";
1313import { resolveOsHomeDir } from "../../infra/home-dir.js" ;
1414import { isPathInside } from "../../infra/path-guards.js" ;
1515import { createSubsystemLogger } from "../../logging/subsystem.js" ;
16- import { CONFIG_DIR , resolveHomeDir , resolveUserPath } from "../../utils.js" ;
16+ import { CONFIG_DIR , resolveConfigDir , resolveUserPath } from "../../utils.js" ;
1717import {
1818 resolveEffectiveAgentSkillFilter ,
1919 resolveEffectiveAgentSkillsLimits ,
@@ -27,6 +27,7 @@ import type {
2727 SkillEntry ,
2828 SkillSnapshot ,
2929} from "../types.js" ;
30+ import { WORKSPACE_SKILLS_PROMPT_FORMAT_VERSION } from "../types.js" ;
3031import { resolveBundledSkillsDir } from "./bundled-dir.js" ;
3132import { resolveBundledAllowlist , shouldIncludeSkill } from "./config.js" ;
3233import { resolveOpenClawMetadata , resolveSkillInvocationPolicy } from "./frontmatter.js" ;
@@ -42,12 +43,11 @@ const SKILL_SOURCE_ORIGIN_RELATIVE_PATH = path.join(".openclaw", "source-origin.
4243const MAX_SKILL_SOURCE_ORIGIN_BYTES = 16 * 1024 ;
4344
4445/**
45- * Replace the user's home directory prefix with `~` in skill file paths
46- * to reduce system prompt token usage. Models understand `~` expansion,
47- * and the read tool resolves `~` to the home directory.
46+ * Replace OS home directory prefixes with `~` in skill file paths to
47+ * reduce system prompt token usage while matching host file-tool expansion.
4848 *
4949 * Example: `/Users/alice/.bun/.../skills/github/SKILL.md`
50- * → `~/.bun/.../skills/github/SKILL.md`
50+ * → `~/.bun/.../skills/github/SKILL.md`
5151 *
5252 * Saves ~5–6 tokens per skill path × N skills ≈ 400–600 tokens total.
5353 */
@@ -64,7 +64,7 @@ function resolveNativeUserHomeDir(): string | undefined {
6464}
6565
6666function resolveCompactHomePrefixes ( ) : string [ ] {
67- const homes = [ resolveHomeDir ( ) , resolveUserHomeDir ( ) , resolveNativeUserHomeDir ( ) ] . filter (
67+ const homes = [ resolveUserHomeDir ( ) , resolveNativeUserHomeDir ( ) ] . filter (
6868 ( home ) : home is string => Boolean ( home ) ,
6969 ) ;
7070 const resolvedHomes = homes . map ( ( home ) => path . resolve ( home ) ) ;
@@ -79,12 +79,66 @@ function compactSkillPaths(skills: Skill[]): Skill[] {
7979 if ( homes . length === 0 ) {
8080 return skills ;
8181 }
82+ const preservedRoots = resolvePreservedPromptSkillPathRoots ( ) ;
83+ const tildeRoots = resolvePromptTildeRoots ( ) ;
8284 return skills . map ( ( s ) => ( {
8385 ...s ,
84- filePath : compactHomePath ( s . filePath , homes ) ,
86+ filePath : shouldPreservePromptSkillPath ( s . filePath , preservedRoots , tildeRoots )
87+ ? s . filePath
88+ : compactHomePath ( s . filePath , homes ) ,
8589 } ) ) ;
8690}
8791
92+ function resolvePreservedPromptSkillPathRoots ( ) : string [ ] {
93+ const configDir = resolveConfigDir ( ) ;
94+ const promptSkillDirs = [
95+ path . resolve ( configDir , "skills" ) ,
96+ path . resolve ( configDir , "plugin-skills" ) ,
97+ ] ;
98+ const realPromptSkillDirs = promptSkillDirs
99+ . map ( ( dir ) => tryRealpath ( dir ) )
100+ . filter ( ( dir ) : dir is string => Boolean ( dir ) ) ;
101+ return uniqueStrings ( [ ...promptSkillDirs , ...realPromptSkillDirs ] ) ;
102+ }
103+
104+ function resolvePromptTildeRoots ( ) : string [ ] {
105+ const nativeHome = resolveNativeUserHomeDir ( ) ;
106+ if ( ! nativeHome ) {
107+ return [ ] ;
108+ }
109+ const resolvedNativeHome = path . resolve ( nativeHome ) ;
110+ if ( isContainerStateHomeWherePromptTildeEscapes ( resolvedNativeHome ) ) {
111+ return [ ] ;
112+ }
113+ const realNativeHome = tryRealpath ( resolvedNativeHome ) ;
114+ return uniqueStrings ( [
115+ resolvedNativeHome ,
116+ ...( realNativeHome ? [ realNativeHome ] : [ ] ) ,
117+ ] ) ;
118+ }
119+
120+ function isContainerStateHomeWherePromptTildeEscapes ( home : string ) : boolean {
121+ const configDir = path . resolve ( resolveConfigDir ( ) ) ;
122+ return home === "/data" && ( configDir === "/data/.openclaw" || isPathInside ( "/data/.openclaw" , configDir ) ) ;
123+ }
124+
125+ function shouldPreservePromptSkillPath (
126+ filePath : string ,
127+ roots : readonly string [ ] ,
128+ tildeRoots : readonly string [ ] ,
129+ ) : boolean {
130+ const resolvedFilePath = path . resolve ( filePath ) ;
131+ const isManagedPromptSkillPath = roots . some (
132+ ( root ) => resolvedFilePath === root || isPathInside ( root , resolvedFilePath ) ,
133+ ) ;
134+ if ( ! isManagedPromptSkillPath ) {
135+ return false ;
136+ }
137+ return ! tildeRoots . some (
138+ ( root ) => resolvedFilePath === root || isPathInside ( root , resolvedFilePath ) ,
139+ ) ;
140+ }
141+
88142function compactHomePath ( filePath : string , homes : readonly string [ ] ) : string {
89143 for ( const home of homes ) {
90144 for ( const prefix of compactHomePrefixesForHome ( home ) ) {
@@ -1368,6 +1422,7 @@ export function buildWorkspaceSkillSnapshot(
13681422 ...( skillFilter === undefined ? { } : { skillFilter } ) ,
13691423 resolvedSkills,
13701424 version : opts ?. snapshotVersion ,
1425+ promptFormatVersion : WORKSPACE_SKILLS_PROMPT_FORMAT_VERSION ,
13711426 } ;
13721427}
13731428
0 commit comments