ForcePilot/backend/package/yuxi/channels/plugins
Kris a484844ea1 chore: 汇总完成多模块功能迭代与缺陷修复
此提交包含了大量跨模块的优化与修复:
1.  配置与依赖调整:调整账号列表分页默认值、重试上限、内容审核长度限制,新增出站文件支持
2.  代码结构优化:重构白名单缓存逻辑、移除冗余日志依赖、清理过时TODO注释
3.  功能增强:添加死信计数接口、会话绑定审计、SSE订阅限流、凭据验证服务
4.  契约更新:修正DTO字段命名、完善类型定义、更新审计操作类型
5.  错误处理:优化限流清理逻辑、添加异常捕获与告警
6.  文档与校验:补充字段校验规则、完善注释与文档说明
2026-07-11 21:37:16 +08:00
..
dingtalk chore: 汇总完成多模块功能迭代与缺陷修复 2026-07-11 21:37:16 +08:00
feishu chore: 汇总完成多模块功能迭代与缺陷修复 2026-07-11 21:37:16 +08:00
instagram chore: 汇总完成多模块功能迭代与缺陷修复 2026-07-11 21:37:16 +08:00
qqbot chore: 汇总完成多模块功能迭代与缺陷修复 2026-07-11 21:37:16 +08:00
wechat_ilink chore: 汇总完成多模块功能迭代与缺陷修复 2026-07-11 21:37:16 +08:00
wechat_mp chore: 汇总完成多模块功能迭代与缺陷修复 2026-07-11 21:37:16 +08:00
wechat_woc chore: 汇总完成多模块功能迭代与缺陷修复 2026-07-11 21:37:16 +08:00
wecom chore: 汇总完成多模块功能迭代与缺陷修复 2026-07-11 21:37:16 +08:00
__init__.py feat(channels): 批量新增多渠道网关限界上下文基础代码与契约 2026-07-02 03:22:12 +08:00
README.md refactor(wechat-woc,transport): 批量代码优化与功能增强 2026-07-11 05:42:26 +08:00

渠道插件包

本目录存放各渠道插件实现。每个渠道插件以独立子目录形式装配,通过 CHANNEL_ENTRY 契约注册到宿主。

最高原则:渠道开发 不得 污染框架层。框架层包括 yuxi.channels 包内除 plugins/ 之外的所有层(契约层、领域核心、应用服务、组合根、被驱动适配器)。若渠道开发需要框架层变更支持,必须 走 §2 的变更申请流程,不得 自行修改。


1. 边界红线:渠道开发不得污染框架层

1.1 框架层范围

路径 插件可执行操作
契约层 yuxi.channels.contract.* 只读 import(不得新增/修改文件)
领域核心层 yuxi.channels.core.* 禁止 import
应用服务层 yuxi.channels.application.* 禁止 import
组合根层 yuxi.channels.infrastructure.* 禁止 import
被驱动适配器层 yuxi.channels.adapters.* 禁止 import
插件层 yuxi.channels.plugins.<channel>.* 可自由编写

契约层文件的增删改属于框架层变更,必须 由架构角色评审后实施,渠道开发者 不得 直接修改(见 §2

1.2 依赖方向铁律INV-1 / INV-6

依据 演进式六边形-管道-插件架构规范.md §5.1

  • 所有依赖 必须 指向契约方向(外 → 内),内圈 不得 引用外圈类型。
  • 插件 不得 直接引用宿主内部实现, 依赖 yuxi.channels.contract.*
  • 跨边界共享的数据结构 必须 定义在契约层,由内外圈共同引用。
  • 依赖方向 不得 因"便利"而绕过,任何绕过都 必须 在评审中记录理由。

INV-1 / INV-2 / INV-3 / INV-7 / INV-8 为硬不变量违反即架构崩坏必须立即修复INV-4 / INV-5 / INV-6 / INV-9 / INV-10 为软不变量(违反应限期修复,可经架构评审临时豁免)。

1.3 插件可依赖的契约入口

插件 只能 import 以下契约层符号(适配器协议导出见 contract/plugin/adapters/__init__.py,其余符号见 contract/plugin/__init__.py

契约模块 内容
yuxi.channels.contract.plugin.entry CHANNEL_ENTRY / PluginHostPluginAdapter 标记 Protocol 与 Matcher 类型别名定义在本模块但未在 __init__.py 导出,仅供类型标注)
yuxi.channels.contract.plugin.manifest PluginManifest / ChannelManifest / ResourceQuota / PluginDependency / ConfigField / FailurePolicy / CredentialStrategy / CredentialType / AcquireMethod
yuxi.channels.contract.plugin.adapters 23 个适配器 ProtocolInboundAdapter / OutboundAdapter / SessionAdapter / RichMessageAdapter / StreamingAdapter / DirectoryAdapter / CommandAdapter / WizardAdapter / DoctorAdapter / WhitelistAdapter / ToolsAdapter / MessageOpsAdapter / StatusAdapter / MentionAdapter / IdentityResolverAdapter / LifecycleAdapter / LoginAdapter / ProbeableAdapter / ContentModerationAdapter / PullerAdapter / StreamConnectorAdapter / AttachmentUploadAdapter / WebhookTestAdapter
yuxi.channels.contract.plugin.capability CapabilityDeclaration / CapabilityProof / CapabilityProverIdentityResolverAdapter 继承 CapabilityProver,实现身份解析的插件 必须 同时实现 proveCapability
yuxi.channels.contract.plugin.lifecycle LifecycleState11 个状态)/ LifecycleHook8 个钩子)/ LifecycleHookHandler
yuxi.channels.contract.plugin.extension_point StageSlot / EventSubscription / ConfigSource / Stage / ConflictStrategy / FailureStrategy注意FailureStrategy 用于阶段执行,与 manifest 的 FailurePolicy 不同,见 §7.1
yuxi.channels.contract.dtos.* 跨边界共享 DTOMatchTier / ChannelCapabilities / ChannelType 等)
yuxi.channels.contract.errors.* 统一错误类型层级(见 §7.2
yuxi.channels.contract.ports.driven.* / yuxi.channels.contract.ports.driving.* 端口定义(仅供类型标注,实现由宿主注入)

适配器协议方法风格:所有适配器 Protocol 使用 @runtime_checkable 装饰,方法签名统一为 async def(少数同步方法如 getAckPolicy / supportsXxx 守卫 / getPollingConfig / getStreamConfig 除外)。方法体区分两种风格:必选方法为 raise NotImplementedError(实现类必须重写),可选方法为 return None / return False(实现类按需重写,默认不操作)。例如 InboundAdapter.normalizeInbound 为必选,InboundAdapter.downloadAttachmentFR-49 媒体下载,可选)默认 return None 表示未实现,由 MediaFetchStage 据此剔除附件并降级。LifecycleAdapterLoginAdapter 的全部方法均为必选(raise NotImplementedError),声明对应能力的插件 必须 实现全部方法。

1.4 资源访问约束INV-I7 / INV-6 / FR-32

插件 不得 直接访问以下宿主资源,必须 通过 PluginHost 提供的端口获取方法与扩展点注册方法获取依赖:

禁止直接访问 替代方式
宿主 settings host.getConfigPort()
宿主 logger host.getLoggerPort()
数据库连接池 host.getPersistencePort()
Redis 客户端 host.getCachePort()
追踪器 host.getTracerPort()
队列 host.getQueuePort()
会话存储 host.getConversationPort()
Agent 运行时 host.getAgentRunPort()
身份解析器 host.getIdentityResolverPort()(未注入抛 NotFoundError,不降级)
仓储子端口8 个) host.getChannelAccountRepositoryPort() / getChannelSessionRepositoryPort() / getPairingRepositoryPort() / getAuditLogRepositoryPort() / getOutboxRepositoryPort() / getUserIdentityRepositoryPort() / getIdempotencyRepositoryPort() / getPersistenceHealthPort()(运行时均返回 DrivenAdapters.persistence 聚合别名)
宿主中间件链 / 路由表 / 事件订阅器 host.registerStageSlot() / host.registerEventSubscription() / host.registerConfigSource() / host.registerMatchTier() / host.registerMatcher()

