ForcePilot/backend/package/yuxi/channels/plugins/README.md
Kris 742299cb07 chore: 批量清理报告相关代码并完成多项功能迭代
本次提交包含多维度代码优化与功能增强:
1.  移除报告模块冗余导入与枚举,清理报表相关代码
2.  新增扫码登录支持方法与飞书适配器适配
3.  完善异常日志与健康检查信息
4.  扩展目录、配对管理、能力查询等接口
5.  优化出站管道与事务提交后钩子逻辑
6.  修复飞书消息解析与响应空值问题
7.  重构配置更新与服务账号创建逻辑
8.  统一传输错误分类契约与错误基类扩展
2026-07-06 20:49:35 +08:00

740 lines
64 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 渠道插件包
本目录存放各渠道插件实现。每个渠道插件以独立子目录形式装配,通过 `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](file:///d:/ForcePilot-v1.1/docs/vibe/v1.0/规范文档/演进式六边形-管道-插件架构规范.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](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/adapters/__init__.py),其余符号见 [contract/plugin/\_\_init\_\_.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/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](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/pipeline/inbound/media_fetch_stage.py) 据此剔除附件并降级。`LifecycleAdapter` 与 `LoginAdapter` 的全部方法均为必选(`raise NotImplementedError`),声明对应能力的插件 **必须** 实现全部方法。
### 1.4 资源访问约束INV-I7 / INV-6 / FR-32
插件 **不得** 直接访问以下宿主资源,**必须** 通过 [PluginHost](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/entry.py) 提供的端口获取方法与扩展点注册方法获取依赖:
| 禁止直接访问 | 替代方式 |
| --- | --- |
| 宿主 `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()` |
**端口访问前置声明(关键)**:插件在 `CHANNEL_ENTRY` 中调用任何 `getXxxPort()` **之前****必须** 先调用 `host.declareAccessiblePorts(...)` 声明将访问的端口名称元组。宿主在 `PluginHostImpl._checkPortAccess` 中校验每次端口获取是否在已声明集合内,未声明时抛 `PermissionDeniedError`
可声明的端口名称17 个,为 [PluginCapabilityChecker.ALLOWED_PORTS](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_capability_checker.py) 的子集):
| 类别 | 端口名称 |
| --- | --- |
| 基础被驱动端口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](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_host_impl.py) 支持 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](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/ports/driven/channel_context_provider_port.py)**为 Port 而非适配器 Protocol**,含 `buildAgentSystemPrompt` / `buildAgentContextNote` / `getChannelFormatSpec` 三个方法,支持 `agent_id` 关键字参数用于多 Agent 协作) |
| `lifecycle` | 列表追加 | `LifecycleAdapter`(见 [contract/plugin/adapters/lifecycle_adapter.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/adapters/lifecycle_adapter.py)AL-01 账户生命周期介入) |
| `login` | 列表追加 | `LoginAdapter`(见 [contract/plugin/adapters/login_adapter.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/adapters/login_adapter.py)QR-01 扫码登录) |
| `probeable` | 列表追加 | `ProbeableAdapter`(见 [contract/plugin/adapters/probeable_adapter.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/adapters/probeable_adapter.py)FR-35 主动探测,可选实现) |
| `content_moderation` | 列表追加 | `ContentModerationAdapter`(见 [contract/plugin/adapters/content_moderation_adapter.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/adapters/content_moderation_adapter.py)CR-01 内容预审核,可选实现) |
| `puller` | 列表追加 | `PullerAdapter`(见 [contract/plugin/adapters/puller_adapter.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/adapters/puller_adapter.py),传输引擎客户端型渠道轮询接入,可选实现,行为约束见 §8 |
| `stream_connector` | 列表追加 | `StreamConnectorAdapter`(见 [contract/plugin/adapters/stream_connector_adapter.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/adapters/stream_connector_adapter.py),传输引擎客户端型渠道长连接接入,可选实现,行为约束见 §8 |
| `attachment_upload` | 列表追加 | `AttachmentUploadAdapter`(见 [contract/plugin/adapters/attachment_upload_adapter.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/adapters/attachment_upload_adapter.py)MSG-ATTACH-UPLOAD 附件上传,可选实现) |
| `webhook_test` | 列表追加 | `WebhookTestAdapter`(见 [contract/plugin/adapters/webhook_test_adapter.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/adapters/webhook_test_adapter.py)WHK-TEST Webhook 测试事件发起,可选实现) |
未列出的 `adapter_type``RuleViolationError(rule="unsupported_adapter_type:<name>")`
> **传输引擎适配器说明**`puller` / `stream_connector` 由 [TransportManager](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/transport/manager.py) 在宿主启动时通过 `PluginRegistry.listPluginAdapters()` 收集(**仅取每个渠道的 `puller_adapters[0]` / `stream_connector_adapters[0]`**,不支持多适配器),驱动 per-account 传输任务生命周期。账号启用/禁用由 `ChannelAccountOnline` / `ChannelAccountOffline` 领域事件自动触发,插件 **不得** 在 `LifecycleAdapter` 中自管理 WS 连接或轮询任务(详见 §8 与 [channels-transport-engine-设计方案-v1.0.md](file:///d:/ForcePilot-v1.1/docs/vibe/v1.0/设计方案/channels-transport-engine-设计方案-v1.0.md))。
### 1.6 能力声明与校验
能力校验分两层,插件开发者需同时满足:
**加载期静态校验**(由 [PluginCapabilityChecker](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_capability_checker.py) 执行):
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` |
| `lifecycle` | `lifecycle_adapters` |
| `supports_qr_login` | `login_adapters` |
| `agent_collaboration` | `mention_adapters`(声明时 `MentionAdapter.classifyAgentMention` 必须可触发) |
| `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` 无专用适配器(由出站适配器承载),不在校验范围。`supports_credential_cloning` 无专用适配器,不在校验范围。`lifecycle`AL-01`supports_qr_login`QR-02由 [manifest\_loader.\_parse\_capabilities](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/manifest_loader.py) 从 `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 申请方式
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. 标准布局
每个渠道插件遵循以下目录结构:
```
plugins/
└── <channel_name>/ # 如 feishu/、dingtalk/、wecom/
├── __init__.py
├── manifest.json # 插件清单(元数据、能力声明、配置 schema
├── entry.py # CHANNEL_ENTRY 入口函数
├── lifecycle.py # LifecycleHookHandler 实现(如需响应生命周期)
├── adapters/ # 适配器实现
│ ├── __init__.py
│ ├── outbound_adapter.py
│ ├── streaming_adapter.py
│ └── ...
└── dtos/ # 渠道特有 DTO可选仅插件内部使用
└── __init__.py
```
**布局约束**
- 插件 **只能** 在自身 `plugins/<channel>/` 子目录内创建文件。
- 跨渠道共享的 DTO **必须** 通过契约层变更申请提升到 `yuxi.channels.contract.dtos.*`**不得** 在插件间互相 import。
- 渠道特有 DTO **不得** 泄漏到契约层,留在插件自身的 `dtos/` 目录内。
### 3.1 manifest.json schema
`manifest.json` 由宿主在 discovered 阶段解析为 [ChannelManifest](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/manifest.py)。字段如下:
| 字段 | 类型 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- | --- |
| `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](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/dtos/capability.py) 字段,共 22 个字段,未声明默认 `false``max_message_length` 默认 `4096` |
| `config_schema` | array | 是 | — | 配置项 schema每项含 `key` / `type` / `required` / `default` / `hot_reloadable` / `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` |
> **必填字段说明**`provides` / `lifecycle` / `compatibility` / `failure_policy` 在 `ChannelManifest` dataclass 中虽有默认值,但 [manifest_loader.load_manifest_from_dir](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/manifest_loader.py) 的 `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](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/manifest_loader.py) 解析 [ChannelCapabilities](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/dtos/capability.py) 的 **22 个字段**——13 个基础能力(`rich_message` / `streaming` / `typing_indicator` / `message_edit` / `message_recall` / `mention` / `command` / `directory` / `doctor` / `whitelist` / `supports_reaction` / `supports_pin` / `supports_card_update`、4 个媒体能力(`supports_image_inbound` / `supports_video_inbound` / `supports_image_outbound` / `supports_video_outbound`FR-43、1 个消息长度上限(`max_message_length`FR19-P0-5、4 个能力字段(`lifecycle` AL-01 / `supports_qr_login` QR-02 / `agent_collaboration` / `supports_credential_cloning`)。未声明的字段默认 `False` 或 `4096`。能力字段声明为 `True` 时,加载期由 [PluginCapabilityChecker.verifyCapabilityConsistency](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_capability_checker.py) 校验对应适配器列表非空(见 §1.6`supports_credential_cloning` 无专用适配器,不在校验范围。
---
## 4. 插件加载时序
依据 [演进式六边形-管道-插件架构规范.md](file:///d:/ForcePilot-v1.1/docs/vibe/v1.0/规范文档/演进式六边形-管道-插件架构规范.md) §9插件生命周期状态机见 [PluginStateMachine](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_state_machine.py)),共 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](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_lifecycle_manager.py) 编排,所有公开方法加 `asyncio.Lock` 串行化):
1. **discovered**:宿主扫描 `plugins/` 目录下每个子目录的 `manifest.json`,解析为 `ChannelManifest` 并注册到 `PluginRegistry`。无 `manifest.json` 的子目录被跳过;解析或注册冲突(`PluginAlreadyRegisteredError` / `ConflictError`)记录错误日志并跳过**该插件**不阻塞其他插件FR-21。发布 `PluginDiscovered` 事件。
2. **resolved**[PluginDependencyResolver](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_dependency_resolver.py) Kahn 拓扑排序(依赖者→被依赖者边,取反得到"被依赖者先加载"序),循环依赖抛 `RuleViolationError(rule="circular_dependency:...")`[PluginCapabilityChecker.check](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_capability_checker.py) 校验能力边界(`accessible_ports` / `injectable_pipelines` / `resource_quota`)。
3. **loaded**[PluginLoader](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_loader.py) 通过 `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` 注册能力声明。
4. **initialized**:取 `host.getLifecycleHandler()`,非 None 时调用 `onInit`(超时 60s
5. **started**:调用 `onStart`(超时 60s插件开始处理请求。成功后将 `manifest.version` 追加写入 `applied_migrations` 配置键ACCOUNT 作用域FR17-P0-3乐观并发控制标记配置已加载写入失败抛异常触发降级。发布 `PluginStarted` 事件。
6. **paused / resumed**:调用 `onPause` / `onResume`。`onPause` 超时 30s**`onResume` 实际使用 `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](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/core/service/degradation_manager.py) 触发优雅降级FR-36固定 60s 退避,最多 3 次重试),发布 `ChannelDegraded` 事件,`_releasePluginResources` 释放插件占用的扩展点注册与 host 实例。失败插件由后台任务(`reloadLoop`,默认 60s 间隔)按退避时间自动重载(`FAILED → RESOLVED → … → STARTED`)。
**宿主关停时的批量停止**INF-013 回滚专用):`stopAllStartedPlugins()` 遍历 `STARTED` + `PAUSED` 状态插件逐个 `stop`,单插件异常告警并继续,不阻塞其他插件关停。
**生命周期钩子完整列表**(见 [LifecycleHookHandler](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/lifecycle.py),全为 `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")`)。
> **`PluginHost` Protocol 当前未声明** `registerLifecycleHandler` / `getLifecycleHandler` 方法,但 [PluginHostImpl](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_host_impl.py) 已实现。插件运行时可通过鸭子类型调用;如需静态类型检查支持,走 §2 变更申请流程补充 Protocol 声明。
---
## 5. CHANNEL_ENTRY 契约
每个插件 **必须** 通过 `CHANNEL_ENTRY` 契约注册,**不得** 隐式全局注入FR-32
```python
# entry.py
from yuxi.channels.contract.plugin.entry import CHANNEL_ENTRY, PluginHost
from yuxi.channels.contract.plugin.manifest import (
ChannelManifest,
PluginManifest,
FailurePolicy,
ResourceQuota,
)
from yuxi.channels.contract.dtos.capability import ChannelCapabilities
from yuxi.channels.contract.dtos.channel import ChannelType
from yuxi.channels.contract.plugin.lifecycle import LifecycleHookHandler
from .adapters.outbound_adapter import FeishuOutboundAdapter
from .adapters.streaming_adapter import FeishuStreamingAdapter
from .lifecycle import FeishuLifecycleHandler
def channel_entry(host: PluginHost) -> PluginManifest:
"""飞书渠道插件入口。"""
# 1. 声明能力边界(必须在 getXxxPort 之前)
host.declareAccessiblePorts((
"ConfigPort",
"LoggerPort",
"PersistencePort",
"CachePort",
"ConversationPort",
))
host.declareInjectablePipelines(("inbound", "outbound"))
host.declareResourceQuota(ResourceQuota(max_cpu="20.0", max_connections=50))
# 2. 注册生命周期钩子onInit/onStart/onStop/onUnload
# registerLifecycleHandler 当前仅在 PluginHostImpl 实现,
# 尚未声明到 PluginHost Protocol运行时通过鸭子类型调用。
host.registerLifecycleHandler(FeishuLifecycleHandler())
# 3. 注册渠道适配器adapter_type 见 §1.5
host.registerAdapter("outbound", FeishuOutboundAdapter())
host.registerAdapter("streaming", FeishuStreamingAdapter())
# 4. 注册扩展点(按需)
# host.registerStageSlot(StageSlot(pipeline="outbound", anchor="after:format", stage=...))
# host.registerEventSubscription(EventSubscription(event_type="ConfigChanged", handler=...))
# host.registerConfigSource(ConfigSource(source_id="feishu_env", loader=...))
# host.registerMatchTier(MatchTier(name="...", priority=..., match_method="exact"))
# host.registerMatcher("custom_matcher", matcher_fn)
# host.registerCapabilityProver(prover)
# 5. 返回 PluginManifest
return PluginManifest(
manifest=ChannelManifest(
id="com.yuxi.channels.feishu",
name="飞书",
version="1.0.0",
channel_type=ChannelType.FEISHU,
provides=("feishu",),
entry_module="yuxi.channels.plugins.feishu.entry",
capabilities=ChannelCapabilities(
rich_message=True,
streaming=True,
typing_indicator=True,
),
config_schema=(),
lifecycle=("init", "start", "stop", "unload"),
compatibility={"min_host_version": "1.0.0"},
accessible_ports=(
"ConfigPort",
"LoggerPort",
"PersistencePort",
"CachePort",
"ConversationPort",
),
injectable_pipelines=("inbound", "outbound"),
failure_policy=FailurePolicy.DEGRADE,
),
adapters=("outbound", "streaming"),
)
CHANNEL_ENTRY = channel_entry
```
### 5.1 PluginHost Protocol 完整方法清单
[PluginHost](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/entry.py) Protocol`@runtime_checkable`)声明的方法(实现见 [PluginHostImpl](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_host_impl.py)
| 类别 | 方法 |
| --- | --- |
| 能力边界声明3 | `declareAccessiblePorts(ports: tuple[str, ...])` / `declareInjectablePipelines(pipelines: tuple[str, ...])` / `declareResourceQuota(quota: ResourceQuota)` |
| 端口获取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` |
> `registerLifecycleHandler` / `getLifecycleHandler` 已在 `PluginHostImpl` 实现但未声明到 Protocol见 §4 末尾说明)。
> **`MatchTier`** 定义在 `contract/dtos/route.py``name: str` / `priority: int` / `match_method: str` / `enabled: bool = True`。宿主在 [factory.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/infrastructure/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](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/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](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/pipeline/stage_slot_injector.py) 映射为 `FailureStrategy`(见 §7.1 |
### 6.1 管道阶段清单(合法锚点目标)
[StageSlotInjector.validateAnchors](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/pipeline/stage_slot_injector.py) 在宿主启动期校验所有槽位的锚点必须命中以下阶段 ID未命中抛 `RuleViolationError`
**Inbound 管道**13 阶段,见 [inbound_pipeline.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/pipeline/inbound/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](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/pipeline/outbound/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](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/pipeline/control_plane/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](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/pipeline/stage_slot_injector.py) = `{"command-check"}`),其余阶段 `replace``PermissionDeniedError`
- 同锚点同 priority 抛 `ConflictError(f"stage_slot:{key}:priority={slot.priority}")`
- 注入按 `priority` 升序串联(不抢占式覆盖)。
### 6.3 事件订阅与配置源
- `host.registerEventSubscription(subscription)` 直接委托 [EventBus.register](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/extension/event_bus.py)`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](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/manifest.py) | 插件隔离策略manifest / EventSubscription / ConfigSource / StageSlot 字段) | `DEGRADE` / `CIRCUIT_BREAK` / `ISOLATE` |
| `FailureStrategy` | [extension_point.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/extension_point.py) | 阶段执行失败策略(`Stage.failure` 字段) | `TERMINATE` / `SKIP` / `COMPENSATE` / `DEGRADE` |
[StageSlotInjector](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/pipeline/stage_slot_injector.py) 将插件 `StageSlot.failure_policy``FailurePolicy`)映射为阶段 `FailureStrategy`
| `FailurePolicy` | → `FailureStrategy` |
| --- | --- |
| `DEGRADE` | `DEGRADE``ctx.degraded = True`,继续下一阶段) |
| `CIRCUIT_BREAK` | `SKIP`(跳过当前阶段继续) |
| `ISOLATE` | `SKIP` |
### 7.2 统一错误类型层级
插件 **必须** 抛出 [contract/errors/](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/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](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/transport/manager.py) + [BaseTransportWorker](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/transport/base_worker.py) 驱动,插件 **不得** 自管理 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](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/transport/puller_worker.py)
-`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](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/transport/stream_worker.py)
-`adapter.connect(account_id, cancellation_token)` 建立长连接。
- 启动 `_heartbeatLoop`name `stream-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](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/policy/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](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/policy/config_schema.py) 的 `CONFIG_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.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。
- [ ] 在调用任何 `getXxxPort()` 之前已通过 `declareAccessiblePorts(...)` 声明端口。
- [ ] 所有被驱动依赖通过 `PluginHost.getXxxPort()` 获取。
- [ ] 未修改宿主中间件链、路由表、事件订阅器(仅通过 `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观测层降级必须有 `LoggerPort` warning。
- [ ] 未混淆 `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](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/entry.py) — `CHANNEL_ENTRY` / `PluginHost` / `PluginAdapter` / `Matcher`
- [manifest.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/manifest.py) — `PluginManifest` / `ChannelManifest` / `ResourceQuota` / `FailurePolicy` / `CredentialStrategy` / `CredentialType` / `AcquireMethod` / `PluginDependency`
- [capability.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/capability.py) — `CapabilityDeclaration` / `CapabilityProof` / `CapabilityProver`
- [lifecycle.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/lifecycle.py) — `LifecycleState`11 个)/ `LifecycleHook`8 个)/ `LifecycleHookHandler`
- [extension_point.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/extension_point.py) — `StageSlot` / `EventSubscription` / `ConfigSource` / `Stage` / `ConflictStrategy` / `FailureStrategy`
- [adapters/](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/adapters/) — 23 个适配器 Protocol15 个基础 + `LifecycleAdapter` + `LoginAdapter` + `ProbeableAdapter` + `ContentModerationAdapter` + `PullerAdapter` + `StreamConnectorAdapter` + `AttachmentUploadAdapter` + `WebhookTestAdapter`;已删除 `MediaAdapter` / `OAuthAdapter` 孤儿协议)
- [contract/plugin/\_\_init\_\_.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/__init__.py) — 契约层导出清单38 个符号manifest 9 + entry 2 + capability 3 + lifecycle 3 + extension_point 6 + adapters 23未导出 `PluginAdapter` / `Matcher`
- [contract/dtos/capability.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/dtos/capability.py) — `ChannelCapabilities`22 个字段)/ `CapabilityResult`
- [contract/dtos/channel.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/dtos/channel.py) — `ChannelType`8 个枚举值)/ `AccountStatus` / `OnboardingStatus` / `SessionStatus`
- [contract/dtos/route.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/dtos/route.py) — `MatchTier`
- [contract/ports/driven/aggregate.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/ports/driven/aggregate.py) — `DrivenAdapters` 聚合13 单实例 + 23 列表 = 36 字段)
- [contract/ports/driven/channel_context_provider_port.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/ports/driven/channel_context_provider_port.py) — `ChannelContextProviderPort`3 方法,支持 `agent_id`
- [contract/errors/](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/errors/) — 统一错误类型层级36 个导出符号,见 §7.2
- [contract/policy/config_schema.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/policy/config_schema.py) — `CONFIG_SCHEMA`50 字段)/ `HOT_RELOADABLE_KEYS` / `NON_HOT_RELOADABLE_KEYS`
**应用层实现**(仅供理解宿主行为,插件 **不得** import
- [plugin_host_impl.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_host_impl.py) — `PluginHost` 实现,含 `_ADAPTER_SETTERS` 分发表24 键)与 `_checkPortAccess` 校验
- [plugin_lifecycle_manager.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_lifecycle_manager.py) — 生命周期编排,含 `discover` / `load` / `reload` / `reloadLoop` / `stopAllStartedPlugins` / `reloadFailedPlugins`
- [plugin_state_machine.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_state_machine.py) — 11 状态转移表与钩子超时约束
- [plugin_capability_checker.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_capability_checker.py) — `ALLOWED_PORTS`17/ `ALLOWED_PIPELINES` / `CAPABILITY_ADAPTER_MAP` / 加载期校验
- [plugin_loader.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_loader.py) — `importlib` 动态导入 / `installFromSource`PLG-INSTALL`path`/ `uninstall`PLG-UNINSTALL-FILE
- [plugin_dependency_resolver.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_dependency_resolver.py) — Kahn 拓扑排序
- [stage_slot_injector.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/pipeline/stage_slot_injector.py) — 锚点校验 / `replace` 限制(仅 `command-check`/ `FailurePolicy→FailureStrategy` 映射
- [inbound_pipeline.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/pipeline/inbound/inbound_pipeline.py) — Inbound 管道13 阶段)
- [outbound_pipeline.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/pipeline/outbound/outbound_pipeline.py) — Outbound 管道14+2 阶段)
- [control_plane_pipeline.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/pipeline/control_plane/control_plane_pipeline.py) — Control-plane 管道5 阶段21 个 handler
- [transport/manager.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/transport/manager.py) — `TransportManager`(事件订阅 + per-account task
- [transport/base_worker.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/transport/base_worker.py) — `BaseTransportWorker`watchdog + 退避 + 熔断)
- [transport/puller_worker.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/transport/puller_worker.py) — `PullerWorker`at-least-once
- [transport/stream_worker.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/transport/stream_worker.py) — `StreamWorker`(心跳 + 重连)
- [event_bus.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/extension/event_bus.py) — `EventBus`5s handler 超时 + failure_policy 分流)
- [channel_probe.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/health/channel_probe.py) — `ChannelProbe` 主动探测器实现FR-3530s 超时)
- [health_aggregator.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/health/health_aggregator.py) — `HealthAggregator`10s 缓存 + 5s 检查超时)
- [degradation_manager.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/core/service/degradation_manager.py) — `DegradationManager`(固定 60s 退避,最多 3 次重试)
**基础设施层实现**(仅供理解宿主行为,插件 **不得** import
- [host_bootstrap.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/infrastructure/host_bootstrap.py) — 13 步启动序列(规范 §15.1 为 7 步,实际扩展 5 个后台任务启动 + `_loadConfig`/`_initSchema` 拆分)
- [host_shutdown.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/infrastructure/host_shutdown.py) — 7 步关停序列(规范 §15.2 含"注销驱动适配器",实际因驱动适配器机制未实现而省略,新增"停止传输引擎管理器"
- [factory.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/infrastructure/factory.py) — 组合根装配8 个默认 MatchTier / 内置事件订阅者 / 5 个 worker 进程依赖工厂)
- [dependency_injection.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/infrastructure/dependency_injection.py) — DI 容器(仅单例,未注册抛 `InternalError`
- [scheduler.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/infrastructure/scheduler.py) — 5 个调度 handlerFR-04/05/06/08/RPT-05
**架构规范**
- [演进式六边形-管道-插件架构规范.md](file:///d:/ForcePilot-v1.1/docs/vibe/v1.0/规范文档/演进式六边形-管道-插件架构规范.md) — §5 不变量INV-1 ~ INV-10硬/软分级、§9 插件规范、§10.2 事务策略、§12 并发与线程模型、§15 启动关停序列
- [channels-transport-engine-设计方案-v1.0.md](file:///d:/ForcePilot-v1.1/docs/vibe/v1.0/设计方案/channels-transport-engine-设计方案-v1.0.md) — 传输引擎设计微内核架构P0 仅 Puller
> **注**`docs/vibe/v1.0/规范文档/外部系统渠道开发规范.md` 是 `yuxi.external_systems` 模块(非 `channels` 模块)的规范,与本文档无直接同源关系,仅供六边形架构落地参考。