mirror of
https://github.com/browser-use/browser-use
synced 2026-05-06 17:52:15 +02:00
- Enhanced logging in the Agent class to inform users about available attachments after task completion. - Revised system prompt guidelines to clarify the use of text and files_to_display in the done action.
115 lines
3.6 KiB
Python
115 lines
3.6 KiB
Python
import asyncio
|
|
import re
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
from pathlib import Path
|
|
|
|
INVALID_FILENAME_ERROR_MESSAGE = 'Error: Invalid filename format. Must be alphanumeric with .txt or .md extension.'
|
|
|
|
|
|
class FileSystem:
|
|
def __init__(self, dir_path: str):
|
|
# Create a base directory
|
|
self.base_dir = Path(dir_path)
|
|
self.base_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Create and use a dedicated subfolder for all operations
|
|
self.dir = self.base_dir / 'data_storage'
|
|
if self.dir.exists():
|
|
raise ValueError(
|
|
'File system directory already exists - stopping for safety purposes. Please delete it first if you want to use this directory.'
|
|
)
|
|
self.dir.mkdir(exist_ok=True)
|
|
|
|
# Initialize default files
|
|
self.results_file = self.dir / 'results.md'
|
|
self.todo_file = self.dir / 'todo.md'
|
|
self.results_file.touch(exist_ok=True)
|
|
self.todo_file.touch(exist_ok=True)
|
|
|
|
def get_dir(self) -> Path:
|
|
return self.dir
|
|
|
|
def _is_valid_filename(self, file_name: str) -> bool:
|
|
"""Check if filename matches the required pattern: name.extension"""
|
|
pattern = r'^[a-zA-Z0-9_\-]+\.(txt|md)$'
|
|
return bool(re.match(pattern, file_name))
|
|
|
|
def display_file(self, file_name: str) -> str | None:
|
|
if not self._is_valid_filename(file_name):
|
|
return None
|
|
|
|
path = self.dir / file_name
|
|
if not path.exists():
|
|
return None
|
|
try:
|
|
return path.read_text()
|
|
except Exception:
|
|
return None
|
|
|
|
async def read_file(self, file_name: str) -> str:
|
|
if not self._is_valid_filename(file_name):
|
|
return INVALID_FILENAME_ERROR_MESSAGE
|
|
|
|
path = self.dir / file_name
|
|
if not path.exists():
|
|
return f"File '{file_name}' not found."
|
|
|
|
try:
|
|
# Create a new executor for this operation
|
|
with ThreadPoolExecutor() as executor:
|
|
# Run file read in a thread to avoid blocking
|
|
content = await asyncio.get_event_loop().run_in_executor(executor, lambda: path.read_text())
|
|
return f'Read from file {file_name}:\n{content}'
|
|
except Exception:
|
|
return f"Error: Could not read file '{file_name}'."
|
|
|
|
async def write_file(self, file_name: str, content: str) -> str:
|
|
if not self._is_valid_filename(file_name):
|
|
return INVALID_FILENAME_ERROR_MESSAGE
|
|
|
|
try:
|
|
path = self.dir / file_name
|
|
# Create a new executor for this operation
|
|
with ThreadPoolExecutor() as executor:
|
|
# Run file write in a thread to avoid blocking
|
|
await asyncio.get_event_loop().run_in_executor(executor, lambda: path.write_text(content))
|
|
return f'Data written to {file_name} successfully.'
|
|
except Exception:
|
|
return f"Error: Could not write to file '{file_name}'."
|
|
|
|
async def append_file(self, file_name: str, content: str) -> str:
|
|
if not self._is_valid_filename(file_name):
|
|
return INVALID_FILENAME_ERROR_MESSAGE
|
|
|
|
path = self.dir / file_name
|
|
if not path.exists():
|
|
return f"File '{file_name}' not found."
|
|
try:
|
|
# Create a new executor for this operation
|
|
with ThreadPoolExecutor() as executor:
|
|
# Run file append in a thread to avoid blocking
|
|
def append_to_file():
|
|
with path.open('a') as f:
|
|
f.write(content)
|
|
|
|
await asyncio.get_event_loop().run_in_executor(executor, append_to_file)
|
|
return f'Data appended to {file_name} successfully.'
|
|
except Exception as e:
|
|
return f"Error: Could not append to file '{file_name}'. {str(e)}"
|
|
|
|
def describe(self) -> str:
|
|
"""List all files with their line counts."""
|
|
description = ''
|
|
for f in self.dir.iterdir():
|
|
if f.is_file():
|
|
try:
|
|
num_lines = len(f.read_text().splitlines())
|
|
description += f'- {f.name} — {num_lines} lines\n'
|
|
except Exception:
|
|
description += f'- {f.name} — [error reading file]\n'
|
|
|
|
return description
|
|
|
|
def get_todo_contents(self) -> str:
|
|
return self.todo_file.read_text()
|