mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-03 22:04:57 +08:00
871 lines
53 KiB
Markdown
871 lines
53 KiB
Markdown
# MoviePilot v3 后端架构总览
|
||
|
||
> 本文面向开发人员,以图文结合的方式说明 MoviePilot v3 的整体架构:分层结构、各核心包的职责、
|
||
> 模块间调用关系、典型业务流程与启动生命周期。
|
||
>
|
||
> 规范性约束以 [`docs/rules/05-architecture.md`](rules/05-architecture.md) 与
|
||
> [`docs/rules/04-design-patterns.md`](rules/04-design-patterns.md) 为准,本文与其保持一致;
|
||
> 如出现差异,以规则文档为准。
|
||
>
|
||
> *最后更新:2026-10-19*
|
||
|
||
---
|
||
|
||
## 一、系统概述
|
||
|
||
MoviePilot 覆盖影视与音乐媒体的自动化管理,实现**订阅、搜索、下载、文件整理、元数据刮削、
|
||
媒体库刷新与消息通知**的全流程自动化。后端基于 FastAPI,前端基于 Vue 3(独立仓库),
|
||
同时对外提供 REST API、MCP 工具调用端点(`/api/v1/mcp`)与 OpenAI / Anthropic 兼容协议端点,
|
||
内置 AI Agent 支持自然语言操控系统。
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
subgraph 外部["外部入口"]
|
||
Web["Vue 3 前端"]
|
||
Msg["消息渠道<br/>Telegram / WeChat / Slack ..."]
|
||
ExtAgent["外部智能体<br/>MCP / Skills"]
|
||
end
|
||
|
||
subgraph 后端["MoviePilot 后端(FastAPI)"]
|
||
API["REST API / MCP / 兼容协议"]
|
||
Core["核心引擎<br/>Chain / Application / Module / Plugin / Agent"]
|
||
Persist["持久化端口 / Oper"]
|
||
end
|
||
|
||
subgraph 外部服务["外部生态"]
|
||
Site["PT 站点 / 索引器"]
|
||
DL["下载器<br/>qBittorrent / Transmission ..."]
|
||
MS["媒体服务器<br/>Emby / Plex / Jellyfin ..."]
|
||
Meta["元数据源<br/>TMDB / Douban / Bangumi ..."]
|
||
LLM["LLM 提供商"]
|
||
end
|
||
|
||
DB[("PostgreSQL / SQLite<br/>+ Alembic 迁移")]
|
||
|
||
Web -->|HTTP| API
|
||
Msg -->|Webhook / 轮询| Core
|
||
ExtAgent -->|JSON-RPC| API
|
||
API --> Core
|
||
Core -->|应用端口 / DB Adapter| Persist
|
||
Persist --> DB
|
||
Core <--> Site
|
||
Core <--> DL
|
||
Core <--> MS
|
||
Core <--> Meta
|
||
Core <--> LLM
|
||
```
|
||
|
||
---
|
||
|
||
## 二、整体分层架构
|
||
|
||
MoviePilot v3 以单向依赖的模块化单体为目标。历史包 `app/core`、`app/helper`、`app/utils`
|
||
已不再存在物理目录,仅作为旧插件的**虚拟兼容导入根**保留。核心边界已有机器门禁,
|
||
完整宿主强连通分量(SCC)、Adapter 直连、网络出口和事件合同均有事实快照与独立语义门禁,
|
||
具体检查入口见第十章。
|
||
|
||
下图同时表达职责关系与运行时调用方向,不等同于 Python 静态 import 图;通过 Port 注入的调用
|
||
会与具体 Adapter 的静态依赖方向相反。
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph 入口层["入口层 Entrypoints"]
|
||
ApiPkg["app/api<br/>REST / MCP 端点"]
|
||
AgentPkg["app/agent<br/>AI Agent 运行时"]
|
||
Monitor["app/monitor<br/>目录监控"]
|
||
Workflow["app/workflow<br/>工作流"]
|
||
Scheduler["app/scheduler/<br/>定时任务职责包"]
|
||
CLI["app/cli.py<br/>命令行"]
|
||
PluginPkg["插件运行时目录<br/>app/plugins/*(副本/覆盖层)"]
|
||
end
|
||
|
||
subgraph 编排层["编排层"]
|
||
Chain["app/chain<br/>Chain 用例编排"]
|
||
App["app/application<br/>聚焦应用服务"]
|
||
end
|
||
|
||
subgraph 能力层["能力层"]
|
||
Modules["app/modules<br/>可插拔后端模块"]
|
||
Db["app/db<br/>模型 + Oper 数据访问"]
|
||
Schemas["app/schemas<br/>传输模型与枚举"]
|
||
end
|
||
|
||
subgraph 契约层["契约层"]
|
||
Domain["app/domain<br/>纯业务语义"]
|
||
Runtime["app/runtime<br/>进程级运行时契约"]
|
||
end
|
||
|
||
subgraph 底层["底层机制"]
|
||
Foundation["app/foundation<br/>无状态原语"]
|
||
Adapters["app/adapters<br/>技术 I/O 适配"]
|
||
end
|
||
|
||
subgraph 组合根["组合根 / 边界"]
|
||
Startup["app/startup<br/>Composition Root"]
|
||
Sdk["app/sdk<br/>插件稳定导入面"]
|
||
Compat["app/runtime/compat<br/>旧导入路径与符号映射"]
|
||
end
|
||
|
||
ApiPkg --> Chain
|
||
AgentPkg --> App
|
||
Monitor --> Chain
|
||
Workflow --> Chain
|
||
Scheduler --> Chain
|
||
CLI --> Chain
|
||
PluginPkg --> Sdk
|
||
|
||
Chain -->|run_module / async_run_module 分发| Modules
|
||
Chain -->|Application service / 命名数据端口| App
|
||
App -->|运行时调用 Protocol / 持久化端口| Db
|
||
|
||
Modules --> Domain
|
||
App --> Domain
|
||
App --> Runtime
|
||
Chain --> Runtime
|
||
Domain --> Schemas
|
||
Domain --> Foundation
|
||
Runtime --> Foundation
|
||
Adapters --> Domain
|
||
Adapters --> Foundation
|
||
Startup -->|构造并注入| Adapters
|
||
|
||
Startup -.注入/装配.-> Runtime
|
||
Startup -.注入/装配.-> App
|
||
Startup -.注入/装配.-> Modules
|
||
Sdk -.门面转发.-> App
|
||
Compat -.惰性映射.-> Sdk
|
||
Compat -.精确别名.-> App
|
||
Compat -.精确别名.-> Db
|
||
Compat -.精确别名.-> Foundation
|
||
Compat -.精确别名.-> Adapters
|
||
```
|
||
|
||
图中的 `Chain → Application` 与 `Application → Db` 是运行时调用关系,表示通过应用端口和组合根
|
||
注入的实现完成持久化;静态 import 方向是 `db.adapters → application Protocol`。
|
||
具体 DB Adapter 再使用 Oper;这不是允许在用例代码中直接创建数据库引擎或拼接 SQL。`compat` 也不是只面向 SDK 的转发层,
|
||
它按 `app/runtime/compat/manifest.py` 的白名单把已经删除的旧模块/符号精确映射到各自的 canonical
|
||
归属。`app/application/subscribe.py` 与 `app/application/plugins.py` 都是 V3 重构过程中新增、
|
||
未形成插件 ABI 的宿主内部聚合文件,主题实现收口后直接删除,不在 manifest 中制造新的兼容债务。
|
||
|
||
当前正式目标和已完成参考切片的持久化调用路径可概括为:
|
||
|
||
```text
|
||
入口(API / Agent / CLI / Scheduler / Workflow)
|
||
-> Chain 或 Application 用例
|
||
-> 命名 Port / Protocol
|
||
-> db/adapters 创建短生命周期 Session/UoW
|
||
-> db/oper 与 db/models
|
||
```
|
||
|
||
Chain 的持久化能力由 `startup` 构造 `ChainRuntimeContext` 并在 `ChainBase` 实例化时显式注入;
|
||
Agent manager、memory、tool 和 scheduler 共享同一个 `AgentDataContext`。原
|
||
`app/application/chain/data.py`、`app/application/agentdata.py` 的进程级 getter 已删除,且不进入
|
||
SDK/Compat。领域查询与写入继续使用 Application 所属的冻结 DTO/Port,具体实现只存在于
|
||
`db/adapters`,宿主生产代码不得恢复 raw Oper、`Any` factory 或数据服务 locator。
|
||
需要跨进程恢复或 commit 后可靠执行的
|
||
业务副作用进入 `app/application/outbox.py` 定义的 Outbox 端口,由
|
||
`app/db/adapters/outbox.py` 实现;`app/runtime/tasks.py` 的 TaskRegistry 只负责进程内任务所有权、
|
||
取消和有限等待,不承担 durable queue 语义。
|
||
|
||
**依赖方向**如下。宿主 Application/Chain 到具体 Adapter 的静态直连已清零;
|
||
持久化 Port 归用例所有,具体 DB Adapter 通过组合根注入。表中的规范、语义测试与生成快照
|
||
分别承担边界定义、违规检测和事实漂移检测,不能相互替代:
|
||
|
||
| 方向 | 状态 |
|
||
|---|---|
|
||
| 入口层 → Chain / Application / 注入 Port | 允许(按工作流复杂度选择;不得直接构造 Oper) |
|
||
| Chain → Module | 仅允许通过 `run_module` 方法名分发,禁止直接 import 模块内部 |
|
||
| Chain → Agent 实现 | 禁止;只能经 `app/application/agent.py` 门面 |
|
||
| API / Chain → Workflow 实现 | 禁止;通过 `app/application/workflow.py` 用例门面 |
|
||
| Application → Domain / Runtime / 注入的 Port | 允许;不得直接依赖具体 DB/Oper/Adapter |
|
||
| DB Adapter → Application 持久化 Protocol / Oper / UoW | 允许;这是依赖倒置的实现方向 |
|
||
| Module → Module / Chain | 禁止(跨模块编排一律进 Chain) |
|
||
| Adapter → Application / runtime.extensions / sdk / compat | 禁止 |
|
||
| Domain → Runtime / Adapter / Application / DB | 禁止 |
|
||
| Foundation → 任何其他 app 包 | 禁止 |
|
||
| 规范实现包 → sdk / compat | 禁止 |
|
||
| 任何形成模块级循环依赖的 import | 禁止(延迟导入也不被接受) |
|
||
|
||
---
|
||
|
||
## 三、核心包职责速查
|
||
|
||
| 包 | 职责 | 代表性文件 |
|
||
|---|---|---|
|
||
| `app/foundation/` | 无状态、无配置、无 I/O 的底层原语:反射/动态导入、加密、DOM、单例、文本、URL、版本比较 | `reflection.py`、`crypto.py`、`singleton.py` |
|
||
| `app/domain/` | 纯 MoviePilot 业务语义:媒体上下文、识别解析、站点状态解释、磁力语义、NFO 刮削、图片内容与格式归一 | `context.py`、`metainfo.py`、`meta/`、`scraper.py`、`artwork.py` |
|
||
| `app/runtime/` | 进程级运行机制:配置、进程拓扑、事件、完整日志、缓存契约与内存后端、运行依赖 profile 与原生载荷激活检测、任务所有权、执行/关联上下文、并发、调度、限流、本地化、GC、重启状态 | `config.py`、`events.py`、`event/`、`dependencies/`、`tasks.py`、`execution.py`、`correlation.py`、`log.py`、`cache.py` |
|
||
| `app/runtime/extensions/` | 模块 / 插件 / 配置化服务 / 托管资源的发现、注册与生命周期适配;管理器归入对应主题包,旧插件路径只由 Compat 精确映射 | `module/manager.py`、`plugin/manager.py`、`service.py` |
|
||
| `app/runtime/compat/` | 仅标准库的精确旧模块、包与符号导入路由;不是业务实现,也不是通用 re-export 层 | `manifest.py`、`imports.py` |
|
||
| `app/adapters/network/` | 通用 HTTP、浏览器、DNS、Cloudflare、IP 传输机制 | `http.py`、`browser.py` |
|
||
| `app/adapters/cache/` | Redis 与文件缓存的具体实现 | `backends.py`、`redis.py` |
|
||
| `app/adapters/system/` | OS/文件/进程/stdio/显示/包安装/Rust 加速适配 | `host.py`、`resource.py`、`fsproxy.py` |
|
||
| `app/adapters/external/` | 命名外部生态:插件市场、CookieCloud、OCR、IP 归属、MP Server、微信加密 | `market.py`、`server.py`、`wechat.py` |
|
||
| `app/adapters/web/` | Web 技术适配:动态插件路由注册、认证依赖和 OpenAPI 重建;不承载插件路由用例 | `plugin/routes.py` |
|
||
| `app/adapters/observability/` | 可选观测技术适配;核心层只依赖 `runtime/observability` 定义的窄端口 | `otel.py` |
|
||
| `app/application/` | 读取配置/持久化状态的聚焦应用服务:识别、过滤、通知、RSS、站点、下载器、媒体服务器、存储、整理规则、可靠副作用等;同一主题拆成子包 | `recognition.py`、`rules.py`、`rss.py`、`outbox.py`、`site/`、`subscription/`、`plugin/` |
|
||
| `app/application/chain/` | Chain 运行时上下文、跨领域数据端口和 durable event 命令;由组合根显式注入 Chain | `context.py`、`events.py` |
|
||
| `app/application/subscription/` | 订阅深度冻结 DTO、typed query/write/staging Repository,以及新增、查询、变更、删除、媒体身份与搜索用例 | `contract.py`、`write.py`、`mutation.py`、`delete.py`、`identity.py`、`search.py` |
|
||
| `app/application/plugin/` | 插件市场、安装、运行时端口、文件夹操作和动态路由用例;具体 FastAPI 路由适配器在 adapters 层 | `catalog.py`、`install.py`、`runtime.py`、`folders.py`、`routes.py` |
|
||
| `app/application/messaging/` | 渠道回环入口、消息渲染/路由、命令交互会话、插件按钮回调、Agent 消息桥接 | `ingress.py`、`message.py`、`router.py`、`agent.py` |
|
||
| `app/application/security/` | 认证、授权、Cookie、Passkey、OTP/二次认证、SSRF 与 URL/路径安全 | `auth.py`、`url.py`、`twofactor.py` |
|
||
| `app/chain/` | 跨入口复用的用例编排:订阅、搜索、下载、整理、媒体、消息等 Chain | `subscribe/`、`search/`、`download/`、`transfer/` |
|
||
| `app/modules/` | 可插拔后端:下载器、媒体服务器、元数据源、消息渠道、索引器、存储 | `qbittorrent/`、`emby/`、`telegram/`、`themoviedb/` |
|
||
| `app/db/` | SQLAlchemy 模型、表级 Oper、会话/UoW 与 Application 持久化适配器;Model 只接受显式 Session,不拥有事务提交 | `models/`、`oper/`、`adapters/`、`uow.py` |
|
||
| `app/schemas/` | Pydantic 传输模型、枚举(`ModuleType`、`EventType`、`SystemConfigKey` 等) | `types.py`、`context.py` |
|
||
| `app/api/` | FastAPI 主端点、鉴权依赖、统一 `Response` 响应封装;动态插件端点不走此统一包装 | `apiv1.py`、`endpoints/`、`response.py` |
|
||
| `app/agent/` | AI Agent:稳定 Manager 门面、会话/生命周期/后台任务 owner、单 Agent 编排器、运行时、工具、中间件、LLM、记忆、技能、策略 | `manager.py`、`session.py`、`lifecycle.py`、`tasks.py`、`orchestrator.py` |
|
||
| `app/startup/` | 唯一组合根:跨层装配、领域初始化、声明式生命周期排序与重启策略 | `composition/`、`initializers/`、`lifecycle/` |
|
||
| `app/sdk/` | 面向新插件的稳定导入面(网络、缓存、日志、浏览器等);`_legacy/` 只承载旧插件行为适配薄门面 | `network.py`、`browser.py`、`cache.py`、`_legacy/` |
|
||
| `app/monitor/` | 源目录监控 → 触发整理 | `watcher.py`、`dispatcher.py` |
|
||
| `app/workflow/` | 工作流执行引擎、动作与运行状态;跨入口消费者通过 Application 门面访问 | `__init__.py`、`actions/` |
|
||
| `app/scheduler/` | 定时任务目录、动态协调、执行和生命周期;业务 callable 由启动组合根注入 | `catalog.py`、`registry.py`、`execution.py`、`lifecycle.py` |
|
||
| `app/cli.py` | 本地命令入口:服务管理、安装更新、诊断和工具调用 | `cli.py` |
|
||
| `app/plugins/` | 插件运行时副本/覆盖目录,由插件管理器加载;不是官方插件源码或宿主架构实现,架构审计以插件仓库与宿主边界为准 | — |
|
||
|
||
`app/runtime/dependencies/` 是一个同名能力包:`profile.py` 只负责解释器 ABI 对应的依赖组,`native.py` 只负责已加载原生发行包的快照与变更检测。包根不重复导出实现,宿主直接导入职责子模块;旧的平级 `dependencies.py` 与 `native_dependencies.py` 已退出规范实现。
|
||
|
||
---
|
||
|
||
## 四、启动与生命周期
|
||
|
||
`app/startup/` 是全应用唯一的组合根:负责向低层注入依赖、编排初始化/关停顺序、决定重启策略。
|
||
低层模块不允许反向 import `startup`。
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Main as app/main.py
|
||
participant Factory as app/factory.py
|
||
participant Life as startup/lifecycle/__init__.py
|
||
participant Init as 各 initializer
|
||
participant FastAPI as FastAPI 主循环
|
||
|
||
Main->>Factory: 导入并创建 FastAPI 实例
|
||
Factory->>Factory: create_app():异常处理器 / CORS / 本地化中间件
|
||
Factory->>Init: register_api_app(app) 注入插件路由服务
|
||
Factory-->>Life: lifespan 绑定到 app
|
||
Main->>FastAPI: Server.run() 触发 lifespan 启动
|
||
|
||
Life->>Life: configure_cache_dependencies()<br/>(必须先于业务模块导入)
|
||
Life->>Life: 校验进程拓扑,登记主事件循环
|
||
Life->>Life: 创建文件日志 writer 与 TaskRegistry
|
||
Life->>Init: prepare_database() + revision/head 校验
|
||
Life->>Init: configure_default_user_agent(注入 UA)
|
||
Life->>Init: configure_domain_dependencies(领域层依赖注入)
|
||
Life->>Init: get_engine() / get_global_async_engine() 预热 + fail-fast
|
||
Life->>Init: check_connection_budget() 连接预算核算
|
||
Life->>Init: init_routers(app) 注册 API 路由
|
||
Life->>Init: init_modules()(app/startup/initializers/modules.py)发现并初始化模块,返回 HostRuntime
|
||
Life->>FastAPI: app.state.host_runtime = HostRuntime
|
||
Life->>Life: 登记社区勋章后台维护,装配插件服务
|
||
Life->>Life: 启动共享消息队列,恢复插件安装与备份
|
||
Life->>Init: init_plugins() / init_scheduler() / init_monitor()
|
||
Life->>Init: replay_pending_transfers()(登记整理恢复调度器)
|
||
Life->>Init: init_command() / init_workflow()
|
||
Life->>Life: 登记插件同步与启动收尾任务
|
||
Note over Life,FastAPI: database_ready 在数据库准备后发布
|
||
Life->>Life: 发布 lifecycle_ready
|
||
Life->>FastAPI: yield,交还控制权
|
||
Note over Life,FastAPI: 运行期……
|
||
FastAPI->>Life: 收到停止信号
|
||
Life->>Init: 按 stop_order 关停(与启动顺序独立)
|
||
Note over Life,Init: 先封口停止信号、Scheduler 与 TaskRegistry
|
||
Life->>Init: 收敛工作流/命令/监控、Agent/整理、事件与插件服务
|
||
Life->>Init: 事件屏障→插件→消息队列→模块→撤销注入端口
|
||
Life->>Life: 关闭共享异步 HTTP 连接池
|
||
Life->>Life: 关闭并释放当前 lifespan 的日志 writer
|
||
```
|
||
|
||
关键设计点:
|
||
|
||
- **缓存装配先于业务导入**:缓存装饰器会在业务模块 import 时创建后端,
|
||
因此 `configure_cache_dependencies()` 在 `startup/lifecycle/__init__.py` 的业务导入前执行。
|
||
- **Uvicorn 入口分流**:生产单 worker 使用带协作停止语义的 `MoviePilotServer`;开发 reload
|
||
和安全模式多 worker 使用 `app.factory:create_app` import string/factory,由 supervisor
|
||
创建应用实例。`app.factory:app` 继续保留给既有 ASGI supervisor 和测试使用。
|
||
- **数据库准备唯一入口**:建表、迁移、迁移前备份和 Alembic head 校验统一由 lifespan
|
||
最早的“数据库准备”组件执行,`app.main` 不再主动迁移。主程序、外部 supervisor、factory
|
||
和 TestClient 因而共享同一 fail-fast 语义。
|
||
- **引擎预热 fail-fast**:同步/异步数据库引擎在单线程期完成首次创建,
|
||
避免调度器放出大量线程后再创建引擎导致连接锁竞争。
|
||
- **类型化请求装配**:`startup/composition/context.py` 的 frozen slots `HostRuntime` 是 lifespan 内唯一宿主
|
||
上下文,`api/context.py` 从 `app.state` 收窄到具体领域能力。认证、消息、历史、媒体服务器、站点、
|
||
订阅、工作流和请求事务均使用命名 runtime 字段,不再通过字符串仓储键定位;API、Scheduler、Chain
|
||
从 `HostRuntime.configuration` 获取 frozen 配置快照。系统设置管理 API 通过
|
||
`HostRuntime.settings` 的窄服务读写可变部署设置,业务域不接触 Settings 实例;生产与测试组合根统一
|
||
复用 `startup/composition/configuration.py` 的映射、快照加载与发布。`startup/composition/database.py`
|
||
持有数据库 worker、兼容事务 runner、查询与插件持久化;`startup/composition/runtime.py` 是
|
||
`HostRuntime`、全部命名领域 Runtime 和旧 `ApiDataPorts` 投影的唯一构造/发布 owner。它先构造
|
||
一份 frozen `RuntimeDependencies`,Agent、Chain 与 HostRuntime 复用同一仓储、transfer execution
|
||
ledger 和消息对象,导入阶段不启动数据库 worker 或消息队列;initializer 只保留顺序调用。
|
||
`startup/composition/chain.py` 集中构造无参 Chain 兼容上下文及其持久化、事件、
|
||
Outbox、消息和模块分发依赖,并延迟绑定旧 Transfer command 与壁纸 Chain,避免组合根导入环。
|
||
`startup/composition/security.py` 统一装配认证、用户查询、PassKey 与 Web 访问
|
||
provider,并把持久化工厂交给 runtime owner 投影为 `AuthenticationRuntime`;
|
||
`startup/composition/network.py` 统一装配网络应用端口;`startup/composition/agent.py` 构造并发布
|
||
唯一的 Agent 数据、会话持久化和自主任务对象,由 runtime owner 投影 `AgentChatRuntime`,
|
||
`HostRuntime`、工具管理器与 Scheduler 复用同一对象身份。`ApiDataPorts` 严格从同一 HostRuntime
|
||
字段投影,仅保留旧导入 ABI,
|
||
不参与正式请求链路。
|
||
- **安全模式**:`MOVIEPILOT_SAFE_MODE` 会跳过插件、定时器、监控器、命令与工作流,用于故障自救。
|
||
- **Scheduler 同名职责包**:旧 `app/scheduler.py` 单体已退役;`catalog.py` 负责作业目录和计划投影,
|
||
`execution.py`、`bridge.py`、`progress.py` 分别负责执行、跨循环句柄和进度终态,`registry.py`
|
||
唯一持有 generation、active generation、reservation 与 handle,`reconcile.py` 和 `lifecycle.py`
|
||
分别负责动态任务协调与启动/重载/关闭,`oncejob.py` 承接插件一次性任务:插件经
|
||
`app.sdk.scheduler.add_plugin_once_job` 在宿主调度器追加延迟单次执行,不再自建常驻
|
||
`BackgroundScheduler`;追加不重建周期服务,服务重建只丢弃已被重载替换实例登记的任务。
|
||
- **Scheduler 显式装配**:`startup/initializers/scheduler.py` 构造业务 Chain 一次,将绑定 callable
|
||
组成 frozen `SchedulerServices` 后注入 Scheduler;Scheduler 包内不再构造业务 Chain。
|
||
`app.scheduler` 包根只惰性保留 `Scheduler`/`SchedulerChain` 旧 ABI,新插件经 `app.sdk.scheduler`
|
||
使用窄调度服务,内部 owner 不重复导出。
|
||
- **进程拓扑**:全功能 V3 强制 `API_WORKERS=1`,避免每个 worker 重复启动插件和后台控制面;安全模式可临时使用多 worker 诊断,但不是正式扩容方案。
|
||
- **健康语义**:`/health/live` 只确认进程和事件循环可响应;`/health/ready` 仅在数据库
|
||
到达当前 head 且生命周期完成后返回 200,启动失败或关停阶段返回 503。两者不公开路径、
|
||
revision、插件和异常详情,深入诊断继续使用 Doctor。
|
||
- **声明式生命周期**:`lifecycle/components.py` 定义组件合同,`lifecycle/__init__.py` 的
|
||
`build_lifecycle_components()` 装配回调、依赖、启用模式及独立的 `start_order` / `stop_order`。
|
||
启动失败只清理已触达和部分启动的资源;关停按 owner 的预算等待,关键 `FAIL_FAST` owner
|
||
未收敛时停止释放后续依赖,保留句柄供重试,避免任务仍在运行而资源已被销毁。
|
||
|
||
---
|
||
|
||
## 五、核心设计模式
|
||
|
||
### 5.1 Module 模式:可插拔后端
|
||
|
||
`app/modules/` 下每个目录是一个可插拔后端(下载器、媒体服务器、消息渠道、元数据源、索引器、
|
||
存储等),均继承 `_ModuleBase` 并实现统一契约:
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
subgraph 契约["_ModuleBase 契约"]
|
||
A["get_name / get_type / get_subtype"]
|
||
B["init_setting():返回控制开关的配置项"]
|
||
C["init_module():初始化"]
|
||
D["test():连通性测试"]
|
||
E["stop():停止"]
|
||
end
|
||
|
||
MM["ModuleManager<br/>(发现 + 注册 + 生命周期)"]
|
||
MM -->|扫描 app/modules/* 一级子包| M1["qbittorrent"]
|
||
MM --> M2["transmission"]
|
||
MM --> M3["emby / plex / jellyfin"]
|
||
MM --> M4["telegram / wechat / slack ..."]
|
||
MM --> M5["themoviedb / douban / bangumi ..."]
|
||
MM --> M6["indexer / subtitle / filter ..."]
|
||
```
|
||
|
||
- 模块由 `runtime/extensions/module/manager.py` 发现并管理生命周期;
|
||
`app/modules/_base/` 承载各模块族的共享模板基类(下载器、媒体服务器、消息渠道)。
|
||
- 模块开关由 `init_setting()` 声明的配置项决定(如 `DOWNLOADER = "qbittorrent"`)。
|
||
- **模块之间、模块到 Chain 的直接依赖被禁止**,跨模块编排一律由 Chain 完成。
|
||
- 渠道/存储的管理操作遵循统一契约:`channel_manage(channel, action, **params)` /
|
||
`storage_manage(storage, action, **params)`,结果统一为 `{"success", "message", "data"}` 形态,
|
||
Chain 与端点只做透传。
|
||
|
||
### 5.2 Chain 模式:用例编排
|
||
|
||
`app/chain/` 承载被 API、CLI、Agent、调度器、Webhook 等多入口共享的业务用例。
|
||
所有 Chain 继承 `ChainBase`,Chain 访问模块**只能通过方法名分发**:
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
Entry["API / CLI / Agent / 调度器 / Webhook"]
|
||
C["SearchChain / SubscribeChain /<br/>DownloadChain / TransferChain ..."]
|
||
Base["ChainBase<br/>run_module / async_run_module"]
|
||
MM["ModuleManager 分发"]
|
||
P["插件模块(同名方法优先)"]
|
||
M1["模块 A"]
|
||
M2["模块 B"]
|
||
|
||
Entry --> C --> Base
|
||
Base -->|先查询插件实现| P
|
||
Base --> MM
|
||
MM --> M1
|
||
MM --> M2
|
||
```
|
||
|
||
- `run_module("method_name", **kwargs)` 与异步入口按契约分发:首个非空、列表/映射合并、
|
||
管道传递或广播等规则由具体方法决定;插件同名实现优先,保留既有短路与返回语义。
|
||
- `runtime/extensions/module/contracts.py` 为宿主已观察到的方法提供显式参数与返回合同;兼容期只诊断
|
||
旧插件签名差异,不改变插件优先级、短路和自由返回语义。
|
||
- 大型稳定 Chain 使用同名目录包治理:包根只保留稳定公开类,Facade 只组合职责 owner;
|
||
`TransferChain`、`SubscribeChain`、`DownloadChain` 与 `SearchChain` 的旧单体文件均不得复活或以
|
||
`source.py` 留存。`SearchChain` 的 provider 列表和流式入口共享同一批次事实,搜索状态仍由
|
||
`application/search/state.py` 唯一拥有。
|
||
- `app/chain/` 中下划线前缀文件(`_recognition.py`、`_messaging.py`、`_interaction.py`、
|
||
`_music.py`、`_transfer.py`)是 `ChainBase` 的功能域 Mixin,不是独立 Chain。
|
||
- 需要斜杠命令交互的 Chain 继承 `InteractionChainMixin`,只实现 `_interaction_handler`。
|
||
|
||
### 5.3 Event 模式:跨切面事件
|
||
|
||
`EventManager`(`app/runtime/events.py`)提供进程级事件总线,用于解耦跨切面反应
|
||
(整理完成后刷新媒体库、配置变更后重载模块、消息分发等):
|
||
|
||
宿主内建 `EventType` / `ChainEventType` 均在事件注册表中绑定 typed payload。开放插件事件允许
|
||
额外字段,校验只生成诊断;分发给既有插件的仍是原始 dict/model 对象,不改变事件 ABI。
|
||
插件可通过 `app.sdk.events` 调用 `event.snapshot()` 或
|
||
`snapshot_event_data(event_type, event_data)` 获取独立的类型化快照。返回结果中的 `raw` 保留
|
||
原始对象,`payload` 是组合契约,链式事件还提供 `input` / `output` 快照;`known`、`valid` 和
|
||
`errors` 分别用于处理未知自定义事件和兼容期校验失败。快照修改不会回写原事件,如需拦截、取消
|
||
或替换链式结果,插件仍应修改原始 `event.event_data`。
|
||
|
||
架构基线中的 consumer 表示宿主源码中可静态证明的注册点,不是运行时 listener 实例数。
|
||
collector 只接受 canonical `eventmanager`、`EventManager()` 及其有限别名,忽略其他对象的同名
|
||
`register`/`add_event_listener`;当前宿主有 17 个静态注册点,另保留 1 个由工作流配置驱动的
|
||
真实动态注册。`app/plugins/**` 插件副本不进入宿主事实。
|
||
|
||
生产者与消费者共用 `scripts/architecture/event_facts.py` 这一份逐调用事实源。当前宿主有 86 个
|
||
生产调用,其中 85 个静态解析为 87 个事件引用,只有 `Command.send_plugin_event` 的插件事件类型
|
||
保持动态;18 个消费注册中 17 个静态、1 个动态。生成的
|
||
`runtime-contract-baseline.json` 保存 line-free 事实、数量和枚举索引;人工维护的
|
||
`runtime-contract-policy.json` 只批准 consumer 的精确 fingerprint、owner 和理由,任何新增、替换、
|
||
重复或陈旧项都会失败,`--write-host` 不会改写该 policy。
|
||
|
||
```python
|
||
from app.sdk.events import snapshot_event_data
|
||
|
||
snapshot = snapshot_event_data(event.event_type, event.event_data)
|
||
if snapshot.valid and snapshot.payload.context.media_info.type == "音乐":
|
||
music_type = snapshot.payload.context.media_info.music_type
|
||
```
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant T as TransferChain
|
||
participant EM as EventManager
|
||
participant MS as MediaServerChain
|
||
participant N as NotificationChain
|
||
participant PL as 插件(注册了同一事件)
|
||
|
||
T->>EM: send_event(EventType.TransferComplete, data)
|
||
EM->>MS: on_transfer_complete(event)
|
||
EM->>N: on_transfer_complete(event)
|
||
EM->>PL: on_transfer_complete(event)
|
||
Note over EM: 处理器通过 @eventmanager.register(EventType.X) 注册<br/>事件类型集中在 schemas.types.EventType
|
||
```
|
||
|
||
### 5.4 Oper 模式:数据访问层
|
||
|
||
数据库读写一律通过 `app/db/oper/` 下的 Oper 类,禁止在 Chain / Module / 端点中直接写
|
||
SQLAlchemy 查询。`models/` 与 `oper/` 按文件一一镜像(站点族聚合于 `oper/site.py` 等少数例外)。
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
Entry["API / Scheduler / Agent"] --> Command["Application Command / Chain"]
|
||
Command -->|调用注入的 Port| Port["Application Protocol / DTO"]
|
||
Port -->|运行时实现| Adapter["db/adapters<br/>Session / UoW 作用域"]
|
||
Adapter --> Oper["db/oper<br/>查询 / stage / flush"]
|
||
Oper --> Models["db/models<br/>显式 Session"]
|
||
Models --> DB[("PostgreSQL / SQLite")]
|
||
Command -.决定提交边界.-> Adapter
|
||
Adapter --> UoW["db/uow.py<br/>commit / rollback"]
|
||
UoW --> DB
|
||
Command -.提交后副作用.-> Effects["Event / Scheduler / Report"]
|
||
```
|
||
|
||
图中 Port 到 Adapter 表示运行时调用;静态依赖仍为 `db.adapters → application Protocol`。
|
||
|
||
- Oper 只接收和返回持久化值;`MediaInfo` / `MetaBase` 与数据库行之间的转换属于业务逻辑,
|
||
归 `app/application/`(见 `application/subscription/write.py`、`application/history/`)。
|
||
订阅新增、查询、变更、删除、身份和搜索契约已经统一收口在 `application/subscription/`,
|
||
不再保留主题包之外的第二个写入入口。
|
||
- 规范写入口中的 Oper 只 stage mutation,不创建独立 Session、不提交;Application Command
|
||
通过请求或任务入口注入的 UnitOfWork 统一 `commit/rollback`,事件、刷新和上报只在 commit
|
||
成功后执行。订阅新增 Port 位于 `application/subscription/write.py`,由
|
||
`db/adapters/subscription.py` 创建独占 Session,`startup/composition/subscription.py` 只装配回调,
|
||
`application/subscription/write.py` 决定事务与 post-commit 边界,`SubscribeOper.stage_add()`
|
||
只查重、`add` 和 `flush`。旧 SDK 显式构造的无会话 Oper 暂留兼容自动短会话,不得被新代码复用。
|
||
`transaction-debt-baseline.json` 要求 Model 上的查询/写装饰器持续保持为 0。Model 与 Base
|
||
已不再导入数据库装饰器,所有 `db` 参数都要求显式 Session;这些方法只查询或 stage,不能
|
||
创建、提交、回滚或关闭事务。无会话入口只存在于 Oper,由 `_execute_*` 经组合根事务执行器
|
||
承接;内置插件必须调用 Oper,不得直接导入宿主 Model。AST 门禁同时约束装饰器、可选 Session
|
||
和插件到 Model 的依赖,保证提交权不会被底层抢走。
|
||
- **Subscription typed boundary**:`application/subscription/contract.py` 是 Subscription 与
|
||
SubscriptionHistory 完整快照、媒体身份、写 Patch 和 Repository Protocol 的唯一 owner;JSON 列在
|
||
Session 内复制并深度冻结。Chain/Agent 的独立操作使用短事务 adapter,请求/API/Application Command
|
||
使用绑定当前 Session 的 adapter;两者都不向调用方泄漏 ORM。旧订阅 Oper 模块和包根符号只经同一
|
||
`sdk/_legacy/subscribe.py` + Compat 门面保留插件 ABI,不进入 canonical `__all__`。
|
||
- **Outbox 可靠副作用**:业务行与 durable intent 在同一 Session/UoW 中提交;提交后由
|
||
Outbox dispatcher 依据 topic、claim/lease、有限重试和 dead-letter 执行。完成通知、事件和统计
|
||
的 post-commit 逻辑必须保持幂等,不能用普通线程或 TaskRegistry 代替持久 intent。终态历史随统一
|
||
数据维护任务分批清理,默认成功记录保留 30 天、dead letter 保留 90 天;总开关和两项保留期由
|
||
高级设置维护,待投递和 lease 中记录不参与清理。
|
||
- **统一历史保留期**:所有可安全按时间回收的追加型数据均受 `DATA_CLEANUP_ENABLE` 控制,包括消息、
|
||
下载及孤儿文件、站点快照、整理历史、下载失败冷却、订阅历史、Agent 会话、Agent 任务运行和 Outbox
|
||
终态。Agent 会话会保护任务引用,Agent 运行会保护运行中与最后一次运行;`transferpending` 和
|
||
`plugininstallation` 承担恢复语义,禁止按年龄删除。
|
||
- 站点、历史、工作流、Agent 会话删除和插件数据重置已经形成同构事务切片;对应 Application
|
||
Command/Service 持有 UoW,Oper 的 `stage_*` 方法只修改当前会话。插件数据重置从
|
||
`startup/initializers/plugins.py` 注入事务能力,插件直接使用 `PluginDataOper` 的旧 ABI 仅作兼容。
|
||
- 每次表结构变更必须新增 `database/versions/` 下的 Alembic 迁移。
|
||
- 运行期业务配置使用 `SystemConfigKey` 枚举 + `SystemConfigOper`,禁止裸字符串键;
|
||
用户级配置使用 `UserConfigOper`。
|
||
|
||
### 5.5 其他横切模式
|
||
|
||
| 模式 | 说明 |
|
||
|---|---|
|
||
| **Config Reload** | 继承 `ConfigReloadMixin` 并声明 `CONFIG_WATCH`,配置变更时自动重建长生命周期对象(如下载器客户端重连) |
|
||
| **Singleton** | `EventManager`、`ModuleManager`、`PluginManager` 等全局共享管理器继承 `foundation/singleton.py` 的 `Singleton` |
|
||
| **Managed Resource** | 可选进程级技术资源(浏览器、虚拟显示等)以 data-only `capability.toml` 声明,`runtime/extensions` 解释生命周期,`startup` 构建 Runtime,消费者经 `runtime/resources.py` 显式获取;插件使用浏览器走 `app.sdk.browser` |
|
||
| **Observability** | `runtime/observability` 定义低基数指标和默认 no-op 端口,Startup 可选装配 OTel;HTTP、DB、Event、Module、Scheduler、插件生命周期和 Agent 只提交白名单标签 |
|
||
|
||
---
|
||
|
||
## 六、典型业务调用流程
|
||
|
||
### 6.1 搜索 → 过滤 → 下载
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant E as 入口(API / 消息命令 / Agent / 订阅)
|
||
participant SC as SearchChain
|
||
participant MC as MediaChain
|
||
participant TC as TorrentsChain(索引)
|
||
participant FC as 过滤(application/rules.py)
|
||
participant DC as DownloadChain
|
||
participant MOD as run_module 分发
|
||
|
||
E->>SC: 按标题/媒体身份搜索
|
||
SC->>MC: 识别与媒体信息补全(TMDB/Douban 模块)
|
||
SC->>TC: 检索站点资源(indexer 模块 / 站点插件)
|
||
TC->>MOD: search_torrents(...)
|
||
SC->>FC: 按规则组过滤与排序
|
||
SC-->>E: 返回候选(普通搜索到此结束)
|
||
E->>DC: 用户或自动化用例提交选定资源
|
||
DC->>MOD: download_torrent(...)(下载器模块)
|
||
DC-->>E: 返回下载结果 / 发送通知事件
|
||
```
|
||
|
||
### 6.2 订阅全流程
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
A["用户创建订阅<br/>API / 消息 / Agent"] --> B["SubscribeChain<br/>写入订阅(application/subscription/write.py)"]
|
||
B --> C{"调度器周期触发<br/>SubscribeChain.process"}
|
||
C --> D["搜索缺失集数<br/>(复用搜索流程)"]
|
||
D --> E{"命中资源?"}
|
||
E -->|是| F["DownloadChain 下载"]
|
||
F --> G["TransferChain 整理入库"]
|
||
G --> H["刷新媒体库 + 发送通知"]
|
||
H --> I{"订阅是否完结?"}
|
||
I -->|否| C
|
||
I -->|是| J["标记完成 / 发送完成通知"]
|
||
```
|
||
|
||
### 6.3 文件整理(Transfer)与监控
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant W as app/monitor 目录监控
|
||
participant T as TransferChain
|
||
participant R as 识别(domain/metainfo + application/recognition)
|
||
participant FS as run_module 存储模块(具体 I/O)
|
||
participant SC as ScrapingChain(刮削)
|
||
participant EM as EventManager
|
||
|
||
W->>T: 发现新文件,触发整理
|
||
T->>R: 解析名称/路径得到 MetaBase
|
||
T->>R: 匹配媒体(MediaChain)
|
||
T->>T: 持久准入、计划与执行检查点
|
||
T->>FS: 按目录配置转移/重命名
|
||
T->>SC: 生成 NFO / 图片刮削(domain/scraper)
|
||
T->>EM: send_event(TransferComplete)
|
||
Note over EM: 媒体服务器刷新、通知、<br/>插件等订阅方各自响应
|
||
```
|
||
|
||
`application/transfer/` 定义准入、计划、稳定操作身份、执行检查点及结算端口,
|
||
`chain/transfer/` 负责识别、队列、存储分发、刮削与通知编排,`db/adapters/transfer/` 持有短事务实现。
|
||
启动时 `replay_pending_transfers()` 登记唯一恢复调度器,统一处理启动回放、同进程补偿与过期租约接管;
|
||
`retry_wait` 与 `manual_review` 分别表达可重试和需要人工核实的执行状态,不能把入队当作整理完成。
|
||
|
||
### 6.4 消息命令交互
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
CH["消息渠道模块<br/>telegram / wechat / slack ..."] --> MP["MessageProcessingMixin<br/>(ChainBase)"]
|
||
MP --> RT["application/messaging/router.py<br/>统一优先级与回调分发"]
|
||
RT --> CMD["Command 命令注册表<br/>(application/commands.py 门面)"]
|
||
RT --> IT["InteractionChainMixin<br/>斜杠命令 / 按钮回调"]
|
||
RT --> PLG["插件输入接管<br/>(messaging/plugin.py)"]
|
||
RT --> AG["Agent 会话<br/>(messaging/agent.py → application/agent.py)"]
|
||
CMD --> Chain["对应业务 Chain"]
|
||
IT --> Chain
|
||
```
|
||
|
||
- `app/application/messaging/` 负责渠道回环入口(`ingress.py`)、消息渲染、模板、队列(`message.py`)、交互会话与视图;
|
||
业务工作流仍由对应 Chain 执行(如媒体交互的业务部分在 `MediaInteractionChain`)。
|
||
- 该包不作为推荐给插件直接使用的公开 SDK。
|
||
|
||
### 6.5 搜索分页与音乐识别
|
||
|
||
- **分页搜索**:`chain/search/` 的 `facade.py` 保留公开入口,`manual.py`、`scan.py` 与
|
||
`pagination.py` 协调分页;`application/search/state.py` 拥有搜索状态,`session.py` 定义
|
||
检查点端口,`domain/search.py` 表达收集与来源游标规则。页面候选和游标一起保存,
|
||
检查点仅保存允许的资源字段,凭据与临时下载票据不进入快照。
|
||
- **页诊断与最近上下文**:`manual.py` 的每次请求持有站点观察、过滤计数与规则适用性,
|
||
`schemas/search.py` 定义可选诊断合同;不保存原始错误或凭据。`state.py` 经注入缓存端口
|
||
按认证用户一次写入完整参数、结果和诊断,API 不拼接旧全局散键;普通搜索以空诊断
|
||
发布自己的完整记录,避免显示其它请求的过滤说明。
|
||
- **音乐识别**:`application/audio.py` 读取本地标签与 CUE,`domain/meta/` 和音乐领域规则解释
|
||
文件名、标签、目录、艺人、发行与曲目身份;`application/music/recognition.py` 经注入的
|
||
来源 Port 有界回退,`observation.py` 记录本次来源结果和共享预算。Domain 不持有来源客户端。
|
||
`chain/transfer/music.py` 负责批次准入与目录共识,`chain/subscribe/music.py` 使用下载/整理历史
|
||
对账专辑入库事实;匹配不到唯一身份时保留歧义保护。
|
||
|
||
---
|
||
|
||
## 七、AI Agent 子系统
|
||
|
||
复杂任务的计划、工具发现和中断恢复由 `app/agent/` 的实现决定,本文只说明其在宿主中的位置与依赖边界。
|
||
|
||
Agent 采用**门面 + 惰性物化**设计,Chain 经应用门面访问,
|
||
启动组合根注册轻量 Provider,避免未启用时加载完整 Agent 运行时:
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
Entry["消息渠道 / API / MCP"] --> Facade["app/application/agent.py<br/>编排门面(get_agent_manager 等)"]
|
||
Reg["app/startup/initializers/agent.py<br/>生命周期显式注册/重置 Provider"]
|
||
Reg --> Facade
|
||
Facade -.能力启用或首次使用时物化.-> RT["app/agent/loader.py<br/>能力发现与服务物化"]
|
||
RT --> Manager["app/agent/manager.py<br/>稳定 AgentManager 门面"]
|
||
Manager --> Session["session.py / lifecycle.py<br/>会话队列与有界生命周期"]
|
||
Manager --> Tasks["tasks.py<br/>后台、调度与心跳任务"]
|
||
Session --> ORC["orchestrator.py<br/>MoviePilotAgent 图与工具编排"]
|
||
Tasks --> Session
|
||
ORC --> Tools["app/agent/tools<br/>系统工具(经 application 门面)"]
|
||
ORC --> LLM["app/agent/llm<br/>LLM 提供商管理"]
|
||
ORC --> MW["middleware / policy / memory / skills"]
|
||
|
||
style Facade fill:#eef
|
||
```
|
||
|
||
约束要点:
|
||
|
||
- Chain 访问 Agent 运行时只能经 `app/application/agent.py`;
|
||
`app/chain/agent.py` 的 `AgentChain` 是链层入口,Agent 实现保持在 `app/agent/`。
|
||
- `app.agent.AgentManager` 是插件稳定路径,精确解析到
|
||
`app/agent/manager.py`;宿主直接导入 owner,包根不得通配转发
|
||
`orchestrator.py` 的任意内部名称。
|
||
- `app.agent.llm` 包根不承载实现或动态通配导出;宿主直接导入
|
||
`llm/helper.py`、`llm/capability.py`、`llm/auth.py`、`llm/provider.py` 等 owner。
|
||
旧 `app.agent.llm.LLMHelper` 由 Compat 精确叠加,`app.helper.llm` 直接映射到
|
||
canonical `llm/helper.py`,三条插件路径保持同一类身份且不公开 Provider 内部 owner。
|
||
- 官方插件使用的 `MoviePilotTool` 与 `moviepilot_tool_manager` 继续由
|
||
`agent/tools/base.py`、`agent/tools/manager.py` 原位拥有,现有 `_load_tools()` 调用保持兼容。
|
||
- Agent 工具不直接 import API / 调度器 / 命令:插件动态路由与文件夹操作使用
|
||
`application/plugin/routes.py`、`application/plugin/folders.py`,调度和命令分别使用
|
||
`application/scheduling.py`、`application/commands.py`。FastAPI 具体实现位于
|
||
`adapters/web/plugin/`,入口层不承载路由实现。
|
||
- 对外暴露 MCP 端点 `/api/v1/mcp` 与 OpenAI / Anthropic 兼容端点,
|
||
错误响应在 `app/factory.py` 中按协议原生格式单独处理(不走统一 `Response` 包装)。
|
||
|
||
Agent 内部状态按责任分开保存:
|
||
|
||
| 能力 | 所有者与边界 |
|
||
|---|---|
|
||
| 计划、工具发现与执行纠偏 | `middleware/plan.py`、`selection.py`、`guardrails.py`;计划和历史只提供上下文,实际调用仍须经过权限与执行合同 |
|
||
| 业务工具 | `policy/api.py` 固定 `moviepilot_api` 的 operation 注册表,工具经 Application 门面访问业务;外部 MCP 工具目录按请求投影 Schema |
|
||
| 写入回执 | `middleware/invocation.py` 经 `application/invocation.py` Port 认领与结算,`db/adapters/invocation.py` 提供持久化;回执未知不等于成功,也不能盲目重试 |
|
||
| 历史回忆 | `application/messaging/recall.py` 定义 DTO/Port 和维护服务,`agent/history/` 持有按用户隔离的独立 SQLite/FTS5 消息库;不共享主库 Session,旧快照仅由 `db/adapters/recall.py` 只读导出 |
|
||
| 个人技能与学习 | `agent/learning/`、`middleware/learning.py` 管理按用户隔离的技能、稳定记忆与复盘;后台快照不能授予新执行权限 |
|
||
| 终端与 Python 会话 | `agent/terminal/`、`agent/code/` 持有会话身份和授权;通用 Python 子进程 I/O 在 `adapters/system/code/`,不拥有用户和业务 API 权限 |
|
||
|
||
`middleware/recall.py` 在压缩或截断前采集脱敏证据,历史库的索引、旧数据回填与学习任务由
|
||
TaskRegistry/会话 owner 有界调度,导入阶段不打开消息库。Agent 主库会话/任务数据、独立历史库、
|
||
个人技能文件与临时工具输出各有生命周期,不能混用主库 Alembic 或把临时输出当作长期记忆。
|
||
|
||
---
|
||
|
||
## 八、插件系统与 SDK / 兼容边界
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph 插件侧
|
||
P["app/plugins/*<br/>(运行时副本/覆盖层)"]
|
||
end
|
||
|
||
subgraph 宿主边界
|
||
SDK["app/sdk<br/>新插件稳定导入面<br/>network / cache / logging / browser ..."]
|
||
Compat["app/runtime/compat<br/>manifest 精确模块/符号映射<br/>app.core / app.helper / app.utils / app.log 及已删除旧路径"]
|
||
PM["PluginManager<br/>发现 / 生命周期 / 事件桥接"]
|
||
end
|
||
|
||
subgraph 规范实现
|
||
Canonical["foundation / domain / runtime /<br/>application / adapters / chain ..."]
|
||
end
|
||
|
||
P -->|新插件推荐| SDK
|
||
P -->|旧插件(DEBUG 下告警)| Compat
|
||
SDK -->|门面转发| Canonical
|
||
Compat -.惰性解析.-> SDK
|
||
Compat -.惰性解析.-> Canonical
|
||
PM -->|run_plugin 方法分发 / 事件广播| P
|
||
Canonical -.-|禁止 import| Compat
|
||
Canonical -.-|禁止 import| SDK
|
||
```
|
||
|
||
- `app/plugins/` 是运行时插件副本,不是官方插件仓库的源码副本;它不纳入宿主架构拆分的源代码审计。
|
||
宿主代码只使用 canonical 路径;只有运行时插件与兼容性测试可用旧路径。
|
||
- `compat` 只存字符串映射并惰性解析,不得在模块导入期急切 import canonical 实现;
|
||
canonical 包也不得为兼容而反向 import `compat` / `sdk`。
|
||
- 插件 API 的动态注册/移除及端口协议统一位于 `app/application/plugin/routes.py`,
|
||
FastAPI 技术实现位于
|
||
`app/adapters/web/plugin/routes.py`;FastAPI 实例由组合根(`app/factory.py`)在创建后注入,
|
||
端点层禁止直接依赖 `factory`。
|
||
- 动态插件路由使用原生 `APIRoute`,插件自行决定返回结构;主程序的统一 `Response` 封装只适用于
|
||
`app/api/` 的宿主端点。插件若已经自行返回 `Response`、字典、列表或其它可序列化值,宿主不再二次包裹。
|
||
- `app/runtime/extensions/plugin/manager.py` 是 canonical 管理器 owner,发现、加载、生命周期、
|
||
目录、同步等实现共同归入 `app/runtime/extensions/plugin/`。旧插件仍从 `app.core.plugin` 或
|
||
`app.sdk.plugins` 进入,并由 Compat 精确路由到同一个 `PluginManager` 身份;新入口是
|
||
`app.sdk.plugin.manager`。
|
||
- `app/sdk/plugin/base.py` 是插件契约基类 `_PluginBase` 与 `PluginChain` 的 owner。
|
||
`app/plugins/` 只是插件安装命名空间,包根不再导出符号,`app.plugins._PluginBase` 与历史拼写
|
||
`app.plugins.PluginChian` 由 Compat 精确叠加承接。
|
||
- 插件可参与 `run_module` 方法分发(同名方法优先响应)并注册事件处理器。
|
||
|
||
---
|
||
|
||
## 九、运行时基础设施
|
||
|
||
### 9.1 runtime 与 adapters 的分工
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
subgraph runtime["app/runtime(契约 + 内存行为)"]
|
||
RC["config.py<br/>Settings / 部署配置"]
|
||
RE["events.py<br/>事件总线"]
|
||
RL["log.py<br/>日志运行时(依赖叶子)"]
|
||
RCA["cache.py<br/>缓存协议 / 内存后端 / 装饰器"]
|
||
MR["resources.py<br/>托管资源门面"]
|
||
end
|
||
|
||
subgraph adapters["app/adapters(具体 I/O)"]
|
||
AC["cache/backends.py<br/>Redis / 文件缓存"]
|
||
AN["network/http.py<br/>同步 + 异步 HTTP 客户端"]
|
||
AB["network/browser.py<br/>浏览器会话"]
|
||
AS["system/*<br/>OS / 文件 / 进程 / Rust"]
|
||
AE["external/*<br/>市场 / OCR / MP Server"]
|
||
end
|
||
|
||
Startup["app/startup<br/>启动期注册具体缓存工厂"]
|
||
Startup --> RCA
|
||
Startup --> AC
|
||
|
||
Caller["Application / Chain"] -->|装饰器/协议| RCA
|
||
RCA -.运行时调用注入后端.-> AC
|
||
Caller --> Port["注入的网络 / 浏览器 Port"]
|
||
Port -.运行时调用.-> AN
|
||
Port -.运行时调用.-> AB
|
||
Startup -->|构造并注入| Port
|
||
```
|
||
|
||
- **普通 HTTP 统一通过 `RequestUtils`**(`app/adapters/network/http.py`);
|
||
网络 SDK、流协议、vendor 和诊断/控制面仅允许规则 09 与人工 policy 中的精确例外;
|
||
这不授权 Application/Chain 直接导入具体 Adapter,插件使用 `app.sdk.network`。
|
||
- `app/runtime/log.py` 是依赖叶子(无任何 `app.*` 导入);`foundation` 不输出运行日志,
|
||
由上层所有者决定是否记录。
|
||
- 缓存契约与内存后端在 `runtime/cache.py`,Redis/文件实现在 `adapters/cache/backends.py`,
|
||
由 startup 在被装饰的业务模块导入前完成工厂注册;Redis 实现仅在选用该后端时加载。
|
||
- **消息协议与结构化通知**:Schema 的 `NotificationContent` / 段落结构表达通用语义,
|
||
`foundation/text.py` 提供无状态格式转换;渠道依据 `ChannelCapabilities` 选择格式与分段预算,
|
||
插件同时保留纯文本回退。Telegram Bot API、飞书 OpenAPI 和 Web Push 的宿主实现复用
|
||
统一 HTTP 传输,飞书长连接等协议入口按精确出口 policy 管理。
|
||
- **安全策略与技术 I/O**:URL/路径/SSRF 与图片域名白名单策略归 `application/security/`,
|
||
DNS、HTTP 和文件操作归 Adapter。白名单快照不替代每次实际访问的安全判定。
|
||
- **社区勋章缓存**:`application/community/medals.py` 经注入的 GitHub Port 维护实例快照;
|
||
生命周期主动预热,TaskRegistry 定期刷新,请求只读本地快照,不等待远端下载。
|
||
|
||
### 9.2 数据与配置总览
|
||
|
||
| 类别 | 载体 | 说明 |
|
||
|---|---|---|
|
||
| 持久化业务数据 | `app/db/models` + Alembic | 每次 schema 变更必须配套迁移 |
|
||
| 运行期业务配置 | `SystemConfigKey` + `SystemConfigOper` | 用户可编辑、跨重启持久 |
|
||
| 用户级配置 | `UserConfigOper` | 按 `user_id` 隔离 |
|
||
| 部署配置 | `runtime/config.py` 的 `Settings` | 环境变量 / `app.env` |
|
||
| 缓存 | `runtime/cache.py` 装饰器 | Redis 或文件后端可切换 |
|
||
| 站点资源 | `app/application/site/sites.*` + `user.sites.v3.bin` | Build 仓生成、Resources 仓发布,宿主安装/加载;不写入虚拟兼容目录 |
|
||
| Agent 历史与学习 | `agent/history/`、`agent/learning/` | 独立消息库及个人文件按用户隔离,与主库会话表分工 |
|
||
| 进程内任务 | `runtime/tasks.py` + 领域生命周期 | TaskRegistry 管取消和等待;跨重启恢复使用持久执行记录/Outbox |
|
||
|
||
---
|
||
|
||
## 十、架构治理
|
||
|
||
### 10.1 事实、策略与自动检查
|
||
|
||
宿主架构统计排除 `app/plugins/**`。生成快照记录源码事实,人工 policy 决定精确准入边界;
|
||
快照一致不等于架构合理,也不能用刷新基线来接受新债务。
|
||
|
||
| 检查对象 | 事实与约束 |
|
||
|---|---|
|
||
| 依赖方向与循环 | `test_architecture_dependencies.py` 构建完整模块图;`dependency-baseline.json` 保存模块、边和 SCC,`dependency-policy.json` 仅容纳精确的 TMDB vendor SCC |
|
||
| Adapter 与网络出口 | `test_architecture_adapter_imports.py`、`test_architecture_egress.py` 分别约束具体 Adapter 直连与 raw transport/SDK/协议操作;出口的 source、target、用途与 fingerprint 均须匹配 policy |
|
||
| 事件合同 | `scripts/architecture/event_facts.py` 是生产/消费事实的唯一收集器;`runtime-contract-policy.json` 独立准入消费者身份,禁止靠生成快照绕过语义检查 |
|
||
| 事务和配置 | `transaction-debt-baseline.json`、`configuration-debt-baseline.json` 检查底层事务所有权和配置边界;Model/Oper 不得重新创建或提交独立 Session |
|
||
| SDK / Compat | manifest、SDK 导出和官方插件快照共同核对旧导入路径、符号身份与行为边界;迁移时同步 canonical 导入、公开契约及兼容测试 |
|
||
| 类型与复杂度 | `mypy.ini` 默认严格检查全部宿主,类型错误预算为零;`mypy_policy.py` 锁定兼容例外、检查配置与本地类型桩,禁止新增豁免;复杂度约束 C901/PLR1702,行数只作观察 |
|
||
| 后台与启动 | 并发所有权、异步阻塞、TaskRegistry/生命周期测试和启动性能检查;结构计数与耗时预算分开维护 |
|
||
| 行为与覆盖率 | pytest 离线隔离执行;CI 按全部宿主文件的覆盖率低水位检查,新文件至少 80%,本机覆盖率不能回写 canonical baseline |
|
||
|
||
所有权迁移须同步 canonical 导入、Compat manifest、公开 SDK(若涉及)、规则 05 和对应测试。
|
||
延迟导入不能用来隐藏循环;`--write-host` 不会改写人工 policy,也不能替代语义审查。
|
||
|
||
### 10.2 当前架构快照
|
||
|
||
以下是 2026-11-02 核对的 `tests/fixtures/architecture/` 数据;这是源码合同快照,
|
||
不代表某个已发布镜像或正在运行的实例已经验证。
|
||
|
||
| 指标 | 当前值 |
|
||
|---|---:|
|
||
| Python 模块 | 1147 |
|
||
| 内部导入边 | 9,930 |
|
||
| 非平凡 SCC | 1(29 个模块的 TMDB vendor 精确 containment) |
|
||
| Application / Chain 具体 Adapter 直连 | 0 / 0 |
|
||
| Direct egress | 51(精确 policy 准入,Application / Chain 均为 0) |
|
||
| Module Contract V2 spec | 224(其中 220 个方法进入 `run_module` 观察面) |
|
||
| Event Contract | 53 |
|
||
| Event producer / consumer | 86(85 静态、1 动态)/ 18(17 静态、1 动态) |
|
||
| Model/Oper 自动事务与自建 Session | 0 |
|
||
| 组合根外 `SystemConfigOper()` | 0 |
|
||
|
||
保留的 SCC 是移植的 TMDB 包内部环,不得扩大或吸收包外模块;精确出口准入也不授予同包其他模块
|
||
网络权限。配置驱动的工作流事件注册是当前唯一动态 consumer,插件自定义事件类型可保持动态。
|
||
|
||
### 10.3 检查入口
|
||
|
||
使用目标项目的锁定虚拟环境;共享工作区的解释器映射见[开发环境设置](development-setup.md)。
|
||
以下是文档与架构的核对入口,修改生产行为仍须按[测试规范](testing.md)扩展验证范围:
|
||
|
||
```bash
|
||
# 文档结构与架构快照合同
|
||
uv run --locked --no-sync pytest tests/test_architecture_documentation.py \
|
||
tests/test_architecture_contract_baseline.py -q
|
||
|
||
# 当前宿主事实与快照是否一致;此命令不写基线
|
||
uv run --locked --no-sync python scripts/architecture/baseline.py --check-host
|
||
```
|
||
|
||
CI 的 `Architecture Contract Gate` 先检查依赖、Adapter、出口与事件语义,再核对宿主快照;
|
||
`Architecture Ratchet Gate` 执行类型、复杂度与并发等治理检查,行为测试、覆盖率、启动性能和
|
||
Pylint 分别提供对应证据。验证结论应绑定实际源码提交,不能引用旧一轮成功记录推断当前状态。
|
||
|
||
---
|
||
|
||
## 附录:相关文档索引
|
||
|
||
| 文档 | 内容 |
|
||
|---|---|
|
||
| [项目概述](rules/01-project-overview.md) | 系统目标与业务范围 |
|
||
| [技术栈](rules/02-tech-stack.md) | 技术栈与第三方集成 |
|
||
| [设计模式](rules/04-design-patterns.md) | Module / Chain / Event / Oper 模式细则 |
|
||
| [架构规则](rules/05-architecture.md) | 层边界、依赖方向与关键文件位置(权威) |
|
||
| [外部接口约定](rules/09-external-response.md) | HTTP、协议适配与响应格式 |
|
||
| [数据与持久化](rules/10-data-and-persistent.md) | 数据模型、事务、迁移与缓存 |
|
||
| [开发环境设置](development-setup.md) | 锁定依赖、环境映射与治理检查 |
|
||
| [测试规范](testing.md) | 隔离、测试范围与 CI 证据 |
|
||
| [MCP 接入](mcp-api.md) | 外部 Agent 连接、认证、工具发现和调用 |
|
||
| [CLI](cli.md) / [Doctor](doctor.md) | 本地管理与故障诊断 |
|