feat(04-02): add matrix context management commands
- add save/load/reset/context handlers and matrix interception flows - persist current session and last token usage in prototype state
This commit is contained in:
parent
da0b76882e
commit
b52fdc4670
7 changed files with 638 additions and 21 deletions
|
|
@ -19,9 +19,22 @@ from dotenv import load_dotenv
|
|||
|
||||
from adapter.matrix.converter import from_room_event
|
||||
from adapter.matrix.handlers import register_matrix_handlers
|
||||
from adapter.matrix.handlers.context_commands import (
|
||||
LOAD_PROMPT,
|
||||
SAVE_PROMPT,
|
||||
_call_reset_endpoint,
|
||||
_sanitize_session_name,
|
||||
)
|
||||
from adapter.matrix.handlers.auth import handle_invite
|
||||
from adapter.matrix.room_router import resolve_chat_id
|
||||
from adapter.matrix.store import get_room_meta, set_pending_confirm
|
||||
from adapter.matrix.store import (
|
||||
clear_load_pending,
|
||||
clear_reset_pending,
|
||||
get_load_pending,
|
||||
get_reset_pending,
|
||||
get_room_meta,
|
||||
set_pending_confirm,
|
||||
)
|
||||
from core.auth import AuthManager
|
||||
from core.chat import ChatManager
|
||||
from core.handler import EventDispatcher
|
||||
|
|
@ -35,8 +48,8 @@ from core.protocol import (
|
|||
)
|
||||
from core.settings import SettingsManager
|
||||
from core.store import InMemoryStore, SQLiteStore, StateStore
|
||||
from sdk.agent_session import AgentSessionClient, AgentSessionConfig
|
||||
from sdk.interface import PlatformClient
|
||||
from sdk.agent_api_wrapper import AgentApiWrapper
|
||||
from sdk.interface import PlatformClient, PlatformError
|
||||
from sdk.mock import MockPlatformClient
|
||||
from sdk.prototype_state import PrototypeStateStore
|
||||
from sdk.real import RealPlatformClient
|
||||
|
|
@ -60,11 +73,20 @@ def build_event_dispatcher(platform: PlatformClient, store: StateStore) -> Event
|
|||
chat_mgr = ChatManager(platform, store)
|
||||
auth_mgr = AuthManager(platform, store)
|
||||
settings_mgr = SettingsManager(platform, store)
|
||||
prototype_state = getattr(platform, "_prototype_state", None)
|
||||
agent_api = getattr(platform, "_agent_api", None)
|
||||
agent_base_url = os.environ.get("AGENT_BASE_URL", "http://127.0.0.1:8000")
|
||||
dispatcher = EventDispatcher(
|
||||
platform=platform, chat_mgr=chat_mgr, auth_mgr=auth_mgr, settings_mgr=settings_mgr
|
||||
)
|
||||
register_all(dispatcher)
|
||||
register_matrix_handlers(dispatcher, store=store)
|
||||
register_matrix_handlers(
|
||||
dispatcher,
|
||||
store=store,
|
||||
agent_api=agent_api,
|
||||
prototype_state=prototype_state,
|
||||
agent_base_url=agent_base_url,
|
||||
)
|
||||
return dispatcher
|
||||
|
||||
|
||||
|
|
@ -73,7 +95,7 @@ def _build_platform_from_env() -> PlatformClient:
|
|||
if backend == "real":
|
||||
ws_url = os.environ["AGENT_WS_URL"]
|
||||
return RealPlatformClient(
|
||||
agent_sessions=AgentSessionClient(AgentSessionConfig(base_ws_url=ws_url)),
|
||||
agent_api=AgentApiWrapper(agent_id="matrix-bot", url=ws_url),
|
||||
prototype_state=PrototypeStateStore(),
|
||||
platform="matrix",
|
||||
)
|
||||
|
|
@ -90,11 +112,21 @@ def build_runtime(
|
|||
chat_mgr = ChatManager(platform, store)
|
||||
auth_mgr = AuthManager(platform, store)
|
||||
settings_mgr = SettingsManager(platform, store)
|
||||
prototype_state = getattr(platform, "_prototype_state", None)
|
||||
agent_api = getattr(platform, "_agent_api", None)
|
||||
agent_base_url = os.environ.get("AGENT_BASE_URL", "http://127.0.0.1:8000")
|
||||
dispatcher = EventDispatcher(
|
||||
platform=platform, chat_mgr=chat_mgr, auth_mgr=auth_mgr, settings_mgr=settings_mgr
|
||||
)
|
||||
register_all(dispatcher)
|
||||
register_matrix_handlers(dispatcher, client=client, store=store)
|
||||
register_matrix_handlers(
|
||||
dispatcher,
|
||||
client=client,
|
||||
store=store,
|
||||
agent_api=agent_api,
|
||||
prototype_state=prototype_state,
|
||||
agent_base_url=agent_base_url,
|
||||
)
|
||||
return MatrixRuntime(
|
||||
platform=platform,
|
||||
store=store,
|
||||
|
|
@ -113,13 +145,118 @@ class MatrixBot:
|
|||
async def on_room_message(self, room: MatrixRoom, event: RoomMessageText) -> None:
|
||||
if getattr(event, "sender", None) == self.client.user_id:
|
||||
return
|
||||
chat_id = await resolve_chat_id(self.runtime.store, room.room_id, event.sender)
|
||||
sender = getattr(event, "sender", None)
|
||||
body = (getattr(event, "body", None) or "").strip()
|
||||
load_pending = await get_load_pending(self.runtime.store, sender, room.room_id)
|
||||
if load_pending is not None and (body.isdigit() or body == "!cancel"):
|
||||
outgoing = await self._handle_load_selection(sender, room.room_id, body, load_pending)
|
||||
await self._send_all(room.room_id, outgoing)
|
||||
return
|
||||
|
||||
reset_pending = await get_reset_pending(self.runtime.store, sender, room.room_id)
|
||||
if reset_pending is not None and (body in {"!yes", "!no"} or body.startswith("!save ")):
|
||||
outgoing = await self._handle_reset_selection(sender, room.room_id, body)
|
||||
await self._send_all(room.room_id, outgoing)
|
||||
return
|
||||
|
||||
chat_id = await resolve_chat_id(self.runtime.store, room.room_id, sender)
|
||||
incoming = from_room_event(event, room_id=room.room_id, chat_id=chat_id)
|
||||
if incoming is None:
|
||||
return
|
||||
outgoing = await self.runtime.dispatcher.dispatch(incoming)
|
||||
try:
|
||||
outgoing = await self.runtime.dispatcher.dispatch(incoming)
|
||||
except PlatformError as exc:
|
||||
logger.warning(
|
||||
"matrix_message_platform_error",
|
||||
room_id=room.room_id,
|
||||
sender=getattr(event, "sender", None),
|
||||
code=exc.code,
|
||||
error=str(exc),
|
||||
)
|
||||
outgoing = [
|
||||
OutgoingMessage(
|
||||
chat_id=chat_id,
|
||||
text="Сервис временно недоступен. Попробуйте ещё раз позже."
|
||||
)
|
||||
]
|
||||
await self._send_all(room.room_id, outgoing)
|
||||
|
||||
async def _handle_load_selection(
|
||||
self,
|
||||
user_id: str,
|
||||
room_id: str,
|
||||
text: str,
|
||||
pending: dict,
|
||||
) -> list[OutgoingEvent]:
|
||||
saves = pending.get("saves", [])
|
||||
if text in {"0", "!cancel"}:
|
||||
await clear_load_pending(self.runtime.store, user_id, room_id)
|
||||
return [OutgoingMessage(chat_id=room_id, text="Отменено.")]
|
||||
|
||||
index = int(text) - 1
|
||||
if index < 0 or index >= len(saves):
|
||||
return [
|
||||
OutgoingMessage(
|
||||
chat_id=room_id,
|
||||
text=f"Неверный номер. Введи от 1 до {len(saves)} или 0 для отмены.",
|
||||
)
|
||||
]
|
||||
|
||||
name = saves[index]["name"]
|
||||
await clear_load_pending(self.runtime.store, user_id, room_id)
|
||||
prototype_state = getattr(self.runtime.platform, "_prototype_state", None)
|
||||
if prototype_state is not None:
|
||||
await prototype_state.set_current_session(user_id, name)
|
||||
|
||||
try:
|
||||
await self.runtime.platform.send_message(
|
||||
user_id,
|
||||
room_id,
|
||||
LOAD_PROMPT.format(name=name),
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning("load_agent_call_failed", error=str(exc))
|
||||
return [OutgoingMessage(chat_id=room_id, text=f"Ошибка при загрузке: {exc}")]
|
||||
return [OutgoingMessage(chat_id=room_id, text=f"Загрузка: {name}")]
|
||||
|
||||
async def _handle_reset_selection(
|
||||
self,
|
||||
user_id: str,
|
||||
room_id: str,
|
||||
text: str,
|
||||
) -> list[OutgoingEvent]:
|
||||
agent_base_url = os.environ.get("AGENT_BASE_URL", "http://127.0.0.1:8000")
|
||||
prototype_state = getattr(self.runtime.platform, "_prototype_state", None)
|
||||
await clear_reset_pending(self.runtime.store, user_id, room_id)
|
||||
|
||||
if text == "!no":
|
||||
return [OutgoingMessage(chat_id=room_id, text="Отменено.")]
|
||||
|
||||
if text.startswith("!save "):
|
||||
name = _sanitize_session_name(text[len("!save ") :].strip())
|
||||
if name is None:
|
||||
return [
|
||||
OutgoingMessage(
|
||||
chat_id=room_id,
|
||||
text="Имя сохранения может содержать только буквы, цифры, _ и -.",
|
||||
)
|
||||
]
|
||||
try:
|
||||
await self.runtime.platform.send_message(
|
||||
user_id,
|
||||
room_id,
|
||||
SAVE_PROMPT.format(name=name),
|
||||
)
|
||||
if prototype_state is not None:
|
||||
await prototype_state.add_saved_session(user_id, name)
|
||||
except Exception as exc:
|
||||
logger.warning("save_before_reset_failed", error=str(exc))
|
||||
return [OutgoingMessage(chat_id=room_id, text=f"Ошибка при сохранении: {exc}")]
|
||||
|
||||
if prototype_state is not None:
|
||||
await prototype_state.clear_current_session(user_id)
|
||||
return await _call_reset_endpoint(agent_base_url, room_id)
|
||||
|
||||
async def on_member(self, room: MatrixRoom, event: RoomMemberEvent) -> None:
|
||||
if getattr(event, "sender", None) == self.client.user_id:
|
||||
return
|
||||
|
|
@ -236,8 +373,12 @@ async def main() -> None:
|
|||
request_timeout=client_config.request_timeout,
|
||||
)
|
||||
try:
|
||||
if isinstance(runtime.platform, RealPlatformClient):
|
||||
await runtime.platform.agent_api.connect()
|
||||
await client.sync_forever(timeout=30000, since=since_token)
|
||||
finally:
|
||||
if isinstance(runtime.platform, RealPlatformClient):
|
||||
await runtime.platform.agent_api.close()
|
||||
await client.close()
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue