包边界:谁需要知道哪个库
这一页说明 packages/ 下每个库负责什么、谁在用它。要给框架加一个跨包依赖,或者拿不准一段代码该放进哪个包,先读这一页。写插件的人只需要看第一张表的第一行。
Covel 是模块化单体:一个仓库、一起发布,内部分成若干包。packages/ 里的包大多只是工作区内部的边界,不单独发布。判断一个库为什么存在,看两点:它负责什么,谁在调用它。
IMPORTANT
唯一面向独立插件作者打包的库是 @covel/plugin-handlers-utils。它产出 dist 和类型声明文件,工作区内部也从这份 dist 读取它。
所以构建产物比源码旧的时候(例如拉取了新提交却没有重新 pnpm install),导入新名字的插件会加载失败。
- 开发服务端每次启动进程时会检查并重新构建(
scripts/ensure-plugin-sdk.mjs)。 - Turbo 会先完成 SDK 构建,再运行它自己的 lint 和测试。SDK 测试经
shared读取自己的dist声明,所以只有依赖包的^build顺序还不够。 - 单独运行某个包的测试之前,仍然要手动执行
pnpm --filter @covel/plugin-handlers-utils build。
其余的库主要由应用和框架在工作区内组合使用。pnpm test:docs:packages 按包合并读取文档的测试文件,包括 SDK README 的 peer 版本和导出子路径同步检查,每个包只启动一次测试运行器;具体范围见命令。
三条使用路径
| 使用者 | 应接触的入口 | 不需要接触的实现 |
|---|---|---|
| 插件作者 | @covel/plugin-handlers-utils 的公开 SDK 类型与辅助函数;运行时注入的 PluginAPI、toolkit 和受限的 FunctionStoreView | runtime、store、tools、shared 的内部入口。tools 依赖 SDK 并实现宿主侧工具注册与执行,作者经注入的 toolkit.tool 定义工具。 |
| 应用宿主(server、desktop、web) | 按职责组合 plugin-loader、runtime、store、memory、approval、events、context 等;选择实际后端并提供资源、会话锁及提交策略 | 插件作者的业务逻辑。执行返回值本身不负责持久化,宿主必须提交执行计划。 |
| 框架内部与开发工具 | shared 领域契约及各包的内部模块;plugin-test-utils 用于插件单元测试,test-runtime 用于隔离运行 runtime cases | 不应把测试运行器视作完整 HTTP 服务。它与 server 共用默认工具及静态工具审批策略,但 store、LLM、事件目录等资源由测试环境提供。 |
插件作者的 SDK 和宿主的实现是分开的:
plugin-handlers-utils不依赖任何私有的工作区包。shared保存框架各层共用的 DTO 和 schema。tools实现 SDK 所描述的工具协议。plugin-loader依赖 SDK 和zod。导入插件的 entry、handler 或 guard 之前,它先登记插件目录(provideHostModules);这个目录里的模块import这两个包时,解析到宿主的那份。内置插件和安装的插件走同一条路,插件不用自带这两个包。
每个包的实际责任
| 包 | 谁需要知道它 | 保留这个边界的理由 |
|---|---|---|
plugin-handlers-utils | 插件作者、实现作者协议的宿主代码 | 唯一可独立打包的作者 SDK;拥有 PluginAPI、handler/context、proposal、显式工具结果和辅助函数契约。 |
shared | 框架包与应用 | Covel 领域记录、事件、schema 和跨层协议的共同类型所有者;不是插件作者的第二套 SDK。 |
plugin-loader | server 与开发测试工具 | 从 Node 文件系统发现、校验和加载插件包,保持插件目录及 manifest 协议的单一实现。 |
runtime | server 与 test-runtime | 调度、执行、挂起/恢复、效果提交及生命周期组成一个 Covel 内核;宿主通过其执行和提交 API 连接持久化。 |
tools | server、runtime、test-runtime | 宿主侧工具定义校验、注册、执行和内置工具装配;依赖作者 SDK 的工具结果契约。默认内置集由 createDefaultToolRegistry 构造。 |
approval | server、runtime、test-runtime | 两种不同授权机制共享一个小包:createDefaultToolApprovalPipeline 是工具来源的静态 allow/deny 策略;createRpcApprovalGate 是会话范围内交互式 Plugin RPC 授权状态。前者不持久化玩家批准,后者由宿主负责 HTTP 决策流程。 |
context | runtime、server 压缩/生成流程 | prompt、历史、插件注入数据和 token budget 的组装。常用入口只有异步 await buildContext(params),按输入自行决定是否读取 store;独立 prompt 目录用 createPromptLoader(root) 注入。 |
events | server 订阅层、runtime、插件注册流程 | Covel 事件总线、重放、可选持久化和 transport 接口;内部 RingBuffer 不属于消费方 API。 |
store | server、runtime、memory、浏览器同步 | 领域持久化契约与 Memory/SQLite/PostgreSQL 后端(/indexeddb 只提供浏览器侧的媒体缓存)。根入口承载契约、工厂和跨后端操作;具体后端工厂从 /memory、/sqlite、/postgres、/indexeddb 导入,浏览器同步和向量能力也有专用子入口。 |
memory | server 装配,runtime 通过注入接口使用 | recall、archival、BM25/向量检索及增量索引;不配置 embedding 模型时全部走 BM25,排序实现来自 plugin-handlers-utils 的 rankTexts,插件用的是同一份。每个 createMemorySystem 实例拥有自己的后台任务集合、pendingTaskCount() 与 drain();宿主停止生产者后排空该实例。 |
ai-provider | server 的模型配置、runtime gateway、test-runtime live 模式 | 模型选择、provider 协议及媒体 wire 的集中适配;具体 provider 不进入插件作者 API。 |
create | server 世界生成路由 | 可注入 LLM 的世界内容生成与验证,显式 writeWorldPackage 导出文件;数据库消费者直接使用生成结果。 |
settings | web 设置面板与持久化 adapter | 设置项注册、校验、订阅和持久化抽象;宿主提供实际 IPC/HTTP transport。 |
plugin-test-utils | 插件单元与集成测试 | 受限上下文、mock,以及复用真实执行和事务提交的工具集成测试入口;不启动完整 HTTP server。 |
test-runtime | 插件开发者运行 runtime cases | 使用隔离 store 与 mock/live gateway 调用真实 runtime 的开发 CLI;复用默认工具集和静态审批策略,但不覆盖 HTTP 路由、生产事件目录或部署锁。 |
approval、context、events、create、settings 这些小包,各自有明确的使用者和独立的责任。
- 不要因为一个包体量小或只在开发时用,就把它并进
runtime。 - 也不要为了“看起来通用”而增加只做转发的包。
依赖方向。 plugin-handlers-utils 是依赖图的根,不依赖任何工作区包。shared 紧挨在它上面。@covel/server 组合所有功能包。精确的依赖边写在各包的 package.json 里,pnpm check:boundaries 检查公开入口和依赖方向。 SQLite 向量扩展 sqlite-vec 由 store 直接声明,根包不重复提供。
常规依赖更新只采用发布满七天的正式版;等待期与精确安全版本例外由 pnpm-workspace.yaml 管理。版本声明以各包的 package.json 和 mise.toml 为准,解析结果以 pnpm-lock.yaml 为准。升级时同步工作区与工具模板,保留 overrides 的消费方范围,并用现有检查验证兼容性。
开发依赖 Fallow 的安全更新同时覆盖分析器、类型分析包和各平台二进制包。它在分析仓库配置时必须把文件读取和路径探测限制在项目根目录内;精确版本的等待期例外不放宽其他依赖的更新规则。
工作区使用 TypeScript 稳定版 7.0.2。Zod、作者 SDK 的 peer dependency 和带工具模板的最低版本统一为 4.6.7;它修复了 TypeScript 7.1 对递归 JSON 类型的循环推断报错。JSON 的校验、可选字段和只读语义不变。Nightly 和 tsrs 的兼容性验证不替代稳定版类型检查门禁。
宿主组合的关键边界
宿主指把这些包装配起来运行的应用:server、desktop、web。
两个独立的站点
- 官网
apps/site是独立的静态站点。它不依赖任何工作区包,也不进入 Web 或桌面的构建。 玩法和世界创作示例使用独立的 HTML 组件,参考应用布局,但不引入会话状态或运行时。文案、图片路径和语言列表可分别编辑;示例跟随官网语言和亮暗主题。轮播展示视觉小说、跑团冒险、长篇叙事和生存沙盒四种体验;它们是插件与世界设定的示范组合,不是互斥的引擎模式。多语言作为所有玩法共享的特性单独展示。每个场景按世界实际插件组合分三步演示,并联动高亮对应插件;自定义世界面板归属世界维度,示例结果不执行真实插件。首页以插件系统和世界包为核心,分别介绍应用内生成与工作区 Agent Skills,并附轻量的社区目录入口。插件体验区提供浏览器内的掷骰、任务、关系与图鉴交互,不执行插件或保存会话。吸顶快捷导航通向各章节;下载文件与安装说明集中在一个下载区,系统识别只标注推荐平台。官网测试会读取内置组合、世界策略和插件声明;这些文件也纳入 Turbo 测试缓存输入。维护入口见 官网 README。 - 文档站
apps/docs同样独立。它用 VitePress 把docs/的页面构建成静态站点,发布在官网的docs/路径下。两个站点由独立的 Turbo 任务并行构建,分别缓存;文档内容和SITE_BASE等实际输入参与缓存计算,最后才组装发布目录。 - 文档图表使用 Mermaid 12,需要支持 ES2024 的浏览器(Safari 17.4+)。Mermaid 只属于文档站,不进入 Web 或桌面应用。
- 应用自己的
/直接跳到世界列表。
桌面壳
桌面壳直接依赖 Zod 来校验本地保存的窗口状态。窗口的位置和大小属于 Electron 宿主,不作为共享的领域 schema 暴露。
桌面壳访问 sidecar 的 HTTP 客户端直接依赖 npm Undici,不使用 Electron 内置的 fetch。ai-provider 的出站传输也显式依赖 npm Undici;升级 Node 或 Electron 不会替换这两处依赖。主进程的 ESM 构建保留原生 require 桥接,让打包进去的 Undici 能加载 Node 内置模块。
这些桌面壳依赖在 apps/desktop/package.json 中列为开发依赖,因为它们已进入 main/preload bundle;安装包不再另带一份桌面 node_modules。服务端拥有独立、完整的运行依赖,包含加载社区 TypeScript 插件需要的 tsx / esbuild 和公开 SDK。裁剪边界见桌面打包。
桌面后端始终使用安装包自身的 Electron 可执行文件,以 RunAsNode 在独立子进程中运行;没有独立 Node 下载器、额外运行时或模式选择配置。玩家不需要安装 Node,启动、就绪、系统代理和关闭仍走原有子进程 IPC。构建与启动校验以及平台验收要求见桌面打包,内嵌 Node 的安全边界见安全。@electron/asar 只作为桌面的开发依赖,用来校验产物内的主进程文件,不进入玩家运行时。
部署形态只在宿主里决定
server 启动时把部署档位、实际存储和令牌合成一份宿主配置(apps/server/src/host-profile.ts,规则见安全边界),之后只问它。核心包不读这些部署变量:
ai-provider的局域网豁免和“不属于会话的请求能否用社区插件的 wire”,由宿主在启动时通过configureProviderHostPolicy传入;没有宿主传入时(工具、测试)按单用户的self处理。shared只保留这些变量的登记和解析。服务端设置是否采用玩家存的值,由宿主传playerWritable决定。- 桌面壳启动 server 时固定传
DEPLOYMENT_TIER=self。
执行之后必须提交
一次执行先通过 executeTurn(...) 或 resumeSuspendedRuntime(...) 得到 { result, commit }:
| 字段 | 是什么 | 说明 |
|---|---|---|
result | 可以传给客户端的业务结果 | 已经去掉各层 runtime result 的 pendingProposals。RuntimeResult.output 里也只有业务数据 |
commit | 只交给宿主的提交计划 | 包含顶层和递归的结果、journal、suspensions、hook scope、schema、setup 和时钟信息 |
缓冲的写入保留在 commit.results[].pendingProposals 里,不藏在 output 上。宿主在会话锁内提交这份计划,不需要自己拼装这些内部数据:
const execution = await executeTurn(input, manifests, deps);
const { result } = execution;
await commitExecution({
execution,
store: deps.store,
completion: {
kind: "turn",
turnId: result.turnId,
durationMs: result.durationMs,
},
});- 恢复执行同样把整个
execution交给commitExecution,完成策略选kind: "resume"。 - 提交事务,以及提交之后的通知、快照、记忆调度,分别遵守事务契约。
WARNING
工具调用成功,不代表 proposal 已经保存。只有提交成功才算保存。
server 和测试运行器共用的部分
server 与 test-runtime 都使用 createDefaultToolRegistry({ store, eventDirectory }) 和 createDefaultToolApprovalPipeline(),所以默认工具的名称和来源策略一致。差别在资源:
| 宿主 | 注入什么 |
|---|---|
| server | 实际的事件 schema、角色能力、持久化资源和交互式 RPC 审批 |
test-runtime | 隔离的资源和 mock gateway |
工具返回 { kind: "covel.tool-result", content, pendingProposals, emittedEvents? } 时,宿主先分离并验证其中的效果,再把正文交给模型。不产生效果的纯工具可以直接返回正文。详见工具契约。
memory 与 create
- memory 统一的
search入口负责复用 query embedding 和跨来源排名。tools 只负责把结果转换成展示的形式。 createWorld返回经过验证的、可移植的内容。writeWorldPackage负责可选的文件导出。数据库和“仅返回”模式直接使用生成的结果。- memory 的后台队列属于
MemorySystem实例,不是进程全局的队列。 - 向量增量索引的游标和内容哈希保存在专门的
vector_index_progress表。它是可以重建的派生索引状态,不占用插件数据的命名空间。 - memory 使用 store 的窄能力接口。server 选择 embedding、跨进程锁、恢复任务,以及关闭时的 drain 策略。
context 的 prompt loader
prompt loader 也按实例划分:
- 共享的模板用默认的
loadPrompt。 - 另一个模板目录通过
createPromptLoader(root)注入,查找顺序和loadPrompt相同。插件不用它:插件的模板用 SDK 里的同名加载器,只读 English 和简体中文两份。 - 包的公开入口不导出进程级的
setPromptsRoot。 buildContext通过同一条异步路径组装提示词,按输入声明读取插件数据。
详见 prompt 结构。
作者 SDK 里放了什么
独立的插件 SDK 拥有这些实现,宿主包只是重新导出同一份:
- locale 解析、token 估算、维度的校验与实例化。
@covel/shared重新导出这些定义。 - 维度提供者从
@covel/plugin-handlers-utils/dimensions导入。插件不到 shared 包里去取这些操作。 - UI 工具工厂和角色字段校验也在 SDK 里。
- 按 locale 读取文件的功能单独放在只能在 Node 里用的
/prompts入口,根入口保持可以在浏览器里使用。 - 宿主包重新导出的内容包括工具校验的错误类。
shared还重新导出 SDK 的ToolError、固定错误码和ToolModelView;runtime 从shared导入错误和模型视图类型,从tools读取工具辅助函数的类型,不直接导入 SDK。
开发阶段的兼容范围
Covel 还在早期开发,只针对当前的契约。改一个契约时,生产者和消费方一起改,不保留旧入口的别名,也不做双写。现在的规则是:
- 作者使用可打包的 SDK。
- 具体的 store 后端从它的子入口导入。
- 调用 context 必须写
await buildContext。 - 宿主必须保留并提交执行计划。
- 工具的效果必须通过显式结果和
pendingProposals传递。 - 测试 fixture 和插件代码按同样的规则更新。
WARNING
这些库目前没有跨版本的持久化迁移承诺。store schema 或向量进度表变化时,受影响的开发数据需要重新创建。不要把“旧数据还能读”或“旧入口还能用”当作保证。
pnpm check:boundaries 检查生产源码的公开入口、依赖声明和包的方向。它还要求 packages/* 的每个导出入口有包外的源码使用者(包括测试、脚手架和脚本),或者在检查脚本中写明保留理由。需要新的跨包依赖时,连同这一页的责任划分一起评审。
独立运行 pnpm check:boundaries 时也会测试它的解析器;完整的 pnpm check 直接运行边界扫描,再由 check:scripts 运行解析器测试,避免重复执行。
test-runtime 只提供 CLI,不提供包导入入口。用 pnpm test:runtime 运行 runtime cases;pnpm plugin:extract <dir> 在作者本机执行入口工厂,记录注册并同步声明,纯文本改写由 plugin-loader 负责。
网页端从 SettingsStore 读取主题和明暗偏好,经主题解析后,通过 appearance 辅助函数应用实际显示值。回退显示不改写已保存的偏好。
可选群像提供方
plugins/character-agent 提供可重复使用的角色演员,plugins/turn-policy 提供一个可选策略读服务;它们与对话叙事通过公开 SDK 契约组合,不是框架依赖或专用宿主路径。