@vetta-org/plugin-sdk 0.2.0 → 0.3.1

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.
Files changed (81) hide show
  1. package/dist/agent.d.ts +0 -12
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js.map +1 -1
  4. package/dist/ai.d.ts +13 -0
  5. package/dist/ai.d.ts.map +1 -1
  6. package/dist/ai.js.map +1 -1
  7. package/dist/app-actions.d.ts +0 -1
  8. package/dist/app-actions.d.ts.map +1 -1
  9. package/dist/app-actions.js.map +1 -1
  10. package/dist/cli-provider.d.ts +18 -0
  11. package/dist/cli-provider.d.ts.map +1 -0
  12. package/dist/cli-provider.js +2 -0
  13. package/dist/cli-provider.js.map +1 -0
  14. package/dist/context.d.ts +13 -2
  15. package/dist/context.d.ts.map +1 -1
  16. package/dist/context.js.map +1 -1
  17. package/dist/index.d.ts +10 -5
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/manifest-schema.d.ts +454 -51
  22. package/dist/manifest-schema.d.ts.map +1 -1
  23. package/dist/manifest-schema.js +139 -33
  24. package/dist/manifest-schema.js.map +1 -1
  25. package/dist/manifest.d.ts +2 -2
  26. package/dist/manifest.d.ts.map +1 -1
  27. package/dist/manifest.js +164 -38
  28. package/dist/manifest.js.map +1 -1
  29. package/dist/models.d.ts +35 -0
  30. package/dist/models.d.ts.map +1 -0
  31. package/dist/models.js +2 -0
  32. package/dist/models.js.map +1 -0
  33. package/dist/ocr.d.ts +78 -0
  34. package/dist/ocr.d.ts.map +1 -0
  35. package/dist/ocr.js +2 -0
  36. package/dist/ocr.js.map +1 -0
  37. package/dist/official.d.ts +1 -0
  38. package/dist/official.d.ts.map +1 -1
  39. package/dist/official.js.map +1 -1
  40. package/dist/permissions.d.ts +1 -1
  41. package/dist/permissions.d.ts.map +1 -1
  42. package/dist/permissions.js +8 -0
  43. package/dist/permissions.js.map +1 -1
  44. package/dist/secrets.d.ts +27 -0
  45. package/dist/secrets.d.ts.map +1 -0
  46. package/dist/secrets.js +2 -0
  47. package/dist/secrets.js.map +1 -0
  48. package/dist/service-provider.d.ts +62 -0
  49. package/dist/service-provider.d.ts.map +1 -0
  50. package/dist/service-provider.js +2 -0
  51. package/dist/service-provider.js.map +1 -0
  52. package/dist/storage.d.ts +39 -6
  53. package/dist/storage.d.ts.map +1 -1
  54. package/dist/storage.js +8 -1
  55. package/dist/storage.js.map +1 -1
  56. package/dist/ui.d.ts +118 -7
  57. package/dist/ui.d.ts.map +1 -1
  58. package/dist/ui.js.map +1 -1
  59. package/docs/.source.json +22 -0
  60. package/docs/README.md +104 -0
  61. package/docs/ability-details.md +424 -0
  62. package/docs/ai.md +121 -0
  63. package/docs/app-actions.md +111 -0
  64. package/docs/browser.md +80 -0
  65. package/docs/conversation-and-agent.md +619 -0
  66. package/docs/file-explorer.md +118 -0
  67. package/docs/getting-started.md +295 -0
  68. package/docs/guiding-the-agent.md +183 -0
  69. package/docs/manifest.md +296 -0
  70. package/docs/mcp.md +152 -0
  71. package/docs/media.md +141 -0
  72. package/docs/message-cards.md +188 -0
  73. package/docs/permissions.md +145 -0
  74. package/docs/styling-and-pitfalls.md +254 -0
  75. package/docs/system-plugins.md +58 -0
  76. package/docs/ui-slots.md +628 -0
  77. package/package.json +7 -3
  78. package/dist/settings.d.ts +0 -13
  79. package/dist/settings.d.ts.map +0 -1
  80. package/dist/settings.js +0 -2
  81. package/dist/settings.js.map +0 -1
