Compare commits

...

2 commits

13 changed files with 4150 additions and 238 deletions

3
.gitignore vendored
View file

@ -12,6 +12,9 @@ __pycache__/
.env.development
.env.test
docker-compose.override.yml
test_browser.py
export*
__pycache__/model_tools.cpython-310.pyc
__pycache__/web_tools.cpython-310.pyc

View file

@ -1,21 +1,24 @@
FROM nikolaik/python-nodejs:python3.11-nodejs20
FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
RUN apt-get update && apt-get install -y \
curl \
git \
docker.io \
curl git docker.io \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /app/hermes_code /app/hermes_data
ENV UV_PROJECT_ENVIRONMENT=/app/venv \
HERMES_HOME=/app/hermes_data \
PYTHONUNBUFFERED=1 \
PATH="/app/venv/bin:$PATH"
RUN mkdir -p /app/hermes_code /app/hermes_data
WORKDIR /app/hermes_code
COPY pyproject.toml requirements.txt* ./
RUN pip install --no-cache-dir browser-use playwright python-telegram-bot
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-install-project --extra tg
COPY . .
RUN pip install -e .
RUN uv sync --frozen --extra tg
CMD ["python", "-m", "gateway.run"]

View file

@ -0,0 +1,27 @@
FROM debian:bookworm-slim
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
chromium \
xvfb \
fluxbox \
x11vnc \
novnc \
websockify \
dbus-x11 \
socat \
procps \
curl \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /src
RUN mkdir -p /src/browser_data
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
EXPOSE 6080 9222
ENTRYPOINT ["/entrypoint.sh"]

39
browser_env/entrypoint.sh Normal file
View file

@ -0,0 +1,39 @@
export DISPLAY=:99
mkdir -p /var/run/dbus
dbus-uuidgen > /var/lib/dbus/machine-id
dbus-daemon --config-file=/usr/share/dbus-1/system.conf --print-address &
Xvfb :99 -screen 0 1280x720x16 -ac +extension GLX +render -noreset &
sleep 2
fluxbox &
x11vnc -display :99 -nopw -listen 0.0.0.0 -xkb -forever -shared &
websockify --web=/usr/share/novnc/ 6080 localhost:5900 &
socat TCP-LISTEN:9222,fork,reuseaddr TCP:127.0.0.1:9223 &
echo "--- Запуск Chromium в режиме Local-Only (Port 9223) ---"
while true; do
rm -f /src/browser_data/SingletonLock
chromium \
--no-sandbox \
--disable-dev-shm-usage \
--remote-debugging-port=9223 \
--remote-debugging-address=127.0.0.1 \
--remote-allow-origins=* \
--window-size=1280,720 \
--user-data-dir=/src/browser_data \
--disable-blink-features=AutomationControlled \
--no-first-run \
--disable-gpu \
--mute-audio \
--no-default-browser-check \
--disable-software-rasterizer \
--disable-features=site-per-process
echo "Chromium упал или был закрыт агентом, рестарт через 2 секунды..."
sleep 2
done

View file