端口访问权限manifest 驱动,关键):插件可访问的端口由 manifest.jsonaccessible_ports 字段静态声明,宿主在 discover 阶段解析为 ChannelManifest.accessible_ports,运行时由 PluginHostImpl._checkPortAccess 在每次 getXxxPort() 调用中校验:请求的端口名 必须manifest.accessible_ports 集合内,否则抛 PermissionDeniedError。插件 无需(也无法)在 CHANNEL_ENTRY 内运行时声明端口权限——PluginHost Protocol 不提供 declareAccessiblePorts / declareInjectablePipelines / declareResourceQuota 方法,能力边界全部以 manifest.json 为唯一真相源F-03accessible_ports 同时由 PluginCapabilityChecker.check 在 resolved 阶段校验是否为下表 17 个端口名称的子集。

可声明的端口名称17 个,为 PluginCapabilityChecker.ALLOWED_PORTS 的子集):

类别 端口名称
基础被驱动端口9 个) ConfigPort / LoggerPort / PersistencePort / CachePort / TracerPort / QueuePort / ConversationPort / AgentRunPort / IdentityResolverPort
仓储子端口8 个,均由 PersistencePort 聚合实现) ChannelAccountRepositoryPort / ChannelSessionRepositoryPort / PairingRepositoryPort / AuditLogRepositoryPort / OutboxRepositoryPort / UserIdentityRepositoryPort / IdempotencyRepositoryPort / PersistenceHealthPort

仓储子端口在运行时返回 DrivenAdapters.persistence 聚合别名,该聚合同时实现全部 8 个细分子端口。IdentityResolverPort 默认为 None,由插件通过 registerAdapter("identity_resolver", ...) 注入后才可获取,未注入时 getIdentityResolverPort()NotFoundErrorfail-closed不降级

1.5 适配器注册FR-32

插件通过 host.registerAdapter(adapter_type, adapter) 注入渠道适配器。宿主 PluginHostImpl._ADAPTER_SETTERS 支持 24 种 adapter_type1 个单实例 + 23 个列表追加,含 23 个适配器 Protocol + 1 个渠道上下文提供者 Port

