diff --git a/astrbot/core/tools/computer_tools/fs.py b/astrbot/core/tools/computer_tools/fs.py index c3236c8a1..0b6003937 100644 --- a/astrbot/core/tools/computer_tools/fs.py +++ b/astrbot/core/tools/computer_tools/fs.py @@ -304,6 +304,11 @@ class FileReadTool(FunctionTool): ) if not normalized_path: raise ValueError("`path` must be a non-empty string.") + if local_env and os.path.isdir(normalized_path): + return ( + f"Error: '{normalized_path}' is a directory, not a file. " + "Use a file path instead, or use 'astrbot_execute_shell' to list directory contents." + ) offset, limit = self._validate_read_window(offset, limit) sb = await get_booter( context.context.context, diff --git a/tests/test_computer_fs_tools.py b/tests/test_computer_fs_tools.py index 11571665a..17dbe3f6b 100644 --- a/tests/test_computer_fs_tools.py +++ b/tests/test_computer_fs_tools.py @@ -620,3 +620,23 @@ async def test_grep_tool_applies_result_limit( assert "match-2" in result assert "match-3" not in result assert "[Truncated to first 2 result groups.]" in result + + +@pytest.mark.asyncio +async def test_file_read_tool_rejects_directory_with_clear_message( + monkeypatch: pytest.MonkeyPatch, + tmp_path, +): + """FileReadTool should return a helpful message when given a directory path.""" + workspace = _setup_local_fs_tools(monkeypatch, tmp_path) + subdir = workspace / "my-directory" + subdir.mkdir() + + result = await fs_tools.FileReadTool().call( + _make_context(), + path="my-directory", + ) + + assert "is a directory, not a file" in result + assert "my-directory" in result + assert "'astrbot_execute_shell'" in result