新增Slack适配器全套核心模块,包括消息处理流水线、会话管理、配置适配、权限控制等完整功能: 1. 新增语音、视觉相关的TTS和图像分析导出接口 2. 实现消息预处理、路由、线程上下文处理的完整流水线 3. 新增账号管理、缓存机制、房间上下文提取功能 4. 支持Webhook和Socket Mode两种事件接收方式 5. 实现权限白名单、审批配对、自动状态管理功能 6. 新增配置迁移、作用域校验、重连策略等辅助模块
152 lines
5.1 KiB
Python
152 lines
5.1 KiB
Python
from __future__ import annotations
|
|
|
|
import uuid
|
|
from collections.abc import Awaitable, Callable
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime, UTC, timedelta
|
|
from enum import StrEnum
|
|
from typing import Any
|
|
|
|
|
|
class ApprovalStatus(StrEnum):
|
|
PENDING = "pending"
|
|
APPROVED = "approved"
|
|
REJECTED = "rejected"
|
|
EXPIRED = "expired"
|
|
EXECUTED = "executed"
|
|
FAILED = "failed"
|
|
|
|
|
|
@dataclass
|
|
class ApprovalRequest:
|
|
approval_id: str
|
|
title: str
|
|
detail: str
|
|
command: str
|
|
chat_id: str
|
|
message_ts: str
|
|
created_by: str = ""
|
|
approved_by: str = ""
|
|
created_at: datetime = field(default_factory=lambda: datetime.now(UTC))
|
|
status: ApprovalStatus = ApprovalStatus.PENDING
|
|
exec_result: str = ""
|
|
exec_error: str = ""
|
|
|
|
@property
|
|
def is_pending(self) -> bool:
|
|
return self.status == ApprovalStatus.PENDING
|
|
|
|
@property
|
|
def is_resolved(self) -> bool:
|
|
return self.status in (ApprovalStatus.APPROVED, ApprovalStatus.REJECTED, ApprovalStatus.EXPIRED)
|
|
|
|
|
|
class ApprovalManager:
|
|
def __init__(self, ttl_seconds: float = 3600.0):
|
|
self._requests: dict[str, ApprovalRequest] = {}
|
|
self._ttl_seconds = ttl_seconds
|
|
self._exec_handlers: dict[str, Callable[..., Awaitable[dict[str, Any]]]] = {}
|
|
|
|
def register_exec_handler(self, command_prefix: str, handler: Callable[..., Awaitable[dict[str, Any]]]) -> None:
|
|
self._exec_handlers[command_prefix] = handler
|
|
|
|
def create_approval(
|
|
self, title: str, detail: str, command: str, chat_id: str, message_ts: str, created_by: str = ""
|
|
) -> ApprovalRequest:
|
|
approval_id = uuid.uuid4().hex[:12]
|
|
req = ApprovalRequest(
|
|
approval_id=approval_id,
|
|
title=title,
|
|
detail=detail,
|
|
command=command,
|
|
chat_id=chat_id,
|
|
message_ts=message_ts,
|
|
created_by=created_by,
|
|
)
|
|
self._requests[approval_id] = req
|
|
self._cleanup_expired()
|
|
return req
|
|
|
|
def get_approval(self, approval_id: str) -> ApprovalRequest | None:
|
|
return self._requests.get(approval_id)
|
|
|
|
def approve(self, approval_id: str, approved_by: str = "") -> ApprovalRequest | None:
|
|
req = self._requests.get(approval_id)
|
|
if not req or not req.is_pending:
|
|
return None
|
|
req.status = ApprovalStatus.APPROVED
|
|
req.approved_by = approved_by
|
|
return req
|
|
|
|
def reject(self, approval_id: str) -> ApprovalRequest | None:
|
|
req = self._requests.get(approval_id)
|
|
if not req or not req.is_pending:
|
|
return None
|
|
req.status = ApprovalStatus.REJECTED
|
|
return req
|
|
|
|
def mark_executed(self, approval_id: str, result: str = "") -> ApprovalRequest | None:
|
|
req = self._requests.get(approval_id)
|
|
if not req or req.status != ApprovalStatus.APPROVED:
|
|
return None
|
|
req.status = ApprovalStatus.EXECUTED
|
|
req.exec_result = result
|
|
return req
|
|
|
|
def mark_failed(self, approval_id: str, error: str = "") -> ApprovalRequest | None:
|
|
req = self._requests.get(approval_id)
|
|
if not req or req.status != ApprovalStatus.APPROVED:
|
|
return None
|
|
req.status = ApprovalStatus.FAILED
|
|
req.exec_error = error
|
|
return req
|
|
|
|
async def execute_approval(self, approval_id: str) -> dict[str, Any]:
|
|
req = self._requests.get(approval_id)
|
|
if not req or req.status != ApprovalStatus.APPROVED:
|
|
return {"success": False, "error": "Approval not in approved state"}
|
|
|
|
handler = None
|
|
for prefix, h in self._exec_handlers.items():
|
|
if req.command.startswith(prefix):
|
|
handler = h
|
|
break
|
|
|
|
if not handler:
|
|
self.mark_failed(approval_id, f"No exec handler for command: {req.command}")
|
|
return {"success": False, "error": f"No exec handler for command: {req.command}"}
|
|
|
|
try:
|
|
result = await handler(command=req.command, approval=req)
|
|
self.mark_executed(approval_id, str(result))
|
|
return {"success": True, "result": result}
|
|
except Exception as e:
|
|
self.mark_failed(approval_id, str(e))
|
|
return {"success": False, "error": str(e)}
|
|
|
|
def list_pending(self, chat_id: str | None = None) -> list[dict[str, Any]]:
|
|
pending = [r for r in self._requests.values() if r.is_pending]
|
|
if chat_id:
|
|
pending = [r for r in pending if r.chat_id == chat_id]
|
|
return [
|
|
{
|
|
"approval_id": r.approval_id,
|
|
"title": r.title,
|
|
"command": r.command,
|
|
"created_by": r.created_by,
|
|
"created_at": r.created_at.isoformat(),
|
|
"status": r.status.value,
|
|
}
|
|
for r in pending
|
|
]
|
|
|
|
def _cleanup_expired(self) -> None:
|
|
now = datetime.now(UTC)
|
|
expired = [
|
|
aid
|
|
for aid, req in self._requests.items()
|
|
if req.is_resolved and now - req.created_at > timedelta(seconds=self._ttl_seconds)
|
|
]
|
|
for aid in expired:
|
|
self._requests.pop(aid, None)
|