新增了Telegram适配器的全套基础模块,包括: 1. 核心适配器入口与会话工具 2. 账号管理、认证与配置系统 3. 连接相关的轮询、Webhook、更新偏移管理 4. 话题路由、管理与缓存系统 5. 消息反抖动、超时配置与工具类 6. 响应式UI与命令交互系统 7. 反应表情与通知系统 8. 审批与安全审计模块 9. 健康检查与状态监控 10. 贴纸缓存与视觉工具 11. 流式响应与协作功能 12. 群组迁移与目标归一化处理
126 lines
4.1 KiB
Python
126 lines
4.1 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import time
|
|
from typing import Any
|
|
|
|
from yuxi.utils.logging_config import logger
|
|
|
|
|
|
class ThreadBinding:
|
|
def __init__(
|
|
self,
|
|
thread_key: str,
|
|
subagent_id: str | None = None,
|
|
acp_binding: str | None = None,
|
|
idle_timeout_ms: int = 86_400_000,
|
|
):
|
|
self.thread_key = thread_key
|
|
self.subagent_id = subagent_id
|
|
self.acp_binding = acp_binding
|
|
self.idle_timeout_ms = idle_timeout_ms
|
|
self.last_activity: float = time.monotonic()
|
|
self.created_at: float = time.monotonic()
|
|
|
|
def touch(self) -> None:
|
|
self.last_activity = time.monotonic()
|
|
|
|
def is_expired(self) -> bool:
|
|
elapsed = (time.monotonic() - self.last_activity) * 1000
|
|
return elapsed > self.idle_timeout_ms
|
|
|
|
|
|
class ThreadBindingManager:
|
|
def __init__(self, config: dict[str, Any] | None = None):
|
|
cfg = config or {}
|
|
bindings_cfg = cfg.get("thread_bindings", {})
|
|
self._enabled = bindings_cfg.get("enabled", False)
|
|
self._default_idle_timeout_ms = bindings_cfg.get("idle_timeout_ms", 86_400_000)
|
|
self._cleanup_interval = bindings_cfg.get("cleanup_interval", 300)
|
|
self._bindings: dict[str, ThreadBinding] = {}
|
|
self._cleanup_task: asyncio.Task | None = None
|
|
|
|
@property
|
|
def enabled(self) -> bool:
|
|
return self._enabled
|
|
|
|
def bind(
|
|
self,
|
|
thread_key: str,
|
|
subagent_id: str | None = None,
|
|
acp_binding: str | None = None,
|
|
idle_timeout_ms: int | None = None,
|
|
) -> ThreadBinding:
|
|
timeout = idle_timeout_ms or self._default_idle_timeout_ms
|
|
binding = ThreadBinding(
|
|
thread_key=thread_key,
|
|
subagent_id=subagent_id,
|
|
acp_binding=acp_binding,
|
|
idle_timeout_ms=timeout,
|
|
)
|
|
self._bindings[thread_key] = binding
|
|
logger.debug(f"[Telegram] Thread bound: {thread_key} -> subagent={subagent_id}")
|
|
return binding
|
|
|
|
def unbind(self, thread_key: str) -> bool:
|
|
if thread_key in self._bindings:
|
|
del self._bindings[thread_key]
|
|
logger.debug(f"[Telegram] Thread unbound: {thread_key}")
|
|
return True
|
|
return False
|
|
|
|
def get_binding(self, thread_key: str) -> ThreadBinding | None:
|
|
binding = self._bindings.get(thread_key)
|
|
if binding and binding.is_expired():
|
|
self.unbind(thread_key)
|
|
return None
|
|
return binding
|
|
|
|
def get_subagent_id(self, thread_key: str) -> str | None:
|
|
binding = self.get_binding(thread_key)
|
|
return binding.subagent_id if binding else None
|
|
|
|
def touch(self, thread_key: str) -> None:
|
|
binding = self._bindings.get(thread_key)
|
|
if binding:
|
|
binding.touch()
|
|
|
|
def cleanup_expired(self) -> int:
|
|
expired = [k for k, v in self._bindings.items() if v.is_expired()]
|
|
for k in expired:
|
|
self.unbind(k)
|
|
if expired:
|
|
logger.info(f"[Telegram] Cleaned up {len(expired)} expired thread bindings")
|
|
return len(expired)
|
|
|
|
async def start_cleanup_loop(self) -> None:
|
|
if not self._enabled:
|
|
return
|
|
|
|
async def _loop():
|
|
while True:
|
|
await asyncio.sleep(self._cleanup_interval)
|
|
self.cleanup_expired()
|
|
|
|
self._cleanup_task = asyncio.create_task(_loop())
|
|
|
|
async def stop_cleanup_loop(self) -> None:
|
|
if self._cleanup_task and not self._cleanup_task.done():
|
|
self._cleanup_task.cancel()
|
|
try:
|
|
await self._cleanup_task
|
|
except asyncio.CancelledError:
|
|
pass
|
|
self._cleanup_task = None
|
|
|
|
def get_all_bindings(self) -> dict[str, dict[str, Any]]:
|
|
return {
|
|
k: {
|
|
"thread_key": v.thread_key,
|
|
"subagent_id": v.subagent_id,
|
|
"acp_binding": v.acp_binding,
|
|
"idle_remaining_ms": max(0, int(v.idle_timeout_ms - (time.monotonic() - v.last_activity) * 1000)),
|
|
}
|
|
for k, v in self._bindings.items()
|
|
}
|