DeepTutor 插件平台 RFC(阶段 C 与目录 E)¶
状态:已接受实现切片
模式:deeptutor.plugin/v1
最后审查日期:2026-09-08
问题¶
DeepTutor 已有多个相互独立的扩展机制:能力插件、循环能力、Reading 扩展、工具,以及较新的 visualizer 目录。第三方开发者无法用一个身份、兼容性矩阵、权限披露、制品固定和市场状态来描述一个包。缺少这一根契约,使得每个扩展点都自行设计自己的分发与信任模型。
因此,插件平台首先对包元数据和目录状态进行标准化。它并不试图让每个扩展都使用一个通用的运行时 API。
本切片非目标¶
- 不加载任意 Chrome CRX/扩展。Chrome 扩展假设存在浏览器 API、扩展进程边界和浏览器权限模型,这些都无法安全映射到 Python 服务器进程。
- 不进行任意 URL 安装、后台更新检查或自动升级。目录安装是一个显式命令,在下载前解析已内置且经过审查的制品固定。
- 不声称 Python 权限字段能创建操作系统沙箱。它们定义的是审批与运行时契约边界。受管 Tool 和 Capability worker 在独立的 Python 进程中运行,并拥有插件私有环境,但该进程边界只是依赖隔离,而非严格的安全沙箱。
- 不替换现有的
deeptutor.plugins能力加载器、deeptutor.loop_capabilities或deeptutor.reading_extensions入口点行为。这些加载器现在会在包声明了已批准根清单时遵循该清单;未声明根清单的包保留迁移窗口期。 - Learning Experience 小部件/事件被有意推迟。
根清单¶
Python 插件在其包或 wheel 内随附 deeptutor.plugin.json。该文件是静态 JSON,必须无需导入插件即可读取。
{
"schema_version": "deeptutor.plugin/v1",
"id": "org.author.example",
"name": "Example Plugin",
"version": "1.0.0",
"description_i18n": {
"en": "Example extension package",
"zh": "示例扩展包"
},
"author": "Author Name",
"license": "Apache-2.0",
"homepage": "https://example.com",
"source_url": "https://github.com/author/example",
"compatibility": {
"deeptutor": ">=1.6.0,<2",
"api": {
"capability": "1",
"loop_capability": "1",
"http_route": "1",
"frontend_page": "1",
"persistence_schema": "1",
"app_connector": "1",
"tool": "1",
"reading_extension": "1",
"visualizer": "1"
}
},
"permissions": {
"reading": ["selection", "visible_text"],
"learning_events": [],
"network": ["https://api.example.com"],
"models": [],
"storage": ["plugin-private"],
"ui": ["sandboxed-iframe"]
},
"dependencies": ["requests>=2.32,<3"],
"extensions": [
{
"type": "reading_extension",
"id": "translation",
"entry_point": "translation"
},
{
"type": "visualizer",
"id": "fraction_tiles",
"manifest": "visualizers/fraction_tiles/visualizer.json"
},
{
"type": "http_route",
"id": "study_summary",
"entry_point": "example_plugin.worker",
"path": "/study/summary",
"methods": ["GET", "POST"],
"auth": "authenticated"
}
]
}
清单解析器会拒绝未知字段。这是有意为之:根文件是公开的兼容性契约,静默接受拼写错误的字段会使插件行为取决于 DeepTutor 的发布版本。
扩展类型¶
| 类型 | 含义 | 当前行为 |
|---|---|---|
capability |
拥有对话回合的能力 | 现有入口点仅在清单批准后加载;受管 worker 通过 JSON 子进程适配器加载 |
loop_capability |
聊天循环能力 | 现有入口点仅在清单批准后加载 |
tool |
单次调用的 LLM 工具 | 现有入口点仅在清单批准后加载;受管 worker 通过 JSON 子进程适配器加载 |
reading_extension |
Reading 工具栏/操作扩展 | 现有入口点保持权威,且仅在清单批准后加载 |
visualizer |
打包的 visualizer 资产包 | 清单声明;不会急切读取 bundle 清单 |
http_route |
受管的 JSON HTTP 端点 | 已批准的受管路由在插件 venv worker 中执行 |
frontend_page |
声明的插件页面 | 打包的静态页面通过由宿主门控的资产路由提供,并带有显式资产闭包和严格 CSP |
persistence_schema |
声明的插件数据模式 | 仅限认证的内省;迁移和数据访问留待未来工作 |
app_connector |
声明的下游应用适配器 | 仅限认证的内省;适配器执行为未来工作 |
每个扩展都有一个稳定的 id。Python 支撑的扩展声明 entry_point;visualizer 声明其打包的 bundle manifest。根清单可包含多个扩展,但它不授予通用运行时 API。运行时代码仍将每个扩展绑定到其类型化协议。
http_route 和 frontend_page 声明以 / 开头的静态路径。HTTP 路由方法仅限于 GET、POST、PUT、PATCH 和 DELETE;认证必须是 public、authenticated 或 admin 中的一种。持久化模式声明一个打包的 JSON 文件和具名操作。应用连接器声明具名操作。对于每种扩展类型,未知字段仍然无效。
内置 DeepTutor ID 始终优先。插件不能通过复用内置 capability、工具、Reading 操作或 visualizer 的 ID 来替换它们。
dependencies 是一个显式、有序的闭包。依赖必须是 PEP 508 名称与版本约束;直接 URL、环境标记、extra 以及重复的规范名称均被拒绝。受管安装会将每个依赖加上插件 wheel 一起传给插件私有解释器,使用 pip install --no-deps --only-binary=:all:,这样 pip 就无法在安装时改动宿主环境或发现不同的传递依赖图。
注册表状态¶
PluginRegistry 读取已安装的分发包文件和随附目录。它从不调用 entry_point.load(),也从不导入第三方代码。
| 状态 | 含义 |
|---|---|
available |
目录条目未安装 |
approval-required |
已安装且兼容,但其精确权限快照尚未获得批准 |
enabled |
已安装、兼容,且未在本地禁用 |
disabled |
已安装且在本地禁用 |
incompatible |
已安装,但其 DeepTutor 或 API 契约不受支持 |
broken |
清单不可读/无效、ID 重复,或无法被表示 |
deprecated |
目录或审核元数据将插件标记为已弃用 |
格式错误的已安装清单仅影响该插件本身。格式错误的目录行仅影响该行。现有的入口点加载器保持其隔离行为。
本地启用状态以如下形式持久化:
{
"version": 2,
"disabled": [],
"plugins": {
"org.author.example": {
"manifest": {},
"installation": null,
"history": [],
"approval": {
"digest": "sha256-of-manifest-and-artifact-identity",
"approved_at": "2026-09-08T00:00:00Z"
}
}
}
}
该文件以原子方式写入 data/user/settings/plugins.json 下。旧版版本 1 的 {version, disabled} 结构会被读取为没有插件行的版本 2;旧版文件本身不会重写,直到另一次插件状态操作提交。
approval.digest 将完整规范化清单与已安装产物身份绑定。托管安装使用经过审核的 wheel SHA-256;外部分发使用其版本和 RECORD 身份。即使权限未变化,清单、版本或产物的变更也会使批准失效。外部分发升级需要重启进程以替换已导入的主机模块;托管 worker 从各自版本私有的环境启动。批准不会自动启用本地已禁用的插件。
托管安装记录还会保留已安装的包根目录。在该字段存在之前写入的记录仍然有效;可执行前端页面不会猜测或扫描该根目录,在插件重新安装或升级之前会返回 404。
官方目录¶
官方目录是随附的、经人工审核的 JSON 快照。浏览和版本解析不会发起实时第三方注册表请求:
- schema
deeptutor.plugin-catalog/v2中每个不可变的(plugin ID, version)对占一行;旧版 v1 快照仍然可读,但不能作为远程安装源; - 包需求被固定;
- 分发产物具有直接不可变的 HTTPS wheel URL、SHA-256 摘要、精确字节大小和非空平台标签;
- 兼容性和权限取自审核时的元数据;
- 状态为
available、deprecated或hidden; - 格式错误或重复的行会被丢弃,而不会使目录失效;
latest仅解析最新的可用非预发布版本;精确版本必须与目录字符串匹配,弃用版本需要显式标志,隐藏行永远不会被返回。
初始快照有意保持为空。DeepTutor 维护者不得虚构第三方插件来填充它。真实条目仅在经过源码审核、产物固定和记录审核者后才添加。
目录分发¶
deeptutor plugin install <plugin-id> 在随附目录中解析一行,仅下载该行经过审核的 HTTPS URL,然后以目录摘要调用本地生命周期。重定向会被拒绝,下载具有超时和大小上限,Content-Length 必须与审核后的大小匹配,且流式字节必须在安装前同时匹配大小与 SHA-256。下载失败会移除其临时 wheel,且永远不会进入 pip 或插件状态。该 URL 是审核元数据,而非 CLI 输入:不存在任意 URL 安装路径,也没有自动更新检查。
托管生命周期¶
PluginLifecycleManager 仅安装已存在的本地 wheel。该 wheel 可以来自显式本地命令或经过验证的目录下载器。它在创建环境或导入插件代码之前,直接从 zip 归档中读取并验证根清单。安装前会检查可选的本地 --sha256 固定值与强制的目录固定值。审核后的产物会被复制到插件的托管根目录下,且每个插件版本都拥有自己的虚拟环境。
安装、升级、同版本重装和回滚都会清除之前的批准。这是有意为之:用户批准的是精确的权限、产物和清单快照,而非对另一个版本的隐式信任转移。依赖安装失败会保持当前版本、批准和环境不变。成功的升级会保留先前版本的目录和环境以供回滚;回滚会将当前版本移至历史记录并恢复先前的环境。卸载会移除该插件的托管状态、产物、环境和本地启用/禁用条目。
托管 Tool 和 Capability 扩展使用 worker 模块入口点,例如 learning_echo.worker。DeepTutor 使用插件 venv 解释器启动该模块,通过 stdin/stdout 交换一次 JSON 请求和响应,并拒绝权限快照超出已批准清单的 worker 声明。现有的 Python 入口点扩展仍会在主机进程中加载类或工厂,但前提是根清单门控接受它们。
托管 HTTP 与内省 API¶
托管 HTTP 路由始终在 /api/plugins/{plugin-id}{path} 下分发。它们无法覆盖内置路由,且会在每个请求开始时从插件状态解析一次。只有已批准、兼容、启用、具有安装记录的托管安装才能暴露路由。未批准、不兼容、损坏、已弃用、已禁用、外部和缺失的路由都会返回相同的 HTTP 404 响应。
主机在启动 worker 之前执行路由认证。公共路由不依赖主机凭据;authenticated 使用常规的 session/bearer 依赖;admin 使用主机管理员依赖。worker 请求仅包含声明的方法和路径、规范化后的查询值、不超过 1 MiB 的 JSON body,以及 manifest 中的认证策略。Cookie、bearer token 和原始主机请求头永远不会被转发。
worker 实现 handle_http 并返回一个 JSON 对象,其中包含响应 status、JSON body 和可选 headers。允许的状态码是插件可以拥有的常规应用状态码(200、201、202、204、400、404、409、422 和 500);认证和重定向状态码属于主机。Cache-Control 是唯一由 worker 提供的响应头,其值仅限于 no-store、no-cache 或 max-age=<seconds>。主机始终设置 JSON 媒体类型。无效的 worker 输出将变为通用的 HTTP 500 响应,并且仅影响该插件请求。
GET /api/plugins/extensions 需要认证,并枚举已批准、已启用、受管安装的扩展声明。它是持久化 schema 和应用连接器的发现入口。可执行的 frontend_page 还包含 entry_url。
受管前端页面¶
声明了打包页面 manifest 的 frontend_page 将作为静态浏览器资源执行,而不是作为主机 React 或 Next 代码。页面 manifest 是一个独立的 JSON 契约(deeptutor.plugin-frontend-page/v1),包含一个 entry 和显式的 assets 闭包。路径相对于已安装的包根目录,必须是普通文件,不能是符号链接,并且仅限于小型 Web 资源类型和聚合大小。
主机托管规范页面 URL /api/plugins/{plugin-id}/pages/{extension-id}/;不带斜杠的形式会重定向到该 URL,以便相对资源 URL 可预测地解析。只有在经过注册表门控和 manifest 的主机认证策略检查后,它才会提供声明的资源路径。响应使用 no-store 和 nosniff,并带有严格的 CSP,包含 sandbox allow-scripts、connect-src 'none',不允许第三方进行框架嵌入,且不包含由插件控制的响应头。入口文档使用同源资源策略,而声明的资源使用 Cross-Origin-Resource-Policy: cross-origin;沙箱化文档具有不透明来源(opaque origin),因此同源资源策略会阻止其自身的打包脚本。这为静态插件 UI 提供了浏览器执行上下文,而不会将不受信任的代码注入 DeepTutor 的前端 bundle。它不是一个签名系统,也不能免除插件审查的需要。
信任与兼容性¶
当前的信任模型是人工策展加上固定的元数据。目录浏览和版本解析仍然离线进行。本地安装接受 wheel 包,并在调用 pip 之前验证可选的工件摘要;目录安装还会将下载 URL、大小和摘要绑定到已审查的快照。签名和进程/容器安全沙箱是独立的未来阶段,绝不能由该目录格式暗示其存在。
在 v1 中,Python 权限是披露和批准元数据。现有的入口点扩展在获得批准后仍在主机 Python 进程中执行。受管 worker 扩展在主机进程之外执行,但仍保留主机操作系统访问权限。审查者和用户需要看到这一事实;操作系统范围强制属于后续隔离设计。
Reading 扩展继续使用服务器验证的 Reading 上下文和受限的结果类型。插件不能通过更改其 manifest 声明来扩大其上下文。
需要不受信任的 HTML/UI 执行的 visualizer 使用现有的沙箱 iframe 模型。它们的打包资源 manifest 和 renderer 契约仍然是独立的类型化规范。受管前端页面使用自己的显式资源闭包和 CSP 沙箱,而不是 visualizer 运行时。
Chrome 兼容性¶
直接复用 Chrome 扩展不在范围内。未来的兼容层可以针对一个窄子集,但它必须将 Chrome 扩展转换为类型化的 DeepTutor 扩展声明和审查记录。它不得将 Chrome 后台/service-worker 代码导入 DeepTutor,也不得将浏览器级权限交给 Python 插件。市场分发仍然基于 Python 包;Chrome 特定资源可以作为此类包中的资源一起发布。
交付阶段¶
- 阶段 A - 契约:根 manifest 验证、RFC、目录 schema。
- 阶段 B - 可见性:仅元数据的已安装/目录注册表,以及 CLI 的 state/search/show/enable/disable 命令。
- 阶段 C - 运行时门控:现有的 capability、loop、Reading 和 tool 加载器遵循本地启用状态和 manifest 兼容性。
- 阶段 D - visualizer 加载:将 visualizer manifest 打包进 v1.6.2 风格的 visualizer 目录,同时保持内置优先级。
- 阶段 E - 安装:固定工件下载、摘要验证以及显式的用户批准安装流程。
- 阶段 F - 沙箱/签名:更强的隔离、签名和开发者发布工作流。
该仓库目前实现了阶段 A 到 C,以及阶段 E 中的本地 wheel 和目录驱动的远程安装、批准、Tool/Capability/HTTP worker、升级、回滚、卸载和受管内省部分。开发者发布自动化、持久化迁移、应用连接器执行、visualizer 打包、签名和更强的沙箱仍然是未来阶段。
阶段 1 本地开发完成情况 (#1307)¶
根 manifest 门控在每次通过共享加载器执行 entry_point.load() 之前运行,包括 legacy 和 reading 组。缓存对象在执行前会重新检查 scope、批准状态、generation 和启用状态。没有根 manifest 的 legacy 包在迁移期间保持兼容性。私有 worker 执行使用 60 秒截止时间、1 MiB 请求预算、2 MiB stdout 预算和 64 KiB stderr 预算;异步 tool/capability/HTTP 调用在事件循环之外运行。
状态变更和生命周期工作具有非阻塞的进程所有权。损坏的状态会被保留并拒绝使用。版本构件是不可变的;失败的同版本安装会保留先前的环境和字节。回滚会验证保留的构件哈希并清除批准。安装仅接受 wheel 依赖,因此安装未批准的包不会构建源码发行版。依赖约束会被披露;依赖哈希签名和更强的隔离仍保留在上述生态路线图中。
经认证的 API 检查包括 GET /api/plugins/catalog、/state、/{id}/info 和 /extensions。管理员变更操作包括 POST /{id}/install(经过审查的目录版本)、/approve(需要已检查的 snapshot_digest)、/enable、/disable、/rollback 以及 DELETE /{id}/installation。CLI 还支持本地 wheel 安装,并可通过可选参数指定预期的 SHA-256。安装、升级和回滚都需要重新批准。随附的目录初始为空;打包的 learning_echo 示例实际运行真实的 wheel/worker/静态页面路径,而不会虚构第三方目录条目。
主机认证在解析任何 HTTP 路由或前端资源之前确立用户作用域。启用主机认证后,即使清单中的 public 路由也需要主机会话;public 意味着不需要额外的插件特定管理员检查。bearer token、cookie 或认证头都不会被转发给 worker。主机常规的禁用认证的本地模式仍然可用。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 dee/DeepTutor