Replace the single-pass FE-driven agent_run/agent_data flow with a BE-orchestrated two-phase execution using LangChain tool-calling: - Phase 1 (Triage): explores directory via new filesystem tools, matches files to existing projects using PROJECT_TOOLS - Phase 2 (Processing): reads files and performs CRUD per project group with clean LLM context windows Key changes: - Add filesystem_agent.py with list_directory, read_file_content, get_file_metadata tools using execute_on_client() - Move setup journey from REST to WebSocket (journey_start/message frames) - Add batch_runs_per_day billing limit and enforce in /trigger - Remove deprecated agent_data/agent_complete frame handlers and queues Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
86 lines
2.5 KiB
Python
86 lines
2.5 KiB
Python
"""Filesystem agent — tools for reading local directories and files on Electron.
|
|
|
|
These tools delegate to the Electron client via ``execute_on_client()`` using
|
|
the same WS tool-call round-trip pattern as CRUD tools. The Electron app
|
|
handles actual disk I/O and responds with ``tool_result`` frames.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from langchain_core.tools import tool
|
|
|
|
from app.core.ws_context import execute_on_client
|
|
|
|
|
|
@tool
|
|
async def list_directory(path: str) -> str:
|
|
"""List files and folders in a local directory on the user's device.
|
|
|
|
Returns a formatted listing of entries with name, type (file/directory),
|
|
and full path.
|
|
"""
|
|
result = await execute_on_client(
|
|
action="list_directory",
|
|
data={"path": path},
|
|
)
|
|
entries: list[dict[str, Any]] = result.get("entries", [])
|
|
if not entries:
|
|
return f"Directory '{path}' is empty or does not exist."
|
|
lines: list[str] = []
|
|
for entry in entries:
|
|
entry_type = entry.get("type", "unknown")
|
|
entry_name = entry.get("name", "")
|
|
entry_path = entry.get("path", "")
|
|
lines.append(f"- [{entry_type}] {entry_name} ({entry_path})")
|
|
return f"Directory listing for '{path}' ({len(entries)} entries):\n" + "\n".join(lines)
|
|
|
|
|
|
@tool
|
|
async def read_file_content(path: str) -> str:
|
|
"""Read the text content of a local file on the user's device.
|
|
|
|
Returns the file content as a string. Large files may be truncated
|
|
by the Electron client.
|
|
"""
|
|
result = await execute_on_client(
|
|
action="read_file_content",
|
|
data={"path": path},
|
|
)
|
|
content: str = result.get("content", "")
|
|
if not content:
|
|
return f"File '{path}' is empty or could not be read."
|
|
return content
|
|
|
|
|
|
@tool
|
|
async def get_file_metadata(path: str) -> str:
|
|
"""Get metadata for a local file: size, creation date, modification date, extension.
|
|
|
|
Returns a formatted summary of the file's metadata.
|
|
"""
|
|
result = await execute_on_client(
|
|
action="get_file_metadata",
|
|
data={"path": path},
|
|
)
|
|
size = result.get("size", "unknown")
|
|
created = result.get("createdAt", "unknown")
|
|
modified = result.get("modifiedAt", "unknown")
|
|
extension = result.get("extension", "unknown")
|
|
name = result.get("name", path)
|
|
return (
|
|
f"File: {name}\n"
|
|
f" Extension: {extension}\n"
|
|
f" Size: {size} bytes\n"
|
|
f" Created: {created}\n"
|
|
f" Modified: {modified}"
|
|
)
|
|
|
|
|
|
FILESYSTEM_TOOLS: list[Any] = [
|
|
list_directory,
|
|
read_file_content,
|
|
get_file_metadata,
|
|
]
|