文件最后提交记录最后更新时间
fix: improve telegram topic mode setup 1 个月前
remove: BOOT.md built-in hook (#17093) BOOT.md was merged in PR #3733 before the feature was ready — the built-in hook spawned a bare AIAgent() with no model/runtime kwargs, which immediately 401s on any provider with a custom endpoint. Three separate community PRs (#5240, #12514, #14992) tried to paper over it. Remove the BOOT.md hook entirely and its user-facing docs/tips. Keep the gateway/builtin_hooks/ package and the HookRegistry._register_builtin_hooks() hook-point intact as the extension surface for future always-on gateway hooks. Closes #5239. Co-authored-by: teknium1 <teknium@users.noreply.github.com>1 个月前
fix(telegram): gate send() on send-path health after reconnect storms (#31165) After sustained Bad Gateway / TimedOut reconnect cycles, the PTB httpx client can enter a state where bot.send_message() returns a valid Message (real message_id) but the message never reaches the recipient. TelegramAdapter.send returns SendResult(success=True) and cron's live-adapter branch marks the run delivered while the message is silently dropped. Add a _send_path_degraded flag. _handle_polling_network_error sets it on reconnect storms; the existing _verify_polling_after_reconnect heartbeat probe clears it once getMe() confirms the Bot client is healthy. While the flag is set, send() short-circuits with SendResult(success=False, retryable=True) so cron falls through to the standalone delivery path (fresh HTTP session). Closes #31165. Co-authored-by: teknium1 <127238744+teknium1@users.noreply.github.com> 11 天前
docs(gateway): mention Weixin in gateway help and docstrings Salvage of #21063 — adds 'Weixin, and more' to module-level docstrings in gateway/__init__.py, gateway/config.py, gateway/platforms/base.py and the 'hermes gateway' subparser description. Co-authored-by: wuwuzhijing <chuang.guo@hopechart.com> 23 天前
refactor(ntfy): convert built-in adapter to platform plugin ntfy now ships as a self-contained plugin under plugins/platforms/ntfy/ instead of editing 8 core files (gateway/config.py Platform enum, gateway/run.py factory + auth maps, cron/scheduler.py, toolsets.py, hermes_cli/status.py, agent/prompt_builder.py, gateway/channel_directory.py, tools/send_message_tool.py). All routing goes through gateway/platform_registry via register_platform(): - adapter_factory, check_fn, validate_config, is_connected - env_enablement_fn seeds PlatformConfig.extra from NTFY_* env vars so gateway status reflects env-only setups without instantiating httpx - standalone_sender_fn handles deliver=ntfy cron jobs when cron runs out-of-process from the gateway - allowed_users_env / allow_all_env hook into _is_user_authorized - cron_deliver_env_var=NTFY_HOME_CHANNEL for cron home routing - platform_hint surfaces in the system prompt - pii_safe=True (topic names are the only identifier; no PII to redact) Tests moved to tests/gateway/test_ntfy_plugin.py using _plugin_adapter_loader so the module lives under plugin_adapter_ntfy in sys.modules and cannot collide with sibling plugin-adapter tests on the same xdist worker. The core-file grep tests (Platform.NTFY in source, hermes-ntfy in toolsets, etc.) are replaced with plugin-shape tests covering register() metadata, env_enablement_fn output, and standalone_sender_fn behavior. 68 tests pass under scripts/run_tests.sh. 12 天前
fix(matrix,gateway): Matrix E2EE installs full dep set; plugins respect is_connected Fixes #31116 — two distinct bugs in fresh-install Matrix gateway: 1. Matrix E2EE setup installed only mautrix[encryption], leaving asyncpg / aiosqlite / Markdown / aiohttp-socks uninstalled. The first encrypted connect failed with 'No module named asyncpg' deep inside MatrixAdapter.connect(). Root cause: the setup wizard hand-rolled a pip install of one package instead of using lazy_deps.ensure( 'platform.matrix'), and check_matrix_requirements() short-circuited the runtime installer on 'import mautrix' alone — so the other 4 packages were never pulled in. 2. Discord auto-enabled itself on every gateway start, even when the user never selected Discord and had no DISCORD_BOT_TOKEN. Root cause: gateway/config.py plugin-enablement loop gated enablement on entry.check_fn() (just 'is the SDK importable?') and ignored entry.is_connected (the 'did the user configure credentials?' probe). Same bug class as commit 7849a3d73 fixed for _platform_status in the setup wizard; this is the runtime counterpart. Affects Discord, Teams, and Google Chat. Changes: - hermes_cli/setup.py::_setup_matrix — install via lazy_deps.ensure('platform.matrix') to pull the full feature group. - gateway/platforms/matrix.py::_check_e2ee_deps — verify asyncpg + aiosqlite + PgCryptoStore in addition to OlmMachine, so E2EE failures surface at startup instead of at first encrypted-room connect. - gateway/platforms/matrix.py::check_matrix_requirements — use feature_missing('platform.matrix') as the install gate instead of a single 'import mautrix' check, so partial installs trigger the lazy installer correctly. - gateway/config.py plugin-enablement loop — consult entry.is_connected before flipping enabled=True. Explicit YAML enabled=true still wins. Tests: 3 new in tests/gateway/test_matrix.py (asyncpg-required, aiosqlite-required, partial-install lazy-runs), 5 new in tests/gateway/test_platform_registry.py (is_connected=False blocks, is_connected=True enables, is_connected=None falls back to check_fn, raising probe doesn't enable, explicit YAML wins). Validation: 310 tests across affected test modules pass. 11 天前
fix(gateway): preserve case-sensitive chat IDs in DeliveryTarget.parse Fixes NousResearch/hermes-agent#11768 Root cause: target.strip().lower() was lowercasing the entire target string, corrupting case-sensitive chat IDs like Slack C123ABC and Matrix !RoomABC. Fix: Only lowercase the platform prefix for case-insensitive matching; preserve the original case for chat_id and thread_id values. 1 个月前
chore: ruff auto-fix PLR6201 — tuple → set in membership tests (#23937) Replace with for all literal-tuple membership tests. Set lookup is O(1) vs O(n) for tuple — consistent micro-optimization across the codebase. 608 instances fixed via ruff --fix --unsafe-fixes, 0 remaining. 133 files, +626/-626 (net zero).25 天前
fix(plugins): register dynamically-loaded modules in sys.modules before exec Dashboard plugin API routes (web_server._mount_plugin_api_routes) and gateway event hooks (gateway.hooks.HookRegistry.discover_and_load) both loaded Python files via importlib.util.spec_from_file_location + exec_module without registering the resulting module in sys.modules. That breaks any plugin or hook handler that uses `from __future__ import annotations` together with a Pydantic BaseModel / dataclass / anything that introspects __module__: at first request Pydantic tries to resolve string-form type hints against the defining module's namespace, can't find it by name, and raises: PydanticUserError: TypeAdapter[...] is not fully defined; you should define ... and all referenced types, then call .rebuild() on the instance. This is what broke the kanban dashboard's 'triage' button — POST /api/plugins/kanban/tasks validated against CreateTaskBody (a Pydantic model in a file using from __future__ import annotations) and returned 500 on every click. The fix, applied symmetrically to both loaders: 1. Compute module_name once. 2. Register the module in sys.modules BEFORE exec_module. 3. On exec_module failure, pop the half-initialized stub so subsequent reloads don't pick up broken state. GETs were unaffected because they don't build a body TypeAdapter, which is why this only surfaced when users started POSTing. 1 个月前
Port from cline/cline#10343: periodic gateway memory logging (#27102) Emit a grep-friendly '[MEMORY] rss=...MB ...' line in agent.log / gateway.log every N minutes (default 5) so slow leaks in the long-lived gateway process show up as a time series. Based on https://github.com/cline/cline/pull/10343 (src/standalone/memory-monitor.ts). - gateway/memory_monitor.py: new module. Daemon thread, baseline on start, final snapshot on stop. Uses resource.getrusage() (stdlib) first, falls back to psutil, disables itself with one WARNING if neither is available. - gateway/run.py: start monitor right after setup_logging() in start_gateway(); stop it in the shutdown block next to MCP teardown. - hermes_cli/config.py: logging.memory_monitor { enabled, interval_seconds } defaults under the existing logging section. - tests/gateway/test_memory_monitor.py: 10 unit tests covering format, baseline/shutdown snapshots, double-start noop, periodic timer, daemon thread invariant, and unavailable-RSS warn-and-skip path. Adapted from TypeScript/Node to Python (threading.Event-based daemon thread instead of setInterval/unref), added Python-specific gc + thread counts to the log line (handier than ext/arrayBuffers for diagnosing Python gateway leaks), and gated behind a config.yaml toggle so users can silence the periodic line if they want. No heap-snapshot-on-OOM equivalent — CPython doesn't have V8's --heapsnapshot-near-heap-limit; tracemalloc would be the Python equivalent but adds non-trivial overhead, so leaving that out.20 天前
refactor(gateway): drop _append_to_jsonl from mirror Mirror messages are persisted via _append_to_sqlite. JSONL writer was a redundant dual-write. Updated test assertions from JSONL file checks to SQLite mock verification. 16 天前
fix(gateway): preserve WhatsApp pairing approvals across JID/LID alias flips 13 天前
refactor(plugins): add apply_yaml_config_fn registry hook Lets platform plugins own their YAML→env config bridge instead of forcing core gateway/config.py to know every platform's schema. The hook receives the full parsed config.yaml and the platform's own sub-dict, may mutate os.environ (env > YAML precedence preserved via the standard not os.getenv(...) guards), and may return a dict to merge into PlatformConfig.extra. It runs during load_gateway_config() after the existing generic shared-key loop and before _apply_env_overrides(), mirroring the env_enablement_fn dispatch pattern (#21306, #21331). Pure addition — no behavior change for existing platforms. Each of the eight platforms with hardcoded YAML→env blocks today (discord, telegram, whatsapp, slack, dingtalk, mattermost, matrix, feishu, ~252 LOC in gateway/config.py) can migrate in independent follow-up PRs; the hardcoded blocks remain functional in the meantime, and their not os.getenv(...) guards make them no-ops for any env var the hook already set. Test coverage: 10 new tests in tests/gateway/test_platform_registry.py covering field default, callable acceptance, env mutation, extras merge, both signature args, exception swallowing, missing/non-dict sections, and env > YAML precedence. Refs #3823, #24356. Closes #24836. 22 天前
fix(gateway): address restart review feedback 1 个月前
fix(telegram): preserve new DM topic lanes 11 天前
feat(gateway): opt-in runtime-metadata footer on final replies (#17026) Append a compact 'model · 68% · ~/projects/hermes' footer to the FINAL message of each turn, disabled by default (display.runtime_footer.enabled). Answers the Telegram-side parity ask: runtime context that the CLI status bar already shows is now available in messaging replies when enabled. Wiring: - gateway/runtime_footer.py: resolve_footer_config + format_runtime_footer + build_footer_line. Pure-function renderer; per-platform overrides under display.platforms.<platform>.runtime_footer. - gateway/run.py: appends footer to response right after reasoning prepend so it lands only on the final message (never tool progress or streaming chunks). When streaming already delivered the body (already_sent), the footer is sent as a small trailing message instead. - agent_result now exposes context_length alongside last_prompt_tokens so the footer can compute the pct; both gateway return paths updated. - /footer [on|off|status] slash command, wired in CLI (cli.py) and gateway (gateway/run.py both running-agent bypass and main dispatch). Global toggle only; per-platform overrides via config.yaml. Graceful degradation: - Missing context_length (unknown model) → pct field silently dropped (no '?%' artifact). - Empty final_response → no footer appended. - Unknown field names in config → silently ignored. Tests: 25-case unit suite (tests/gateway/test_runtime_footer.py) plus E2E harness covering streaming vs non-streaming branches, per-platform override, and the exact argument contract gateway/run.py uses. Co-authored-by: teknium1 <teknium@users.noreply.github.com>1 个月前
fix(gateway): separate observed Telegram group context 13 天前
fix(cli): synchronize HERMES_SESSION_ID across environment and contextvar during session switches 12 天前
chore: ruff auto-fixes — collapsible-else-if, if-stmt-min-max, dict.fromkeys (#23926) PLR5501 (collapsible-else-if): 28 instances — else: if: → elif: PLR1730 (if-stmt-min-max): 15 instances — if x<y: x=y → x=max(x,y) C420 (dict.fromkeys): 2 instances — dictcomp → dict.fromkeys PLR1704 (redefined-argument): 1 instance — reason → err_msg (shadow fix) C414 (unnecessary-list): 1 instance — sorted(list(x)) → sorted(x) 28 files, -44 net lines. All mechanical, zero logic changes. 17,211 tests pass, zero regressions.25 天前
feat(gateway): per-platform admin/user split for slash commands (salvage of #4443) (#23373) * feat(gateway): per-platform admin/user split for slash commands Adds an opt-in two-list access control on top of the existing per-platform allow_from allowlists, scoped to slash commands only: - allow_admin_from — full slash command access - user_allowed_commands — what non-admins may run - group_allow_admin_from — same, group/channel scope - group_user_allowed_commands When allow_admin_from is unset for a scope, gating is disabled and every allowed user keeps full access (backward compat). Plain chat is unaffected. /help and /whoami are always reachable so users can see what they can run. Gate runs at the slash command dispatch site in gateway/run.py and uses is_gateway_known_command(), so it covers built-in AND plugin-registered commands through the live registry without per-feature wiring. Adds /whoami showing platform, scope, tier, and runnable commands. Salvage of PR #4443's permission tier work, scoped down. The full tier system, tool filtering, audit log, usage tracking, rate limiting, /promote flow, and persistent SQLite stores are not included here — those can be re-expanded later if needed. Co-authored-by: ReqX <mike@grossmann.at> * fix(gateway): close running-agent fast-path bypass + add coverage and central docs The slash command access gate was only applied at the cold dispatch site (line ~5921). When an agent was already running, the running-agent fast-path block (line ~5574) dispatched /restart, /stop, /new, /steer, /model, /approve, /deny, /agents, /background, /kanban, /goal, /yolo, /verbose, /footer, /help, /commands, /profile, /update directly without going through the gate — letting non-admins bypass gating just because an agent happens to be busy. Refactored the gate into _check_slash_access() and called from BOTH paths. /status remains intentionally pre-gate so users can always see session state. Also added 18 more dispatch tests covering: - Running-agent fast-path: blocks non-admin, allows admin, /status always works - Alias canonicalization (gate uses canonical name, not user alias) - Unknown / unregistered commands pass through (don't false-positive) - DM admin scope-locked when group has its own admin list - Multi-platform isolation (Discord gated, Telegram unrestricted) Docs: added Slash Command Access Control section to the central messaging index page + /whoami row in the chat commands table. Co-authored-by: ReqX <mike@grossmann.at> --------- Co-authored-by: ReqX <mike@grossmann.at>26 天前
fix: gateway PID detection fails on Windows (two issues) - _read_process_cmdline: /proc and 'ps' are unavailable on Windows, so process cmdline was always empty. Add psutil fallback (already a hard dependency used by _pid_exists in the same module). - _record_looks_like_gateway: argv paths use backslashes on Windows but patterns use forward slashes/dots, so the fallback record check always failed. Normalize backslashes to forward slashes before matching. Together these caused get_running_pid() to return None on Windows even when the gateway process is alive, making the dashboard report gateway as 'stopped' despite it functioning normally. 22 天前
fix: guard yaml.safe_load, flock unlock, TOCTOU races, and atomic writes 1. trajectory_compressor.py: yaml.safe_load() returns None on empty files, crashing with TypeError on if 'tokenizer' in data. Fix by adding or {} fallback. (HIGH — blocks startup with empty config) 2. 6 files with fcntl.flock(LOCK_UN) in finally blocks without try/except: cron/scheduler.py, hermes_cli/auth.py, agent/shell_hooks.py, tools/skill_usage.py, tools/environments/file_sync.py, tools/memory_tool.py. If unlock raises OSError, fd.close() is skipped and the lock is held forever. The msvcrt branches already had try/except; the fcntl branches did not. Fix by wrapping in try/except (OSError, IOError): pass. 3. agent/copilot_acp_client.py line 639: TOCTOU race — path.exists() followed by path.read_text() with no try/except. If file is deleted between the check and the read, FileNotFoundError propagates. Fix by using try/except FileNotFoundError. 4. gateway/sticker_cache.py: non-atomic write via Path.write_text() can leave truncated JSON on crash, causing JSONDecodeError on next load. Fix by writing to tempfile + fsync + os.replace (atomic). 17 天前
test(gateway): regression for plugin-transformed response after streaming Adds a test that fails without the gateway fix, exercising the response_transformed=True branch in _finalize_response: a streamed response whose final text was modified by a transform_llm_output plugin hook must be edit_message'd in place (not duplicate-sent), with already_sent=True so the normal final-send is skipped. Also drops two minor leftovers from the salvaged PR #29119: * accumulated_text property on GatewayStreamConsumer (unused) * duplicate _response_transformed=False inside the hook try block 12 天前
fix(whatsapp_identity): pin identifier regex to ASCII, clarify it's defense-in-depth Follow-up on top of #16243. Two small tweaks: - Compile the regex once as _SAFE_IDENTIFIER_RE and pin it to [A-Za-z0-9@.+\-]. The previous \w accepts Unicode word chars (full-width digits, accented letters) which aren't valid WhatsApp identifiers and shouldn't reach the mapping-file lookup. - Add a comment clarifying this is defense-in-depth, not a live traversal. The hardcoded lid-mapping-{current}{suffix}.json prefix already prevents escape via pathlib's component split — with current='../secrets', the first path component under session/ is the literal directory name lid-mapping-.., which the attacker cannot create. E2E verified: legit mapping chains still resolve, all probed attack shapes (../, absolute paths, shell metacharacters, Unicode digit tricks) are rejected before any file access. 1 个月前