From 85e2fda6bc9632bebcfcdff909d7b6404ddc3f04 Mon Sep 17 00:00:00 2001 From: Mikhail Putilovskij Date: Tue, 28 Apr 2026 01:15:39 +0300 Subject: [PATCH] feat(05-02): ship room-local clear semantics - register clear as the room-context reset entrypoint when supported - keep save and context bound to room platform chat ids and clear old upstream state --- adapter/matrix/handlers/__init__.py | 13 +++--- adapter/matrix/handlers/context_commands.py | 48 ++++++++++++++------- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/adapter/matrix/handlers/__init__.py b/adapter/matrix/handlers/__init__.py index 7484a37..6d8c3f1 100644 --- a/adapter/matrix/handlers/__init__.py +++ b/adapter/matrix/handlers/__init__.py @@ -47,13 +47,12 @@ def register_matrix_handlers( dispatcher.register(IncomingCommand, "archive", make_handle_archive(client, store)) dispatcher.register(IncomingCommand, "help", handle_help) dispatcher.register(IncomingCommand, "settings", handle_settings) - dispatcher.register( - IncomingCommand, - "reset", - make_handle_reset(store, prototype_state) - if prototype_state is not None - else handle_settings, - ) + if prototype_state is not None: + clear_handler = make_handle_reset(store, prototype_state) + dispatcher.register(IncomingCommand, "clear", clear_handler) + dispatcher.register(IncomingCommand, "reset", clear_handler) + else: + dispatcher.register(IncomingCommand, "reset", handle_settings) dispatcher.register(IncomingCommand, "settings_skills", handle_settings_skills) dispatcher.register(IncomingCommand, "settings_connectors", handle_settings_connectors) dispatcher.register(IncomingCommand, "settings_soul", handle_settings_soul) diff --git a/adapter/matrix/handlers/context_commands.py b/adapter/matrix/handlers/context_commands.py index 648978d..121d76b 100644 --- a/adapter/matrix/handlers/context_commands.py +++ b/adapter/matrix/handlers/context_commands.py @@ -59,6 +59,17 @@ async def _resolve_context_scope( return room_id, platform_chat_id +async def _require_platform_context( + event: IncomingCommand, + store: StateStore, + chat_mgr, +) -> tuple[str, str]: + room_id, platform_chat_id = await _resolve_context_scope(event, store, chat_mgr) + if not platform_chat_id: + raise RuntimeError(f"matrix room context is incomplete: {room_id}") + return room_id, platform_chat_id + + def make_handle_save(agent_api, store: StateStore, prototype_state: PrototypeStateStore): async def handle_save( event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr @@ -85,11 +96,16 @@ def make_handle_save(agent_api, store: StateStore, prototype_state: PrototypeSta logger.warning("save_agent_call_failed", error=str(exc)) return [OutgoingMessage(chat_id=event.chat_id, text=f"Ошибка при сохранении: {exc}")] - _, platform_chat_id = await _resolve_context_scope(event, store, chat_mgr) + try: + _, platform_chat_id = await _require_platform_context(event, store, chat_mgr) + except RuntimeError as exc: + logger.warning("save_context_incomplete", error=str(exc)) + return [OutgoingMessage(chat_id=event.chat_id, text="Контекст комнаты не готов. Попробуй позже.")] + await prototype_state.add_saved_session( event.user_id, name, - source_context_id=platform_chat_id or event.chat_id, + source_context_id=platform_chat_id, ) return [ OutgoingMessage( @@ -132,9 +148,11 @@ def make_handle_reset(store: StateStore, prototype_state: PrototypeStateStore): async def handle_reset( event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr ) -> list[OutgoingEvent]: - room_id = await _resolve_room_id(event, chat_mgr) - room_meta = await get_room_meta(store, room_id) - old_chat_id = (room_meta or {}).get("platform_chat_id") or room_id + try: + room_id, old_chat_id = await _require_platform_context(event, store, chat_mgr) + except RuntimeError as exc: + logger.warning("clear_context_incomplete", error=str(exc)) + return [OutgoingMessage(chat_id=event.chat_id, text="Контекст комнаты не готов. Попробуй позже.")] new_chat_id = await next_platform_chat_id(store) await set_platform_chat_id(store, room_id, new_chat_id) @@ -143,6 +161,7 @@ def make_handle_reset(store: StateStore, prototype_state: PrototypeStateStore): if callable(disconnect): await disconnect(old_chat_id) + await prototype_state.clear_current_session(old_chat_id) await prototype_state.clear_current_session(new_chat_id) return [ @@ -182,20 +201,19 @@ def make_handle_context(store: StateStore, prototype_state: PrototypeStateStore) async def handle_context( event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr ) -> list[OutgoingEvent]: - _, platform_chat_id = await _resolve_context_scope(event, store, chat_mgr) - context_key = platform_chat_id or event.chat_id - current_session = await prototype_state.get_current_session(context_key) - tokens_used = await prototype_state.get_last_tokens_used(context_key) - if platform_chat_id is not None and event.chat_id != platform_chat_id: - if current_session is None: - current_session = await prototype_state.get_current_session(event.chat_id) - if tokens_used == 0: - tokens_used = await prototype_state.get_last_tokens_used(event.chat_id) + try: + _, platform_chat_id = await _require_platform_context(event, store, chat_mgr) + except RuntimeError as exc: + logger.warning("context_scope_incomplete", error=str(exc)) + return [OutgoingMessage(chat_id=event.chat_id, text="Контекст комнаты не готов. Попробуй позже.")] + + current_session = await prototype_state.get_current_session(platform_chat_id) + tokens_used = await prototype_state.get_last_tokens_used(platform_chat_id) sessions = await prototype_state.list_saved_sessions(event.user_id) lines = [ "Контекст:", - f" Контекст чата: {platform_chat_id or event.chat_id}", + f" Контекст чата: {platform_chat_id}", f" Сессия: {current_session or 'не загружена'}", f" Токены (последний ответ): {tokens_used}", f" Сохранения ({len(sessions)}):",