develop #2

Merged
roberto merged 160 commits from develop into main 2026-06-12 15:27:23 +00:00
2 changed files with 44 additions and 6 deletions
Showing only changes of commit 2b71469e86 - Show all commits

View File

@@ -374,14 +374,15 @@ async def _handle_floating_request(
# ── v8 Contextual Sidebar Handlers ─────────────────────────────────── # ── v8 Contextual Sidebar Handlers ───────────────────────────────────
def get_session_buffer(session_id: str, channel: str = "contextual"): def get_session_buffer(user_id: str, session_id: str, channel: str = "contextual"):
"""Return the session buffer for the given session. """Return a session-scoped buffer proxy for the given user+session.
The channel kwarg is accepted for forward-compatibility but not used for Returns a _ContextualBufferProxy that exposes append_system_message().
namespacing yet (session ids are UUIDs so collisions are negligible).
Defined at module level so tests can monkeypatch it. Defined at module level so tests can monkeypatch it.
The channel kwarg is accepted for forward-compatibility.
""" """
return session_buffer from app.core.agent_session_buffer import ContextualBufferProxy # noqa: PLC0415
return ContextualBufferProxy(session_buffer, user_id, session_id)
async def _handle_contextual_request( async def _handle_contextual_request(
@@ -472,7 +473,7 @@ async def _handle_contextual_scope_update(
session_id: str = frame.get("session_id") or str(uuid4()) session_id: str = frame.get("session_id") or str(uuid4())
scope = ContextualScope.model_validate(frame.get("scope", {})) scope = ContextualScope.model_validate(frame.get("scope", {}))
block = render_scope_block(scope) block = render_scope_block(scope)
buf = get_session_buffer(session_id, channel="contextual") buf = get_session_buffer(user_id, session_id, channel="contextual")
buf.append_system_message( buf.append_system_message(
f"User navigated to a new view. {block} Treat this as the new active context." f"User navigated to a new view. {block} Treat this as the new active context."
) )

View File

@@ -54,6 +54,43 @@ class _SessionBuffer:
with self._lock: with self._lock:
self._store.pop((user_id, session_id), None) self._store.pop((user_id, session_id), None)
def append_system_message(self, user_id: str, session_id: str, text: str) -> None:
"""Append a synthetic system message to the buffer for the given session.
Creates the session slot if it does not yet exist. Used by the
contextual_scope_update handler to inject navigation events without
making an LLM call.
"""
from langchain_core.messages import SystemMessage # noqa: PLC0415
key = (user_id, session_id)
with self._lock:
entry = self._store.get(key)
if entry is None:
msgs: list[BaseMessage] = [SystemMessage(content=text)]
else:
_, existing = entry
msgs = list(existing) + [SystemMessage(content=text)]
capped = msgs[-MAX_MESSAGES_PER_SESSION:]
self._store[key] = (time.monotonic(), capped)
class ContextualBufferProxy:
"""Thin wrapper around _SessionBuffer that closes over user_id + session_id.
Returned by get_session_buffer() so callers can call
``proxy.append_system_message(text)`` without threading user_id/session_id
through every call site.
"""
def __init__(self, buf: "_SessionBuffer", user_id: str, session_id: str) -> None:
self._buf = buf
self._user_id = user_id
self._session_id = session_id
def append_system_message(self, text: str) -> None:
self._buf.append_system_message(self._user_id, self._session_id, text)
# Module-level singleton — same pattern as _pending_states in api/app/api/routes/auth.py # Module-level singleton — same pattern as _pending_states in api/app/api/routes/auth.py
session_buffer = _SessionBuffer() session_buffer = _SessionBuffer()