ForcePilot/backend/package/yuxi/channel/extensions/slack/status.py
Kris bfc7755137 feat(channel): 添加 Slack 渠道扩展
新增 Slack 渠道扩展,支持在 Yuxi 平台中集成 Slack 团队协作平台。

包含以下功能模块:
- config: 渠道配置管理
- gateway: SSE/WebSocket 网关接入
- outbound: 外发消息管理
- streaming: 流式消息处理
- pairing: 用户配对与绑定
- security: 安全校验
- monitor: 渠道状态监控
- status: 会话状态管理
- actions: 交互动作处理
- interactive: 交互式消息
- commands: 斜杠指令
- threading: 线程管理
- mentions: @提及
- constants: 常量定义
- types: 类型定义
2026-05-21 11:43:23 +08:00

226 lines
8.0 KiB
Python

import time
import logging
from yuxi.channel.extensions.slack.types import SlackProbeResult
from yuxi.channel.extensions.slack.config import SlackConfigAdapter
from yuxi.channel.protocols import ChannelAccountSnapshot, ChannelStatusIssue
logger = logging.getLogger(__name__)
class SlackStatus:
default_runtime: ChannelAccountSnapshot | None = None
async def probe(self, account: dict | None = None) -> bool:
if not account:
return False
bot_token = account.get("bot_token", "")
if not bot_token:
return False
probe_result = await self._probe_slack(bot_token)
return probe_result.ok
async def _probe_slack(self, token: str, timeout_ms: int = 5000) -> SlackProbeResult:
try:
from slack_sdk.web.async_client import AsyncWebClient
except ImportError:
return SlackProbeResult(ok=False, error="slack_sdk not installed")
client = AsyncWebClient(token=token)
start = time.monotonic()
try:
resp = await client.auth_test()
latency_ms = (time.monotonic() - start) * 1000
return SlackProbeResult(
ok=resp.get("ok", False),
user=resp.get("user", ""),
user_id=resp.get("user_id", ""),
bot_id=resp.get("bot_id", ""),
team=resp.get("team", ""),
team_id=resp.get("team_id", ""),
url=resp.get("url", ""),
api_app_id=resp.get("api_app_id", ""),
latency_ms=latency_ms,
)
except Exception as e:
latency_ms = (time.monotonic() - start) * 1000
return SlackProbeResult(ok=False, error=str(e), latency_ms=latency_ms)
def build_summary(self, snapshot: object) -> dict:
if not isinstance(snapshot, ChannelAccountSnapshot):
return {}
from yuxi.channel.protocols import build_standard_summary
base = build_standard_summary(snapshot, "slack")
base["status"] = getattr(snapshot, "status_state", "unknown")
return base
def build_channel_summary(
self,
account: dict,
config: dict,
default_account_id: str,
snapshot: ChannelAccountSnapshot,
) -> dict:
account_id = account.get("account_id", "")
return {
"channel": "slack",
"account_id": account_id,
"name": account.get("name", account_id),
"is_default": account_id == default_account_id,
"configured": snapshot.configured,
"enabled": snapshot.enabled,
"connected": snapshot.connected,
"running": snapshot.running,
"status": snapshot.status_state,
"health_state": snapshot.health_state,
"mode": snapshot.mode,
"dm_policy": snapshot.dm_policy,
"last_error": snapshot.last_error,
"last_connected_at": snapshot.last_connected_at,
"last_message_at": snapshot.last_message_at,
"last_event_at": snapshot.last_event_at,
"probe": snapshot.probe,
"audit": snapshot.audit,
}
def build_account_snapshot(
self,
account: dict,
config: dict,
runtime: ChannelAccountSnapshot | None = None,
probe: object | None = None,
audit: object | None = None,
) -> ChannelAccountSnapshot:
adapter = SlackConfigAdapter()
account_id = account.get("account_id", "")
configured = adapter.is_configured(account)
enabled = adapter.is_enabled(account, config)
status_state = self.resolve_account_state(account, config, configured, enabled)
return ChannelAccountSnapshot(
account_id=account_id,
name=account.get("name", account_id),
enabled=enabled,
configured=configured,
status_state=status_state,
mode=account.get("mode", "socket"),
dm_policy=account.get("dm_policy", "pairing"),
allow_from=account.get("allow_from", []),
probe=probe,
audit=audit,
)
def format_capabilities_probe(self, probe: object) -> list[dict]:
if probe is None:
return []
return [
{
"label": "Slack 连接",
"ok": getattr(probe, "ok", False),
"detail": f"{getattr(probe, 'team', '')} / {getattr(probe, 'user', '')}",
"latency_ms": getattr(probe, "latency_ms", None),
}
]
async def audit_account(
self,
account: dict,
timeout_ms: int,
config: dict,
probe: object | None = None,
) -> object:
bot_token = account.get("bot_token", "")
if not bot_token:
return {"ok": False, "error": "No bot token configured"}
probe_result = await self._probe_slack(bot_token, timeout_ms=timeout_ms)
return {
"ok": probe_result.ok,
"user": probe_result.user,
"user_id": probe_result.user_id,
"bot_id": probe_result.bot_id,
"team": probe_result.team,
"team_id": probe_result.team_id,
"url": probe_result.url,
"api_app_id": probe_result.api_app_id,
"latency_ms": probe_result.latency_ms,
"error": probe_result.error,
}
async def build_capabilities_diagnostics(
self,
account: dict,
timeout_ms: int,
config: dict,
probe: object | None = None,
audit: object | None = None,
target: str | None = None,
) -> dict | None:
return {
"channel": "slack",
"probe": self.format_capabilities_probe(probe),
"audit": audit,
}
def log_self_id(
self,
account: dict,
config: dict,
runtime: object,
include_channel_prefix: bool = False,
) -> None:
prefix = "Slack " if include_channel_prefix else ""
account_id = account.get("account_id", "default")
name = account.get("name", account_id)
logger.info(f"{prefix}Bot account loaded: {name} (id={account_id})")
def resolve_account_state(
self,
account: dict,
config: dict,
configured: bool,
enabled: bool,
) -> str:
if not configured:
return "unconfigured"
if not enabled:
return "disabled"
return "enabled"
def collect_status_issues(self, accounts: list[ChannelAccountSnapshot]) -> list[ChannelStatusIssue]:
issues: list[ChannelStatusIssue] = []
for snapshot in accounts:
if snapshot.connected is False:
issues.append(
ChannelStatusIssue(
channel="slack",
account_id=snapshot.account_id,
kind="disconnected",
message=f"Slack 账户 {snapshot.name or snapshot.account_id} 连接断开",
fix="检查 Bot Token 和网络连接",
)
)
if snapshot.last_error:
issues.append(
ChannelStatusIssue(
channel="slack",
account_id=snapshot.account_id,
kind="error",
message=f"Slack 账户 {snapshot.name or snapshot.account_id}: {snapshot.last_error}",
fix="查看日志排查错误原因",
)
)
if snapshot.status_state == "unconfigured":
issues.append(
ChannelStatusIssue(
channel="slack",
account_id=snapshot.account_id,
kind="unconfigured",
message=f"Slack 账户 {snapshot.name or snapshot.account_id} 未配置凭证",
fix="在管理面板中配置 Slack Bot Token",
)
)
return issues