@ -1,22 +1,4 @@
services:
browser:
image: browserless/chrome:latest
container_name: hermes_browser
ports:
- "3000:3000"
environment:
- MAX_CONCURRENT_SESSIONS=5
- SCREEN_WIDTH=1280
- SCREEN_HEIGHT=720
- ENABLE_DEBUGGER=true
restart: always
networks:
- hermes-net
deploy:
resources:
limits:
memory: 2G
agent:
build: .
container_name: hermes-brain
@ -24,7 +6,7 @@ services:
- .env
volumes:
- .:/app/hermes_code:ro
- ${HERMES_DATA_PATH}/config.yaml:/app/hermes_data/config.yaml:ro
- ${HERMES_DATA_PATH}/SOUL.md:/app/hermes_data/SOUL.md:ro
- ./.env:/app/hermes_data/.env:ro
@ -37,7 +19,7 @@ services:
- ${HERMES_DATA_PATH}/memories:/app/hermes_data/memories:rw
- ${HERMES_WORKSPACE_PATH}/hermes:/app/hermes_data/workspace:rw
environment:
- BROWSER_URL=ws://browser:3000
- BROWSER_URL=http://browser:9222
depends_on:
- browser
stdin_open: true
@ -50,6 +32,31 @@ services:
limits:
memory: 1.5G
browser:
build:
context: ./browser_env
dockerfile: Dockerfile.browser
container_name: hermes-browser
networks:
hermes-net:
aliases:
- browser
shm_size: '2gb'
volumes:
- browser_profiles:/src/browser_data
restart: always
tunnel:
image: cloudflare/cloudflared:latest
container_name: hermes-tunnel
restart: always
command: tunnel --url http://browser:6080
networks:
- hermes-net
volumes:
browser_profiles:
networks:
hermes-net:
driver: bridge

View file

@ -144,7 +144,7 @@ def _discover_tools():
"tools.image_generation_tool",
"tools.skills_tool",
"tools.skill_manager_tool",
"tools.browser_tool",
# "tools.browser_tool",
"tools.cronjob_tools",
"tools.rl_training_tool",
"tools.tts_tool",
@ -158,6 +158,7 @@ def _discover_tools():
"tools.send_message_tool",
"tools.honcho_tools",
"tools.homeassistant_tool",
"tools.browser_use_tool"
]
import importlib
for mod_name in _modules:

View file

@ -34,12 +34,17 @@ dependencies = [
"faster-whisper>=1.0.0,<2",
# Skills Hub (GitHub App JWT auth — optional, only needed for bot identity)
"PyJWT[crypto]>=2.10.1,<3",
"browser-use>=0.12.5",
"playwright>=1.49.0",
"playwright-stealth>=1.0.6",
"langchain-openai>=1.1.12",
]
[project.optional-dependencies]
modal = ["swe-rex[modal]>=1.4.0,<2"]
daytona = ["daytona>=0.148.0,<1"]
dev = ["pytest>=9.0.2,<10", "pytest-asyncio>=1.3.0,<2", "pytest-xdist>=3.0,<4", "mcp>=1.2.0,<2"]
tg = ["python-telegram-bot>=22.6,<23", "aiohttp>=3.13.3,<4"]
messaging = ["python-telegram-bot>=22.6,<23", "discord.py[voice]>=2.7.1,<3", "aiohttp>=3.13.3,<4", "slack-bolt>=1.18.0,<2", "slack-sdk>=3.27.0,<4"]
cron = ["croniter>=6.0.0,<7"]
slack = ["slack-bolt>=1.18.0,<2", "slack-sdk>=3.27.0,<4"]

View file

@ -19,6 +19,9 @@ PyJWT[crypto]
# Web tools
firecrawl-py
parallel-web>=0.4.2
browser-use>=0.12.5
playwright
playwright-stealth
# Image generation
fal-client
@ -33,3 +36,5 @@ croniter
python-telegram-bot>=20.0
discord.py>=2.0
aiohttp>=3.9.0
langchain-openai>=1.1.12,

View file

