@@ -11,6 +11,11 @@ import { FSUtil } from "../../fs-util"
1111import { ModelV2 } from "../../model"
1212import { ConfigAgentV1 } from "../../v1/config/agent"
1313import { ConfigMigrateV1 } from "../../v1/config/migrate"
14+ import { Global } from "../../global"
15+ import { PermissionV2 } from "../../permission"
16+ import type { LocationMutation } from "../../location-mutation"
17+ import type { ReadTool } from "../../tool/read"
18+ import type { EditTool } from "../../tool/edit"
1419
1520const legacySources = [
1621 { pattern : "{agent,agents}/**/*.md" , primary : false } ,
@@ -19,6 +24,11 @@ const legacySources = [
1924const decodeAgent = Schema . decodeUnknownOption ( ConfigAgent . Info )
2025const decodeLegacyAgent = Schema . decodeUnknownOption ( ConfigAgentV1 . Info )
2126const decodeConfig = Schema . decodeUnknownOption ( Config . Info )
27+ type PathAction =
28+ | LocationMutation . ExternalDirectoryAuthorization [ "action" ]
29+ | typeof ReadTool . name
30+ | typeof EditTool . name
31+ const pathActions = [ "external_directory" , "read" , "edit" ] as const satisfies readonly PathAction [ ]
2232const agentKeys = new Set ( [
2333 "model" ,
2434 "variant" ,
@@ -38,6 +48,7 @@ export const Plugin = define({
3848 effect : Effect . fn ( function * ( ctx ) {
3949 const config = yield * Config . Service
4050 const fs = yield * FSUtil . Service
51+ const global = yield * Global . Service
4152 yield * ctx . agent . transform (
4253 Effect . fn ( function * ( draft ) {
4354 const documents = yield * Effect . forEach ( yield * config . entries ( ) , ( entry ) => {
@@ -56,11 +67,14 @@ export const Plugin = define({
5667 )
5768 } )
5869 } ) . pipe ( Effect . map ( ( documents ) => documents . flat ( ) ) )
59- const global = documents . flatMap ( ( document ) => document . info . permissions ?? [ ] )
70+ const permissions = expandPermissions (
71+ documents . flatMap ( ( document ) => document . info . permissions ?? [ ] ) ,
72+ global . home ,
73+ )
6074 const configuredDefault = Config . latest ( documents , "default_agent" )
6175 if ( configuredDefault !== undefined ) draft . default ( AgentV2 . ID . make ( configuredDefault ) )
6276 for ( const current of draft . list ( ) ) {
63- draft . update ( current . id , ( agent ) => agent . permissions . push ( ...global ) )
77+ draft . update ( current . id , ( agent ) => agent . permissions . push ( ...permissions ) )
6478 }
6579
6680 for ( const document of documents ) {
@@ -73,7 +87,7 @@ export const Plugin = define({
7387
7488 const exists = draft . get ( agentID ) !== undefined
7589 draft . update ( agentID , ( agent ) => {
76- if ( ! exists ) agent . permissions . push ( ...global )
90+ if ( ! exists ) agent . permissions . push ( ...permissions )
7791 if ( item . model !== undefined ) {
7892 const model = ModelV2 . parse ( item . model )
7993 agent . model = { id : model . modelID , providerID : model . providerID , variant : agent . model ?. variant }
@@ -91,7 +105,9 @@ export const Plugin = define({
91105 if ( item . hidden !== undefined ) agent . hidden = item . hidden
92106 if ( item . color !== undefined ) agent . color = item . color
93107 if ( item . steps !== undefined ) agent . steps = item . steps
94- if ( item . permissions !== undefined ) agent . permissions . push ( ...item . permissions )
108+ if ( item . permissions !== undefined ) {
109+ agent . permissions . push ( ...expandPermissions ( item . permissions , global . home ) )
110+ }
95111 } )
96112 }
97113 }
@@ -100,6 +116,25 @@ export const Plugin = define({
100116 } ) ,
101117} )
102118
119+ function expandPermissions ( rules : PermissionV2 . Ruleset , home : string ) : PermissionV2 . Ruleset {
120+ // Expand only resources tools resolve as filesystem paths. Bash resources are raw shell text:
121+ // rewriting `$HOME/private/**` would miss `$HOME/private/key`, and safe expansion needs shell-aware parsing.
122+ return rules . map ( ( rule ) => ( isPathAction ( rule . action ) ? { ...rule , resource : expandHome ( rule . resource , home ) } : rule ) )
123+ }
124+
125+ function isPathAction ( action : string ) : action is PathAction {
126+ return pathActions . some ( ( item ) => item === action )
127+ }
128+
129+ function expandHome ( resource : string , home : string ) {
130+ if ( resource . startsWith ( "~/" ) ) return home + resource . slice ( 1 )
131+ if ( resource === "~" ) return home
132+ if ( resource === "$HOME" ) return home
133+ if ( resource . startsWith ( "$HOME/" ) ) return home + resource . slice ( 5 )
134+ if ( resource . startsWith ( "$HOME\\" ) ) return home + resource . slice ( 5 )
135+ return resource
136+ }
137+
103138function discover ( fs : FSUtil . Interface , directory : string ) {
104139 return Effect . forEach ( legacySources , ( source ) =>
105140 fs
0 commit comments