@vetta-org/plugin-sdk 0.3.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.
@@ -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 代替。
@@ -0,0 +1,80 @@
1
+ # 浏览器 API
2
+
3
+ `ctx.browser` 提供两类能力:`open()` 只负责把页面展示到 Desktop 内置浏览器;其余 session/snapshot/act API 是宿主管理的浏览器自动化能力。插件不应自行执行浏览器 CLI。
4
+
5
+ 当前宿主始终提供 `ctx.browser`,创建这个 API 对象不会启动浏览器或进行导航。权限在调用具体方法时校验,缺少声明或用户授权时抛出 `Plugin permission denied: <permission>`。不要用 `ctx.browser` 是否存在判断权限;需要提前判断时使用 `ctx.permissions.has(...)`。兼容尚未提供该 API 的旧宿主时,仍需自行检测是否存在。
6
+
7
+ ## 打开内置浏览器
8
+
9
+ 如果插件只需要让用户查看一个页面,声明 `browser.open`,并调用:
10
+
11
+ ```ts
12
+ ctx.browser.open("https://studio.example.com/posts");
13
+ ```
14
+
15
+ `open()` 会打开当前会话的内置 Browser Panel。它只接受 `http` / `https`,并受清单中的 `browser.allowedHosts` 限制;不会返回页面内容,也不会授予点击、填充、脚本执行或 Cookie 访问权限。
16
+
17
+ 没有活动会话时,`open()` 会抛出错误,不会创建新会话。只有 `browser.open` 权限也能使用该方法,不需要 `browser.read` 或安装自动化运行时。
18
+
19
+ ## 清单
20
+
21
+ ```json
22
+ {
23
+ "permissions": [
24
+ "browser.open",
25
+ "browser.read",
26
+ "browser.interact",
27
+ "browser.profile.persist"
28
+ ],
29
+ "browser": {
30
+ "allowedHosts": ["studio.example.com", "*.assets.example.com"]
31
+ }
32
+ }
33
+ ```
34
+
35
+ 只要声明任一 `browser.*` 权限,就必须提供非空 `browser.allowedHosts`。`*` 表示显式允许任意顶层导航;应尽量声明具体站点。`browser.interact` 依赖 `browser.read`。`browser.attach` 和 `browser.runtime.manage` 也必须分别显式声明并由用户授权。
36
+
37
+ ## 多账号 profile
38
+
39
+ ```ts
40
+ const session = await ctx.browser.sessions.create({
41
+ source: "managed",
42
+ profile: { type: "persistent", id: "brand-a" },
43
+ headed: true,
44
+ allowedHosts: ["studio.example.com"],
45
+ });
46
+ ```
47
+
48
+ `profile.id` 是插件 namespace 内的逻辑 ID。不同 ID 对应独立登录态,适合一个媒体账号一个 profile。插件不会得到物理路径、Cookie 或 token。关闭 session 不会删除持久 profile。
49
+
50
+ `allowedHosts` 可省略(使用 manifest 全集),也可传 manifest 授权的子集;不能在运行时扩权。不要把邮箱、密码或 token 放进 profile ID。
51
+
52
+ ## 操作流程
53
+
54
+ ```ts
55
+ const session = await ctx.browser.sessions.create({
56
+ profile: { type: "persistent", id: "brand-a" },
57
+ allowedHosts: ["studio.example.com"],
58
+ });
59
+
60
+ await ctx.browser.navigate(session.id, "https://studio.example.com/posts/new");
61
+ const snapshot = await ctx.browser.snapshot(session.id, { interactiveOnly: true });
62
+ await ctx.browser.act(
63
+ session.id,
64
+ { type: "fill", target: "@e3", value: "Draft title" },
65
+ { snapshotRevision: snapshot.revision },
66
+ );
67
+ await ctx.browser.sessions.close(session.id);
68
+ ```
69
+
70
+ 动作支持 `click`、`fill`、`type`、`select`、`check`、`press`、`scroll`、`wait`、`back` 和 `reload`。页面变化后重新获取 snapshot;传入旧 revision 会被宿主拒绝,避免误操作过期 ref。
71
+
72
+ ## 安全与生命周期
73
+
74
+ - 公共 API 不提供任意 JavaScript、argv、文件上传、下载、Cookie 或认证数据导出。
75
+ - 页面内容是不可信数据。发布、提交、发送、删除、购买和权限变更等不可逆动作仍需产品层获得用户确认。
76
+ - 域名范围限制顶层导航,不是页面子资源的网络防火墙。显式导航在执行前校验;不透明动作完成后发现越界会关闭 session。
77
+ - Capability 被取消时,宿主会终止对应浏览器子进程。插件停用或 capability session 撤销时,活动浏览器 session 会被回收,持久 profile 保留。
78
+ - 宿主日志只记录脱敏 session/profile 标识、操作、耗时与错误分类,不记录 URL query、页面正文、表单值、Cookie、token 或截图。
79
+
80
+ 完整架构见 [ADR-0088](../adr/0088-browser-automation-as-a-foundation-capability.md)。