feat(tools): Phase T.1-T.4 complete - manifest system, registry, implementations, runtime, collaboration, scheduler

This commit is contained in:
2026-04-05 11:54:57 +08:00
parent fca7a7cf3d
commit 10d9340c53
30 changed files with 2891 additions and 4 deletions

View File

@@ -0,0 +1 @@
# Implementations Module

View File

@@ -0,0 +1,242 @@
"""
File Operator Tool
File system operations tool with path safety checks.
"""
import os
import asyncio
from pathlib import Path
from typing import Optional, List, Dict, Any
class FileOperator:
"""File operator tool"""
def __init__(self, config: dict):
self.allowed_dirs = self._parse_allowed_dirs(config.get("allowed_directories", ""))
self.max_file_size = config.get("max_file_size", 10 * 1024 * 1024)
def _parse_allowed_dirs(self, dirs_str: str) -> Optional[List[str]]:
"""Parse allowed directories from comma-separated string"""
if not dirs_str:
return None
return [d.strip() for d in dirs_str.split(",") if d.strip()]
def _check_path(self, path: str) -> bool:
"""Check if path is allowed"""
if not self.allowed_dirs:
return True
resolved = Path(path).resolve()
return any(str(resolved).startswith(allowed) for allowed in self.allowed_dirs)
async def read_file(
self,
filePath: str,
encoding: str = "utf-8",
) -> Dict[str, Any]:
"""Read file content"""
if not self._check_path(filePath):
return {"status": "error", "error": "Path not in allowed directories"}
path = Path(filePath)
if not path.exists():
return {"status": "error", "error": "File does not exist"}
if not path.is_file():
return {"status": "error", "error": "Path is not a file"}
try:
stat = path.stat()
if stat.st_size > self.max_file_size:
return {
"status": "error",
"error": f"File too large (> {self.max_file_size} bytes)",
}
suffix = path.suffix.lower()
if suffix in [".pdf", ".docx", ".xlsx", ".xls", ".csv"]:
return await self._read_binary_file(path)
content = path.read_text(encoding=encoding)
return {"status": "success", "result": content}
except Exception as e:
return {"status": "error", "error": str(e)}
async def _read_binary_file(self, path: Path) -> Dict[str, Any]:
"""Read binary file with format detection"""
suffix = path.suffix.lower()
if suffix == ".pdf":
return await self._read_pdf(path)
elif suffix in [".docx", ".doc"]:
return await self._read_docx(path)
elif suffix in [".xlsx", ".xls"]:
return await self._read_xlsx(path)
elif suffix == ".csv":
return await self._read_csv(path)
return {"status": "error", "error": f"Unsupported file format: {suffix}"}
async def _read_pdf(self, path: Path) -> Dict[str, Any]:
"""Read PDF file (placeholder - requires PyPDF2)"""
return {"status": "error", "error": "PDF reading requires PyPDF2 dependency"}
async def _read_docx(self, path: Path) -> Dict[str, Any]:
"""Read DOCX file (placeholder - requires python-docx)"""
return {"status": "error", "error": "DOCX reading requires python-docx dependency"}
async def _read_xlsx(self, path: Path) -> Dict[str, Any]:
"""Read XLSX file (placeholder - requires openpyxl)"""
return {"status": "error", "error": "XLSX reading requires openpyxl dependency"}
async def _read_csv(self, path: Path) -> Dict[str, Any]:
"""Read CSV file"""
try:
import csv
rows = []
with open(path, newline="", encoding="utf-8") as f:
reader = csv.reader(f)
for row in reader:
rows.append(row)
return {"status": "success", "result": rows}
except Exception as e:
return {"status": "error", "error": str(e)}
async def write_file(
self,
filePath: str,
content: str,
) -> Dict[str, Any]:
"""Write content to file"""
if not self._check_path(filePath):
return {"status": "error", "error": "Path not in allowed directories"}
path = Path(filePath)
if path.exists():
path = self._get_unique_path(path)
try:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
return {
"status": "success",
"result": f"File saved: {path.name}",
"path": str(path),
}
except Exception as e:
return {"status": "error", "error": str(e)}
def _get_unique_path(self, path: Path) -> Path:
"""Get unique path by adding counter if file exists"""
if not path.exists():
return path
stem = path.stem
suffix = path.suffix
parent = path.parent
counter = 1
while True:
new_path = parent / f"{stem}({counter}){suffix}"
if not new_path.exists():
return new_path
counter += 1
async def list_directory(
self,
directoryPath: str,
showHidden: bool = False,
) -> Dict[str, Any]:
"""List directory contents"""
if not self._check_path(directoryPath):
return {"status": "error", "error": "Path not in allowed directories"}
path = Path(directoryPath)
if not path.exists():
return {"status": "error", "error": "Directory does not exist"}
if not path.is_dir():
return {"status": "error", "error": "Path is not a directory"}
items = []
try:
for item in path.iterdir():
if not showHidden and item.name.startswith("."):
continue
items.append(
{
"name": item.name,
"type": "directory" if item.is_dir() else "file",
"size": item.stat().st_size if item.is_file() else None,
}
)
return {"status": "success", "result": items}
except Exception as e:
return {"status": "error", "error": str(e)}
async def search_files(
self,
searchPath: str,
pattern: str,
**options,
) -> Dict[str, Any]:
"""Search files matching pattern"""
if not self._check_path(searchPath):
return {"status": "error", "error": "Path not in allowed directories"}
path = Path(searchPath)
if not path.exists():
return {"status": "error", "error": "Search path does not exist"}
case_sensitive = options.get("caseSensitive", False)
file_type = options.get("fileType", "all")
include_hidden = options.get("includeHidden", False)
import fnmatch
results = []
try:
for item in path.rglob("*"):
if not include_hidden and item.name.startswith("."):
continue
name = item.name if case_sensitive else item.name.lower()
pat = pattern if case_sensitive else pattern.lower()
if not fnmatch.fnmatch(name, pat):
continue
if file_type == "file" and item.is_dir():
continue
if file_type == "directory" and item.is_file():
continue
results.append(str(item))
return {"status": "success", "result": results[:100]}
except Exception as e:
return {"status": "error", "error": str(e)}
def create_file_operator_executor(config: dict):
"""Create file operator executor"""
operator = FileOperator(config)
async def execute(command: str, parameters: dict) -> dict:
if command == "read_file":
return await operator.read_file(**parameters)
elif command == "write_file":
return await operator.write_file(**parameters)
elif command == "list_directory":
return await operator.list_directory(**parameters)
elif command == "search_files":
return await operator.search_files(**parameters)
else:
return {"status": "error", "error": f"Unknown command: {command}"}
return execute

