Files
Anton 052591281d Initial clean version - Agent Maxim
- Complete agent system with planning and execution
- Bridge API with FastAPI server
- WhatsApp-style mobile dashboard
- Enhanced web search with query improvement
- Security guards and policy checks
- Comprehensive documentation
2026-04-09 18:07:43 +03:00

147 lines
4.8 KiB
Python

"""Agent adapter — executes the real local agent via subprocess.
Calls agent/main.py with the given instruction as --goal.
The planner keyword-matches on goal text, so the instruction
(which contains the original goal as a substring) routes correctly.
Run ID mapping:
bridge run_id : <uuid> (e.g. "a1b2c3d4")
agent run_id : "agent-<bridge_run_id>" (e.g. "agent-a1b2c3d4")
This makes every bridge run traceable to a unique agent workspace dir:
agent/workspace/runs/agent-<bridge_run_id>/state.json
"""
from __future__ import annotations
import json
import subprocess
import sys
import time
from pathlib import Path
from typing import Callable, Optional
from adapters.base import AdapterResult
MODE = "real"
# Resolve paths relative to this file: bridge/adapters/ → bridge/ → localagent/ → agent/
_BRIDGE_DIR: Path = Path(__file__).resolve().parents[1]
_AGENT_DIR: Path = _BRIDGE_DIR.parent / "agent"
_AGENT_MAIN: Path = _AGENT_DIR / "main.py"
def execute(prompt: str, bridge_run_id: Optional[str] = None, trace_id: Optional[str] = None, is_cancelled: Optional[Callable[[], bool]] = None) -> AdapterResult:
"""Run the agent with prompt as --goal, return its stdout + parsed state."""
print("=== THIS ADAPTER IS USED: agent_adapter ===")
if not _AGENT_MAIN.exists():
return AdapterResult(
response=None,
error=f"agent/main.py not found at {_AGENT_MAIN}",
exit_code=1,
mode=MODE,
)
agent_run_id = f"agent-{bridge_run_id}" if bridge_run_id else "agent-unlinked"
# Check if run is cancelled via callback
if is_cancelled and is_cancelled():
return AdapterResult(
response=None,
error="user_cancelled",
exit_code=130, # Exit code for cancelled
mode=MODE,
metadata={"agent_run_id": agent_run_id, "agent_final_status": "cancelled"},
)
cmd = [sys.executable, str(_AGENT_MAIN), "--goal", prompt, "--run-id", agent_run_id]
if trace_id:
cmd += ["--trace-id", trace_id]
_t_sub = time.time()
print("=== BEFORE AGENT SUBPROCESS ===")
try:
proc = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=60,
shell=False,
cwd=str(_AGENT_DIR),
)
except subprocess.TimeoutExpired:
print("=== AFTER AGENT SUBPROCESS ===")
print(f"[TIMING] subprocess: {time.time() - _t_sub:.3f}s [TIMEOUT]")
return AdapterResult(
response=None,
error="agent subprocess timed out after 60s",
exit_code=1,
mode=MODE,
metadata={"agent_run_id": agent_run_id},
)
except Exception as exc: # noqa: BLE001
print("=== AFTER AGENT SUBPROCESS ===")
print(f"[TIMING] subprocess: {time.time() - _t_sub:.3f}s [ERROR: {exc}]")
return AdapterResult(
response=None,
error=f"{type(exc).__name__}: {exc}",
exit_code=1,
mode=MODE,
metadata={"agent_run_id": agent_run_id},
)
print("=== AFTER AGENT SUBPROCESS ===")
print(f"[TIMING] subprocess: {time.time() - _t_sub:.3f}s")
stdout = proc.stdout.strip()
stderr = proc.stderr.strip()
print(f"[STDOUT]: {stdout[:300]}")
if stderr:
print(f"[STDERR]: {stderr[:300]}")
combined = stdout + ("\n" + stderr if stderr else "")
# Read agent's persisted state.json for structured result
agent_state_path = _AGENT_DIR / "workspace" / "runs" / agent_run_id / "state.json"
agent_final_status: Optional[str] = None
if agent_state_path.exists():
try:
agent_state = json.loads(agent_state_path.read_text(encoding="utf-8"))
agent_final_status = agent_state.get("status")
except Exception: # noqa: BLE001
pass
metadata = {
"agent_run_id": agent_run_id,
"agent_final_status": agent_final_status,
"agent_state_path": str(agent_state_path),
}
if proc.returncode != 0:
return AdapterResult(
response=combined or None,
error=f"agent exited {proc.returncode}" + (f": {stderr}" if stderr else ""),
exit_code=proc.returncode,
mode=MODE,
metadata=metadata,
)
# exit_code=0 but agent state missing or unreadable → treat as failure
if agent_final_status is None:
return AdapterResult(
response=combined or None,
error="agent state missing or invalid (exit 0 but state.json unreadable)",
exit_code=1,
mode=MODE,
metadata=metadata,
)
return AdapterResult(
response=combined,
error=None,
exit_code=0,
mode=MODE,
metadata=metadata,
)
def mode() -> str:
return MODE if _AGENT_MAIN.exists() else "unavailable"