@@ -37,6 +37,8 @@ import {
3737 MessageBusType ,
3838 PolicyDecision ,
3939 type ToolConfirmationRequest ,
40+ resolveAtCommandPath ,
41+ type ResolvedAtCommandPath ,
4042} from '@google/gemini-cli-core' ;
4143import * as acp from '@agentclientprotocol/sdk' ;
4244import type { Part , FunctionCall } from '@google/genai' ;
@@ -1023,99 +1025,120 @@ export class Session {
10231025 let currentPathSpec = pathName ;
10241026 let resolvedSuccessfully = false ;
10251027 let readDirectly = false ;
1026- try {
1027- const absolutePath = path . resolve (
1028+
1029+ const result = await resolveAtCommandPath (
1030+ pathName ,
1031+ this . context . config ,
1032+ ( msg ) => this . debug ( msg ) ,
1033+ ) ;
1034+
1035+ let validationError : string | null = null ;
1036+ let absolutePath : string ;
1037+ let resolved : ResolvedAtCommandPath | undefined ;
1038+
1039+ if ( result . status === 'resolved' ) {
1040+ resolved = result . resolved ;
1041+ absolutePath = resolved . absolutePath ;
1042+ } else if ( result . status === 'unauthorized' ) {
1043+ absolutePath = result . absolutePath ;
1044+ validationError = result . error ;
1045+ } else if ( result . status === 'invalid' ) {
1046+ // Already logged in resolveAtCommandPath
1047+ continue ;
1048+ } else {
1049+ // Result is not_found.
1050+ // We still check if it's an unauthorized absolute path that we can ask permission for,
1051+ // specifically for paths that are completely outside the root and not even in any workspace directory.
1052+ // For relative paths not found anywhere, we resolve relative to targetDir for permission check.
1053+ absolutePath = path . resolve (
10281054 this . context . config . getTargetDir ( ) ,
10291055 pathName ,
10301056 ) ;
1057+ }
10311058
1032- let validationError = this . context . config . validatePathAccess (
1033- absolutePath ,
1034- 'read' ,
1035- ) ;
1036-
1037- // We ask the user for explicit permission to read them if outside sandboxed workspace boundaries (and not already authorized).
1038- if (
1039- validationError &&
1040- ! isWithinRoot ( absolutePath , this . context . config . getTargetDir ( ) )
1041- ) {
1042- try {
1043- const stats = await fs . stat ( absolutePath ) ;
1044- if ( stats . isFile ( ) ) {
1045- const syntheticCallId = `resolve-prompt-${ pathName } -${ randomUUID ( ) } ` ;
1046- const params = {
1047- sessionId : this . id ,
1048- options : [
1049- {
1050- optionId : ToolConfirmationOutcome . ProceedOnce ,
1051- name : 'Allow once' ,
1052- kind : 'allow_once' ,
1053- } ,
1059+ if (
1060+ ! resolved &&
1061+ validationError &&
1062+ ! isWithinRoot ( absolutePath , this . context . config . getTargetDir ( ) )
1063+ ) {
1064+ try {
1065+ const stats = await fs . stat ( absolutePath ) ;
1066+ if ( stats . isFile ( ) ) {
1067+ const syntheticCallId = `resolve-prompt-${ pathName } -${ randomUUID ( ) } ` ;
1068+ const params = {
1069+ sessionId : this . id ,
1070+ options : [
1071+ {
1072+ optionId : ToolConfirmationOutcome . ProceedOnce ,
1073+ name : 'Allow once' ,
1074+ kind : 'allow_once' ,
1075+ } ,
1076+ {
1077+ optionId : ToolConfirmationOutcome . Cancel ,
1078+ name : 'Deny' ,
1079+ kind : 'reject_once' ,
1080+ } ,
1081+ ] as acp . PermissionOption [ ] ,
1082+ toolCall : {
1083+ toolCallId : syntheticCallId ,
1084+ status : 'pending' ,
1085+ title : `Allow access to absolute path: ${ pathName } ` ,
1086+ content : [
10541087 {
1055- optionId : ToolConfirmationOutcome . Cancel ,
1056- name : 'Deny' ,
1057- kind : 'reject_once' ,
1058- } ,
1059- ] as acp . PermissionOption [ ] ,
1060- toolCall : {
1061- toolCallId : syntheticCallId ,
1062- status : 'pending' ,
1063- title : `Allow access to absolute path: ${ pathName } ` ,
1064- content : [
1065- {
1066- type : 'content' ,
1067- content : {
1068- type : 'text' ,
1069- text : `The Agent needs access to read an attached file outside your workspace: ${ pathName } ` ,
1070- } ,
1088+ type : 'content' ,
1089+ content : {
1090+ type : 'text' ,
1091+ text : `The Agent needs access to read an attached file outside your workspace: ${ pathName } ` ,
10711092 } ,
1072- ] ,
1073- locations : [ ] ,
1074- kind : 'read' ,
1075- } ,
1076- } ;
1093+ } ,
1094+ ] ,
1095+ locations : [ ] ,
1096+ kind : 'read' ,
1097+ } ,
1098+ } ;
10771099
1078- const output = RequestPermissionResponseSchema . parse (
1079- await this . connection . requestPermission ( params ) ,
1080- ) ;
1100+ const output = RequestPermissionResponseSchema . parse (
1101+ await this . connection . requestPermission ( params ) ,
1102+ ) ;
10811103
1082- const outcome =
1083- output . outcome . outcome === 'cancelled'
1084- ? ToolConfirmationOutcome . Cancel
1085- : z
1086- . nativeEnum ( ToolConfirmationOutcome )
1087- . parse ( output . outcome . optionId ) ;
1088-
1089- if ( outcome === ToolConfirmationOutcome . ProceedOnce ) {
1090- this . context . config
1091- . getWorkspaceContext ( )
1092- . addReadOnlyPath ( absolutePath ) ;
1093- validationError = null ;
1094- } else {
1095- this . debug (
1096- `Direct read authorization denied for absolute path ${ pathName } ` ,
1097- ) ;
1098- directContents . push ( {
1099- spec : pathName ,
1100- content : `[Warning: Access to absolute path \`${ pathName } \` denied by user.]` ,
1101- } ) ;
1102- continue ;
1103- }
1104+ const outcome =
1105+ output . outcome . outcome === 'cancelled'
1106+ ? ToolConfirmationOutcome . Cancel
1107+ : z
1108+ . nativeEnum ( ToolConfirmationOutcome )
1109+ . parse ( output . outcome . optionId ) ;
1110+
1111+ if ( outcome === ToolConfirmationOutcome . ProceedOnce ) {
1112+ this . context . config
1113+ . getWorkspaceContext ( )
1114+ . addReadOnlyPath ( absolutePath ) ;
1115+ validationError = null ;
1116+ } else {
1117+ this . debug (
1118+ `Direct read authorization denied for absolute path ${ pathName } ` ,
1119+ ) ;
1120+ directContents . push ( {
1121+ spec : pathName ,
1122+ content : `[Warning: Access to absolute path \`${ pathName } \` denied by user.]` ,
1123+ } ) ;
1124+ continue ;
11041125 }
1105- } catch ( error ) {
1106- this . debug (
1107- `Failed to request permission for absolute attachment ${ pathName } : ${ getErrorMessage ( error ) } ` ,
1108- ) ;
1109- await this . sendUpdate ( {
1110- sessionUpdate : 'agent_thought_chunk' ,
1111- content : {
1112- type : 'text' ,
1113- text : `Warning: Failed to display permission dialog for \`${ absolutePath } \`. Error: ${ getErrorMessage ( error ) } ` ,
1114- } ,
1115- } ) ;
11161126 }
1127+ } catch ( error ) {
1128+ this . debug (
1129+ `Failed to request permission for absolute attachment ${ pathName } : ${ getErrorMessage ( error ) } ` ,
1130+ ) ;
1131+ await this . sendUpdate ( {
1132+ sessionUpdate : 'agent_thought_chunk' ,
1133+ content : {
1134+ type : 'text' ,
1135+ text : `Warning: Failed to display permission dialog for \`${ absolutePath } \`. Error: ${ getErrorMessage ( error ) } ` ,
1136+ } ,
1137+ } ) ;
11171138 }
1139+ }
11181140
1141+ try {
11191142 if ( ! validationError ) {
11201143 // If it's an absolute path that is authorized (e.g. added via readOnlyPaths),
11211144 // read it directly to avoid ReadManyFilesTool absolute path resolution issues.
@@ -1128,7 +1151,9 @@ export class Session {
11281151 ! readDirectly
11291152 ) {
11301153 try {
1131- const stats = await fs . stat ( absolutePath ) ;
1154+ const stats = resolved
1155+ ? resolved . stats
1156+ : await fs . stat ( absolutePath ) ;
11321157 if ( stats . isFile ( ) ) {
11331158 const fileReadResult = await processSingleFileContent (
11341159 absolutePath ,
@@ -1187,7 +1212,9 @@ export class Session {
11871212 }
11881213
11891214 if ( ! readDirectly ) {
1190- const stats = await fs . stat ( absolutePath ) ;
1215+ const stats = resolved
1216+ ? resolved . stats
1217+ : await fs . stat ( absolutePath ) ;
11911218 if ( stats . isDirectory ( ) ) {
11921219 currentPathSpec = pathName . endsWith ( '/' )
11931220 ? `${ pathName } **`
0 commit comments