ForcePilot/backend/package/yuxi/channels/plugins/README.md

455 lines
35 KiB
Markdown
Raw Normal View History

# 渠道插件包
本目录存放各渠道插件实现。每个渠道插件以独立子目录形式装配,通过 `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) — 同源参考实现(协议适配器 / 认证插件 / 厂商集成包)