启航插件 API v1
开发者面向启航 Code 编写插件时唯一需要遵守的接口。DSH 内核升级由平台适配层消化,插件代码不修改、用户不重装。
1文档定位
这份文档定义开发者面向启航 Code 写插件时唯一需要遵守的接口,不描述 DSH 内部机制。
核心承诺:开发者只依赖《启航插件 API v1》。DSH 内核升级由平台的兼容垫片消化,插件代码不修改、用户不重装。
要兑现这个承诺,插件与 DSH 之间必须有一层受控边界。当前代码里已有运行时适配层,但开发者 API 层尚未建立,自有插件仍直接调用 DSH 的插槽与词典内部结构。本文档即该 API 层的契约定义。
2三层架构与职责边界
插件(开发者代码)
只允许 import '@qicode/plugin-sdk'
│ 稳定契约(本文档)
L2 启航插件 SDK
生命周期 / UI 扩展点 / 宿主服务 / 权限
│ 每个 DSH 版本一份实现
L3 兼容垫片 Compat Shim
SDK 调用 → DSH slots / locale / 宿主服务
│
L1 运行时适配层(已具备)
内核版本校验 / profile 生成 / 依赖链接 / 启动 / IPC / 窗口
- L1 不感知插件业务,只负责把 DSH 以正确形态跑起来。
- L2 是对外唯一契约,版本号独立于 DSH,遵循语义化版本。
- L3 允许随 DSH 升级重写,是唯一允许接触 DSH 内部结构的层。
- 插件不得越过 L2 直接访问 DSH。
3插件包结构与 Manifest
3.1 包结构
my-plugin/
├── package.json # 含 qicode 字段
├── dist/
│ ├── host.js # 主机端入口(Node 侧,可选)
│ └── client.js # 界面端入口(浏览器侧,可选)
├── README.md
└── icon.png # 128×128,市场展示用
包格式与 npm 一致(.tgz),便于复用现有 registry 与缓存机制。
3.2 Manifest 示例
{
"name": "@qicode/plugin-example",
"version": "1.0.0",
"description": "示例插件",
"qicode": {
"apiVersion": "1",
"id": "com.blokeai.example",
"displayName": "示例插件",
"publisher": "朔序科技",
"system": false,
"entry": { "host": "./dist/host.js", "client": "./dist/client.js" },
"engines": { "qicode": ">=1.0.0 <2.0.0" },
"permissions": ["storage", "net.outbound"],
"network": { "allow": ["api.blokeai.com"] }
}
}
| 字段 | 必填 | 说明 |
|---|---|---|
apiVersion | 是 | 固定为 "1",SDK 大版本 |
id | 是 | 全局唯一,反向域名。官方保留 com.blokeai.* |
displayName | 是 | 市场与插件列表展示名 |
publisher | 是 | 发布者名称,签名主体需一致 |
system | 否 | true 表示核心组件,不可禁用、不可卸载 |
entry.host | 二选一 | 主机端入口,ESM |
entry.client | 二选一 | 界面端入口,ESM |
engines.qicode | 是 | 兼容的 SDK 版本区间,安装时校验 |
permissions | 是 | 权限清单,见第 7 节 |
network.allow | 否 | 联网白名单,仅 net.outbound 生效 |
4生命周期
export interface QicodePlugin {
activate(ctx: QicodeContext): void | Promise<void>;
deactivate?(): void | Promise<void>;
}
| 阶段 | 时机 | 约束 |
|---|---|---|
| 解析 | 客户端启动,读取已安装清单 | 校验 manifest、签名、SDK 版本区间 |
| 激活 | DSH 就绪后按依赖顺序调用 | 单插件超时 10s;失败不阻塞其他插件 |
| 运行 | 注册扩展点、注册工具 | 所有资源必须挂在 ctx 上,随停用释放 |
| 停用 | 用户禁用 / 卸载 / 退出 | deactivate 超时 3s 后强制回收 |
| 失败隔离 | 激活或运行抛错 | 标记为“异常”,界面提示,不白屏 |
崩溃保护:同一插件连续 3 次激活失败,自动置为“已禁用”并在插件页提示,需用户手动恢复。
4.1 与 DSH cordis 生命周期的映射
插件不实现 cordis 的 apply / inject,由 L3 垫片完成翻译:
| SDK 概念 | DSH cordis 对应实现 | 说明 |
|---|---|---|
activate(ctx) | 垫片导出的 apply(ctx) | 异步 activate 由垫片包装,DSH 启动不被阻塞 |
deactivate() | apply 返回的 disposer / ctx.effect 回收 | 先调用插件 deactivate,再释放扩展点与工具 |
manifest permissions | 垫片内的能力校验 | 开发者不写 inject 数组,服务按权限注入 |
ctx.tools.register | defineTool + ctx.tools.register | 工具定义按 SDK 归一化后透传 |
ctx.ui.* | ctx.slots.inject/register | 声明式扩展点翻译为具体槽位 |
ctx.storage/config/secrets | profile 目录 + 主进程密钥服务 | 按插件 id 隔离 |
边界:activate 内抛错不会导致 DSH 启动失败;垫片捕获后标记插件异常,其余插件继续激活。
5UI 扩展点
插件不写 DOM、不写 CSS 选择器、不依赖官方类名。所有界面接入通过声明式扩展点完成。
5.1 API
ctx.ui.brand({ mark?: Component, name?: Component | string });
ctx.ui.headline({ zh: string, en: string });
ctx.ui.sidebarItem({ id, title, icon, order, onOpen: () => void });
ctx.ui.sidebarFooterAction({ id, title, icon, onClick });
ctx.ui.page({ id, title, component, placement: 'main' | 'right-pane' });
ctx.ui.composerDock({ id, component, order });
ctx.ui.settingsSection({ id, title, component });
ctx.ui.messageAction({ id, title, onClick, when: 'assistant' | 'user' });
5.2 与当前 DSH 槽位的映射(L3 内部实现,插件不感知)
| SDK 扩展点 | 当前实现槽位 | 稳定性 |
|---|---|---|
ui.brand | sidebar.brand.mark / sidebar.brand.name / conversation.hero.brand.mark | 稳定 |
ui.headline | conversation 词典 hero.headline | 稳定 |
ui.sidebarItem | sidebar.panellist(list,条目参数 id / order / label / locale) | 稳定 |
ui.sidebarFooterAction | sidebar.footer.action(list) | 稳定 |
ui.settingsSection | settings.general.item | 稳定 |
ui.messageAction | conversation.chat.assistant-actions | 稳定 |
ui.composerDock | conversation.input.dock | 稳定 |
ui.page | 主面板:ctx.layout 面板注册 + sidebar.panellist 条目;右侧:sidebar.right.pane.tab | Beta |
约定:ui.sidebarItem 与 ui.page 必须成对使用,条目 id 即面板 id。标注 Beta 的扩展点允许在 SDK 次版本内调整参数,需在变更日志中提前一个版本公告。
6宿主服务
ctx.log.info / warn / error(msg, meta?)
ctx.storage.get(key) / set(key, value) / delete(key) / list()
ctx.config.get() / ctx.config.onChange(fn)
ctx.session.current() / list() / read(id)
ctx.session.onEvent(fn)
ctx.model.invoke({ messages, model?, temperature? })
ctx.fs.readText(path) / writeText(path, content) / pick({ mode })
ctx.net.fetch(url, init?)
ctx.tools.register(toolDef)
ctx.secrets.get(name)
- 所有异步 API 必须支持
AbortSignal(ctx.signal),插件停用时自动中止。 ctx.model.invoke、ctx.net.fetch、ctx.secrets.get计入用量与审计日志。- 不允许插件直接
require('electron')、访问window.blokeai或 Node 原生fs/net/child_process。
7权限模型
| 权限 | 能力 | 用户可见提示 |
|---|---|---|
storage | 插件私有键值存储 | 无 |
fs.read | 读取工作区文件 | 首次使用时授权 |
fs.write | 写入工作区文件 | 首次使用时授权 |
net.outbound | 访问 manifest 白名单域名 | 安装时列出域名 |
model.invoke | 调用平台模型 | 消耗用户配额,明确提示 |
session.read | 读取会话内容 | 安装时提示 |
secrets | 读取平台托管密钥 | 单独二次确认 |
process.exec | 执行本地命令 | 默认拒绝,需企业策略放开 |
权限在安装时展示,运行期由 L3 强制校验,插件无法自行提权。权限变更(新增)需重新授权。
8错误处理与降级
- 插件异常不得导致主界面白屏:L3 为每个插件包裹错误边界。
- UI 组件抛错时渲染占位块,并提供“禁用此插件”入口。
- 宿主服务调用失败返回结构化错误
{ code, message, retryable },不抛裸异常。 - 插件日志统一写入
plugin-data/<id>/logs/,滚动保留 7 天。
9兼容与弃用策略
- 版本独立:SDK 版本(apiVersion)与 DSH 版本解耦。DSH 升级只改 L3。
- 兼容垫片按 DSH 次版本维护:每个 DSH 次版本对应一份 shim,附契约测试用例。
- 契约测试:每次升级 DSH 前自动运行,覆盖品牌位、扩展点挂载、工具注册、词典改写、插件启停。
- 弃用窗口:任何 SDK API 删除前,至少保留两个次版本的过渡期,并在启动时输出弃用警告。
- 紧急熔断:服务端可下发插件黑名单与最低 SDK 版本,客户端启动时拉取并生效。
- 回滚:新版本插件加载失败自动回退上一可用版本。
10禁止条款
插件发布时静态扫描,命中即拒绝上架:
| 禁止项 | 检测方式 |
|---|---|
import / require 任何 @deepseek-ai/* 包 | AST 扫描依赖与源码 |
| DOM 查询与样式注入(querySelector、style 注入) | AST + 字符串规则 |
| 读取 DSH 词典内部结构(locale.dicts) | AST 规则 |
直接访问 window.blokeai | AST 规则 |
| 未声明权限却调用对应宿主能力 | 运行时拦截 + 静态校验 |
| 打包内含原生二进制且未声明 | 包内容扫描 |
11现状接口盘点(截至 2026-10-22)
11.1 已具备的运行时适配能力(L1)
| 能力 | 实现位置 | 判定 |
|---|---|---|
| DSH 版本读取与兼容区间校验 | electron/dsh-adapt.js | 稳定 |
| 旧插件公共包链接(9 个) | electron/dsh-adapt.js | 稳定,随 DSH 大版本复核 |
| 适配结果落盘 .qicode-compat.json | electron/dsh-adapt.js | 稳定 |
| profile 生成(bundles、patchReload、用户补丁层) | electron/main.js | 稳定 |
| 自有插件链接/复制到 profile | electron/main.js | 稳定 |
| DSH 子进程启动、端口清理、孤儿锁自愈 | electron/main.js | 稳定 |
11.2 渲染进程桥
window.blokeai 当前暴露 22 个方法:设置读写、登录鉴权、支付下单与轮询、外链、关于页、退出、版本号等。判定:内部接口,不对插件开放。SDK v1 另建权限受控的能力桥。
11.3 插件现有接入方式与稳定性判定
| 机制 | 现状 | 判定 |
|---|---|---|
| 主机端入口 apply(ctx) + inject | qicode-dsh-kit/lib/index.js | 可用,需包装为 SDK 生命周期 |
| 工具注册 ctx.tools.register | 同上 | 稳定,纳入 ctx.tools |
| 界面端 window.__ModuleLoader__.load | qicode-dsh-kit/lib/client.js | 无版本契约,需由 L3 封装 |
| 插槽注册 ctx.slots.inject/register | 品牌位、插件页标签 | 稳定,插件不再直接调用 |
| 词典内部改写 locale.dicts | 首屏标语覆盖 | 临时 |
| 插件页开关守卫(DOM 注入) | injectPluginGuard | 技术债 |
| 设置页标题修正(DOM 注入) | injectSettingsTitleFix | 技术债 |
| 标题栏菜单注入(DOM) | injectTitleBarMenu | 技术债 |
11.4 缺口清单
- 无
@qicode/plugin-sdk包,无统一上下文对象。 - 无 manifest 规范落地与安装期校验。
- 无签名、无校验和、无私有 registry。
- 无扩展点声明式 API,现有插件直接写 DSH 槽位。
- 无权限模型与能力桥。
- 无插件启停隔离、超时回收、崩溃熔断。
- 无契约测试,DOM 补丁未迁移。
12参考实现:qicode-dsh-kit 改造前后
改造前(直接依赖 DSH)
window.__ModuleLoader__.load({
id: 'qicode-dsh-kit/client',
factory: (require) => {
var React = require('react');
function apply(ctx) {
ctx.slots.inject('sidebar.brand.mark', function* () {
yield ctx.slots.register({ name: 'sidebar.brand.mark', priority: -10 }, Mark);
});
}
return { apply, inject: ['slots', 'locale'] };
}
});
改造后(只依赖 SDK)
import { definePlugin } from '@qicode/plugin-sdk';
import { BrandMark } from './brand.js';
export default definePlugin({
id: 'com.blokeai.core-kit',
system: true,
activate(ctx) {
ctx.ui.brand({ mark: BrandMark, name: '启航 Code' });
ctx.ui.headline({ zh: '启新朔 · 定万序', en: 'Set Sail · Shape the Order' });
ctx.tools.register(supportTool);
}
});
system: true 由 L3 强制执行:插件页不显示开关,配置层无法禁用,启动时自动恢复。
13里程碑
| 阶段 | 交付物 | 验收标准 |
|---|---|---|
| M1 | 本文档冻结 + manifest JSON Schema | 评审通过,Schema 可校验 |
| M2 | @qicode/plugin-sdk 骨架(host + client) | 示例插件跑通生命周期 |
| M3 | qicode-dsh-kit 迁移为 SDK 插件 | 品牌/标语/工具与现版本行为一致 |
| M4 | L3 兼容垫片 + 契约测试 | DSH 升级后插件零改动 |
| M5 | 私有 registry + 签名 + 安装校验 | 全链路打通,篡改包被拒绝 |
| M6 | 客户端内插件市场 UI | 列表、详情、安装、更新、回滚 |
14变更记录
| 版本 | 日期 | 内容 |
|---|---|---|
| v1.0-draft | 2026-10-22 | 初稿:三层架构、manifest、扩展点、宿主服务、权限、现状盘点 |
