mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
Complete ACP implementation enabling hermes-agent to work as a coding agent inside VS Code, Zed, JetBrains IDEs, and any ACP-compatible editor. Based on PR #837 by teknium1, with full implementation of the prompt flow and fixes for broken event bridging. ## ACP Adapter (acp_adapter/, ~1200 lines) server.py — HermesACPAgent with all 15 Agent protocol methods: - Full session lifecycle (new, load, resume, list, fork, cancel) - prompt() runs AIAgent in thread executor, streams tool events, thinking, and agent messages back to the editor in real-time - Permission bridging for dangerous command approval dialogs - Model switching support session.py — Thread-safe SessionManager with per-session AIAgent, conversation history, and model tracking events.py — Callback factories bridging AIAgent's sync callbacks to ACP's async notifications via run_coroutine_threadsafe() tools.py — Tool kind mapping (25+ tools) with human-readable titles, diff content for file edits, and result truncation permissions.py — Maps ACP permission dialogs to hermes approval flow auth.py — Provider credential detection entry.py — CLI entry point with stderr logging ## Key Design Decisions - No modifications to run_agent.py — ACP works entirely through AIAgent's existing callback system (tool_progress_callback, thinking_callback, step_callback) - File edits shown as diffs in the editor (FileEditToolCallContent) - Terminal commands shown with $ prefix - Large tool outputs truncated for the UI (5000 char limit) - Approval for dangerous commands routed to editor permission dialog ## Also includes - jupyter-live-kernel skill for data science workflows - acp_registry/ with agent.json for editor auto-discovery - docs/acp-setup.md with VS Code, Zed, JetBrains setup guides - hermes acp CLI subcommand ## Tests - 81 new ACP tests covering server, session, events, tools, auth, permissions - Full suite: 3330 passed, 16 skipped Closes #837
86 lines
2.5 KiB
Python
86 lines
2.5 KiB
Python
"""CLI entry point for the hermes-agent ACP adapter.
|
|
|
|
Loads environment variables from ``~/.hermes/.env``, configures logging
|
|
to write to stderr (so stdout is reserved for ACP JSON-RPC transport),
|
|
and starts the ACP agent server.
|
|
|
|
Usage::
|
|
|
|
python -m acp_adapter.entry
|
|
# or
|
|
hermes-agent --acp (once wired into the CLI)
|
|
"""
|
|
|
|
import asyncio
|
|
import logging
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def _setup_logging() -> None:
|
|
"""Route all logging to stderr so stdout stays clean for ACP stdio."""
|
|
handler = logging.StreamHandler(sys.stderr)
|
|
handler.setFormatter(
|
|
logging.Formatter(
|
|
"%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
datefmt="%Y-%m-%d %H:%M:%S",
|
|
)
|
|
)
|
|
root = logging.getLogger()
|
|
root.handlers.clear()
|
|
root.addHandler(handler)
|
|
root.setLevel(logging.INFO)
|
|
|
|
# Quiet down noisy libraries
|
|
logging.getLogger("httpx").setLevel(logging.WARNING)
|
|
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
|
logging.getLogger("openai").setLevel(logging.WARNING)
|
|
|
|
|
|
def _load_env() -> None:
|
|
"""Load .env from HERMES_HOME (default ``~/.hermes``)."""
|
|
from dotenv import load_dotenv
|
|
|
|
hermes_home = Path(os.getenv("HERMES_HOME", Path.home() / ".hermes"))
|
|
env_file = hermes_home / ".env"
|
|
if env_file.exists():
|
|
try:
|
|
load_dotenv(dotenv_path=env_file, encoding="utf-8")
|
|
except UnicodeDecodeError:
|
|
load_dotenv(dotenv_path=env_file, encoding="latin-1")
|
|
logging.getLogger(__name__).info("Loaded env from %s", env_file)
|
|
else:
|
|
logging.getLogger(__name__).info(
|
|
"No .env found at %s, using system env", env_file
|
|
)
|
|
|
|
|
|
def main() -> None:
|
|
"""Entry point: load env, configure logging, run the ACP agent."""
|
|
_setup_logging()
|
|
_load_env()
|
|
|
|
logger = logging.getLogger(__name__)
|
|
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
|
|
from .server import HermesACPAgent
|
|
|
|
agent = HermesACPAgent()
|
|
try:
|
|
asyncio.run(acp.run_agent(agent))
|
|
except KeyboardInterrupt:
|
|
logger.info("Shutting down (KeyboardInterrupt)")
|
|
except Exception:
|
|
logger.exception("ACP agent crashed")
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|