from __future__ import annotations from datetime import datetime from pathlib import Path import platform import subprocess import sys from typing import Any, Dict, List from .registry import ToolContext, ToolRegistry def build_default_registry() -> ToolRegistry: registry = ToolRegistry() registry.register( name="current_time", description="Get the current local time for the runtime.", parameters={"type": "object", "properties": {}, "additionalProperties": False}, handler=_current_time, ) registry.register( name="memory_add", description="Save a durable memory entry for future turns and sessions.", parameters={ "type": "object", "properties": { "content": {"type": "string"}, "kind": {"type": "string", "enum": ["memory", "preference", "project"]}, }, "required": ["content"], "additionalProperties": False, }, handler=_memory_add, ) registry.register( name="memory_list", description="List saved memory entries.", parameters={ "type": "object", "properties": { "kind": {"type": "string", "enum": ["memory", "preference", "project"]}, }, "additionalProperties": False, }, handler=_memory_list, ) registry.register( name="list_skills", description="List installed skills with name and description.", parameters={"type": "object", "properties": {}, "additionalProperties": False}, handler=_list_skills, ) registry.register( name="load_skill", description="Load a skill into the active session by skill name.", parameters={ "type": "object", "properties": { "name": {"type": "string", "description": "Skill name to activate."}, }, "required": ["name"], "additionalProperties": False, }, handler=_load_skill, ) registry.register( name="dispatch_task", description="Create one or more subtasks for the current goal.", parameters={ "type": "object", "properties": { "tasks": { "type": "array", "items": { "type": "object", "properties": { "title": {"type": "string"}, "description": {"type": "string"}, "depends_on": {"type": "array", "items": {"type": "string"}}, "assignee": {"type": "string"}, }, "required": ["title", "description"], "additionalProperties": False, }, } }, "required": ["tasks"], "additionalProperties": False, }, handler=_dispatch_task, ) registry.register( name="list_tasks", description="List the current task board.", parameters={"type": "object", "properties": {}, "additionalProperties": False}, handler=_list_tasks, ) registry.register( name="update_task", description="Update task status or metadata.", parameters={ "type": "object", "properties": { "task_id": {"type": "string"}, "status": { "type": "string", "enum": ["pending", "ready", "running", "blocked", "done"], }, "notes": {"type": "string"}, }, "required": ["task_id"], "additionalProperties": False, }, handler=_update_task, ) registry.register( name="read_file", description="Read a UTF-8 text file inside the workspace only.", parameters={ "type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"], "additionalProperties": False, }, handler=_read_file, ) registry.register( name="execute_shell", description="Run a shell command for environment inspection or task execution. Use this for OS, hardware, process, disk, and command-line checks.", parameters={ "type": "object", "properties": { "command": {"type": "string"}, "timeout_seconds": {"type": "integer", "minimum": 1, "maximum": 120}, }, "required": ["command"], "additionalProperties": False, }, handler=_execute_shell, ) registry.register( name="run_python", description="Run a short Python snippet when no built-in tool directly solves the task. Prefer printing concise results.", parameters={ "type": "object", "properties": { "code": {"type": "string"}, "timeout_seconds": {"type": "integer", "minimum": 1, "maximum": 120}, }, "required": ["code"], "additionalProperties": False, }, handler=_run_python, ) registry.register( name="write_file", description="Write a UTF-8 text file inside the workspace.", parameters={ "type": "object", "properties": { "path": {"type": "string"}, "content": {"type": "string"}, }, "required": ["path", "content"], "additionalProperties": False, }, handler=_write_file, ) return registry def _current_time(ctx: ToolContext, args: Dict[str, Any]) -> Dict[str, Any]: return {"success": True, "current_time": datetime.now().isoformat()} def _memory_add(ctx: ToolContext, args: Dict[str, Any]) -> Dict[str, Any]: if ctx.memory_store is None: return {"success": False, "error": "memory store is not configured"} entry = ctx.memory_store.add(args["content"], kind=args.get("kind", "memory")) return {"success": True, "entry": {"id": entry.id, "kind": entry.kind, "content": entry.content}} def _memory_list(ctx: ToolContext, args: Dict[str, Any]) -> Dict[str, Any]: if ctx.memory_store is None: return {"success": False, "error": "memory store is not configured"} return {"success": True, "entries": ctx.memory_store.list_entries(kind=args.get("kind"))} def _list_skills(ctx: ToolContext, args: Dict[str, Any]) -> Dict[str, Any]: return {"success": True, "skills": ctx.skill_store.list_skills()} def _load_skill(ctx: ToolContext, args: Dict[str, Any]) -> Dict[str, Any]: skill = ctx.skill_store.load_skill(args["name"]) active = ctx.session.setdefault("active_skills", []) if skill.name not in active: active.append(skill.name) return { "success": True, "skill": skill.summary(), "content": skill.content, "active_skills": active, } def _dispatch_task(ctx: ToolContext, args: Dict[str, Any]) -> Dict[str, Any]: created: List[Dict[str, Any]] = [] for item in args["tasks"]: task = ctx.dispatcher.create_task( title=item["title"], description=item["description"], depends_on=item.get("depends_on") or [], assignee=item.get("assignee"), ) created.append(task.to_dict()) return {"success": True, "created_tasks": created} def _list_tasks(ctx: ToolContext, args: Dict[str, Any]) -> Dict[str, Any]: return { "success": True, "tasks": ctx.dispatcher.list_tasks(), "next_ready_task": ctx.dispatcher.next_ready_task(), } def _update_task(ctx: ToolContext, args: Dict[str, Any]) -> Dict[str, Any]: metadata = {} if args.get("notes"): metadata["notes"] = args["notes"] task = ctx.dispatcher.update_task( args["task_id"], status=args.get("status"), metadata=metadata, ) return {"success": True, "task": task.to_dict()} def _read_file(ctx: ToolContext, args: Dict[str, Any]) -> Dict[str, Any]: path = _safe_workspace_path(ctx.workspace, args["path"]) return {"success": True, "path": str(path), "content": path.read_text(encoding="utf-8")} def _execute_shell(ctx: ToolContext, args: Dict[str, Any]) -> Dict[str, Any]: timeout = int(args.get("timeout_seconds", 20)) proc = subprocess.run( _default_shell_command(args["command"]), cwd=str(ctx.workspace), capture_output=True, text=True, timeout=timeout, shell=False, ) return { "success": proc.returncode == 0, "returncode": proc.returncode, "stdout": _trim_output(proc.stdout), "stderr": _trim_output(proc.stderr), "command": args["command"], } def _run_python(ctx: ToolContext, args: Dict[str, Any]) -> Dict[str, Any]: timeout = int(args.get("timeout_seconds", 20)) proc = subprocess.run( [sys.executable, "-c", args["code"]], cwd=str(ctx.workspace), capture_output=True, text=True, timeout=timeout, shell=False, ) return { "success": proc.returncode == 0, "returncode": proc.returncode, "stdout": _trim_output(proc.stdout), "stderr": _trim_output(proc.stderr), } def _write_file(ctx: ToolContext, args: Dict[str, Any]) -> Dict[str, Any]: path = _safe_workspace_path(ctx.workspace, args["path"]) path.parent.mkdir(parents=True, exist_ok=True) path.write_text(args["content"], encoding="utf-8") return {"success": True, "path": str(path), "bytes_written": len(args["content"].encode("utf-8"))} def _safe_workspace_path(workspace: Path, value: str) -> Path: candidate = (workspace / value).resolve() workspace = workspace.resolve() if candidate != workspace and workspace not in candidate.parents: raise ValueError(f"Path escapes workspace: {value}") return candidate def _default_shell_command(command: str) -> List[str]: if platform.system().lower().startswith("win"): return ["powershell", "-Command", command] return ["bash", "-lc", command] def _trim_output(text: str, limit: int = 12000) -> str: text = text or "" if len(text) <= limit: return text return text[:limit] + "\n...[truncated]"