@vetta-org/plugin-sdk 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/manifest-schema.d.ts +75 -8
- package/dist/manifest-schema.d.ts.map +1 -1
- package/dist/manifest-schema.js +40 -5
- package/dist/manifest-schema.js.map +1 -1
- package/dist/manifest.d.ts +2 -2
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +32 -1
- package/dist/manifest.js.map +1 -1
- package/docs/.source.json +22 -0
- package/docs/README.md +112 -0
- package/docs/ability-details.md +424 -0
- package/docs/ai.md +121 -0
- package/docs/app-actions.md +111 -0
- package/docs/browser.md +80 -0
- package/docs/conversation-and-agent.md +619 -0
- package/docs/file-explorer.md +118 -0
- package/docs/getting-started.md +295 -0
- package/docs/guiding-the-agent.md +183 -0
- package/docs/manifest.md +371 -0
- package/docs/mcp.md +152 -0
- package/docs/media.md +141 -0
- package/docs/message-cards.md +188 -0
- package/docs/permissions.md +145 -0
- package/docs/styling-and-pitfalls.md +254 -0
- package/docs/system-plugins.md +58 -0
- package/docs/ui-slots.md +628 -0
- package/package.json +6 -3
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# 文件列表扩展
|
|
2
|
+
|
|
3
|
+
`ctx.fileExplorer` 扩展宿主自带的项目文件列表。插件可以贡献右键菜单、工具栏动作和文件装饰,也可以读取当前工作区/选中项、定位文件、刷新目录并订阅文件事件。
|
|
4
|
+
|
|
5
|
+
文件列表 API 只暴露文件名、路径、类型、大小和修改时间等元数据。读取文件内容仍需 `fs.read`,修改文件仍需 `fs.write`。
|
|
6
|
+
|
|
7
|
+
## 权限
|
|
8
|
+
|
|
9
|
+
| 权限 | 能力 |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `ui.file-explorer.context-menu` | 注册文件或目录右键菜单动作 |
|
|
12
|
+
| `ui.file-explorer.toolbar` | 注册文件列表顶部工具栏动作 |
|
|
13
|
+
| `ui.file-explorer.decorations` | 注册图标、徽标和提示信息提供者 |
|
|
14
|
+
| `workspace.read` | 查询根目录/选中项、定位、刷新和订阅事件 |
|
|
15
|
+
|
|
16
|
+
上述 API 缺权限时会抛 `Plugin permission denied: <permission>`。它们不会隐式授予 `fs.read` 或 `fs.write`。
|
|
17
|
+
|
|
18
|
+
## 右键菜单
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
ctx.fileExplorer.registerContextMenuAction({
|
|
22
|
+
id: "format-file",
|
|
23
|
+
label: "%explorer.format%",
|
|
24
|
+
icon: <span className="icon-[solar--magic-stick-3-linear] h-3.5 w-3.5" />,
|
|
25
|
+
order: 50,
|
|
26
|
+
when: {
|
|
27
|
+
resourceType: "file",
|
|
28
|
+
extensions: ["ts", "tsx"],
|
|
29
|
+
},
|
|
30
|
+
async run({ entry, workspaceRoot }) {
|
|
31
|
+
// 读取内容需要插件另外声明 fs.read。
|
|
32
|
+
const source = await ctx.fs.readFile(entry.path);
|
|
33
|
+
console.info(workspaceRoot?.path, source.content.length);
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`when` 支持:
|
|
39
|
+
|
|
40
|
+
- `resourceType`: `file` 或 `directory`
|
|
41
|
+
- `extensions`: 不带点的扩展名数组,大小写不敏感
|
|
42
|
+
- `fileNames`: 精确文件名数组,大小写不敏感
|
|
43
|
+
|
|
44
|
+
多个动作按 `order` 升序显示,缺省为 `100`。
|
|
45
|
+
|
|
46
|
+
## 工具栏动作
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
ctx.fileExplorer.registerToolbarAction({
|
|
50
|
+
id: "sync",
|
|
51
|
+
label: "%explorer.sync%",
|
|
52
|
+
icon: <SyncIcon />,
|
|
53
|
+
async run({ workspaceRoot, selection }) {
|
|
54
|
+
await synchronize(workspaceRoot.path, selection.map((entry) => entry.path));
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
工具栏动作只在文件列表存在活动工作区时显示。`selection` 是当前文件列表选中项的只读快照。
|
|
60
|
+
|
|
61
|
+
## 文件装饰
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
ctx.fileExplorer.registerDecorationProvider({
|
|
65
|
+
id: "git-status",
|
|
66
|
+
priority: 100,
|
|
67
|
+
when: { resourceType: "file" },
|
|
68
|
+
provideDecoration(entry) {
|
|
69
|
+
const status = statusByPath.get(entry.path);
|
|
70
|
+
if (!status) return null;
|
|
71
|
+
return {
|
|
72
|
+
badge: status,
|
|
73
|
+
tooltip: `%explorer.gitStatus.${status}%`,
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`provideDecoration` 在文件树渲染时同步调用,必须快速且无副作用。网络、命令和文件读取应提前完成并缓存在插件内;状态变化后可调用 `ctx.fileExplorer.refresh()` 触发目录刷新。多个提供者命中时,宿主采用 `priority` 最高且返回非空结果的提供者。
|
|
80
|
+
|
|
81
|
+
装饰可返回:
|
|
82
|
+
|
|
83
|
+
- `icon`: 替换内置文件/文件夹图标的 React 节点
|
|
84
|
+
- `badge`: 文件名后的紧凑状态文本,建议一到两个字符
|
|
85
|
+
- `tooltip`: 文件行提示信息
|
|
86
|
+
|
|
87
|
+
## 工作区、选择与定位
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
const roots = ctx.fileExplorer.getWorkspaceRoots();
|
|
91
|
+
const selection = ctx.fileExplorer.getSelection();
|
|
92
|
+
|
|
93
|
+
await ctx.fileExplorer.reveal("C:/workspace/src/index.ts", {
|
|
94
|
+
select: true,
|
|
95
|
+
focus: true,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
await ctx.fileExplorer.refresh(); // 刷新根目录
|
|
99
|
+
await ctx.fileExplorer.refresh(dirPath); // 刷新工作区内指定目录
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
当前版本的内置文件列表只有一个活动根目录,因此 `getWorkspaceRoots()` 返回零项或一项。`reveal` 和 `refresh(path)` 只接受当前工作区内路径。
|
|
103
|
+
|
|
104
|
+
## 事件
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const selectionHandle = ctx.fileExplorer.onDidChangeSelection((selection) => {
|
|
108
|
+
console.info("selection", selection);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const filesHandle = ctx.fileExplorer.onDidChangeFiles((changes) => {
|
|
112
|
+
for (const change of changes) console.info(change.type, change.path);
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
文件事件类型为 `changed`、`created`、`deleted` 或 `moved`;`moved` 额外包含 `oldPath`。文件系统监听只能确定目录发生变化时,宿主会发送该目录的 `changed` 事件。
|
|
117
|
+
|
|
118
|
+
所有注册和订阅都返回 `Disposable`。插件可以主动调用 `dispose()`;插件停用、重载或卸载时宿主也会统一清理贡献和订阅。
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# 快速开始
|
|
2
|
+
|
|
3
|
+
从零搭建、构建、安装、调试一个 Vetta 桌面插件。
|
|
4
|
+
|
|
5
|
+
## 0. 在仓库外开发(推荐给 Agent)
|
|
6
|
+
|
|
7
|
+
你不需要 Vetta 的源码仓库,也不需要插件工作台。任意空目录里:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @vetta-org/plugin-cli init --id my-plugin --name "My Plugin"
|
|
11
|
+
cd my-plugin && npm install
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
脚手架会落下一份 `AGENTS.md`,把「先读手册再写代码」这条规矩和构建安装闭环交代清楚。
|
|
15
|
+
|
|
16
|
+
**手册就在工程里**——它随 `@vetta-org/plugin-sdk` 一起装进 `node_modules`:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx vetta-plugin-cli docs # 装完依赖后可用;未装时用 npx @vetta-org/plugin-cli docs + 它对应的 SDK 版本
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**不要硬编码那个路径**:工作区可能把依赖提升到仓库根,一仓多插件时各插件还可能钉不同的
|
|
23
|
+
SDK 版本。这条命令按 Node 的解析规则找,拿回来的永远是当前工程实际编译所针对的那一份。
|
|
24
|
+
|
|
25
|
+
这一点很重要:手册与 SDK 同版本发布,因此它描述的合同**就是你即将编译的合同**。从网络现取
|
|
26
|
+
最新文档做不到这一点——那会教你写出用户宿主还不支持的东西,而 UI 槽位这类缺失不会在构建期
|
|
27
|
+
暴露,装上去只是静默跳过。
|
|
28
|
+
|
|
29
|
+
装进正在运行的 Vetta:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm run install:vetta # = vite build && vetta-plugin pack && vetta-plugin-cli add .
|
|
33
|
+
npx vetta-plugin-cli reload my-plugin # 提示有 pending 版本时
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`add` 传目录即可(`add .`):它向上找到最近的 `plugin.json`,再定位该工程打出来的归档,
|
|
37
|
+
交给正在运行的 Desktop 校验、授权、安装,**不直接写** `~/.vetta/plugins`。
|
|
38
|
+
|
|
39
|
+
### 一仓多插件(能力市场 hub)
|
|
40
|
+
|
|
41
|
+
仓库根有 `.vetta/marketplace.json` 时(如官方能力市场那种布局),命令一律作用于「最近的那个
|
|
42
|
+
插件」,所以先 `cd` 进目标插件目录。在 hub 里 `init` 还会把新插件登记进那份索引——手动维护它
|
|
43
|
+
是最容易漏的一步,插件建好了能装能跑、市场上却看不到。
|
|
44
|
+
|
|
45
|
+
站在 hub 根执行 `add .` 会被拒绝并要求指明插件:一仓多插件时猜一个出来比报错更糟。
|
|
46
|
+
|
|
47
|
+
## 前置条件
|
|
48
|
+
|
|
49
|
+
- Node / Bun(仓库统一用 [Bun](https://bun.sh))。
|
|
50
|
+
- 一个 Vetta 桌面 App(用于安装调试)。
|
|
51
|
+
- 插件用 React 19 + TypeScript + Vite,经 **Module Federation** 打成 remote。
|
|
52
|
+
|
|
53
|
+
## 1. 项目结构
|
|
54
|
+
|
|
55
|
+
一个最小插件项目:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
my-plugin/
|
|
59
|
+
plugin.json # 清单(见 manifest.md)
|
|
60
|
+
ability.json # 可选:能力详情页(见 ability-details.md)
|
|
61
|
+
presentation/ # 可选:详情页 Markdown 与图片
|
|
62
|
+
README.md
|
|
63
|
+
package.json
|
|
64
|
+
tsconfig.json
|
|
65
|
+
vite.config.ts # Module Federation + Tailwind
|
|
66
|
+
src/
|
|
67
|
+
index.tsx # 插件入口:export default definePlugin(...)
|
|
68
|
+
style.css # Tailwind 入口,也可包含插件业务 CSS
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
构建产物(`dist/`)形如:
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
dist/
|
|
75
|
+
mf-manifest.json # MF 清单(plugin.json 的 entry 指向它)
|
|
76
|
+
remoteEntry.js # MF remote 入口
|
|
77
|
+
style.css # Tailwind 生成的 utilities(由入口 import 产出)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## 2. package.json
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"name": "my-plugin",
|
|
85
|
+
"private": true,
|
|
86
|
+
"type": "module",
|
|
87
|
+
"scripts": {
|
|
88
|
+
"dev": "vetta-plugin dev",
|
|
89
|
+
"build": "bunx vite build",
|
|
90
|
+
"check": "bunx tsc --noEmit"
|
|
91
|
+
},
|
|
92
|
+
"devDependencies": {
|
|
93
|
+
"@tailwindcss/vite": "^4.1.12",
|
|
94
|
+
"@types/react": "^19.1.1",
|
|
95
|
+
"@types/react-dom": "^19.1.1",
|
|
96
|
+
"@vetta-org/plugin-sdk": "workspace:*",
|
|
97
|
+
"@vetta-org/plugin-vite": "workspace:*",
|
|
98
|
+
"react": "19.1.1",
|
|
99
|
+
"react-dom": "19.1.1",
|
|
100
|
+
"tailwindcss": "^4.1.12",
|
|
101
|
+
"typescript": "^5.9.2",
|
|
102
|
+
"vite": "^7.1.7"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
> `react` / `react-dom` 仅用于类型与本地构建——运行时由**宿主作为共享单例提供**,不会打进你的 bundle(见 [styling-and-pitfalls.md](./styling-and-pitfalls.md))。`@vetta-org/plugin-sdk` 同理:构建时被 external 化,运行时由宿主提供。可选 UI primitives `@vetta-org/ui`(`Button` / `Dialog` / `Switch`…)同样由宿主单例提供,需要时在 `devDependencies` 加类型依赖即可。仓库内插件用 `workspace:*` 直链源码;仓库外插件改用发布版本号。
|
|
108
|
+
|
|
109
|
+
## 3. vite.config.ts
|
|
110
|
+
|
|
111
|
+
用 `@vetta-org/plugin-vite` 的 `vettaPluginFederation` 封装 Module Federation;**UI 插件请始终接 Tailwind**(样式只走 className,见 [styling-and-pitfalls.md](./styling-and-pitfalls.md)):
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
115
|
+
import { vettaPluginFederation } from "@vetta-org/plugin-vite";
|
|
116
|
+
import { defineConfig } from "vite";
|
|
117
|
+
|
|
118
|
+
export default defineConfig({
|
|
119
|
+
plugins: [
|
|
120
|
+
tailwindcss(),
|
|
121
|
+
vettaPluginFederation({
|
|
122
|
+
name: "my_plugin", // MF remoteName,与 plugin.json.moduleFederation.remoteName 一致
|
|
123
|
+
entry: "./src/index.tsx", // 入口(默认即此)
|
|
124
|
+
expose: "./plugin", // 暴露名(默认 "./plugin",与 plugin.json.moduleFederation.expose 一致)
|
|
125
|
+
// package: true, // 见 §5:构建后自动产出 release/<id>-<version>.zip
|
|
126
|
+
}),
|
|
127
|
+
],
|
|
128
|
+
esbuild: { jsx: "automatic", jsxImportSource: "react" },
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`vettaPluginFederation` 自动:把 `react` / `react-dom` / `@vetta-org/plugin-sdk` / `@vetta-org/ui` 设为 `singleton`、`import:false`(用宿主的),生产构建时把 SDK 与 UI external 化,产出 `mf-manifest.json` + `remoteEntry.js`,CSS 落 `dist/style.css`。
|
|
133
|
+
|
|
134
|
+
它还会在插件 Tailwind 编译前自动接入 plugin-sdk 的宿主主题 Token 契约,因此
|
|
135
|
+
`text-foreground`、`text-muted-foreground/50`、`bg-card` 等语义类可以直接使用;
|
|
136
|
+
无需在插件中导入 Desktop CSS 或手写 `@theme` 映射。
|
|
137
|
+
|
|
138
|
+
## 4. 样式入口 src/style.css
|
|
139
|
+
|
|
140
|
+
插件 CSS 会由 `vettaPluginFederation` 自动限定到插件根节点,并由宿主放入低优先级 layer;
|
|
141
|
+
不需要手写插件 id 前缀或 `@layer`。需要 Tailwind 时可以直接:
|
|
142
|
+
|
|
143
|
+
```css
|
|
144
|
+
@import "tailwindcss";
|
|
145
|
+
|
|
146
|
+
/* 可选:正常编写插件业务 CSS */
|
|
147
|
+
.panel button {
|
|
148
|
+
min-width: 6rem;
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## 5. 入口 src/index.tsx
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
import { definePlugin } from "@vetta-org/plugin-sdk";
|
|
156
|
+
import { useState } from "react";
|
|
157
|
+
import "./style.css";
|
|
158
|
+
|
|
159
|
+
function MyPanel() {
|
|
160
|
+
const [open, setOpen] = useState(true);
|
|
161
|
+
if (!open) return null;
|
|
162
|
+
// 可以使用 Tailwind className,也可以使用插件自己的 CSS
|
|
163
|
+
return (
|
|
164
|
+
<div className="flex flex-col gap-2 p-3 text-sm text-foreground">
|
|
165
|
+
<button
|
|
166
|
+
type="button"
|
|
167
|
+
className="rounded-md border border-border bg-accent px-2 py-1"
|
|
168
|
+
onClick={() => setOpen(false)}
|
|
169
|
+
>
|
|
170
|
+
关闭
|
|
171
|
+
</button>
|
|
172
|
+
</div>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export default definePlugin({
|
|
177
|
+
activate(ctx) {
|
|
178
|
+
// ctx 提供全部能力出口,按需注册贡献 / 调用能力
|
|
179
|
+
ctx.ui.registerGlobalSlot({ id: "root", component: MyPanel });
|
|
180
|
+
const subscription = createMySubscription();
|
|
181
|
+
// cleanup 只属于本次 activation;热更新的新旧实例不会互相清理。
|
|
182
|
+
return () => subscription.dispose();
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`definePlugin` 只是身份函数。`activate()` 可返回 cleanup 函数或 `Disposable`;有状态资源优先使用这种 activation-scoped cleanup。旧插件的模块级 `deactivate()` 仍兼容。也可不用它、直接 `export function activate(ctx) {}` —— 宿主两种形态都认。
|
|
188
|
+
|
|
189
|
+
> **顶层禁用共享依赖(含 JSX)**:MF 的 react / jsx-runtime 是异步填充的,bootstrap 完成前为 `undefined`。模块顶层写 `const ICON = <svg/>` 会在求值时抛 `TypeError: ... is not a function`,整个插件加载失败。把这类 JSX 放进 `activate()` 或组件函数体内。详见 [styling-and-pitfalls.md](./styling-and-pitfalls.md)。
|
|
190
|
+
|
|
191
|
+
## 6. 构建与打包
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
bunx vite build # 产出 dist/(mf-manifest.json + remoteEntry.js + style.css)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
发布需要一个 **zip**:根目录放 `plugin.json`,其下 `dist/`。两种方式:
|
|
198
|
+
|
|
199
|
+
- **自动**:`vettaPluginFederation({ ..., package: true })`,`vite build` 后自动产出 `release/<id>-<version>.zip`(打包 `plugin.json` + `dist/` + 清单声明的 `styles` / `agent.promptPaths` / `agent.skillPaths`;存在 `ability.json` 时也打包它和 `presentation/`)。
|
|
200
|
+
- **手动**:自行把 `plugin.json` 与 `dist/` 一起 zip:
|
|
201
|
+
|
|
202
|
+
```text
|
|
203
|
+
my-plugin.zip
|
|
204
|
+
plugin.json
|
|
205
|
+
ability.json # 可选
|
|
206
|
+
presentation/ # 使用 ability.json 时可选
|
|
207
|
+
README.md
|
|
208
|
+
dist/
|
|
209
|
+
mf-manifest.json
|
|
210
|
+
remoteEntry.js
|
|
211
|
+
style.css
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
> 归档根目录必须有 `plugin.json`,或只含**一个**顶层文件夹、`plugin.json` 在其中。
|
|
215
|
+
> 能力详情是可选的;需要 showcase、功能网格、图片或长篇 Markdown 时见 [ability-details.md](./ability-details.md)。
|
|
216
|
+
|
|
217
|
+
## 7. 安装
|
|
218
|
+
|
|
219
|
+
### GUI
|
|
220
|
+
|
|
221
|
+
通过桌面 App **设置 → 插件**(或独立插件页)安装:
|
|
222
|
+
|
|
223
|
+
- **本地 zip**:选择本地 `.zip` 文件(`installFromArchive`)。
|
|
224
|
+
- **远程 URL**:填写 zip 下载地址(`installFromUrl`)。
|
|
225
|
+
|
|
226
|
+
安装后用户插件落在:
|
|
227
|
+
|
|
228
|
+
```text
|
|
229
|
+
~/.vetta/plugins/<id>/versions/<version>/
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
`listPlugins()` 中每条记录含 **`rootPath`**(该版本包的绝对根路径)。
|
|
233
|
+
|
|
234
|
+
随后在该页**授予声明权限**并启用(缺权限时 API 会抛错或 warn,见 [permissions.md](./permissions.md))。
|
|
235
|
+
|
|
236
|
+
### Agent / 脚本:`install-from-path`(ADR-0042)
|
|
237
|
+
|
|
238
|
+
宿主 Action `plugins.manage`:
|
|
239
|
+
|
|
240
|
+
```json
|
|
241
|
+
{
|
|
242
|
+
"operation": "install-from-path",
|
|
243
|
+
"path": "/abs/path/to/my-plugin-0.1.2.zip"
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
- 路径:本机可读 **`.zip` 绝对路径**(不限 cwd)。
|
|
248
|
+
- 用户确认后:按 `plugin.json` **一次授予声明权限**并默认**启用**。
|
|
249
|
+
- Desktop API:`window.vetta.plugins.installFromPath(path, { grantedPermissions?, enable? })`。
|
|
250
|
+
- 不可覆盖系统插件 id。
|
|
251
|
+
|
|
252
|
+
> **系统插件(presets)**不经此安装流,见 [system-plugins.md](./system-plugins.md)。
|
|
253
|
+
|
|
254
|
+
### 依赖注意(用户机)
|
|
255
|
+
|
|
256
|
+
仓库内 preset / external 可用 `workspace:*` 链本地 SDK。**用户自建工程**应使用发布到 registry 的 `@vetta-org/plugin-sdk` / `@vetta-org/plugin-vite` **semver**(sdk `^0.3.1`,手册随该版本进 node_modules;版本化热更新协议对应 vite `^0.2.0`,两者版本独立)。推出包含该脚手架的 Desktop 前,必须先发布对应的 vite 版本并确认 registry 可达。
|
|
257
|
+
|
|
258
|
+
## 8. 调试闭环(dev loop)
|
|
259
|
+
|
|
260
|
+
1. 插件工作台制作的用户插件首次先点「应用到 Vetta」;安装、授权和启用完成后,工作台会等待工程内的 `vetta-plugin dev` 真正就绪,再把热更新标为运行中。
|
|
261
|
+
2. 后续可在插件工作台开关热更新;开发进程由 Desktop 主进程持有,关闭工作台面板不会中止,不需要另开 `vite build --watch`。
|
|
262
|
+
3. 修改 React 组件或 CSS 后由 Vite HMR 直接更新,组件状态在 Fast Refresh 可保留时不会丢失。
|
|
263
|
+
4. 修改插件入口、`plugin.json`、locale 或 agent 资源时,宿主只替换当前插件的 activation,其他插件不重载。
|
|
264
|
+
5. permissions、commands 等安装态能力需要正式同步时,再重新构建并安装 zip。
|
|
265
|
+
|
|
266
|
+
宿主会一直保留安装版或系统插件 staging 作为稳定基线;工程内开发服务器确认 manifest 可访问并完成插件本地入口模块图转换后,才发送版本化 ready 握手并原子切换到源码 overlay。ready 前的依赖编译失败不会替换当前插件,运行中的服务器异常退出会先回退稳定版本并有限重启。React 等宿主共享依赖仍在 Renderer 的真实 share scope 中加载。
|
|
267
|
+
|
|
268
|
+
`bun run dev` 可单独启动同一个开发服务器并输出 NDJSON 状态,主要用于宿主或工具集成;使用插件工作台时不要重复启动。安装更新版本仍会记为 **pending**,直到 `reload` 才切换正式安装态的 `activeVersion`。
|
|
269
|
+
|
|
270
|
+
开发 Desktop 仓库内的 preset 时,不需要打开插件工作台。`apps/desktop` 的开发启动器默认会为当前
|
|
271
|
+
`VETTA_TENANT` 包含的全部 preset 启动开发服务器;直接运行即可:
|
|
272
|
+
|
|
273
|
+
```powershell
|
|
274
|
+
bun run --cwd apps/desktop dev
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
需要缩小启动范围时,可显式指定逗号分隔的插件 id;显式设置为空字符串则关闭插件开发服务器,回落到
|
|
278
|
+
staging 制品:
|
|
279
|
+
|
|
280
|
+
```powershell
|
|
281
|
+
$env:VETTA_PLUGIN_DEV="git,content-creation"
|
|
282
|
+
bun run --cwd apps/desktop dev
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
仓库外工程使用 `VETTA_PLUGIN_DEV_ROOTS`,多个绝对路径以当前平台的 PATH 分隔符分开。该入口只在未打包的 Desktop 中生效;显式选择但尚未安装的 external 使用纯内存开发记录,退出 App 后不会写入插件注册表。
|
|
286
|
+
|
|
287
|
+
## 下一步
|
|
288
|
+
|
|
289
|
+
- 清单全字段:[manifest.md](./manifest.md)
|
|
290
|
+
- 权限:[permissions.md](./permissions.md)
|
|
291
|
+
- UI 扩展点:[ui-slots.md](./ui-slots.md)
|
|
292
|
+
- 消息卡片:[message-cards.md](./message-cards.md)
|
|
293
|
+
- 对话 / 命令 / 文件 / 图像 / i18n:[conversation-and-agent.md](./conversation-and-agent.md)
|
|
294
|
+
- **MCP 三源聚合**:[mcp.md](./mcp.md)
|
|
295
|
+
- 样式与陷阱:[styling-and-pitfalls.md](./styling-and-pitfalls.md)
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# 引导模型用好你的扩展:重心偏移与优质扩展设计
|
|
2
|
+
|
|
3
|
+
> 适用对象:往仓库里新增 tools / MCP / skills / 系统插件的内部开发者,以及第三方插件开发者。
|
|
4
|
+
> 背景决策:[ADR-0071](../adr/0071-agent-mode-is-a-task-interpretation-prior.md)(工作模式是任务解释的先验)。
|
|
5
|
+
|
|
6
|
+
## 先建立正确的心智模型
|
|
7
|
+
|
|
8
|
+
模型处理一次请求有三个可施力的层:
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
① 解释层 「这句话是什么任务?」——工作模式先验、工作区事实、路线声明在这里博弈
|
|
12
|
+
② 选择层 任务定了之后选哪个工具/skill——由 name + description 的语义匹配决定
|
|
13
|
+
③ 执行层 副作用发生前可不可以拦——execution mode 沙盒、插件权限、工具自身的业务校验
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
设计时应遵循以下约束:
|
|
17
|
+
|
|
18
|
+
1. **不能依靠注册顺序控制工具选择。** 模型会结合请求、上下文与 description 选择工具;`agent_mode`
|
|
19
|
+
字段已整体废弃(解析容忍、语义为零),声明它不会让你的工具在任何模式下更靠前或更靠后。
|
|
20
|
+
2. **模式不隐藏也不降权任何能力。** 你的扩展在 Work / Coding(以及未来的任何模式)下完整可用、
|
|
21
|
+
顺序一致。引导应帮助模型识别真实适用的任务,而不是把不相关请求解释成自己的使用场景。
|
|
22
|
+
3. **description 占用模型上下文。** 工具 description 随工具定义提供,不在系统提示词中重复列出;缓存和计费取决于 Provider。
|
|
23
|
+
完整方法与流程属于 skill(按需加载);description 保留准确的功能摘要、适用任务与必要前提,避免重复长篇教程。
|
|
24
|
+
|
|
25
|
+
## 四个工具引导面
|
|
26
|
+
|
|
27
|
+
前三个作用在工具选择前的语义匹配上;返回值在工具执行后引导后续决策。
|
|
28
|
+
|
|
29
|
+
### 1. name:动词化、具体、无歧义
|
|
30
|
+
|
|
31
|
+
模型对工具名做的第一件事是语义联想。`render_chart` 好于 `chart`;`vetd_create` 带产品前缀避免
|
|
32
|
+
与通用词碰撞;`content_creation_edit` 一眼可知归属与动作。坏名字(`process`、`handle_data`)
|
|
33
|
+
迫使模型完全依赖 description,等于自废一半匹配信号。
|
|
34
|
+
|
|
35
|
+
### 2. description 正向触发段:用用户的语言描述任务
|
|
36
|
+
|
|
37
|
+
触发条件应说明用户想要的结果和被操作的资源,并考虑前文已建立的目标。使用用户会说的任务措辞,
|
|
38
|
+
不要只匹配原话里的主题词,也不要只罗列实现功能:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
✅ Use for a standalone design mockup or edits to an existing design canvas.
|
|
42
|
+
For working UI code in a repository, use that project's framework instead.
|
|
43
|
+
❌ Provides vetd document scaffolding and frame management capabilities.
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
参数上同理:`vetd_create` 要求调用前先判断 `product` 品类,并把判断依据写进参数
|
|
47
|
+
description——「Take it from the user's request in whatever language they wrote it」。
|
|
48
|
+
这把一次容易做错的隐式判断变成了 schema 强制的显式判断。
|
|
49
|
+
|
|
50
|
+
### 3. description 反向触发段:在选择前说明边界
|
|
51
|
+
|
|
52
|
+
这是被最多人忽视、却是重心偏移最关键的一半。想收窄使用场景,**不要**指望模式、排序或任何
|
|
53
|
+
声明式字段——把「什么时候不该用我 + 该用什么替代」直接写进 description。仓库里的范本:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
Do NOT use when the user is writing or modifying code in an existing codebase —
|
|
57
|
+
implement the page directly in that repo's own framework instead.
|
|
58
|
+
Only for standalone visual exploration decoupled from any codebase,
|
|
59
|
+
when the user asked for a design/mockup rather than working code.
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
(`vetd_create`,packages/plugins/presets/vetta-ui-design/src/tools.ts)
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
Do NOT use to read or move media that belongs to the user's codebase — assets a
|
|
66
|
+
repository ships (public/, assets/, src/) stay where they are and are handled
|
|
67
|
+
with the ordinary file tools instead.
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
(`content_creation_assets`)
|
|
71
|
+
|
|
72
|
+
写反向触发段的三条纪律:
|
|
73
|
+
|
|
74
|
+
- **给替代路径,不只给禁令。** "别用我"之后必须跟"改用什么",否则模型在无路可走时还是会用你。
|
|
75
|
+
- **针对真实误调场景写**,不是防御性堆砌。问自己:模型最可能在哪句用户指令上错选我?
|
|
76
|
+
(`vetd_create` 的答案是"写个页面"——注释里就是这么记录的。)
|
|
77
|
+
- **与 mode md 的路线声明对齐措辞。** 宿主的 coding.md 用 "design-exploration tools /
|
|
78
|
+
standalone design documents / canvas mockups" 这类**类别措辞**描述次要路线(宿主永不点名
|
|
79
|
+
具体插件);你的反向触发段把自己归入类别("standalone visual exploration"),两边就接上了。
|
|
80
|
+
这是插件与模式之间唯一的、也是刻意设计的接口:**自然语言归类,而不是字段注册**。
|
|
81
|
+
|
|
82
|
+
工具描述应与真实误调风险匹配:会创建工程、产生费用或对外发送内容的工具,要写清适用场景与替代路径。
|
|
83
|
+
只读工具没有明确误调场景时,不必堆砌防御性说明。这些描述帮助模型选择工具,不构成权限保证。
|
|
84
|
+
|
|
85
|
+
### 4. 工具返回值:被低估的最强引导面
|
|
86
|
+
|
|
87
|
+
description 与返回值承担不同职责:前者帮助选择,后者提供执行后状态和恢复依据。返回值仍可能被截断或压缩,
|
|
88
|
+
不能假定模型每次都完整阅读。范本是 `vetd_status`:
|
|
89
|
+
|
|
90
|
+
- 返回里带 `sharedShell`(已有的公共外壳与组件清单)+ 一句 note:"This design already has
|
|
91
|
+
shared UI — reuse it instead of writing a second copy."。这解决了"模型一屏一屏写、写到第三屏
|
|
92
|
+
早忘了第一屏抽过公共导航"的真实问题——**在返回值里把该复用的东西端到眼前,比在 skill 里
|
|
93
|
+
多写一段规则管用**,因为返回值是每次都会读的。
|
|
94
|
+
- note 按「此刻最该做的一件事」给,不叠加——三条并列的建议等于没有重点。
|
|
95
|
+
- 出错时返回可执行的下一步("Fix these first, with a targeted edit at the reported line"),
|
|
96
|
+
而不是裸错误。
|
|
97
|
+
|
|
98
|
+
设计返回值时问自己:模型拿到这个返回后,下一步最容易做错什么?把纠偏信息放在那里。
|
|
99
|
+
|
|
100
|
+
## 执行边界
|
|
101
|
+
|
|
102
|
+
工具注册不再提供副作用等级,也不再触发会话首次调用确认。宿主不会替插件依据工具名分类或请求整段会话授权。
|
|
103
|
+
沙盒、插件权限、输入校验和工具已有的业务确认继续独立生效,例如内容生成计划仍须由用户在全局弹窗确认。
|
|
104
|
+
工具描述应说明实际会发生的文件写入、费用和外部动作,不能承诺宿主会自动拦截误调用。
|
|
105
|
+
|
|
106
|
+
## Skill 的引导设计
|
|
107
|
+
|
|
108
|
+
Skill 走渐进披露:清单里只有 name + description(每轮都在 prompt 里),正文按 `invoke_skill`
|
|
109
|
+
加载(只在被选中时付费)。所以:
|
|
110
|
+
|
|
111
|
+
- **description 是功能与选择入口**,完整描述 Skill 自身能完成的任务和专业方法。
|
|
112
|
+
不要为了防误调用删减功能、把创意与审查能力限定为已有工作流,或要求用户显式点名 Skill。任务匹配应由选择层结合用户目标与上下文完成。
|
|
113
|
+
- **规则、流程、约束全部放正文。** 任何"你想让模型每次用你的工具前都知道"的长内容,都属于
|
|
114
|
+
skill 正文而不是工具 description;用工具 description 的一句话把模型引到 skill
|
|
115
|
+
("invoke the vetta-ui-design skill for the rules before writing any of them")。
|
|
116
|
+
- frontmatter 的 `agent_mode` 已废弃,写了会被忽略。
|
|
117
|
+
- 选用相关 Skill 后,正常执行完成用户目标所需的步骤;加载方法本身不代表授权执行与目标无关的动作。
|
|
118
|
+
|
|
119
|
+
## App Action 的发现说明
|
|
120
|
+
|
|
121
|
+
App Action 还应声明 `usage.target`、`useWhen`、`avoidWhen`、`alternatives`,随 search / describe 返回。
|
|
122
|
+
例如 Vetta 主题设置与网站深色模式、Vetta 定时 Agent 与业务 cron、插件安装与插件源码开发,需要明确区分。
|
|
123
|
+
检索候选不是执行指令;查询/解释不等于修改,不要先调用写入 Action 再把意图判断交给审批框。
|
|
124
|
+
usage 不参与权限决策,也不进入正向检索文本。合同见 [app-actions.md](./app-actions.md#模型选择边界)。
|
|
125
|
+
|
|
126
|
+
## 系统插件 / 宿主侧作者的额外杠杆
|
|
127
|
+
|
|
128
|
+
以下两个面属于宿主,第三方插件碰不到;内部开发者新增一条"路线级"能力(新的画布、新的重交付
|
|
129
|
+
形态)时应当考虑:
|
|
130
|
+
|
|
131
|
+
1. **mode md 的路线段**(`apps/desktop/src/main/agent-modes/modes/*.md`):如果你的能力构成一条
|
|
132
|
+
与"仓库内写代码 / 文档交付"并列的新路线,需要在相关模式的路线段用**类别措辞**声明它的
|
|
133
|
+
默认位次与准入条件(参考 coding.md 的 "Default route for UI work" 段)。纪律:永不出现具体
|
|
134
|
+
工具名或插件名——宿主描述任务类别,插件用 description 自归类。
|
|
135
|
+
2. **工作区事实探测**(`packages/coding-agent/src/model-context/workspace-facts.ts`):为模型提供实际存在的项目形态与资源。
|
|
136
|
+
文件或画布的存在只能辅助识别上下文,不能单凭标记就推断用户希望编辑它,更不能据此创建或运行新任务。
|
|
137
|
+
注意它在会话创建时探测一次并固化。
|
|
138
|
+
|
|
139
|
+
## 第三方插件作者的边界
|
|
140
|
+
|
|
141
|
+
- 你**不能**改 mode md、workspace facts——你的全部引导面是:name、
|
|
142
|
+
description(正反触发段)、参数 schema、返回值、skill。这个约束是
|
|
143
|
+
刻意的:它保证任何插件都不能为自己抢占解释层。
|
|
144
|
+
- `ctx.getAgentMode()` 读到的是**新会话默认模式**,不是当前会话固化的模式——只能用于
|
|
145
|
+
展示层软性定制(不同文案、不同默认视图),**禁止**在 tool / hook handler 里用它做行为
|
|
146
|
+
分支(handler 可能跑在一个模式与默认值不同的会话里)。未知模式 id 一律按通用处理。
|
|
147
|
+
- Hook 按 `scope_use` + 事件/工具 matcher 触发,与模式无关——你的 hook 在所有模式下都会跑,
|
|
148
|
+
按此设计幂等性。
|
|
149
|
+
|
|
150
|
+
## 反模式清单
|
|
151
|
+
|
|
152
|
+
| 反模式 | 为什么无效/有害 | 正解 |
|
|
153
|
+
| --- | --- | --- |
|
|
154
|
+
| 写 `agent_mode` 期待模式偏好 | 字段语义为零,纯装饰 | 反向触发段自归类 |
|
|
155
|
+
| 把使用规则塞进 description | 每轮计费、稀释触发信号 | 规则进 skill 正文,description 引流 |
|
|
156
|
+
| 用 `toolPolicy.deny` 表达"不推荐" | deny 是硬闸,模型连看都看不到 | 反向触发段 + 替代路径 |
|
|
157
|
+
| 依赖清单位置/注册顺序 | 无法稳定表达适用条件 | 明确目标与排除场景 |
|
|
158
|
+
| handler 里读 `getAgentMode()` 分支行为 | 读的是默认值不是会话值,必然出错 | 行为分支由输入参数或工作区状态驱动 |
|
|
159
|
+
| 返回裸数据/裸错误 | 浪费每轮必读的引导面 | 返回值带"下一步该做什么" |
|
|
160
|
+
|
|
161
|
+
## 发布前自检清单
|
|
162
|
+
|
|
163
|
+
- [ ] 工具名是动词化的具体名,带产品前缀避免碰撞
|
|
164
|
+
- [ ] description 有正向触发段(用户任务措辞)
|
|
165
|
+
- [ ] 有真实误调场景的,写了反向触发段 + 替代路径
|
|
166
|
+
- [ ] Skill frontmatter 完整保留自身功能,不以收窄能力替代选择判断;App Action 的 search / describe 能看到 usage
|
|
167
|
+
- [ ] 成对验证相似措辞下不同目标的选择,以及查询不会自动变成修改;合同测试不冒充模型效果评估
|
|
168
|
+
- [ ] 长规则在 skill 正文,description 不超过一段
|
|
169
|
+
- [ ] 返回值在引导下一步,错误返回可执行
|
|
170
|
+
- [ ] 描述写明实际副作用,已有业务确认与权限校验仍然有效
|
|
171
|
+
- [ ] 没有写 `agent_mode`,没有在 handler 里读 `getAgentMode()`
|
|
172
|
+
- [ ] (宿主侧)构成新路线的能力更新了 mode md 类别措辞 / facts 探测,并保持零插件点名
|
|
173
|
+
|
|
174
|
+
## Vetta 扩展设计的八荣八耻
|
|
175
|
+
|
|
176
|
+
- 以描述任务为荣,以罗列功能为耻。
|
|
177
|
+
- 以指明何时别用为荣,以逢场必荐自己为耻。
|
|
178
|
+
- 以返回值引路为荣,以裸数据甩锅为耻。
|
|
179
|
+
- 以规则归入 skill 为荣,以 description 灌水为耻。
|
|
180
|
+
- 以说明实际影响为荣,以隐瞒费用和外部动作为耻。
|
|
181
|
+
- 以类别措辞自归类为荣,以字段注册求偏爱为耻。
|
|
182
|
+
- 以一句 note 点睛为荣,以三条建议并列为耻。
|
|
183
|
+
- 以放手让模型判断为荣,以硬闸藏匿能力为耻。
|