diff --git a/packages/core/src/tool/glob.ts b/packages/core/src/tool/glob.ts index edcf98d7f1d..5b269d8dccd 100644 --- a/packages/core/src/tool/glob.ts +++ b/packages/core/src/tool/glob.ts @@ -83,13 +83,17 @@ export const Plugin = { agent: context.agent, source, }) - yield* fs + const info = yield* fs .stat(target.canonical) .pipe( Effect.catchReason("PlatformError", "NotFound", () => Effect.fail(new ToolFailure({ message: `Search path does not exist: ${input.path ?? "."}` })), ), ) + if (info.type !== "Directory") + return yield* Effect.fail( + new ToolFailure({ message: `Search path is not a directory: ${input.path ?? "."}` }), + ) const root = path.resolve(location.directory, input.path ?? ".") const limit = input.limit ?? FileSystem.DEFAULT_SEARCH_LIMIT const entries = yield* ripgrep diff --git a/packages/core/test/tool-search.test.ts b/packages/core/test/tool-search.test.ts index 022599e8841..b9da2007639 100644 --- a/packages/core/test/tool-search.test.ts +++ b/packages/core/test/tool-search.test.ts @@ -143,6 +143,29 @@ describe("search tools", () => { ) } + it.live("reports a file used as the glob search path", () => + Effect.acquireUseRelease( + Effect.promise(() => tmpdir()), + (tmp) => + Effect.promise(() => fs.writeFile(path.join(tmp.path, "file.txt"), "content\n")).pipe( + Effect.andThen( + withTools(tmp.path, (registry) => + executeTool(registry, call("glob", { path: "file.txt", pattern: "*" })), + ), + ), + Effect.tap((result) => + Effect.sync(() => { + expect(result).toEqual({ + status: "error", + error: { type: "tool.execution", message: "Search path is not a directory: file.txt" }, + }) + }), + ), + ), + (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), + ), + ) + it.live("requires external_directory approval for an explicit external glob path", () => Effect.acquireUseRelease( Effect.promise(() => Promise.all([tmpdir(), tmpdir()])),