外掛開發文件
為 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 |