@@ -0,0 +1,424 @@
1
+ # 能力详情页(ability.json)
2
+
3
+ `ability.json` 是插件在 Desktop「能力」页里的**可选展示描述**。它不参与插件加载,也不授予权限:
4
+
5
+ - `plugin.json` 决定插件身份、入口、权限、命令和 Agent 贡献;
6
+ - `ability.json` 只决定能力详情中的介绍内容;
7
+ - 安装、启停、权限、命令、版本和贡献项仍由宿主固定渲染,详情文件不能覆盖这些交互。
8
+
9
+ 没有 `ability.json` 的插件仍能正常安装和运行。能力页会显示 `plugin.json` 提供的名称、简介、图标、作者,
10
+ 以及宿主自动生成的权限和贡献信息,但不会出现 showcase、功能网格或长篇说明。
11
+
12
+ ## 推荐目录
13
+
14
+ ```text
15
+ my-plugin/
16
+ ├── plugin.json
17
+ ├── ability.json
18
+ ├── presentation/
19
+ │ ├── README.md
20
+ │ ├── README.en.md
21
+ │ └── preview.webp
22
+ ├── src/
23
+ └── dist/
24
+ ```
25
+
26
+ `@vetta-org/plugin-vite` 发现根目录的 `ability.json` 后,会把它和整个 `presentation/` 目录打进插件 zip。
27
+ 因此,插件的 Markdown 与图片展示资源应放在 `presentation/` 下。
28
+
29
+ ## 最小详情
30
+
31
+ 身份字段必须和 `plugin.json` 保持一致:`slug = plugin.json.id`,`version = plugin.json.version`。
32
+
33
+ ```json
34
+ {
35
+ "schemaVersion": 1,
36
+ "type": "plugin",
37
+ "slug": "my-plugin",
38
+ "version": "0.1.0",
39
+ "detail": {
40
+ "blocks": [
41
+ {
42
+ "type": "markdown",
43
+ "path": "presentation/README.md"
44
+ }
45
+ ]
46
+ }
47
+ }
48
+ ```
49
+
50
+ Markdown 区块可以二选一:
51
+
52
+ ```json
53
+ { "type": "markdown", "content": "## 使用方法\n\n直接写短内容。" }
54
+ ```
55
+
56
+ ```json
57
+ { "type": "markdown", "path": "presentation/README.md" }
58
+ ```
59
+
60
+ `content` 与 `path` 必须且只能提供一个。长正文优先使用 `path`,避免在 JSON 字符串里手工转义换行、引号和代码块。
61
+ 路径相对插件根目录解析,不能是绝对路径,也不能用 `..` 越出插件目录。
62
+
63
+ ## 推荐的丰富详情
64
+
65
+ ```json
66
+ {
67
+ "schemaVersion": 1,
68
+ "type": "plugin",
69
+ "slug": "my-plugin",
70
+ "version": "0.1.0",
71
+ "detail": {
72
+ "blocks": [
73
+ {
74
+ "type": "showcase",
75
+ "showcase": {
76
+ "template": "workbench",
77
+ "canvas": "code",
78
+ "user_prompt": "检查这个页面在手机上的效果。",
79
+ "assistant_reply": "我会打开预览并检查响应式布局。"
80
+ }
81
+ },
82
+ {
83
+ "type": "feature-grid",
84
+ "title": "主要能力",
85
+ "items": [
86
+ {
87
+ "title": "实时预览",
88
+ "description": "在工作区中直接查看页面。",
89
+ "icon": "solar:monitor-smartphone-linear"
90
+ },
91
+ {
92
+ "title": "继续交给 Agent",
93
+ "description": "把检查结果带回当前任务继续修改。",
94
+ "icon": "solar:magic-stick-3-linear"
95
+ }
96
+ ]
97
+ },
98
+ {
99
+ "type": "markdown",
100
+ "path": "presentation/README.md"
101
+ },
102
+ {
103
+ "type": "links",
104
+ "title": "相关资源",
105
+ "items": [
106
+ { "label": "使用文档", "href": "https://example.com/docs" }
107
+ ]
108
+ }
109
+ ],
110
+ "meta": [
111
+ { "key": "homepage", "value": "https://example.com" },
112
+ { "key": "repository", "value": "https://github.com/example/my-plugin" }
113
+ ]
114
+ }
115
+ }
116
+ ```
117
+
118
+ ## 区块参考
119
+
120
+ 区块按数组顺序渲染。宿主只接受下列白名单类型,不执行包内 HTML、JavaScript、CSS、iframe 或自定义操作。
121
+ 如果需要兼容尚未支持新区块的旧版客户端,请在整页 `format: "blocks"` 声明中提供 `fallback` Markdown;旧版校验失败时会回退到该文件。
122
+
123
+ ### hero
124
+
125
+ 封面承诺,适合详情页开头。它只描述一句主张、徽章和可选配图;宿主把它画成带侧线的引言,而不是能力清单或右侧 Logo 栏。
126
+
127
+ ```json
128
+ {
129
+ "type": "hero",
130
+ "eyebrow": "REAL BROWSER AUTOMATION",
131
+ "title": "让 Agent 在你看得见的浏览器里工作",
132
+ "description": "复用登录态,完成多步骤网页任务;提交前始终确认。",
133
+ "image": "presentation/preview.webp",
134
+ "image_alt": "在可见窗口中完成登录",
135
+ "layout": "stacked",
136
+ "badges": ["可见窗口", "会话隔离"]
137
+ }
138
+ ```
139
+
140
+ `layout` 可选 `stacked`(默认:主张在上、配图在下)或 `split`(有配图时文案与图片左右分栏)。没有 `image` 时不会留下空栏。配图必须是场景静帧;`icon.png` / `logo.svg` 以及与能力图标同一张图会被宿主忽略,避免再画一遍页头 Logo。
141
+
142
+ ### stats
143
+
144
+ 用少量数字或短词概括适用范围、规模和关键约束。宿主把 `value` 放进色块,右边跟标签和说明,排成度量行;不拉成通栏 KPI,也不需要填写列数。
145
+
146
+ ```json
147
+ {
148
+ "type": "stats",
149
+ "title": "适合哪些任务",
150
+ "items": [
151
+ { "value": "真实", "label": "页面环境", "description": "不是静态 HTML" },
152
+ { "value": "多步", "label": "任务流程" },
153
+ { "value": "可控", "label": "关键动作", "description": "提交前人工确认" }
154
+ ]
155
+ }
156
+ ```
157
+
158
+ 最多 6 项。`value` 和 `label` 必填,`description` 可省略。
159
+
160
+ ### gallery
161
+
162
+ 展示多张界面截图或流程图。图片可以是插件包内的 `presentation/**` 文件,也可以是 HTTPS 地址;不接受 HTML、iframe 或脚本。
163
+
164
+ ```json
165
+ {
166
+ "type": "gallery",
167
+ "title": "工作流预览",
168
+ "items": [
169
+ {
170
+ "src": "presentation/step-1.webp",
171
+ "alt": "打开网站并等待登录",
172
+ "caption": "1. 在可见窗口中完成登录"
173
+ },
174
+ {
175
+ "src": "presentation/step-2.webp",
176
+ "alt": "读取页面快照",
177
+ "caption": "2. Agent 根据页面结构定位内容"
178
+ }
179
+ ]
180
+ }
181
+ ```
182
+
183
+ 图片按自适应网格排列,最多 8 张;`alt` 和 `caption` 可省略,但建议为截图提供有意义的 `alt`。
184
+
185
+ ### comparison
186
+
187
+ 解释两种方式、适用边界或「之前 / 之后」。左右是两份独立论点,宿主不会按行一一对应。`tone` 决定这一列是不是选中面板:中性列是带减号的未选列表,强调列是带勾选和主色描边的选中面板。默认左列 `neutral`、右列 `accent`。
188
+
189
+ ```json
190
+ {
191
+ "type": "comparison",
192
+ "title": "从查资料到完成任务",
193
+ "left": {
194
+ "title": "只做网页搜索",
195
+ "items": ["返回搜索结果", "遇到登录态就中断"]
196
+ },
197
+ "right": {
198
+ "title": "使用 Browser",
199
+ "tone": "accent",
200
+ "items": ["打开真实网站", "提交前交还人工确认"]
201
+ }
202
+ }
203
+ ```
204
+
205
+ `tone` 可选 `neutral` 或 `accent`;默认左列为 `neutral`、右列为 `accent`。每列最多 8 条。
206
+
207
+ ### feature-grid
208
+
209
+ 能力清单:并列主张,不是先后步骤。宿主按短条目自动并排,不需要填写列数。`items` 至少一项,图标可省略;图标支持 `solar:` 或包内/HTTPS 图片。
210
+
211
+ ```json
212
+ {
213
+ "type": "feature-grid",
214
+ "title": "主要能力",
215
+ "items": [
216
+ { "title": "读取页面", "description": "提取页面结构与文字。", "icon": "solar:document-text-linear" }
217
+ ]
218
+ }
219
+ ```
220
+
221
+ ### steps
222
+
223
+ 有先后顺序的流程。宿主画成带序号和连线的步骤轨,文案一次全部可见。
224
+
225
+ ```json
226
+ {
227
+ "type": "steps",
228
+ "title": "开始使用",
229
+ "items": [
230
+ { "title": "安装插件" },
231
+ { "title": "授予权限", "description": "只开启任务实际需要的权限。" }
232
+ ]
233
+ }
234
+ ```
235
+
236
+ ### showcase
237
+
238
+ 宿主生成的场景头图,不是真实截图,也不能由插件注入 CSS。插件只选择 `template`、`canvas` 和文案;
239
+ 窗体外形、舞台和对话样式全部由 Desktop 绘制。
240
+
241
+ `template` 决定构图,不只是「一问一答」:
242
+
243
+ | template | 构图 |
244
+ | --- | --- |
245
+ | `canvas-hero` | 大号产品窗口 + 一句说明;提示词收成角标 |
246
+ | `prompt-result` | 左侧提示词卡片,右侧变成产物窗口 |
247
+ | `spotlight` | 居中命令面板:检索条 + 高亮结果 |
248
+ | `workbench` | 迷你工作台:活动栏 + 窗口 + 助手批注 |
249
+ | `chat-over-canvas` | 产品窗口为主角,对话作为附注 |
250
+ | `chat-thread` | 完整会话窗口(顶栏、消息、输入条) |
251
+
252
+ 需要产品窗口的模板再选 `canvas`。每种 canvas 是可辨认的窗体外形,不是同一外壳里换几根色条:
253
+
254
+ | canvas | 窗体 |
255
+ | --- | --- |
256
+ | `design` | 点状画板 + 带控制点的 Frame |
257
+ | `code` | 编辑器:文件页签、行号、状态栏 |
258
+ | `docs` | 纸页文档 + 清单 |
259
+ | `browser` | 浏览器:标签、地址栏、页面列表 |
260
+ | `terminal` | 深色终端与提示符 |
261
+ | `board` | 三列看板 |
262
+ | `generic` | 指标卡 + 趋势图的仪表盘 |
263
+
264
+ ```json
265
+ {
266
+ "type": "showcase",
267
+ "showcase": {
268
+ "template": "canvas-hero",
269
+ "canvas": "browser",
270
+ "brand_name": "Orders",
271
+ "user_prompt": "打开后台订单页。",
272
+ "assistant_reply": "我会在真实浏览器里读取页面,提交前先停下来确认。"
273
+ }
274
+ }
275
+ ```
276
+
277
+ `user_prompt` 与 `assistant_reply` 在非对话模板里也会用到:分别作为提示词/检索条和结果说明。
278
+ 可选 `brand_name`、`brand_icon_url` 会出现在窗体标题或页签上。
279
+
280
+ ### image
281
+
282
+ 展示真实图片。包内资源建议放进 `presentation/`;也可使用 HTTPS 图片。
283
+
284
+ ```json
285
+ {
286
+ "type": "image",
287
+ "src": "presentation/preview.webp",
288
+ "alt": "插件界面预览",
289
+ "caption": "工作区主界面"
290
+ }
291
+ ```
292
+
293
+ ### callout
294
+
295
+ 提示块;`tone` 支持 `info`、`success`、`warning`。
296
+
297
+ ```json
298
+ {
299
+ "type": "callout",
300
+ "tone": "info",
301
+ "title": "首次使用",
302
+ "content": "启用前需要完成本地运行时安装。"
303
+ }
304
+ ```
305
+
306
+ ### markdown
307
+
308
+ Markdown 正文,支持内联 `content` 或包内文件 `path`。代码块由宿主统一高亮。
309
+
310
+ ```json
311
+ { "type": "markdown", "path": "presentation/README.md" }
312
+ ```
313
+
314
+ ### links
315
+
316
+ HTTP(S) 外链按钮。
317
+
318
+ ```json
319
+ {
320
+ "type": "links",
321
+ "title": "继续阅读",
322
+ "items": [{ "label": "文档", "href": "https://example.com/docs" }]
323
+ }
324
+ ```
325
+
326
+ ## 整页引用文件
327
+
328
+ 如果详情只有 Markdown,不需要 `blocks`:
329
+
330
+ ```json
331
+ {
332
+ "schemaVersion": 1,
333
+ "type": "plugin",
334
+ "slug": "my-plugin",
335
+ "version": "0.1.0",
336
+ "detail": {
337
+ "format": "markdown",
338
+ "path": "presentation/README.md"
339
+ }
340
+ }
341
+ ```
342
+
343
+ 也可以把全部结构化区块放进独立 JSON:
344
+
345
+ ```json
346
+ {
347
+ "detail": {
348
+ "format": "blocks",
349
+ "path": "presentation/detail.json",
350
+ "fallback": "presentation/README.md"
351
+ }
352
+ }
353
+ ```
354
+
355
+ 此时 `presentation/detail.json` 的格式为:
356
+
357
+ ```json
358
+ {
359
+ "schemaVersion": 1,
360
+ "blocks": [
361
+ { "type": "markdown", "path": "presentation/README.md" }
362
+ ]
363
+ }
364
+ ```
365
+
366
+ `fallback` 只在结构化详情文件无法读取或校验失败时生效。
367
+
368
+ ## 多语言
369
+
370
+ 详情页的 `i18n` 与 `plugin.json` 的 `%catalogKey%`/`locales/*.json` 是两套合同。详情文件不解析
371
+ `%catalogKey%`;应在 `ability.json#detail.i18n` 中提供本地化内容或文件路径。
372
+
373
+ ```json
374
+ {
375
+ "detail": {
376
+ "blocks": [
377
+ { "type": "markdown", "path": "presentation/README.md" }
378
+ ],
379
+ "i18n": {
380
+ "en": {
381
+ "blocks": [
382
+ { "type": "markdown", "path": "presentation/README.en.md" }
383
+ ]
384
+ }
385
+ }
386
+ }
387
+ }
388
+ ```
389
+
390
+ 本地化字段采用**整体覆盖**:一旦 `i18n.en.blocks` 存在,它会替换默认的整个 `blocks` 数组,不做逐项合并。
391
+ 因此,多语言丰富详情需要在每个 locale 中给出完整区块序列。
392
+
393
+ ## 元信息
394
+
395
+ `detail.meta` 是有序数组。预置 `key` 支持 `homepage`、`repository`、`docs`、`license`;也可使用
396
+ `label` 创建自定义文本项。以 `http://` 或 `https://` 开头的值会渲染成链接。
397
+
398
+ ```json
399
+ {
400
+ "meta": [
401
+ { "key": "docs", "value": "https://example.com/docs" },
402
+ { "label": "维护团队", "value": "Example Team" }
403
+ ]
404
+ }
405
+ ```
406
+
407
+ ## 安全与大小限制
408
+
409
+ - `ability.json` 最大 64 KiB;
410
+ - 单个 Markdown/结构化详情文件最大 512 KiB;
411
+ - 单张本地图片最大 8 MiB;
412
+ - 包内引用必须留在插件根目录;
413
+ - 图片只接受 AVIF、GIF、ICO、JPEG、JPG、PNG、SVG、WebP;
414
+ - 外部图片只接受 HTTPS;链接按钮接受 HTTP(S);
415
+ - 详情损坏不会阻断插件或能力页启动,宿主会忽略该插件的自定义介绍并记录诊断日志。
416
+
417
+ ## 发布前检查
418
+
419
+ - `ability.json` 的 `slug`、`version` 与 `plugin.json` 完全一致;
420
+ - 长 Markdown 使用 `path`,文件位于 `presentation/`;
421
+ - `i18n` 中的 `blocks` 是完整数组;
422
+ - 图片路径大小写与归档中的真实文件一致;
423
+ - `bunx vite build` 生成的 zip 包含 `ability.json` 和 `presentation/**`;
424
+ - 安装后在「能力 → 我的」打开插件详情,核对宿主自动生成的权限和贡献项是否符合预期。
package/docs/ai.md ADDED
@@ -0,0 +1,121 @@
1
+ # AI 文本能力
2
+
3
+ `ctx.ai` 让插件调用用户已经在 Vetta 中配置的文本模型。模型发现、默认模型解析、API Key 与登录凭据注入、实际请求都由 Desktop 主进程负责;插件只能看到脱敏后的模型描述与完成结果。
4
+
5
+ ## 权限
6
+
7
+ - `ai.models.list`:调用 `ctx.ai.listModels()`。
8
+ - `ai.complete`:调用 `ctx.ai.complete()`、`ctx.ai.stream()` 或 `ctx.ai.chat()`,可能产生模型费用或消耗用户额度。
9
+
10
+ 两项权限独立。只知道固定模型标识的插件可以仅声明 `ai.complete`;需要展示模型选择器时再同时声明 `ai.models.list`。
11
+
12
+ ## 列出模型
13
+
14
+ ```ts
15
+ const { defaultModel, models } = await ctx.ai.listModels();
16
+
17
+ const options = models.map((model) => ({
18
+ value: model.modelKey,
19
+ label: model.name,
20
+ provider: model.provider,
21
+ }));
22
+ ```
23
+
24
+ 列表只包含当前可用且支持文本输入的模型。`modelKey` 使用 `provider/model` 格式;`defaultModel` 只有在用户设置的默认模型当前可用时才返回,否则为 `null`。
25
+
26
+ ## 完成文本
27
+
28
+ ```ts
29
+ const result = await ctx.ai.complete({
30
+ modelKey: selectedModelKey,
31
+ systemPrompt: "在不改变含义的前提下优化用户提示词。只返回优化结果。",
32
+ prompt: userPrompt,
33
+ temperature: 0.3,
34
+ maxTokens: 1200,
35
+ });
36
+
37
+ console.log(result.text, result.usage.totalTokens);
38
+ ```
39
+
40
+ `modelKey` 可省略,此时宿主使用用户明确设置且当前可用的默认模型;没有可用默认模型时调用会失败。`reasoning` 只会传给声明支持推理的模型,`maxTokens` 不会超过该模型自身的输出上限。
41
+
42
+ `complete` 是单轮契约(`systemPrompt + prompt`),不接受工具或图片。多轮对话使用下方的 `chat`;插件提供 API Key 仍然不被接受——凭据永远由宿主注入。
43
+
44
+ ## 流式完成
45
+
46
+ `ctx.ai.stream()` 与 `complete()` 接受相同请求并返回相同的最终结果,但会在生成期间通过
47
+ `onTextDelta` 交付增量文本。`delta` 是本次新增片段,`text` 是截至当前事件的完整文本;UI 通常直接使用
48
+ `text` 更新同一条消息,完成后再使用 Promise 返回值保存最终结果。
49
+
50
+ ```ts
51
+ const controller = new AbortController();
52
+
53
+ const result = await ctx.ai.stream(
54
+ {
55
+ modelKey: selectedModelKey,
56
+ systemPrompt: "使用 Markdown 回答;公式使用 LaTeX。",
57
+ prompt: userPrompt,
58
+ maxTokens: 1600,
59
+ },
60
+ {
61
+ signal: controller.signal,
62
+ onTextDelta: ({ text }) => updatePreview(text),
63
+ },
64
+ );
65
+
66
+ await saveAnswer(result.text);
67
+ ```
68
+
69
+ 传入的 `AbortSignal` 取消时,宿主会中止主进程中的 Provider 请求,而不只是停止 UI 更新。事件与最终结果
70
+ 仍经过 Capability Schema 校验;模型选择、凭据、额度、错误与 usage 语义均和 `complete()` 相同。
71
+
72
+ ## 多轮对话 chat
73
+
74
+ `ctx.ai.chat()` 是**无状态**的多轮文本完成:宿主不保存任何会话状态,插件自己持有完整消息转写(需要跨重启保留时配合 `ctx.storage` 持久化),每次调用都发送全量 `messages`。权限沿用 `ai.complete`。当前 `chat()` 只返回完整结果;需要边生成边展示的单轮文本使用 `stream()`。
75
+
76
+ ```ts
77
+ const messages: PluginAiChatMessage[] = [
78
+ { role: "user", content: "轮到你走棋了。当前局面:…" },
79
+ ];
80
+
81
+ const result = await ctx.ai.chat({
82
+ modelKey: selectedModelKey, // 可省略,同 complete 的默认模型解析
83
+ systemPrompt: "你是中国象棋棋手。",
84
+ messages,
85
+ tools: [
86
+ {
87
+ name: "make_move",
88
+ description: "落子。走法使用 ICCS 坐标,如 h2e2。",
89
+ parameters: {
90
+ type: "object",
91
+ properties: { move: { type: "string" } },
92
+ required: ["move"],
93
+ },
94
+ },
95
+ ],
96
+ });
97
+ ```
98
+
99
+ - `messages` 为全量转写,元素是 `user` / `assistant` / `toolResult` 三种角色;`assistant` 消息可携带其历史 `toolCalls`,`toolResult` 通过 `toolCallId` 与之对应。
100
+ - `tools` 是**插件内部工具**:只对本次请求可见,模型触发时宿主不执行任何东西,只把 `toolCalls` 原样返回(`stopReason: "toolUse"`)。插件自行执行,把结果作为 `toolResult` 消息追加进 `messages` 后再次调用 `chat`,形成插件内部 loop。这类工具**不会**注册进宿主 Agent,不影响正常会话。
101
+ - `temperature` / `maxTokens` / `reasoning` 语义与 `complete` 一致。
102
+
103
+ 典型 loop:
104
+
105
+ ```ts
106
+ for (;;) {
107
+ const turn = await ctx.ai.chat({ systemPrompt, messages, tools });
108
+ messages.push({ role: "assistant", content: turn.text, toolCalls: turn.toolCalls });
109
+ if (turn.stopReason !== "toolUse") break;
110
+ for (const call of turn.toolCalls) {
111
+ const outcome = runLocalTool(call); // 插件内部执行,例如校验并落子
112
+ messages.push({
113
+ role: "toolResult",
114
+ toolCallId: call.id,
115
+ toolName: call.name,
116
+ content: outcome.text,
117
+ isError: outcome.isError,
118
+ });
119
+ }
120
+ }
121
+ ```
@@ -0,0 +1,111 @@
1
+ # 动态 App Action
2
+
3
+ 插件可在 `activate(ctx)` 中调用 `ctx.appActions.register()`,把 Action 动态加入 Desktop 的 Action 目录。宿主会先暂存本次 activation 的全部 Action,待 `activate` 和所有注册都成功后一次发布;任一注册失败则整次 activation 回滚。插件重载、停用、卸载或权限被撤销时,宿主会同步注销 Action 并取消执行中的请求。
4
+
5
+ 这使官方 Action 插件可以作为独立制品从插件服务更新,不必等待 Desktop 发版。Desktop 只维护稳定的注册协议、审批和执行边界。
6
+
7
+ ## 权限
8
+
9
+ 插件必须声明并获得两个权限:
10
+
11
+ ```json
12
+ {
13
+ "permissions": ["app.actions.register", "app.actionHandler.execute"]
14
+ }
15
+ ```
16
+
17
+ - `app.actions.register`:向宿主 Action 目录提交可序列化声明。
18
+ - `app.actionHandler.execute`:允许宿主把通过校验和审批的请求送到插件 handler。
19
+
20
+ ## 注册示例
21
+
22
+ ```ts
23
+ import { definePlugin } from "@vetta-org/plugin-sdk";
24
+
25
+ export default definePlugin({
26
+ activate(ctx) {
27
+ ctx.appActions.register({
28
+ id: "notes.list",
29
+ title: "List notes",
30
+ summary: "List notes from this plugin",
31
+ usage: {
32
+ target: "Notes stored by this plugin",
33
+ useWhen: "The user wants to inspect this plugin's notes.",
34
+ avoidWhen: "Reading repository files or creating notes.",
35
+ alternatives: "Use file tools for repository files; use the note editor to create notes.",
36
+ },
37
+ effect: "read",
38
+ inputSchema: {
39
+ type: "object",
40
+ properties: {
41
+ limit: { type: "integer", minimum: 1, maximum: 100 },
42
+ },
43
+ additionalProperties: false,
44
+ },
45
+ examples: [{ description: "List ten notes", input: { limit: 10 } }],
46
+ async handler({ input, signal }) {
47
+ if (signal.aborted) throw new Error("Action cancelled");
48
+ return { notes: await listNotes(input.limit ?? 10, signal) };
49
+ },
50
+ });
51
+ },
52
+ });
53
+ ```
54
+
55
+ 插件局部 id `notes.list` 会被宿主公开为 `plugin.<pluginId>.notes.list`,避免插件之间及插件与公共 Action 冲突。`describe` 会返回原始 `inputSchema`,调用方可据此生成输入。
56
+
57
+ 可信官方插件还可声明 `publicId`,例如 `publicId: "general.query"`。若该 id 已被其它实现占用,后到的注册会被忽略并记日志(先注册为准)。普通插件使用 `publicId` 会被拒绝。门控依据宿主生成的 `trustLevel: "official"`,而不是插件 id 或安装来源;当前随包系统插件会获得该级别,远端和本地插件不会。
58
+
59
+ 官方插件需要读写宿主数据时使用 `ctx.official`。该 API 在 SDK 中可见,但普通插件调用会被宿主拒绝;宿主按领域提供窄 API,并通过 `pluginApiVersion` 做主版本兼容检查。`vetta-actions` 当前要求 `^2.0.0`,旧主版本或高于宿主能力的版本不会激活。
60
+
61
+ ## 模型选择边界
62
+
63
+ `usage` 是可选的模型可见说明;建议每个 Action 声明。提供时,`target`、`useWhen`、`avoidWhen`、`alternatives` 必须都是非空字符串,宿主在 IPC 边界校验并去除首尾空白。`search` 与 `describe` 均返回这四项,因此模型在选择候选时即可知道作用对象、适用场景、排除场景与替代路径,而不只看到参数 Schema。
64
+
65
+ 官方 `vetta-actions` 的所有 Action 都声明使用边界。需要区分 Vetta 自身的项目、主题、插件和定时任务,与用户正在开发的软件:例如开发网页深色模式应编辑项目样式,不应修改 Vetta 主题。查询能力或解释功能也不等于要求创建、修改或执行。已有对话中明确的目标与操作意图可以继续使用,不应反复确认。
66
+
67
+ 检索使用能力名称、关键词、摘要、描述与操作名,不使用内部权限标识,也不索引 `usage`,避免“不要用于安装插件”等排除说明反而成为命中理由。Schema 联合分支中的 `operation` / `type` 常量及枚举仍可用于查找操作。搜索结果只是候选;调用方应核对使用说明,再通过 `describe` 获取参数,不能把命中当作执行指令。缺少 `usage` 时,需从详细说明确认目标,不能推定适用。
68
+
69
+ 这组字段不是授权、可信身份或执行门禁,不能关闭校验、扩大插件权限或替代审批。不要为了判断用户意图先调用写入 Action,把选择错误交给用户在审批框中处理。
70
+
71
+ ## effect 与审批
72
+
73
+ `effect` 必须是:
74
+
75
+ - `read`:只读,不触发 Action 审批。
76
+ - `write`:修改应用或用户数据;从本地 Action RPC 调用时必须审批。
77
+ - `execute`:启动外部执行或有明显副作用;从本地 Action RPC 调用时必须审批。
78
+
79
+ 插件不能绕过审批。宿主根据 `effect` 决定是否审批,并在用户批准后再次使用同一 JSON Schema 校验输入。普通插件固定使用通用审批;可信官方插件可通过 `approval` 引用宿主已有 presentation,并用 `presentationByOperation` 自动选择领域专用界面。operation 映射是宿主执行时的权威选择;调用方只能使用映射结果、通用审批或声明在 `alternativePresentationsByOperation` 中的备选界面。该能力不能注入新组件,也不能把 `write` / `execute` 改为免审批。
80
+
81
+ ## 运行时边界
82
+
83
+ - `inputSchema` 使用 JSON Schema,由主进程在注册时编译、在执行前校验。
84
+ - 输入、示例和返回值都必须可 JSON 序列化;否则宿主返回稳定 Action 错误。
85
+ - `timeoutMs` 默认 30 秒,最大 120 秒。
86
+ - 超时、调用方取消、插件重载或注销会触发 `signal.abort()`。
87
+ - 每次执行都会重新检查插件是否启用以及两个权限是否仍有效。
88
+ - Action 按 activation 两阶段发布,不会把注册到一半的声明暴露给 search/describe/run。
89
+ - 同一 provider 的 staging 会完整校验后原子替换旧快照;新 activation 失败时继续使用上一版,不先卸载旧版。
90
+ - 可选 `assertReady` 在审批前执行;审批 UI 改写输入后会再次执行。适合检查待编辑、删除或取消的实体是否仍存在。
91
+ - `assertReady` 或 `handler` 可抛 `PluginAppActionError(code, message, details)`,宿主保留稳定错误码和 JSON 详情。`assertReady` 失败不会展示审批。
92
+ - handler 在插件 renderer 运行,可以继续使用闭包中的 `ctx.fs`、`ctx.storage` 等 API;这些 API 各自的权限边界不变。
93
+
94
+ ## 迁移状态
95
+
96
+ 全部内置领域已由随包系统插件 `vetta-actions` 提供;Desktop **不再保留静态领域 Action 实现**。
97
+
98
+ Catalog 规则:
99
+
100
+ - 每个 action id **仅一份**实现。
101
+ - **先注册为准**;后到的同 id 注册只写主进程日志(`action id conflict, keeping first registration`)并忽略。
102
+ - 同一插件 commit 新 activation 时原子替换该 provider 的完整快照,既不被自己的旧注册挡住,也不产生热更新空窗。
103
+ - `vetta-actions` 是 required 系统插件,不能被停用或卸载;未激活时 Catalog 返回 `ACTION_RUNTIME_NOT_READY`,而不是返回一个看似正常的空结果。
104
+
105
+ ## 独立发布建议
106
+
107
+ 官方 Action 插件可以由 Desktop 的首装流程放入插件注册表,也可以由插件服务下发更新。更新服务负责版本、灰度、回滚和签名验证;Action Runtime 不承担下载职责,只消费已经通过插件安装链验证并激活的版本。这样发布机制与执行机制解耦,远端协议变化不会扩大 Action Runtime 的可信边界。
108
+
109
+ 产品意义上的“内置 Action 插件”最终应当是**官方托管插件**:可附带 bootstrap 版本,更新包经过签名验证后获得 `trustLevel: "official"`。远端更新服务最后实施;在此之前远端插件不能使用公共 Action id。
110
+
111
+ 当前 Module Federation 插件与宿主共享 renderer JavaScript realm。`ctx.official` 的 trust gate 是宿主能力门控,但不是针对恶意插件的进程级安全隔离;同 realm 的普通插件仍可能尝试访问宿主已暴露的通用 preload API。若要把第三方插件视为不可信代码,必须另建 Worker、utility process 或独立受限 renderer,并让所有宿主能力经过按插件身份授权的消息通道。该隔离属于插件运行时演进,不应以 renderer token 代替。