fix(tui_gateway): pass live session context to plugin slash-command handlers - #44563
fix(tui_gateway): pass live session context to plugin slash-command handlers#44563benegessarit wants to merge 2 commits into
Conversation
…andlers Both TUI plugin dispatch paths (the slash.exec inline plugin fast path and the command.dispatch plugin fallback) called handler(arg) bare. The gateway process carries no HERMES_SESSION_* environment, so a plugin command that needs the invoking session (to bookmark it, annotate it, or read its transcript) had no reliable way to resolve it: it either failed, or worse, guessed from profile-global state and bound the action to an unrelated session. Add call_plugin_command_handler() to hermes_cli.plugins: it inspects the handler signature and passes session_id/session_key kwargs only to handlers that declare them (or **kwargs), keeping the documented fn(raw_args) contract intact for existing plugins. Both dispatch sites now resolve the gateway session's canonical session_key and call handlers through it. Explicit kwargs rather than a HERMES_SESSION_* env bridge because gateway handlers can run concurrently; process-global mutation could bind one session's command to another session's context.
|
Verified clean.
|
✅ Verification: session context pass-through for plugin handlersThe
Test coverage is thorough: 6 tests covering legacy handlers, declared kwargs, Looks good to me. |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for isolating the two TUI dispatch paths and preserving legacy fn(raw_args) handlers.
Problems
- The new calls pass the same
session_keyfor bothsession_idandsession_key. Current main explicitly keeps those identities separate during session rotation:tui_gateway/server.py:3123-3130documents that compression can rotateagent.session_id, and_session_lookup_key()attui_gateway/server.py:6017-6024prefers that live agent ID. A plugin acceptingsession_idcan therefore receive a gateway key rather than the durable session ID. - The added tests set
agenttoNoneand assert identical values, so they cannot detect that mismatch.
Suggested changes
- Derive
session_idfromgetattr(session.get("agent"), "session_id", None) or session_key, while preservingsession_keyfrom the session dict, at both TUI dispatch sites. - Cover a distinct agent session ID and gateway session key in both dispatch-path regressions.
Automated hermes-sweeper review.
| ) | ||
|
|
||
| handler = get_plugin_command_handler(name) | ||
| if handler: |
There was a problem hiding this comment.
session_id and session_key are distinct TUI identities: use getattr(session.get("agent"), "session_id", None) or session_key for the former, while retaining the stored gateway key for the latter. Current main documents their divergence during compression in _sync_session_key_after_compress().
What does this PR do?
Plugin slash commands registered via
ctx.register_command()can't tell which session invoked them when dispatched through the TUI gateway. Both dispatch paths (slash.exec's inline plugin fast path andcommand.dispatch's plugin fallback) callhandler(arg)with only the raw argument string, and the gateway process doesn't reliably carryHERMES_SESSION_*env the way the CLI process does (whereagent_initsets it in-process). I hit this building a session-scoped bookmark command: the handler either fails closed or, worse, resolves a session from profile-global state and binds the action to the wrong conversation.This adds
call_plugin_command_handler()tohermes_cli.plugins: it inspects the handler signature and passessession_id/session_keykwargs only to handlers that declare them (or**kwargs). Legacyfn(raw_args)handlers are called exactly as before. Both TUI dispatch sites now resolve the gateway session'ssession_keyand route through it.Explicit kwargs rather than an env-var bridge because gateway handlers can run concurrently — process-global mutation could bind one session's command to another session's context.
Scoped deliberately to the TUI gateway, the path with no working fallback. The CLI and messaging-gateway dispatch sites could adopt the same helper as a follow-up; #42416 is in flight covering those plus hook propagation, and I'm happy to converge with it whichever way you prefer (the helper here is intentionally compatible with that PR's design).
Related Issue
No existing issue found (searched for plugin session context). #42416 overlaps on the command-handler half — see note above.
Type of Change
Changes Made
hermes_cli/plugins.py— newcall_plugin_command_handler();register_command()docstring documents the optional context kwargstui_gateway/server.py— both plugin dispatch sites (slash.execfast path,command.dispatchfallback) pass the live session keytests/tui_gateway/test_protocol.py— regression tests: both paths deliver kwargs to declaring handlers, legacy handlers stay untouched, staleHERMES_SESSION_*env is neither consulted nor mutatedtests/hermes_cli/test_plugins.py— unit tests for the helper (legacy, declared-kwargs,**kwargs, empty-context skip, uninspectable-handler fallback, async)How to Test
scripts/run_tests.sh tests/tui_gateway/test_protocol.py tests/hermes_cli/test_plugins.py— 152 tests, greensession_key="", run it from the TUI, and check the handler receives the live session key (before this change it gets"")Checklist
Code
fix(scope):,feat(scope):, etc.)scripts/run_tests.sh): the touched suites are fully green (152/152). A full local run has a handful of environment-dependent failures (browser/vision/provider-credential tests) that reproduce byte-identically on the unmodified base commit — verified by A/B at 880107a — so none are introduced by this change.Documentation & Housekeeping
register_command()docstring) — or N/Acli-config.yaml.exampleif I added/changed config keys — N/A (no config changes)CONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/Ainspectonly