新增大量渠道适配器相关的协议、策略、工具类与基础设施代码,包括: 1. 多协议定义:认证、消息、配置、网关等核心接口 2. 策略模块:上下文、群聊、去重、防抖等业务策略 3. 工具集:重试、去重、文本分块、消息格式化等SDK工具 4. 基础设施:外部进程管理、事件广播、熔断机制等 5. 账户与管道系统:账户管理、消息处理管道实现 6. 运行时服务:状态收集、维护任务、日志等后台服务
166 lines
4.8 KiB
Python
166 lines
4.8 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import time
|
|
import uuid as uuid_lib
|
|
from dataclasses import dataclass, field
|
|
from enum import StrEnum
|
|
from typing import Any
|
|
|
|
|
|
class ApprovalAction(StrEnum):
|
|
"""审批动作"""
|
|
|
|
APPROVE = "approve"
|
|
REJECT = "reject"
|
|
ALLOW_ONCE = "allow-once"
|
|
ALLOW_ALWAYS = "allow-always"
|
|
DENY = "deny"
|
|
|
|
|
|
class ApprovalState(StrEnum):
|
|
"""审批状态"""
|
|
|
|
PENDING = "pending"
|
|
APPROVED = "approved"
|
|
DENIED = "denied"
|
|
TIMED_OUT = "timed_out"
|
|
CANCELLED = "cancelled"
|
|
|
|
|
|
@dataclass
|
|
class ApprovalRequest:
|
|
"""审批请求"""
|
|
|
|
approval_id: str
|
|
agent_id: str
|
|
command: str
|
|
requester_id: str
|
|
approver_ids: list[str]
|
|
created_at: float
|
|
timeout_at: float
|
|
state: ApprovalState = ApprovalState.PENDING
|
|
metadata: dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
@dataclass
|
|
class ApprovalConfig:
|
|
"""审批配置(渠道通用字段)"""
|
|
|
|
enabled: bool | str = False
|
|
approvers: list[str] = field(default_factory=list)
|
|
agent_filter: list[str] = field(default_factory=list)
|
|
session_filter: list[str] = field(default_factory=list)
|
|
timeout_s: float = 300.0
|
|
target: str = "dm"
|
|
auto_approve_owner: bool = True
|
|
|
|
|
|
class BaseApprovalCapability:
|
|
"""审批能力基类
|
|
|
|
封装:
|
|
- 审批请求创建/撤销
|
|
- 审批结果解析(多 action 映射)
|
|
- 超时处理
|
|
- Agent 回复抑制(审批未完成时不回复结果)
|
|
- 可选:自动批准拥有者
|
|
|
|
各适配器子类可扩展:
|
|
- 审批通知发送(向审批者发送审批请求消息)
|
|
- 审批按钮数据解析(各渠道按钮格式不同)
|
|
"""
|
|
|
|
def __init__(self, config: ApprovalConfig):
|
|
self._config = config
|
|
self._pending: dict[str, ApprovalRequest] = {}
|
|
self._lock = asyncio.Lock()
|
|
|
|
def is_enabled(self) -> bool:
|
|
return self._config.enabled is True or self._config.enabled == "auto"
|
|
|
|
def requires_approval(self, agent_id: str, session_id: str = "") -> bool:
|
|
if not self.is_enabled():
|
|
return False
|
|
if self._config.agent_filter and agent_id not in self._config.agent_filter:
|
|
return False
|
|
if self._config.session_filter and session_id not in self._config.session_filter:
|
|
return False
|
|
return True
|
|
|
|
async def create_approval(
|
|
self,
|
|
agent_id: str,
|
|
command: str,
|
|
requester_id: str,
|
|
) -> str:
|
|
approval_id = str(uuid_lib.uuid4())[:8]
|
|
now = time.time()
|
|
|
|
async with self._lock:
|
|
self._pending[approval_id] = ApprovalRequest(
|
|
approval_id=approval_id,
|
|
agent_id=agent_id,
|
|
command=command,
|
|
requester_id=requester_id,
|
|
approver_ids=list(self._config.approvers),
|
|
created_at=now,
|
|
timeout_at=now + self._config.timeout_s,
|
|
)
|
|
|
|
return approval_id
|
|
|
|
async def resolve(
|
|
self,
|
|
approval_id: str,
|
|
action: ApprovalAction,
|
|
approver_id: str = "",
|
|
) -> bool:
|
|
async with self._lock:
|
|
req = self._pending.get(approval_id)
|
|
if req is None or req.state != ApprovalState.PENDING:
|
|
return False
|
|
|
|
if approver_id and req.approver_ids and approver_id not in req.approver_ids:
|
|
return False
|
|
|
|
if action in (
|
|
ApprovalAction.APPROVE,
|
|
ApprovalAction.ALLOW_ONCE,
|
|
ApprovalAction.ALLOW_ALWAYS,
|
|
):
|
|
req.state = ApprovalState.APPROVED
|
|
else:
|
|
req.state = ApprovalState.DENIED
|
|
return True
|
|
|
|
async def cancel(self, approval_id: str) -> bool:
|
|
async with self._lock:
|
|
removed = self._pending.pop(approval_id, None)
|
|
return removed is not None
|
|
|
|
def get_state(self, approval_id: str) -> ApprovalState | None:
|
|
req = self._pending.get(approval_id)
|
|
return req.state if req else None
|
|
|
|
def should_suppress_reply(self, approval_id: str) -> bool:
|
|
req = self._pending.get(approval_id)
|
|
if req is None:
|
|
return False
|
|
return req.state == ApprovalState.PENDING
|
|
|
|
def check_timeouts(self) -> list[str]:
|
|
now = time.time()
|
|
expired: list[str] = []
|
|
for approval_id, req in self._pending.items():
|
|
if req.state == ApprovalState.PENDING and now >= req.timeout_at:
|
|
req.state = ApprovalState.TIMED_OUT
|
|
expired.append(approval_id)
|
|
return expired
|
|
|
|
def cleanup_completed(self) -> int:
|
|
to_remove = [aid for aid, req in self._pending.items() if req.state != ApprovalState.PENDING]
|
|
for aid in to_remove:
|
|
del self._pending[aid]
|
|
return len(to_remove)
|