mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-27 12:04:49 +02:00
[Haik]: round 1 of absracting the agents dir. Leaving this as is for now then will circle back bc hella code needs to be redone here and de slopped
This commit is contained in:
@@ -1,132 +0,0 @@
|
||||
"""Mock agent and session-completed analytics.
|
||||
|
||||
Extracted from agent_loop.py to keep every file under 250 lines.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from uuid import uuid4
|
||||
|
||||
from backend.apps.agents.models import AgentSession, ApprovalRequest, Message
|
||||
from backend.apps.agents.ws_manager import ws_manager
|
||||
from backend.apps.analytics.collector import record as _analytics
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def stream_text(session_id: str, msg_id: str, text: str, delay: float = 0.03):
|
||||
await ws_manager.emit_stream_start(session_id, msg_id, "assistant")
|
||||
words = text.split(" ")
|
||||
for i, word in enumerate(words):
|
||||
chunk = word if i == 0 else " " + word
|
||||
await ws_manager.emit_stream_delta(session_id, msg_id, chunk)
|
||||
await asyncio.sleep(delay)
|
||||
await ws_manager.emit_stream_end(session_id, msg_id)
|
||||
|
||||
|
||||
async def stream_tool_input(session_id: str, msg_id: str, tool_name: str, input_json: str, delay: float = 0.02):
|
||||
await ws_manager.emit_stream_start(session_id, msg_id, "tool_call", tool_name=tool_name)
|
||||
chunk_size = 12
|
||||
for i in range(0, len(input_json), chunk_size):
|
||||
await ws_manager.emit_stream_delta(session_id, msg_id, input_json[i:i + chunk_size])
|
||||
await asyncio.sleep(delay)
|
||||
await ws_manager.emit_stream_end(session_id, msg_id)
|
||||
|
||||
|
||||
def fire_session_completed(session: AgentSession, sessions_dict: dict[str, AgentSession]):
|
||||
duration = 0.0
|
||||
if session.created_at:
|
||||
end = session.closed_at or datetime.now()
|
||||
duration = (end - session.created_at).total_seconds()
|
||||
tool_names = [
|
||||
m.content.get("tool", "") for m in session.messages
|
||||
if m.role == "tool_call" and isinstance(m.content, dict)
|
||||
]
|
||||
user_messages = [
|
||||
(m.content if isinstance(m.content, str) else str(m.content))[:200]
|
||||
for m in session.messages if m.role == "user"
|
||||
]
|
||||
_analytics("session.completed", {
|
||||
"model": session.model,
|
||||
"provider": getattr(session, "provider", "anthropic"),
|
||||
"mode": session.mode,
|
||||
"cost_usd": session.cost_usd,
|
||||
"message_count": len([m for m in session.messages if m.role in ("user", "assistant")]),
|
||||
"duration_seconds": round(duration, 1),
|
||||
"status": session.status,
|
||||
"tool_count": len(tool_names),
|
||||
"tools_list": list(set(tool_names)),
|
||||
"session_title": session.name,
|
||||
"first_user_message": user_messages[0] if user_messages else "",
|
||||
"input_tokens": session.tokens.get("input", 0),
|
||||
"output_tokens": session.tokens.get("output", 0),
|
||||
"is_sub_agent": session.parent_session_id is not None,
|
||||
"parent_session_id": session.parent_session_id,
|
||||
"sub_agent_count": len([s for s in sessions_dict.values() if s.parent_session_id == session.id]),
|
||||
"branch_count": len(session.branches),
|
||||
}, session_id=session.id, dashboard_id=session.dashboard_id)
|
||||
|
||||
|
||||
async def run_mock_agent(session_id: str, prompt: str, sessions: dict[str, AgentSession]):
|
||||
session = sessions.get(session_id)
|
||||
if not session:
|
||||
return
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
request_id = uuid4().hex
|
||||
approval_req = ApprovalRequest(
|
||||
id=request_id, session_id=session_id, tool_name="Bash",
|
||||
tool_input={"command": f"echo 'Processing: {prompt}'", "description": "Echo the user prompt"},
|
||||
)
|
||||
session.pending_approvals.append(approval_req)
|
||||
session.status = "waiting_approval"
|
||||
await ws_manager.emit_status(session_id, "waiting_approval")
|
||||
|
||||
decision = await ws_manager.send_approval_request(
|
||||
session_id, request_id, "Bash",
|
||||
{"command": f"echo 'Processing: {prompt}'", "description": "Echo the user prompt"},
|
||||
)
|
||||
|
||||
session.pending_approvals = [a for a in session.pending_approvals if a.id != request_id]
|
||||
session.status = "running"
|
||||
await ws_manager.emit_status(session_id, "running")
|
||||
|
||||
tool_input_content = {"tool": "Bash", "input": {"command": f"echo 'Processing: {prompt}'"}, "approved": decision.get("behavior") == "allow"}
|
||||
tool_msg_id = uuid4().hex
|
||||
await stream_tool_input(session_id, tool_msg_id, "Bash", json.dumps(tool_input_content["input"], indent=2))
|
||||
tool_msg = Message(id=tool_msg_id, role="tool_call", content=tool_input_content, branch_id=session.active_branch_id)
|
||||
session.messages.append(tool_msg)
|
||||
await ws_manager.emit_message(session_id, tool_msg)
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
if decision.get("behavior") == "allow":
|
||||
tool_result = Message(role="tool_result", content=f"Processing: {prompt}", branch_id=session.active_branch_id)
|
||||
session.messages.append(tool_result)
|
||||
await ws_manager.emit_message(session_id, tool_result)
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
asst_text = (
|
||||
f"I've processed your request: \"{prompt}\"\n\n"
|
||||
"This is a mock response because `claude-agent-sdk` is not installed. "
|
||||
"Install it with `pip install claude-agent-sdk` to use real Claude Code instances.\n\n"
|
||||
f"The agent was configured with:\n- Model: {session.model}\n- Mode: {session.mode}"
|
||||
)
|
||||
asst_msg_id = uuid4().hex
|
||||
await stream_text(session_id, asst_msg_id, asst_text)
|
||||
|
||||
asst_msg = Message(id=asst_msg_id, role="assistant", content=asst_text, branch_id=session.active_branch_id)
|
||||
session.messages.append(asst_msg)
|
||||
await ws_manager.emit_message(session_id, asst_msg)
|
||||
|
||||
session.status = "completed"
|
||||
session.closed_at = datetime.now()
|
||||
session.cost_usd = 0.001
|
||||
await ws_manager.emit_status(session_id, "completed", session)
|
||||
await ws_manager.emit_cost_update(session_id, session.cost_usd)
|
||||
@@ -1,11 +1,10 @@
|
||||
from backend.config.Apps import SubApp
|
||||
from backend.apps.agents.agent_manager import agent_manager
|
||||
from backend.apps.agents.ws_manager import ws_manager
|
||||
from backend.apps.agents.manager.agent_manager import agent_manager
|
||||
from backend.apps.agents.models import AgentConfig, ApprovalResponse
|
||||
from backend.apps.agents.browser.runner import run_browser_agents
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import HTTPException, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from uuid import uuid4
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -169,25 +168,9 @@ async def resume_session(session_id: str):
|
||||
return {"session": session.model_dump(mode="json")}
|
||||
|
||||
|
||||
@agents.router.post("/browser/command")
|
||||
async def browser_command(request: Request):
|
||||
"""Proxy browser commands to the frontend via WebSocket and wait for results."""
|
||||
body = await request.json()
|
||||
action = body.get("action", "")
|
||||
browser_id = body.get("browser_id", "")
|
||||
tab_id = body.get("tab_id", "")
|
||||
params = body.get("params", {})
|
||||
if not action or not browser_id:
|
||||
return JSONResponse({"error": "action and browser_id are required"}, status_code=400)
|
||||
request_id = uuid4().hex
|
||||
result = await ws_manager.send_browser_command(request_id, action, browser_id, params, tab_id=tab_id)
|
||||
return JSONResponse(result)
|
||||
|
||||
|
||||
@agents.router.post("/browser-agent/run")
|
||||
async def browser_agent_run(request: Request):
|
||||
"""Run one or more browser sub-agents in parallel."""
|
||||
from backend.apps.agents.browser_agent import run_browser_agents
|
||||
body = await request.json()
|
||||
tasks = body.get("tasks", [])
|
||||
if not tasks:
|
||||
|
||||
@@ -7,8 +7,8 @@ import logging
|
||||
from uuid import uuid4
|
||||
|
||||
from backend.apps.agents.models import AgentSession
|
||||
from backend.apps.agents.ws_manager import ws_manager
|
||||
from backend.apps.agents.approval import request_approval
|
||||
from backend.apps.agents.manager.ws_manager import ws_manager
|
||||
from backend.apps.agents.execution.approval import request_approval
|
||||
from backend.apps.agents.browser.schemas import ACTION_MAP
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -9,9 +9,17 @@ from datetime import datetime
|
||||
from uuid import uuid4
|
||||
|
||||
from backend.apps.agents.models import AgentSession, Message
|
||||
from backend.apps.agents.ws_manager import ws_manager
|
||||
from backend.apps.agents.manager.ws_manager import ws_manager
|
||||
from backend.apps.common.model_registry import resolve_model_id
|
||||
from backend.apps.tools_lib.tools_lib import load_builtin_permissions
|
||||
from backend.apps.agents.manager.agent_manager import agent_manager
|
||||
from backend.apps.settings.settings import load_settings
|
||||
from backend.apps.settings.credentials import get_anthropic_client
|
||||
from backend.apps.common.llm_helpers import _resolve_model as _resolve_9r
|
||||
from backend.apps.dashboards.dashboards import _load, _save
|
||||
from backend.apps.dashboards.models import BrowserCardPosition, BrowserTab
|
||||
from backend.apps.analytics.collector import record as _analytics
|
||||
|
||||
from backend.apps.agents.browser.schemas import (
|
||||
BROWSER_TOOLS_SCHEMA, SYSTEM_PROMPT, MAX_TURNS,
|
||||
)
|
||||
@@ -28,7 +36,6 @@ async def run_browser_agent(
|
||||
pre_selected: bool = False, initial_url: str | None = None,
|
||||
parent_session_id: str | None = None,
|
||||
) -> dict:
|
||||
from backend.apps.agents.agent_manager import agent_manager
|
||||
|
||||
_browser_perms = load_builtin_permissions()
|
||||
session_id = uuid4().hex
|
||||
@@ -49,9 +56,6 @@ async def run_browser_agent(
|
||||
logger.info(f"Browser agent {session_id}: navigated to {initial_url}: {nav_result.get('text', nav_result.get('error', ''))}")
|
||||
|
||||
api_model = resolve_model_id(model)
|
||||
from backend.apps.settings.settings import load_settings
|
||||
from backend.apps.settings.credentials import get_anthropic_client
|
||||
from backend.apps.common.llm_helpers import _resolve_model as _resolve_9r
|
||||
_settings = load_settings()
|
||||
api_model = _resolve_9r(api_model, _settings)
|
||||
client = get_anthropic_client(_settings)
|
||||
@@ -168,8 +172,6 @@ async def run_browser_agent(
|
||||
|
||||
|
||||
async def _create_browser_card(dashboard_id: str, url: str, parent_session_id: str | None = None) -> str:
|
||||
from backend.apps.dashboards.dashboards import _load, _save
|
||||
from backend.apps.dashboards.models import BrowserCardPosition, BrowserTab
|
||||
|
||||
dashboard = _load(dashboard_id)
|
||||
browser_id = f"browser-{uuid4().hex[:8]}"
|
||||
@@ -196,7 +198,6 @@ async def run_browser_agents(
|
||||
pre_selected_browser_ids: list[str] | None = None,
|
||||
parent_session_id: str | None = None,
|
||||
) -> list[dict]:
|
||||
from backend.apps.analytics.collector import record as _analytics
|
||||
_analytics("feature.used", {
|
||||
"feature": "browser_agent.launched", "task_count": len(tasks), "model": model,
|
||||
}, dashboard_id=dashboard_id)
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Backward-compatible shim — re-exports from the browser sub-package."""
|
||||
|
||||
from backend.apps.agents.browser.runner import ( # noqa: F401
|
||||
run_browser_agent,
|
||||
run_browser_agents,
|
||||
)
|
||||
from backend.apps.agents.browser.executor import ( # noqa: F401
|
||||
execute_browser_tool,
|
||||
)
|
||||
@@ -1,212 +0,0 @@
|
||||
"""Tool schema definitions for the browser MCP server."""
|
||||
|
||||
TAB_ID_PROP = {
|
||||
"type": "string",
|
||||
"description": "Optional tab ID within the browser card. If omitted, targets the active tab.",
|
||||
}
|
||||
|
||||
TOOLS = [
|
||||
{
|
||||
"name": "BrowserScreenshot",
|
||||
"description": (
|
||||
"Capture a screenshot of the browser page. Returns the screenshot as a "
|
||||
"base64-encoded PNG image. Use this to see what is currently displayed."
|
||||
),
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"browser_id": {
|
||||
"type": "string",
|
||||
"description": "The browser card ID to capture. Use the ID from the selected browser card context.",
|
||||
},
|
||||
"tab_id": TAB_ID_PROP,
|
||||
},
|
||||
"required": ["browser_id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "BrowserGetText",
|
||||
"description": (
|
||||
"Get the visible text content of the browser page. Returns the page's "
|
||||
"innerText (up to 15000 characters)."
|
||||
),
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"browser_id": {
|
||||
"type": "string",
|
||||
"description": "The browser card ID.",
|
||||
},
|
||||
"tab_id": TAB_ID_PROP,
|
||||
},
|
||||
"required": ["browser_id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "BrowserNavigate",
|
||||
"description": "Navigate the browser to a URL.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"browser_id": {
|
||||
"type": "string",
|
||||
"description": "The browser card ID.",
|
||||
},
|
||||
"tab_id": TAB_ID_PROP,
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "The URL to navigate to.",
|
||||
},
|
||||
},
|
||||
"required": ["browser_id", "url"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "BrowserClick",
|
||||
"description": (
|
||||
"Click an element in the browser page identified by a CSS selector."
|
||||
),
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"browser_id": {
|
||||
"type": "string",
|
||||
"description": "The browser card ID.",
|
||||
},
|
||||
"tab_id": TAB_ID_PROP,
|
||||
"selector": {
|
||||
"type": "string",
|
||||
"description": "CSS selector of the element to click.",
|
||||
},
|
||||
},
|
||||
"required": ["browser_id", "selector"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "BrowserType",
|
||||
"description": (
|
||||
"Type text into an input element in the browser page. Clears the "
|
||||
"existing value first, then types the new text."
|
||||
),
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"browser_id": {
|
||||
"type": "string",
|
||||
"description": "The browser card ID.",
|
||||
},
|
||||
"tab_id": TAB_ID_PROP,
|
||||
"selector": {
|
||||
"type": "string",
|
||||
"description": "CSS selector of the input element.",
|
||||
},
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "The text to type.",
|
||||
},
|
||||
},
|
||||
"required": ["browser_id", "selector", "text"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "BrowserEvaluate",
|
||||
"description": (
|
||||
"Evaluate a JavaScript expression in the browser page and return the result. "
|
||||
"The expression is run via executeJavaScript on the webview."
|
||||
),
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"browser_id": {
|
||||
"type": "string",
|
||||
"description": "The browser card ID.",
|
||||
},
|
||||
"tab_id": TAB_ID_PROP,
|
||||
"expression": {
|
||||
"type": "string",
|
||||
"description": "JavaScript expression to evaluate.",
|
||||
},
|
||||
},
|
||||
"required": ["browser_id", "expression"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "BrowserGetElements",
|
||||
"description": (
|
||||
"Get a list of interactive elements on the page with their CSS selectors. "
|
||||
"Returns clickable elements, inputs, links, and buttons with selector paths "
|
||||
"you can use with BrowserClick and BrowserType. Call this BEFORE attempting "
|
||||
"to click or type so you know which selectors are valid."
|
||||
),
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"browser_id": {
|
||||
"type": "string",
|
||||
"description": "The browser card ID.",
|
||||
},
|
||||
"tab_id": TAB_ID_PROP,
|
||||
"selector": {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"Optional CSS selector to scope the search "
|
||||
"(e.g. 'form', '#main'). Defaults to 'body'."
|
||||
),
|
||||
},
|
||||
},
|
||||
"required": ["browser_id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "BrowserScroll",
|
||||
"description": (
|
||||
"Scroll the page up or down. Automatically finds the correct scrollable "
|
||||
"container (works on SPAs like Notion, Gmail, etc. that use nested scroll "
|
||||
"containers instead of window-level scrolling). Returns scroll position info "
|
||||
"including whether top/bottom has been reached."
|
||||
),
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"browser_id": {
|
||||
"type": "string",
|
||||
"description": "The browser card ID.",
|
||||
},
|
||||
"tab_id": TAB_ID_PROP,
|
||||
"direction": {
|
||||
"type": "string",
|
||||
"enum": ["up", "down"],
|
||||
"description": "Scroll direction. Defaults to 'down'.",
|
||||
},
|
||||
"amount": {
|
||||
"type": "number",
|
||||
"description": "Pixels to scroll. Defaults to 500.",
|
||||
},
|
||||
},
|
||||
"required": ["browser_id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "BrowserWait",
|
||||
"description": (
|
||||
"Wait for a specified duration. Useful after navigation or actions that "
|
||||
"trigger page loads, animations, or async content rendering. "
|
||||
"Min 100ms, max 10000ms."
|
||||
),
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"browser_id": {
|
||||
"type": "string",
|
||||
"description": "The browser card ID.",
|
||||
},
|
||||
"tab_id": TAB_ID_PROP,
|
||||
"milliseconds": {
|
||||
"type": "number",
|
||||
"description": "Duration to wait in milliseconds. Defaults to 1000.",
|
||||
},
|
||||
},
|
||||
"required": ["browser_id"],
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -1,180 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Minimal stdio MCP server that exposes browser interaction tools.
|
||||
|
||||
Launched as a subprocess by the Claude Agent SDK. Proxies tool calls
|
||||
to the OpenSwarm backend via HTTP, which bridges them to the Electron
|
||||
frontend via WebSocket where the actual webview lives.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
from io import BytesIO
|
||||
|
||||
try:
|
||||
from PIL import Image
|
||||
HAS_PIL = True
|
||||
except ImportError:
|
||||
HAS_PIL = False
|
||||
|
||||
from browser_mcp_schemas import TOOLS # noqa: E402 (sibling script import)
|
||||
|
||||
BACKEND_PORT = os.environ.get("OPENSWARM_PORT", "8325")
|
||||
BACKEND_URL = f"http://127.0.0.1:{BACKEND_PORT}/api/agents/browser/command"
|
||||
|
||||
|
||||
def send_response(id_, result=None, error=None):
|
||||
msg = {"jsonrpc": "2.0", "id": id_}
|
||||
if error is not None:
|
||||
msg["error"] = error
|
||||
else:
|
||||
msg["result"] = result
|
||||
sys.stdout.write(json.dumps(msg) + "\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
def call_backend(action: str, browser_id: str, params: dict | None = None, tab_id: str = "") -> dict:
|
||||
payload = json.dumps({
|
||||
"action": action,
|
||||
"browser_id": browser_id,
|
||||
"tab_id": tab_id,
|
||||
"params": params or {},
|
||||
}).encode()
|
||||
req = urllib.request.Request(
|
||||
BACKEND_URL,
|
||||
data=payload,
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
return json.loads(resp.read().decode())
|
||||
except urllib.error.HTTPError as e:
|
||||
body = e.read().decode() if e.fp else str(e)
|
||||
return {"error": f"HTTP {e.code}: {body}"}
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
MAX_IMAGE_B64_BYTES = 400_000
|
||||
|
||||
|
||||
def compress_screenshot(b64_png: str) -> tuple[str, str] | None:
|
||||
"""Resize and re-encode as JPEG to stay under the stdio buffer limit."""
|
||||
if not HAS_PIL:
|
||||
return None
|
||||
try:
|
||||
raw = base64.b64decode(b64_png)
|
||||
img = Image.open(BytesIO(raw))
|
||||
max_width = 1024
|
||||
if img.width > max_width:
|
||||
ratio = max_width / img.width
|
||||
img = img.resize((max_width, int(img.height * ratio)), Image.LANCZOS)
|
||||
buf = BytesIO()
|
||||
img.convert("RGB").save(buf, format="JPEG", quality=45)
|
||||
return base64.b64encode(buf.getvalue()).decode(), "image/jpeg"
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def handle_tool_call(tool_name: str, arguments: dict) -> dict:
|
||||
browser_id = arguments.get("browser_id", "")
|
||||
tab_id = arguments.get("tab_id", "")
|
||||
if not browser_id:
|
||||
return {"content": [{"type": "text", "text": "Error: browser_id is required"}], "isError": True}
|
||||
|
||||
action_map = {
|
||||
"BrowserScreenshot": "screenshot",
|
||||
"BrowserGetText": "get_text",
|
||||
"BrowserNavigate": "navigate",
|
||||
"BrowserClick": "click",
|
||||
"BrowserType": "type",
|
||||
"BrowserEvaluate": "evaluate",
|
||||
"BrowserGetElements": "get_elements",
|
||||
"BrowserScroll": "scroll",
|
||||
"BrowserWait": "wait",
|
||||
}
|
||||
action = action_map.get(tool_name)
|
||||
if not action:
|
||||
return {"content": [{"type": "text", "text": f"Unknown tool: {tool_name}"}], "isError": True}
|
||||
|
||||
params = {k: v for k, v in arguments.items() if k not in ("browser_id", "tab_id")}
|
||||
result = call_backend(action, browser_id, params, tab_id=tab_id)
|
||||
|
||||
if "error" in result:
|
||||
return {"content": [{"type": "text", "text": f"Error: {result['error']}"}], "isError": True}
|
||||
|
||||
if action == "screenshot" and result.get("image"):
|
||||
image_data = result["image"]
|
||||
mime_type = "image/png"
|
||||
|
||||
if len(image_data) > MAX_IMAGE_B64_BYTES:
|
||||
compressed = compress_screenshot(image_data)
|
||||
if compressed:
|
||||
image_data, mime_type = compressed
|
||||
|
||||
if len(image_data) > MAX_IMAGE_B64_BYTES:
|
||||
return {
|
||||
"content": [
|
||||
{"type": "text", "text": (
|
||||
f"Screenshot too large to return ({len(image_data)} bytes base64). "
|
||||
f"URL: {result.get('url', 'unknown')}. "
|
||||
"Use BrowserGetText to read the page content instead."
|
||||
)},
|
||||
],
|
||||
}
|
||||
|
||||
return {
|
||||
"content": [
|
||||
{"type": "image", "data": image_data, "mimeType": mime_type},
|
||||
{"type": "text", "text": f"Screenshot captured. URL: {result.get('url', 'unknown')}"},
|
||||
],
|
||||
}
|
||||
|
||||
text = result.get("text", result.get("data", json.dumps(result)))
|
||||
return {"content": [{"type": "text", "text": str(text)}]}
|
||||
|
||||
|
||||
def main():
|
||||
for line in sys.stdin:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
msg = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
method = msg.get("method")
|
||||
id_ = msg.get("id")
|
||||
params = msg.get("params", {})
|
||||
|
||||
if method == "initialize":
|
||||
send_response(id_, {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {"tools": {}},
|
||||
"serverInfo": {
|
||||
"name": "openswarm-browser",
|
||||
"version": "1.0.0",
|
||||
},
|
||||
})
|
||||
elif method == "notifications/initialized":
|
||||
pass
|
||||
elif method == "tools/list":
|
||||
send_response(id_, {"tools": TOOLS})
|
||||
elif method == "tools/call":
|
||||
tool_name = params.get("name", "")
|
||||
arguments = params.get("arguments", {})
|
||||
result = handle_tool_call(tool_name, arguments)
|
||||
send_response(id_, result)
|
||||
elif method == "ping":
|
||||
send_response(id_, {})
|
||||
elif id_ is not None:
|
||||
send_response(id_, error={"code": -32601, "message": f"Method not found: {method}"})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -14,10 +14,11 @@ from datetime import datetime
|
||||
from uuid import uuid4
|
||||
|
||||
from backend.apps.agents.models import AgentSession, Message
|
||||
from backend.apps.agents.ws_manager import ws_manager
|
||||
from backend.apps.agents.approval import request_approval
|
||||
from backend.apps.agents.mcp_builder import get_effective_policy
|
||||
from backend.apps.agents.manager.ws_manager import ws_manager
|
||||
from backend.apps.agents.execution.approval import request_approval
|
||||
from backend.apps.agents.execution.mcp_builder import get_effective_policy
|
||||
from backend.apps.analytics.collector import record as _analytics
|
||||
import asyncio
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -38,7 +39,7 @@ def create_sdk_hooks(
|
||||
safe_input = tool_input if isinstance(tool_input, dict) else {}
|
||||
return await request_approval(session, tool_name, safe_input, track_analytics=True)
|
||||
|
||||
async def can_use_tool(tool_name, input_data, context):
|
||||
async def can_use_tool(tool_name, input_data):
|
||||
if tool_name != "AskUserQuestion":
|
||||
policy = get_effective_policy(tool_name, builtin_perms)
|
||||
if policy == "always_allow":
|
||||
@@ -50,7 +51,7 @@ def create_sdk_hooks(
|
||||
return PermissionResultAllow(updated_input=decision.get("updated_input", input_data))
|
||||
return PermissionResultDeny(message=decision.get("message", "User denied this action"))
|
||||
|
||||
async def pre_tool_hook(input_data, tool_use_id, context):
|
||||
async def pre_tool_hook(input_data, tool_use_id):
|
||||
tool_name = input_data.get("tool_name", "")
|
||||
hook_event = input_data.get("hook_event_name", "PreToolUse")
|
||||
if tool_name and tool_name != "AskUserQuestion":
|
||||
@@ -69,7 +70,7 @@ def create_sdk_hooks(
|
||||
tool_start_times[tool_use_id] = time.time()
|
||||
return {}
|
||||
|
||||
async def post_tool_hook(input_data, tool_use_id, context):
|
||||
async def post_tool_hook(input_data, tool_use_id):
|
||||
elapsed_ms = None
|
||||
if tool_use_id and tool_use_id in tool_start_times:
|
||||
elapsed_ms = int((time.time() - tool_start_times.pop(tool_use_id)) * 1000)
|
||||
@@ -176,6 +177,5 @@ def _build_sub_agent_session(
|
||||
dashboard_id=session.dashboard_id, parent_session_id=session_id,
|
||||
)
|
||||
sessions[sub_session_id] = sub_session
|
||||
import asyncio
|
||||
asyncio.ensure_future(_broadcast_sub_session(sub_session))
|
||||
return sub_session_id
|
||||
@@ -13,15 +13,25 @@ import logging
|
||||
from uuid import uuid4
|
||||
|
||||
from backend.apps.agents.models import AgentSession, Message
|
||||
from backend.apps.agents.ws_manager import ws_manager
|
||||
from backend.apps.agents.session_store import save_session
|
||||
from backend.apps.agents.prompt_builder import build_prompt_content
|
||||
from backend.apps.agents.manager.ws_manager import ws_manager
|
||||
from backend.apps.agents.manager.session_store import save_session
|
||||
from backend.apps.agents.execution.prompt_builder import build_prompt_content
|
||||
from backend.apps.tools_lib.tools_lib import (
|
||||
_load_all as load_all_tools,
|
||||
load_builtin_permissions,
|
||||
)
|
||||
from backend.apps.analytics.collector import record as _analytics
|
||||
from backend.apps.agents.agent_mock import run_mock_agent, fire_session_completed
|
||||
from backend.apps.agents.execution.agent_hooks import create_sdk_hooks
|
||||
|
||||
from claude_agent_sdk import (
|
||||
query, ClaudeAgentOptions, AssistantMessage, ResultMessage,
|
||||
)
|
||||
from claude_agent_sdk.types import (
|
||||
PermissionResultAllow, PermissionResultDeny,
|
||||
TextBlock, ToolUseBlock, StreamEvent, SystemMessage,
|
||||
)
|
||||
from backend.apps.agents.execution.agent_options import build_agent_options
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -47,30 +57,14 @@ async def run_agent_loop(
|
||||
load_all_tools_fn=load_all_tools,
|
||||
)
|
||||
|
||||
try:
|
||||
from claude_agent_sdk import (
|
||||
query, ClaudeAgentOptions, AssistantMessage, ResultMessage,
|
||||
)
|
||||
from claude_agent_sdk.types import (
|
||||
HookMatcher, PermissionResultAllow, PermissionResultDeny,
|
||||
TextBlock, ToolUseBlock, StreamEvent, SystemMessage,
|
||||
)
|
||||
except ImportError:
|
||||
logger.warning("claude_agent_sdk not installed, running in mock mode")
|
||||
await run_mock_agent(session_id, prompt, sessions)
|
||||
return
|
||||
|
||||
session.status = "running"
|
||||
builtin_perms = load_builtin_permissions()
|
||||
|
||||
from backend.apps.agents.agent_hooks import create_sdk_hooks
|
||||
can_use_tool, pre_tool_hook, post_tool_hook = create_sdk_hooks(
|
||||
session, session_id, sessions, builtin_perms,
|
||||
PermissionResultAllow, PermissionResultDeny,
|
||||
)
|
||||
|
||||
from backend.apps.agents.agent_options import build_agent_options
|
||||
|
||||
try:
|
||||
options_kwargs = await build_agent_options(
|
||||
session, builtin_perms, can_use_tool, pre_tool_hook, post_tool_hook,
|
||||
@@ -0,0 +1,48 @@
|
||||
"""Mock agent and session-completed analytics.
|
||||
|
||||
Extracted from agent_loop.py to keep every file under 250 lines.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from backend.apps.agents.models import AgentSession
|
||||
from backend.apps.analytics.collector import record as _analytics
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def fire_session_completed(session: AgentSession, sessions_dict: dict[str, AgentSession]):
|
||||
duration = 0.0
|
||||
if session.created_at:
|
||||
end = session.closed_at or datetime.now()
|
||||
duration = (end - session.created_at).total_seconds()
|
||||
tool_names = [
|
||||
m.content.get("tool", "") for m in session.messages
|
||||
if m.role == "tool_call" and isinstance(m.content, dict)
|
||||
]
|
||||
user_messages = [
|
||||
(m.content if isinstance(m.content, str) else str(m.content))[:200]
|
||||
for m in session.messages if m.role == "user"
|
||||
]
|
||||
_analytics("session.completed", {
|
||||
"model": session.model,
|
||||
"provider": getattr(session, "provider", "anthropic"),
|
||||
"mode": session.mode,
|
||||
"cost_usd": session.cost_usd,
|
||||
"message_count": len([m for m in session.messages if m.role in ("user", "assistant")]),
|
||||
"duration_seconds": round(duration, 1),
|
||||
"status": session.status,
|
||||
"tool_count": len(tool_names),
|
||||
"tools_list": list(set(tool_names)),
|
||||
"session_title": session.name,
|
||||
"first_user_message": user_messages[0] if user_messages else "",
|
||||
"input_tokens": session.tokens.get("input", 0),
|
||||
"output_tokens": session.tokens.get("output", 0),
|
||||
"is_sub_agent": session.parent_session_id is not None,
|
||||
"parent_session_id": session.parent_session_id,
|
||||
"sub_agent_count": len([s for s in sessions_dict.values() if s.parent_session_id == session.id]),
|
||||
"branch_count": len(session.branches),
|
||||
}, session_id=session.id, dashboard_id=session.dashboard_id)
|
||||
|
||||
+15
-10
@@ -11,23 +11,33 @@ import os
|
||||
import sys
|
||||
|
||||
from backend.apps.agents.models import AgentSession
|
||||
from backend.apps.agents.prompt_builder import resolve_mode, compose_system_prompt
|
||||
from backend.apps.agents.prompt_context import (
|
||||
from backend.apps.agents.execution.prompt_builder import resolve_mode, compose_system_prompt
|
||||
from backend.apps.agents.execution.prompt_context import (
|
||||
build_connected_tools_context, build_outputs_context,
|
||||
build_browser_context, get_pre_selected_browser_ids,
|
||||
)
|
||||
from backend.apps.agents.mcp_builder import (
|
||||
from backend.apps.agents.execution.mcp_builder import (
|
||||
FULL_TOOLS, build_mcp_servers, get_all_tool_names,
|
||||
_get_denied_tool_names, _get_all_known_tool_names, _is_fully_denied,
|
||||
)
|
||||
from backend.apps.settings.settings import load_settings
|
||||
from backend.apps.tools_lib.tools_lib import (
|
||||
_load_all as load_all_tools,
|
||||
load_builtin_permissions,
|
||||
_load_all as load_all_tools
|
||||
)
|
||||
from backend.apps.common.mcp_utils import sanitize_server_name as _sanitize_server_name
|
||||
from backend.ports import BACKEND_DEV_PORT, NINE_ROUTER_PORT
|
||||
|
||||
from claude_agent_sdk.types import HookMatcher
|
||||
|
||||
from backend.apps.outputs.view_builder_templates import VIEW_BUILDER_SKILL
|
||||
|
||||
from backend.apps.common.model_registry import resolve_model_id as _resolve_mid
|
||||
|
||||
from backend.apps.nine_router import is_running as _9r_running
|
||||
|
||||
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -45,8 +55,6 @@ async def build_agent_options(
|
||||
Requires claude_agent_sdk types to be imported by the caller; they are
|
||||
passed in via the hook callables.
|
||||
"""
|
||||
from claude_agent_sdk import ClaudeAgentOptions
|
||||
from claude_agent_sdk.types import HookMatcher
|
||||
|
||||
_, mode_sys_prompt, _ = resolve_mode(session.mode, get_all_tool_names)
|
||||
connected_tools_ctx = build_connected_tools_context(
|
||||
@@ -61,7 +69,6 @@ async def build_agent_options(
|
||||
)
|
||||
|
||||
if session.mode == "view-builder":
|
||||
from backend.apps.outputs.view_builder_templates import VIEW_BUILDER_SKILL
|
||||
skill_block = f"<app_builder_reference>\n{VIEW_BUILDER_SKILL}\n</app_builder_reference>"
|
||||
composed_prompt = f"{composed_prompt}\n\n{skill_block}" if composed_prompt else skill_block
|
||||
|
||||
@@ -122,7 +129,6 @@ async def build_agent_options(
|
||||
"include_partial_messages": True,
|
||||
}
|
||||
|
||||
from backend.apps.nine_router import is_running as _9r_running
|
||||
if global_settings.anthropic_api_key:
|
||||
options_kwargs["env"] = {"ANTHROPIC_API_KEY": global_settings.anthropic_api_key}
|
||||
logger.info("[MCP-DEBUG] Using direct API key")
|
||||
@@ -132,7 +138,6 @@ async def build_agent_options(
|
||||
"ANTHROPIC_BASE_URL": f"http://localhost:{NINE_ROUTER_PORT}",
|
||||
}
|
||||
options_kwargs["extra_args"] = {"bare": None}
|
||||
from backend.apps.common.model_registry import resolve_model_id as _resolve_mid
|
||||
resolved = _resolve_mid(session.model)
|
||||
if not resolved.startswith("cc/"):
|
||||
options_kwargs["model"] = f"cc/{resolved}"
|
||||
@@ -11,7 +11,10 @@ from datetime import datetime
|
||||
from uuid import uuid4
|
||||
|
||||
from backend.apps.agents.models import AgentSession, ApprovalRequest
|
||||
from backend.apps.agents.ws_manager import ws_manager
|
||||
from backend.apps.agents.manager.ws_manager import ws_manager
|
||||
|
||||
from backend.apps.analytics.collector import record as _analytics
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -40,7 +43,6 @@ async def request_approval(
|
||||
session.status = "waiting_approval"
|
||||
|
||||
if track_analytics:
|
||||
from backend.apps.analytics.collector import record as _analytics
|
||||
_analytics("approval.requested", {
|
||||
"tool_name": tool_name,
|
||||
"is_first_approval_in_session": len(session.pending_approvals) == 1,
|
||||
@@ -63,7 +65,6 @@ async def request_approval(
|
||||
)
|
||||
|
||||
if track_analytics:
|
||||
from backend.apps.analytics.collector import record as _analytics
|
||||
latency_ms = int((datetime.now() - approval_req.created_at).total_seconds() * 1000)
|
||||
_analytics("approval.resolved", {
|
||||
"tool_name": tool_name,
|
||||
+3
-2
@@ -11,7 +11,9 @@ from typing import Any
|
||||
|
||||
from backend.apps.modes.modes import load_mode
|
||||
from backend.apps.common.mcp_utils import sanitize_server_name as _sanitize_server_name
|
||||
from backend.apps.agents.prompt_context import resolve_context_paths
|
||||
from backend.apps.agents.execution.prompt_context import resolve_context_paths
|
||||
from backend.apps.tools_lib.models import BUILTIN_TOOLS
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -47,7 +49,6 @@ def resolve_forced_tools(
|
||||
) -> str:
|
||||
if not forced_tools:
|
||||
return ""
|
||||
from backend.apps.tools_lib.models import BUILTIN_TOOLS
|
||||
desc_map: dict[str, str] = {t.name: t.description for t in BUILTIN_TOOLS}
|
||||
tool_to_server: dict[str, str] = {}
|
||||
tool_to_email: dict[str, str] = {}
|
||||
+3
-2
@@ -13,6 +13,9 @@ import os
|
||||
from backend.apps.outputs.outputs import _load_all as load_all_outputs
|
||||
from backend.apps.common.mcp_utils import sanitize_server_name as _sanitize_server_name
|
||||
|
||||
from backend.apps.dashboards.dashboards import _load as load_dashboard
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -109,7 +112,6 @@ def build_browser_context(
|
||||
if not dashboard_id:
|
||||
return None
|
||||
try:
|
||||
from backend.apps.dashboards.dashboards import _load as load_dashboard
|
||||
dashboard = load_dashboard(dashboard_id)
|
||||
except Exception:
|
||||
return None
|
||||
@@ -160,7 +162,6 @@ def get_pre_selected_browser_ids(dashboard_id: str | None) -> list[str]:
|
||||
if not dashboard_id:
|
||||
return []
|
||||
try:
|
||||
from backend.apps.dashboards.dashboards import _load as load_dashboard
|
||||
dashboard = load_dashboard(dashboard_id)
|
||||
except Exception:
|
||||
return []
|
||||
+11
-11
@@ -19,25 +19,26 @@ from typing import Optional
|
||||
from uuid import uuid4
|
||||
|
||||
from backend.apps.agents.models import AgentConfig, AgentSession, Message
|
||||
from backend.apps.agents.ws_manager import ws_manager
|
||||
from backend.apps.agents.prompt_builder import resolve_mode
|
||||
from backend.apps.agents.mcp_builder import get_all_tool_names
|
||||
from backend.apps.agents.session_store import (
|
||||
delete_session_file, get_history,
|
||||
from backend.apps.agents.manager.ws_manager import ws_manager
|
||||
from backend.apps.agents.execution.prompt_builder import resolve_mode
|
||||
from backend.apps.agents.execution.mcp_builder import get_all_tool_names
|
||||
from backend.apps.agents.manager.session_store import (
|
||||
get_history,
|
||||
reconcile_on_startup, get_browser_agent_children,
|
||||
)
|
||||
from backend.apps.agents.agent_loop import run_agent_loop
|
||||
from backend.apps.agents.agent_manager_ops import (
|
||||
from backend.apps.agents.execution.agent_loop import run_agent_loop
|
||||
from backend.apps.agents.manager.agent_manager_ops import (
|
||||
edit_message_op, close_session_op, resume_session_op,
|
||||
duplicate_session_op, invoke_agent_op,
|
||||
duplicate_session_op, invoke_agent_op
|
||||
)
|
||||
from backend.apps.agents.agent_manager_meta import (
|
||||
from backend.apps.agents.manager.agent_manager_meta import (
|
||||
generate_title_op, generate_group_meta_op,
|
||||
persist_all_sessions_op, restore_all_sessions_op,
|
||||
persist_all_sessions_op, restore_all_sessions_op, delete_session_op
|
||||
)
|
||||
from backend.apps.settings.settings import load_settings
|
||||
from backend.apps.analytics.collector import record as _analytics
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
os.environ.setdefault("CLAUDE_CODE_STREAM_CLOSE_TIMEOUT", "3600000")
|
||||
@@ -201,7 +202,6 @@ class AgentManager:
|
||||
await close_session_op(self.sessions, self.tasks, session_id)
|
||||
|
||||
async def delete_session(self, session_id: str) -> None:
|
||||
from backend.apps.agents.agent_manager_ops import delete_session_op
|
||||
await delete_session_op(self, session_id)
|
||||
|
||||
async def resume_session(self, session_id: str) -> AgentSession:
|
||||
+5
-4
@@ -9,11 +9,14 @@ import asyncio
|
||||
import logging
|
||||
|
||||
from backend.apps.agents.models import AgentSession, ToolGroupMeta
|
||||
from backend.apps.agents.ws_manager import ws_manager
|
||||
from backend.apps.agents.session_store import (
|
||||
from backend.apps.agents.manager.ws_manager import ws_manager
|
||||
from backend.apps.agents.manager.session_store import (
|
||||
save_session, delete_session_file, build_search_text,
|
||||
)
|
||||
from backend.apps.common.llm_helpers import quick_llm_call, quick_llm_json
|
||||
from backend.apps.agents.manager.session_store import load_all_session_data
|
||||
from backend.apps.agents.execution.agent_mock import fire_session_completed
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -75,7 +78,6 @@ async def generate_group_meta_op(
|
||||
|
||||
|
||||
async def persist_all_sessions_op(sessions: dict, tasks: dict) -> None:
|
||||
from backend.apps.agents.agent_mock import fire_session_completed
|
||||
for session_id, session in list(sessions.items()):
|
||||
if session.status in ("running", "waiting_approval"):
|
||||
session.status = "stopped"
|
||||
@@ -92,7 +94,6 @@ async def persist_all_sessions_op(sessions: dict, tasks: dict) -> None:
|
||||
|
||||
|
||||
async def restore_all_sessions_op(sessions: dict) -> None:
|
||||
from backend.apps.agents.session_store import load_all_session_data
|
||||
for sid, data in load_all_session_data():
|
||||
try:
|
||||
session = AgentSession(**data)
|
||||
+12
-14
@@ -14,12 +14,19 @@ from uuid import uuid4
|
||||
from backend.apps.agents.models import (
|
||||
AgentSession, Message, MessageBranch,
|
||||
)
|
||||
from backend.apps.agents.ws_manager import ws_manager
|
||||
from backend.apps.agents.session_store import (
|
||||
from backend.apps.agents.manager.ws_manager import ws_manager
|
||||
from backend.apps.agents.manager.session_store import (
|
||||
save_session, load_session_data, delete_session_file,
|
||||
build_search_text, copy_session_messages,
|
||||
)
|
||||
from backend.apps.analytics.collector import record as _analytics
|
||||
from backend.apps.agents.execution.agent_loop import run_agent_loop
|
||||
|
||||
from backend.apps.agents.execution.agent_loop import run_agent_loop
|
||||
|
||||
from backend.apps.agents.execution.agent_mock import fire_session_completed
|
||||
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -75,7 +82,6 @@ async def edit_message_op(
|
||||
session.sdk_session_id = None
|
||||
session.status = "running"
|
||||
await ws_manager.emit_status(session_id, "running", session)
|
||||
from backend.apps.agents.agent_loop import run_agent_loop
|
||||
task = asyncio.create_task(run_agent_loop(
|
||||
sessions, session_id, new_content,
|
||||
images=target_msg.images, context_paths=target_msg.context_paths,
|
||||
@@ -88,8 +94,9 @@ async def close_session_op(
|
||||
sessions: dict, tasks: dict,
|
||||
session_id: str,
|
||||
):
|
||||
from backend.apps.agents.agent_manager import agent_manager
|
||||
children = [s for s in sessions.values() if s.parent_session_id == session_id and s.mode == "browser-agent"]
|
||||
from backend.apps.agents.manager.agent_manager import agent_manager
|
||||
# NOTE: this is a circular dependency, must be fixed soon by fixing the ai slop
|
||||
for child in children:
|
||||
await agent_manager.stop_agent(child.id)
|
||||
task = tasks.get(session_id)
|
||||
@@ -110,7 +117,6 @@ async def close_session_op(
|
||||
session.pending_approvals = []
|
||||
if hasattr(session, '_cancel_event'):
|
||||
session._cancel_event.set()
|
||||
from backend.apps.agents.agent_mock import fire_session_completed
|
||||
fire_session_completed(session, sessions)
|
||||
doc_data = session.model_dump(mode="json")
|
||||
doc_data["search_text"] = build_search_text(session)
|
||||
@@ -204,7 +210,6 @@ async def invoke_agent_op(
|
||||
user_msg = Message(role="user", content=message, branch_id=fork.active_branch_id)
|
||||
fork.messages.append(user_msg)
|
||||
await ws_manager.emit_message(fork.id, user_msg)
|
||||
from backend.apps.agents.agent_loop import run_agent_loop
|
||||
await run_agent_loop(sessions, fork.id, message, fork_session=True)
|
||||
last_assistant = None
|
||||
for msg in reversed(fork.messages):
|
||||
@@ -222,11 +227,4 @@ async def invoke_agent_op(
|
||||
"forked_session_id": fork.id, "source_name": source_name,
|
||||
"response": last_assistant or "No response from invoked agent.",
|
||||
"cost_usd": fork.cost_usd,
|
||||
}
|
||||
|
||||
|
||||
from backend.apps.agents.agent_manager_meta import ( # noqa: F401 — re-exports
|
||||
generate_title_op, generate_group_meta_op,
|
||||
persist_all_sessions_op, restore_all_sessions_op,
|
||||
delete_session_op,
|
||||
)
|
||||
}
|
||||
@@ -8,15 +8,14 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from backend.apps.agents.ws_manager import ws_manager
|
||||
from backend.apps.agents.manager.ws_manager import ws_manager
|
||||
from backend.apps.agents.manager.agent_manager import agent_manager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def handle_session_message(session_id: str, event: str, payload: dict):
|
||||
"""Dispatch an incoming WebSocket message for a session."""
|
||||
if event == "agent:send_message":
|
||||
from backend.apps.agents.agent_manager import agent_manager
|
||||
await agent_manager.send_message(
|
||||
session_id,
|
||||
payload.get("prompt", ""),
|
||||
@@ -26,28 +25,23 @@ async def handle_session_message(session_id: str, event: str, payload: dict):
|
||||
images=payload.get("images"),
|
||||
)
|
||||
elif event == "agent:approval_response":
|
||||
from backend.apps.agents.agent_manager import agent_manager
|
||||
agent_manager.handle_approval(payload.get("request_id"), {
|
||||
"behavior": payload.get("behavior", "deny"),
|
||||
"message": payload.get("message"),
|
||||
"updated_input": payload.get("updated_input"),
|
||||
})
|
||||
elif event == "agent:edit_message":
|
||||
from backend.apps.agents.agent_manager import agent_manager
|
||||
await agent_manager.edit_message(
|
||||
session_id,
|
||||
payload.get("message_id", ""),
|
||||
payload.get("content", ""),
|
||||
)
|
||||
elif event == "agent:stop":
|
||||
from backend.apps.agents.agent_manager import agent_manager
|
||||
await agent_manager.stop_agent(session_id)
|
||||
|
||||
|
||||
async def handle_dashboard_message(event: str, payload: dict):
|
||||
"""Dispatch an incoming WebSocket message for the dashboard."""
|
||||
if event == "agent:approval_response":
|
||||
from backend.apps.agents.agent_manager import agent_manager
|
||||
agent_manager.handle_approval(payload.get("request_id"), {
|
||||
"behavior": payload.get("behavior", "deny"),
|
||||
"message": payload.get("message"),
|
||||
|
||||
@@ -10,6 +10,14 @@ from backend.config.Apps import SubApp
|
||||
from backend.apps.analytics.collector import init as init_collector, shutdown as shutdown_collector, record, identify
|
||||
from backend.apps.analytics.usage_summary import load_all_sessions, compute_session_stats, enrich_with_nine_router
|
||||
|
||||
from backend.apps.agents.manager.agent_manager import agent_manager
|
||||
from backend.apps.settings.settings import load_settings, _save_settings
|
||||
from backend.apps.agents.manager.agent_manager import agent_manager
|
||||
|
||||
from backend.apps.nine_router import get_usage_stats, is_running as _9r_running
|
||||
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
APP_VERSION = "1.0.20"
|
||||
@@ -22,13 +30,11 @@ async def _heartbeat_loop():
|
||||
while True:
|
||||
await asyncio.sleep(60)
|
||||
try:
|
||||
from backend.apps.agents.agent_manager import agent_manager
|
||||
props = {
|
||||
"active_session_count": len(agent_manager.sessions),
|
||||
}
|
||||
|
||||
try:
|
||||
from backend.apps.nine_router import get_usage_stats, is_running as _9r_running
|
||||
if _9r_running():
|
||||
stats = await get_usage_stats()
|
||||
if stats:
|
||||
@@ -64,7 +70,6 @@ async def analytics_lifespan():
|
||||
logger.info("PostHog analytics initialised")
|
||||
|
||||
try:
|
||||
from backend.apps.settings.settings import load_settings, _save_settings
|
||||
settings = load_settings()
|
||||
|
||||
is_first_open = settings.first_opened_at is None
|
||||
@@ -132,8 +137,6 @@ analytics = SubApp("analytics", analytics_lifespan)
|
||||
@analytics.router.get("/usage-summary")
|
||||
async def usage_summary():
|
||||
"""Compute usage stats from persisted sessions for the Settings page."""
|
||||
from backend.apps.agents.agent_manager import agent_manager
|
||||
from backend.apps.nine_router import get_usage_stats, is_running as _9r_running
|
||||
|
||||
sessions = load_all_sessions()
|
||||
for s in agent_manager.get_all_sessions():
|
||||
|
||||
@@ -12,6 +12,8 @@ import platform
|
||||
from uuid import uuid4
|
||||
|
||||
from posthog import Posthog
|
||||
from backend.apps.settings.settings import load_settings, _save_settings
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -50,7 +52,6 @@ def _get_installation_id() -> str:
|
||||
if _installation_id:
|
||||
return _installation_id
|
||||
try:
|
||||
from backend.apps.settings.settings import load_settings, _save_settings
|
||||
settings = load_settings()
|
||||
iid = getattr(settings, "installation_id", None)
|
||||
if not iid:
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ logger = logging.getLogger(__name__)
|
||||
from backend.config.Apps import MainApp
|
||||
from backend.apps.health.health import health
|
||||
from backend.apps.agents.agents import agents
|
||||
from backend.apps.agents.ws_manager import ws_manager
|
||||
from backend.apps.agents.manager.ws_manager import ws_manager
|
||||
from backend.apps.agents.ws_routes import handle_session_message, handle_dashboard_message
|
||||
from backend.apps.skills.skills import skills
|
||||
from backend.apps.tools_lib.tools_lib import tools_lib
|
||||
|
||||
@@ -12,12 +12,11 @@
|
||||
"knip": "knip"
|
||||
},
|
||||
"dependencies": {
|
||||
"@assistant-ui/core": "^0.1.9",
|
||||
"@assistant-ui/core": "^0.1.9",
|
||||
"@assistant-ui/core": "^0.1.9",
|
||||
"@assistant-ui/react": "^0.12.21",
|
||||
"@assistant-ui/react-lexical": "^0.0.3",
|
||||
"@assistant-ui/react-markdown": "^0.12.7",
|
||||
"@codemirror/commands": "^6.10.3",
|
||||
"@codemirror/lang-html": "^6.4.11",
|
||||
"@codemirror/lang-json": "^6.0.2",
|
||||
"@codemirror/lang-python": "^6.2.1",
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"enabled": {
|
||||
"max-file-lines": false,
|
||||
"max-folder-items": false,
|
||||
"no-nested-imports": false,
|
||||
"vulture": false,
|
||||
"max-folder-items": true,
|
||||
"no-nested-imports": true,
|
||||
"vulture": true,
|
||||
"eslint": false,
|
||||
"knip": true,
|
||||
"endpoints": false,
|
||||
"classes": false
|
||||
"endpoints": true,
|
||||
"classes": true
|
||||
},
|
||||
"rules": {
|
||||
"max-file-lines": 250,
|
||||
|
||||
Reference in New Issue
Block a user