mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-01 08:21:50 +08:00
Compare commits
3 Commits
opencode-p
...
sid/restru
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b49bd7b93d | ||
|
|
420c4d02e2 | ||
|
|
193f3b8339 |
@@ -69,7 +69,7 @@ hermes-agent/
|
|||||||
│ ├── server.py # RPC handlers and session logic
|
│ ├── server.py # RPC handlers and session logic
|
||||||
│ ├── render.py # Optional rich/ANSI bridge
|
│ ├── render.py # Optional rich/ANSI bridge
|
||||||
│ └── slash_worker.py # Persistent HermesCLI subprocess for slash commands
|
│ └── slash_worker.py # Persistent HermesCLI subprocess for slash commands
|
||||||
├── acp_adapter/ # ACP server (VS Code / Zed / JetBrains integration)
|
├── hermes_agent/acp/ # ACP server (VS Code / Zed / JetBrains integration)
|
||||||
├── cron/ # Scheduler (jobs.py, scheduler.py)
|
├── cron/ # Scheduler (jobs.py, scheduler.py)
|
||||||
├── environments/ # RL training environments (Atropos)
|
├── environments/ # RL training environments (Atropos)
|
||||||
├── tests/ # Pytest suite (~3000 tests)
|
├── tests/ # Pytest suite (~3000 tests)
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
"""Allow running the ACP adapter as ``python -m acp_adapter``."""
|
|
||||||
|
|
||||||
from .entry import main
|
|
||||||
|
|
||||||
main()
|
|
||||||
2
cli.py
2
cli.py
@@ -8454,7 +8454,7 @@ class HermesCLI:
|
|||||||
# in terminal_tool is populated for this thread. The main thread
|
# in terminal_tool is populated for this thread. The main thread
|
||||||
# registration (run() line ~9046) is invisible here because
|
# registration (run() line ~9046) is invisible here because
|
||||||
# _callback_tls is threading.local(). Matches the pattern used
|
# _callback_tls is threading.local(). Matches the pattern used
|
||||||
# by acp_adapter/server.py for ACP sessions.
|
# by hermes_agent/acp/server.py for ACP sessions.
|
||||||
set_sudo_password_callback(self._sudo_password_callback)
|
set_sudo_password_callback(self._sudo_password_callback)
|
||||||
set_approval_callback(self._approval_callback)
|
set_approval_callback(self._approval_callback)
|
||||||
try:
|
try:
|
||||||
|
|||||||
1
hermes_agent/__init__.py
Normal file
1
hermes_agent/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""Hermes Agent — The self-improving AI agent."""
|
||||||
5
hermes_agent/acp/__main__.py
Normal file
5
hermes_agent/acp/__main__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
"""Allow running the ACP adapter as ``python -m hermes_agent.acp``."""
|
||||||
|
|
||||||
|
from hermes_agent.acp.entry import main
|
||||||
|
|
||||||
|
main()
|
||||||
@@ -6,7 +6,7 @@ and starts the ACP agent server.
|
|||||||
|
|
||||||
Usage::
|
Usage::
|
||||||
|
|
||||||
python -m acp_adapter.entry
|
python -m hermes_agent.acp.entry
|
||||||
# or
|
# or
|
||||||
hermes acp
|
hermes acp
|
||||||
# or
|
# or
|
||||||
@@ -16,7 +16,6 @@ Usage::
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
|
||||||
from hermes_constants import get_hermes_home
|
from hermes_constants import get_hermes_home
|
||||||
|
|
||||||
|
|
||||||
@@ -104,13 +103,8 @@ def main() -> None:
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logger.info("Starting hermes-agent ACP adapter")
|
logger.info("Starting hermes-agent ACP adapter")
|
||||||
|
|
||||||
# Ensure the project root is on sys.path so ``from run_agent import AIAgent`` works
|
|
||||||
project_root = str(Path(__file__).resolve().parent.parent)
|
|
||||||
if project_root not in sys.path:
|
|
||||||
sys.path.insert(0, project_root)
|
|
||||||
|
|
||||||
import acp
|
import acp
|
||||||
from .server import HermesACPAgent
|
from hermes_agent.acp.server import HermesACPAgent
|
||||||
|
|
||||||
agent = HermesACPAgent()
|
agent = HermesACPAgent()
|
||||||
try:
|
try:
|
||||||
@@ -15,7 +15,7 @@ from typing import Any, Callable, Deque, Dict
|
|||||||
|
|
||||||
import acp
|
import acp
|
||||||
|
|
||||||
from .tools import (
|
from hermes_agent.acp.tools import (
|
||||||
build_tool_complete,
|
build_tool_complete,
|
||||||
build_tool_start,
|
build_tool_start,
|
||||||
make_tool_call_id,
|
make_tool_call_id,
|
||||||
@@ -52,15 +52,15 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
from acp.schema import AuthMethod as AuthMethodAgent # type: ignore[attr-defined]
|
from acp.schema import AuthMethod as AuthMethodAgent # type: ignore[attr-defined]
|
||||||
|
|
||||||
from acp_adapter.auth import detect_provider
|
from hermes_agent.acp.auth import detect_provider
|
||||||
from acp_adapter.events import (
|
from hermes_agent.acp.events import (
|
||||||
make_message_cb,
|
make_message_cb,
|
||||||
make_step_cb,
|
make_step_cb,
|
||||||
make_thinking_cb,
|
make_thinking_cb,
|
||||||
make_tool_progress_cb,
|
make_tool_progress_cb,
|
||||||
)
|
)
|
||||||
from acp_adapter.permissions import make_approval_callback
|
from hermes_agent.acp.permissions import make_approval_callback
|
||||||
from acp_adapter.session import SessionManager, SessionState
|
from hermes_agent.acp.session import SessionManager, SessionState
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -8516,7 +8516,7 @@ Examples:
|
|||||||
def cmd_acp(args):
|
def cmd_acp(args):
|
||||||
"""Launch Hermes Agent as an ACP server."""
|
"""Launch Hermes Agent as an ACP server."""
|
||||||
try:
|
try:
|
||||||
from acp_adapter.entry import main as acp_main
|
from hermes_agent.acp.entry import main as acp_main
|
||||||
|
|
||||||
acp_main()
|
acp_main()
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ all = [
|
|||||||
[project.scripts]
|
[project.scripts]
|
||||||
hermes = "hermes_cli.main:main"
|
hermes = "hermes_cli.main:main"
|
||||||
hermes-agent = "run_agent:main"
|
hermes-agent = "run_agent:main"
|
||||||
hermes-acp = "acp_adapter.entry:main"
|
hermes-acp = "hermes_agent.acp.entry:main"
|
||||||
|
|
||||||
[tool.setuptools]
|
[tool.setuptools]
|
||||||
py-modules = ["run_agent", "model_tools", "toolsets", "batch_runner", "trajectory_compressor", "toolset_distributions", "cli", "hermes_constants", "hermes_state", "hermes_time", "hermes_logging", "rl_cli", "utils"]
|
py-modules = ["run_agent", "model_tools", "toolsets", "batch_runner", "trajectory_compressor", "toolset_distributions", "cli", "hermes_constants", "hermes_state", "hermes_time", "hermes_logging", "rl_cli", "utils"]
|
||||||
@@ -126,7 +126,7 @@ py-modules = ["run_agent", "model_tools", "toolsets", "batch_runner", "trajector
|
|||||||
hermes_cli = ["web_dist/**/*"]
|
hermes_cli = ["web_dist/**/*"]
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
include = ["agent", "agent.*", "tools", "tools.*", "hermes_cli", "gateway", "gateway.*", "tui_gateway", "tui_gateway.*", "cron", "acp_adapter", "plugins", "plugins.*"]
|
include = ["agent", "agent.*", "tools", "tools.*", "hermes_cli", "gateway", "gateway.*", "tui_gateway", "tui_gateway.*", "cron", "hermes_agent", "hermes_agent.*", "plugins", "plugins.*"]
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
testpaths = ["tests"]
|
testpaths = ["tests"]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Tests for acp_adapter.auth — provider detection."""
|
"""Tests for hermes_agent.acp.auth — provider detection."""
|
||||||
|
|
||||||
from acp_adapter.auth import has_provider, detect_provider
|
from hermes_agent.acp.auth import has_provider, detect_provider
|
||||||
|
|
||||||
|
|
||||||
class TestHasProvider:
|
class TestHasProvider:
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"""Tests for acp_adapter.entry startup wiring."""
|
"""Tests for hermes_agent.acp.entry startup wiring."""
|
||||||
|
|
||||||
import acp
|
import acp
|
||||||
|
|
||||||
from acp_adapter import entry
|
from hermes_agent.acp import entry
|
||||||
|
|
||||||
|
|
||||||
def test_main_enables_unstable_protocol(monkeypatch):
|
def test_main_enables_unstable_protocol(monkeypatch):
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Tests for acp_adapter.events — callback factories for ACP notifications."""
|
"""Tests for hermes_agent.acp.events — callback factories for ACP notifications."""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from concurrent.futures import Future
|
from concurrent.futures import Future
|
||||||
@@ -9,7 +9,7 @@ import pytest
|
|||||||
import acp
|
import acp
|
||||||
from acp.schema import ToolCallStart, ToolCallProgress, AgentThoughtChunk, AgentMessageChunk
|
from acp.schema import ToolCallStart, ToolCallProgress, AgentThoughtChunk, AgentMessageChunk
|
||||||
|
|
||||||
from acp_adapter.events import (
|
from hermes_agent.acp.events import (
|
||||||
make_message_cb,
|
make_message_cb,
|
||||||
make_step_cb,
|
make_step_cb,
|
||||||
make_thinking_cb,
|
make_thinking_cb,
|
||||||
@@ -48,7 +48,7 @@ class TestToolProgressCallback:
|
|||||||
cb = make_tool_progress_cb(mock_conn, "session-1", loop, tool_call_ids, tool_call_meta)
|
cb = make_tool_progress_cb(mock_conn, "session-1", loop, tool_call_ids, tool_call_meta)
|
||||||
|
|
||||||
# Run callback in the event loop context
|
# Run callback in the event loop context
|
||||||
with patch("acp_adapter.events.asyncio.run_coroutine_threadsafe") as mock_rcts:
|
with patch("hermes_agent.acp.events.asyncio.run_coroutine_threadsafe") as mock_rcts:
|
||||||
future = MagicMock(spec=Future)
|
future = MagicMock(spec=Future)
|
||||||
future.result.return_value = None
|
future.result.return_value = None
|
||||||
mock_rcts.return_value = future
|
mock_rcts.return_value = future
|
||||||
@@ -72,7 +72,7 @@ class TestToolProgressCallback:
|
|||||||
|
|
||||||
cb = make_tool_progress_cb(mock_conn, "session-1", loop, tool_call_ids, tool_call_meta)
|
cb = make_tool_progress_cb(mock_conn, "session-1", loop, tool_call_ids, tool_call_meta)
|
||||||
|
|
||||||
with patch("acp_adapter.events.asyncio.run_coroutine_threadsafe") as mock_rcts:
|
with patch("hermes_agent.acp.events.asyncio.run_coroutine_threadsafe") as mock_rcts:
|
||||||
future = MagicMock(spec=Future)
|
future = MagicMock(spec=Future)
|
||||||
future.result.return_value = None
|
future.result.return_value = None
|
||||||
mock_rcts.return_value = future
|
mock_rcts.return_value = future
|
||||||
@@ -89,7 +89,7 @@ class TestToolProgressCallback:
|
|||||||
|
|
||||||
cb = make_tool_progress_cb(mock_conn, "session-1", loop, tool_call_ids, tool_call_meta)
|
cb = make_tool_progress_cb(mock_conn, "session-1", loop, tool_call_ids, tool_call_meta)
|
||||||
|
|
||||||
with patch("acp_adapter.events.asyncio.run_coroutine_threadsafe") as mock_rcts:
|
with patch("hermes_agent.acp.events.asyncio.run_coroutine_threadsafe") as mock_rcts:
|
||||||
future = MagicMock(spec=Future)
|
future = MagicMock(spec=Future)
|
||||||
future.result.return_value = None
|
future.result.return_value = None
|
||||||
mock_rcts.return_value = future
|
mock_rcts.return_value = future
|
||||||
@@ -107,7 +107,7 @@ class TestToolProgressCallback:
|
|||||||
progress_cb = make_tool_progress_cb(mock_conn, "session-1", loop, tool_call_ids, tool_call_meta)
|
progress_cb = make_tool_progress_cb(mock_conn, "session-1", loop, tool_call_ids, tool_call_meta)
|
||||||
step_cb = make_step_cb(mock_conn, "session-1", loop, tool_call_ids, tool_call_meta)
|
step_cb = make_step_cb(mock_conn, "session-1", loop, tool_call_ids, tool_call_meta)
|
||||||
|
|
||||||
with patch("acp_adapter.events.asyncio.run_coroutine_threadsafe") as mock_rcts:
|
with patch("hermes_agent.acp.events.asyncio.run_coroutine_threadsafe") as mock_rcts:
|
||||||
future = MagicMock(spec=Future)
|
future = MagicMock(spec=Future)
|
||||||
future.result.return_value = None
|
future.result.return_value = None
|
||||||
mock_rcts.return_value = future
|
mock_rcts.return_value = future
|
||||||
@@ -135,7 +135,7 @@ class TestThinkingCallback:
|
|||||||
|
|
||||||
cb = make_thinking_cb(mock_conn, "session-1", loop)
|
cb = make_thinking_cb(mock_conn, "session-1", loop)
|
||||||
|
|
||||||
with patch("acp_adapter.events.asyncio.run_coroutine_threadsafe") as mock_rcts:
|
with patch("hermes_agent.acp.events.asyncio.run_coroutine_threadsafe") as mock_rcts:
|
||||||
future = MagicMock(spec=Future)
|
future = MagicMock(spec=Future)
|
||||||
future.result.return_value = None
|
future.result.return_value = None
|
||||||
mock_rcts.return_value = future
|
mock_rcts.return_value = future
|
||||||
@@ -150,7 +150,7 @@ class TestThinkingCallback:
|
|||||||
|
|
||||||
cb = make_thinking_cb(mock_conn, "session-1", loop)
|
cb = make_thinking_cb(mock_conn, "session-1", loop)
|
||||||
|
|
||||||
with patch("acp_adapter.events.asyncio.run_coroutine_threadsafe") as mock_rcts:
|
with patch("hermes_agent.acp.events.asyncio.run_coroutine_threadsafe") as mock_rcts:
|
||||||
cb("")
|
cb("")
|
||||||
|
|
||||||
mock_rcts.assert_not_called()
|
mock_rcts.assert_not_called()
|
||||||
@@ -169,7 +169,7 @@ class TestStepCallback:
|
|||||||
|
|
||||||
cb = make_step_cb(mock_conn, "session-1", loop, tool_call_ids, {})
|
cb = make_step_cb(mock_conn, "session-1", loop, tool_call_ids, {})
|
||||||
|
|
||||||
with patch("acp_adapter.events.asyncio.run_coroutine_threadsafe") as mock_rcts:
|
with patch("hermes_agent.acp.events.asyncio.run_coroutine_threadsafe") as mock_rcts:
|
||||||
future = MagicMock(spec=Future)
|
future = MagicMock(spec=Future)
|
||||||
future.result.return_value = None
|
future.result.return_value = None
|
||||||
mock_rcts.return_value = future
|
mock_rcts.return_value = future
|
||||||
@@ -187,7 +187,7 @@ class TestStepCallback:
|
|||||||
|
|
||||||
cb = make_step_cb(mock_conn, "session-1", loop, tool_call_ids, {})
|
cb = make_step_cb(mock_conn, "session-1", loop, tool_call_ids, {})
|
||||||
|
|
||||||
with patch("acp_adapter.events.asyncio.run_coroutine_threadsafe") as mock_rcts:
|
with patch("hermes_agent.acp.events.asyncio.run_coroutine_threadsafe") as mock_rcts:
|
||||||
cb(1, [{"name": "unknown_tool", "result": "ok"}])
|
cb(1, [{"name": "unknown_tool", "result": "ok"}])
|
||||||
|
|
||||||
mock_rcts.assert_not_called()
|
mock_rcts.assert_not_called()
|
||||||
@@ -199,7 +199,7 @@ class TestStepCallback:
|
|||||||
|
|
||||||
cb = make_step_cb(mock_conn, "session-1", loop, tool_call_ids, {})
|
cb = make_step_cb(mock_conn, "session-1", loop, tool_call_ids, {})
|
||||||
|
|
||||||
with patch("acp_adapter.events.asyncio.run_coroutine_threadsafe") as mock_rcts:
|
with patch("hermes_agent.acp.events.asyncio.run_coroutine_threadsafe") as mock_rcts:
|
||||||
future = MagicMock(spec=Future)
|
future = MagicMock(spec=Future)
|
||||||
future.result.return_value = None
|
future.result.return_value = None
|
||||||
mock_rcts.return_value = future
|
mock_rcts.return_value = future
|
||||||
@@ -218,8 +218,8 @@ class TestStepCallback:
|
|||||||
|
|
||||||
cb = make_step_cb(mock_conn, "session-1", loop, tool_call_ids, {})
|
cb = make_step_cb(mock_conn, "session-1", loop, tool_call_ids, {})
|
||||||
|
|
||||||
with patch("acp_adapter.events.asyncio.run_coroutine_threadsafe") as mock_rcts, \
|
with patch("hermes_agent.acp.events.asyncio.run_coroutine_threadsafe") as mock_rcts, \
|
||||||
patch("acp_adapter.events.build_tool_complete") as mock_btc:
|
patch("hermes_agent.acp.events.build_tool_complete") as mock_btc:
|
||||||
future = MagicMock(spec=Future)
|
future = MagicMock(spec=Future)
|
||||||
future.result.return_value = None
|
future.result.return_value = None
|
||||||
mock_rcts.return_value = future
|
mock_rcts.return_value = future
|
||||||
@@ -240,8 +240,8 @@ class TestStepCallback:
|
|||||||
|
|
||||||
cb = make_step_cb(mock_conn, "session-1", loop, tool_call_ids, {})
|
cb = make_step_cb(mock_conn, "session-1", loop, tool_call_ids, {})
|
||||||
|
|
||||||
with patch("acp_adapter.events.asyncio.run_coroutine_threadsafe") as mock_rcts, \
|
with patch("hermes_agent.acp.events.asyncio.run_coroutine_threadsafe") as mock_rcts, \
|
||||||
patch("acp_adapter.events.build_tool_complete") as mock_btc:
|
patch("hermes_agent.acp.events.build_tool_complete") as mock_btc:
|
||||||
future = MagicMock(spec=Future)
|
future = MagicMock(spec=Future)
|
||||||
future.result.return_value = None
|
future.result.return_value = None
|
||||||
mock_rcts.return_value = future
|
mock_rcts.return_value = future
|
||||||
@@ -259,8 +259,8 @@ class TestStepCallback:
|
|||||||
|
|
||||||
cb = make_step_cb(mock_conn, "session-1", loop, tool_call_ids, tool_call_meta)
|
cb = make_step_cb(mock_conn, "session-1", loop, tool_call_ids, tool_call_meta)
|
||||||
|
|
||||||
with patch("acp_adapter.events.asyncio.run_coroutine_threadsafe") as mock_rcts, \
|
with patch("hermes_agent.acp.events.asyncio.run_coroutine_threadsafe") as mock_rcts, \
|
||||||
patch("acp_adapter.events.build_tool_complete") as mock_btc:
|
patch("hermes_agent.acp.events.build_tool_complete") as mock_btc:
|
||||||
future = MagicMock(spec=Future)
|
future = MagicMock(spec=Future)
|
||||||
future.result.return_value = None
|
future.result.return_value = None
|
||||||
mock_rcts.return_value = future
|
mock_rcts.return_value = future
|
||||||
@@ -280,8 +280,8 @@ class TestStepCallback:
|
|||||||
tool_call_meta = {}
|
tool_call_meta = {}
|
||||||
loop = event_loop_fixture
|
loop = event_loop_fixture
|
||||||
|
|
||||||
with patch("acp_adapter.events.make_tool_call_id", return_value="tc-meta"), \
|
with patch("hermes_agent.acp.events.make_tool_call_id", return_value="tc-meta"), \
|
||||||
patch("acp_adapter.events._send_update") as mock_send, \
|
patch("hermes_agent.acp.events._send_update") as mock_send, \
|
||||||
patch("agent.display.capture_local_edit_snapshot", return_value="snapshot"):
|
patch("agent.display.capture_local_edit_snapshot", return_value="snapshot"):
|
||||||
cb = make_tool_progress_cb(mock_conn, "session-1", loop, tool_call_ids, tool_call_meta)
|
cb = make_tool_progress_cb(mock_conn, "session-1", loop, tool_call_ids, tool_call_meta)
|
||||||
cb("tool.started", "write_file", None, {"path": "diff-test.txt", "content": "hello"})
|
cb("tool.started", "write_file", None, {"path": "diff-test.txt", "content": "hello"})
|
||||||
@@ -306,7 +306,7 @@ class TestMessageCallback:
|
|||||||
|
|
||||||
cb = make_message_cb(mock_conn, "session-1", loop)
|
cb = make_message_cb(mock_conn, "session-1", loop)
|
||||||
|
|
||||||
with patch("acp_adapter.events.asyncio.run_coroutine_threadsafe") as mock_rcts:
|
with patch("hermes_agent.acp.events.asyncio.run_coroutine_threadsafe") as mock_rcts:
|
||||||
future = MagicMock(spec=Future)
|
future = MagicMock(spec=Future)
|
||||||
future.result.return_value = None
|
future.result.return_value = None
|
||||||
mock_rcts.return_value = future
|
mock_rcts.return_value = future
|
||||||
@@ -321,7 +321,7 @@ class TestMessageCallback:
|
|||||||
|
|
||||||
cb = make_message_cb(mock_conn, "session-1", loop)
|
cb = make_message_cb(mock_conn, "session-1", loop)
|
||||||
|
|
||||||
with patch("acp_adapter.events.asyncio.run_coroutine_threadsafe") as mock_rcts:
|
with patch("hermes_agent.acp.events.asyncio.run_coroutine_threadsafe") as mock_rcts:
|
||||||
cb("")
|
cb("")
|
||||||
|
|
||||||
mock_rcts.assert_not_called()
|
mock_rcts.assert_not_called()
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ from acp.schema import (
|
|||||||
ToolCallStart,
|
ToolCallStart,
|
||||||
)
|
)
|
||||||
|
|
||||||
from acp_adapter.server import HermesACPAgent
|
from hermes_agent.acp.server import HermesACPAgent
|
||||||
from acp_adapter.session import SessionManager
|
from hermes_agent.acp.session import SessionManager
|
||||||
from acp_adapter.tools import build_tool_start
|
from hermes_agent.acp.tools import build_tool_start
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Tests for acp_adapter.permissions — ACP approval bridging."""
|
"""Tests for hermes_agent.acp.permissions — ACP approval bridging."""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from concurrent.futures import Future
|
from concurrent.futures import Future
|
||||||
@@ -11,7 +11,7 @@ from acp.schema import (
|
|||||||
DeniedOutcome,
|
DeniedOutcome,
|
||||||
RequestPermissionResponse,
|
RequestPermissionResponse,
|
||||||
)
|
)
|
||||||
from acp_adapter.permissions import make_approval_callback
|
from hermes_agent.acp.permissions import make_approval_callback
|
||||||
|
|
||||||
|
|
||||||
def _make_response(outcome):
|
def _make_response(outcome):
|
||||||
@@ -37,7 +37,7 @@ def _setup_callback(outcome, timeout=60.0):
|
|||||||
future = MagicMock(spec=Future)
|
future = MagicMock(spec=Future)
|
||||||
future.result.return_value = response
|
future.result.return_value = response
|
||||||
|
|
||||||
with patch("acp_adapter.permissions.asyncio.run_coroutine_threadsafe", return_value=future):
|
with patch("hermes_agent.acp.permissions.asyncio.run_coroutine_threadsafe", return_value=future):
|
||||||
cb = make_approval_callback(mock_rp, loop, session_id="s1", timeout=timeout)
|
cb = make_approval_callback(mock_rp, loop, session_id="s1", timeout=timeout)
|
||||||
result = cb("rm -rf /", "dangerous command")
|
result = cb("rm -rf /", "dangerous command")
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ class TestApprovalMapping:
|
|||||||
future = MagicMock(spec=Future)
|
future = MagicMock(spec=Future)
|
||||||
future.result.side_effect = TimeoutError("timed out")
|
future.result.side_effect = TimeoutError("timed out")
|
||||||
|
|
||||||
with patch("acp_adapter.permissions.asyncio.run_coroutine_threadsafe", return_value=future):
|
with patch("hermes_agent.acp.permissions.asyncio.run_coroutine_threadsafe", return_value=future):
|
||||||
cb = make_approval_callback(mock_rp, loop, session_id="s1", timeout=0.01)
|
cb = make_approval_callback(mock_rp, loop, session_id="s1", timeout=0.01)
|
||||||
result = cb("rm -rf /", "dangerous")
|
result = cb("rm -rf /", "dangerous")
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ class TestApprovalMapping:
|
|||||||
future = MagicMock(spec=Future)
|
future = MagicMock(spec=Future)
|
||||||
future.result.return_value = None
|
future.result.return_value = None
|
||||||
|
|
||||||
with patch("acp_adapter.permissions.asyncio.run_coroutine_threadsafe", return_value=future):
|
with patch("hermes_agent.acp.permissions.asyncio.run_coroutine_threadsafe", return_value=future):
|
||||||
cb = make_approval_callback(mock_rp, loop, session_id="s1", timeout=1.0)
|
cb = make_approval_callback(mock_rp, loop, session_id="s1", timeout=1.0)
|
||||||
result = cb("echo hi", "demo")
|
result = cb("echo hi", "demo")
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Tests for acp_adapter.entry._BenignProbeMethodFilter.
|
"""Tests for hermes_agent.acp.entry._BenignProbeMethodFilter.
|
||||||
|
|
||||||
Covers both the isolated filter logic and the full end-to-end path where a
|
Covers both the isolated filter logic and the full end-to-end path where a
|
||||||
client sends a bare JSON-RPC ``ping`` request over stdio and the acp runtime
|
client sends a bare JSON-RPC ``ping`` request over stdio and the acp runtime
|
||||||
@@ -18,7 +18,7 @@ import pytest
|
|||||||
|
|
||||||
from acp.exceptions import RequestError
|
from acp.exceptions import RequestError
|
||||||
|
|
||||||
from acp_adapter.entry import _BenignProbeMethodFilter
|
from hermes_agent.acp.entry import _BenignProbeMethodFilter
|
||||||
|
|
||||||
|
|
||||||
# -- Unit tests on the filter itself ----------------------------------------
|
# -- Unit tests on the filter itself ----------------------------------------
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Tests for acp_adapter.server — HermesACPAgent ACP server."""
|
"""Tests for hermes_agent.acp.server — HermesACPAgent ACP server."""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
@@ -28,8 +28,8 @@ from acp.schema import (
|
|||||||
TextContentBlock,
|
TextContentBlock,
|
||||||
Usage,
|
Usage,
|
||||||
)
|
)
|
||||||
from acp_adapter.server import HermesACPAgent, HERMES_VERSION
|
from hermes_agent.acp.server import HermesACPAgent, HERMES_VERSION
|
||||||
from acp_adapter.session import SessionManager
|
from hermes_agent.acp.session import SessionManager
|
||||||
from hermes_state import SessionDB
|
from hermes_state import SessionDB
|
||||||
|
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ class TestAuthenticate:
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_authenticate_with_matching_method_id(self, agent, monkeypatch):
|
async def test_authenticate_with_matching_method_id(self, agent, monkeypatch):
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"acp_adapter.server.detect_provider",
|
"hermes_agent.acp.server.detect_provider",
|
||||||
lambda: "openrouter",
|
lambda: "openrouter",
|
||||||
)
|
)
|
||||||
resp = await agent.authenticate(method_id="openrouter")
|
resp = await agent.authenticate(method_id="openrouter")
|
||||||
@@ -106,7 +106,7 @@ class TestAuthenticate:
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_authenticate_is_case_insensitive(self, agent, monkeypatch):
|
async def test_authenticate_is_case_insensitive(self, agent, monkeypatch):
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"acp_adapter.server.detect_provider",
|
"hermes_agent.acp.server.detect_provider",
|
||||||
lambda: "openrouter",
|
lambda: "openrouter",
|
||||||
)
|
)
|
||||||
resp = await agent.authenticate(method_id="OpenRouter")
|
resp = await agent.authenticate(method_id="OpenRouter")
|
||||||
@@ -115,7 +115,7 @@ class TestAuthenticate:
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_authenticate_rejects_mismatched_method_id(self, agent, monkeypatch):
|
async def test_authenticate_rejects_mismatched_method_id(self, agent, monkeypatch):
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"acp_adapter.server.detect_provider",
|
"hermes_agent.acp.server.detect_provider",
|
||||||
lambda: "openrouter",
|
lambda: "openrouter",
|
||||||
)
|
)
|
||||||
resp = await agent.authenticate(method_id="totally-invalid-method")
|
resp = await agent.authenticate(method_id="totally-invalid-method")
|
||||||
@@ -124,7 +124,7 @@ class TestAuthenticate:
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_authenticate_without_provider(self, agent, monkeypatch):
|
async def test_authenticate_without_provider(self, agent, monkeypatch):
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"acp_adapter.server.detect_provider",
|
"hermes_agent.acp.server.detect_provider",
|
||||||
lambda: None,
|
lambda: None,
|
||||||
)
|
)
|
||||||
resp = await agent.authenticate(method_id="openrouter")
|
resp = await agent.authenticate(method_id="openrouter")
|
||||||
@@ -272,7 +272,7 @@ class TestListAndFork:
|
|||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_list_sessions_pagination_first_page(self, agent):
|
async def test_list_sessions_pagination_first_page(self, agent):
|
||||||
from acp_adapter import server as acp_server
|
from hermes_agent.acp import server as acp_server
|
||||||
|
|
||||||
infos = [
|
infos = [
|
||||||
{"session_id": f"s{i}", "cwd": "/tmp", "title": None, "updated_at": 0.0}
|
{"session_id": f"s{i}", "cwd": "/tmp", "title": None, "updated_at": 0.0}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Tests for acp_adapter.session — SessionManager and SessionState."""
|
"""Tests for hermes_agent.acp.session — SessionManager and SessionState."""
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
import io
|
import io
|
||||||
@@ -8,7 +8,7 @@ from types import SimpleNamespace
|
|||||||
import pytest
|
import pytest
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from acp_adapter.session import SessionManager, SessionState
|
from hermes_agent.acp.session import SessionManager, SessionState
|
||||||
from hermes_state import SessionDB
|
from hermes_state import SessionDB
|
||||||
|
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ class TestCreateSession:
|
|||||||
|
|
||||||
def test_create_session_registers_task_cwd(self, manager, monkeypatch):
|
def test_create_session_registers_task_cwd(self, manager, monkeypatch):
|
||||||
calls = []
|
calls = []
|
||||||
monkeypatch.setattr("acp_adapter.session._register_task_cwd", lambda task_id, cwd: calls.append((task_id, cwd)))
|
monkeypatch.setattr("hermes_agent.acp.session._register_task_cwd", lambda task_id, cwd: calls.append((task_id, cwd)))
|
||||||
state = manager.create_session(cwd="/tmp/work")
|
state = manager.create_session(cwd="/tmp/work")
|
||||||
assert calls == [(state.session_id, "/tmp/work")]
|
assert calls == [(state.session_id, "/tmp/work")]
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"""Tests for acp_adapter.tools — tool kind mapping and ACP content building."""
|
"""Tests for hermes_agent.acp.tools — tool kind mapping and ACP content building."""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from acp_adapter.tools import (
|
from hermes_agent.acp.tools import (
|
||||||
TOOL_KIND_MAP,
|
TOOL_KIND_MAP,
|
||||||
build_tool_complete,
|
build_tool_complete,
|
||||||
build_tool_start,
|
build_tool_start,
|
||||||
|
|||||||
@@ -10,20 +10,20 @@ The ACP adapter wraps Hermes' synchronous `AIAgent` in an async JSON-RPC stdio s
|
|||||||
|
|
||||||
Key implementation files:
|
Key implementation files:
|
||||||
|
|
||||||
- `acp_adapter/entry.py`
|
- `hermes_agent/acp/entry.py`
|
||||||
- `acp_adapter/server.py`
|
- `hermes_agent/acp/server.py`
|
||||||
- `acp_adapter/session.py`
|
- `hermes_agent/acp/session.py`
|
||||||
- `acp_adapter/events.py`
|
- `hermes_agent/acp/events.py`
|
||||||
- `acp_adapter/permissions.py`
|
- `hermes_agent/acp/permissions.py`
|
||||||
- `acp_adapter/tools.py`
|
- `hermes_agent/acp/tools.py`
|
||||||
- `acp_adapter/auth.py`
|
- `hermes_agent/acp/auth.py`
|
||||||
- `acp_registry/agent.json`
|
- `acp_registry/agent.json`
|
||||||
|
|
||||||
## Boot flow
|
## Boot flow
|
||||||
|
|
||||||
```text
|
```text
|
||||||
hermes acp / hermes-acp / python -m acp_adapter
|
hermes acp / hermes-acp / python -m hermes_agent.acp
|
||||||
-> acp_adapter.entry.main()
|
-> hermes_agent.acp.entry.main()
|
||||||
-> load ~/.hermes/.env
|
-> load ~/.hermes/.env
|
||||||
-> configure stderr logging
|
-> configure stderr logging
|
||||||
-> construct HermesACPAgent
|
-> construct HermesACPAgent
|
||||||
@@ -36,7 +36,7 @@ Stdout is reserved for ACP JSON-RPC transport. Human-readable logs go to stderr.
|
|||||||
|
|
||||||
### `HermesACPAgent`
|
### `HermesACPAgent`
|
||||||
|
|
||||||
`acp_adapter/server.py` implements the ACP agent protocol.
|
`hermes_agent/acp/server.py` implements the ACP agent protocol.
|
||||||
|
|
||||||
Responsibilities:
|
Responsibilities:
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ Responsibilities:
|
|||||||
|
|
||||||
### `SessionManager`
|
### `SessionManager`
|
||||||
|
|
||||||
`acp_adapter/session.py` tracks live ACP sessions.
|
`hermes_agent/acp/session.py` tracks live ACP sessions.
|
||||||
|
|
||||||
Each session stores:
|
Each session stores:
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ The manager is thread-safe and supports:
|
|||||||
|
|
||||||
### Event bridge
|
### Event bridge
|
||||||
|
|
||||||
`acp_adapter/events.py` converts AIAgent callbacks into ACP `session_update` events.
|
`hermes_agent/acp/events.py` converts AIAgent callbacks into ACP `session_update` events.
|
||||||
|
|
||||||
Bridged callbacks:
|
Bridged callbacks:
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ asyncio.run_coroutine_threadsafe(...)
|
|||||||
|
|
||||||
### Permission bridge
|
### Permission bridge
|
||||||
|
|
||||||
`acp_adapter/permissions.py` adapts dangerous terminal approval prompts into ACP permission requests.
|
`hermes_agent/acp/permissions.py` adapts dangerous terminal approval prompts into ACP permission requests.
|
||||||
|
|
||||||
Mapping:
|
Mapping:
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ Timeouts and bridge failures deny by default.
|
|||||||
|
|
||||||
### Tool rendering helpers
|
### Tool rendering helpers
|
||||||
|
|
||||||
`acp_adapter/tools.py` maps Hermes tools to ACP tool kinds and builds editor-facing content.
|
`hermes_agent/acp/tools.py` maps Hermes tools to ACP tool kinds and builds editor-facing content.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ ACP does not implement its own auth store.
|
|||||||
|
|
||||||
Instead it reuses Hermes' runtime resolver:
|
Instead it reuses Hermes' runtime resolver:
|
||||||
|
|
||||||
- `acp_adapter/auth.py`
|
- `hermes_agent/acp/auth.py`
|
||||||
- `hermes_cli/runtime_provider.py`
|
- `hermes_cli/runtime_provider.py`
|
||||||
|
|
||||||
So ACP advertises and uses the currently configured Hermes provider/credentials.
|
So ACP advertises and uses the currently configured Hermes provider/credentials.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ This page is the top-level map of Hermes Agent internals. Use it to orient yours
|
|||||||
┌─────────────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────────────┐
|
||||||
│ Entry Points │
|
│ Entry Points │
|
||||||
│ │
|
│ │
|
||||||
│ CLI (cli.py) Gateway (gateway/run.py) ACP (acp_adapter/) │
|
│ CLI (cli.py) Gateway (gateway/run.py) ACP (hermes_agent/acp/)│
|
||||||
│ Batch Runner API Server Python Library │
|
│ Batch Runner API Server Python Library │
|
||||||
└──────────┬──────────────┬───────────────────────┬───────────────────┘
|
└──────────┬──────────────┬───────────────────────┬───────────────────┘
|
||||||
│ │ │
|
│ │ │
|
||||||
@@ -122,7 +122,7 @@ hermes-agent/
|
|||||||
│ # dingtalk, feishu, wecom, wecom_callback, weixin,
|
│ # dingtalk, feishu, wecom, wecom_callback, weixin,
|
||||||
│ # bluebubbles, qqbot, homeassistant, webhook, api_server
|
│ # bluebubbles, qqbot, homeassistant, webhook, api_server
|
||||||
│
|
│
|
||||||
├── acp_adapter/ # ACP server (VS Code / Zed / JetBrains)
|
├── hermes_agent/acp/ # ACP server (VS Code / Zed / JetBrains)
|
||||||
├── cron/ # Scheduler (jobs.py, scheduler.py)
|
├── cron/ # Scheduler (jobs.py, scheduler.py)
|
||||||
├── plugins/memory/ # Memory provider plugins
|
├── plugins/memory/ # Memory provider plugins
|
||||||
├── plugins/context_engine/ # Context engine plugins
|
├── plugins/context_engine/ # Context engine plugins
|
||||||
|
|||||||
@@ -650,7 +650,7 @@ Related entrypoints:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
hermes-acp
|
hermes-acp
|
||||||
python -m acp_adapter
|
python -m hermes_agent.acp
|
||||||
```
|
```
|
||||||
|
|
||||||
Install support first:
|
Install support first:
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ This installs the `agent-client-protocol` dependency and enables:
|
|||||||
|
|
||||||
- `hermes acp`
|
- `hermes acp`
|
||||||
- `hermes-acp`
|
- `hermes-acp`
|
||||||
- `python -m acp_adapter`
|
- `python -m hermes_agent.acp`
|
||||||
|
|
||||||
## Launching the ACP server
|
## Launching the ACP server
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ hermes-acp
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m acp_adapter
|
python -m hermes_agent.acp
|
||||||
```
|
```
|
||||||
|
|
||||||
Hermes logs to stderr so stdout remains reserved for ACP JSON-RPC traffic.
|
Hermes logs to stderr so stdout remains reserved for ACP JSON-RPC traffic.
|
||||||
|
|||||||
Reference in New Issue
Block a user