@@ -27,6 +27,8 @@ import { URI } from '../../../../util/vs/base/common/uri';
2727import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation' ;
2828import { ServiceCollection } from '../../../../util/vs/platform/instantiation/common/serviceCollection' ;
2929import { ChatRequestTurn , ChatRequestTurn2 , ChatResponseMarkdownPart , ChatResponseTurn2 , ChatSessionStatus , ChatToolInvocationPart , MarkdownString , ThemeIcon } from '../../../../vscodeTypes' ;
30+ import { createExtensionUnitTestingServices } from '../../../test/node/services' ;
31+ import { MockChatResponseStream , TestChatRequest } from '../../../test/node/testHelpers' ;
3032import { ClaudeSessionUri } from '../../claude/common/claudeSessionUri' ;
3133import type { ClaudeAgentManager } from '../../claude/node/claudeCodeAgent' ;
3234import { IClaudeCodeModels } from '../../claude/node/claudeCodeModels' ;
@@ -35,8 +37,6 @@ import { IClaudeSessionTitleService } from '../../claude/node/claudeSessionTitle
3537import { ClaudeCodeSessionService , IClaudeCodeSessionService } from '../../claude/node/sessionParser/claudeCodeSessionService' ;
3638import { IClaudeCodeSessionInfo } from '../../claude/node/sessionParser/claudeSessionSchema' ;
3739import { IClaudeSlashCommandService } from '../../claude/vscode-node/claudeSlashCommandService' ;
38- import { createExtensionUnitTestingServices } from '../../../test/node/services' ;
39- import { MockChatResponseStream , TestChatRequest } from '../../../test/node/testHelpers' ;
4040import { FolderRepositoryMRUEntry , IFolderRepositoryManager } from '../../common/folderRepositoryManager' ;
4141import { ClaudeChatSessionContentProvider , ClaudeChatSessionItemController } from '../claudeChatSessionContentProvider' ;
4242
@@ -626,6 +626,254 @@ describe('ChatSessionContentProvider', () => {
626626 const permissionMode = provider . getPermissionModeForSession ( 'test-session' ) ;
627627 expect ( permissionMode ) . toBe ( 'plan' ) ;
628628 } ) ;
629+
630+ it ( 'ignores invalid permission mode values in provideHandleOptionsChange' , async ( ) => {
631+ const sessionUri = createClaudeSessionUri ( 'test-session' ) ;
632+
633+ await provider . provideHandleOptionsChange (
634+ sessionUri ,
635+ [ { optionId : 'permissionMode' , value : 'not-a-real-mode' } ] ,
636+ CancellationToken . None ,
637+ ) ;
638+
639+ // Should fall through to session state service default, not store the invalid value
640+ const permissionMode = provider . getPermissionModeForSession ( 'test-session' ) ;
641+ expect ( permissionMode ) . not . toBe ( 'not-a-real-mode' ) ;
642+ } ) ;
643+
644+ it ( 'ignores empty permission mode value in provideHandleOptionsChange' , async ( ) => {
645+ const sessionUri = createClaudeSessionUri ( 'test-session' ) ;
646+
647+ await provider . provideHandleOptionsChange (
648+ sessionUri ,
649+ [ { optionId : 'permissionMode' , value : '' } ] ,
650+ CancellationToken . None ,
651+ ) ;
652+
653+ // Should not store empty string as permission mode
654+ const permissionMode = provider . getPermissionModeForSession ( 'test-session' ) ;
655+ expect ( permissionMode ) . not . toBe ( '' ) ;
656+ } ) ;
657+
658+ it ( 'accepts all valid permission modes in provideHandleOptionsChange' , async ( ) => {
659+ const validModes = [ 'default' , 'acceptEdits' , 'bypassPermissions' , 'plan' , 'dontAsk' ] as const ;
660+
661+ for ( const mode of validModes ) {
662+ const sessionUri = createClaudeSessionUri ( `test-session-${ mode } ` ) ;
663+ await provider . provideHandleOptionsChange (
664+ sessionUri ,
665+ [ { optionId : 'permissionMode' , value : mode } ] ,
666+ CancellationToken . None ,
667+ ) ;
668+
669+ const permissionMode = provider . getPermissionModeForSession ( `test-session-${ mode } ` ) ;
670+ expect ( permissionMode ) . toBe ( mode ) ;
671+ }
672+ } ) ;
673+
674+ it ( 'does not update _lastUsedPermissionMode when invalid mode is provided' , async ( ) => {
675+ // First set a valid mode
676+ const sessionUri1 = createClaudeSessionUri ( 'session-valid' ) ;
677+ await provider . provideHandleOptionsChange (
678+ sessionUri1 ,
679+ [ { optionId : 'permissionMode' , value : 'plan' } ] ,
680+ CancellationToken . None ,
681+ ) ;
682+
683+ // Try to set an invalid mode on a different session
684+ const sessionUri2 = createClaudeSessionUri ( 'session-invalid' ) ;
685+ await provider . provideHandleOptionsChange (
686+ sessionUri2 ,
687+ [ { optionId : 'permissionMode' , value : 'bogus' } ] ,
688+ CancellationToken . None ,
689+ ) ;
690+
691+ // newSessionOptions should still reflect the last valid mode
692+ const options = await provider . provideChatSessionProviderOptions ( ) ;
693+ expect ( options . newSessionOptions ! [ 'permissionMode' ] ) . toBe ( 'plan' ) ;
694+ } ) ;
695+ } ) ;
696+
697+ // #endregion
698+
699+ // #region Initial Session Options
700+
701+ describe ( 'initial session options on new sessions' , ( ) => {
702+ let mockAgentManager : ClaudeAgentManager ;
703+ let handlerProvider : ClaudeChatSessionContentProvider ;
704+
705+ function createChatContext ( sessionId : string , initialSessionOptions ?: Array < { optionId : string ; value : string } > ) : vscode . ChatContext {
706+ return {
707+ history : [ ] ,
708+ yieldRequested : false ,
709+ chatSessionContext : {
710+ isUntitled : false ,
711+ chatSessionItem : {
712+ resource : ClaudeSessionUri . forSessionId ( sessionId ) ,
713+ label : 'Test Session' ,
714+ } ,
715+ initialSessionOptions,
716+ } ,
717+ } as vscode . ChatContext ;
718+ }
719+
720+ beforeEach ( ( ) => {
721+ const mocks = createDefaultMocks ( ) ;
722+ mockSessionService = mocks . mockSessionService ;
723+ mockClaudeCodeModels = mocks . mockClaudeCodeModels ;
724+ mockFolderRepositoryManager = mocks . mockFolderRepositoryManager ;
725+ mockAgentManager = createMockAgentManager ( ) ;
726+
727+ const result = createProviderWithServices ( store , [ workspaceFolderUri ] , mocks , mockAgentManager ) ;
728+ handlerProvider = result . provider ;
729+ } ) ;
730+
731+ it ( 'sets permission mode from initialSessionOptions on new session' , async ( ) => {
732+ vi . mocked ( mockSessionService . getSession ) . mockResolvedValue ( undefined ) ;
733+
734+ const handler = handlerProvider . createHandler ( ) ;
735+ const context = createChatContext ( 'new-session-1' , [
736+ { optionId : 'permissionMode' , value : 'plan' } ,
737+ ] ) ;
738+ const stream = new MockChatResponseStream ( ) ;
739+
740+ await handler ( createTestRequest ( 'hello' ) , context , stream , CancellationToken . None ) ;
741+
742+ // The handler commits state — verify the permission mode was used
743+ expect ( handlerProvider . getPermissionModeForSession ( 'new-session-1' ) ) . toBe ( 'plan' ) ;
744+ } ) ;
745+
746+ it ( 'defaults to _lastUsedPermissionMode when initialSessionOptions has no permission mode' , async ( ) => {
747+ vi . mocked ( mockSessionService . getSession ) . mockResolvedValue ( undefined ) ;
748+
749+ // First, set the last used mode to 'plan' via a different session
750+ const setupUri = createClaudeSessionUri ( 'setup-session' ) ;
751+ await handlerProvider . provideHandleOptionsChange (
752+ setupUri ,
753+ [ { optionId : 'permissionMode' , value : 'plan' } ] ,
754+ CancellationToken . None ,
755+ ) ;
756+
757+ const handler = handlerProvider . createHandler ( ) ;
758+ const context = createChatContext ( 'new-session-2' ) ;
759+ const stream = new MockChatResponseStream ( ) ;
760+
761+ await handler ( createTestRequest ( 'hello' ) , context , stream , CancellationToken . None ) ;
762+
763+ expect ( handlerProvider . getPermissionModeForSession ( 'new-session-2' ) ) . toBe ( 'plan' ) ;
764+ } ) ;
765+
766+ it ( 'does not overwrite permission mode if already set for the session' , async ( ) => {
767+ vi . mocked ( mockSessionService . getSession ) . mockResolvedValue ( undefined ) ;
768+
769+ // Pre-set permission mode via provideHandleOptionsChange
770+ const sessionUri = createClaudeSessionUri ( 'pre-set-session' ) ;
771+ await handlerProvider . provideHandleOptionsChange (
772+ sessionUri ,
773+ [ { optionId : 'permissionMode' , value : 'default' } ] ,
774+ CancellationToken . None ,
775+ ) ;
776+
777+ const handler = handlerProvider . createHandler ( ) ;
778+ const context = createChatContext ( 'pre-set-session' , [
779+ { optionId : 'permissionMode' , value : 'plan' } ,
780+ ] ) ;
781+ const stream = new MockChatResponseStream ( ) ;
782+
783+ await handler ( createTestRequest ( 'hello' ) , context , stream , CancellationToken . None ) ;
784+
785+ // Should keep the pre-set value, not overwrite with initialSessionOptions
786+ expect ( handlerProvider . getPermissionModeForSession ( 'pre-set-session' ) ) . toBe ( 'default' ) ;
787+ } ) ;
788+
789+ it ( 'does not apply initialSessionOptions on resumed sessions' , async ( ) => {
790+ // Session exists on disk → not new
791+ vi . mocked ( mockSessionService . getSession ) . mockResolvedValue ( {
792+ id : 'existing-session' ,
793+ messages : [ { type : 'user' , message : { role : 'user' , content : 'Hello' } } ] ,
794+ subagents : [ ] ,
795+ } as any ) ;
796+
797+ const handler = handlerProvider . createHandler ( ) ;
798+ const context = createChatContext ( 'existing-session' , [
799+ { optionId : 'permissionMode' , value : 'bypassPermissions' } ,
800+ ] ) ;
801+ const stream = new MockChatResponseStream ( ) ;
802+
803+ await handler ( createTestRequest ( 'hello' ) , context , stream , CancellationToken . None ) ;
804+
805+ // Should not have been set from initialSessionOptions since session is not new
806+ expect ( handlerProvider . getPermissionModeForSession ( 'existing-session' ) ) . not . toBe ( 'bypassPermissions' ) ;
807+ } ) ;
808+ } ) ;
809+
810+ describe ( 'initial folder option on new sessions' , ( ) => {
811+ const folderA = URI . file ( '/project-a' ) ;
812+ const folderB = URI . file ( '/project-b' ) ;
813+ let mockAgentManager : ClaudeAgentManager ;
814+ let multiRootProvider : ClaudeChatSessionContentProvider ;
815+
816+ function createChatContext ( sessionId : string , initialSessionOptions ?: Array < { optionId : string ; value : string } > ) : vscode . ChatContext {
817+ return {
818+ history : [ ] ,
819+ yieldRequested : false ,
820+ chatSessionContext : {
821+ isUntitled : false ,
822+ chatSessionItem : {
823+ resource : ClaudeSessionUri . forSessionId ( sessionId ) ,
824+ label : 'Test Session' ,
825+ } ,
826+ initialSessionOptions,
827+ } ,
828+ } as vscode . ChatContext ;
829+ }
830+
831+ beforeEach ( ( ) => {
832+ const mocks = createDefaultMocks ( ) ;
833+ mockSessionService = mocks . mockSessionService ;
834+ mockAgentManager = createMockAgentManager ( ) ;
835+
836+ const result = createProviderWithServices ( store , [ folderA , folderB ] , mocks , mockAgentManager ) ;
837+ multiRootProvider = result . provider ;
838+ } ) ;
839+
840+ it ( 'sets folder from initialSessionOptions on new session' , async ( ) => {
841+ vi . mocked ( mockSessionService . getSession ) . mockResolvedValue ( undefined ) ;
842+
843+ const handler = multiRootProvider . createHandler ( ) ;
844+ const context = createChatContext ( 'new-folder-session' , [
845+ { optionId : 'folder' , value : folderB . fsPath } ,
846+ ] ) ;
847+ const stream = new MockChatResponseStream ( ) ;
848+
849+ await handler ( createTestRequest ( 'hello' ) , context , stream , CancellationToken . None ) ;
850+
851+ const folderInfo = await multiRootProvider . getFolderInfoForSession ( 'new-folder-session' ) ;
852+ expect ( folderInfo . cwd ) . toBe ( folderB . fsPath ) ;
853+ } ) ;
854+
855+ it ( 'does not overwrite folder if already set for the session' , async ( ) => {
856+ vi . mocked ( mockSessionService . getSession ) . mockResolvedValue ( undefined ) ;
857+
858+ // Pre-set folder via provideHandleOptionsChange
859+ const sessionUri = createClaudeSessionUri ( 'pre-folder-session' ) ;
860+ await multiRootProvider . provideHandleOptionsChange (
861+ sessionUri ,
862+ [ { optionId : 'folder' , value : folderA . fsPath } ] ,
863+ CancellationToken . None ,
864+ ) ;
865+
866+ const handler = multiRootProvider . createHandler ( ) ;
867+ const context = createChatContext ( 'pre-folder-session' , [
868+ { optionId : 'folder' , value : folderB . fsPath } ,
869+ ] ) ;
870+ const stream = new MockChatResponseStream ( ) ;
871+
872+ await handler ( createTestRequest ( 'hello' ) , context , stream , CancellationToken . None ) ;
873+
874+ const folderInfo = await multiRootProvider . getFolderInfoForSession ( 'pre-folder-session' ) ;
875+ expect ( folderInfo . cwd ) . toBe ( folderA . fsPath ) ;
876+ } ) ;
629877 } ) ;
630878
631879 // #endregion
0 commit comments