M0: toolchain, monorepo scaffold, gateway plugin skeleton, CMP app

- gateway-plugin/: android platform plugin (plugin.yaml + adapter.py
  register(ctx) + no-op AndroidAdapter) + stub modules for M1-M5
- app/: Compose Multiplatform project (shared KMP + androidApp +
  desktopApp) with Gradle wrapper; builds :androidApp:assembleDebug
  and :desktopApp:compileKotlin
- scripts/guard_hermes_agent.sh + pre-commit hook: fail if hermes-agent/
  is staged (read-only reference, never committed)
- .gitignore excludes hermes-agent/; docs/ reference library
This commit is contained in:
ARIA committed 2026-08-19 11:27:02 +02:00
commit 59acf66c89
49 files changed
+3950

No files matched your search

+136
View File
@@ -0,0 +1,136 @@
# 15 — hermes-agent Source Reference Map
A cheat-sheet of the **exact hermes-agent files/lines** to read for each
integration point. Paths are relative to `hermes-agent/` (the read-only
reference). This lets a coder jump straight to the right code instead of
re-deriving the architecture.
> ⚠️ Read-only. We **install** our plugin into `~/.hermes/plugins/android`; we
> never edit these files.
## Plugin / platform registration
| What | Where |
|---|---|
| How to add a platform (Plugin Path) | `gateway/platforms/ADDING_A_PLATFORM.md` |
| Canonical plugin-platform example | `plugins/platforms/irc/adapter.py` (esp. `register(ctx)` at :953, `_env_enablement` :677, `_standalone_send` :743, `_get_scoped_secret` :42) |
| ntfy plugin example (push-ish) | `plugins/platforms/ntfy/adapter.py`, `plugin.yaml` |
| `register_platform()` (PluginContext) | `hermes_cli/plugins.py:2774` |
| `PlatformEntry` dataclass (all fields) | `gateway/platform_registry.py:63` |
| `Platform` enum | `gateway/config.py` |
| Plugin discovery (`PluginManager`) | `hermes_cli/plugins.py` |
## Base adapter contract
| What | Where |
|---|---|
| `BasePlatformAdapter` (ABC) | `gateway/platforms/base.py:2890` |
| `MessageEvent` (inbound) | `gateway/platforms/base.py:2300` |
| `MessageType` | `gateway/platforms/base.py:2278` |
| `SendResult` | `gateway/platforms/base.py:2466` |
| `build_source(...)` (SessionSource) | `gateway/platforms/base.py:7047` |
| `handle_message(event)` | `gateway/platforms/base.py:5981` |
| `send()` / `edit_message()` / `delete_message()` | `base.py:3920 / 3976 / 4005` |
| `send_typing` / `stop_typing` | `base.py:4298 / 4307` |
| Media send: `send_image/video/document/voice/animation/image_file/multiple_images` | `base.py:4396 / 4632 / 4659 / 4486 / 4415 / 4339 / 7834(tg)` |
| `extract_images` / `extract_media` / `extract_local_files` | `base.py:4439 / 4884 / 5020` |
| Media cache helpers: `cache_image/audio/video/document_from_bytes` | `base.py:854 / 1005 / 1122 / 2126` |
| Inbound media size: `get_inbound_media_max_bytes` / `validate_inbound_media_size` | `base.py:758 / 779` |
| Media delivery security: `validate_media_delivery_path` + roots/recency/denied | `base.py:1684 / 1312-1480` |
| Interactive: `send_slash_confirm` / `send_clarify` / `send_private_notice` | `base.py:4169 / 4204 / 4278` |
| `create_handoff_thread` | `base.py:3949` |
| Streaming hooks: `supports_draft_streaming` / `send_draft` / `render_message_event` / `format_tool_event` | `base.py:3215 / 3274 / 3316 / 3337` |
| Scoped secret read pattern (profile-safe) | `plugins/platforms/irc/adapter.py:42` |
| Scoped lock (profile-safe bind) | `gateway/status.py` (`acquire_scoped_lock`) |
## Streaming (legacy callback path — what the main gateway uses)
| What | Where |
|---|---|
| `GatewayStreamConsumer` (the sink) | `gateway/stream_consumer.py:156` |
| `on_delta` / `on_commentary` / `on_segment_break` / `finish` | `stream_consumer.py:611 / 518 / 514 / 623` |
| Consumer `run()` loop (edit cadence) | `stream_consumer.py:781` |
| Structured events (ACP-only, NOT main gateway) | `gateway/stream_events.py`, `gateway/stream_dispatch.py:40` |
| Callback wiring (agent → consumer) | `gateway/run.py:5642-5704` (`tool_progress_callback`, `stream_delta_callback`, `interim_assistant_callback`, `status_callback`, `event_callback`) |
| `send_progress_messages` (tool progress → `send`) | `gateway/run.py:4603` |
| Progress metadata | `gateway/run.py:28089` |
## Reasoning display
| What | Where |
|---|---|
| Reasoning prepended to final response | `gateway/run.py:20089-20127` |
| `show_reasoning` / `reasoning_style` defaults + per-platform | `gateway/display_config.py:33-181` |
| `resolve_display_setting()` | `gateway/display_config.py:187` |
| `last_reasoning` in agent result | `gateway/run.py:6471` |
| Think-tag filtering in consumer | `stream_consumer.py:175-185, 627+` |
## Display settings (tool progress, interim, streaming, etc.)
| What | Where |
|---|---|
| All overrideable display keys + defaults | `gateway/display_config.py:33` (`tool_progress`, `show_reasoning`, `reasoning_style`, `tool_preview_length`, `streaming`, `interim_assistant_messages`, `long_running_notifications`, `cleanup_progress`, `live_status`) |
| Per-platform tiers | `gateway/display_config.py:81-181` |
## Slash commands
| What | Where |
|---|---|
| `COMMAND_REGISTRY` / `CommandDef` (all commands) | `hermes_cli/commands.py:144+` |
| `GATEWAY_KNOWN_COMMANDS` / `is_gateway_known_command` / `resolve_command` | `hermes_cli/commands.py` |
| Gateway command dispatch (alias, access, hooks) | `gateway/run.py:16974-17099` |
| `send_model_picker` / `send_choice_picker` (Telegram ref) | `plugins/platforms/telegram/adapter.py:6350 / 6424` |
| Picker invocation from gateway | `gateway/slash_commands.py:1859, 2157, 3622-3657` |
| Button-callback id conventions (`cl:`, `appr:`, `sc:`) | `gateway/platforms/ADDING_A_PLATFORM.md` (Interactive UX) |
## Cron delivery
| What | Where |
|---|---|
| Resolve a delivery target (`platform:chat_id[:thread_id]`) | `cron/scheduler.py:2148` (`_resolve_single_delivery_target`) |
| `_resolve_delivery_targets` / routing tokens (`all`) | `cron/scheduler.py:2296 / 2278` |
| `cron_deliver_env_var` handling (home channel) | `cron/scheduler.py:1903+` |
| `deliver` param normalization | `cron/scheduler.py:2251` |
| `send_message` tool target resolution (`resolve_send_target`, `prepare_send_message_platforms`) | `tools/send_message_tool.py` |
| `parse_target_ref_fn` usage | `tools/send_message_tool.py` (`_parse_target_ref`) |
| Cron mirror delivery (default off) | `cron/scheduler.py:1520` |
| `cronjob` tool schema (`deliver` description) | `tools/cronjob_tools.py` |
## Search (FTS5)
| What | Where |
|---|---|
| Session store (SQLite + FTS5) | `hermes_state.py` |
| Search implementation | `hermes_state_search.py` |
| Schema | `hermes_state_schema.py` |
## Config / env / profiles
| What | Where |
|---|---|
| `get_hermes_home()` / `display_hermes_home()` (profile-safe paths) | `hermes_constants.py` |
| `DEFAULT_CONFIG` / `OPTIONAL_ENV_VARS` | `hermes_cli/config.py` |
| Gateway config load (`load_gateway_config`, `_apply_env_overrides`) | `gateway/config.py` |
| Profile override (`_apply_profile_override`) | `hermes_cli/main.py` |
| Secret scope (multiplex fail-closed) | `agent/secret_scope.py` |
| PII redaction | `agent/redact.py` |
## Dependencies (confirm zero new deps)
| What | Where |
|---|---|
| `websockets==15.0.1` (core) | `pyproject.toml:111` |
| `httpx[socks]==0.28.1` (core) | `pyproject.toml:44` |
| `aiohttp` (messaging extra, NOT core) | `pyproject.toml:185` |
| Dependency pinning policy | `pyproject.toml:19-39`, root `AGENTS.md` |
## Testing
| What | Where |
|---|---|
| Hermetic test runner (use this, not bare pytest) | `scripts/run_tests.sh` |
| `_isolate_hermes_home` fixture | `tests/conftest.py` |
| Example platform tests | `tests/gateway/test_*.py` (e.g. `test_google_chat.py`, `test_line_plugin.py`) |
| Stream-event tests (dispatcher) | `tests/gateway/test_stream_events.py` |
## Existing desktop app (for reference only — we do NOT reuse its backend)
| What | Where |
|---|---|
| Electron desktop app | `apps/desktop/` (its own `AGENTS.md`, `DESIGN.md`) |
| Shared JSON-RPC WS client (tui_gateway protocol) | `apps/shared/src/json-rpc-gateway.ts` |
| tui_gateway WS transport (mobile-client-ready) | `tui_gateway/ws.py` |
| tui_gateway method catalog | `tui_gateway/server.py` |
> Note: the existing desktop app talks to the **`tui_gateway`** backend
> (`hermes serve`), a *different* process from the messaging gateway. Our apps
> talk to the **messaging gateway** via our own plugin + protocol. We borrow
> naming conventions only.