fix(gateway): propagate user identity through process watcher pipeline

Background process watchers (notify_on_complete, check_interval) created
synthetic SessionSource objects without user_id/user_name. While the
internal=True bypass (1d8d4f28) prevented false pairing for agent-
generated notifications, the missing identity caused:

- Garbage entries in pairing rate limiters (discord:None, telegram:None)
- 'User None' in approval messages and logs
- No user identity available for future code paths that need it

Additionally, platform messages arriving without from_user (Telegram
service messages, channel forwards, anonymous admin actions) could still
trigger false pairing because they are not internal events.

Fix:
1. Propagate user_id/user_name through the full watcher chain:
   session_context.py → gateway/run.py → terminal_tool.py →
   process_registry.py (including checkpoint persistence/recovery)

2. Add None user_id guard in _handle_message() — silently drop
   non-internal messages with no user identity instead of triggering
   the pairing flow.

Salvaged from PRs #7664 (kagura-agent, ContextVar approach),
#6540 (MestreY0d4-Uninter, tests), and #7709 (guang384, None guard).

Closes #6341, #6485, #7643
Relates to #6516, #7392
This commit is contained in:
Teknium
2026-04-11 12:09:01 -07:00
committed by Teknium
parent dafe443beb
commit cac6178104
8 changed files with 167 additions and 0 deletions

View File

@@ -1427,8 +1427,12 @@ def terminal_tool(
if _gw_platform and not check_interval:
_gw_chat_id = _gse("HERMES_SESSION_CHAT_ID", "")
_gw_thread_id = _gse("HERMES_SESSION_THREAD_ID", "")
_gw_user_id = _gse("HERMES_SESSION_USER_ID", "")
_gw_user_name = _gse("HERMES_SESSION_USER_NAME", "")
proc_session.watcher_platform = _gw_platform
proc_session.watcher_chat_id = _gw_chat_id
proc_session.watcher_user_id = _gw_user_id
proc_session.watcher_user_name = _gw_user_name
proc_session.watcher_thread_id = _gw_thread_id
proc_session.watcher_interval = 5
process_registry.pending_watchers.append({
@@ -1437,6 +1441,8 @@ def terminal_tool(
"session_key": session_key,
"platform": _gw_platform,
"chat_id": _gw_chat_id,
"user_id": _gw_user_id,
"user_name": _gw_user_name,
"thread_id": _gw_thread_id,
"notify_on_complete": True,
})
@@ -1457,10 +1463,14 @@ def terminal_tool(
watcher_platform = _gse2("HERMES_SESSION_PLATFORM", "")
watcher_chat_id = _gse2("HERMES_SESSION_CHAT_ID", "")
watcher_thread_id = _gse2("HERMES_SESSION_THREAD_ID", "")
watcher_user_id = _gse2("HERMES_SESSION_USER_ID", "")
watcher_user_name = _gse2("HERMES_SESSION_USER_NAME", "")
# Store on session for checkpoint persistence
proc_session.watcher_platform = watcher_platform
proc_session.watcher_chat_id = watcher_chat_id
proc_session.watcher_user_id = watcher_user_id
proc_session.watcher_user_name = watcher_user_name
proc_session.watcher_thread_id = watcher_thread_id
proc_session.watcher_interval = effective_interval
@@ -1470,6 +1480,8 @@ def terminal_tool(
"session_key": session_key,
"platform": watcher_platform,
"chat_id": watcher_chat_id,
"user_id": watcher_user_id,
"user_name": watcher_user_name,
"thread_id": watcher_thread_id,
})