@@ -664,10 +664,6 @@ export class AgentToolManager {
664664 return this . callProcessTool ( toolName , args , conversationId )
665665 }
666666
667- if ( ! this . fileSystemHandler ) {
668- throw new Error ( 'FileSystem handler not initialized' )
669- }
670-
671667 const schema = this . fileSystemSchemas [ toolName as keyof typeof this . fileSystemSchemas ]
672668 if ( ! schema ) {
673669 throw new Error ( `No schema found for FileSystem tool: ${ toolName } ` )
@@ -680,6 +676,50 @@ export class AgentToolManager {
680676
681677 const parsedArgs = validationResult . data
682678
679+ if ( toolName === 'exec' ) {
680+ if ( ! this . bashHandler ) {
681+ throw new Error ( 'Bash handler not initialized for exec tool' )
682+ }
683+ const execArgs = parsedArgs as {
684+ command : string
685+ timeoutMs ?: number
686+ description ?: string
687+ cwd ?: string
688+ background ?: boolean
689+ yieldMs ?: number
690+ }
691+ const commandResult = await this . bashHandler . executeCommand (
692+ {
693+ command : execArgs . command ,
694+ timeout : execArgs . timeoutMs ,
695+ description : execArgs . description ?? 'Execute command' ,
696+ cwd : execArgs . cwd ,
697+ background : execArgs . background ,
698+ yieldMs : execArgs . yieldMs
699+ } ,
700+ {
701+ conversationId
702+ }
703+ )
704+ const content =
705+ typeof commandResult . output === 'string'
706+ ? commandResult . output
707+ : JSON . stringify ( commandResult . output )
708+ return {
709+ content,
710+ rawData : {
711+ content,
712+ rtkApplied : commandResult . rtkApplied ,
713+ rtkMode : commandResult . rtkMode ,
714+ rtkFallbackReason : commandResult . rtkFallbackReason
715+ }
716+ }
717+ }
718+
719+ if ( ! this . fileSystemHandler ) {
720+ throw new Error ( 'FileSystem handler not initialized' )
721+ }
722+
683723 // Get dynamic workdir from conversation settings
684724 let dynamicWorkdir : string | null = null
685725 if ( conversationId ) {
@@ -698,7 +738,7 @@ export class AgentToolManager {
698738 const baseDirectory = explicitBaseDirectory ?? dynamicWorkdir ?? undefined
699739 const workspaceRoot =
700740 dynamicWorkdir ?? this . agentWorkspacePath ?? this . getDefaultAgentWorkspacePath ( )
701- const allowedDirectories = this . buildAllowedDirectories ( workspaceRoot , conversationId )
741+ const allowedDirectories = await this . buildAllowedDirectories ( workspaceRoot , conversationId )
702742 const fileSystemHandler = new AgentFileSystemHandler ( allowedDirectories , { conversationId } )
703743
704744 try {
@@ -868,45 +908,6 @@ export class AgentToolManager {
868908 )
869909 }
870910 }
871- case 'exec' : {
872- if ( ! this . bashHandler ) {
873- throw new Error ( 'Bash handler not initialized for exec tool' )
874- }
875- const execArgs = parsedArgs as {
876- command : string
877- timeoutMs ?: number
878- description ?: string
879- cwd ?: string
880- background ?: boolean
881- yieldMs ?: number
882- }
883- const commandResult = await this . bashHandler . executeCommand (
884- {
885- command : execArgs . command ,
886- timeout : execArgs . timeoutMs ,
887- description : execArgs . description ?? 'Execute command' ,
888- cwd : execArgs . cwd ,
889- background : execArgs . background ,
890- yieldMs : execArgs . yieldMs
891- } ,
892- {
893- conversationId
894- }
895- )
896- const content =
897- typeof commandResult . output === 'string'
898- ? commandResult . output
899- : JSON . stringify ( commandResult . output )
900- return {
901- content,
902- rawData : {
903- content,
904- rtkApplied : commandResult . rtkApplied ,
905- rtkMode : commandResult . rtkMode ,
906- rtkFallbackReason : commandResult . rtkFallbackReason
907- }
908- }
909- }
910911 default :
911912 throw new Error ( `Unknown FileSystem tool: ${ toolName } ` )
912913 }
@@ -937,7 +938,10 @@ export class AgentToolManager {
937938 }
938939 }
939940
940- private buildAllowedDirectories ( workspacePath : string , conversationId ?: string ) : string [ ] {
941+ private async buildAllowedDirectories (
942+ workspacePath : string ,
943+ conversationId ?: string
944+ ) : Promise < string [ ] > {
941945 const ordered : string [ ] = [ ]
942946 const seen = new Set < string > ( )
943947 const addPath = ( value ?: string | null ) => {
@@ -951,7 +955,14 @@ export class AgentToolManager {
951955
952956 addPath ( workspacePath )
953957 addPath ( this . agentWorkspacePath )
954- addPath ( this . configPresenter . getSkillsPath ( ) )
958+
959+ if ( conversationId ) {
960+ const activeSkillRoots = await this . resolveActiveSkillRoots ( conversationId )
961+ for ( const skillRoot of activeSkillRoots ) {
962+ addPath ( skillRoot )
963+ }
964+ }
965+
955966 addPath ( path . join ( app . getPath ( 'home' ) , '.deepchat' ) )
956967 addPath ( app . getPath ( 'temp' ) )
957968
@@ -965,6 +976,86 @@ export class AgentToolManager {
965976 return ordered
966977 }
967978
979+ private async resolveActiveSkillRoots ( conversationId : string ) : Promise < string [ ] > {
980+ const skillPresenter = this . getSkillPresenter ( )
981+ if ( ! skillPresenter ?. getActiveSkills || ! skillPresenter ?. getMetadataList ) {
982+ return [ ]
983+ }
984+
985+ let activeSkillNames : string [ ]
986+ let metadataList : Awaited < ReturnType < typeof skillPresenter . getMetadataList > >
987+
988+ try {
989+ ; [ activeSkillNames , metadataList ] = await Promise . all ( [
990+ skillPresenter . getActiveSkills ( conversationId ) ,
991+ skillPresenter . getMetadataList ( )
992+ ] )
993+ } catch ( error ) {
994+ logger . warn ( '[AgentToolManager] Failed to resolve active skill roots' , {
995+ conversationId,
996+ error
997+ } )
998+ return [ ]
999+ }
1000+
1001+ const metadataByName = new Map (
1002+ metadataList
1003+ . filter ( ( metadata ) => metadata ?. name ?. trim ( ) )
1004+ . map ( ( metadata ) => [ metadata . name . trim ( ) , metadata ] )
1005+ )
1006+ const roots : string [ ] = [ ]
1007+
1008+ for ( const skillName of activeSkillNames ) {
1009+ const normalizedSkillName = skillName ?. trim ( )
1010+ if ( ! normalizedSkillName ) {
1011+ continue
1012+ }
1013+
1014+ const metadata = metadataByName . get ( normalizedSkillName )
1015+ if ( ! metadata ) {
1016+ logger . warn (
1017+ '[AgentToolManager] Active skill metadata missing during file allowlist build' ,
1018+ {
1019+ conversationId,
1020+ skillName : normalizedSkillName
1021+ }
1022+ )
1023+ continue
1024+ }
1025+
1026+ const skillRoot = metadata . skillRoot ?. trim ( )
1027+ if ( ! skillRoot ) {
1028+ logger . warn ( '[AgentToolManager] Active skill root missing during file allowlist build' , {
1029+ conversationId,
1030+ skillName : normalizedSkillName
1031+ } )
1032+ continue
1033+ }
1034+
1035+ try {
1036+ const resolvedRoot = path . resolve ( skillRoot )
1037+ if ( ! fs . existsSync ( resolvedRoot ) || ! fs . statSync ( resolvedRoot ) . isDirectory ( ) ) {
1038+ logger . warn ( '[AgentToolManager] Active skill root is not a directory' , {
1039+ conversationId,
1040+ skillName : normalizedSkillName ,
1041+ skillRoot : resolvedRoot
1042+ } )
1043+ continue
1044+ }
1045+ roots . push ( resolvedRoot )
1046+ } catch ( error ) {
1047+ logger . warn ( '[AgentToolManager] Failed to normalize active skill root' , {
1048+ conversationId,
1049+ skillName : normalizedSkillName ,
1050+ skillRoot,
1051+ error
1052+ } )
1053+ }
1054+ }
1055+
1056+ return roots
1057+ }
1058+
9681059 private async resolveValidatedReadPath (
9691060 fileSystemHandler : AgentFileSystemHandler ,
9701061 requestedPath : string ,
@@ -1470,8 +1561,13 @@ export class AgentToolManager {
14701561
14711562 const workspaceRoot =
14721563 dynamicWorkdir ?? this . agentWorkspacePath ?? this . getDefaultAgentWorkspacePath ( )
1473- const allowedDirectories = this . buildAllowedDirectories ( workspaceRoot , conversationId )
1564+ const allowedDirectories = await this . buildAllowedDirectories ( workspaceRoot , conversationId )
14741565 const fileSystemHandler = new AgentFileSystemHandler ( allowedDirectories , { conversationId } )
1566+ const explicitBaseDirectory =
1567+ typeof args . base_directory === 'string' && args . base_directory . trim ( ) . length > 0
1568+ ? args . base_directory
1569+ : undefined
1570+ const baseDirectory = explicitBaseDirectory ?? dynamicWorkdir ?? undefined
14751571
14761572 // Collect target paths
14771573 const targets = this . collectWriteTargets ( toolName , args )
@@ -1485,7 +1581,7 @@ export class AgentToolManager {
14851581 // Check each path
14861582 const denied : string [ ] = [ ]
14871583 for ( const target of targets ) {
1488- const resolved = fileSystemHandler . resolvePath ( target , undefined )
1584+ const resolved = fileSystemHandler . resolvePath ( target , baseDirectory )
14891585 if ( ! fileSystemHandler . isPathAllowedAbsolute ( resolved ) ) {
14901586 denied . push ( target )
14911587 }
0 commit comments