11import { describe , expect } from "bun:test"
2+ import fs from "fs/promises"
3+ import os from "os"
24import path from "path"
35import { Effect , Layer } from "effect"
46import { GrepTool } from "../../src/tool/grep"
@@ -11,6 +13,8 @@ import { Ripgrep } from "../../src/file/ripgrep"
1113import { AppFileSystem } from "@opencode-ai/core/filesystem"
1214import { testEffect } from "../lib/effect"
1315import { Reference } from "@/reference/reference"
16+ import { Permission } from "../../src/permission"
17+ import type * as Tool from "../../src/tool/tool"
1418
1519const it = testEffect (
1620 Layer . mergeAll (
@@ -110,4 +114,53 @@ describe("tool.grep", () => {
110114 expect ( result . output ) . toContain ( "Line 2: line2" )
111115 } ) ,
112116 )
117+
118+ it . instance ( "does not ask for external_directory when alias path is allowed" , ( ) =>
119+ Effect . gen ( function * ( ) {
120+ if ( process . platform === "win32" ) return
121+
122+ yield * TestInstance
123+ const tmp = yield * Effect . acquireRelease (
124+ Effect . promise ( ( ) => fs . mkdtemp ( path . join ( os . tmpdir ( ) , "opencode-grep-alias-" ) ) ) ,
125+ ( dir ) => Effect . promise ( ( ) => fs . rm ( dir , { recursive : true , force : true } ) ) ,
126+ )
127+ const real = path . join ( tmp , "real" )
128+ const alias = path . join ( tmp , "alias" )
129+ yield * Effect . promise ( ( ) => fs . mkdir ( real ) )
130+ yield * Effect . promise ( ( ) => fs . symlink ( real , alias , "dir" ) )
131+ yield * Effect . promise ( ( ) => Bun . write ( path . join ( real , "test.txt" ) , "needle" ) )
132+
133+ const ruleset = Permission . fromConfig ( {
134+ grep : "allow" ,
135+ external_directory : {
136+ [ path . join ( alias , "*" ) ] : "allow" ,
137+ } ,
138+ } )
139+ const requests : Array < Omit < Permission . Request , "id" | "sessionID" | "tool" > > = [ ]
140+ const next : Tool . Context = {
141+ ...ctx ,
142+ ask : ( req ) =>
143+ Effect . sync ( ( ) => {
144+ const needsAsk = req . patterns . some (
145+ ( pattern ) => Permission . evaluate ( req . permission , pattern , ruleset ) . action !== "allow" ,
146+ )
147+ if ( needsAsk ) requests . push ( req )
148+ } ) ,
149+ }
150+
151+ const info = yield * GrepTool
152+ const grep = yield * info . init ( )
153+ const result = yield * grep . execute (
154+ {
155+ pattern : "needle" ,
156+ path : alias ,
157+ include : "*.txt" ,
158+ } ,
159+ next ,
160+ )
161+
162+ expect ( result . metadata . matches ) . toBe ( 1 )
163+ expect ( requests . find ( ( req ) => req . permission === "external_directory" ) ) . toBeUndefined ( )
164+ } ) ,
165+ )
113166} )
0 commit comments