ForcePilot/backend/package/yuxi/channels/adapters/feishu/subagent.py
Kris a6fa7245e5 feat(feishu): 完整实现飞书适配器核心模块
新增飞书机器人适配器全套功能,包括:
- 基础适配器入口与工具导出
- 消息格式化、卡片渲染、回复调度逻辑
- 会话ID生成、模型覆盖策略
- 消息发送缓存、顺序队列管理
- 飞书签名验证、加解密webhook请求
- 审批权限校验、机器人菜单事件处理
- 文档评论、钉消息、语音转码处理
- 静态/动态目录管理、子代理生命周期管理
- 各类工具集:聊天、云盘、文档、知识库API封装
2026-05-12 00:43:59 +08:00

115 lines
3.8 KiB
Python

from __future__ import annotations
import logging
from dataclasses import dataclass, field
from typing import Any
logger = logging.getLogger(__name__)
@dataclass
class SubAgentLifecycle:
agent_id: str
parent_chat_id: str
status: str = "pending"
created_at: float = 0.0
completed_at: float | None = None
result: dict[str, Any] = field(default_factory=dict)
error: str = ""
class FeishuSubAgentManager:
def __init__(self, max_concurrent: int = 10):
self._sub_agents: dict[str, SubAgentLifecycle] = {}
self._max_concurrent = max_concurrent
def start_sub_agent(
self,
agent_id: str,
parent_chat_id: str,
*,
metadata: dict[str, Any] | None = None,
) -> SubAgentLifecycle:
if len(self._sub_agents) >= self._max_concurrent:
oldest = next(iter(self._sub_agents))
self._sub_agents.pop(oldest)
logger.info("[FeishuSubAgent] Evicted oldest sub-agent: %s", oldest)
import time
lifecycle = SubAgentLifecycle(
agent_id=agent_id,
parent_chat_id=parent_chat_id,
status="active",
created_at=time.monotonic(),
result=metadata or {},
)
self._sub_agents[agent_id] = lifecycle
logger.info("[FeishuSubAgent] Started sub-agent %s for chat %s", agent_id, parent_chat_id)
return lifecycle
def complete_sub_agent(self, agent_id: str, *, result: dict[str, Any] | None = None) -> SubAgentLifecycle | None:
lifecycle = self._sub_agents.get(agent_id)
if lifecycle is None:
logger.warning("[FeishuSubAgent] Sub-agent %s not found for completion", agent_id)
return None
import time
lifecycle.status = "completed"
lifecycle.completed_at = time.monotonic()
if result:
lifecycle.result.update(result)
logger.info("[FeishuSubAgent] Completed sub-agent %s", agent_id)
return lifecycle
def fail_sub_agent(self, agent_id: str, error: str) -> SubAgentLifecycle | None:
lifecycle = self._sub_agents.get(agent_id)
if lifecycle is None:
return None
lifecycle.status = "failed"
lifecycle.error = error
logger.warning("[FeishuSubAgent] Sub-agent %s failed: %s", agent_id, error)
return lifecycle
def end_sub_agent(self, agent_id: str, *, result: dict[str, Any] | None = None) -> SubAgentLifecycle | None:
lifecycle = self._sub_agents.pop(agent_id, None)
if lifecycle is None:
return None
import time
lifecycle.completed_at = time.monotonic()
lifecycle.status = "ended"
if result:
lifecycle.result.update(result)
logger.info("[FeishuSubAgent] Ended sub-agent %s", agent_id)
return lifecycle
def get_sub_agent(self, agent_id: str) -> SubAgentLifecycle | None:
return self._sub_agents.get(agent_id)
def list_active(self) -> list[SubAgentLifecycle]:
return [s for s in self._sub_agents.values() if s.status == "active"]
def list_by_parent(self, parent_chat_id: str) -> list[SubAgentLifecycle]:
return [s for s in self._sub_agents.values() if s.parent_chat_id == parent_chat_id]
def clean_completed(self, max_age_s: float = 3600) -> int:
import time
now = time.monotonic()
to_remove = [
aid
for aid, s in self._sub_agents.items()
if s.status in ("completed", "failed", "ended") and s.completed_at and (now - s.completed_at) > max_age_s
]
for aid in to_remove:
self._sub_agents.pop(aid, None)
if to_remove:
logger.info("[FeishuSubAgent] Cleaned %d completed sub-agents", len(to_remove))
return len(to_remove)
def clear(self) -> None:
self._sub_agents.clear()