本次提交包含多维度代码优化与功能增强: 1. 移除报告模块冗余导入与枚举,清理报表相关代码 2. 新增扫码登录支持方法与飞书适配器适配 3. 完善异常日志与健康检查信息 4. 扩展目录、配对管理、能力查询等接口 5. 优化出站管道与事务提交后钩子逻辑 6. 修复飞书消息解析与响应空值问题 7. 重构配置更新与服务账号创建逻辑 8. 统一传输错误分类契约与错误基类扩展
740 lines
64 KiB
Markdown
740 lines
64 KiB
Markdown
# 渠道插件包
|
||
|
||
本目录存放各渠道插件实现。每个渠道插件以独立子目录形式装配,通过 `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`(任一为 True,FR-43) | `inbound_adapters` |
|
||
| `supports_image_outbound` / `supports_video_outbound`(任一为 True,FR-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` 钩子(超时 30s,onFail 自身失败仅告警不阻断降级流程),调用 [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 个内置 matcher(session_key/identity_id/peer_id/account/default),其余 3 个层级(chat_type/channel_session/channel_type)暂未实现匹配逻辑,可由插件注入。
|
||
|
||
---
|
||
|
||
## 6. 管道阶段与扩展点注入
|
||
|
||
插件通过 `host.registerStageSlot(slot: StageSlot)` 向管道注入自定义阶段。`StageSlot` 字段(见 [extension_point.py](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 被禁止。
|
||
|
||
- 可热更新 key(38 个,如 `dm_policy` / `allow_from` / `rate_limit` / `bot_loop_budget` / `rich_message_enabled` / `streaming_enabled` / `ack_policy` / `identity_confidence_threshold` / `channel_access_default_level` / `credential_failure_threshold` 等):通过 `ConfigPort` 热更新,触发 `ConfigChanged` 事件。
|
||
- 不可热更新 key(12 个,如 `webhook_port` / `webhook_secret` / `tls_cert` / `plugin_entry` / `database_url` / `redis_url` / `credentials` / `login_status` / `credential_version` 等):修改需重启进程。
|
||
- `transport.*` 5 个 key 见 §8.5。
|
||
|
||
插件自身的渠道业务配置(如飞书 app_id、企业微信 corp_id)通过 `manifest.json` 的 `config_schema` 声明,由 `ConfigPort` 在 ACCOUNT 作用域读写。`ConfigField.required` 必须被消费(`HostBootstrap` 启动期与 `ConfigManager` 校验,禁止装饰性死字段)。
|
||
|
||
`onReconfigure(config: dict)` 钩子接收热更新配置,**必须** 支持回滚(FR-37):配置应用失败时恢复至上一次有效配置,失败抛 `ConfigRollbackError`。
|
||
|
||
---
|
||
|
||
## 10. 禁止事项清单(Code Review Checklist)
|
||
|
||
提交渠道代码前,逐项确认:
|
||
|
||
### 10.1 边界
|
||
|
||
- [ ] 未修改 `plugins/<channel>/` 之外的任何文件。
|
||
- [ ] 未 import `yuxi.channels.core.*` / `yuxi.channels.application.*` / `yuxi.channels.infrastructure.*` / `yuxi.channels.adapters.*`。
|
||
- [ ] 未新增 / 修改 `yuxi.channels.contract.*` 中的任何文件(契约层变更走 §2 流程)。
|
||
- [ ] 未在插件间互相 import 内部实现(跨渠道共享走契约层提升)。
|
||
- [ ] 需要框架层支持时已走变更申请流程,未自行突破边界。
|
||
|
||
### 10.2 资源访问
|
||
|
||
- [ ] 未直接访问 `settings` / `logger` / DB pool / Redis client。
|
||
- [ ] 在调用任何 `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 个适配器 Protocol(15 个基础 + `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-35,30s 超时)
|
||
- [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 个调度 handler(FR-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` 模块)的规范,与本文档无直接同源关系,仅供六边形架构落地参考。
|