@ -64,23 +64,27 @@ from .skill_manager_tool import (
)
# Browser automation tools (agent-browser + Browserbase)
from .browser_tool import (
browser_navigate,
browser_snapshot,
browser_click,
browser_type,
browser_scroll,
browser_back,
browser_press,
browser_close,
browser_get_images,
browser_vision,
cleanup_browser,
cleanup_all_browsers,
get_active_browser_sessions,
check_browser_requirements,
BROWSER_TOOL_SCHEMAS
)
# from .browser_tool import (
# browser_navigate,
# browser_snapshot,
# browser_click,
# browser_type,
# browser_scroll,
# browser_back,
# browser_press,
# browser_close,
# browser_get_images,
# browser_vision,
# cleanup_browser,
# cleanup_all_browsers,
# get_active_browser_sessions,
# check_browser_requirements,
# BROWSER_TOOL_SCHEMAS
# )
from .browser_use_tool import run_browser_task
from .browser_tool import cleanup_browser, cleanup_all_browsers
# Cronjob management tools (CLI-only, hermes-cli toolset)
from .cronjob_tools import (

View file

@ -298,30 +298,32 @@ _cleanup_lock = threading.Lock()
def _emergency_cleanup_all_sessions():
"""
Emergency cleanup of all active browser sessions.
Called on process exit or interrupt to prevent orphaned sessions.
"""
global _cleanup_done
if _cleanup_done:
return
_cleanup_done = True
# """
# Emergency cleanup of all active browser sessions.
# Called on process exit or interrupt to prevent orphaned sessions.
# """
# global _cleanup_done
# if _cleanup_done:
# return
# _cleanup_done = True
if not _active_sessions:
return
# if not _active_sessions:
# return
logger.info("Emergency cleanup: closing %s active session(s)...",
len(_active_sessions))
# logger.info("Emergency cleanup: closing %s active session(s)...",
# len(_active_sessions))
try:
cleanup_all_browsers()
except Exception as e:
logger.error("Emergency cleanup error: %s", e)
finally:
with _cleanup_lock:
_active_sessions.clear()
_session_last_activity.clear()
_recording_sessions.clear()
# try:
# cleanup_all_browsers()
# except Exception as e:
# logger.error("Emergency cleanup error: %s", e)
# finally:
# with _cleanup_lock:
# _active_sessions.clear()
# _session_last_activity.clear()
# _recording_sessions.clear()
pass
# Register cleanup via atexit only. Previous versions installed SIGINT/SIGTERM
@ -1657,85 +1659,89 @@ def _cleanup_old_recordings(max_age_hours=72):
# ============================================================================
def cleanup_browser(task_id: Optional[str] = None) -> None:
"""
Clean up browser session for a task.
# """
# Clean up browser session for a task.
Called automatically when a task completes or when inactivity timeout is reached.
Closes both the agent-browser session and the Browserbase session.
# Called automatically when a task completes or when inactivity timeout is reached.
# Closes both the agent-browser session and the Browserbase session.
Args:
task_id: Task identifier to clean up
"""
if task_id is None:
task_id = "default"
# Args:
# task_id: Task identifier to clean up
# """
# if task_id is None:
# task_id = "default"
logger.debug("cleanup_browser called for task_id: %s", task_id)
logger.debug("Active sessions: %s", list(_active_sessions.keys()))
# logger.debug("cleanup_browser called for task_id: %s", task_id)
# logger.debug("Active sessions: %s", list(_active_sessions.keys()))
# Check if session exists (under lock), but don't remove yet -
# _run_browser_command needs it to build the close command.
with _cleanup_lock:
session_info = _active_sessions.get(task_id)
# # Check if session exists (under lock), but don't remove yet -
# # _run_browser_command needs it to build the close command.
# with _cleanup_lock:
# session_info = _active_sessions.get(task_id)
if session_info:
bb_session_id = session_info.get("bb_session_id", "unknown")
logger.debug("Found session for task %s: bb_session_id=%s", task_id, bb_session_id)
# if session_info:
# bb_session_id = session_info.get("bb_session_id", "unknown")
# logger.debug("Found session for task %s: bb_session_id=%s", task_id, bb_session_id)
# Stop auto-recording before closing (saves the file)
_maybe_stop_recording(task_id)
# # Stop auto-recording before closing (saves the file)
# _maybe_stop_recording(task_id)
# Try to close via agent-browser first (needs session in _active_sessions)
try:
_run_browser_command(task_id, "close", [], timeout=10)
logger.debug("agent-browser close command completed for task %s", task_id)
except Exception as e:
logger.warning("agent-browser close failed for task %s: %s", task_id, e)
# # Try to close via agent-browser first (needs session in _active_sessions)
# try:
# _run_browser_command(task_id, "close", [], timeout=10)
# logger.debug("agent-browser close command completed for task %s", task_id)
# except Exception as e:
# logger.warning("agent-browser close failed for task %s: %s", task_id, e)
# Now remove from tracking under lock
with _cleanup_lock:
_active_sessions.pop(task_id, None)
_session_last_activity.pop(task_id, None)
# # Now remove from tracking under lock
# with _cleanup_lock:
# _active_sessions.pop(task_id, None)
# _session_last_activity.pop(task_id, None)
# Cloud mode: close the cloud browser session via provider API
if bb_session_id:
provider = _get_cloud_provider()
if provider is not None:
try:
provider.close_session(bb_session_id)
except Exception as e:
logger.warning("Could not close cloud browser session: %s", e)
# # Cloud mode: close the cloud browser session via provider API
# if bb_session_id:
# provider = _get_cloud_provider()
# if provider is not None:
# try:
# provider.close_session(bb_session_id)
# except Exception as e:
# logger.warning("Could not close cloud browser session: %s", e)
# Kill the daemon process and clean up socket directory
session_name = session_info.get("session_name", "")
if session_name:
socket_dir = os.path.join(_socket_safe_tmpdir(), f"agent-browser-{session_name}")
if os.path.exists(socket_dir):
# agent-browser writes {session}.pid in the socket dir
pid_file = os.path.join(socket_dir, f"{session_name}.pid")
if os.path.isfile(pid_file):
try:
daemon_pid = int(Path(pid_file).read_text().strip())
os.kill(daemon_pid, signal.SIGTERM)
logger.debug("Killed daemon pid %s for %s", daemon_pid, session_name)
except (ProcessLookupError, ValueError, PermissionError, OSError):
logger.debug("Could not kill daemon pid for %s (already dead or inaccessible)", session_name)
shutil.rmtree(socket_dir, ignore_errors=True)
# # Kill the daemon process and clean up socket directory
# session_name = session_info.get("session_name", "")
# if session_name:
# socket_dir = os.path.join(_socket_safe_tmpdir(), f"agent-browser-{session_name}")
# if os.path.exists(socket_dir):
# # agent-browser writes {session}.pid in the socket dir
# pid_file = os.path.join(socket_dir, f"{session_name}.pid")
# if os.path.isfile(pid_file):
# try:
# daemon_pid = int(Path(pid_file).read_text().strip())
# os.kill(daemon_pid, signal.SIGTERM)
# logger.debug("Killed daemon pid %s for %s", daemon_pid, session_name)
# except (ProcessLookupError, ValueError, PermissionError, OSError):
# logger.debug("Could not kill daemon pid for %s (already dead or inaccessible)", session_name)
# shutil.rmtree(socket_dir, ignore_errors=True)
logger.debug("Removed task %s from active sessions", task_id)
else:
logger.debug("No active session found for task_id: %s", task_id)
# logger.debug("Removed task %s from active sessions", task_id)
# else:
# logger.debug("No active session found for task_id: %s", task_id)
pass
def cleanup_all_browsers() -> None:
"""
Clean up all active browser sessions.
# """
# Clean up all active browser sessions.
Useful for cleanup on shutdown.
"""
with _cleanup_lock:
task_ids = list(_active_sessions.keys())
for task_id in task_ids:
cleanup_browser(task_id)
# Useful for cleanup on shutdown.
# """
# with _cleanup_lock:
# task_ids = list(_active_sessions.keys())
# for task_id in task_ids:
# cleanup_browser(task_id)
pass
def get_active_browser_sessions() -> Dict[str, Dict[str, str]]:
@ -1822,96 +1828,96 @@ if __name__ == "__main__":
# ---------------------------------------------------------------------------
# Registry
# ---------------------------------------------------------------------------
from tools.registry import registry
# from tools.registry import registry
_BROWSER_SCHEMA_MAP = {s["name"]: s for s in BROWSER_TOOL_SCHEMAS}
# _BROWSER_SCHEMA_MAP = {s["name"]: s for s in BROWSER_TOOL_SCHEMAS}
registry.register(
name="browser_navigate",
toolset="browser",
schema=_BROWSER_SCHEMA_MAP["browser_navigate"],
handler=lambda args, **kw: browser_navigate(url=args.get("url", ""), task_id=kw.get("task_id")),
check_fn=check_browser_requirements,
emoji="🌐",
)
registry.register(
name="browser_snapshot",
toolset="browser",
schema=_BROWSER_SCHEMA_MAP["browser_snapshot"],
handler=lambda args, **kw: browser_snapshot(
full=args.get("full", False), task_id=kw.get("task_id"), user_task=kw.get("user_task")),
check_fn=check_browser_requirements,
emoji="📸",
)
registry.register(
name="browser_click",
toolset="browser",
schema=_BROWSER_SCHEMA_MAP["browser_click"],
handler=lambda args, **kw: browser_click(ref=args.get("ref", ""), task_id=kw.get("task_id")),
check_fn=check_browser_requirements,
emoji="👆",
)
registry.register(
name="browser_type",
toolset="browser",
schema=_BROWSER_SCHEMA_MAP["browser_type"],
handler=lambda args, **kw: browser_type(ref=args.get("ref", ""), text=args.get("text", ""), task_id=kw.get("task_id")),
check_fn=check_browser_requirements,
emoji="⌨️",
)
registry.register(
name="browser_scroll",
toolset="browser",
schema=_BROWSER_SCHEMA_MAP["browser_scroll"],
handler=lambda args, **kw: browser_scroll(direction=args.get("direction", "down"), task_id=kw.get("task_id")),
check_fn=check_browser_requirements,
emoji="📜",
)
registry.register(
name="browser_back",
toolset="browser",
schema=_BROWSER_SCHEMA_MAP["browser_back"],
handler=lambda args, **kw: browser_back(task_id=kw.get("task_id")),
check_fn=check_browser_requirements,
emoji="◀️",
)
registry.register(
name="browser_press",
toolset="browser",
schema=_BROWSER_SCHEMA_MAP["browser_press"],
handler=lambda args, **kw: browser_press(key=args.get("key", ""), task_id=kw.get("task_id")),
check_fn=check_browser_requirements,
emoji="⌨️",
)
registry.register(
name="browser_close",
toolset="browser",
schema=_BROWSER_SCHEMA_MAP["browser_close"],
handler=lambda args, **kw: browser_close(task_id=kw.get("task_id")),
check_fn=check_browser_requirements,
emoji="🚪",
)
registry.register(
name="browser_get_images",
toolset="browser",
schema=_BROWSER_SCHEMA_MAP["browser_get_images"],
handler=lambda args, **kw: browser_get_images(task_id=kw.get("task_id")),
check_fn=check_browser_requirements,
emoji="🖼️",
)
registry.register(
name="browser_vision",
toolset="browser",
schema=_BROWSER_SCHEMA_MAP["browser_vision"],
handler=lambda args, **kw: browser_vision(question=args.get("question", ""), annotate=args.get("annotate", False), task_id=kw.get("task_id")),
check_fn=check_browser_requirements,
emoji="👁️",
)
registry.register(
name="browser_console",
toolset="browser",
schema=_BROWSER_SCHEMA_MAP["browser_console"],
handler=lambda args, **kw: browser_console(clear=args.get("clear", False), task_id=kw.get("task_id")),
check_fn=check_browser_requirements,
emoji="🖥️",
)
# registry.register(
# name="browser_navigate",
# toolset="browser",
# schema=_BROWSER_SCHEMA_MAP["browser_navigate"],
# handler=lambda args, **kw: browser_navigate(url=args.get("url", ""), task_id=kw.get("task_id")),
# check_fn=check_browser_requirements,
# emoji="🌐",
# )
# registry.register(
# name="browser_snapshot",
# toolset="browser",
# schema=_BROWSER_SCHEMA_MAP["browser_snapshot"],
# handler=lambda args, **kw: browser_snapshot(
# full=args.get("full", False), task_id=kw.get("task_id"), user_task=kw.get("user_task")),
# check_fn=check_browser_requirements,
# emoji="📸",
# )
# registry.register(
# name="browser_click",
# toolset="browser",
# schema=_BROWSER_SCHEMA_MAP["browser_click"],
# handler=lambda args, **kw: browser_click(ref=args.get("ref", ""), task_id=kw.get("task_id")),
# check_fn=check_browser_requirements,
# emoji="👆",
# )
# registry.register(
# name="browser_type",
# toolset="browser",
# schema=_BROWSER_SCHEMA_MAP["browser_type"],
# handler=lambda args, **kw: browser_type(ref=args.get("ref", ""), text=args.get("text", ""), task_id=kw.get("task_id")),
# check_fn=check_browser_requirements,
# emoji="⌨️",
# )
# registry.register(
# name="browser_scroll",
# toolset="browser",
# schema=_BROWSER_SCHEMA_MAP["browser_scroll"],
# handler=lambda args, **kw: browser_scroll(direction=args.get("direction", "down"), task_id=kw.get("task_id")),
# check_fn=check_browser_requirements,
# emoji="📜",
# )
# registry.register(
# name="browser_back",
# toolset="browser",
# schema=_BROWSER_SCHEMA_MAP["browser_back"],
# handler=lambda args, **kw: browser_back(task_id=kw.get("task_id")),
# check_fn=check_browser_requirements,
# emoji="◀️",
# )
# registry.register(
# name="browser_press",
# toolset="browser",
# schema=_BROWSER_SCHEMA_MAP["browser_press"],
# handler=lambda args, **kw: browser_press(key=args.get("key", ""), task_id=kw.get("task_id")),
# check_fn=check_browser_requirements,
# emoji="⌨️",
# )
# registry.register(
# name="browser_close",
# toolset="browser",
# schema=_BROWSER_SCHEMA_MAP["browser_close"],
# handler=lambda args, **kw: browser_close(task_id=kw.get("task_id")),
# check_fn=check_browser_requirements,
# emoji="🚪",
# )
# registry.register(
# name="browser_get_images",
# toolset="browser",
# schema=_BROWSER_SCHEMA_MAP["browser_get_images"],
# handler=lambda args, **kw: browser_get_images(task_id=kw.get("task_id")),
# check_fn=check_browser_requirements,
# emoji="🖼️",
# )
# registry.register(
# name="browser_vision",
# toolset="browser",
# schema=_BROWSER_SCHEMA_MAP["browser_vision"],
# handler=lambda args, **kw: browser_vision(question=args.get("question", ""), annotate=args.get("annotate", False), task_id=kw.get("task_id")),
# check_fn=check_browser_requirements,
# emoji="👁️",
# )
# registry.register(
# name="browser_console",
# toolset="browser",
# schema=_BROWSER_SCHEMA_MAP["browser_console"],
# handler=lambda args, **kw: browser_console(clear=args.get("clear", False), task_id=kw.get("task_id")),
# check_fn=check_browser_requirements,
# emoji="🖥️",
# )

86
tools/browser_use_tool.py Normal file
View file

@ -0,0 +1,86 @@
import json
import os
import asyncio
import socket
from browser_use import Agent, Browser, ChatOpenAI
from tools.registry import registry
async def run_browser_task(task):
browser_host = "browser"
browser_port = 9222
BROWSER_VIEW_URL = os.getenv("BROWSER_VIEW_URL", "")
try:
browser_ip = socket.gethostbyname(browser_host)
cdp_url = f"http://{browser_ip}:{browser_port}"
except Exception:
cdp_url = f"http://{browser_host}:{browser_port}"
browser = Browser(cdp_url=cdp_url)
llm = ChatOpenAI(
model=os.getenv("MODEL_DEFAULT", "qwen3.5-122b"),
api_key=os.getenv("OPENAI_API_KEY"),
base_url=os.getenv("OPENAI_BASE_URL"),
temperature=0.0,
)
agent = Agent(
task=task,
llm=llm,
browser=browser,
use_vision=False
)
try:
history = await agent.run()
final_result = history.final_result()
response = {
"success": True,
"result": final_result,
"browser_view": BROWSER_VIEW_URL
}
return json.dumps(response, ensure_ascii=False)
except Exception as e:
return json.dumps({
"success": False,
"error": f"Browser automation failed: {str(e)}"
}, ensure_ascii=False)
finally:
if browser:
try:
await browser.close()
except Exception:
pass
registry.register(
name="internet_browser",
toolset="browse_cmd",
schema={
"name": "internet_browser",
"description": (
"ГЛАВНЫЙ ИНСТРУМЕНТ ДЛЯ ВЕБ-СЕРФИНГА. Вызывай этот инструмент НАПРЯМУЮ (через стандартный tool call/function call). "
"КАТЕГОРИЧЕСКИ ЗАПРЕЩЕНО использовать `execute_code` или `delegate_task` для работы с браузером. "
"Не пиши Python-скрипты! Просто передай в этот инструмент параметр `task`. "
"Используй для любых задач в интернете: поиск товаров (Wildberries, Ozon), чтение статей, клики, навигация."
),
"parameters": {
"type": "object",
"properties": {
"task": {
"type": "string",
"description": "Подробная задача на естественном языке. Например: 'Зайди на wildberries.ru, найди черную футболку и верни цену'."
}
},
"required": ["task"]
}
},
handler=lambda args, **kw: asyncio.run(run_browser_task(args.get("task"))),
emoji="🌐",
)

View file

@ -29,6 +29,12 @@ from typing import List, Dict, Any, Set, Optional
# Shared tool list for CLI and all messaging platform toolsets.
# Edit this once to update all platforms simultaneously.
_HERMES_CORE_TOOLS = [
# Browser automation
# "browser_navigate", "browser_snapshot", "browser_click",
# "browser_type", "browser_scroll", "browser_back",
# "browser_press", "browser_close", "browser_get_images",
# "browser_vision", "browser_console",
"internet_browser",
# Web
"web_search", "web_extract",
# Terminal + process management
@ -41,11 +47,6 @@ _HERMES_CORE_TOOLS = [
"mixture_of_agents",
# Skills
"skills_list", "skill_view", "skill_manage",
# Browser automation
"browser_navigate", "browser_snapshot", "browser_click",
"browser_type", "browser_scroll", "browser_back",
"browser_press", "browser_close", "browser_get_images",
"browser_vision", "browser_console",
# Text-to-speech
"text_to_speech",
# Planning & memory
@ -116,13 +117,20 @@ TOOLSETS = {
"browser": {
"description": "Browser automation for web interaction (navigate, click, type, scroll, iframes, hold-click) with web search for finding URLs",
"tools": [
"browser_navigate", "browser_snapshot", "browser_click",
"browser_type", "browser_scroll", "browser_back",
"browser_press", "browser_close", "browser_get_images",
"browser_vision", "browser_console", "web_search"
# "browser_navigate", "browser_snapshot", "browser_click",
# "browser_type", "browser_scroll", "browser_back",
# "browser_press", "browser_close", "browser_get_images",
# "browser_vision", "browser_console", "web_search"
# "internet_browser"
],
"includes": []
},
"browse_cmd": {
"description": "Advanced browser automation via browser-use",
"tools": ["internet_browser"],
"includes": []
},
"cronjob": {
"description": "Cronjob management tool - create, list, update, pause, resume, remove, and trigger scheduled tasks",

3730
uv.lock generated

File diff suppressed because it is too large Load diff