插件开发文档
为 AnBoWork 桌面客户端开发插件:给 AI 增加新工具、在会话旁边挂一块自己的面板、发系统通知、解析新的文件格式。一个 manifest.json 加一个入口脚本就能跑起来。
01概览
AnBoWork 插件是一个普通的 Node.js 程序。安装后,客户端把它 fork 成一个独立的子进程运行(纯 Node 环境,没有 Electron API),通过进程间消息跟宿主通信。插件不需要依赖任何 SDK 包,只要会 process.on('message') 和 process.send()。
插件“有哪些能力”全部写在 manifest.json 里,是静态声明:安装前的知情同意页、市场审核,都不需要运行插件代码就能知道它会装哪些工具、要什么权限。所有 UI(顶栏图标、设置项、命令面板、右键菜单)都由宿主按声明统一渲染,插件代码不会跑进主窗口。只有停靠视图和浮动面板是插件自己的 HTML,也运行在独立沙箱页面里。
能力一览
| manifest 字段 | 能力 | 用户在哪里看到 |
|---|---|---|
tools | 给 AI 增加可调用的工具 | AI 在对话中自动调用;工具调用记录显示在会话里 |
views | 停靠在会话右侧栏的常驻视图 | 会话顶栏多一个图标,点开在右侧展示你的页面 |
ui | 独立浮动窗口 | 插件列表的“⋯”菜单里打开 |
sessionAction | 会话顶栏常驻操作图标 | 点一下调用你指定的工具 |
configuration | 设置页的配置项 | 设置 → 插件配置,值随每次调用传给你 |
commands | Ctrl/Cmd+K 命令面板条目 | 命令面板 |
keybindings | 全局快捷键 | 按键触发某条命令 |
menus | 会话右键菜单项 | 会话列表行的“⋯”/右键菜单 |
documentParsers | 解析拖进输入框的文件 | 用户拖入对应格式文件时自动调用 |
activationEvents | 随应用启动拉起 | 无感知;用于需要后台定时器的插件 |
permissions | 声明需要的权限 | 安装前的知情同意页 |
02快速开始
一个最小插件只需要两个文件。
1. 写 manifest.json
2. 写入口脚本
3. 安装到客户端
打开 AnBoWork → 顶部「工具」→「插件」→ 右上角「本地安装」,选择插件目录(或打成 zip 后选 zip 文件)。安装前会弹出知情同意页,列出这个插件声明的权限和全部工具,确认后启用。
4. 试一下
在任意会话里对 AI 说“让插件跟小明打个招呼”,AI 会根据工具描述决定调用 say_hello,会话里会显示这次调用和返回结果。
03manifest 字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 是 | 反向域名风格,如 com.example.my-plugin。只允许字母、数字、点、短横线。创建后不可改,安装同 id 视为覆盖重装。 |
name | string | 是 | 插件名。这个字段和 description 会作为上下文喂给模型,建议用英文。 |
version | string | 是 | 语义化版本,市场按它判断更新。 |
description | string | 是 | 一句话说明插件做什么(英文)。 |
author | string | 否 | 作者。 |
readme | string | 否 | 市场详情页展示的长文说明,支持 Markdown。图片只能用 https 链接,不支持引用 zip 内的相对路径图片。 |
icon | string | 否 | 市场列表/详情页展示的图标,填 zip 内一个相对路径(如 icon.png)。必须是 PNG,不支持 SVG,尺寸至少 128×128。 |
main | string | 是 | 入口脚本,相对插件目录的路径,不允许 .. 跳出目录。 |
permissions | string[] | 是 | network / filesystem / notifications,可为空数组。见「权限模型」。 |
tools | object[] | 是 | 工具声明,可为空数组。见「工具与调用协议」。 |
documentParsers | object[] | 否 | 文件解析能力。 |
ui | object | 否 | 浮动面板:{ entry, title } |
views | object[] | 否 | 停靠视图:{ id, title, icon, entry, width? } |
sessionAction | object | 否 | 顶栏图标,每个插件最多一个。 |
configuration | object[] | 否 | 设置项列表。 |
commands | object[] | 否 | 命令面板条目。 |
keybindings | object[] | 否 | 快捷键,command 必须引用 commands[] 里某条的 tool,否则安装被拒绝。 |
menus | object | 否 | 目前只开放 session/context 一个位置。 |
activationEvents | string[] | 否 | 目前只有 onStartupFinished。 |
文案语言约定
manifest 里的文本分两类受众。给模型看的:name、description、tools[].description、tools[].parameters.*.description,请用英文,模型对英文工具描述的理解和参数填写更稳定。给人看的:tools[].summary(知情同意页的一句话摘要)、ui.title、views[].title、sessionAction.tooltip、configuration[].label、commands[].title、menus 里的 title,用你的目标用户的语言。
04权限模型
permissions 必须如实反映代码实际做的事。安装前的知情同意页会逐条展示,用户据此决定要不要装;市场审核也会对照代码检查声明是否属实。
| 权限 | 什么时候必须声明 | 宿主如何对待 |
|---|---|---|
filesystem | 读写用户文件(读项目代码、生成文档、在用户目录存数据) | 知情同意,向用户展示。 |
network | 发起任何网络请求(调第三方 API、下载资源) | 知情同意,向用户展示。 |
notifications | 通过反向 RPC show_notification 弹系统通知 | 硬校验:没有声明就调用,宿主直接返回 host_error,通知不会弹出。 |
filesystem 和 network 目前是知情同意层面的声明,不是运行时沙箱:宿主不会拦截 fs 或 fetch。这正是为什么声明必须诚实——漏报权限的插件在市场审核时会被拒绝,被用户发现后会失去信任。不要多报(用不到的权限会让用户犹豫),也不要少报。隐私与数据
context.cwd 是用户的项目目录,里面可能有源码和敏感配置;工具参数里可能有用户的对话内容。除非功能必需,不要把这些数据发到网络;需要发送时在 description 和 summary 里说清楚发给谁、发什么。插件自己的持久化数据建议放在用户主目录下一个以插件 id 命名的文件里,卸载时用户能一眼找到。
05工具与调用协议
声明
| 字段 | 说明 |
|---|---|
name | 工具名,小写字母加下划线,在整个客户端里要唯一(会和内置工具、其它插件的工具一起注册给模型)。 |
description | 给模型的说明(英文)。写清楚三件事:这个工具做什么、用户说什么话时应该调用、什么情况下不要调用。这是决定 AI 会不会用你插件的最关键字段。 |
summary | 给人看的一句话摘要,知情同意页展示。可选但强烈建议提供,否则会退回展示裁剪过的 description。 |
parameters | 标准 JSON Schema 对象,每个属性都写 description。模型按它填参数,宿主原样传给你,不做额外校验,代码里要自己检查必填项。 |
renderAs | 可选,目前只有 checklist:宿主把这次调用参数里的 todos 数组渲染成清单卡片,见下方。 |
tools[]——sessionAction.tool、commands[].tool、menus 里的 tool 字段只在安装时校验是否为非空字符串,不会校验这个名字有没有在 tools[] 里声明过,点击/触发时宿主照样按这个名字发起调用。所以一个纯 UI 触发的按钮可以只在你自己的 index.js 里实现处理逻辑、完全不出现在 tools[] 里——但这样 AI 永远看不到也调用不了它。想让同一个功能既能点击触发、也能被 AI 在对话里直接调用,就把这个名字也声明进 tools[],两条入口共用同一份处理逻辑;确定只要 UI 触发、不希望 AI 插手,不声明即可,不用为了“齐全”硬加一条。消息协议
宿主发 call_tool,插件必须用同一个 requestId 回一条 tool_result 或 tool_error。单次调用 30 秒内没有回应会被判为超时,模型收到错误。
content 只认 text 和 image 两种块(image 用 base64 的 data 加 mimeType),其它类型会被丢弃。details 是任意结构化数据,不会喂给模型,用于宿主 UI 渲染和你自己的停靠视图。
产物是文件:文件卡片
清单渲染:renderAs checklist
写好 description 的要点
用祈使句描述触发条件(“Call this when the user asks to…”),明确排除项(“Do not call this for…”)。如果产物是文件,要求模型在回复里引用返回的文件链接、不要凭空声称已生成。如果参数会被转发给外部生成类 API,说明参数要用什么语言。避免只写“manage todos”这类一句话,模型会不知道什么时候该用。
06停靠视图 views
停靠视图是一张挂在会话右侧栏的常驻页面:会话顶栏多出一个图标,点开后你的 HTML 显示在右侧,切换会话、收起再展开都不会丢失页面状态(滚动位置、草稿),跟 VS Code 侧栏视图同一个语义。
| 字段 | 说明 |
|---|---|
id | 插件内唯一。 |
title | 图标的提示文字(人类可读)。 |
icon | 从宿主白名单里选:git-branch、checklist。保持全应用图标风格统一,不接受自带图片。 |
entry | HTML 文件,相对插件目录。 |
width | 可选,面板默认宽度(像素),240 到 640,默认 360。用户之后可以拖动调整并被记住。 |
页面运行环境
页面在沙箱里运行(sandbox + contextIsolation,没有 Node、没有宿主应用的任何数据),只能通过 window.pluginHost 这几个方法跟自己的插件进程通信。样式自己写,建议按下面示例跟随应用的亮/暗色。
推荐模式:页面首次加载用 invoke() 调自己的一个查询工具,并让工具在 details 里返回结构化数据;之后每次数据变化由插件进程 ui_push 推送,页面不轮询。AI 在对话里改了数据,面板会即时刷新。
07浮动面板 ui
ui: { entry, title } 声明一个独立的浮动窗口(900×700),用户在插件列表的“⋯”菜单里打开,关掉即销毁。页面环境和 window.pluginHost API 跟停靠视图完全一样,ui_push 也会同时送到浮动面板和停靠视图。适合“偶尔打开看一眼”的大面板;需要常驻在会话旁边的内容用 views。
08会话顶栏图标 sessionAction
每个插件最多一个常驻图标,点击时用空参数调用 tool,所以那个工具必须在没有参数时也能有意义地执行。
| 字段 | 说明 |
|---|---|
tool | 点击调用的工具名。 |
icon | 白名单图标名。 |
tooltip | 悬停提示。 |
visibleTool | 可选。给了就在渲染前先用空参数调用它,按返回的 details.visible(布尔)决定要不要显示图标。适合“当前目录是不是 git 仓库”这类依赖运行时状态的判断。 |
09设置项 configuration
声明后出现在设置页的插件配置里。用户改的值由宿主保存,每次 call_tool 都会以 context.config 传给你(键是 id,没改过的用 defaultValue),不管这次调用来自 AI、顶栏图标还是你的面板,你不需要自己去问。
| type | 渲染成 | defaultValue 类型 |
|---|---|---|
select | 单选列表,候选项来自 optionsTool | string |
boolean | 开关 | boolean |
string | 文本输入框,失焦提交 | string |
number | 数字输入框 | number |
10命令面板 / 快捷键 / 右键菜单
三者都是“用空参数调用某个工具”的不同入口,一个插件可以声明多条。
| 字段 | 形状 | 说明 |
|---|---|---|
commands | { tool, icon, title }[] | 出现在 Ctrl/Cmd+K 命令面板,用户搜索后触发。 |
keybindings | { command, key }[] | command 必须等于 commands[] 里某条的 tool。key 用 mod+shift+g 这种写法,mod 在 macOS 是 Cmd、其它平台是 Ctrl。跟其它快捷键冲突不做静态检查,选不常用的组合。 |
menus | { "session/context": { tool, icon, title }[] } | 挂进会话列表行的菜单。目前只开放这一个位置。 |
11文档解析 documentParsers
用户把文件拖进输入框时,宿主按 MIME 类型找已启用插件里声明的解析器,把文件内容 base64 传给对应的 toolName。这不是 AI 决定调用的工具,所以不要把解析工具也写进 tools,否则会被当成 AI 可调用工具暴露出去。
12反向 RPC host_request
插件进程没有 Electron API,需要“真实系统能力”时向宿主发 host_request,宿主回 host_response 或 host_error。这条通道和 call_tool 完全独立,可以在处理一次工具调用的过程中发起,也可以在定时器里发起。
| capability | params | result | 前提 |
|---|---|---|---|
show_notification | { title, body?, viewId? } | { shown: true } / { shown: false, reason: "disabled" | "unsupported" } | 声明 notifications 权限;用户在设置里关闭桌面通知时返回 disabled 而不是报错。viewId 指向自己的停靠视图时,点击通知会把应用拉到前台并展开该视图。 |
render_pdf | { outputPath, title, content } | {} | content 是 Markdown,宿主用内置渲染器生成 PDF 到 outputPath。 |
13生命周期
| 阶段 | 行为 |
|---|---|
| 启动 | 默认懒启动:应用启动时不拉起,第一次有人调用你的工具(AI、顶栏图标、面板、命令、文档解析)时才 fork。声明 activationEvents: ["onStartupFinished"] 的插件在应用启动完成后立即拉起,给需要后台定时器(到点提醒、轮询)的插件用;普通工具型插件不要声明。 |
| 常驻 | 进程启动后一直存活,跨多次工具调用复用,可以在内存里缓存状态,但要假设随时可能被重启。 |
| 崩溃 | 进程意外退出时宿主自动重启:立即一次,再等 5 秒、15 秒,连续 3 次失败后标记为“崩溃”不再重试,用户在插件页能看到状态。进行中的调用全部以错误返回。 |
| 禁用 / 卸载 / 重装 | 进程被终止,停靠视图和浮动面板销毁;重装是整个目录推倒重来。 |
| 日志 | 你的 stdout/stderr 会带着插件 id 前缀写进应用日志文件。用 console.log 调试即可,见「调试与常见问题」。 |
ELECTRON_RUN_AS_NODE=1 启动,cwd 是插件安装目录,Node 版本跟客户端内置的 Electron 一致。不要依赖全局安装的任何包,也不要假设有 npx。14打包与发布
打包
把插件目录打成 zip,manifest.json 必须在压缩包根目录。有第三方依赖时建议先用 esbuild/webpack 打成单文件,不要把整个 node_modules 塞进去。
发布到市场
AnBoWork → 工具 → 插件 → 「市场」标签 → 右上角「发布插件」,选择 zip 上传。提交后进入待审核状态,审核会核对 manifest 声明与代码是否一致(尤其是权限)。通过后其他用户可以在市场里搜索安装,AI 也可以在判断需要时自动为用户安装已认证的插件。
更新
改 version 后重新发布。客户端按已安装版本和市场版本比对提示更新;重装会保留用户的启用状态和设置项的值。
15完整示例:四象限待办
一个真实可用的插件(精简版):AI 可以增删待办,右侧栏有实时刷新的看板,可以弹系统通知提醒。把三个能力串起来正好覆盖了工具协议、停靠视图推送和反向 RPC。
16调试与常见问题
| 现象 | 原因与解法 |
|---|---|
| 安装时提示 manifest 校验失败 | 按提示逐字段对照「manifest 字段」表:id 格式、main/entry 不能带 ..、icon 必须在白名单、keybindings 的 command 必须指向 commands 里存在的 tool、width 在 240 到 640 之间。 |
| AI 从不调用我的工具 | 几乎都是 description 的问题:没写清楚触发条件,或者用了非英文。参考「写好 description 的要点」。也检查工具名是否和别的工具重名。 |
| 调用超时(30 秒) | 入口脚本没有回 tool_result,通常是 requestId 没原样带回、消息 type 拼错,或者脚本启动时就抛异常退出了。看应用日志里带 [plugin:你的id] 前缀的行。 |
| 插件状态显示“崩溃” | 连续 3 次启动失败。常见于 require 了不存在的包;在插件目录里直接 node index.js 能复现。修好后在插件页关掉再打开即可。 |
| 面板打开是空白 | entry 路径不对,或者页面 JS 报错。页面运行在沙箱里,没有 Node 的 require,只能用 window.pluginHost。 |
| 通知没有弹出 | 依次检查:manifest 声明了 notifications;返回值不是 shown:false(用户关了桌面通知);系统设置里允许了 AnBoWork 的通知。 |
| 设置项的值读不到 | 值在 message.context.config[id],不在 params 里。 |
日志文件位置
| 系统 | 路径 |
|---|---|
| macOS | ~/Library/Logs/AnBoWork/main.log |
| Windows | %USERPROFILE%\AppData\Roaming\AnBoWork\logs\main.log |