插件 API 由 @astravia-org/plugin-sdk 提供,构建由 @astravia-org/plugin-vite 提供,脚手架与安装命令由 @astravia-org/plugin-cli 提供。插件代码运行在 Desktop renderer 进程的共享 JavaScript realm 内;权限声明用于知情同意和宿主 API 门控,不是 iframe、Worker 或恶意代码沙箱。
activate(ctx) 拿到的 ctx 是所有能力的唯一出口。宿主在主进程一侧并不认识「插件」这个概念,只认识一个带授权清单的能力会话,因此每一次调用都会被重新校验、约束到本插件的命名空间,并留下审计记录。
能力地图
| 目标 | 常用入口 | 权限 | 需要注意 |
|---|---|---|---|
| 上报错误与提示 | ctx.ui.notify | 无 | 任何可能失败的路径都应在 catch 里带上原始 error |
| 增加界面与页面 | ctx.ui.register* | 各槽位独立权限 | 注册返回的 Disposable 要在停用时释放 |
| 扩展文件浏览器 | ctx.fileExplorer.* | ui.file-explorer.*、workspace.read | 解析失败要返回可理解的错误状态 |
| 读对话与订阅事件 | ctx.conversation.on 及对话 hook | agent.session.read | 事件是宿主状态的只读视图 |
| 驾驶当前对话 | ctx.conversation.sendPrompt、insertText、abort | agent.session.write | 不要在用户没有触发的情况下自动发起提问 |
| 注册工具与 Hook | ctx.agent.registerTool、registerHook | agent.tools.register / agent.hooks.register 及对应 execute | 输入使用结构化 schema,执行错误要稳定返回 |
| 干预系统提示词与续跑 | registerSystemPromptProvider、registerContinuationProvider | agent.systemPrompt.write、agent.continuation.register | Provider 每个 Turn 开始时求值一次,本 Turn 内不会因插件状态变化重跑 |
| 执行宿主命令 | ctx.command.run、ctx.command.spawn | agent.command.run / agent.command.spawn | 清单必须声明 commands,不要把密钥放进参数 |
| 在终端里替用户跑命令 | 底部面板里的 useBottomPanel().openTerminal | terminal.run | 命令敲进用户自己的 shell、全程可见;cwd 只能在会话目录之内 |
| 离屏渲染截图 | ctx.capture.offscreen | capture.offscreen | 旧宿主上为 undefined,使用前判空 |
| 读写文件 | ctx.fs.* | fs.read / fs.write | 大文件先 stat 再决定读取方式 |
| 调用外部网络 | ctx.network.request | network.fetch | 清单声明最小 network.allowedHosts |
| 浏览器自动化 | ctx.browser.* | browser.open / read / interact 等 | 使用逻辑 profile;不获得 Cookie、Token 或任意 JS 执行权 |
| 插件私有持久化 | ctx.storage.* | storage.read / storage.write | 按插件 id 隔离;多文件一致性用一次 commit() 而不是连续 writeFile() |
| 保存密钥 | ctx.secrets.* | secrets.read / secrets.write | 宿主加密凭据库,不要把 Key 写进普通存储 |
| 调用用户已配置模型 | ctx.ai.listModels、complete、stream、chat | ai.models.list / ai.complete | 使用用户配置的凭据,不自行保存 Key |
| 维护自有模型服务商 | ctx.models.replaceOwnedProviders、listOwnedProviders | models.manage | 写入是原子快照,省略即删除;先读回对账,读到的 apiKey 是掩码 |
| 生成与注册媒体能力 | ctx.media.submit、registerProvider | media.generate / media.provider.register | 长任务通过 ctx.jobs 观察进度与取消 |
| 识别与注册 OCR 能力 | ctx.ocr.recognize、registerProvider | ai.ocr.recognize / ai.ocr.provider.register | 消费与提供是两个独立权限 |
| 管理 CLI 与本地服务 | ctx.cliProviders.*、ctx.services.* | 由清单 providers 声明 | 宿主负责探测、安装、启停与健康检查 |
| 注册可发现动作 | ctx.appActions.register | app.actions.register + app.actionHandler.execute | 高影响文件和网络操作仍需确认 |
| 提供技能或 MCP | plugin.json 的 agent 字段 | agent.skills.control / agent.mcp.control | 安装、启用和重载都要重新验证来源与权限 |
| 贡献智能体与团队 | plugin.json 的 agent.agents、agent.teams | 无 | 宿主铺成普通档案;成员可跨插件引用,队长必须是本插件自己的 |
| 在新会话页摆素材 | ctx.ui.registerNewSessionContext | ui.slot.new-session-context | 激活条件只能写本插件自己的智能体或能力,拿不到用户逐键输入 |
| 提供多语言文案 | ctx.i18n 与包内 locales/ | 无 | 文案 key 和 defaultLocale 要与清单一致 |
网络请求的实际边界
ctx.network.request 由主进程代理,除了主机白名单还有一组固定约束:默认超时 120 秒、上限 300 秒,请求体与响应体各自上限 32 MB,最多 5 次重定向,只允许 http 与 https。
重定向逐跳重新校验白名单,非 GET 与 HEAD 的重定向直接拒绝,跨源跳转时会剥掉 authorization 和 cookie 头。失败原因是枚举值而不是笼统的网络错误,可以据此区分是主机未声明、超出体积、超时还是传输失败。
选择 API 的原则
- 只增加 Agent 方法时,优先技能或插件的 Agent 贡献,不要先创建复杂界面。
- 只连接外部服务时,优先独立 MCP;插件内聚 MCP 适合与插件生命周期强绑定的服务。
- 只需要颜色、表面和组件替换时,使用主题系统。
- 需要完整页面、文件预览、消息卡片或宿主动作时,才使用插件。
- 需要给宿主补一类能力(模型服务商、媒体生成、OCR、本地服务)时,用对应的 Provider 扩展点而不是自己在界面里另起一套。
权限与生命周期
一个完整的插件能力需要同时通过三层:清单声明意图和最小权限,安装时由用户确认,宿主在运行时再次校验并在插件重载或停用时清理注册项。
activate() 可以返回一个清理函数或 Disposable,把资源所有权绑定到这一次激活上。热更新时新旧激活会短暂共存,各自的清理互不干扰,所以有状态资源优先用这种写法,而不是模块级的 deactivate()。
系统内置插件与外置插件的授权策略不同:系统插件随应用发布并自动全量授权,外置插件仍应按不可信代码审查。详见清单与权限。
发布前验收
至少验证这些情况
- 全新安装时清单字段、入口、样式和构建产物路径一致。
- 权限提示只包含完成任务所需的权限。
- 停用、重载和再次启用不会重复注册界面、工具或事件监听。
- 撤销任一可选权限后,插件其余能力仍然可用。
- 无权限、外部网络失败、文件解析失败和模型失败都能显示可恢复的错误。
- 发布 ZIP 与开发链接都经过验证;开发链接成功不能代替 ZIP 安装测试。