adapter_type 写入方式 对应 Protocol / Port
identity_resolver 单实例直接赋值 IdentityResolverAdapter
inbound 列表追加 InboundAdapter
outbound 列表追加 OutboundAdapter
status 列表追加 StatusAdapter
rich_message 列表追加 RichMessageAdapter
tools 列表追加 ToolsAdapter
message_ops 列表追加 MessageOpsAdapter
directory 列表追加 DirectoryAdapter
whitelist 列表追加 WhitelistAdapter
wizard 列表追加 WizardAdapter
doctor 列表追加 DoctorAdapter
command 列表追加 CommandAdapter
mention 列表追加 MentionAdapter
streaming 列表追加 StreamingAdapter
session 列表追加 SessionAdapter
channel_context_provider 列表追加 ChannelContextProviderPort(见 contract/ports/driven/channel_context_provider_port.py为 Port 而非适配器 Protocol,含 buildAgentSystemPrompt / buildAgentContextNote / getChannelFormatSpec 三个方法,支持 agent_id 关键字参数用于多 Agent 协作)
lifecycle 列表追加 LifecycleAdapter(见 contract/plugin/adapters/lifecycle_adapter.pyAL-01 账户生命周期介入)
login 列表追加 LoginAdapter(见 contract/plugin/adapters/login_adapter.pyQR-01 扫码登录)
probeable 列表追加 ProbeableAdapter(见 contract/plugin/adapters/probeable_adapter.pyFR-35 主动探测,可选实现)
content_moderation 列表追加 ContentModerationAdapter(见 contract/plugin/adapters/content_moderation_adapter.pyCR-01 内容预审核,可选实现)
puller 列表追加 PullerAdapter(见 contract/plugin/adapters/puller_adapter.py,传输引擎客户端型渠道轮询接入,可选实现,行为约束见 §8
stream_connector 列表追加 StreamConnectorAdapter(见 contract/plugin/adapters/stream_connector_adapter.py,传输引擎客户端型渠道长连接接入,可选实现,行为约束见 §8
attachment_upload 列表追加 AttachmentUploadAdapter(见 contract/plugin/adapters/attachment_upload_adapter.pyMSG-ATTACH-UPLOAD 附件上传,可选实现)
webhook_test 列表追加 WebhookTestAdapter(见 contract/plugin/adapters/webhook_test_adapter.pyWHK-TEST Webhook 测试事件发起,可选实现)

未列出的 adapter_typeRuleViolationError(rule="unsupported_adapter_type:<name>")

传输引擎适配器说明puller / stream_connectorTransportManager 在宿主启动时通过 PluginRegistry.listPluginAdapters() 收集(仅取每个渠道的 puller_adapters[0] / stream_connector_adapters[0],不支持多适配器),驱动 per-account 传输任务生命周期。账号启用/禁用由 ChannelAccountOnline / ChannelAccountOffline 领域事件自动触发,插件 不得LifecycleAdapter 中自管理 WS 连接或轮询任务(详见 §8 与 channels-transport-engine-设计方案-v1.0.md)。

1.6 能力声明与校验

能力校验分两层,插件开发者需同时满足:

加载期静态校验(由 PluginCapabilityChecker 执行):

  1. check(manifest):校验 accessible_ports / injectable_pipelines / resource_quota.max_cpu(≤ 50.0%)是否在允许范围内,违规抛 PermissionDeniedError / ValidationError

  2. verifyCapabilityConsistency(manifest, adapters):清单 capabilities 中声明为 True 的能力 必须 有对应的运行时适配器注册FR-21 契约一致性)。映射关系(CAPABILITY_ADAPTER_MAP

    能力字段 要求非空的适配器列表
    rich_message rich_message_adapters
    streaming streaming_adapters
    mention mention_adapters
    command command_adapters
    directory directory_adapters
    doctor doctor_adapters
    whitelist whitelist_adapters
    wizard wizard_adapters
    tools tools_adapters
    status status_adapters
    probeable probeable_adapters
    lifecycle lifecycle_adapters
    supports_qr_login login_adapters
    agent_collaboration mention_adapters(声明时 MentionAdapter.classifyAgentMention 必须可触发)
    supports_card_update_streaming streaming_adapters(复用 StreamingAdapterFR-12 流式卡片更新)
    message_edit / message_recall / supports_reaction / supports_pin / supports_card_update(任一为 True message_ops_adapters
    supports_image_inbound / supports_video_inbound(任一为 TrueFR-43 inbound_adapters
    supports_image_outbound / supports_video_outbound(任一为 TrueFR-43 outbound_adapters

    typing_indicator(由出站适配器承载)/ identity_resolver(由 IdentityResolverPort 承载)/ supports_credential_cloning(顶层安全策略字段,非能力声明)无专用适配器,不在校验范围。lifecycleAL-01supports_qr_loginQR-02manifest_loader._parse_capabilitiesmanifest.json 解析,声明为 True 时触发对应适配器列表非空校验。

运行时能力证明(由插件实现 CapabilityProver,出站中间件调用):

  • proveCapability(capability_name) 返回 CapabilityProof,结果可缓存(默认 TTL 300s
  • 静态声明支持但运行时证明不支持时,必须 记录告警日志并走降级路径FR-08 增强 / FR-21
  • IdentityResolverAdapter 继承 CapabilityProver,实现身份解析的插件 必须 同时实现 proveCapability

1.7 适配器职责约束INV-8

插件提供的适配器 只做协议转换与错误翻译不得 承载业务规则、事务编排、跨请求状态。业务规则归属领域核心,事务边界归属应用服务层。

1.8 错误显式化INV-7

  • 契约违反、配置错误、插件故障 必须 显式抛出 yuxi.channels.contract.errors.* 中的统一异常,禁止 用防御性回退掩盖。
  • 禁止 静默吞错(如 except Exception: pass);观测层失败允许降级,但 必须 通过 LoggerPort 记录 warning。
  • 原生异常(网络库、协议库异常)必须 在适配器内捕获并转换为统一异常,禁止 直接向外抛出。

2. 框架层变更申请流程

当渠道开发遇到以下情况时,不得 自行修改框架层,必须 提出变更申请:

  • 契约层缺少必要端口 / DTO / Protocol 方法。
  • 组合根层缺少必要能力(如新的扩展点类型、阶段插槽锚点)。
  • 管道缺少必要的阶段插槽。
  • 端口签名需要扩展(须走版本化流程,对应 INV-4 端口契约稳定性)。
  • PluginHost 缺少必要的端口获取方法或扩展点注册方法。

2.1 申请方式

  1. docs/vibe/v1.x/问题文档/渠道开发/ 下新建问题清单文档(命名 YYYY-MM-DD-<channel>-前置问题清单.md),内容包括:
    • 缺口描述:缺少什么、为什么需要、当前无法实现的用例。
    • 影响范围:契约层 / 组合根层 / 应用层,涉及的文件路径。
    • 期望方案:建议的变更方案与替代方案。
    • 紧急程度P0阻断渠道开发/ P1影响核心能力/ P2增强能力
  2. 提交架构评审,由架构角色确认方案后,由框架开发者实施 框架层变更。
  3. 变更完成后,渠道开发者同步切换到新契约,不得 在插件内保留对旧实现的绕过逻辑。

2.2 严禁的行为

  • plugins/<channel>/ 之外的目录直接修改代码以"临时"支持某渠道。
  • 在插件内通过反射、monkey-patch、import private symbol_ 前缀)、TYPE_CHECKING 之外的方式绕过契约边界。
  • yuxi.channels.core.* 中 import 任何框架类型(违反 INV-2 核心纯净性)。
  • 自行在契约层新增 DTO / Protocol 方法以"补全"渠道所需能力。

3. 标准布局

每个渠道插件遵循以下目录结构(以 wechat_woc 为参照,含客户端渠道的典型文件):

plugins/
└── <channel_name>/              # 如 wechat_woc/、feishu/、wechat_ilink/
    ├── __init__.py
    ├── manifest.json             # 插件清单(元数据、能力声明、配置 schemaF-03 单真相源)
    ├── entry.py                  # CHANNEL_ENTRY 入口函数host, manifest-> PluginManifest
    ├── lifecycle.py              # LifecycleHookHandler 实现onInit/onStart/.../onFail管理 httpx 连接池等插件级资源)
    ├── _constants.py             # 渠道共享常量(配置默认值/依赖标识,与 manifest.json default 对齐)
    ├── <channel>_client.py       # 渠道 HTTP/WS 客户端(如 woc_bridge_client.py / feishu_client.py封装 API 调用与错误翻译)
    ├── error_translator.py       # 渠道原生错误码 → 契约层 Error 子类 / TransportError 翻译INV-7
    ├── adapters/                 # 适配器实现(无 mutable 跨请求状态INV-5 / INV-8
    │   ├── __init__.py
    │   ├── inbound_adapter.py
    │   ├── outbound_adapter.py
    │   ├── puller_adapter.py            # 仅 pull/stream/both 传输模式需要
    │   ├── stream_connector_adapter.py  # 仅 stream/both 传输模式需要
    │   ├── session_adapter.py           # 通常无状态
    │   ├── status_adapter.py            # 通常无状态
    │   ├── lifecycle_adapter.py         # 账户生命周期account_id 派生、配置规范化、缓存清理)
    │   ├── login_adapter.py             # 扫码登录supports_qr_login=true 时必选)
    │   ├── probeable_adapter.py         # 连接探活probeable=true 时必选)
    │   ├── doctor_adapter.py            # 配置诊断
    │   ├── wizard_adapter.py            # 配置向导
    │   ├── whitelist_adapter.py         # 白名单管理
    │   ├── directory_adapter.py         # 通讯录查询
    │   ├── capability_prover.py         # CapabilityProver 实现(如需运行时能力证明)
    │   └── ...                          # 按需rich_message / streaming / mention / command / message_ops 等
    └── dtos/                     # 渠道特有 DTO可选仅插件内部使用不泄漏到契约层
        └── __init__.py

布局约束

  • 插件 只能 在自身 plugins/<channel>/ 子目录内创建文件。
  • 跨渠道共享的 DTO 必须 通过契约层变更申请提升到 yuxi.channels.contract.dtos.*不得 在插件间互相 import。
  • 渠道特有 DTO 不得 泄漏到契约层,留在插件自身的 dtos/ 目录内。
  • error_translator.py_constants.py 为可选但推荐集中管理渠道错误码翻译与跨模块共享常量避免重复定义wechat_woc / feishu / wechat_ilink 均采用此模式)。

3.1 manifest.json schema

manifest.json 由宿主在 discovered 阶段解析为 ChannelManifest。字段如下:

字段 类型 必填 默认值 说明
id string 全局唯一标识(如 com.yuxi.channels.feishu
name string 人类可读名称
version string 语义化版本
channel_type string 渠道类型枚举值(feishu / dingtalk / wecom / webchat / telegram / discord / whatsapp / custom,共 8 个;框架层禁止分支到具体枚举值,必须通过 ChannelManifest 声明字段驱动)
entry_module string 插件入口模块路径Python import 路径,如 yuxi.channels.plugins.feishu.entry
capabilities object 能力集合(见 ChannelCapabilities 字段,共 26 个 bool 字段,未声明默认 false,详见下方 capabilities 解析范围说明)
config_schema array 配置项 schema每项含 key / type / required / default / hot_reloadable / sensitive / scope / constraints
provides array 提供的能力列表
lifecycle array 支持的生命周期钩子(如 ["init","start","stop","unload"]
compatibility object 兼容性信息(自由结构 dict由插件自行约定语义框架仅透传不解释
failure_policy string 失败策略(degrade / circuit_break / isolate,对应 FailurePolicy 枚举,见 §7.1
depends array [] 依赖的其他插件(每项含 plugin_id / version_range;加载遵循拓扑序,卸载遵循反向拓扑序)
resource_quota object null 资源配额(max_cpu ≤ 50.0、max_memorymax_connectionsmax_calls_per_sec
accessible_ports array [] 可访问的端口名称列表(必须为 §1.4 中 17 个端口的子集)
injectable_pipelines array [] 可注入的管道列表(inbound / outbound / control-plane 的子集)
skills array [] 提供的技能列表
env_vars array [] 环境变量声明(每项含 name / description / required / default / sensitive
critical bool false 是否为关键渠道(失败时宿主标记 unhealthy,否则标记 degradedFR-31 / FR-35
requires_dm_pairing bool true 是否要求 DM 安全配对审批(声明 false 跳过配对审批FR-31插件未注册时框架回退为 truefail-closed
requires_outbound_delivery bool true 是否要求出站投递(声明 false 无需实现 OutboundAdapter框架跳过出站投递阶段FR-31
credential_strategy object null 凭据策略(CredentialStrategy,含 typestatic/dynamic/hybridacquire_methodmanual/qr_login/oauth/webhook_verifyrequired_fields / optional_fields / supports_rotation / supports_revocation
max_message_length integer 4096 单条消息最大文本长度FR19-P0-5 渠道级内容长度校验)。出站管道据此对超长内容做截断或分片决策。顶层字段,非 capabilities 内字段(如 wechat_woc 声明 2000、feishu 声明 30720
supports_credential_cloning bool false 是否允许账户克隆时复制凭据字段ACC-CLONE。声明 falseinclude_credentials=True 的克隆请求被 handler 拒绝(RuleViolationErrorfail-closed 防止凭据泄露。顶层安全策略字段,非能力声明,无专用适配器
icon string null 渠道展示图标名lucide 图标库 kebab-case 名称,如 "smartphone" / "message-square")。供前端卡片渲染,缺失时前端回退默认图标。纯展示元数据,框架不做取值校验
transport_mode string both 传输模式声明Task 11.3pull 仅轮询 / stream 仅长连接 / both 两者皆可(优先 StreamTransportManager 据此与适配器能力选择启动 Puller 或 Stream如 wechat_woc 声明 both
capability_requirements array [] 能力运行时依赖声明,每项为 [能力字段名, [所需配置字段名清单]] 二元组(如 wechat_woc 声明 ["supports_image_inbound", ["bridge_url","bridge_token"]])。用于能力查询页判断"声明支持"与"实际可用"的差距

必填字段说明provides / lifecycle / compatibility / failure_policyChannelManifest dataclass 中虽有默认值,但 manifest_loader.load_manifest_from_dirrequired_fields(共 11 个:id / name / version / channel_type / entry_module / capabilities / config_schema / provides / lifecycle / compatibility / failure_policy)将它们标记为 JSON 必填,缺失时抛 ValidationError

entry_module 说明:宿主通过 importlib.import_module(manifest.entry_module) 动态导入,必须为可 import 的完整模块路径。返回的 PluginManifest.manifest.id 必须manifest.jsonid 一致,否则抛 ValidationError。模块缺失抛 DependencyErrorCHANNEL_ENTRY 不可调用抛 ValidationError,调用异常抛 InternalError

capabilities 解析范围manifest_loader._parse_capabilities 解析 ChannelCapabilities26 个 bool 字段——5 个基础消息能力(rich_message / streaming / typing_indicator / message_edit / message_recall、4 个消息操作子能力(supports_reaction / supports_pin / supports_card_update / supports_card_update_streaming、4 个媒体能力(supports_image_inbound / supports_video_inbound / supports_image_outbound / supports_video_outboundFR-43、10 个扩展能力(mention / command / directory / doctor / whitelist / wizard / tools / status / probeable / identity_resolver、3 个生命周期与协作能力(lifecycle AL-01 / supports_qr_login QR-02 / agent_collaboration)。未声明的字段默认 False。能力字段声明为 True 时,加载期由 PluginCapabilityChecker.verifyCapabilityConsistency 校验对应适配器列表非空(见 §1.6typing_indicator / identity_resolver / supports_credential_cloning 无专用适配器,不在校验范围。

注意max_message_length(消息长度上限)与 supports_credential_cloning(凭据克隆策略)是 ChannelManifest顶层字段不在 capabilities 对象内,声明位置见上表对应行。


4. 插件加载时序

依据 演进式六边形-管道-插件架构规范.md §9插件生命周期状态机PluginStateMachine),共 11 个状态:

文件级NOT_INSTALLED ⇄ INSTALLED → DISCOVERED
运行时discovered → resolved → loaded → initialized → started ⇄ paused
                                    ↓                ↓
                                  failed ←─────── (任一阶段失败)
                                    ↓
                                  resolved (重载FR-36)

started/paused → stopped → unloaded → NOT_INSTALLED (终态)
状态 说明
NOT_INSTALLED 文件级初始态(未安装)
INSTALLED 文件级已安装(不触发 LifecycleHook
DISCOVEREDRESOLVEDLOADEDINITIALIZEDSTARTED 运行时主路径
PAUSED 暂停态(与 STARTED 双向)
STOPPEDUNLOADED 关停路径,UNLOADED 后回到 NOT_INSTALLED
FAILED 任一阶段失败后的态,仅允许 → RESOLVED(重载)

完整加载流程(由 PluginLifecycleManager 编排,所有公开方法加 asyncio.Lock 串行化):

  1. discovered:宿主扫描 plugins/ 目录下每个子目录的 manifest.json,解析为 ChannelManifest 并注册到 PluginRegistry。无 manifest.json 的子目录被跳过;解析或注册冲突(PluginAlreadyRegisteredError / ConflictError)记录错误日志并跳过该插件不阻塞其他插件FR-21。发布 PluginDiscovered 事件。
  2. resolvedPluginDependencyResolver Kahn 拓扑排序(依赖者→被依赖者边,取反得到"被依赖者先加载"序),循环依赖抛 RuleViolationError(rule="circular_dependency:...")PluginCapabilityChecker.check 校验能力边界(accessible_ports / injectable_pipelines / resource_quota)。
  3. loadedPluginLoader 通过 importlib.import_module 导入 entry_module,调用 CHANNEL_ENTRY(host) 获取 PluginManifest,校验清单 ID 一致性。插件在 CHANNEL_ENTRY 内通过 host.registerAdapter 注入适配器,通过 host.registerStageSlot / registerEventSubscription / registerConfigSource / registerMatchTier / registerMatcher 注入扩展点,通过 host.registerLifecycleHandler 注册生命周期钩子,通过 host.registerCapabilityProver 注册能力证明器。随后 verifyCapabilityConsistency 校验声明能力与运行时适配器一致性FR-21CapabilityRegistry.registerDeclaration 注册能力声明。
  4. initialized:取 host.getLifecycleHandler(),非 None 时调用 onInit(超时 60s
  5. started:调用 onStart(超时 60s插件开始处理请求。成功后将 manifest.version 追加写入 applied_migrations 配置键ACCOUNT 作用域FR17-P0-3乐观并发控制标记配置已加载写入失败抛异常触发降级。发布 PluginStarted 事件。
  6. paused / resumed:调用 onPause / onResumeonPause 超时 30sonResume 实际使用 STARTED 状态的超时 60s(由 _with_timeout(handler.onResume(), LifecycleState.STARTED) 决定)。
  7. stopped:调用 onStop(超时 30s必须等待在途请求完成
  8. unloaded:调用 onUnload(超时 30s必须释放所有资源注销六个注册表stage_slots / event_subs / config_sources / event_bus / route_match_registry / capability_registry单个注销异常告警不阻断host.close() 释放 DrivenAdapters 资源,从 PluginRegistry 注销,清单保存到 _reload_manifests 供重载使用。
  9. failed(任一阶段失败):setState(FAILED),发布 PluginFailed 事件,调用 onFail 钩子(超时 30sonFail 自身失败仅告警不阻断降级流程),调用 DegradationManager.onPluginFailed 触发优雅降级FR-36固定 60s 退避,最多 3 次重试),发布 ChannelDegraded 事件,_releasePluginResources 释放插件占用的扩展点注册与 host 实例。失败插件由后台任务(reloadLoop,默认 60s 间隔)按退避时间自动重载(FAILED → RESOLVED → … → STARTED)。

宿主关停时的批量停止INF-013 回滚专用):stopAllStartedPlugins() 遍历 STARTED + PAUSED 状态插件逐个 stop,单插件异常告警并继续,不阻塞其他插件关停。

生命周期钩子完整列表(见 LifecycleHookHandler,全为 async

钩子方法 触发阶段 超时 用途
onInit initialized 60s 初始化资源(连接、缓存预热)
onStart started 60s 开始处理请求
onPause paused 30s 暂停接收新请求
onResume started从 paused 60s 恢复接收请求(使用 STARTED 状态超时)
onStop stopped 30s 等待在途请求完成
onUnload unloaded 30s 释放所有资源
onReconfigure 配置热更新FR-37 接收新配置字典,必须 支持回滚
onFail failed 30s 失败清理(由宿主在 _handle_failure 中调用,自身异常不得阻断降级)

reloadLoop 多 Worker 保护:后台扫描任务通过 CachePort.acquireAdvisoryLock("plugin_reload_scanner", ttl_seconds=interval) 获取分布式咨询锁,锁未获取时跳过本轮,避免多 Worker 重复重载失败插件§10.2 并发控制)。正常路径在 finally 中释放锁,锁 TTL 与扫描间隔一致作为崩溃恢复安全网。reloadFailedPlugins() 调用 DegradationManager.listPluginsDueForReload() 获取到期插件,逐个 reload成功后调 onPluginRecovered 并发布 ChannelRecovered 事件。

生命周期约束

  • 插件 不得started 之前对外提供服务。
  • 插件 必须 支持 stoppedunloaded 的干净退出,不得 遗留线程、连接、临时资源。
  • 插件失败 不得 拖垮宿主宿主负责按扩展点策略降级或熔断FR-36
  • 生命周期钩子超时后标记插件 FAILED 并触发降级;超时抛 OperationTimeoutError(timeout_ms=..., message=...) 保留原异常链。
  • onReconfigure 必须 支持配置回滚FR-37配置应用失败时恢复至上一次有效配置。
  • 运行中插件(STARTED / PAUSED)不允许直接 unregister,必须先 stopPluginRegistry.unregisterRuleViolationError)。
  • 同一 ChannelType 仅允许注册一个插件(PluginRegistry.register 重复时抛 ConflictError("channel_type"))。

PluginHost Protocol 当前未声明 registerLifecycleHandler / getLifecycleHandler 方法,但 PluginHostImpl 已实现。插件运行时可通过鸭子类型调用;如需静态类型检查支持,走 §2 变更申请流程补充 Protocol 声明。


5. CHANNEL_ENTRY 契约

每个插件 必须 通过 CHANNEL_ENTRY 契约注册,不得 隐式全局注入FR-32。签名 (host, manifest) -> PluginManifest(见 entry.py 末尾类型别名):宿主在 discover 阶段从 manifest.json 解析出 ChannelManifest 后,于 loaded 阶段连同 host 一并传入。插件 直接复用 入参 manifest 作为返回 PluginManifest.manifest不得 在 entry 内重新构造声明性字段(capabilities / config_schema / accessible_ports / injectable_pipelines / resource_quota / env_vars / credential_strategy避免双真相源F-03 单真相源。entry 仅负责:端口获取 → client/handler 实例化 → registerAdapter 调用 → registerLifecycleHandler 注册 → 返回 PluginManifest

以下示例取自 wechat_woc/entry.py(已精简),展示 13 适配器型客户端渠道插件的标准写法feishu / wechat_ilink 同模式):

# entry.py
from yuxi.channels.contract.plugin.entry import PluginHost
from yuxi.channels.contract.plugin.manifest import (
    ChannelManifest,
    PluginManifest,
)

from .adapters.capability_prover import WocCapabilityProver
from .adapters.directory_adapter import WeChatWocDirectoryAdapter
from .adapters.doctor_adapter import WeChatWocDoctorAdapter
from .adapters.inbound_adapter import WeChatWocInboundAdapter
from .adapters.lifecycle_adapter import WeChatWocLifecycleAdapter
from .adapters.login_adapter import WeChatWocLoginAdapter
from .adapters.outbound_adapter import WeChatWocOutboundAdapter
from .adapters.probeable_adapter import WeChatWocProbeableAdapter
from .adapters.puller_adapter import WeChatWocPullerAdapter
from .adapters.session_adapter import WeChatWocSessionAdapter
from .adapters.status_adapter import WeChatWocStatusAdapter
from .adapters.stream_connector_adapter import WeChatWocStreamConnectorAdapter
from .adapters.whitelist_adapter import WeChatWocWhitelistAdapter
from .adapters.wizard_adapter import WeChatWocWizardAdapter
from .lifecycle import WeChatWocLifecycleHandler
from .woc_bridge_client import WocBridgeClient

# 运行时适配器清单(与 registerAdapter 调用顺序一致,非声明性字段)
_ADAPTER_TYPES: tuple[str, ...] = (
    "puller", "stream_connector", "inbound", "outbound", "session",
    "status", "login", "lifecycle", "probeable", "doctor",
    "wizard", "whitelist", "directory",
)


def channel_entry(host: PluginHost, manifest: ChannelManifest) -> PluginManifest:
    """微信 wechat_woc 渠道插件入口。

    能力边界由 manifest 静态字段承载,宿主在加载期通过
    PluginCapabilityChecker.check 校验,运行时通过 _checkPortAccess
    从 manifest.accessible_ports 强制约束。本函数不重复构造声明性字段。
    """

    # 1. 获取端口实例(宿主已从 manifest.accessible_ports 加载权限,
    #    getXxxPort 调用经 _checkPortAccess 校验,未声明抛 PermissionDeniedError
    config_port = host.getConfigPort()
    logger_port = host.getLoggerPort()
    cache_port = host.getCachePort()
    persistence_port = host.getPersistencePort()

    # 2. 实例化 client 与 LifecycleHandler
    #    handler 在 onInit 调 client.attach_http_client 注入连接池,
    #    config_schema / resource_quota 取自 manifestF-03 单真相源)
    client = WocBridgeClient(config_port, cache_port, logger_port, plugin_version=manifest.version)
    handler = WeChatWocLifecycleHandler(
        config_port, logger_port, cache_port, manifest.config_schema, client,
        max_connections=(
            manifest.resource_quota.max_connections
            if manifest.resource_quota is not None else 10
        ),
    )

    # 3. 注册 13 个适配器adapter_type 见 §1.5
    #    puller/outbound 接收 (client, config_port, logger_port)
    #    stream_connector/inbound/doctor/wizard/directory/login 接收 (client, logger_port)
    #    lifecycle/whitelist 接收 (cache_port, logger_port)
    #    probeable 接收 (client, persistence_port, logger_port)session/status 无状态
    host.registerAdapter("puller", WeChatWocPullerAdapter(client, config_port, logger_port))
    host.registerAdapter("stream_connector", WeChatWocStreamConnectorAdapter(client, config_port, logger_port))
    host.registerAdapter("inbound", WeChatWocInboundAdapter(client, logger_port))
    host.registerAdapter("outbound", WeChatWocOutboundAdapter(client, config_port, logger_port))
    host.registerAdapter("session", WeChatWocSessionAdapter())
    host.registerAdapter("status", WeChatWocStatusAdapter())
    host.registerAdapter("login", WeChatWocLoginAdapter(client, logger_port))
    host.registerAdapter("lifecycle", WeChatWocLifecycleAdapter(cache_port, logger_port))
    host.registerAdapter("probeable", WeChatWocProbeableAdapter(client, persistence_port, logger_port))
    host.registerAdapter("doctor", WeChatWocDoctorAdapter(client, logger_port))
    host.registerAdapter("wizard", WeChatWocWizardAdapter(client, config_port, logger_port, manifest.channel_type))
    host.registerAdapter("whitelist", WeChatWocWhitelistAdapter(cache_port, logger_port))
    host.registerAdapter("directory", WeChatWocDirectoryAdapter(client, logger_port))

    # 4. 注册能力证明者FR-08 增强,对 manifest 声明的媒体出站能力运行时证明)
    host.registerCapabilityProver(WocCapabilityProver(client))

    # 5. 注册生命周期钩子registerLifecycleHandler 仅 PluginHostImpl 实现,
    #    未声明到 Protocol运行时通过鸭子类型调用
    host.registerLifecycleHandler(handler)

    # 6. 返回 PluginManifestmanifest 直接复用入参adapters 为运行时清单)
    return PluginManifest(manifest=manifest, adapters=_ADAPTER_TYPES)


CHANNEL_ENTRY = channel_entry

扩展点注册(按需)registerStageSlot / registerEventSubscription / registerConfigSource / registerMatchTier / registerMatcher 可在 entry 内按需调用wechat_woc / feishu / wechat_ilink 三个内置插件当前均未注入自定义阶段或匹配层级依赖宿主默认装配8 个默认 MatchTier 见 §5.1)。

适配器 DI 风格:适配器构造参数由插件自行设计,仅约束"无 mutable 跨请求状态INV-5+ 仅做协议转换INV-8"。三种典型形态:①持有 (client, config_port, logger_port) 需读 ConfigPort 的适配器puller / outbound / stream_connector②仅持有 (client, logger_port) 的纯协议转换适配器inbound / doctor / wizard / directory / login③无状态适配器session / status__init__ 参数,所有数据从 raw_event.payload 提取)。

5.1 PluginHost Protocol 完整方法清单

PluginHost Protocol@runtime_checkable)声明的方法(实现见 PluginHostImpl

类别 方法
端口获取17 getConfigPort / getLoggerPort / getPersistencePort / getCachePort / getTracerPort / getQueuePort / getConversationPort / getAgentRunPort / getIdentityResolverPort / getChannelAccountRepositoryPort / getChannelSessionRepositoryPort / getPairingRepositoryPort / getAuditLogRepositoryPort / getOutboxRepositoryPort / getUserIdentityRepositoryPort / getIdempotencyRepositoryPort / getPersistenceHealthPort
扩展点注册7 registerAdapter(adapter_type: str, adapter: PluginAdapter) / registerStageSlot(slot: StageSlot) / registerEventSubscription(subscription: EventSubscription) / registerConfigSource(source: ConfigSource) / registerCapabilityProver(prover: CapabilityProver) / registerMatchTier(tier: MatchTier) / registerMatcher(name: str, matcher: Matcher)
事件发布(异步) async publishEvent(event: DomainEvent) -> None

能力边界声明不在 ProtocolPluginHost Protocol 声明 declareAccessiblePorts / declareInjectablePipelines / declareResourceQuota——这三项能力边界由 manifest.jsonaccessible_ports / injectable_pipelines / resource_quota 字段静态承载,宿主在 discover 阶段解析resolved 阶段由 PluginCapabilityChecker.check 校验,运行时由 _checkPortAccessmanifest.accessible_ports 强制约束(见 §1.4)。

registerLifecycleHandler / getLifecycleHandler 已在 PluginHostImpl 实现但未声明到 Protocol见 §4 末尾说明)。

MatchTier 定义在 contract/dtos/route.pyname: str / priority: int / match_method: str / enabled: bool = True。宿主在 factory.py 注册 8 个默认匹配层级(升序:default=100 / account=200 / channel_type=300 / channel_session=400 / chat_type=500 / peer_id=600 / identity_id=700 / session_key=800其中 5 个内置 matchersession_key/identity_id/peer_id/account/default其余 3 个层级chat_type/channel_session/channel_type暂未实现匹配逻辑可由插件注入。


6. 管道阶段与扩展点注入

插件通过 host.registerStageSlot(slot: StageSlot) 向管道注入自定义阶段。StageSlot 字段(见 extension_point.py

字段 类型 默认值 说明
pipeline str (必填) 管道名:inbound / outbound / control(注意 control-plane 管道名为 control
anchor str (必填) 锚点格式:before:<stage_id> / after:<stage_id> / replace:<stage_id>
stage Stage (必填) 阶段实现(含 id / reads / writes / idempotent / thread_safe / failure: FailureStrategy / compensate: str | None / condition / async process(ctx) -> bool
priority int 100 同锚点多插件按 priority 升序注入
multi bool False 是否允许多插件注入同锚点
conflict_strategy ConflictStrategy CHAIN CHAIN / REJECT / OVERRIDE
failure_policy FailurePolicy DEGRADE 插件阶段的失败策略,由 StageSlotInjector 映射为 FailureStrategy(见 §7.1

6.1 管道阶段清单(合法锚点目标)

StageSlotInjector.validateAnchors 在宿主启动期校验所有槽位的锚点必须命中以下阶段 ID未命中抛 RuleViolationError

Inbound 管道13 阶段,见 inbound_pipeline.py

Stage ID failure 说明
receive TERMINATE 接收原始事件
signature-verify TERMINATE 签名校验
classify TERMINATE 事件分类
media-fetch DEGRADE 媒体下载(可选,附件为空时跳过)
status-route SKIP 状态事件路由
identity-resolve SKIP 身份解析
service-account-resolve TERMINATE 服务账号解析
security TERMINATE 安全检查DM 配对、Bot 循环预算)
command-check TERMINATE 命令检查(唯一允许 replace 锚点的阶段
session-resolve TERMINATE 会话解析
route TERMINATE 路由绑定
agent-run-enqueue TERMINATE Agent 运行入队
reply SKIP ACK 决策FR-24

Outbound 管道14 原生 + 2 补偿阶段,见 outbound_pipeline.py

Stage ID failure compensate 说明
fence-check TERMINATE 幂等栅栏
load-build TERMINATE 加载并构建载荷
capability-verify DEGRADE 能力校验(声明+证明双层)
format DEGRADE 格式化(富消息降级到 Markdown
trusted-inject TERMINATE 可信注入
prefix SKIP 前缀处理
typing-indicator SKIP 打字指示器
stream-chunk DEGRADE 流式分块
truncation-check SKIP 截断检查
typing-stop SKIP 停止打字指示器
whitelist-check TERMINATE 白名单检查
outbox-persist COMPENSATE outbox-rollback Outbox 持久化(仅 delivery_mode=="persistent" 触发)
deliver COMPENSATE outbox-mark-failed 投递(仅 delivery_mode=="persistent" 触发)
status-writeback SKIP 状态回写
outbox-rollback / outbox-mark-failed SKIP 补偿阶段

Control-plane 管道5 阶段,见 control_plane_pipeline.py

Stage ID failure 说明
auth TERMINATE 认证
permission TERMINATE 权限校验
rate-limit TERMINATE 限流
dispatch TERMINATE 分派到 21 个 handler
audit DEGRADE 审计日志best-effort

6.2 锚点规则

  • 锚点格式:before:<stage_id> / after:<stage_id> / replace:<stage_id>
  • replace 锚点 仅允许 替换 command-check 阶段(StageSlotInjector._REPLACE_ALLOWED_STAGE_IDS = {"command-check"}),其余阶段 replacePermissionDeniedError
  • 同锚点同 priority 抛 ConflictError(f"stage_slot:{key}:priority={slot.priority}")
  • 注入按 priority 升序串联(不抢占式覆盖)。

6.3 事件订阅与配置源

  • host.registerEventSubscription(subscription) 直接委托 EventBus.registerEventSubscriptionRegistry 已废弃,仅供遗留代码兼容)。每个 handler 调用施加 5s 超时;超时或异常按 failure_policy 处理(DEGRADE 记告警继续;CIRCUIT_BREAK / ISOLATEDegradationManager.onPluginFailed 触发降级)。
  • host.registerConfigSource(source) 注册配置源v1.0 阶段无插件注册且 loadConfig 无调用方,能力预留)。

7. 错误处理与失败策略

7.1 FailurePolicy vs FailureStrategy

两个不同的枚举,不得混淆

枚举 定义位置 用途 取值
FailurePolicy manifest.py 插件隔离策略manifest / EventSubscription / ConfigSource / StageSlot 字段) DEGRADE / CIRCUIT_BREAK / ISOLATE
FailureStrategy extension_point.py 阶段执行失败策略(Stage.failure 字段) TERMINATE / SKIP / COMPENSATE / DEGRADE

StageSlotInjector 将插件 StageSlot.failure_policyFailurePolicy)映射为阶段 FailureStrategy

FailurePolicy FailureStrategy
DEGRADE DEGRADEctx.degraded = True,继续下一阶段)
CIRCUIT_BREAK SKIP(跳过当前阶段继续)
ISOLATE SKIP

7.2 统一错误类型层级

插件 必须 抛出 contract/errors/ 中的统一异常(基类 Error(Exception, ABC),含 error_code / message / trace_id / status_code / details)。常用错误类:

错误类 error_code HTTP 典型场景
ValidationError(ClientError, ValueError) VALIDATION_ERROR 400 字段校验失败(同时继承 ValueError 兼容 Pydantic
AuthError AUTH_ERROR 401 认证失败
NotFoundError NOT_FOUND 404 资源不存在
PermissionDeniedError PERMISSION_DENIED 403 端口未声明 / replace 锚点非法 / 权限不足(命名避开内置 PermissionError
ConflictError CONFLICT 409 重复注册 / 状态冲突
RuleViolationError RULE_VIOLATION 422 状态机非法转移 / 循环依赖 / unsupported adapter_type
RateLimitError RATE_LIMIT 429 速率限制(details.retry_after 触发 Retry-After header
ChannelDegradedError CHANNEL_DEGRADED 503 渠道降级(降级渠道发送抛此异常)
BotLoopBudgetExceededError BOT_LOOP_BUDGET_EXCEEDED 429 Bot 循环预算耗尽
CapabilityNotProvenError CAPABILITY_NOT_PROVEN 422 运行时能力证明失败
ConfigNotHotReloadableError CONFIG_NOT_HOT_RELOADABLE 422 不可热更新配置
ConfigRollbackError CONFIG_ROLLBACK_FAILED 500 配置回滚失败
LifecycleHookError LIFECYCLE_HOOK_ERROR 生命周期钩子失败(构造含 hook / reason
InternalError(ServerError) INTERNAL 500 内部错误(携带 cause
DependencyError(ServerError) DEPENDENCY 依赖缺失(如 entry_module 导入失败)
OperationTimeoutError(ServerError) TIMEOUT 操作超时(避免与内置 TimeoutError 冲突,含 timeout_ms
NotImplementedError(ServerError) NOT_IMPLEMENTED 未实现(含 operation
PluginNotFoundError / PluginAlreadyRegisteredError 404/409 插件注册表操作
TransportError category 映射 401/429/503/500 传输层错误(auth_expired/rate_limited/transient/permanent,含 retry_after_ms

凭据相关:CredentialNotFoundError / CredentialExpiredError / CredentialInvalidError / CredentialAcquireFailedError。内容审核:ContentReviewError / ContentViolationError。Agent 协作:AgentCollaborationError / AgentNotAvailableError / AgentHandoffFailedError

7.3 异常处理约束

  • 原生异常(网络库、协议库、asyncio.TimeoutError必须 在适配器内捕获并转换为统一异常,禁止 直接向外抛出。
  • 禁止 静默吞错(except Exception: pass);观测层降级 必须 通过 LoggerPort 记录 warning。
  • 异常链 必须raise ... from exc 保留原 traceback。
  • onFail 钩子自身失败不得阻断降级流程(仅告警)。

8. 传输引擎适配器说明

注册 puller / stream_connector 适配器的插件,其传输任务由 TransportManager + BaseTransportWorker 驱动,插件 不得 自管理 WS 连接或轮询任务。

8.1 收集与绑定

  • TransportManager.start() 通过 PluginRegistry.listPluginAdapters() 收集适配器,每个渠道仅取 puller_adapters[0] / stream_connector_adapters[0](不支持多适配器)。
  • TransportManager 订阅 7 类事件:ChannelAccountOnline(启动 per-account task/ ChannelAccountOffline(停止 task/ ChannelDegraded / ChannelRecovered / TransportErrorOccurred / ConfigChangedtransport.* 热更新)/ ConfigRollback
  • 账号上线/下线由 LifecycleAdapter.onAccountEnabled / onAccountDisabled 发布 ChannelAccountOnline / ChannelAccountOffline 领域事件自动触发。

8.2 Puller 模式(PullerWorker

  • ChannelAccount.transport_cursor 恢复游标,循环调 adapter.poll(account_id, cursor)
  • at-least-once 语义:一批消息全部成功投递后才推进游标;失败不推进,下次重新拉取。
  • 游标持久化通过 persistence_port.updateChannelAccount
  • long_poll_timeout_ms <= 0 and poll_interval_ms <= 0 抛 permanent 错误。
  • 空消息重置 backoff_attempt,连续成功 1 次调 circuit_breaker.recordSuccess

8.3 Stream 模式(StreamWorker

  • adapter.connect(account_id, cancellation_token) 建立长连接。
  • 启动 _heartbeatLoopname stream-heartbeat-{channel_type}-{account_id})定期调 adapter.ping(connection)
  • _receiveLoop 循环 connection.receive() + _deliverMessage(..., source="stream")
  • 连接断开通过指数退避重连。

8.4 错误处理与熔断

_handleTransportErrorTransportError.category 分流:

category 行为
auth_expired recordFailure + state="error" + 发布 ChannelAccountOffline(reason="auth_expired") → 停止账号循环
permanent recordFailure + state="error" → 停止账号循环
rate_limited 不记失败,用 retry_after_ms(默认 5000ms等待 → 继续
transient / 其他 recordFailure + 指数退避(默认序列 (1.0, 2.0, 5.0, 10.0, 30.0)jitter 0.2)→ 继续

_watchdogLoop(间隔 2s监控 last_activity_at,超过 stall_timeout_ms(默认 120000无活动则 cancel task 并重启(重启前调 adapter.onTransportReset(account_id) 如存在)。

8.5 配置项

通过 ConfigPort 读取(transport.* 前缀,定义在 config_schema.py

配置项 默认值 热更新
transport.stall_timeout_ms 120000
transport.backoff_schedule "1,2,5,10,30"
transport.backoff_jitter 0.2
transport.max_restart_attempts None restart required
transport.graceful_shutdown_timeout_s 10.0 restart required

9. 配置 Schema 与热更新

渠道配置项的元数据定义在 contract/policy/config_schema.pyCONFIG_SCHEMA(共 50 个 ConfigField,派生 HOT_RELOADABLE_KEYS 38 个 + NON_HOT_RELOADABLE_KEYS 12 个)。任何通过 ConfigPort 读写的 key 必须 先在 CONFIG_SCHEMA 声明,读写未声明 key 被禁止。

  • 可热更新 key38 个,如 dm_policy / allow_from / rate_limit / bot_loop_budget / rich_message_enabled / streaming_enabled / ack_policy / identity_confidence_threshold / channel_access_default_level / credential_failure_threshold 等):通过 ConfigPort 热更新,触发 ConfigChanged 事件。
  • 不可热更新 key12 个,如 webhook_port / webhook_secret / tls_cert / plugin_entry / database_url / redis_url / credentials / login_status / credential_version 等):修改需重启进程。
  • transport.* 5 个 key 见 §8.5。

插件自身的渠道业务配置(如飞书 app_id、企业微信 corp_id通过 manifest.jsonconfig_schema 声明,由 ConfigPort 在 ACCOUNT 作用域读写。ConfigField.required 必须被消费(HostBootstrap 启动期与 ConfigManager 校验,禁止装饰性死字段)。

onReconfigure(config: dict) 钩子接收热更新配置,必须 支持回滚FR-37配置应用失败时恢复至上一次有效配置失败抛 ConfigRollbackError


10. 禁止事项清单Code Review Checklist

提交渠道代码前,逐项确认:

10.1 边界

  • 未修改 plugins/<channel>/ 之外的任何文件。
  • 未 import yuxi.channels.core.* / yuxi.channels.application.* / yuxi.channels.infrastructure.* / yuxi.channels.adapters.*
  • 未新增 / 修改 yuxi.channels.contract.* 中的任何文件(契约层变更走 §2 流程)。
  • 未在插件间互相 import 内部实现(跨渠道共享走契约层提升)。
  • 需要框架层支持时已走变更申请流程,未自行突破边界。

10.2 资源访问

  • 未直接访问 settings / logger / DB pool / Redis client。
  • 所有被驱动依赖通过 PluginHost.getXxxPort() 获取。
  • manifest.jsonaccessible_ports 声明了所有将调用的 getXxxPort() 对应端口(宿主运行时据 _checkPortAccess 校验,未声明抛 PermissionDeniedError)。
  • 未修改宿主中间件链、路由表、事件订阅器(仅通过 registerXxx() 注入)。
  • accessible_ports 为 §1.4 中 17 个端口的子集,injectable_pipelinesinbound / outbound / control-plane 的子集。
  • resource_quota.max_cpu 不超过 50.0%。

10.3 契约与异常

  • 通过 CHANNEL_ENTRY 显式注册,未隐式全局注入。
  • manifest.jsonentry_module 为可 import 的完整模块路径,返回清单 ID 与声明 ID 一致。
  • manifest.json 包含全部 11 个必填字段(id / name / version / channel_type / entry_module / capabilities / config_schema / provides / lifecycle / compatibility / failure_policy)。
  • 清单 capabilities 声明为 True 的能力均有对应运行时适配器注册FR-21映射见 §1.6)。
  • 声明 lifecycle: true 时已注册 LifecycleAdapter,声明 supports_qr_login: true 时已注册 LoginAdapter,声明 agent_collaboration: trueMentionAdapter.classifyAgentMention 可触发。
  • 注册 ProbeableAdapter 时已实现 async def probe(self) -> AdapterProbeOutcomeFR-35
  • 实现 IdentityResolverAdapter 时同时实现了 proveCapability(继承 CapabilityProver)。
  • 适配器只做协议转换与错误翻译不承载业务规则INV-8
  • 原生异常已转换为 yuxi.channels.contract.errors.* 统一异常(含 raise ... from exc)。
  • 未用 try/except 静默吞错INV-7观测层降级必须有 LoggerPort warning。
  • 未混淆 FailurePolicymanifest / StageSlotFailureStrategyStage.failure

10.4 管道与扩展点

  • registerStageSlotpipelineinbound / outbound / control 之一,anchor 命中 §6.1 阶段 ID。
  • replace 锚点仅用于 command-check 阶段。
  • 同锚点 priority 不与其他插件冲突。
  • Stage.failureFailureStrategy)与 StageSlot.failure_policyFailurePolicy)按 §7.1 选用。

10.5 生命周期

  • 通过 host.registerLifecycleHandler(handler) 注册了 LifecycleHookHandler(如需响应生命周期)。
  • 未在 started 之前对外提供服务。
  • stoppedunloaded 干净退出,无遗留线程 / 连接 / 临时资源。
  • onInit / onStart 在 60s 内完成,onPause / onStop / onUnload / onFail 在 30s 内完成,onResume 在 60s 内完成。
  • onReconfigure如实现支持配置回滚FR-37
  • onFail(如实现)不抛异常,失败清理逻辑不得阻断降级流程。

10.6 传输引擎(仅 puller / stream_connector 插件)

  • 未在 LifecycleAdapter 中自管理 WS 连接或轮询任务(由 TransportManager 驱动)。
  • PullerAdapter.getPollingConfigStreamConnectorAdapter.getStreamConfig 返回有效配置(long_poll_timeout_mspoll_interval_ms 不同时为 0
  • 游标推进在消息成功投递后at-least-once
  • 原生网络异常已翻译为 TransportError(含 category)。

11. 参考

契约层源码

  • entry.pyCHANNEL_ENTRY / PluginHost / PluginAdapter / Matcher
  • manifest.pyPluginManifest / ChannelManifest / ResourceQuota / FailurePolicy / CredentialStrategy / CredentialType / AcquireMethod / PluginDependency
  • capability.pyCapabilityDeclaration / CapabilityProof / CapabilityProver
  • lifecycle.pyLifecycleState11 个)/ LifecycleHook8 个)/ LifecycleHookHandler
  • extension_point.pyStageSlot / EventSubscription / ConfigSource / Stage / ConflictStrategy / FailureStrategy
  • adapters/ — 23 个适配器 Protocol15 个基础 + LifecycleAdapter + LoginAdapter + ProbeableAdapter + ContentModerationAdapter + PullerAdapter + StreamConnectorAdapter + AttachmentUploadAdapter + WebhookTestAdapter;已删除 MediaAdapter / OAuthAdapter 孤儿协议)
  • contract/plugin/__init__.py — 契约层导出清单38 个符号manifest 9 + entry 2 + capability 3 + lifecycle 3 + extension_point 6 + adapters 23未导出 PluginAdapter / Matcher
  • contract/dtos/capability.pyChannelCapabilities26 个 bool 字段)/ CapabilityResult
  • contract/dtos/channel.pyChannelType8 个枚举值)/ AccountStatus / OnboardingStatus / SessionStatus
  • contract/dtos/route.pyMatchTier
  • contract/ports/driven/aggregate.pyDrivenAdapters 聚合13 单实例 + 23 列表 = 36 字段)
  • contract/ports/driven/channel_context_provider_port.pyChannelContextProviderPort3 方法,支持 agent_id
  • contract/errors/ — 统一错误类型层级36 个导出符号,见 §7.2
  • contract/policy/config_schema.pyCONFIG_SCHEMA50 字段)/ HOT_RELOADABLE_KEYS / NON_HOT_RELOADABLE_KEYS

应用层实现(仅供理解宿主行为,插件 不得 import

基础设施层实现(仅供理解宿主行为,插件 不得 import

  • host_bootstrap.py — 13 步启动序列(规范 §15.1 为 7 步,实际扩展 5 个后台任务启动 + _loadConfig/_initSchema 拆分)
  • host_shutdown.py — 7 步关停序列(规范 §15.2 含"注销驱动适配器",实际因驱动适配器机制未实现而省略,新增"停止传输引擎管理器"
  • factory.py — 组合根装配8 个默认 MatchTier / 内置事件订阅者 / 5 个 worker 进程依赖工厂)
  • dependency_injection.py — DI 容器(仅单例,未注册抛 InternalError
  • scheduler.py — 5 个调度 handlerFR-04/05/06/08/RPT-05

架构规范

docs/vibe/v1.0/规范文档/外部系统渠道开发规范.mdyuxi.external_systems 模块(非 channels 模块)的规范,与本文档无直接同源关系,仅供六边形架构落地参考。