本页从零写一个最小的工作区插件:一个可以保存草稿、并能被 Agent 读取的页面。整个过程不需要 Worker,也不需要网络权限。
先选最小架构
只为当前需求申请能力,不为“以后可能用到”预留:
| 需求 | 采用 |
|---|---|
| 面板、表单、看板、可视化 | 工作区界面(Workspace) |
| 插件私有的解析、转换、后台计算 | 插件 Worker |
| 多个插件共享、版本独立的重型能力 | 全局 Worker 服务 |
| Agent 读取或修改插件状态 | Workspace 加包内技能(Skill) |
| 少量设置和索引 | storage.kv |
| 文档、图片、音视频和大块状态 | storage.blob |
| 外部 HTTP API | 界面侧 net.fetch 或 Worker 侧 host.net.fetch |
推荐的演进顺序是:Workspace → 插件 Worker → 全局 Worker 服务。纯展示的插件不要创建占位 Worker。
目录结构
<插件目录>/
├── plugin.json
└── payload/
├── ui/
│ ├── index.html
│ ├── app.js
│ └── styles.css
├── i18n/
│ ├── zh-CN.json
│ └── en-US.json
├── resources/
│ └── icon.png # 必需:200×200 PNG,≤ 500 KiB
└── agent/
└── SKILL.md
所有入口和运行资源都必须放在 payload/ 下,并出现在最终 plugin.json 的 files 里。不要把 node_modules、构建缓存、测试数据、密钥或本机绝对路径放进包。
编写 plugin.json
{
"schema_version": 3,
"plugin_id": "com.example.my-notes",
"name": "My Notes",
"version": "0.1.0",
"minimum_app_version": "0.4.5",
"icon": { "resource_id": "icon" },
"contributions": {
"resources": [
{ "id": "icon", "path": "payload/resources/icon.png", "media_type": "image/png" }
],
"workspaces": [
{
"id": "main",
"title": "My Notes",
"entrypoint": "payload/ui/index.html",
"bridge_version": "1.0",
"allowed_placements": ["replace_files", "replace_editor", "replace_both"],
"default_placement": "replace_editor"
}
],
"skills": [{ "id": "agent", "entrypoint": "payload/agent/SKILL.md" }]
},
"workers": [],
"requested_permissions": [
{ "permission": "agent.context", "worker": "host", "optional": false },
{ "permission": "agent.events", "worker": "host", "optional": false },
{ "permission": "storage.kv", "worker": "host", "optional": true }
],
"required_services": [],
"platforms": ["windows-x86_64", "macos-aarch64", "macos-x86_64"],
"files": {},
"publisher": "Example Inc."
}
plugin_id一经发布不可更改,建议用反向域名。agent.context、agent.events、storage.kv都是 Workspace 作用域权限,按 workspace 授权,可以直接写"worker": "host",不需要为此建 Worker。- 有 workspace 时,
skills和agent.context必须成对出现。skills只接受 1 项。 files在开发时可以先留空,打包工具会计算每个文件的 SHA-256 后写入。
编写界面
界面必须先等待 stabledgePlugin.ready(),再调用其他方法。首次 context.publish 成功后,宿主才把这个 Workspace 标记为可以绑定 Agent。
async function initialize() {
const host = await window.stabledgePlugin.ready();
const draft = (await window.stabledgePlugin.storage.get("draft")) ?? "";
render(draft);
await window.stabledgePlugin.context.publish({
version: 1,
draft,
available_actions: [{ name: "draft.set", payload: { text: "string" } }],
});
}
if (window.stabledgePlugin) void initialize();
else window.addEventListener("stabledge:ready", initialize, { once: true });
window.stabledgePlugin.events.on("agent.custom", async ({ event, payload }) => {
if (event !== "draft.set" || typeof payload?.text !== "string") {
showError("不支持的操作");
return;
}
await window.stabledgePlugin.storage.set("draft", payload.text);
render(payload.text);
});
遇到无法处理的 Agent 动作时,在界面里给出明确提示即可,不要直接
throw。未捕获的异常只会让页面停在半初始化状态,用户看不到原因。编写技能说明
payload/agent/SKILL.md 告诉 Agent 这个插件发布了哪些上下文、接受哪些动作(例如 draft.set 及其参数)。说明要和界面里 available_actions 的定义保持一致。
打包与安装
- 在桌面端打开「插件创建模式」,选择
<插件目录>。 - 点击「校验项目」,修复清单、图标、文件集和权限方面的报错。
- 点击「打包项目」生成
.seplugin;也可以直接「安装到预览环境」进行调试。 - 双击
.seplugin会打开安装预览,确认权限后安装。新安装的插件默认停用,需要在插件列表里手动启用。
未经官方签名的包按「开发者」来源安装,需要先在「设置 → 插件 → 安装与签名」中打开「允许安装未签名插件」。详见 打包、签名与发布。
完成检查
- 每项能力都有对应的清单声明和用户授权路径。
- 界面不直接访问系统路径、裸网络或桌面端内部接口。
- 持久化、外部数据和 Agent 交互都考虑了失败、撤权和重新打开后的恢复。
- 没有为了挂权限而存在、却声称可调用业务方法的空 Worker。