fix(gateway): allow settings slash commands during active sessions - #12387
Closed
cometjc wants to merge 14 commits into
Closed
fix(gateway): allow settings slash commands during active sessions#12387cometjc wants to merge 14 commits into
cometjc wants to merge 14 commits into
Conversation
Introduces a dedicated `telegram_topic` tool for forum topic lifecycle ops (create/close/reopen/delete/rename/list) alongside the existing `send_message` tool. Targets use the same `telegram:<chat_id>[:<thread_id>]` format; `delete` is gated behind `confirm=true`; Telegram API errors are mapped to stable codes (`no_rights`, `topic_not_found`, `chat_not_found`, `topic_closed`, `unknown`). `action='list'` enumerates topics observed via incoming messages (source='observed_sessions'), since the Bot API does not expose a topic enumeration endpoint. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Short orientation file for Claude Code instances: points at AGENTS.md as the canonical dev guide, then extracts the must-know-before-editing surface -- `scripts/run_tests.sh` as the only correct test entrypoint, the auto-discovery registration pattern in `tools/registry.py`, the single `COMMAND_REGISTRY` as source of truth for slash commands, profile-safe path rules (`get_hermes_home()` / `display_hermes_home()`), the Ink+Python TUI split, and the prompt-caching invariant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without this entry, the gateway's platform→toolset reverse-map in _get_platform_tools (hermes_cli/tools_config.py) only surfaced tools whose names are subset members of a CONFIGURABLE_TOOLSETS key. Since telegram_topic was added to _HERMES_CORE_TOOLS but not to the "messaging" toolset definition, it was silently dropped before reaching get_tool_definitions — the bot registered the handler but never saw the schema, so the model couldn't call it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CLAUDE.md was a condensed pointer-summary of AGENTS.md; every invariant and architecture note it carried was already spelled out in AGENTS.md at greater depth. Replace it with a symlink so Claude Code and other agents that probe CLAUDE.md resolve to the same canonical guide — keeping one source of truth and removing the risk of the summary drifting out of sync with the authoritative doc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adding a tool to _HERMES_CORE_TOOLS alone silently drops it from messaging-platform bots when the user has no platform_toolsets override, because _get_platform_tools reverse-maps to CONFIGURABLE_TOOLSETS keys — anything not listed under one of those keys never reaches the model. Document the two-place registration requirement, the _check_send_message gating pattern for bot-only tools, and reference the recent telegram_topic regression so this trap stops catching people. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The watch_file list referenced nix/package.nix, which does not exist in the tree — the real file is nix/packages.nix. `direnv status` showed it as a 1970-01-01 epoch entry, meaning direnv was tracking a non-existent path and changes to the actual packages.nix never triggered a reload. Correct the name so edits to the package derivation invalidate the dev shell as intended. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a user opens a new forum topic by forwarding a message into a group, the kickoff message arrives with forum_topic_created (and/or forward_origin/forward_from/forward_from_chat). Under require_mention=true none of these match the existing trigger gates, so the bot would stay silent until a second message landed in the topic. Treat both signals as valid triggers in _should_process_message so forwarded topic kickoffs get picked up immediately. ignored_threads still wins, so muted threads are unaffected. Tests: - test_forum_topic_created_message_bypasses_mention_requirement - test_forwarded_message_bypasses_mention_requirement (forward_origin / forward_from / forward_from_chat) - test_ignored_threads_still_drop_forum_topic_created
- Design doc: architecture, data flow, failure modes, rollout. - Implementation plan: 10 phases, bite-sized TDD tasks. - Phase 0 pre-flight checks done (real state.db, 12/12 recall). - Zero-change to Hermes core; standalone mempalace-sync daemon repo.
…c w/ 24h TTL
When a parent session on Telegram dispatches delegate_task, the gateway now
lazily creates a dedicated forum topic named from the task goal (e.g. "SA
讀 README…") and forwards every subagent.* event into that topic instead
of interleaving it with the main conversation. Benefits:
* Parent thread stays clean — only a one-shot pointer message (t.me deep
link to the new topic) on lazy create.
* Each parent session maps to exactly one topic; user replies inside the
new topic naturally become a fresh top-level agent session (depth=0)
because gateway's session key already includes thread_id.
* State persisted at ~/.hermes/state/subagent_topics.json (atomic writes,
chmod 0o600).
* A cron reaper (`cron/subagent_topic_reaper.py`, scheduled every 15m
via mcp_cronjob) deletes topics whose last_message_ts is older than
24h, keeping the forum tidy.
Highlights:
* gateway/subagent_topic_router.py — per-session lazy create, linear-
history forwarding, soft-fail everywhere, runtime blocklist for
non-forum chats, per-session backpressure.
* gateway/run.py — progress_callback now routes subagent.* events via
the router and short-circuits before the existing tool.started path,
so subagent chatter never leaks into the parent's progress message.
* cron/subagent_topic_reaper.py — standalone sweep (`python -m` + CLI),
dry-run mode, re-load-before-prune to avoid clobbering concurrent
router writes, classified Telegram errors → skipped_gone vs failed.
* 103 new unit tests (router 74 + reaper 29) — all pass in <1s.
Design doc: docs/plans/2026-04-19-subagent-progress-ttl-topics.md
…space) The /plan skill writes timestamped markdown into the workspace-local .hermes/plans/ directory. Published plans are copied to docs/plans/ with the agreed repo naming convention; the .hermes/ root is pure scratch and should not pollute git status.
The gateway progress callback truncates terminal previews to 40 chars via tool_preview_length, so `cd /home/long/path && real_cmd` used to render as "cd /home/long/path..." in Telegram/Slack progress messages — completely hiding what the agent actually ran. build_tool_preview() now strips leading `cd <path> && ` prefixes (including chained `cd a && cd b && cmd`) before truncation, so the preview shows the real command. Quoted paths with spaces work; malformed inputs are left alone. Preferred solution is still the tool's `workdir` parameter (no cd at all), but this fallback catches the common agent habit when we forget. 6 new unit tests in tests/agent/test_display.py cover the happy paths, chained cds, quoted paths, truncation interaction, and the malformed-input safe fallback.
Root cause: the adapter-layer dispatch in gateway/platforms/base.py:1617 checks should_bypass_active_session(cmd) BEFORE any per-command logic in gateway/run.py runs. Commands not in the allowlist fall through to _busy_session_handler, which feeds the command text to the running agent as interrupt input and replies with '⚡ Interrupting current task...' — the command handler is never called. Symptom: on Telegram, sending /model (or /verbose, /fast, /reasoning, /yolo, /personality, /provider, /voice) while the agent is running produces the interrupt ack instead of executing the command. Fix: extend ACTIVE_SESSION_BYPASS_COMMANDS with the settings/info commands. Each handler was audited and confirmed NOT to touch running_agent.* or mutate _running_agents — they only read/write config. Commands added: - model, provider (model selection / provider listing) - verbose, fast, reasoning, yolo (runtime config toggles) - personality, voice (persona / TTS toggles) Also replaced the dead-code 'Agent is running — wait or /stop first' guard for /model in gateway/run.py:3022 with an actual dispatch to the model handler, kept as a defensive fallback in case the allowlist is later narrowed. TDD: tests/gateway/test_active_session_bypass_commands.py added first (failed before the fix, passes after). Tests run: tests/gateway/test_active_session_bypass_commands.py -> 23 passed tests/hermes_cli/test_commands.py -> passed tests/gateway/test_busy_session_ack.py -> passed tests/gateway/test_verbose_command.py -> passed
Author
|
Closing as duplicate — #12334 already landed the more comprehensive fix on Thanks for the clean upstream fix. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Slash commands like
/model,/verbose,/fast,/reasoning,/yolo,/personality,/provider,/voiceare silently eaten when sent on Telegram while an agent is actively running. Instead of invoking the command handler, the user gets the⚡ Interrupting current task...ack and the command text is fed into the running agent as interrupt input.Root Cause
gateway/platforms/base.py:1614-1634checksshould_bypass_active_session(cmd)before any per-command logic ingateway/run.pyruns. Commands not in the bypass allowlist fall through to_busy_session_handler(gateway/run.py:1391), which callsrunning_agent.interrupt(event.text)and replies with the interrupt ack. The command handler is never reached.The bypass allowlist
ACTIVE_SESSION_BYPASS_COMMANDSinhermes_cli/commands.pypreviously only covered control/info commands (agents, approve, background, commands, deny, help, new, profile, queue, restart, status, stop, update). Settings commands were missing, so/model(and the other settings toggles) fell through to the busy handler.Side effect: the
/modelguard atgateway/run.py:3022-3024(return "Agent is running — wait or /stop first, then switch models.") became dead code, since the adapter short-circuits before_handle_messageis ever called.Fix
ACTIVE_SESSION_BYPASS_COMMANDSwith 8 settings commands:model,provider,verbose,fast,reasoning,yolo,personality,voice./modelstring-return ingateway/run.py:3022-3028with an actual dispatch to_handle_model_command, kept as a defensive fallback for adapters that don't call the bypass check or if the allowlist is later narrowed.Handler safety audit
Each of the 8 added handlers was audited — none call
running_agent.interrupt/cancelor mutate_running_agents. They only read/write config:/model_handle_model_command_session_model_overrides,_agent_cacheeviction, cached entryswitch_model()(public API)/provider_handle_provider_command/verbose_handle_verbose_commanddisplay.platforms.<p>.tool_progressYAML/fast_handle_fast_command_service_tier+ YAML/reasoning_handle_reasoning_command_reasoning_config,_show_reasoning+ YAML/yolo_handle_yolo_commandtools/approval/personality_handle_personality_command_ephemeral_system_prompt+ YAML/voice_handle_voice_command_voice_mode, adapter voice stateAll 8 effects apply to the next turn (or are read-only), so dispatching them mid-run is safe.
Tests
New:
tests/gateway/test_active_session_bypass_commands.py(TDD — failed before fix, 23 green after):/skills) and unknown command both returnFalseVerified green locally:
Test Plan
/modelon a Telegram thread with an agent mid-run — should now open the model picker, not produce the interrupt ack./stopand/newstill interrupt as expected (regression check).Scope
Touches 3 files (
hermes_cli/commands.py,gateway/run.py, new test). No behavioral changes outside the allowlist extension.