diff --git a/adapter/matrix/handlers/__init__.py b/adapter/matrix/handlers/__init__.py index d3635bf..32a2c87 100644 --- a/adapter/matrix/handlers/__init__.py +++ b/adapter/matrix/handlers/__init__.py @@ -10,6 +10,7 @@ from adapter.matrix.handlers.confirm import make_handle_cancel, make_handle_conf from adapter.matrix.handlers.context_commands import ( make_handle_context, make_handle_load, + make_handle_reset, make_handle_save, ) from adapter.matrix.handlers.settings import ( @@ -43,7 +44,7 @@ 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", handle_settings) + dispatcher.register(IncomingCommand, "reset", make_handle_reset(store, prototype_state) if prototype_state is not None else 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 ff52223..2f02112 100644 --- a/adapter/matrix/handlers/context_commands.py +++ b/adapter/matrix/handlers/context_commands.py @@ -7,7 +7,7 @@ from typing import TYPE_CHECKING import httpx import structlog -from adapter.matrix.store import get_room_meta, set_load_pending, set_reset_pending +from adapter.matrix.store import get_room_meta, set_load_pending, set_platform_chat_id from core.protocol import IncomingCommand, OutgoingEvent, OutgoingMessage if TYPE_CHECKING: @@ -123,23 +123,26 @@ def make_handle_load(store: "StateStore", prototype_state: "PrototypeStateStore" return handle_load -def make_handle_reset(store: "StateStore", agent_base_url: str): +def make_handle_reset(store: "StateStore", prototype_state: "PrototypeStateStore"): async def handle_reset( event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr ) -> list[OutgoingEvent]: + import time + room_id = await _resolve_room_id(event, chat_mgr) - await set_reset_pending(store, event.user_id, room_id, {"active": True}) - return [ - OutgoingMessage( - chat_id=event.chat_id, - text=( - "Сбросить контекст агента? Выбери:\n" - " !yes - сбросить\n" - " !save [имя] - сохранить и сбросить\n" - " !no - отмена" - ), - ) - ] + room_meta = await get_room_meta(store, room_id) + old_chat_id = (room_meta or {}).get("platform_chat_id") or room_id + + new_chat_id = f"matrix:{room_id}#{int(time.time())}" + await set_platform_chat_id(store, room_id, new_chat_id) + + disconnect = getattr(platform, "disconnect_chat", None) + if callable(disconnect): + await disconnect(old_chat_id) + + await prototype_state.clear_current_session(new_chat_id) + + return [OutgoingMessage(chat_id=event.chat_id, text="Контекст сброшен. Агент не помнит предыдущий разговор.")] return handle_reset diff --git a/sdk/real.py b/sdk/real.py index 8641b69..f6e40ed 100644 --- a/sdk/real.py +++ b/sdk/real.py @@ -120,6 +120,15 @@ class RealPlatformClient(PlatformClient): async def update_settings(self, user_id: str, action) -> None: await self._prototype_state.update_settings(user_id, action) + async def disconnect_chat(self, chat_id: str) -> None: + chat_key = str(chat_id) + chat_api = self._chat_apis.pop(chat_key, None) + self._chat_send_locks.pop(chat_key, None) + if chat_api is not None: + close = getattr(chat_api, "close", None) + if callable(close): + await close() + async def close(self) -> None: for chat_api in list(self._chat_apis.values()): close = getattr(chat_api, "close", None) diff --git a/tests/adapter/matrix/test_context_commands.py b/tests/adapter/matrix/test_context_commands.py index 517f605..652d96c 100644 --- a/tests/adapter/matrix/test_context_commands.py +++ b/tests/adapter/matrix/test_context_commands.py @@ -3,7 +3,7 @@ from __future__ import annotations from types import SimpleNamespace from unittest.mock import AsyncMock, patch -import httpx + import pytest from adapter.matrix.bot import MatrixBot, build_runtime @@ -15,7 +15,7 @@ from adapter.matrix.handlers.context_commands import ( ) from adapter.matrix.store import ( get_load_pending, - get_reset_pending, + set_load_pending, set_room_meta, ) @@ -141,40 +141,26 @@ async def test_load_command_without_saved_sessions_reports_empty(): @pytest.mark.asyncio -async def test_reset_command_shows_dialog_and_sets_pending(): +async def test_reset_command_assigns_new_platform_chat_id(): + from adapter.matrix.store import get_platform_chat_id, set_room_meta + from sdk.prototype_state import PrototypeStateStore + + prototype_state = PrototypeStateStore() platform = MatrixCommandPlatform() runtime = build_runtime(platform=platform) - await runtime.chat_mgr.get_or_create( - user_id="u1", - chat_id="C1", - platform="matrix", - surface_ref="!room:example.org", - name="Chat 1", - ) - handler = make_handle_reset(store=runtime.store, agent_base_url="http://127.0.0.1:8000") - event = IncomingCommand(user_id="u1", platform="matrix", chat_id="C1", command="reset", args=[]) + store = runtime.store + + await set_room_meta(store, "!room:example.org", {"platform_chat_id": "matrix:!room:example.org"}) + + handler = make_handle_reset(store=store, prototype_state=prototype_state) + event = IncomingCommand(user_id="u1", platform="matrix", chat_id="!room:example.org", command="reset", args=[]) result = await handler(event, runtime.auth_mgr, platform, runtime.chat_mgr, runtime.settings_mgr) - assert "!yes" in result[0].text - assert "!save" in result[0].text - assert "!no" in result[0].text - assert await get_reset_pending(runtime.store, "u1", "!room:example.org") == {"active": True} - - -@pytest.mark.asyncio -async def test_reset_endpoint_unavailable_reports_error(): - with patch("adapter.matrix.handlers.context_commands.httpx.AsyncClient") as client_cls: - client = client_cls.return_value - client.__aenter__ = AsyncMock(return_value=client) - client.__aexit__ = AsyncMock(return_value=False) - client.post = AsyncMock(side_effect=httpx.ConnectError("refused")) - - from adapter.matrix.handlers.context_commands import _call_reset_endpoint - - result = await _call_reset_endpoint("http://127.0.0.1:8000", "!room:example.org") - - assert "недоступен" in result[0].text.lower() + new_id = await get_platform_chat_id(store, "!room:example.org") + assert new_id != "matrix:!room:example.org" + assert new_id.startswith("matrix:!room:example.org#") + assert "сброшен" in result[0].text.lower() @pytest.mark.asyncio