ForcePilot/backend/package/yuxi/channels/adapters/matrix/thread_bindings.py
Kris 2063145ce1 feat(matrix): 新增完整的Matrix适配器工具集
新增了20+个Matrix相关工具模块,包含线程处理、自动加入、消息去重、配置管理、安全策略、媒体处理、命令解析等功能,完善Matrix适配器基础能力
2026-05-12 00:45:54 +08:00

100 lines
3.4 KiB
Python

from __future__ import annotations
import time
from dataclasses import dataclass
from typing import Any
from yuxi.utils.logging_config import logger
@dataclass
class ThreadBinding:
room_id: str
root_event_id: str
session_key: str = ""
bound_at: float = 0.0
last_activity: float = 0.0
is_subagent: bool = False
is_acp: bool = False
def __post_init__(self):
now = time.monotonic()
if not self.bound_at:
self.bound_at = now
if not self.last_activity:
self.last_activity = now
@property
def idle_seconds(self) -> float:
return time.monotonic() - self.last_activity
@property
def age_hours(self) -> float:
return (time.monotonic() - self.bound_at) / 3600
def touch(self) -> None:
self.last_activity = time.monotonic()
class ThreadBindingManager:
def __init__(self, config: dict[str, Any]):
self._config = config
self._bindings: dict[str, ThreadBinding] = {}
thread_cfg = config.get("threadBindings", {})
self._enabled = thread_cfg.get("enabled", True)
self._idle_hours = thread_cfg.get("idleHours", 0)
self._max_age_hours = thread_cfg.get("maxAgeHours", 0)
self._spawn_subagent = thread_cfg.get("spawnSubagentSessions", False)
self._spawn_acp = thread_cfg.get("spawnAcpSessions", False)
def bind(self, room_id: str, root_event_id: str, session_key: str = "") -> ThreadBinding:
binding_key = f"{room_id}:{root_event_id}"
if binding_key in self._bindings:
binding = self._bindings[binding_key]
binding.touch()
return binding
binding = ThreadBinding(
room_id=room_id,
root_event_id=root_event_id,
session_key=session_key,
is_subagent=self._spawn_subagent,
is_acp=self._spawn_acp,
)
self._bindings[binding_key] = binding
logger.debug(f"Matrix thread bound: {binding_key} -> {session_key}")
return binding
def unbind(self, room_id: str, root_event_id: str) -> None:
binding_key = f"{room_id}:{root_event_id}"
if binding_key in self._bindings:
del self._bindings[binding_key]
logger.debug(f"Matrix thread unbound: {binding_key}")
def get(self, room_id: str, root_event_id: str) -> ThreadBinding | None:
binding_key = f"{room_id}:{root_event_id}"
return self._bindings.get(binding_key)
def check_timeouts(self) -> list[ThreadBinding]:
expired: list[ThreadBinding] = []
for key, binding in list(self._bindings.items()):
if self._idle_hours > 0 and binding.idle_seconds > self._idle_hours * 3600:
expired.append(binding)
del self._bindings[key]
logger.debug(f"Matrix thread binding expired (idle): {key}")
elif self._max_age_hours > 0 and binding.age_hours > self._max_age_hours:
expired.append(binding)
del self._bindings[key]
logger.debug(f"Matrix thread binding expired (max age): {key}")
return expired
@property
def binding_count(self) -> int:
return len(self._bindings)
def list_bindings(self) -> list[ThreadBinding]:
return list(self._bindings.values())
def get_room_bindings(self, room_id: str) -> list[ThreadBinding]:
return [b for b in self._bindings.values() if b.room_id == room_id]