本次提交包含多类代码优化与修复: 1. 重命名适配器协议类:WebhookTestable→WebhookTestAdapter、AttachmentUploadable→AttachmentUploadAdapter,并同步更新所有引用 2. 为飞书/企业微信插件添加凭证克隆能力开关配置 3. 修复飞书入站适配器URL解析错误,使用hostname替代host属性 4. 新增批量发送消息DTO与已读状态DTO 5. 新增插件目录列表接口与插件能力校验规则 6. 修复权限阶段配置,添加analytics权限映射 7. 优化健康检查、限流模块的环境变量配置支持 8. 修复配对过期扫描器参数名不匹配问题 9. 优化日志调用、文档注释与代码可读性 10. 移除废弃的AuditExportTask聚合根与相关导入 |
||
|---|---|---|
| .. | ||
| feishu | ||
| wechat_ilink | ||
| __init__.py | ||
| README.md | ||
渠道插件包
本目录存放各渠道插件实现。每个渠道插件以独立子目录形式装配,通过 CHANNEL_ENTRY 契约注册到宿主。
最高原则:渠道开发 不得 污染框架层。框架层包括 yuxi.channels 包内除 plugins/ 之外的所有层(契约层、领域核心、应用服务、组合根、被驱动适配器)。若渠道开发需要框架层变更支持,必须 走 §2 的变更申请流程,不得 自行修改。
1. 边界红线:渠道开发不得污染框架层
1.1 框架层范围
| 层 | 路径 | 插件可执行操作 |
|---|---|---|
| 契约层 | yuxi.channels.contract.* |
只读 import(不得新增/修改文件) |
| 领域核心层 | yuxi.channels.core.* |
禁止 import |
| 应用服务层 | yuxi.channels.application.* |
禁止 import |
| 组合根层 | yuxi.channels.infrastructure.* |
禁止 import |
| 被驱动适配器层 | yuxi.channels.adapters.* |
禁止 import |
| 插件层 | yuxi.channels.plugins.<channel>.* |
可自由编写 |
契约层文件的增删改属于框架层变更,必须 由架构角色评审后实施,渠道开发者 不得 直接修改(见 §2)。
1.2 依赖方向铁律(INV-1 / INV-6)
依据 演进式六边形-管道-插件架构规范.md §5.1:
- 所有依赖 必须 指向契约方向(外 → 内),内圈 不得 引用外圈类型。
- 插件 不得 直接引用宿主内部实现,仅 依赖
yuxi.channels.contract.*。 - 跨边界共享的数据结构 必须 定义在契约层,由内外圈共同引用。
- 依赖方向 不得 因"便利"而绕过,任何绕过都 必须 在评审中记录理由。
1.3 插件可依赖的契约入口
插件 只能 import 以下契约层符号(适配器协议导出见 contract/plugin/adapters/__init__.py,其余符号见 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 / AttachmentUploadAdapter / WebhookTestAdapter) |
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 据此剔除附件并降级。LifecycleAdapter与LoginAdapter的全部方法均为必选(raise NotImplementedError),声明对应能力的插件 必须 实现全部方法。
1.4 资源访问约束(INV-I7 / INV-6 / FR-32)
插件 不得 直接访问以下宿主资源,必须 通过 PluginHost 提供的端口获取方法与扩展点注册方法获取依赖:
| 禁止直接访问 | 替代方式 |
|---|---|
宿主 settings |
host.getConfigPort() |
宿主 logger |
host.getLoggerPort() |
| 数据库连接池 | host.getPersistencePort() |
| Redis 客户端 | host.getCachePort() |
| 追踪器 | host.getTracerPort() |
| 队列 | host.getQueuePort() |
| 会话存储 | host.getConversationPort() |
| Agent 运行时 | host.getAgentRunPort() |
| 身份解析器 | host.getIdentityResolverPort() |
| 宿主中间件链 / 路由表 / 事件订阅器 | host.registerStageSlot() / host.registerEventSubscription() / host.registerConfigSource() / host.registerMatchTier() / host.registerMatcher() |
端口访问前置声明(关键):插件在 CHANNEL_ENTRY 中调用任何 getXxxPort() 之前,必须 先调用 host.declareAccessiblePorts(...) 声明将访问的端口名称元组。宿主在 PluginHostImpl._checkPortAccess 中校验每次端口获取是否在已声明集合内,未声明时抛 PermissionDeniedError。
可声明的端口名称(17 个,为 PluginCapabilityChecker.ALLOWED_PORTS 的子集):
| 类别 | 端口名称 |
|---|---|
| 基础被驱动端口(9 个) | ConfigPort / LoggerPort / PersistencePort / CachePort / TracerPort / QueuePort / ConversationPort / AgentRunPort / IdentityResolverPort |
仓储子端口(8 个,均由 PersistencePort 聚合实现) |
ChannelAccountRepositoryPort / ChannelSessionRepositoryPort / PairingRepositoryPort / AuditLogRepositoryPort / OutboxRepositoryPort / UserIdentityRepositoryPort / IdempotencyRepositoryPort / PersistenceHealthPort |
仓储子端口在运行时返回
DrivenAdapters.persistence聚合别名,该聚合同时实现全部 8 个细分子端口。IdentityResolverPort默认为None,由插件通过registerAdapter("identity_resolver", ...)注入后才可获取。
1.5 适配器注册(FR-32)
插件通过 host.registerAdapter(adapter_type, adapter) 注入渠道适配器。宿主 PluginHostImpl._ADAPTER_SETTERS 支持 24 种 adapter_type(1 个单实例 + 23 个列表追加,含 23 个适配器 Protocol + 1 个渠道上下文提供者 Port):
| adapter_type | 写入方式 | 对应 Protocol / Port |
|---|---|---|
identity_resolver |
单实例直接赋值 | IdentityResolverAdapter |
inbound |
列表追加 | InboundAdapter |
outbound |
列表追加 | OutboundAdapter |
status |
列表追加 | StatusAdapter |
rich_message |
列表追加 | RichMessageAdapter |
tools |
列表追加 | ToolsAdapter |
message_ops |
列表追加 | MessageOpsAdapter |
directory |
列表追加 | DirectoryAdapter |
whitelist |
列表追加 | WhitelistAdapter |
wizard |
列表追加 | WizardAdapter |
doctor |
列表追加 | DoctorAdapter |
command |
列表追加 | CommandAdapter |
mention |
列表追加 | MentionAdapter |
streaming |
列表追加 | StreamingAdapter |
session |
列表追加 | SessionAdapter |
channel_context_provider |
列表追加 | ChannelContextProviderPort(见 contract/ports/driven/channel_context_provider_port.py,为 Port 而非适配器 Protocol) |
lifecycle |
列表追加 | LifecycleAdapter(见 contract/plugin/adapters/lifecycle_adapter.py,AL-01 账户生命周期介入) |
login |
列表追加 | LoginAdapter(见 contract/plugin/adapters/login_adapter.py,QR-01 扫码登录) |
probeable |
列表追加 | ProbeableAdapter(见 contract/plugin/adapters/probeable_adapter.py,FR-35 主动探测,可选实现) |
content_moderation |
列表追加 | ContentModerationAdapter(见 contract/plugin/adapters/content_moderation_adapter.py,CR-01 内容预审核,可选实现) |
puller |
列表追加 | PullerAdapter(见 contract/plugin/adapters/puller_adapter.py,传输引擎客户端型渠道轮询接入,可选实现) |
stream_connector |
列表追加 | StreamConnectorAdapter(见 contract/plugin/adapters/stream_connector_adapter.py,传输引擎客户端型渠道长连接接入,可选实现) |
attachment_upload |
列表追加 | AttachmentUploadAdapter(见 contract/plugin/adapters/attachment_upload_adapter.py,MSG-ATTACH-UPLOAD 附件上传,可选实现) |
webhook_test |
列表追加 | WebhookTestAdapter(见 contract/plugin/adapters/webhook_test_adapter.py,WHK-TEST Webhook 测试事件发起,可选实现) |
未列出的 adapter_type 抛 RuleViolationError。
传输引擎适配器说明:
puller/stream_connector由 TransportManager 在宿主启动时通过PluginRegistry.listPluginAdapters()收集,驱动 per-account 传输任务生命周期。插件注册这两类适配器后,账号启用/禁用由ChannelAccountOnline/ChannelAccountOffline领域事件自动触发,插件 不得 在LifecycleAdapter中自管理 WS 连接或轮询任务(详见 channels-transport-engine-设计方案-v1.0.md)。
1.6 能力声明与校验
能力校验分两层,插件开发者需同时满足:
加载期静态校验(由 PluginCapabilityChecker 执行):
-
check(manifest):校验accessible_ports/injectable_pipelines/resource_quota.max_cpu(≤ 50.0%)是否在允许范围内,违规抛PermissionDeniedError/ValidationError。 -
verifyCapabilityConsistency(manifest, adapters):清单capabilities中声明为True的能力 必须 有对应的运行时适配器注册(FR-21 契约一致性)。映射关系:能力字段 要求非空的适配器列表 rich_messagerich_message_adaptersstreamingstreaming_adaptersmentionmention_adapterscommandcommand_adaptersdirectorydirectory_adaptersdoctordoctor_adapterswhitelistwhitelist_adapterslifecyclelifecycle_adapterssupports_qr_loginlogin_adaptersmessage_edit/message_recall/supports_reaction/supports_pin/supports_card_update(任一为 True)message_ops_adapterssupports_image_inbound/supports_video_inbound(任一为 True,FR-43)inbound_adapterssupports_image_outbound/supports_video_outbound(任一为 True,FR-43)outbound_adapterstyping_indicator无专用适配器(由出站适配器承载),不在校验范围。lifecycle(AL-01)与supports_qr_login(QR-02)由 PluginLifecycleManager._parse_capabilities 从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 申请方式
- 在
docs/vibe/v1.x/问题文档/渠道开发/下新建问题清单文档(命名YYYY-MM-DD-<channel>-前置问题清单.md),内容包括:- 缺口描述:缺少什么、为什么需要、当前无法实现的用例。
- 影响范围:契约层 / 组合根层 / 应用层,涉及的文件路径。
- 期望方案:建议的变更方案与替代方案。
- 紧急程度:P0(阻断渠道开发)/ P1(影响核心能力)/ P2(增强能力)。
- 提交架构评审,由架构角色确认方案后,由框架开发者实施 框架层变更。
- 变更完成后,渠道开发者同步切换到新契约,不得 在插件内保留对旧实现的绕过逻辑。
2.2 严禁的行为
- 在
plugins/<channel>/之外的目录直接修改代码以"临时"支持某渠道。 - 在插件内通过反射、monkey-patch、import private symbol(
_前缀)、TYPE_CHECKING之外的方式绕过契约边界。 - 在
yuxi.channels.core.*中 import 任何框架类型(违反 INV-2 核心纯净性)。 - 自行在契约层新增 DTO / Protocol 方法以"补全"渠道所需能力。
3. 标准布局
每个渠道插件遵循以下目录结构:
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。字段如下:
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
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 字段,未声明默认 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在ChannelManifestdataclass 中虽有默认值,但 PluginLifecycleManager._parse_manifest 的required_fields校验将它们标记为 JSON 必填,缺失时抛ValidationError。
entry_module说明:宿主通过importlib.import_module(manifest.entry_module)动态导入,必须为可 import 的完整模块路径。返回的PluginManifest.manifest.id必须 与manifest.json的id一致,否则抛ValidationError。
capabilities解析范围:PluginLifecycleManager._parse_capabilities 完整解析 ChannelCapabilities 的 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 个新增能力(lifecycleAL-01 /supports_qr_loginQR-02)。未声明的字段默认False或4096。能力字段声明为True时,加载期由 PluginCapabilityChecker.verifyCapabilityConsistency 校验对应适配器列表非空(见 §1.6)。
4. 插件加载时序
依据 演进式六边形-管道-插件架构规范.md §9,插件生命周期状态机(见 PluginStateMachine):
discovered → resolved → loaded → initialized → started ⇄ paused
↓ ↓
failed ←─────── (任一阶段失败)
↓
resolved (重载,FR-36)
started/paused → stopped → unloaded (终态)
完整加载流程(由 PluginLifecycleManager 编排):
- discovered:宿主扫描
plugins/目录下每个子目录的manifest.json,解析为ChannelManifest并注册到PluginRegistry。无manifest.json的子目录被跳过;解析或注册冲突记录错误日志并跳过该插件,不阻塞其他插件(FR-21)。 - resolved:PluginCapabilityChecker.check 校验能力边界(
accessible_ports/injectable_pipelines/resource_quota)。 - loaded:
importlib导入entry_module,调用CHANNEL_ENTRY(host)获取PluginManifest,校验清单 ID 一致性。插件在CHANNEL_ENTRY内通过host.registerAdapter注入适配器,通过host.registerStageSlot/registerEventSubscription/registerConfigSource/registerMatchTier/registerMatcher注入扩展点,通过host.registerLifecycleHandler注册生命周期钩子。随后verifyCapabilityConsistency校验声明能力与运行时适配器一致性(FR-21)。 - initialized:调用
LifecycleHookHandler.onInit(超时 60s)。 - started:调用
onStart(超时 60s),插件开始处理请求。成功后将manifest.version追加写入applied_migrations配置键(ACCOUNT 作用域,FR17-P0-3),标记配置已加载;写入失败抛异常触发降级。 - paused / resumed:调用
onPause/onResume(超时 30s)。 - stopped:调用
onStop(超时 30s,必须等待在途请求完成)。 - unloaded:调用
onUnload(超时 30s,必须释放所有资源),注销扩展点注册,从PluginRegistry注销。 - failed(任一阶段失败):标记
FAILED状态,发布PluginFailed事件,调用onFail钩子(超时 30s),触发 DegradationManager 优雅降级(FR-36),发布ChannelDegraded事件,释放插件占用的扩展点注册与 host 实例。失败插件由后台任务(reloadLoop,默认 60s 间隔)按退避时间自动重载(FAILED → RESOLVED → … → STARTED)。
生命周期钩子完整列表(见 LifecycleHookHandler):
| 钩子方法 | 触发阶段 | 超时 | 用途 |
|---|---|---|---|
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):
# 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 说明:
PluginHostProtocol(entry.py)当前未声明registerLifecycleHandler/getLifecycleHandler方法,但 PluginHostImpl 已实现。插件运行时可通过鸭子类型调用;如需静态类型检查支持,走 §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);观测层降级必须有
LoggerPortwarning。
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 —
CHANNEL_ENTRY/PluginHost - manifest.py —
PluginManifest/ChannelManifest/ResourceQuota/FailurePolicy - capability.py —
CapabilityDeclaration/CapabilityProof/CapabilityProver - lifecycle.py —
LifecycleState/LifecycleHook/LifecycleHookHandler - extension_point.py —
StageSlot/EventSubscription/ConfigSource/Stage/ConflictStrategy/FailureStrategy - adapters/ — 23 个适配器 Protocol(15 个基础 +
LifecycleAdapter+LoginAdapter+ProbeableAdapter+ContentModerationAdapter+PullerAdapter+StreamConnectorAdapter+AttachmentUploadAdapter+WebhookTestAdapter;已删除MediaAdapter/OAuthAdapter孤儿协议) - contract/plugin/__init__.py — 契约层导出清单(entry/manifest/capability/lifecycle/extension_point 全量 + 适配器 23 个全量导出)
应用层实现(仅供理解宿主行为,插件 不得 import):
- plugin_host_impl.py —
PluginHost实现,含_ADAPTER_SETTERS分发表与_checkPortAccess校验 - plugin_lifecycle_manager.py — 生命周期编排,含
discover/load/reload/reloadLoop - plugin_state_machine.py — 状态转移表与钩子超时约束
- plugin_capability_checker.py — 加载期能力边界校验与契约一致性校验
- plugin_loader.py —
importlib动态导入与CHANNEL_ENTRY调用 - channel_probe.py —
ChannelProbe主动探测器实现(FR-35;ProbeableAdapterProtocol 已提升到契约层)
架构规范:
- 演进式六边形-管道-插件架构规范.md — §5 不变量(INV-1 / INV-6 / INV-7 / INV-8)、§9 插件规范
- 外部系统渠道开发规范.md — 同源参考实现(协议适配器 / 认证插件 / 厂商集成包)