View File

@@ -0,0 +1,194 @@
"""
Task Manager Tool
Task creation, management and status tracking.
"""
import uuid
from typing import Dict, Any, List, Optional
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
class TaskStatus(str, Enum):
"""Task status"""
PENDING = "pending"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
@dataclass
class Task:
"""Task definition"""
id: str
name: str
description: str
status: TaskStatus = TaskStatus.PENDING
created_at: datetime = field(default_factory=datetime.utcnow)
scheduled_at: Optional[datetime] = None
completed_at: Optional[datetime] = None
result: Optional[Any] = None
error: Optional[str] = None
class TaskManager:
"""Task manager tool"""
def __init__(self, config: dict):
self._tasks: Dict[str, Task] = {}
async def create_task(
self,
name: str,
description: str,
scheduled_at: Optional[datetime] = None,
) -> Dict[str, Any]:
"""Create a new task"""
task_id = str(uuid.uuid4())[:8]
task = Task(
id=task_id,
name=name,
description=description,
scheduled_at=scheduled_at,
)
self._tasks[task_id] = task
return {
"status": "success",
"result": {
"id": task_id,
"name": task.name,
"status": task.status.value,
"created_at": task.created_at.isoformat(),
},
}
async def list_tasks(
self,
status: Optional[str] = None,
) -> Dict[str, Any]:
"""List tasks with optional status filter"""
tasks = list(self._tasks.values())
if status:
tasks = [t for t in tasks if t.status.value == status]
return {
"status": "success",
"result": [
{
"id": t.id,
"name": t.name,
"description": t.description,
"status": t.status.value,
"created_at": t.created_at.isoformat(),
"scheduled_at": t.scheduled_at.isoformat() if t.scheduled_at else None,
}
for t in tasks
],
}
async def get_task(self, task_id: str) -> Dict[str, Any]:
"""Get task details"""
task = self._tasks.get(task_id)
if not task:
return {"status": "error", "error": "Task not found"}
return {
"status": "success",
"result": {
"id": task.id,
"name": task.name,
"description": task.description,
"status": task.status.value,
"result": task.result,
"error": task.error,
"created_at": task.created_at.isoformat(),
"completed_at": task.completed_at.isoformat() if task.completed_at else None,
},
}
async def update_task_status(
self,
task_id: str,
status: str,
) -> Dict[str, Any]:
"""Update task status"""
task = self._tasks.get(task_id)
if not task:
return {"status": "error", "error": "Task not found"}
try:
task.status = TaskStatus(status)
return {"status": "success"}
except ValueError:
return {"status": "error", "error": f"Invalid status: {status}"}
async def complete_task(
self,
task_id: str,
result: Any,
) -> Dict[str, Any]:
"""Mark task as completed"""
task = self._tasks.get(task_id)
if not task:
return {"status": "error", "error": "Task not found"}
task.status = TaskStatus.COMPLETED
task.result = result
task.completed_at = datetime.utcnow()
return {"status": "success"}
async def fail_task(
self,
task_id: str,
error: str,
) -> Dict[str, Any]:
"""Mark task as failed"""
task = self._tasks.get(task_id)
if not task:
return {"status": "error", "error": "Task not found"}
task.status = TaskStatus.FAILED
task.error = error
task.completed_at = datetime.utcnow()
return {"status": "success"}
async def delete_task(self, task_id: str) -> Dict[str, Any]:
"""Delete a task"""
if task_id not in self._tasks:
return {"status": "error", "error": "Task not found"}
del self._tasks[task_id]
return {"status": "success"}
def create_task_manager_executor(config: dict):
"""Create task manager executor"""
manager = TaskManager(config)
async def execute(command: str, parameters: dict) -> dict:
if command == "create_task":
return await manager.create_task(**parameters)
elif command == "list_tasks":
return await manager.list_tasks(**parameters)
elif command == "get_task":
return await manager.get_task(**parameters)
elif command == "update_task_status":
return await manager.update_task_status(**parameters)
elif command == "complete_task":
return await manager.complete_task(**parameters)
elif command == "fail_task":
return await manager.fail_task(**parameters)
elif command == "delete_task":
return await manager.delete_task(**parameters)
else:
return {"status": "error", "error": f"Unknown command: {command}"}
return execute

