1. 重构端口权限校验逻辑,改为manifest静态声明而非运行时声明 2. 新增wizard/tools/status/probeable等4个能力项与对应适配器 3. 补充标准插件目录结构与更多示例代码细节 4. 更新manifest.json字段说明与capabilities解析范围 5. 修正CHANNEL_ENTRY契约示例,移除运行时端口声明逻辑 6. 更新契约层文件与能力字段数量说明
72 KiB
渠道插件包
本目录存放各渠道插件实现。每个渠道插件以独立子目录形式装配,通过 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 / PluginHost(PluginAdapter 标记 Protocol 与 Matcher 类型别名定义在本模块但未在 __init__.py 导出,仅供类型标注) |
yuxi.channels.contract.plugin.manifest |
PluginManifest / ChannelManifest / ResourceQuota / PluginDependency / ConfigField / FailurePolicy / CredentialStrategy / CredentialType / AcquireMethod |
yuxi.channels.contract.plugin.adapters |
23 个适配器 Protocol(InboundAdapter / 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 / CapabilityProver(IdentityResolverAdapter 继承 CapabilityProver,实现身份解析的插件 必须 同时实现 proveCapability) |
yuxi.channels.contract.plugin.lifecycle |
LifecycleState(11 个状态)/ LifecycleHook(8 个钩子)/ LifecycleHookHandler |
yuxi.channels.contract.plugin.extension_point |
StageSlot / EventSubscription / ConfigSource / Stage / ConflictStrategy / FailureStrategy(注意:FailureStrategy 用于阶段执行,与 manifest 的 FailurePolicy 不同,见 §7.1) |
yuxi.channels.contract.dtos.* |
跨边界共享 DTO(含 MatchTier / 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.downloadAttachment(FR-49 媒体下载,可选)默认return None表示未实现,由 MediaFetchStage 据此剔除附件并降级。LifecycleAdapter与LoginAdapter的全部方法均为必选(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.json 的 accessible_ports 字段静态声明,宿主在 discover 阶段解析为 ChannelManifest.accessible_ports,运行时由 PluginHostImpl._checkPortAccess 在每次 getXxxPort() 调用中校验:请求的端口名 必须 在 manifest.accessible_ports 集合内,否则抛 PermissionDeniedError。插件 无需(也无法)在 CHANNEL_ENTRY 内运行时声明端口权限——PluginHost Protocol 不提供 declareAccessiblePorts / declareInjectablePipelines / declareResourceQuota 方法,能力边界全部以 manifest.json 为唯一真相源(F-03)。accessible_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()抛NotFoundError(fail-closed,不降级)。
1.5 适配器注册(FR-32)
插件通过 host.registerAdapter(adapter_type, adapter) 注入渠道适配器。宿主 PluginHostImpl._ADAPTER_SETTERS 支持 24 种 adapter_type(1 个单实例 + 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.py,AL-01 账户生命周期介入) |
login |
列表追加 | LoginAdapter(见 contract/plugin/adapters/login_adapter.py,QR-01 扫码登录) |
probeable |
列表追加 | ProbeableAdapter(见 contract/plugin/adapters/probeable_adapter.py,FR-35 主动探测,可选实现) |
content_moderation |
列表追加 | ContentModerationAdapter(见 contract/plugin/adapters/content_moderation_adapter.py,CR-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.py,MSG-ATTACH-UPLOAD 附件上传,可选实现) |
webhook_test |
列表追加 | WebhookTestAdapter(见 contract/plugin/adapters/webhook_test_adapter.py,WHK-TEST Webhook 测试事件发起,可选实现) |
未列出的 adapter_type 抛 RuleViolationError(rule="unsupported_adapter_type:<name>")。
传输引擎适配器说明:
puller/stream_connector由 TransportManager 在宿主启动时通过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 执行):
-
check(manifest):校验accessible_ports/injectable_pipelines/resource_quota.max_cpu(≤ 50.0%)是否在允许范围内,违规抛PermissionDeniedError/ValidationError。 -
verifyCapabilityConsistency(manifest, adapters):清单capabilities中声明为True的能力 必须 有对应的运行时适配器注册(FR-21 契约一致性)。映射关系(CAPABILITY_ADAPTER_MAP):能力字段 要求非空的适配器列表 rich_messagerich_message_adaptersstreamingstreaming_adaptersmentionmention_adapterscommandcommand_adaptersdirectorydirectory_adaptersdoctordoctor_adapterswhitelistwhitelist_adapterswizardwizard_adapterstoolstools_adaptersstatusstatus_adaptersprobeableprobeable_adapterslifecyclelifecycle_adapterssupports_qr_loginlogin_adaptersagent_collaborationmention_adapters(声明时MentionAdapter.classifyAgentMention必须可触发)supports_card_update_streamingstreaming_adapters(复用 StreamingAdapter,FR-12 流式卡片更新)message_edit/message_recall/supports_reaction/supports_pin/supports_card_update(任一为 True)message_ops_adapterssupports_image_inbound/supports_video_inbound(任一为 True,FR-43)inbound_adapterssupports_image_outbound/supports_video_outbound(任一为 True,FR-43)outbound_adapterstyping_indicator(由出站适配器承载)/identity_resolver(由IdentityResolverPort承载)/supports_credential_cloning(顶层安全策略字段,非能力声明)无专用适配器,不在校验范围。lifecycle(AL-01)与supports_qr_login(QR-02)由 manifest_loader._parse_capabilities 从manifest.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 申请方式
- 在
docs/vibe/v1.x/问题文档/渠道开发/下新建问题清单文档(命名YYYY-MM-DD-<channel>-前置问题清单.md),内容包括:- 缺口描述:缺少什么、为什么需要、当前无法实现的用例。
- 影响范围:契约层 / 组合根层 / 应用层,涉及的文件路径。
- 期望方案:建议的变更方案与替代方案。
- 紧急程度:P0(阻断渠道开发)/ P1(影响核心能力)/ P2(增强能力)。
- 提交架构评审,由架构角色确认方案后,由框架开发者实施 框架层变更。
- 变更完成后,渠道开发者同步切换到新契约,不得 在插件内保留对旧实现的绕过逻辑。
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 # 插件清单(元数据、能力声明、配置 schema,F-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_memory、max_connections、max_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,否则标记 degraded,FR-31 / FR-35) |
requires_dm_pairing |
bool | 否 | true |
是否要求 DM 安全配对审批(声明 false 跳过配对审批,FR-31;插件未注册时框架回退为 true,fail-closed) |
requires_outbound_delivery |
bool | 否 | true |
是否要求出站投递(声明 false 无需实现 OutboundAdapter,框架跳过出站投递阶段,FR-31) |
credential_strategy |
object | 否 | null |
凭据策略(CredentialStrategy,含 type ∈ static/dynamic/hybrid、acquire_method ∈ manual/qr_login/oauth/webhook_verify、required_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)。声明 false 时 include_credentials=True 的克隆请求被 handler 拒绝(RuleViolationError),fail-closed 防止凭据泄露。顶层安全策略字段,非能力声明,无专用适配器 |
icon |
string | 否 | null |
渠道展示图标名(lucide 图标库 kebab-case 名称,如 "smartphone" / "message-square")。供前端卡片渲染,缺失时前端回退默认图标。纯展示元数据,框架不做取值校验 |
transport_mode |
string | 否 | both |
传输模式声明(Task 11.3):pull 仅轮询 / stream 仅长连接 / both 两者皆可(优先 Stream)。TransportManager 据此与适配器能力选择启动 Puller 或 Stream(如 wechat_woc 声明 both) |
capability_requirements |
array | 否 | [] |
能力运行时依赖声明,每项为 [能力字段名, [所需配置字段名清单]] 二元组(如 wechat_woc 声明 ["supports_image_inbound", ["bridge_url","bridge_token"]])。用于能力查询页判断"声明支持"与"实际可用"的差距 |
必填字段说明:
provides/lifecycle/compatibility/failure_policy在ChannelManifestdataclass 中虽有默认值,但 manifest_loader.load_manifest_from_dir 的required_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.json的id一致,否则抛ValidationError。模块缺失抛DependencyError,CHANNEL_ENTRY不可调用抛ValidationError,调用异常抛InternalError。
capabilities解析范围:manifest_loader._parse_capabilities 解析 ChannelCapabilities 的 26 个 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_outbound,FR-43)、10 个扩展能力(mention/command/directory/doctor/whitelist/wizard/tools/status/probeable/identity_resolver)、3 个生命周期与协作能力(lifecycleAL-01 /supports_qr_loginQR-02 /agent_collaboration)。未声明的字段默认False。能力字段声明为True时,加载期由 PluginCapabilityChecker.verifyCapabilityConsistency 校验对应适配器列表非空(见 §1.6);typing_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) |
DISCOVERED → RESOLVED → LOADED → INITIALIZED → STARTED |
运行时主路径 |
PAUSED |
暂停态(与 STARTED 双向) |
STOPPED → UNLOADED |
关停路径,UNLOADED 后回到 NOT_INSTALLED |
FAILED |
任一阶段失败后的态,仅允许 → RESOLVED(重载) |
完整加载流程(由 PluginLifecycleManager 编排,所有公开方法加 asyncio.Lock 串行化):
- discovered:宿主扫描
plugins/目录下每个子目录的manifest.json,解析为ChannelManifest并注册到PluginRegistry。无manifest.json的子目录被跳过;解析或注册冲突(PluginAlreadyRegisteredError/ConflictError)记录错误日志并跳过该插件,不阻塞其他插件(FR-21)。发布PluginDiscovered事件。 - resolved:PluginDependencyResolver Kahn 拓扑排序(依赖者→被依赖者边,取反得到"被依赖者先加载"序),循环依赖抛
RuleViolationError(rule="circular_dependency:...");PluginCapabilityChecker.check 校验能力边界(accessible_ports/injectable_pipelines/resource_quota)。 - loaded:PluginLoader 通过
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-21),CapabilityRegistry.registerDeclaration注册能力声明。 - initialized:取
host.getLifecycleHandler(),非 None 时调用onInit(超时 60s)。 - started:调用
onStart(超时 60s),插件开始处理请求。成功后将manifest.version追加写入applied_migrations配置键(ACCOUNT 作用域,FR17-P0-3,乐观并发控制),标记配置已加载;写入失败抛异常触发降级。发布PluginStarted事件。 - paused / resumed:调用
onPause/onResume。onPause超时 30s;onResume实际使用STARTED状态的超时 60s(由_with_timeout(handler.onResume(), LifecycleState.STARTED)决定)。 - stopped:调用
onStop(超时 30s,必须等待在途请求完成)。 - unloaded:调用
onUnload(超时 30s,必须释放所有资源),注销六个注册表(stage_slots / event_subs / config_sources / event_bus / route_match_registry / capability_registry,单个注销异常告警不阻断),host.close()释放DrivenAdapters资源,从PluginRegistry注销,清单保存到_reload_manifests供重载使用。 - failed(任一阶段失败):
setState(FAILED),发布PluginFailed事件,调用onFail钩子(超时 30s,onFail 自身失败仅告警不阻断降级流程),调用 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之前对外提供服务。 - 插件 必须 支持
stopped→unloaded的干净退出,不得 遗留线程、连接、临时资源。 - 插件失败 不得 拖垮宿主:宿主负责按扩展点策略降级或熔断(FR-36)。
- 生命周期钩子超时后标记插件
FAILED并触发降级;超时抛OperationTimeoutError(timeout_ms=..., message=...)保留原异常链。 onReconfigure必须 支持配置回滚(FR-37),配置应用失败时恢复至上一次有效配置。- 运行中插件(
STARTED/PAUSED)不允许直接unregister,必须先stop(PluginRegistry.unregister抛RuleViolationError)。 - 同一
ChannelType仅允许注册一个插件(PluginRegistry.register重复时抛ConflictError("channel_type"))。
PluginHostProtocol 当前未声明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 取自 manifest(F-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, logger_port))
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. 返回 PluginManifest(manifest 直接复用入参,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 |
能力边界声明不在 Protocol:
PluginHostProtocol 不 声明declareAccessiblePorts/declareInjectablePipelines/declareResourceQuota——这三项能力边界由manifest.json的accessible_ports/injectable_pipelines/resource_quota字段静态承载,宿主在 discover 阶段解析,resolved 阶段由PluginCapabilityChecker.check校验,运行时由_checkPortAccess从manifest.accessible_ports强制约束(见 §1.4)。
registerLifecycleHandler/getLifecycleHandler已在PluginHostImpl实现但未声明到 Protocol(见 §4 末尾说明)。
MatchTier定义在contract/dtos/route.py:name: 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 个内置 matcher(session_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"}),其余阶段replace抛PermissionDeniedError。- 同锚点同 priority 抛
ConflictError(f"stage_slot:{key}:priority={slot.priority}")。 - 注入按
priority升序串联(不抢占式覆盖)。
6.3 事件订阅与配置源
host.registerEventSubscription(subscription)直接委托 EventBus.register(EventSubscriptionRegistry已废弃,仅供遗留代码兼容)。每个 handler 调用施加 5s 超时;超时或异常按failure_policy处理(DEGRADE记告警继续;CIRCUIT_BREAK/ISOLATE调DegradationManager.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_policy(FailurePolicy)映射为阶段 FailureStrategy:
FailurePolicy |
→ FailureStrategy |
|---|---|
DEGRADE |
DEGRADE(ctx.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/ConfigChanged(transport.*热更新)/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)建立长连接。 - 启动
_heartbeatLoop(namestream-heartbeat-{channel_type}-{account_id})定期调adapter.ping(connection)。 _receiveLoop循环connection.receive()+_deliverMessage(..., source="stream")。- 连接断开通过指数退避重连。
8.4 错误处理与熔断
_handleTransportError 按 TransportError.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.py 的 CONFIG_SCHEMA(共 50 个 ConfigField,派生 HOT_RELOADABLE_KEYS 38 个 + NON_HOT_RELOADABLE_KEYS 12 个)。任何通过 ConfigPort 读写的 key 必须 先在 CONFIG_SCHEMA 声明,读写未声明 key 被禁止。
- 可热更新 key(38 个,如
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事件。 - 不可热更新 key(12 个,如
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.json 的 config_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.json的accessible_ports声明了所有将调用的getXxxPort()对应端口(宿主运行时据_checkPortAccess校验,未声明抛PermissionDeniedError)。- 未修改宿主中间件链、路由表、事件订阅器(仅通过
registerXxx()注入)。 accessible_ports为 §1.4 中 17 个端口的子集,injectable_pipelines为inbound/outbound/control-plane的子集。resource_quota.max_cpu不超过 50.0%。
10.3 契约与异常
- 通过
CHANNEL_ENTRY显式注册,未隐式全局注入。 manifest.json的entry_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: true时MentionAdapter.classifyAgentMention可触发。 - 注册
ProbeableAdapter时已实现async def probe(self) -> AdapterProbeOutcome(FR-35)。 - 实现
IdentityResolverAdapter时同时实现了proveCapability(继承CapabilityProver)。 - 适配器只做协议转换与错误翻译,不承载业务规则(INV-8)。
- 原生异常已转换为
yuxi.channels.contract.errors.*统一异常(含raise ... from exc)。 - 未用 try/except 静默吞错(INV-7);观测层降级必须有
LoggerPortwarning。 - 未混淆
FailurePolicy(manifest / StageSlot)与FailureStrategy(Stage.failure)。
10.4 管道与扩展点
registerStageSlot的pipeline为inbound/outbound/control之一,anchor命中 §6.1 阶段 ID。replace锚点仅用于command-check阶段。- 同锚点 priority 不与其他插件冲突。
Stage.failure(FailureStrategy)与StageSlot.failure_policy(FailurePolicy)按 §7.1 选用。
10.5 生命周期
- 通过
host.registerLifecycleHandler(handler)注册了LifecycleHookHandler(如需响应生命周期)。 - 未在
started之前对外提供服务。 stopped→unloaded干净退出,无遗留线程 / 连接 / 临时资源。onInit/onStart在 60s 内完成,onPause/onStop/onUnload/onFail在 30s 内完成,onResume在 60s 内完成。onReconfigure(如实现)支持配置回滚(FR-37)。onFail(如实现)不抛异常,失败清理逻辑不得阻断降级流程。
10.6 传输引擎(仅 puller / stream_connector 插件)
- 未在
LifecycleAdapter中自管理 WS 连接或轮询任务(由TransportManager驱动)。 PullerAdapter.getPollingConfig与StreamConnectorAdapter.getStreamConfig返回有效配置(long_poll_timeout_ms与poll_interval_ms不同时为 0)。- 游标推进在消息成功投递后(at-least-once)。
- 原生网络异常已翻译为
TransportError(含category)。
11. 参考
契约层源码:
- entry.py —
CHANNEL_ENTRY/PluginHost/PluginAdapter/Matcher - manifest.py —
PluginManifest/ChannelManifest/ResourceQuota/FailurePolicy/CredentialStrategy/CredentialType/AcquireMethod/PluginDependency - capability.py —
CapabilityDeclaration/CapabilityProof/CapabilityProver - lifecycle.py —
LifecycleState(11 个)/LifecycleHook(8 个)/LifecycleHookHandler - extension_point.py —
StageSlot/EventSubscription/ConfigSource/Stage/ConflictStrategy/FailureStrategy - adapters/ — 23 个适配器 Protocol(15 个基础 +
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.py —
ChannelCapabilities(26 个 bool 字段)/CapabilityResult - contract/dtos/channel.py —
ChannelType(8 个枚举值)/AccountStatus/OnboardingStatus/SessionStatus - contract/dtos/route.py —
MatchTier - contract/ports/driven/aggregate.py —
DrivenAdapters聚合(13 单实例 + 23 列表 = 36 字段) - contract/ports/driven/channel_context_provider_port.py —
ChannelContextProviderPort(3 方法,支持agent_id) - contract/errors/ — 统一错误类型层级(36 个导出符号,见 §7.2)
- contract/policy/config_schema.py —
CONFIG_SCHEMA(50 字段)/HOT_RELOADABLE_KEYS/NON_HOT_RELOADABLE_KEYS
应用层实现(仅供理解宿主行为,插件 不得 import):
- plugin_host_impl.py —
PluginHost实现,含_ADAPTER_SETTERS分发表(24 键)与_checkPortAccess校验 - plugin_lifecycle_manager.py — 生命周期编排,含
discover/load/reload/reloadLoop/stopAllStartedPlugins/reloadFailedPlugins - plugin_state_machine.py — 11 状态转移表与钩子超时约束
- plugin_capability_checker.py —
ALLOWED_PORTS(17)/ALLOWED_PIPELINES/CAPABILITY_ADAPTER_MAP/ 加载期校验 - plugin_loader.py —
importlib动态导入 /installFromSource(PLG-INSTALL,仅path)/uninstall(PLG-UNINSTALL-FILE) - plugin_dependency_resolver.py — Kahn 拓扑排序
- stage_slot_injector.py — 锚点校验 /
replace限制(仅command-check)/FailurePolicy→FailureStrategy映射 - inbound_pipeline.py — Inbound 管道(13 阶段)
- outbound_pipeline.py — Outbound 管道(14+2 阶段)
- control_plane_pipeline.py — Control-plane 管道(5 阶段,21 个 handler)
- transport/manager.py —
TransportManager(事件订阅 + per-account task) - transport/base_worker.py —
BaseTransportWorker(watchdog + 退避 + 熔断) - transport/puller_worker.py —
PullerWorker(at-least-once) - transport/stream_worker.py —
StreamWorker(心跳 + 重连) - event_bus.py —
EventBus(5s handler 超时 + failure_policy 分流) - channel_probe.py —
ChannelProbe主动探测器实现(FR-35,30s 超时) - health_aggregator.py —
HealthAggregator(10s 缓存 + 5s 检查超时) - degradation_manager.py —
DegradationManager(固定 60s 退避,最多 3 次重试)
基础设施层实现(仅供理解宿主行为,插件 不得 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 个调度 handler(FR-04/05/06/08/RPT-05)
架构规范:
- 演进式六边形-管道-插件架构规范.md — §5 不变量(INV-1 ~ INV-10,硬/软分级)、§9 插件规范、§10.2 事务策略、§12 并发与线程模型、§15 启动关停序列
- channels-transport-engine-设计方案-v1.0.md — 传输引擎设计(微内核架构,P0 仅 Puller)
注:
docs/vibe/v1.0/规范文档/外部系统渠道开发规范.md是yuxi.external_systems模块(非channels模块)的规范,与本文档无直接同源关系,仅供六边形架构落地参考。