ForcePilot/backend/package/yuxi/channels/plugins/README.md
Kris b88c0ae29e feat(channels): 批量新增多渠道网关限界上下文基础代码与契约
新增完整的 channels 限界上下文模块,包含契约层、领域核心层、应用服务、管道编排、插件体系、基础设施组合根等全层级代码,新增飞书与微信 iLink 渠道插件基础结构,补充各类 DTO、端口协议与领域服务实现。
2026-07-02 03:22:12 +08:00

455 lines
35 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.*`
- 跨边界共享的数据结构 **必须** 定义在契约层,由内外圈共同引用。
- 依赖方向 **不得** 因"便利"而绕过,任何绕过都 **必须** 在评审中记录理由。
### 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` |
| `yuxi.channels.contract.plugin.manifest` | `PluginManifest` / `ChannelManifest` / `ResourceQuota` / `PluginDependency` / `ConfigField` / `FailurePolicy` |
| `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` / `AttachmentUploadable` / `WebhookTestable` |
| `yuxi.channels.contract.plugin.capability` | `CapabilityDeclaration` / `CapabilityProof` / `CapabilityProver` |
| `yuxi.channels.contract.plugin.lifecycle` | `LifecycleState` / `LifecycleHook` / `LifecycleHookHandler` |
| `yuxi.channels.contract.plugin.extension_point` | `StageSlot` / `EventSubscription` / `ConfigSource` / `Stage` / `ConflictStrategy` / `FailureStrategy` |
| `yuxi.channels.contract.dtos.*` | 跨边界共享 DTO |
| `yuxi.channels.contract.errors.*` | 统一错误类型层级 |
| `yuxi.channels.contract.ports.driven.*` / `yuxi.channels.contract.ports.driving.*` | 端口定义(仅供类型标注,实现由宿主注入) |
> **适配器协议方法风格**:所有适配器 `Protocol` 使用 `@runtime_checkable` 装饰,方法签名统一为 `async def`。方法体区分两种风格:必选方法为 `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()` |
| 宿主中间件链 / 路由表 / 事件订阅器 | `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", ...)` 注入后才可获取。
### 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** |
| `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),传输引擎客户端型渠道轮询接入,可选实现) |
| `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),传输引擎客户端型渠道长连接接入,可选实现) |
| `attachment_upload` | 列表追加 | `AttachmentUploadable`(见 [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` | 列表追加 | `WebhookTestable`(见 [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`
> **传输引擎适配器说明**`puller` / `stream_connector` 由 [TransportManager](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/transport/manager.py) 在宿主启动时通过 `PluginRegistry.listPluginAdapters()` 收集,驱动 per-account 传输任务生命周期。插件注册这两类适配器后,账号启用/禁用由 `ChannelAccountOnline` / `ChannelAccountOffline` 领域事件自动触发,插件 **不得** 在 `LifecycleAdapter` 中自管理 WS 连接或轮询任务(详见 [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 契约一致性)。映射关系:
| 能力字段 | 要求非空的适配器列表 |
| --- | --- |
| `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` |
| `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` 无专用适配器(由出站适配器承载),不在校验范围。`lifecycle`AL-01`supports_qr_login`QR-02由 [PluginLifecycleManager.\_parse\_capabilities](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_lifecycle_manager.py) 从 `manifest.json` 解析,声明为 `True` 时触发对应适配器列表非空校验。
**运行时能力证明**(由插件实现 `CapabilityProver`,出站中间件调用):
- `proveCapability(capability_name)` 返回 `CapabilityProof`,结果可缓存(默认 TTL 300s
- 静态声明支持但运行时证明不支持时,**必须** 记录告警日志并走降级路径FR-08 增强 / FR-21
### 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` |
| `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) 字段,未声明默认 `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` |
| `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 |
> **必填字段说明**`provides` / `lifecycle` / `compatibility` / `failure_policy` 在 `ChannelManifest` dataclass 中虽有默认值,但 [PluginLifecycleManager._parse_manifest](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_lifecycle_manager.py) 的 `required_fields` 校验将它们标记为 JSON 必填,缺失时抛 `ValidationError`。
> **`entry_module` 说明**:宿主通过 `importlib.import_module(manifest.entry_module)` 动态导入,必须为可 import 的完整模块路径。返回的 `PluginManifest.manifest.id` **必须** 与 `manifest.json` 的 `id` 一致,否则抛 `ValidationError`。
> **`capabilities` 解析范围**[PluginLifecycleManager.\_parse\_capabilities](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_lifecycle_manager.py) 完整解析 [ChannelCapabilities](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/dtos/capability.py) 的 20 个字段——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、2 个新增能力(`lifecycle` AL-01 / `supports_qr_login` QR-02。未声明的字段默认 `False` 或 `4096`。能力字段声明为 `True` 时,加载期由 [PluginCapabilityChecker.verifyCapabilityConsistency](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_capability_checker.py) 校验对应适配器列表非空(见 §1.6)。
---
## 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)
```
discovered → resolved → loaded → initialized → started ⇄ paused
↓ ↓
failed ←─────── (任一阶段失败)
resolved (重载FR-36)
started/paused → stopped → unloaded (终态)
```
**完整加载流程**(由 [PluginLifecycleManager](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_lifecycle_manager.py) 编排):
1. **discovered**:宿主扫描 `plugins/` 目录下每个子目录的 `manifest.json`,解析为 `ChannelManifest` 并注册到 `PluginRegistry`。无 `manifest.json` 的子目录被跳过;解析或注册冲突记录错误日志并跳过**该插件**不阻塞其他插件FR-21
2. **resolved**[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**`importlib` 导入 `entry_module`,调用 `CHANNEL_ENTRY(host)` 获取 `PluginManifest`,校验清单 ID 一致性。插件在 `CHANNEL_ENTRY` 内通过 `host.registerAdapter` 注入适配器,通过 `host.registerStageSlot` / `registerEventSubscription` / `registerConfigSource` / `registerMatchTier` / `registerMatcher` 注入扩展点,通过 `host.registerLifecycleHandler` 注册生命周期钩子。随后 `verifyCapabilityConsistency` 校验声明能力与运行时适配器一致性FR-21
4. **initialized**:调用 `LifecycleHookHandler.onInit`(超时 60s
5. **started**:调用 `onStart`(超时 60s插件开始处理请求。成功后将 `manifest.version` 追加写入 `applied_migrations` 配置键ACCOUNT 作用域FR17-P0-3标记配置已加载写入失败抛异常触发降级。
6. **paused / resumed**:调用 `onPause` / `onResume`(超时 30s
7. **stopped**:调用 `onStop`(超时 30s必须等待在途请求完成
8. **unloaded**:调用 `onUnload`(超时 30s必须释放所有资源注销扩展点注册`PluginRegistry` 注销。
9. **failed**(任一阶段失败):标记 `FAILED` 状态,发布 `PluginFailed` 事件,调用 `onFail` 钩子(超时 30s触发 [DegradationManager](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/core/service/degradation_manager.py) 优雅降级FR-36发布 `ChannelDegraded` 事件,释放插件占用的扩展点注册与 host 实例。失败插件由后台任务(`reloadLoop`,默认 60s 间隔)按退避时间自动重载(`FAILED → RESOLVED → … → STARTED`)。
**生命周期钩子完整列表**(见 [LifecycleHookHandler](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/lifecycle.py)
| 钩子方法 | 触发阶段 | 超时 | 用途 |
| --- | --- | --- | --- |
| `onInit` | initialized | 60s | 初始化资源(连接、缓存预热) |
| `onStart` | started | 60s | 开始处理请求 |
| `onPause` | paused | 30s | 暂停接收新请求 |
| `onResume` | started从 paused | 30s | 恢复接收请求 |
| `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 与扫描间隔一致作为崩溃恢复安全网。
**生命周期约束**
- 插件 **不得**`started` 之前对外提供服务。
- 插件 **必须** 支持 `stopped``unloaded` 的干净退出,**不得** 遗留线程、连接、临时资源。
- 插件失败 **不得** 拖垮宿主宿主负责按扩展点策略降级或熔断FR-36
- 生命周期钩子超时后标记插件 `FAILED` 并触发降级。
- `onReconfigure` **必须** 支持配置回滚FR-37配置应用失败时恢复至上一次有效配置。
---
## 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=...))
# 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
```
> **registerLifecycleHandler 说明**`PluginHost` Protocol[entry.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/entry.py))当前未声明 `registerLifecycleHandler` / `getLifecycleHandler` 方法,但 [PluginHostImpl](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_host_impl.py) 已实现。插件运行时可通过鸭子类型调用;如需静态类型检查支持,走 §2 变更申请流程补充 Protocol 声明。
---
## 6. 禁止事项清单Code Review Checklist
提交渠道代码前,逐项确认:
### 6.1 边界
- [ ] 未修改 `plugins/<channel>/` 之外的任何文件。
- [ ] 未 import `yuxi.channels.core.*` / `yuxi.channels.application.*` / `yuxi.channels.infrastructure.*` / `yuxi.channels.adapters.*`
- [ ] 未新增 / 修改 `yuxi.channels.contract.*` 中的任何文件(契约层变更走 §2 流程)。
- [ ] 未在插件间互相 import 内部实现(跨渠道共享走契约层提升)。
- [ ] 需要框架层支持时已走变更申请流程,未自行突破边界。
### 6.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%。
### 6.3 契约与异常
- [ ] 通过 `CHANNEL_ENTRY` 显式注册,未隐式全局注入。
- [ ] `manifest.json``entry_module` 为可 import 的完整模块路径,返回清单 ID 与声明 ID 一致。
- [ ] `manifest.json` 包含全部必填字段(`id` / `name` / `version` / `channel_type` / `entry_module` / `capabilities` / `config_schema` / `provides` / `lifecycle` / `compatibility` / `failure_policy`)。
- [ ] 清单 `capabilities` 声明为 `True` 的能力均有对应运行时适配器注册FR-21
- [ ] 声明 `lifecycle: true` 时已注册 `LifecycleAdapter`,声明 `supports_qr_login: true` 时已注册 `LoginAdapter`(见 §1.6)。
- [ ] 注册 `ProbeableAdapter` 时已实现 `async def probe(self) -> AdapterProbeOutcome`(从 `yuxi.channels.contract.plugin.adapters` 导入FR-35
- [ ] 适配器只做协议转换与错误翻译不承载业务规则INV-8
- [ ] 原生异常已转换为 `yuxi.channels.contract.errors.*` 统一异常。
- [ ] 未用 try/except 静默吞错INV-7观测层降级必须有 `LoggerPort` warning。
### 6.4 生命周期
- [ ] 通过 `host.registerLifecycleHandler(handler)` 注册了 `LifecycleHookHandler`(如需响应生命周期)。
- [ ] 未在 `started` 之前对外提供服务。
- [ ] `stopped``unloaded` 干净退出,无遗留线程 / 连接 / 临时资源。
- [ ] `onInit` / `onStart` 在 60s 内完成,`onPause` / `onResume` / `onStop` / `onUnload` / `onFail` 在 30s 内完成。
- [ ] `onReconfigure`如实现支持配置回滚FR-37
- [ ] `onFail`(如实现)不抛异常,失败清理逻辑不得阻断降级流程。
---
## 7. 参考
**契约层源码**
- [entry.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/entry.py) — `CHANNEL_ENTRY` / `PluginHost`
- [manifest.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/manifest.py) — `PluginManifest` / `ChannelManifest` / `ResourceQuota` / `FailurePolicy`
- [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` / `LifecycleHook` / `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` + `AttachmentUploadable` + `WebhookTestable`;已删除 `MediaAdapter` / `OAuthAdapter` 孤儿协议)
- [contract/plugin/\_\_init\_\_.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/contract/plugin/__init__.py) — 契约层导出清单entry/manifest/capability/lifecycle/extension_point 全量 + 适配器 23 个全量导出)
**应用层实现**(仅供理解宿主行为,插件 **不得** import
- [plugin_host_impl.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_host_impl.py) — `PluginHost` 实现,含 `_ADAPTER_SETTERS` 分发表与 `_checkPortAccess` 校验
- [plugin_lifecycle_manager.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_lifecycle_manager.py) — 生命周期编排,含 `discover` / `load` / `reload` / `reloadLoop`
- [plugin_state_machine.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_state_machine.py) — 状态转移表与钩子超时约束
- [plugin_capability_checker.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_capability_checker.py) — 加载期能力边界校验与契约一致性校验
- [plugin_loader.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/lifecycle/plugin_loader.py) — `importlib` 动态导入与 `CHANNEL_ENTRY` 调用
- [channel_probe.py](file:///d:/ForcePilot-v1.1/backend/package/yuxi/channels/application/health/channel_probe.py) — `ChannelProbe` 主动探测器实现FR-35`ProbeableAdapter` Protocol 已提升到契约层)
**架构规范**
- [演进式六边形-管道-插件架构规范.md](file:///d:/ForcePilot-v1.1/docs/vibe/v1.0/规范文档/演进式六边形-管道-插件架构规范.md) — §5 不变量INV-1 / INV-6 / INV-7 / INV-8、§9 插件规范
- [外部系统渠道开发规范.md](file:///d:/ForcePilot-v1.1/docs/vibe/v1.0/规范文档/外部系统渠道开发规范.md) — 同源参考实现(协议适配器 / 认证插件 / 厂商集成包)