From 4847813ca7ce55eabfe2358a744ad9d813ca0210 Mon Sep 17 00:00:00 2001 From: InfinityPacer Date: Wed, 30 Sep 2026 15:18:53 +0800 Subject: [PATCH] feat(sdk): expose Feishu event long connection to plugins Co-Authored-By: Claude Opus 5.5 --- .../network/feishu.py} | 4 +- app/modules/feishu/feishu.py | 2 +- app/sdk/feishu.py | 5 ++ docs/architecture-overview.md | 4 +- docs/refactor/optimization-checklist.md | 2 +- docs/rules/05-architecture.md | 3 +- .../architecture/dependency-baseline.json | 48 ++++++++++--------- .../architecture/dependency-policy.json | 2 +- .../runtime-contract-baseline.json | 17 +++++++ tests/test_architecture_egress.py | 4 +- tests/test_feishu_longconn.py | 4 +- tests/test_feishu_ws_lifecycle.py | 2 +- tests/test_plugin_sdk.py | 10 ++++ 13 files changed, 73 insertions(+), 34 deletions(-) rename app/{modules/feishu/longconn.py => adapters/network/feishu.py} (98%) create mode 100644 app/sdk/feishu.py diff --git a/app/modules/feishu/longconn.py b/app/adapters/network/feishu.py similarity index 98% rename from app/modules/feishu/longconn.py rename to app/adapters/network/feishu.py index e7c373669..0f3a06016 100644 --- a/app/modules/feishu/longconn.py +++ b/app/adapters/network/feishu.py @@ -7,6 +7,8 @@ 整个连接在一个线程内同步运行:阻塞 recv 以心跳间隔为超时,超时即发送 ping,因此收发都在 同一线程,无需发送锁或事件循环。 + +宿主飞书模块与插件共用本实现,插件经 ``app.sdk.feishu`` 获取,不必再依赖 lark-oapi。 """ import base64 @@ -23,7 +25,7 @@ import websocket from app.adapters.network.http import RequestUtils from app.runtime.log import logger -# 本模块不引用同包的 openapi,避免子模块经父包形成导入环;域名默认值与其保持一致。 +# 适配层不反向引用飞书模块的 openapi;域名默认值与 app.modules.feishu.openapi 保持一致。 FEISHU_DOMAIN = "https://open.feishu.cn" # 事件报文与应答结果的 JSON 结构。 JsonDict = Dict[str, Any] diff --git a/app/modules/feishu/feishu.py b/app/modules/feishu/feishu.py index 61a2e7a6d..67602629c 100644 --- a/app/modules/feishu/feishu.py +++ b/app/modules/feishu/feishu.py @@ -7,12 +7,12 @@ from pathlib import Path from typing import Any, Callable, Dict, List, Optional, Tuple, Union from urllib.parse import urlparse +from app.adapters.network.feishu import FeishuLongConnection from app.adapters.network.http import RequestUtils from app.application.messaging.channel.admin import matches_channel_admin from app.application.messaging.ingress import submit_message_to_host from app.application.security.user import get_configured_user_channel_lookup from app.domain.context import Context, MediaInfo -from app.modules.feishu.longconn import FeishuLongConnection from app.modules.feishu.openapi import FEISHU_DOMAIN, FeishuOpenApi, JsonDict from app.runtime.log import logger from app.runtime.settings import get_runtime_setting diff --git a/app/sdk/feishu.py b/app/sdk/feishu.py new file mode 100644 index 000000000..61a3e0041 --- /dev/null +++ b/app/sdk/feishu.py @@ -0,0 +1,5 @@ +"""插件可依赖的飞书事件长连接接口,与宿主飞书模块共用同一传输实现。""" + +from app.adapters.network.feishu import FEISHU_DOMAIN, FatalConnectionError, FeishuLongConnection + +__all__ = ["FEISHU_DOMAIN", "FatalConnectionError", "FeishuLongConnection"] diff --git a/docs/architecture-overview.md b/docs/architecture-overview.md index 8e3dc7fe7..0cff78f5e 100644 --- a/docs/architecture-overview.md +++ b/docs/architecture-overview.md @@ -763,7 +763,7 @@ flowchart LR | 指标 | 当前值 | |---|---:| | Python 模块 | 1060 | -| 内部导入边 | 9,057 | +| 内部导入边 | 9,060 | | 非平凡 SCC | 1(精确 containment 的 TMDB 移植包环) | | Application / Chain 具体 Adapter 直连 | 0 / 0 | | Direct egress | 48(债务已清零,48 条精确 containment) | @@ -773,7 +773,7 @@ flowchart LR | Model/Oper 自动事务与自建 Session | 0 | | 组合根外 `SystemConfigOper()` | 0 | -整理失败反馈由 `app.application.transfer.feedback` 集中投影;已整理下载的站点字幕复用 `app.chain.transfer` 自动整理队列;网络连通性检测按 `app.application.nettest` 归档,并覆盖 HTTP 与 WebSocket 探测。未启用智能助手的消息音乐交互由 `app.chain.music_interaction` 包中的解析展示与订阅协调模块处理;音乐标签准入与批次共识由 `app.chain.transfer.music` 负责,预览证据由 `app.application.transfer.projection` 只读投影;宿主依赖边现为 9,057,音乐交互包未新增 SCC,插件一次性任务 owner `app.scheduler.oncejob` 带来 1 个模块和 9 条依赖边。Agent 持久回执新增 Application 端口及 DB Model/Oper/Adapter 四个冷导入模块。缓存组合根只在启动时选用 Redis 缓存才导入 Redis 适配器,依赖 langchain_core 的对话记忆模型归属 `app.agent.memory`,文件缓存且未启用智能体的实例不再加载 redis 与 langchain_core。Telegram 渠道改用模块内 `app.modules.telegram.botapi` 精简 Bot API 客户端,经 `RequestUtils` 出站,宿主不再导入 pyTelegramBotAPI,启用 Telegram 的实例也不会因该 SDK 加载 redis;该依赖暂时保留给插件使用。Web Push 协议(aes128gcm 加密、VAPID 签名与经 `RequestUtils` 投递)由网络适配器 `app.adapters.network.webpush` 直接基于 http-ece 与 py-vapid 实现,消息端点与 `WebPushModule` 共用,宿主不再导入 pywebpush 及其连带的 aiohttp;该依赖同样暂时保留给插件使用。飞书渠道改用模块内 `app.modules.feishu.openapi` OpenAPI 客户端(经 `RequestUtils` 出站)与 `app.modules.feishu.longconn` 事件长连接(websocket-client 同步收发 pbbp2 帧),宿主不再导入 lark-oapi 及其上万个生成模块,长连接也不再依赖 SDK 的模块级事件循环;lark-oapi 暂时保留给插件使用。当前 `app.startup.lifecycle` 为 576、`app.factory` 为 588、`app.main` 为 590。音乐调用观察 `app.application.music.observation` 新增一个轻量模块,记录来源结果及请求预算;AcoustID及路径识别复用此上下文传递有界指纹候选,保留旧单ID模块合同。站点图片域名快照新增 `app.application.security.image`,各启动入口增加一个模块;性能基线只同步模块数量,原有耗时预算、历史采样和生命周期资源约束保持有效。 +整理失败反馈由 `app.application.transfer.feedback` 集中投影;已整理下载的站点字幕复用 `app.chain.transfer` 自动整理队列;网络连通性检测按 `app.application.nettest` 归档,并覆盖 HTTP 与 WebSocket 探测。未启用智能助手的消息音乐交互由 `app.chain.music_interaction` 包中的解析展示与订阅协调模块处理;音乐标签准入与批次共识由 `app.chain.transfer.music` 负责,预览证据由 `app.application.transfer.projection` 只读投影;宿主依赖边现为 9,060,音乐交互包未新增 SCC,插件一次性任务 owner `app.scheduler.oncejob` 带来 1 个模块和 9 条依赖边。Agent 持久回执新增 Application 端口及 DB Model/Oper/Adapter 四个冷导入模块。缓存组合根只在启动时选用 Redis 缓存才导入 Redis 适配器,依赖 langchain_core 的对话记忆模型归属 `app.agent.memory`,文件缓存且未启用智能体的实例不再加载 redis 与 langchain_core。Telegram 渠道改用模块内 `app.modules.telegram.botapi` 精简 Bot API 客户端,经 `RequestUtils` 出站,宿主不再导入 pyTelegramBotAPI,启用 Telegram 的实例也不会因该 SDK 加载 redis;该依赖暂时保留给插件使用。Web Push 协议(aes128gcm 加密、VAPID 签名与经 `RequestUtils` 投递)由网络适配器 `app.adapters.network.webpush` 直接基于 http-ece 与 py-vapid 实现,消息端点与 `WebPushModule` 共用,宿主不再导入 pywebpush 及其连带的 aiohttp;该依赖同样暂时保留给插件使用。飞书渠道改用模块内 `app.modules.feishu.openapi` OpenAPI 客户端(经 `RequestUtils` 出站)与网络适配器 `app.adapters.network.feishu` 事件长连接(websocket-client 同步收发 pbbp2 帧),宿主不再导入 lark-oapi 及其上万个生成模块,长连接也不再依赖 SDK 的模块级事件循环;插件经 `app.sdk.feishu` 复用同一长连接,lark-oapi 暂时保留给尚未迁移的插件使用。当前 `app.startup.lifecycle` 为 576、`app.factory` 为 588、`app.main` 为 590。音乐调用观察 `app.application.music.observation` 新增一个轻量模块,记录来源结果及请求预算;AcoustID及路径识别复用此上下文传递有界指纹候选,保留旧单ID模块合同。站点图片域名快照新增 `app.application.security.image`,各启动入口增加一个模块;性能基线只同步模块数量,原有耗时预算、历史采样和生命周期资源约束保持有效。 架构专项验证分为两个 CI 投影:`Check event semantic policy` 先运行依赖、Adapter、出口和 Event 语义门禁,`Check host architecture snapshot` 再执行快照测试及一次 diff --git a/docs/refactor/optimization-checklist.md b/docs/refactor/optimization-checklist.md index 2b17b3c2e..e1d7dbdc7 100644 --- a/docs/refactor/optimization-checklist.md +++ b/docs/refactor/optimization-checklist.md @@ -96,7 +96,7 @@ ARCH-201 至 ARCH-204 均达到实现、验证、提交、推送和远端门禁 | 指标 | 当前值 | 解释 | |---|---:|---| -| 宿主 Python 模块 / 内部依赖边 | 1060 / 9,057 | `dependency-baseline.json` 当前快照;系统设置合同按 `app.application.settings` 包组织,系统设置 API 端点独立按 `app.api.endpoints.settings` 归档;消息音乐交互由 `app.chain.music_interaction` 包中的音乐解析/展示与订阅协调模块处理;已整理下载的站点字幕经 `app.chain.transfer` 自动整理队列完成命名与归档;网络连通性检测按 `app.application.nettest` 归档 | +| 宿主 Python 模块 / 内部依赖边 | 1061 / 9,060 | `dependency-baseline.json` 当前快照;系统设置合同按 `app.application.settings` 包组织,系统设置 API 端点独立按 `app.api.endpoints.settings` 归档;消息音乐交互由 `app.chain.music_interaction` 包中的音乐解析/展示与订阅协调模块处理;已整理下载的站点字幕经 `app.chain.transfer` 自动整理队列完成命名与归档;网络连通性检测按 `app.application.nettest` 归档 | | 非平凡 SCC | 1 | 仅保留精确 containment 的 29 模块 TMDB 移植包环 | | 跨层 DB 边界债务 | 0 | Application、Chain、API、Agent、Runtime、Workflow 到 DB 的受控债务均为零 | | Model/Oper 事务债务 | 0 | 自建 Session、自动事务装饰器、直接 commit/rollback 等基线均为零 | diff --git a/docs/rules/05-architecture.md b/docs/rules/05-architecture.md index dcba7ee6f..17efb276e 100644 --- a/docs/rules/05-architecture.md +++ b/docs/rules/05-architecture.md @@ -400,6 +400,7 @@ API 中允许丢失或可重建的进程内任务必须登记到 `app/runtime/ta 进入 Outbox 或持久任务表,不能把 TaskRegistry 当成 durable queue。 Runtime 关闭后不可逆;完整应用生命周期的再次启动必须由新进程承载,不能在同一解释器中重建局部资源域。 插件需要浏览器时使用 `app.sdk.browser`,由宿主浏览器适配器协调资源,不直接依赖资源实现。 +插件需要飞书事件长连接时使用 `app.sdk.feishu.FeishuLongConnection`,与宿主飞书模块共用同一传输实现,不再各自依赖 lark-oapi。 旧插件若直接导入有资源前置条件的第三方包,compat 在插件 import 前递归扫描源码并保守准备资源; 无法精确解析的文件按全部已登记资源降级,最终可导入性仍由 Python loader 判断。 @@ -1214,7 +1215,7 @@ driven workflow registration. | `app/application/security/url.py` | URL/path validation, SSRF protection and signed image policy | | `app/application/mediaserver.py` | Configured media-server discovery and identity matching | | `app/runtime/compat/manifest.py` | Exact legacy-to-canonical import manifest | -| `app/sdk/` | Stable plugin imports, including provider-neutral browser launch functions | +| `app/sdk/` | Stable plugin imports, including provider-neutral browser launch functions and the Feishu event long connection | | `app/sdk/plugin/` | Plugin contract base class (`base.py`) and runtime manager facade (`manager.py`); the package root is a lazy export map so importing the contract does not load the managers | Run `tests/test_architecture_dependencies.py` after every ownership or import diff --git a/tests/fixtures/architecture/dependency-baseline.json b/tests/fixtures/architecture/dependency-baseline.json index 13383d68a..ea2b1cc7b 100644 --- a/tests/fixtures/architecture/dependency-baseline.json +++ b/tests/fixtures/architecture/dependency-baseline.json @@ -162,6 +162,18 @@ "doh_query_json|call:urlopen" ] }, + { + "bindings": [ + "import:websocket as websocket" + ], + "fingerprint": "95aca65eed288e0c7654ab49aad6c535d9313d5e38c44b1cb1768dcdfc772453", + "kind": "raw_transport", + "source": "app.adapters.network.feishu", + "target": "websocket", + "uses": [ + "FeishuLongConnection._run_once|call:create_connection" + ] + }, { "bindings": [ "import:httpx as httpx" @@ -520,18 +532,6 @@ "target": "requests", "uses": [] }, - { - "bindings": [ - "import:websocket as websocket" - ], - "fingerprint": "8ccfc3b86ad029942cdf0cab9d01fa2e1498576ee59e0df7239e1a744b370eae", - "kind": "raw_transport", - "source": "app.modules.feishu.longconn", - "target": "websocket", - "uses": [ - "FeishuLongConnection._run_once|call:create_connection" - ] - }, { "bindings": [ "from:smbclient.ClientConfig as ClientConfig", @@ -930,8 +930,8 @@ "runtime_only": true } }, - "edge_count": 9057, - "edge_sha256": "81d2d96d462b2069f5e01f6ca7f58ee2d377cb91be21c1341a57805c78204e7c", + "edge_count": 9060, + "edge_sha256": "cb948f057f3a8075ddf419370773b879f73aa13f6a13b1c0d9690f8c49f7f8a8", "edges": [ "app -> app.foundation", "app -> app.foundation.environment", @@ -1041,6 +1041,11 @@ "app.adapters.network.doh -> app.runtime.log", "app.adapters.network.doh -> app.runtime.reload", "app.adapters.network.doh -> app.runtime.settings", + "app.adapters.network.feishu -> app.adapters", + "app.adapters.network.feishu -> app.adapters.network", + "app.adapters.network.feishu -> app.adapters.network.http", + "app.adapters.network.feishu -> app.runtime", + "app.adapters.network.feishu -> app.runtime.log", "app.adapters.network.http -> app.runtime", "app.adapters.network.http -> app.runtime.correlation", "app.adapters.network.webpush -> app.adapters", @@ -6777,6 +6782,7 @@ "app.modules.fanart -> app.schemas.types", "app.modules.feishu.feishu -> app.adapters", "app.modules.feishu.feishu -> app.adapters.network", + "app.modules.feishu.feishu -> app.adapters.network.feishu", "app.modules.feishu.feishu -> app.adapters.network.http", "app.modules.feishu.feishu -> app.application", "app.modules.feishu.feishu -> app.application.messaging", @@ -6789,7 +6795,6 @@ "app.modules.feishu.feishu -> app.domain.context", "app.modules.feishu.feishu -> app.modules", "app.modules.feishu.feishu -> app.modules.feishu", - "app.modules.feishu.feishu -> app.modules.feishu.longconn", "app.modules.feishu.feishu -> app.modules.feishu.openapi", "app.modules.feishu.feishu -> app.runtime", "app.modules.feishu.feishu -> app.runtime.log", @@ -6798,11 +6803,6 @@ "app.modules.feishu.feishu -> app.schemas", "app.modules.feishu.feishu -> app.schemas.message", "app.modules.feishu.feishu -> app.schemas.types", - "app.modules.feishu.longconn -> app.adapters", - "app.modules.feishu.longconn -> app.adapters.network", - "app.modules.feishu.longconn -> app.adapters.network.http", - "app.modules.feishu.longconn -> app.runtime", - "app.modules.feishu.longconn -> app.runtime.log", "app.modules.feishu.module -> app.application", "app.modules.feishu.module -> app.application.messaging", "app.modules.feishu.module -> app.application.messaging.channel", @@ -8956,6 +8956,9 @@ "app.sdk.events -> app.runtime.events", "app.sdk.events -> app.schemas", "app.sdk.events -> app.schemas.event", + "app.sdk.feishu -> app.adapters", + "app.sdk.feishu -> app.adapters.network", + "app.sdk.feishu -> app.adapters.network.feishu", "app.sdk.logging -> app.runtime", "app.sdk.logging -> app.runtime.log", "app.sdk.media -> app.domain", @@ -9991,7 +9994,7 @@ "app.workflow.actions.transfer_file -> app.workflow", "app.workflow.actions.transfer_file -> app.workflow.actions" ], - "module_count": 1060, + "module_count": 1061, "modules": [ "app", "app.adapters", @@ -10012,6 +10015,7 @@ "app.adapters.network.browser", "app.adapters.network.cloudflare", "app.adapters.network.doh", + "app.adapters.network.feishu", "app.adapters.network.http", "app.adapters.network.ip", "app.adapters.network.resolver", @@ -10673,7 +10677,6 @@ "app.modules.fanart", "app.modules.feishu", "app.modules.feishu.feishu", - "app.modules.feishu.longconn", "app.modules.feishu.module", "app.modules.feishu.openapi", "app.modules.filemanager", @@ -10980,6 +10983,7 @@ "app.sdk.config", "app.sdk.database", "app.sdk.events", + "app.sdk.feishu", "app.sdk.logging", "app.sdk.media", "app.sdk.network", diff --git a/tests/fixtures/architecture/dependency-policy.json b/tests/fixtures/architecture/dependency-policy.json index 896054cac..12568342c 100644 --- a/tests/fixtures/architecture/dependency-policy.json +++ b/tests/fixtures/architecture/dependency-policy.json @@ -155,7 +155,7 @@ "reason": "产品长连接与 WSS 连通探测由协议适配器精确持有,SCGI/XML-RPC transport 由对应模块持有。", "facts": [ {"source": "app.startup.composition.network", "target": "websocket", "kind": "raw_transport", "fingerprint": "09e1909ac750fe40b145dde8fee52eedf84aaf2123f6a1bc38109252c89d6000"}, - {"source": "app.modules.feishu.longconn", "target": "websocket", "kind": "raw_transport", "fingerprint": "8ccfc3b86ad029942cdf0cab9d01fa2e1498576ee59e0df7239e1a744b370eae"}, + {"source": "app.adapters.network.feishu", "target": "websocket", "kind": "raw_transport", "fingerprint": "95aca65eed288e0c7654ab49aad6c535d9313d5e38c44b1cb1768dcdfc772453"}, {"source": "app.modules.qqbot.gateway", "target": "websocket", "kind": "raw_transport", "fingerprint": "6f6b7d61f3a95e620e67c450d544f0aa077f087a188d98f32e4501d94c6b37ae"}, {"source": "app.modules.rtorrent.rtorrent", "target": "socket.create_connection", "kind": "protocol_operation", "fingerprint": "878114d6b1bda091bd3b3d8aa819831d84381682feba78267592d2593295af90"}, {"source": "app.modules.rtorrent.rtorrent", "target": "xmlrpc.client.ServerProxy", "kind": "protocol_operation", "fingerprint": "2ac64b5670c930bc28bb2135cc4b73891c1cae01a74a9586f74e302878c45c85"}, diff --git a/tests/fixtures/architecture/runtime-contract-baseline.json b/tests/fixtures/architecture/runtime-contract-baseline.json index 756e53441..0655b522c 100644 --- a/tests/fixtures/architecture/runtime-contract-baseline.json +++ b/tests/fixtures/architecture/runtime-contract-baseline.json @@ -9985,6 +9985,23 @@ "target": "app.runtime.event.snapshot.snapshot_event_data" } ], + "app.sdk.feishu": [ + { + "kind": "import", + "name": "FEISHU_DOMAIN", + "target": "app.adapters.network.feishu.FEISHU_DOMAIN" + }, + { + "kind": "import", + "name": "FatalConnectionError", + "target": "app.adapters.network.feishu.FatalConnectionError" + }, + { + "kind": "import", + "name": "FeishuLongConnection", + "target": "app.adapters.network.feishu.FeishuLongConnection" + } + ], "app.sdk.logging": [ { "kind": "import", diff --git a/tests/test_architecture_egress.py b/tests/test_architecture_egress.py index ca947efe3..e20e9325b 100644 --- a/tests/test_architecture_egress.py +++ b/tests/test_architecture_egress.py @@ -49,7 +49,7 @@ FROZEN_EGRESS_EDGES_BY_REASON = { }, "streaming_protocol": { ("app.startup.composition.network", "websocket"), - ("app.modules.feishu.longconn", "websocket"), + ("app.adapters.network.feishu", "websocket"), ("app.modules.qqbot.gateway", "websocket"), ("app.modules.rtorrent.rtorrent", "socket.create_connection"), ("app.modules.rtorrent.rtorrent", "xmlrpc.client.ServerProxy"), @@ -113,7 +113,7 @@ FROZEN_EGRESS_FINGERPRINT_BY_EDGE = { ("app.doctor.checks", "urllib.request"): "d8dd2279263ef58c3b4c59c70451533a4fc27d2ccd76960ca3aff29cd73f3354", ("app.modules.discord.discord", "discord"): "e1486525fedbfe574aa47d60b483426c8c48e4e27fa35dbe34f6cd2f47d37c86", ("app.modules.emby.emby", "requests"): "6c4c17fe170226ea1272f5859bc119c2775442cabe8ce2a9edcabaf9c37bec31", - ("app.modules.feishu.longconn", "websocket"): "8ccfc3b86ad029942cdf0cab9d01fa2e1498576ee59e0df7239e1a744b370eae", + ("app.adapters.network.feishu", "websocket"): "95aca65eed288e0c7654ab49aad6c535d9313d5e38c44b1cb1768dcdfc772453", ("app.modules.filemanager.storages.smb", "smbclient"): "37619f050b03598fa559c6ae2df94bda6adf7cffd1cc42d9db9059f1fb41af47", ("app.modules.filemanager.storages.smb", "smbprotocol"): "f02686afd99c59820dffa7b4c2627a0be1ad62980c98a9cd33b7564697842b22", ("app.modules.filemanager.storages.u115", "oss2"): "f7b89c8ae6dad2603f0a9e0caaa159769aef5b3581d7e728f62445b979366eae", diff --git a/tests/test_feishu_longconn.py b/tests/test_feishu_longconn.py index 05f9139d2..7e861dfa7 100644 --- a/tests/test_feishu_longconn.py +++ b/tests/test_feishu_longconn.py @@ -10,8 +10,8 @@ import pytest import websocket from websockets.sync.server import serve -from app.modules.feishu import longconn -from app.modules.feishu.longconn import FatalConnectionError, FeishuLongConnection, Frame +from app.adapters.network import feishu as longconn +from app.adapters.network.feishu import FatalConnectionError, FeishuLongConnection, Frame # 以下字节由 lark-oapi 1.6 的 pbbp2_pb2.Frame.SerializeToString() 生成,作为协议对照样本。 SDK_PING_FRAME = bytes.fromhex("08001000180720002a0c0a0474797065120470696e67") diff --git a/tests/test_feishu_ws_lifecycle.py b/tests/test_feishu_ws_lifecycle.py index 8d0547f75..f61d25dfb 100644 --- a/tests/test_feishu_ws_lifecycle.py +++ b/tests/test_feishu_ws_lifecycle.py @@ -11,8 +11,8 @@ ensure_optional_stub("psutil") ensure_optional_stub("dateparser") ensure_optional_stub("Pinyin2Hanzi", is_pinyin=lambda value: False) +from app.adapters.network.feishu import FeishuLongConnection # noqa: E402 from app.modules.feishu.feishu import Feishu # noqa: E402 -from app.modules.feishu.longconn import FeishuLongConnection # noqa: E402 from app.modules.feishu.openapi import FeishuOpenApi # noqa: E402 # 停止单个配置实例的耗时上限;远小于 Feishu._ws_join_timeout_seconds,证明停止不是靠等待超时。 diff --git a/tests/test_plugin_sdk.py b/tests/test_plugin_sdk.py index 8a70dae5d..83ee72fa7 100644 --- a/tests/test_plugin_sdk.py +++ b/tests/test_plugin_sdk.py @@ -160,6 +160,16 @@ for name in ( assert result.returncode == 0, result.stderr +def test_feishu_sdk_exports_host_long_connection(): + """飞书 SDK 复用宿主传输实现,插件与宿主飞书模块是同一个类。""" + from app.adapters.network import feishu as feishu_adapter + from app.sdk import feishu as feishu_sdk + + assert feishu_sdk.FeishuLongConnection is feishu_adapter.FeishuLongConnection + assert feishu_sdk.FatalConnectionError is feishu_adapter.FatalConnectionError + assert feishu_sdk.FEISHU_DOMAIN == "https://open.feishu.cn" + + def test_browser_sdk_delegates_sync_and_async_launch(monkeypatch): """SDK 只转发浏览器参数,不复制宿主生命周期实现。""" from app.sdk import browser as browser_sdk