fix: judge a search that names no path against the directory it would use
The guard's rule was that a call naming no path is not judged, because a tool that names no path cannot leave the facet. That is true of a tool with no path argument and false of three that have one with a default: `grep` and `glob` document theirs as "Defaults to the session workspace", and `recall` falls back to a directory its own marker search picks. On any session whose facet is narrower than its directory — every session whose focus was set from the panel rather than by starting the conversation in the folder — a bare `grep` searches outside the facet and returns what it finds. An absent argument is not an absent path; it is the default one. The three tools are now judged against the directory they would have searched, through the same containment test that decides every other call, and the refusal is its own sentence rather than the "that path is outside" one: the path at issue is one the model never wrote, so the remedy is a second argument, not a grant. A tool with no path argument at all is still left alone. `bash` is still the documented way out, and the first deployment running this plugin confirmed it is a real one: an agent scoped to one module used a shell command to parse a config file outside its facet, having been told in the same prompt not to. That is what this plugin is for, and what it cannot do is stated in the README rather than implied.
This commit is contained in:
1 parent
b0902017fd
commit
a9a25490db
4 files changed
+137
-13
No files matched your search
+16
-1
@@ -37,6 +37,8 @@ import {
|
||||
FACET_TOOL,
|
||||
facetContextText,
|
||||
facetRuleText,
|
||||
IMPLICIT_SESSION_PATHS,
|
||||
implicitSearchReason,
|
||||
isWideningCall,
|
||||
outsideReason,
|
||||
pathArgumentsFor,
|
||||
@@ -181,7 +183,20 @@ export function apply(ctx, config) {
|
||||
if (scope === undefined) return undefined
|
||||
|
||||
const paths = pathArgumentsFor(exec.name, exec.arguments)
|
||||
if (paths.length === 0) return undefined
|
||||
if (paths.length === 0) {
|
||||
// A tool that names no path is judged against the path it will actually
|
||||
// use. For most of them that is nothing — a tool with no path argument
|
||||
// cannot leave the facet. For the ones whose own default is a directory it
|
||||
// is the session directory, which a narrowed facet does not contain, and
|
||||
// "no opinion" there would hand the model a read the guard exists to
|
||||
// refuse. The containment test is the same one; only the path is ours
|
||||
// rather than the model's.
|
||||
if (!IMPLICIT_SESSION_PATHS.includes(exec.name)) return undefined
|
||||
const verdict = await inside(scope, scope.cwd, exec.signal)
|
||||
if (verdict.inside) return undefined
|
||||
const kind = mode === 'deny' ? 'deny' : 'ask'
|
||||
return { kind, reason: implicitSearchReason({ cwd: scope.cwd, root: scope.root }) }
|
||||
}
|
||||
|
||||
for (const raw of paths) {
|
||||
const verdict = await inside(scope, raw, exec.signal)
|
||||
|
||||
Reference in new issue
Block a user