新增元宝(Yuanbao)渠道的完整适配器实现,包含以下核心模块: - 基础适配器与导出入口 - 协议编解码与WebSocket帧处理 - 会话管理与路由逻辑 - 事件队列与出站消息队列 - 消息格式转换与发送重试 - 安全审计与权限校验 - 配置映射与账户管理 - 视觉分析与工具函数 - 文档生成与设置向导
119 lines
3.7 KiB
Python
119 lines
3.7 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import time
|
|
from dataclasses import dataclass, field
|
|
from enum import StrEnum
|
|
from typing import Any
|
|
|
|
from yuxi.utils.logging_config import logger
|
|
|
|
|
|
class ApprovalStatus(StrEnum):
|
|
PENDING = "pending"
|
|
APPROVED = "approved"
|
|
REJECTED = "rejected"
|
|
EXPIRED = "expired"
|
|
|
|
|
|
@dataclass
|
|
class ApprovalRequest:
|
|
request_id: str
|
|
action: str
|
|
description: str
|
|
requester: str
|
|
chat_id: str = ""
|
|
status: ApprovalStatus = ApprovalStatus.PENDING
|
|
created_at: float = field(default_factory=time.time)
|
|
expires_at: float = field(default=0)
|
|
metadata: dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
class ExecApprovalManager:
|
|
DEFAULT_TIMEOUT = 300
|
|
|
|
def __init__(self):
|
|
self._pending: dict[str, ApprovalRequest] = {}
|
|
self._history: list[ApprovalRequest] = []
|
|
self._lock = asyncio.Lock()
|
|
|
|
async def request_approval(
|
|
self,
|
|
action: str,
|
|
description: str,
|
|
requester: str,
|
|
chat_id: str = "",
|
|
timeout_seconds: int = DEFAULT_TIMEOUT,
|
|
metadata: dict[str, Any] | None = None,
|
|
) -> ApprovalRequest:
|
|
request_id = f"exec_{int(time.time() * 1000)}_{action}"
|
|
now = time.time()
|
|
request = ApprovalRequest(
|
|
request_id=request_id,
|
|
action=action,
|
|
description=description,
|
|
requester=requester,
|
|
chat_id=chat_id,
|
|
created_at=now,
|
|
expires_at=now + timeout_seconds,
|
|
metadata=metadata or {},
|
|
)
|
|
|
|
async with self._lock:
|
|
self._pending[request_id] = request
|
|
|
|
logger.info(
|
|
f"[Yuanbao] Exec approval requested: {request_id} action={action} by {requester} timeout={timeout_seconds}s"
|
|
)
|
|
return request
|
|
|
|
async def approve(self, request_id: str, approver: str = "") -> bool:
|
|
async with self._lock:
|
|
request = self._pending.pop(request_id, None)
|
|
if request is None:
|
|
return False
|
|
if time.time() > request.expires_at:
|
|
request.status = ApprovalStatus.EXPIRED
|
|
self._history.append(request)
|
|
return False
|
|
|
|
request.status = ApprovalStatus.APPROVED
|
|
self._history.append(request)
|
|
logger.info(f"[Yuanbao] Exec approved: {request_id} by {approver or 'system'}")
|
|
return True
|
|
|
|
async def reject(self, request_id: str, reason: str = "") -> bool:
|
|
async with self._lock:
|
|
request = self._pending.pop(request_id, None)
|
|
if request is None:
|
|
return False
|
|
request.status = ApprovalStatus.REJECTED
|
|
request.metadata["reject_reason"] = reason
|
|
self._history.append(request)
|
|
logger.info(f"[Yuanbao] Exec rejected: {request_id} reason={reason}")
|
|
return True
|
|
|
|
async def get_pending(self, requester: str | None = None) -> list[ApprovalRequest]:
|
|
async with self._lock:
|
|
pending = list(self._pending.values())
|
|
if requester:
|
|
pending = [r for r in pending if r.requester == requester]
|
|
return pending
|
|
|
|
async def cleanup_expired(self) -> int:
|
|
async with self._lock:
|
|
now = time.time()
|
|
to_remove = []
|
|
for rid, req in self._pending.items():
|
|
if now > req.expires_at:
|
|
req.status = ApprovalStatus.EXPIRED
|
|
self._history.append(req)
|
|
to_remove.append(rid)
|
|
for rid in to_remove:
|
|
self._pending.pop(rid, None)
|
|
return len(to_remove)
|
|
|
|
@property
|
|
def pending_count(self) -> int:
|
|
return len(self._pending)
|