@@ -22,7 +22,15 @@ function makeSkill(name: string, desc = "A skill", filePath = `/skills/${name}/S
2222}
2323
2424function makeEntry ( skill : Skill ) : SkillEntry {
25- return { skill, frontmatter : { } } ;
25+ return {
26+ skill,
27+ frontmatter : { } ,
28+ exposure : {
29+ includeInRuntimeRegistry : true ,
30+ includeInAvailableSkillsPrompt : true ,
31+ userInvocable : true ,
32+ } ,
33+ } ;
2634}
2735
2836function buildPrompt (
@@ -43,16 +51,21 @@ function buildPrompt(
4351}
4452
4553describe ( "formatSkillsCompact" , ( ) => {
46- it ( "keeps the full-format XML output aligned with the upstream formatter" , ( ) => {
47- const hidden : Skill = { ...makeSkill ( "hidden" ) , disableModelInvocation : true } ;
54+ it ( "keeps the full-format XML output aligned with the upstream formatter for visible skills" , ( ) => {
4855 const skills = [
4956 makeSkill ( "weather" , "Get weather <data> & forecasts" ) ,
5057 makeSkill ( "notes" , "Summarize notes" , "/tmp/notes/SKILL.md" ) ,
51- hidden ,
5258 ] ;
5359 expect ( formatSkillsForPrompt ( skills ) ) . toBe ( upstreamFormatSkillsForPrompt ( skills ) ) ;
5460 } ) ;
5561
62+ it ( "renders all passed skills in the full formatter without reapplying visibility policy" , ( ) => {
63+ const hidden : Skill = { ...makeSkill ( "hidden" ) , disableModelInvocation : true } ;
64+ const out = formatSkillsForPrompt ( [ makeSkill ( "visible" ) , hidden ] ) ;
65+ expect ( out ) . toContain ( "visible" ) ;
66+ expect ( out ) . toContain ( "hidden" ) ;
67+ } ) ;
68+
5669 it ( "returns empty string for no skills" , ( ) => {
5770 expect ( formatSkillsCompact ( [ ] ) ) . toBe ( "" ) ;
5871 } ) ;
@@ -65,11 +78,11 @@ describe("formatSkillsCompact", () => {
6578 expect ( out ) . not . toContain ( "<description>" ) ;
6679 } ) ;
6780
68- it ( "filters out disableModelInvocation skills" , ( ) => {
81+ it ( "renders all passed skills without reapplying visibility policy " , ( ) => {
6982 const hidden : Skill = { ...makeSkill ( "hidden" ) , disableModelInvocation : true } ;
7083 const out = formatSkillsCompact ( [ makeSkill ( "visible" ) , hidden ] ) ;
7184 expect ( out ) . toContain ( "visible" ) ;
72- expect ( out ) . not . toContain ( "hidden" ) ;
85+ expect ( out ) . toContain ( "hidden" ) ;
7386 } ) ;
7487
7588 it ( "escapes XML special characters" , ( ) => {
@@ -87,6 +100,29 @@ describe("formatSkillsCompact", () => {
87100} ) ;
88101
89102describe ( "applySkillsPromptLimits (via buildWorkspaceSkillsPrompt)" , ( ) => {
103+ it ( "respects explicit exposure metadata before compact formatting" , ( ) => {
104+ const hidden = makeEntry ( { ...makeSkill ( "hidden" ) , disableModelInvocation : true } ) ;
105+ hidden . exposure = {
106+ includeInRuntimeRegistry : true ,
107+ includeInAvailableSkillsPrompt : false ,
108+ userInvocable : true ,
109+ } ;
110+
111+ const prompt = buildWorkspaceSkillsPrompt ( "/fake" , {
112+ entries : [ makeEntry ( makeSkill ( "visible" ) ) , hidden ] ,
113+ config : {
114+ skills : {
115+ limits : {
116+ maxSkillsPromptChars : 4_000 ,
117+ } ,
118+ } ,
119+ } satisfies OpenClawConfig ,
120+ } ) ;
121+
122+ expect ( prompt ) . toContain ( "visible" ) ;
123+ expect ( prompt ) . not . toContain ( "hidden" ) ;
124+ } ) ;
125+
90126 it ( "tier 1: uses full format when under budget" , ( ) => {
91127 const skills = [ makeSkill ( "weather" , "Get weather data" ) ] ;
92128 const prompt = buildPrompt ( skills , { maxChars : 50_000 } ) ;
0 commit comments