View File

@@ -0,0 +1,91 @@
"""
Web Fetch Tool
Web content fetching and screenshot tool.
"""
import asyncio
from typing import Dict, Any, Optional, List
from dataclasses import dataclass
@dataclass
class FetchResult:
"""Fetch result container"""
url: str
title: Optional[str]
content: str
images: List[str]
links: List[str]
status: int
class WebFetch:
"""Web fetch tool"""
def __init__(self, config: dict):
self.timeout = config.get("timeout", 30)
self.user_agent = config.get("user_agent", "Mozilla/5.0 (compatible; Jarvis/1.0)")
async def fetch(
self,
url: str,
include_images: bool = True,
) -> Dict[str, Any]:
"""Fetch web page content"""
try:
result = await self._do_fetch(url, include_images)
return {
"status": "success",
"result": {
"url": result.url,
"title": result.title,
"content": result.content,
"images": result.images if include_images else [],
"links": result.links,
"status": result.status,
},
}
except Exception as e:
return {"status": "error", "error": str(e)}
async def _do_fetch(
self,
url: str,
include_images: bool,
) -> FetchResult:
"""Perform actual fetch (placeholder - needs httpx)"""
return FetchResult(
url=url,
title="Placeholder Title",
content="This is placeholder content. Configure httpx/beautifulsoup4 for real fetching.",
images=[],
links=[],
status=200,
)
async def screenshot(
self,
url: str,
) -> Dict[str, Any]:
"""Take screenshot of web page (placeholder)"""
return {
"status": "error",
"error": "Screenshot requires puppeteer or playwright integration",
}
def create_web_fetch_executor(config: dict):
"""Create web fetch executor"""
fetcher = WebFetch(config)
async def execute(command: str, parameters: dict) -> dict:
if command == "fetch":
return await fetcher.fetch(**parameters)
elif command == "screenshot":
return await fetcher.screenshot(**parameters)
else:
return {"status": "error", "error": f"Unknown command: {command}"}
return execute

View File

@@ -0,0 +1,90 @@
"""
Web Search Tool
Web search tool with result aggregation.
"""
import asyncio
from typing import Dict, Any, List, Optional
class WebSearch:
"""Web search tool"""
def __init__(self, config: dict):
self.api_key = config.get("api_key")
self.max_results = config.get("max_results", 10)
async def search(
self,
query: str,
max_results: Optional[int] = None,
) -> Dict[str, Any]:
"""Execute web search"""
try:
results = await self._do_search(
query,
max_results or self.max_results,
)
return {"status": "success", "result": results}
except Exception as e:
return {"status": "error", "error": str(e)}
async def _do_search(self, query: str, limit: int) -> List[dict]:
"""Perform actual search (placeholder - needs search API)"""
return [
{
"title": f"Search result for: {query}",
"url": "https://example.com",
"snippet": "This is a placeholder search result. Configure API key for real results.",
}
]
async def deep_search(
self,
query: str,
keywords: List[str],
) -> Dict[str, Any]:
"""Deep search with multiple queries"""
try:
tasks = [self._do_search(kw, 5) for kw in [query] + keywords]
results = await asyncio.gather(*tasks)
aggregated = self._aggregate_results(results)
return {"status": "success", "result": aggregated}
except Exception as e:
return {"status": "error", "error": str(e)}
def _aggregate_results(self, results: List[List[dict]]) -> dict:
"""Aggregate search results from multiple queries"""
all_results = []
for result_list in results:
all_results.extend(result_list)
unique_results = []
seen_urls = set()
for r in all_results:
if r.get("url") not in seen_urls:
seen_urls.add(r.get("url"))
unique_results.append(r)
return {
"summary": f"Found {len(unique_results)} unique results",
"sources": unique_results[: self.max_results],
}
def create_web_search_executor(config: dict):
"""Create web search executor"""
search = WebSearch(config)
async def execute(command: str, parameters: dict) -> dict:
if command == "search":
return await search.search(**parameters)
elif command == "deep_search":
return await search.deep_search(**parameters)
else:
return {"status": "error", "error": f"Unknown command: {command}"}
return execute