AGENTS.md@packages/plugins · diff
git:20260919.c82cd04 to git:20261001.0ce1911
26 added, 26 removed. Audit A to A.
# Plugin Development Rules
本目录中的包统一属于仓库根 workspace。创建或修改以下内容时,必须遵守本文件:
- `plugin-sdk/`:插件运行时 API 和类型契约。
- `plugin-vite/`:插件 Vite、Module Federation 和 zip 打包工具。
- - `presets/<id>/`:随 Vetta Desktop 发布的系统插件。
+ - `presets/<id>/`:随 Astravia Desktop 发布的系统插件。
- `externals/<id>/`:不随 App 打包、供用户安装的外置插件。
- `tenants.json`:按业务租户划分系统插件的打包清单(见下)。
修改子目录文件前仍需继续读取更具体的同级或下级说明文件(如存在)。
## 相关文档
- - [Module Federation 共享依赖约定](./docs/module-federation.md):说明 `vettaPluginFederation` 的宿主共享依赖、构建期本地依赖要求、顶层求值限制和验证方式。
+ - [Module Federation 共享依赖约定](./docs/module-federation.md):说明 `astraviaPluginFederation` 的宿主共享依赖、构建期本地依赖要求、顶层求值限制和验证方式。
## 改动对外合同时必须同步文档
**判据**:改动会不会让仓库外的插件作者写出不一样的代码?会,就落在本节。包括
`plugin-sdk` 的公开类型与 manifest Schema、`plugin-vite` 的构建约定、宿主对清单字段的解析行为、
以及 `plugin-cli` 的命令与输出。纯内部重构、仓库内 preset 的业务逻辑不算。
漏同步的代价不对称:**仓库外的作者只能看到手册**。代码合上了而手册没跟上,他们会照着旧手册写出
编译不过、或者能编译但装不上的东西,而且这种错误只在别人机器上复现。
### 唯一真源:`docs/plugin/`
手册写在仓库根的 `docs/plugin/`,**不要在别处维护副本**。它有两个自动出口:
| 出口 | 机制 | 到达谁 |
| --- | --- | --- |
- | npm tarball | `plugin-sdk/scripts/bundle-docs.mjs` 在 `plugin-sdk` 的 `build` 里跑,把整个目录拷进包 | 插件工程的 `node_modules/@vetta-org/plugin-sdk/docs/`,由 `vetta-plugin-cli docs` 解析 |
+ | npm tarball | `plugin-sdk/scripts/bundle-docs.mjs` 在 `plugin-sdk` 的 `build` 里跑,把整个目录拷进包 | 插件工程的 `node_modules/@astravia-org/plugin-sdk/docs/`,由 `astravia-plugin-cli docs` 解析 |
| 插件工作台 | 工作台内置 `plugin-cli`(`bundle-cli.mjs`),由它解析上面那份 | 工作台里开发的插件 |
两条都是构建期自动的,**不需要手工拷贝**。改 `docs/plugin/` 就够了。
### 一次对外合同改动要动的东西
按顺序过,缺一项都会让作者在某个环节卡住:
1. **`docs/plugin/<对应章节>.md`**——改字段改行为就改它。新增字段要写清默认值、失败时怎么降级。
规则写在手册里,**不要写进 `plugin-cli` 的脚手架模板**:模板是工程创建那天的快照,写进去的
规则会在所有存量工程里就地凝固。
2. **`packages/plugins/plugin-sdk/CHANGELOG.md`**——追加到 `[Unreleased]` 或新版本段,说明**为什么**,
不是罗列改了什么。破坏性改动要给迁移路径。
3. **`plugin-sdk/package.json` 的 `version`**——纯增量走 patch。注意 `plugin-vite` 的 peer 范围
(当前 `>=0.3.0 <0.4.0`),跨 minor 要连它一起改、一起发。
4. **`PLUGIN_API_VERSION`**(`apps/desktop/src/main/plugins/plugin-api-version.ts`)——**只要新增了清单字段就必须推**。
清单校验对未知字段 fail-closed,用了新字段的插件装到旧宿主上是整份清单被拒、插件根本装不上;
推了版本号,作者声明 `^<新版本>` 之后旧宿主给出的才是「Unsupported plugin API version」这种
指向明确的错误。同时在手册里写明该写哪一档。
5. **`apps/docs-site/content/docs/plugins/*.mdx` 与 `en/` 镜像**——公开文档站是**独立内容,不自动同步**。
只在能力面、权限面、或某条约束的结论变了时才需要动(例如「团队成员只能引用本插件的智能体」
这类判断句)。API 细节不进文档站。改了中文版就要改 `en/` 那份。
6. **合同测试**——`plugin-sdk` 的 Schema 变化必须覆盖合法输入、拒绝输入和存量清单的兼容路径。
见下方「测试要求」。
### 不属于 Agent 的一步
**不要执行 `npm publish`**,除非用户明确要求。改完提交即可,发布由用户决定时机——桌面端往往要
跟着发一版,两者的先后顺序有产品判断在里面。
### 脚手架模板(`plugin-cli`)
`plugin-cli` 的 `AGENTS.md` 模板只写「去哪读手册」,不写规则。**改了模板内容就必须推进
`AGENTS_GUIDE_REVISION`**(`plugin-cli/src/agents-template.ts`),否则存量工程不会收到
「说明书过期」的提示,用户也就永远不会去刷新它。
## 环境与租户打包(tenants.json)
系统插件先按开发/生产 profile,再按业务租户动态构建/打包。`tenants.json`
定义每个 profile 下各租户要包含的 preset 插件 id 完整列表:
```json
{
"default": "common",
"defaultProfile": "development",
"profiles": {
"development": {
- "common": ["vetta-actions", "image-gen", "svg-viewer"]
+ "common": ["astravia-actions", "image-gen", "svg-viewer"]
},
"production": {
"common": ["image-gen", "svg-viewer"]
}
}
}
```
- `default`:未指定租户时使用的租户名。
- `defaultProfile`:手动运行通用构建脚本时的缺省 profile。
- 每个 profile/租户组合写**完整**插件 id 列表(不是增量)。新增 preset
后,需要它的组合都要在各自数组里补上对应 id。
- `build:presets:dev` 强制使用 `development`;`prepare:desktop-pack` 及所有 `pack` / `dist`
入口强制使用 `production`,避免发布时因本地环境变量带入开发插件。
- 构建/开发时通过 `VETTA_TENANT` 环境变量选择租户(缺省取 `default`):
+ 构建/开发时通过 `ASTRAVIA_TENANT` 环境变量选择租户(缺省取 `default`):
```bash
# dev:仅构建并 staging 当前租户的系统插件
cd apps/desktop
- VETTA_TENANT=common bun run dev
+ ASTRAVIA_TENANT=common bun run dev
- # 打包 App:build:presets 与 prepare-pack 都读取同一 VETTA_TENANT
- VETTA_TENANT=common bun run dist:win
+ # 打包 App:build:presets 与 prepare-pack 都读取同一 ASTRAVIA_TENANT
+ ASTRAVIA_TENANT=common bun run dist:win
```
`build-presets.mjs` 只构建/staging 当前 profile + 租户的插件,切换组合时会自动清理
`.artifacts/system-plugins` 下不属于该组合的旧插件;`prepare-pack.js` 只把该
profile + 租户的 zip 制品打入 `Resources/system-plugins`。同一次构建务必使用一致的
- `VETTA_SYSTEM_PLUGIN_PROFILE` 与 `VETTA_TENANT`,否则打包阶段会因缺少对应 zip 而报错。
+ `ASTRAVIA_SYSTEM_PLUGIN_PROFILE` 与 `ASTRAVIA_TENANT`,否则打包阶段会因缺少对应 zip 而报错。
若租户包含 `plugin-workbench`,`build-presets.mjs` 在算缓存哈希之前会先跑
`presets/plugin-workbench/scripts/bundle-cli.mjs`,把 `plugin-cli` 的构建产物内置到该插件包内
- `agent/cli/`。**工作台不再自带手册副本**:手册随 `@vetta-org/plugin-sdk` 进被编辑工程自己的
+ `agent/cli/`。**工作台不再自带手册副本**:手册随 `@astravia-org/plugin-sdk` 进被编辑工程自己的
`node_modules`,由 CLI 的 `docs` 命令解析——那份才与该工程实际编译的 SDK 版本一致。
## Preset 与外置插件的区别
| 项目 | Preset 系统插件 | 外置插件 |
| --- | --- | --- |
| 源码位置 | `packages/plugins/presets/<id>/` | `packages/plugins/externals/<id>/` |
| 仓库内依赖管理 | 根 workspace 和根 `bun.lock` | 当前仓库示例同样属于根 workspace |
- | Vetta 开发包依赖 | 可使用 `workspace:*` 或与本地包匹配的 semver | 仓库内同左;移出仓库后必须使用已发布版本 |
+ | Astravia 开发包依赖 | 可使用 `workspace:*` 或与本地包匹配的 semver | 仓库内同左;移出仓库后必须使用已发布版本 |
| 安装方式 | 随 Desktop 发布,不需要用户安装 | 构建 zip 后由用户安装 |
- | 开发加载 | 构建 zip 后解压到 Desktop `.artifacts/system-plugins`;`bun dev` 默认叠加当前租户全部 preset 的内存 dev 链接 | 从 `~/.vetta/plugins` 读取已安装版本;显式 dev 链接可覆盖 |
- | App 打包 | 从 `release/<id>-<version>.vettapkg` 解压到 `Resources/system-plugins` | 不随 App 打包 |
- | 插件制品 | `@vetta-org/plugin-vite` 在构建后生成 zip | `@vetta-org/plugin-vite` 在构建后生成安装 zip |
+ | 开发加载 | 构建 zip 后解压到 Desktop `.artifacts/system-plugins`;`bun dev` 默认叠加当前租户全部 preset 的内存 dev 链接 | 从 `~/.astravia/plugins` 读取已安装版本;显式 dev 链接可覆盖 |
+ | App 打包 | 从 `release/<id>-<version>.astraviapkg` 解压到 `Resources/system-plugins` | 不随 App 打包 |
+ | 插件制品 | `@astravia-org/plugin-vite` 在构建后生成 zip | `@astravia-org/plugin-vite` 在构建后生成安装 zip |
| 权限 | manifest 中声明的权限自动授予,不可撤销 | 安装后由用户授权 |
| 生命周期 | 默认启用,可停用,不可卸载,版本随 App | 可安装、更新、重载和卸载 |
- Preset 不进入 `~/.vetta/plugins`,也不写 `plugins-manifest.json`。
+ Preset 不进入 `~/.astravia/plugins`,也不写 `plugins-manifest.json`。
`plugin-sdk`、`plugin-vite`、`presets/*` 与 `externals/*` 都列在根 workspace
中,共用根依赖图与锁文件;各插件仍保留独立的 `package.json` 和构建制品。
## 创建插件
目录至少包含:
```text
packages/plugins/<presets|externals>/<id>/
.gitignore
package.json
plugin.json
tsconfig.json
vite.config.ts
src/
index.tsx
```
Preset 可参考 `presets/svg-viewer`;外置插件可
参考 `externals/global-slot-demo` 和 `externals/mobile-ui-preview`。
### package.json
Preset 和 external 插件直接纳入根 `package.json` 定义的 workspace。仓库内
需要持续跟随 SDK、构建包或其它本地包源码时,使用 `workspace:*`:
```json
{
- "name": "@vetta/plugin-example",
+ "name": "@astravia/plugin-example",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"build": "bunx vite build",
"check": "bunx tsc --noEmit"
},
"devDependencies": {
"@types/react": "^19.1.1",
"@types/react-dom": "^19.1.1",
- "@vetta-org/plugin-sdk": "workspace:*",
- "@vetta-org/plugin-vite": "workspace:*",
+ "@astravia-org/plugin-sdk": "workspace:*",
+ "@astravia-org/plugin-vite": "workspace:*",
"react": "19.1.1",
"react-dom": "19.1.1"
}
}
```
强制规则:
- 使用 `workspace:*` 时,两个开发包修改后无需发布,下一次 preset 构建直接
使用本地源码。
- 也可使用与本地包版本匹配的 semver,例如当前版本可写
`^0.0.1`。在根 workspace 内安装时,Bun 会链接满足该版本范围的本地
workspace 包;插件移出本仓库后,同一声明会从 registry 安装已发布版本。
- 使用 semver 前必须确认 `plugin-sdk`、`plugin-vite` 的本地版本满足范围;
独立构建或发布外置插件前,还必须确认对应版本已经发布到可访问的 registry。
- 不要使用 `file:`、相对路径或手工复制开发包,避免插件与仓库目录结构绑定。
- 修改插件依赖后重新生成根 workspace 锁文件。
- React、React DOM 及其类型版本应与根 workspace 保持兼容。
- 第三方运行时依赖放在 `dependencies`,构建工具放在
`devDependencies`。
- 插件需要成熟的通用能力时应安装并使用对应依赖,不要手写低质量替代实现:UI 样式优先使用
`tailwindcss` + `@tailwindcss/vite`,运行时输入校验按现有技术栈选择 `zod` 或
`@sinclair/typebox`,React 交互测试使用 `@testing-library/react`、
`@testing-library/user-event` 与 `jsdom`/`happy-dom`。只安装当前插件实际使用的依赖,
不得为了预留能力一次性引入整套工具。
- Tailwind 插件应在 Vite 中接入 `@tailwindcss/vite`,样式入口导入 `tailwindcss`,组件优先使用
utility class;只有宿主主题变量映射、第三方内容适配或 utility 难以清晰表达的规则才保留业务 CSS。
### 锁文件
- 全仓库统一提交根目录 `bun.lock`,不再维护 `packages/plugins/bun.lock`。
- 修改插件依赖后,在仓库根目录执行 `bun install`。
- preset 和 external 插件不提交自己的 `bun.lock`。
- 根 `bun.lock` 包含 `plugin-sdk`、`plugin-vite`、preset、external 示例插件
及其第三方依赖。
### plugin.json
使用 Module Federation:
```json
{
"id": "example",
"name": "Example",
"version": "0.1.0",
"pluginApiVersion": "^2.0.0",
"entry": "dist/mf-manifest.json",
"moduleFederation": {
"remoteName": "example",
"expose": "./plugin"
},
"styles": ["dist/style.css"],
"permissions": ["ui.slot.global"],
- "author": "Vetta"
+ "author": "Astravia"
}
```
- `id` 必须与目录名一致,并且不能与其他插件重复。
- `remoteName` 使用合法的 JavaScript 标识符风格,例如下划线形式。
- 只声明实际使用的权限。
- 权限列表和 API 说明见 `docs/plugin/`(开发手册:清单 `manifest.md`、权限 `permissions.md`、各扩展点 `ui-slots.md` / `message-cards.md` / `conversation-and-agent.md`)。
### Vite 配置
Module Federation 的共享依赖约定和常见构建警告见
[docs/module-federation.md](./docs/module-federation.md)。新增或修改插件的
`vite.config.ts`、共享依赖或 `package.json` 时,必须同步检查该文档中的依赖和验证清单。
```ts
- import { vettaPluginFederation } from "@vetta-org/plugin-vite";
+ import { astraviaPluginFederation } from "@astravia-org/plugin-vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
- vettaPluginFederation({
+ astraviaPluginFederation({
name: "example",
entry: "./src/index.tsx",
}),
],
});
```
- React、React DOM 和 `@vetta-org/plugin-sdk` 由宿主共享。模块顶层禁止创建
+ React、React DOM 和 `@astravia-org/plugin-sdk` 由宿主共享。模块顶层禁止创建
依赖共享模块的 JSX;将 JSX 放在组件或 `activate()` 内。
- `@vetta-org/plugin-vite` 会在构建产物中自动用原生 `@scope` 把 CSS 限定到 manifest
+ `@astravia-org/plugin-vite` 会在构建产物中自动用原生 `@scope` 把 CSS 限定到 manifest
声明的插件 id 根节点,并将 `:root` / `:host` 映射为 `:scope`。插件作者可以正常使用
Tailwind 或业务 CSS,不要手写插件 id 前缀,也不要依赖修改 `body`、`html` 或宿主私有 class。
## 测试要求
各子包遵循根目录的风险分级,并按插件层级选择测试方式:
- `plugin-sdk` 的公开类型、manifest/权限 Schema、注册与释放合同发生变化时,必须使用 Vitest 合同测试覆盖合法输入、拒绝输入和兼容路径。
- `plugin-vite` 的 Module Federation 配置、CSS scope、manifest、zip 路径或构建产物发生变化时,必须使用临时 fixture 做构建/制品集成测试;只断言内部函数或大面积快照不足以证明产物可加载。
- Preset/External 的 React UI 行为使用该插件包显式声明的 Vitest + `jsdom`/`happy-dom` + React Testing Library;点击、表单、异步状态、权限导致的显示差异和宿主回调必须按用户行为测试。没有测试基线的插件在首次重要 UI 变更时应在同一任务补齐,不得依赖 workspace 偶然提升的依赖。
- `activate()`/deactivate、资源注册/撤销、权限、宿主 Slot、命令和生命周期变化必须测试成功、拒绝、重复调用和清理;宿主 API 在 Plugin SDK 边界使用窄 fake。
- 修改 `plugin.json`、共享依赖、Vite 配置或打包脚本时,除定向测试外必须运行目标插件实际 build 并检查生成 manifest/zip;纯样式且无交互变化时可以不新增单元测试,但应完成适用的视觉检查。
插件测试应放在对应子包并由其 `test` script 独立运行,避免测试反向依赖 Desktop 私有实现。需要验证宿主集成的行为由 Desktop 包补充合同或 E2E 测试,不在插件测试中复制宿主实现。
## 安装、构建与验证
在仓库根 workspace 安装依赖,再进入目标插件目录构建:
```bash
bun install
cd packages/plugins/<presets|externals>/<id>
bun run build
```
验证当前 profile + 租户选中的 Preset 和仓库:
```bash
cd apps/desktop
bun run build:presets
bunx tsc --noEmit
cd ../..
bun run check
```
`build:presets` 会先按根 `bun.lock` 为根 workspace
执行一次 `bun install --frozen-lockfile`,构建根 workspace 中的 SDK/构建包,
再遍历 `presets/` 构建。每个插件构建会生成 `dist/` 和
- `release/<id>-<version>.vettapkg`,随后插件包会经过路径、manifest 和入口校验,
+ `release/<id>-<version>.astraviapkg`,随后插件包会经过路径、manifest 和入口校验,
解压到 `apps/desktop/.artifacts/system-plugins/<id>/` 供开发加载。
## 提交前检查
- 系统插件位于 `packages/plugins/presets/<id>/`,外置插件位于
`packages/plugins/externals/<id>/`。
- preset 和 external 插件已纳入根 workspace。
- 根 `bun.lock` 已更新。
- - `@vetta-org/plugin-sdk` 和 `@vetta-org/plugin-vite` 使用 `workspace:*`,或使用
+ - `@astravia-org/plugin-sdk` 和 `@astravia-org/plugin-vite` 使用 `workspace:*`,或使用
可由当前本地包满足且已按发布场景验证的 semver。
- `dist/`、`release/`、`node_modules/` 已加入 `.gitignore`,没有提交。
- `plugin.json` 的入口与实际构建产物一致。
- - `release/<id>-<version>.vettapkg` 的 ZIP 容器根目录包含 `plugin.json` 和完整运行时文件。
+ - `release/<id>-<version>.astraviapkg` 的 ZIP 容器根目录包含 `plugin.json` 和完整运行时文件。
- `bun run build:presets`、`bun run check` 和 Desktop TypeScript 检查通过。
- 改动了对外合同(`plugin-sdk` 公开类型 / manifest Schema、`plugin-vite` 构建约定、宿主对清单字段的
解析、`plugin-cli` 命令)时,已按「改动对外合同时必须同步文档」逐项核对:手册、CHANGELOG、
SDK 版本、`PLUGIN_API_VERSION`、文档站结论句、合同测试。