feat: support multi-user Discord threads via context var

- Add contextvars.ContextVar to carry resolved peer name from
  pre_gateway_dispatch to Honcho session init without modifying
  event.user_id (auth uses raw Discord snowflake)
- _pre_gateway_dispatch: now stores resolved peer in context var
- _patched_do_session_init: reads context var first, falls back
  to kanban resolution, then to existing user_id

Requires thread_sessions_per_user: true in profile config.yaml
(added to ashley, claire, finn, matt, paul)
This commit is contained in:
2026-05-24 22:52:57 -04:00
parent 514706bcce
commit 6ebeb39268

View File

@@ -10,6 +10,7 @@ Config: /opt/data/identity-config.json (persistent volume)
from __future__ import annotations from __future__ import annotations
import contextvars
import json import json
import logging import logging
import os import os
@@ -23,6 +24,12 @@ logger = logging.getLogger(__name__)
CONFIG_PATH = Path("/opt/data/identity-config.json") CONFIG_PATH = Path("/opt/data/identity-config.json")
# Context var to carry resolved peer name from pre_gateway_dispatch
# to Honcho's _do_session_init without modifying event.user_id.
_resolved_peer_var: contextvars.ContextVar[str | None] = contextvars.ContextVar(
"identity_resolved_peer", default=None
)
# ── Config ────────────────────────────────────────────────────────────────── # ── Config ──────────────────────────────────────────────────────────────────
@@ -161,10 +168,22 @@ _original_init: Callable | None = None
def _patched_do_session_init(self, cfg, session_id: str, **kwargs): def _patched_do_session_init(self, cfg, session_id: str, **kwargs):
"""Wrapper around Honcho's _do_session_init. """Wrapper around Honcho's _do_session_init.
If no user_id was provided by the gateway (kanban worker / CLI context), Priority for user_id:
resolve one from the identity config and inject it. 1. _resolved_peer_var from pre_gateway_dispatch (gateway multi-user)
2. get_peer_name() for kanban workers
3. kwargs.get("user_id") as-is (existing Discord snowflake fallback)
""" """
if not kwargs.get("user_id"): # Check context var first (set by _pre_gateway_dispatch for
# gateway-originated messages — peer name already resolved)
resolved = _resolved_peer_var.get()
if resolved:
logger.info(
"identity: Honcho peer '%s' from gateway dispatch (was %s)",
resolved, kwargs.get("user_id", "(none)"),
)
kwargs["user_id"] = resolved
elif not kwargs.get("user_id"):
# Kanban worker / CLI — no user_id set by gateway
resolved = get_peer_name() resolved = get_peer_name()
if resolved: if resolved:
logger.info( logger.info(
@@ -222,13 +241,17 @@ def _apply_honcho_patch():
def _pre_gateway_dispatch(event: Any, gateway: Any, session_store: Any, **kw) -> dict | None: def _pre_gateway_dispatch(event: Any, gateway: Any, session_store: Any, **kw) -> dict | None:
"""Log resolved peer identity before dispatch.""" """Resolve peer identity and store it for Honcho session init."""
platform = getattr(event, "platform", "unknown") platform = getattr(event, "platform", "unknown")
user_id = getattr(event, "user_id", "unknown") user_id = getattr(event, "user_id", "unknown")
resolved = resolve_peer(platform, user_id) resolved = resolve_peer(platform, user_id)
if resolved: if resolved:
logger.debug("identity: gateway platform=%s user_id=%s → peer=%s", platform, user_id, resolved) logger.debug(
"identity: gateway platform=%s user_id=%s → peer=%s",
platform, user_id, resolved,
)
_resolved_peer_var.set(resolved)
else: else:
logger.info("identity: unmapped gateway user platform=%s user_id=%s", platform, user_id) logger.info("identity: unmapped gateway user platform=%s user_id=%s", platform, user_id)
return None return None