本次提交包含多项核心改进: 1. 新增微信公众号插件拉取传输模式配置,完善manifest与manifest加载逻辑 2. 新增运行态状态机与幂等冲突错误体系,补充错误映射与领域错误导出 3. 优化出站与入站上下文,新增幂等键、流式中断标记等字段 4. 完善发件箱仓储与模型,新增失败条目查询、投递原子语义字段 5. 修复签名验证阶段异常捕获逻辑,防御性处理内置NotImplementedError 6. 新增出站预算释放方法,完善机器人循环预算管控 7. 优化出站管道格式阶段,新增消息长度校验逻辑 8. 完善出站打字指示器阶段,新增重复启动防御与状态同步 9. 重构出站标记失败阶段,按源状态分支处理状态转换 10. 新增入站幂等过滤阶段,修复入站路由阶段空指针问题 11. 优化出站恢复扫描器,修复状态机调用与聚合根重建逻辑 12. 完善微信公众号适配器,新增类型校验与异常包装 13. 修复数据库事务回滚逻辑,简化不必要的显式回滚操作
256 lines
11 KiB
Python
256 lines
11 KiB
Python
"""插件元数据。
|
||
|
||
定义插件清单相关的枚举、不可变值对象,包括失败策略、凭证策略、插件依赖、
|
||
资源配额、配置字段、渠道清单与插件清单。所有 DTO 均为 ``dataclass(frozen=True)``,
|
||
集合字段使用 tuple,仅依赖标准库与契约层内部类型,用于插件注册、依赖解析、
|
||
资源配额声明与能力边界声明。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from dataclasses import dataclass
|
||
from datetime import datetime
|
||
from enum import StrEnum
|
||
from typing import Any, Literal
|
||
|
||
from yuxi.channels.contract.dtos.capability import ChannelCapabilities
|
||
from yuxi.channels.contract.dtos.channel import ChannelType
|
||
from yuxi.channels.contract.dtos.config import ConfigField
|
||
from yuxi.channels.contract.dtos.plugin import EnvVar
|
||
|
||
|
||
class FailurePolicy(StrEnum):
|
||
"""失败策略。
|
||
|
||
标识插件失败时的处理策略,用于宿主决定降级、熔断或隔离。继承
|
||
``str, Enum`` 以支持 JSON 序列化与字符串比较。
|
||
|
||
取值:
|
||
DEGRADE: 降级(走降级路径,保留部分能力)。
|
||
CIRCUIT_BREAK: 熔断(打开熔断器,暂时停止调用)。
|
||
ISOLATE: 隔离(隔离插件,阻止其继续运行)。
|
||
"""
|
||
|
||
DEGRADE = "degrade"
|
||
CIRCUIT_BREAK = "circuit_break"
|
||
ISOLATE = "isolate"
|
||
|
||
|
||
class CredentialType(StrEnum):
|
||
"""凭证类型。
|
||
|
||
标识渠道插件凭证的获取形态,用于 ``WizardService.finalize`` 分支选择
|
||
``CredentialService`` 的 acquire 方法(INV-3 契约显式化)。
|
||
|
||
取值:
|
||
STATIC: 纯静态凭证(如 app_id/app_secret),通过 ``acquireByManual``
|
||
落库。
|
||
DYNAMIC: 纯动态凭证(如 iLink bot_token),通过 ``acquireByQrLogin``
|
||
/ ``acquireByOAuth`` 落库。
|
||
HYBRID: 混合策略(如飞书 app_id/app_secret 静态 + user_access_token
|
||
动态),静态部分 ``acquireByManual``,动态部分可跳过(skippable)。
|
||
"""
|
||
|
||
STATIC = "static"
|
||
DYNAMIC = "dynamic"
|
||
HYBRID = "hybrid"
|
||
|
||
|
||
class AcquireMethod(StrEnum):
|
||
"""凭证获取方法。
|
||
|
||
标识 ``CredentialService`` 应调用的具体 acquire 方法,与
|
||
``CredentialType`` 配合指导凭证落库流程。
|
||
|
||
取值:
|
||
MANUAL: 手动录入(对应 ``acquireByManual``)。
|
||
QR_LOGIN: 扫码登录(对应 ``acquireByQrLogin``)。
|
||
OAUTH: OAuth 授权(对应 ``acquireByOAuth``)。
|
||
WEBHOOK_VERIFY: Webhook 验证(对应 ``acquireByWebhookVerify``)。
|
||
"""
|
||
|
||
MANUAL = "manual"
|
||
QR_LOGIN = "qr_login"
|
||
OAUTH = "oauth"
|
||
WEBHOOK_VERIFY = "webhook_verify"
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class PluginDependency:
|
||
"""插件依赖。
|
||
|
||
描述插件对其他插件的依赖关系,包括插件 ID 与版本范围,用于插件加载
|
||
前的依赖解析。
|
||
|
||
字段:
|
||
plugin_id: 依赖的插件 ID。
|
||
version_range: 语义化版本范围表达式(SemVer range)。
|
||
"""
|
||
|
||
plugin_id: str
|
||
version_range: str
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class ResourceQuota:
|
||
"""资源配额。
|
||
|
||
描述插件的资源配额限制,包括 CPU、内存、连接数与每秒调用数,用于
|
||
宿主对插件施加资源边界(FR-32 / §8.5)。
|
||
|
||
字段:
|
||
max_cpu: CPU 上限(如 "500m"),可选。
|
||
max_memory: 内存上限(如 "512Mi"),可选。
|
||
max_connections: 最大连接数,可选。
|
||
max_calls_per_sec: 每秒最大调用数,可选。
|
||
"""
|
||
|
||
max_cpu: str | None = None
|
||
max_memory: str | None = None
|
||
max_connections: int | None = None
|
||
max_calls_per_sec: int | None = None
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class CredentialStrategy:
|
||
"""凭证获取策略。
|
||
|
||
声明渠道插件的凭证获取策略,指导 ``WizardService.finalize`` 分支调用
|
||
``CredentialService`` 的何种 acquire 方法将凭证落库(INV-3 契约显式化)。
|
||
缺失该声明时(``None``)按 ``type=static`` / ``acquire_method=manual``
|
||
兜底。
|
||
|
||
字段:
|
||
type: 凭证类型(static/dynamic/hybrid)。
|
||
acquire_method: 凭证获取方法(manual/qr_login/oauth/webhook_verify)。
|
||
required_fields: 必需凭证字段名清单(对应 ``config_schema`` 中的字段)。
|
||
optional_fields: 可选凭证字段名清单。
|
||
supports_rotation: 是否支持凭证轮换。
|
||
supports_revocation: 是否支持凭证撤销。
|
||
"""
|
||
|
||
type: CredentialType
|
||
acquire_method: AcquireMethod
|
||
required_fields: tuple[str, ...]
|
||
optional_fields: tuple[str, ...] = ()
|
||
supports_rotation: bool = False
|
||
supports_revocation: bool = False
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class ChannelManifest:
|
||
"""渠道清单。
|
||
|
||
描述渠道插件的元数据,包括标识、版本、渠道类型、提供的能力、配置 schema、
|
||
入口模块、依赖、生命周期、兼容性、失败策略、资源配额与能力边界声明,
|
||
用于插件注册与装配。
|
||
|
||
字段:
|
||
id: 全局唯一标识(如 com.yuxi.channels.feishu)。
|
||
name: 人类可读名称。
|
||
version: 语义化版本。
|
||
channel_type: 渠道类型。
|
||
provides: 提供的能力列表。
|
||
entry_module: 插件入口模块路径。
|
||
capabilities: 渠道能力集合(静态声明,必填)。
|
||
config_schema: 配置项 schema(必填)。
|
||
capability_requirements: 能力运行时依赖声明(默认空)。每项为
|
||
``(能力字段名, (所需配置字段名清单))`` 元组,保持 frozen dataclass
|
||
可哈希。用于能力查询页判断"声明支持"与"实际可用"之间的差距。
|
||
depends: 依赖的其他插件(默认空)。
|
||
lifecycle: 支持的生命周期钩子(默认 init/start/stop/unload)。
|
||
compatibility: 兼容性信息(可选)。
|
||
failure_policy: 失败策略(默认 DEGRADE)。
|
||
resource_quota: 资源配额(可选)。
|
||
accessible_ports: 可访问的端口列表(默认空)。
|
||
injectable_pipelines: 可注入的管道列表(默认空)。
|
||
skills: 提供的技能列表(默认空)。
|
||
env_vars: 环境变量声明(默认空)。
|
||
critical: 是否为关键渠道(默认 False)。关键渠道失败时宿主标记为
|
||
``unhealthy``,非关键渠道失败标记为 ``degraded``(FR-31 / FR-35)。
|
||
requires_dm_pairing: 是否要求 DM 安全配对审批(默认 True)。声明
|
||
``False`` 的渠道(如已通过宿主认证体系)跳过 DM 配对审批直接放行
|
||
(FR-31)。插件未注册时框架回退为 ``True``(fail-closed)。
|
||
requires_outbound_delivery: 是否要求出站投递(默认 True)。声明 ``False``
|
||
的渠道无需实现 ``OutboundAdapter``,框架跳过出站投递阶段(FR-31)。
|
||
credential_strategy: 凭证获取策略(默认 ``None`` 表示无动态凭证,按
|
||
``type=static`` / ``acquire_method=manual`` 兜底)。声明该策略后,
|
||
``WizardService.finalize`` 据此分支调用 ``CredentialService`` 的
|
||
acquire 方法完成凭证落库(INV-3 契约显式化)。
|
||
max_message_length: 单条消息最大文本长度(FR19-P0-5 渠道级内容
|
||
长度校验,默认 4096)。出站管道据此对超长内容做截断或分片
|
||
决策,未声明的渠道沿用默认值。本字段为配置型参数(非能力
|
||
声明),故置于 manifest 顶层而非 ``capabilities`` 中。
|
||
supports_credential_cloning: 是否允许在账户克隆时复制凭据字段
|
||
(默认 False,ACC-CLONE)。声明为 False 时 ``include_credentials=True``
|
||
的克隆请求将被 handler 拒绝(``RuleViolationError``),fail-closed
|
||
防止凭据经克隆路径泄露。本字段为安全策略声明(非能力声明),
|
||
故置于 manifest 顶层而非 ``capabilities`` 中。
|
||
icon: 渠道展示图标名(lucide 图标库的 kebab-case 名称,如
|
||
``"message-square"``)。供前端卡片视图、筛选下拉、标签等场景
|
||
渲染渠道图标。缺失时前端回退默认图标。本字段为纯展示元数据
|
||
(非能力声明、非业务规则),由插件作者根据渠道视觉特征选择,
|
||
框架层不做取值校验。
|
||
transport_mode: 传输模式声明(Task 11.3,默认 ``both``)。声明渠道
|
||
偏好的入站传输模式:``pull`` 仅轮询、``stream`` 仅长连接、
|
||
``both`` 两者皆可(优先 Stream)。``TransportManager`` 据此与
|
||
适配器能力选择启动 Puller 或 Stream,避免模式冲突。
|
||
"""
|
||
|
||
id: str
|
||
name: str
|
||
version: str
|
||
channel_type: ChannelType
|
||
provides: tuple[str, ...]
|
||
entry_module: str
|
||
capabilities: ChannelCapabilities
|
||
config_schema: tuple[ConfigField, ...]
|
||
capability_requirements: tuple[tuple[str, tuple[str, ...]], ...] = ()
|
||
depends: tuple[PluginDependency, ...] = ()
|
||
lifecycle: tuple[str, ...] = ("init", "start", "stop", "unload")
|
||
compatibility: dict[str, Any] | None = None
|
||
failure_policy: FailurePolicy = FailurePolicy.DEGRADE
|
||
resource_quota: ResourceQuota | None = None
|
||
accessible_ports: tuple[str, ...] = ()
|
||
injectable_pipelines: tuple[str, ...] = ()
|
||
skills: tuple[str, ...] = ()
|
||
env_vars: tuple[EnvVar, ...] = ()
|
||
critical: bool = False
|
||
requires_dm_pairing: bool = True
|
||
requires_outbound_delivery: bool = True
|
||
credential_strategy: CredentialStrategy | None = None
|
||
max_message_length: int = 4096
|
||
supports_credential_cloning: bool = False
|
||
icon: str | None = None
|
||
transport_mode: Literal["pull", "stream", "both"] = "both"
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class PluginManifest:
|
||
"""插件清单。
|
||
|
||
描述插件注册到宿主的完整清单,包括渠道清单与插件提供的扩展点(适配器、
|
||
阶段、事件订阅、配置源),由 ``CHANNEL_ENTRY`` 返回给宿主。
|
||
|
||
字段:
|
||
manifest: 渠道清单。
|
||
adapters: 提供的适配器类型列表(默认空)。
|
||
stages: 提供的管道阶段列表(默认空)。
|
||
event_subscriptions: 订阅的事件类型列表(默认空)。
|
||
config_sources: 配置源列表(默认空)。
|
||
installed_at: 文件级安装时间(PLG-INSTALL 端点写入,默认 None
|
||
表示未通过文件级安装流程注册)。
|
||
install_source: 安装来源标识(如文件路径、URL、包名,默认 None)。
|
||
install_version: 安装时的版本号(可能与 manifest.version 不同,
|
||
如安装后未升级,默认 None)。
|
||
"""
|
||
|
||
manifest: ChannelManifest
|
||
adapters: tuple[str, ...] = ()
|
||
stages: tuple[str, ...] = ()
|
||
event_subscriptions: tuple[str, ...] = ()
|
||
config_sources: tuple[str, ...] = ()
|
||
installed_at: datetime | None = None
|
||
install_source: str | None = None
|
||
install_version: str | None = None
|