@@ -644,6 +644,78 @@ test("SessionManager excludes disabled skills by resolved skill name", async ()
644644 assert . equal ( skills [ 0 ] ?. path , "./.deepcode/skills/enabled-skill/SKILL.md" ) ;
645645} ) ;
646646
647+ test ( "SessionManager keeps implicit opt-out skills available for manual invocation" , async ( ) => {
648+ const workspace = createTempDir ( "deepcode-manual-only-skill-workspace-" ) ;
649+ const home = createTempDir ( "deepcode-manual-only-skill-home-" ) ;
650+ setHomeDir ( home ) ;
651+
652+ const skillDir = path . join ( workspace , ".agents" , "skills" , "manual-only" ) ;
653+ fs . mkdirSync ( skillDir , { recursive : true } ) ;
654+ fs . writeFileSync (
655+ path . join ( skillDir , "SKILL.md" ) ,
656+ "---\nname: manual-only\ndescription: Manual-only skill\nmetadata:\n allow-implicit-invocation: false\n---\n# Manual Only\n" ,
657+ "utf8"
658+ ) ;
659+
660+ const manager = createSessionManager ( workspace , "machine-id-manual-only-skill" ) ;
661+ const skill = ( await manager . listSkills ( ) ) . find ( ( candidate ) => candidate . name === "manual-only" ) ;
662+ assert . ok ( skill ) ;
663+ assert . equal ( skill . allowImplicitInvocation , false ) ;
664+
665+ const sessionId = await manager . createSession ( { text : "" , skills : [ skill ] } ) ;
666+ const skillMessages = manager
667+ . listSessionMessages ( sessionId )
668+ . filter ( ( message ) => message . role === "system" && message . meta ?. skill ?. name === "manual-only" ) ;
669+
670+ assert . equal ( skillMessages . length , 1 ) ;
671+ assert . match ( skillMessages [ 0 ] ?. content ?? "" , / < m a n u a l - o n l y - s k i l l / ) ;
672+ assert . doesNotMatch ( skillMessages [ 0 ] ?. content ?? "" , / a l l o w - i m p l i c i t - i n v o c a t i o n / ) ;
673+ } ) ;
674+
675+ test ( "SessionManager excludes implicit opt-out skills from automatic matching candidates" , async ( ) => {
676+ const workspace = createTempDir ( "deepcode-implicit-opt-out-workspace-" ) ;
677+ const home = createTempDir ( "deepcode-implicit-opt-out-home-" ) ;
678+ setHomeDir ( home ) ;
679+ globalThis . fetch = ( async ( ) => ( { ok : true , text : async ( ) => "" } ) as Response ) as typeof fetch ;
680+
681+ const writeSkill = ( name : string , metadata = "" ) : void => {
682+ const skillDir = path . join ( workspace , ".deepcode" , "skills" , name ) ;
683+ fs . mkdirSync ( skillDir , { recursive : true } ) ;
684+ fs . writeFileSync (
685+ path . join ( skillDir , "SKILL.md" ) ,
686+ `---\nname: ${ name } \ndescription: ${ name } description${ metadata } \n---\n# ${ name } \n` ,
687+ "utf8"
688+ ) ;
689+ } ;
690+ writeSkill ( "auto-skill" ) ;
691+ writeSkill ( "manual-only" , "\nmetadata:\n allow-implicit-invocation: false" ) ;
692+
693+ const requests : any [ ] = [ ] ;
694+ const client = {
695+ chat : {
696+ completions : {
697+ create : async ( request : any ) => {
698+ requests . push ( request ) ;
699+ if ( isSkillMatchingRequest ( request ) ) {
700+ return createSkillMatchingResponse ( [ "manual-only" , "auto-skill" ] ) ;
701+ }
702+ return createChatResponse ( "done" , { prompt_tokens : 1 , completion_tokens : 1 , total_tokens : 2 } ) ;
703+ } ,
704+ } ,
705+ } ,
706+ } ;
707+ const manager = createMockedClientSessionManagerWithClient ( workspace , client ) ;
708+ ( manager as any ) . activateSession = async ( ) => { } ;
709+
710+ const sessionId = await manager . createSession ( { text : "choose an automatic skill" } ) ;
711+ const matchingPrompt = String ( requests [ 0 ] ?. messages ?. [ 0 ] ?. content ?? "" ) ;
712+
713+ assert . match ( matchingPrompt , / " n a m e " : " a u t o - s k i l l " / ) ;
714+ assert . doesNotMatch ( matchingPrompt , / " n a m e " : " m a n u a l - o n l y " / ) ;
715+ assert . equal ( countLoadedSkillMessages ( manager . listSessionMessages ( sessionId ) , "auto-skill" ) , 1 ) ;
716+ assert . equal ( countLoadedSkillMessages ( manager . listSessionMessages ( sessionId ) , "manual-only" ) , 0 ) ;
717+ } ) ;
718+
647719test ( "SessionManager dispose disconnects MCP servers" , async ( ) => {
648720 const workspace = createTempDir ( "deepcode-mcp-dispose-workspace-" ) ;
649721 const serverPath = path . join ( workspace , "mcp-server.cjs" ) ;
0 commit comments