開發者指南

外掛開發文件

為 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設定頁的設定項設定 → 外掛設定,值隨每次呼叫傳給你
commandsCtrl/Cmd+K 命令面板項目命令面板
keybindings全域快捷鍵按鍵觸發某條命令
menus工作階段右鍵選單項工作階段清單列的「⋯」/右鍵選單
documentParsers解析拖進輸入框的檔案使用者拖入對應格式檔案時自動呼叫
activationEvents隨應用程式啟動拉起無感知;用於需要背景計時器的外掛
permissions宣告需要的權限安裝前的知情同意頁

02快速開始

一個最小外掛只需要兩個檔案。

my-plugin/
├── manifest.json   # plugin manifest (required)
├── index.js        # entry script, referenced by manifest.main (required)
├── view.html       # docked view page (optional, manifest.views[].entry)
└── ui.html         # floating panel page (optional, manifest.ui.entry)

1. 寫 manifest.json

{
  "id": "com.example.hello",
  "name": "Hello Plugin",
  "version": "1.0.0",
  "description": "Say hello to the user.",
  "main": "index.js",
  "permissions": [],
  "tools": [
    {
      "name": "say_hello",
      "summary": "Greet the user by name",
      "description": "Return a greeting for the given name. Call this when the user explicitly asks the plugin to say hello.",
      "parameters": {
        "type": "object",
        "properties": {
          "name": { "type": "string", "description": "Who to greet." }
        },
        "required": ["name"]
      }
    }
  ]
}

2. 寫進入點腳本

// The entry script runs in a standalone Node.js child process (ELECTRON_RUN_AS_NODE=1).
// Only Node built-ins and dependencies shipped inside the plugin folder are available;
// there is no Electron API.
process.on('message', (msg) => {
  if (!msg || msg.type !== 'call_tool') return;
  const { requestId, toolName, params } = msg;
  try {
    if (toolName !== 'say_hello') throw new Error(`unknown tool: ${toolName}`);
    process.send({
      type: 'tool_result',
      requestId,
      content: [{ type: 'text', text: `Hello, ${params.name}!` }],
    });
  } catch (err) {
    process.send({ type: 'tool_error', requestId, message: err.message });
  }
});

3. 安裝到用戶端

開啟 AnBoWork → 頂部「工具」→「外掛」→ 右上角「本機安裝」,選擇外掛目錄(或打成 zip 後選 zip 檔)。安裝前會彈出知情同意頁,列出這個外掛宣告的權限和全部工具,確認後啟用。

4. 試一下

在任意工作階段裡對 AI 說「讓外掛跟小明打個招呼」,AI 會根據工具描述決定呼叫 say_hello,工作階段裡會顯示這次呼叫和回傳結果。

提示
AnBoWork 裡的 AI 本身也能替你產生外掛(對它說「幫我寫一個 xxx 外掛」,它會呼叫 create_local_plugin),產生的外掛預設停用,需要你在外掛頁看過程式碼後手動啟用。這份文件同樣適用於閱讀和修改 AI 產生的外掛。

03manifest 欄位

欄位型別必填說明
idstring是反向網域風格,如 com.example.my-plugin。只允許字母、數字、點、連字號。建立後不可改,安裝同 id 視為覆蓋重裝。
namestring是外掛名稱。這個欄位和 description 會作為上下文餵給模型,建議用英文。
versionstring是語意化版本,市集按它判斷更新。
descriptionstring是一句話說明外掛做什麼(英文)。
authorstring否作者。
readmestring否市集詳情頁展示的長文說明,支援 Markdown。圖片只能用 https 連結,不支援引用 zip 裡的相對路徑圖片。
iconstring否市集列表/詳情頁展示的圖示,填 zip 裡一個相對路徑(例如 icon.png)。必須是 PNG,不支援 SVG,尺寸至少 128×128。
mainstring是進入點腳本,相對外掛目錄的路徑,不允許 .. 跳出目錄。
permissionsstring[]是network / filesystem / notifications,可為空陣列。見「權限模型」。
toolsobject[]是工具宣告,可為空陣列。見「工具與呼叫協定」。
documentParsersobject[]否檔案解析能力。
uiobject否浮動面板:{ entry, title }
viewsobject[]否停靠視圖:{ id, title, icon, entry, width? }
sessionActionobject否頂欄圖示,每個外掛最多一個。
configurationobject[]否設定項清單。
commandsobject[]否命令面板項目。
keybindingsobject[]否快捷鍵,command 必須引用 commands[] 裡某條的 tool,否則安裝被拒絕。
menusobject否目前只開放 session/context 一個位置。
activationEventsstring[]否目前只有 onStartupFinished。

文案語言約定

manifest 裡的文字分兩類受眾。給模型看的:name、description、tools[].description、tools[].parameters.*.description,請用英文,模型對英文工具描述的理解和參數填寫更穩定。給人看的:tools[].summary(知情同意頁的一句話摘要)、ui.title、views[].title、sessionAction.tooltip、configuration[].label、commands[].title、menus 裡的 title,用你的目標使用者的語言。

{
  "id": "com.example.full",
  "name": "Full Example",
  "version": "1.2.0",
  "description": "Every contribution point in one manifest.",
  "author": "Example Inc.",
  "readme": "## Full Example\n\nLonger Markdown shown on the marketplace detail page. Images must be https URLs.",
  "icon": "icon.png",
  "main": "index.js",
  "permissions": ["filesystem", "network", "notifications"],
  "activationEvents": ["onStartupFinished"],
  "tools": [
    {
      "name": "do_something",
      "summary": "Do one thing to the current project",
      "description": "…English. State clearly when to call this and when not to…",
      "parameters": { "type": "object", "properties": {}, "required": [] },
      "renderAs": "checklist"
    },
    { "name": "is_available", "summary": "Whether the current folder applies", "description": "…", "parameters": { "type": "object", "properties": {} } },
    { "name": "list_modes", "summary": "List selectable modes", "description": "…", "parameters": { "type": "object", "properties": {} } }
  ],
  "documentParsers": [
    { "mimeType": "text/markdown", "toolName": "parse_markdown" }
  ],
  "ui": { "entry": "ui.html", "title": "Example Panel" },
  "views": [
    { "id": "board", "title": "Example Board", "icon": "checklist", "entry": "view.html", "width": 380 }
  ],
  "sessionAction": {
    "tool": "do_something",
    "icon": "git-branch",
    "tooltip": "Do one thing to the current project",
    "visibleTool": "is_available"
  },
  "configuration": [
    { "id": "mode", "label": "Mode", "type": "select", "optionsTool": "list_modes", "defaultValue": "auto" },
    { "id": "verbose", "label": "Verbose output", "type": "boolean", "defaultValue": false },
    { "id": "prefix", "label": "Prefix", "type": "string", "defaultValue": "" },
    { "id": "limit", "label": "Max items", "type": "number", "defaultValue": 20 }
  ],
  "commands": [
    { "tool": "do_something", "icon": "git-branch", "title": "Do one thing to the current project" }
  ],
  "keybindings": [
    { "command": "do_something", "key": "mod+shift+d" }
  ],
  "menus": {
    "session/context": [
      { "tool": "do_something", "icon": "git-branch", "title": "Do one thing to this session" }
    ]
  }
}

04權限模型

permissions 必須如實反映程式碼實際做的事。安裝前的知情同意頁會逐條展示,使用者據此決定要不要裝;市集審核也會對照程式碼檢查宣告是否屬實。

權限什麼時候必須宣告宿主如何對待
filesystem讀寫使用者檔案(讀專案程式碼、產生文件、在使用者目錄存資料)知情同意,向使用者展示。
network發起任何網路請求(呼叫第三方 API、下載資源)知情同意,向使用者展示。
notifications透過反向 RPC show_notification 彈系統通知硬校驗:沒有宣告就呼叫,宿主直接回傳 host_error,通知不會彈出。
注意
外掛子程序是完整的 Node.js 環境,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 秒內沒有回應會被判為逾時,模型收到錯誤。

// host → plugin
{
  "type": "call_tool",
  "requestId": "0b2d…",          // unique per call, echo it back unchanged
  "toolName": "do_something",
  "params": { "name": "…" },     // arguments validated against tools[].parameters
  "context": {
    "cwd": "/Users/me/project",  // project folder of the calling session; a temp folder for chat sessions; empty string for document parsing
    "config": { "mode": "auto", "verbose": false }   // present only when configuration is declared; keys are configuration[].id
  }
}

content 只認 text 和 image 兩種區塊(image 用 base64 的 data 加 mimeType),其它型別會被丟棄。details 是任意結構化資料,不會餵給模型,用於宿主 UI 渲染和你自己的停靠視圖。

產物是檔案:檔案卡片

// When the result is a file: include the absolute path and file name. The session
// renders a clickable file card that previews in-app (docx/xlsx/pdf/markdown/code…).
process.send({
  type: 'tool_result',
  requestId,
  content: [{ type: 'text', text: `Generated [${fileName}](${filePath})` }],
  details: { filePath, fileName },
});

清單渲染:renderAs checklist

// For tools with renderAs: "checklist", the host renders the todos array from the call arguments:
{
  "todos": [
    { "id": "1", "content": "Initialize repository", "status": "completed" },
    { "id": "2", "content": "Write unit tests", "status": "in_progress" },
    { "id": "3", "content": "Publish", "status": "pending" }
  ]
}

寫好 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。保持全應用程式圖示風格統一,不接受自帶圖片。
entryHTML 檔案,相對外掛目錄。
width可選,面板預設寬度(像素),240 到 640,預設 360。使用者之後可以拖曳調整並被記住。

頁面執行環境

頁面在沙箱裡執行(sandbox + contextIsolation,沒有 Node、沒有宿主應用程式的任何資料),只能透過 window.pluginHost 這幾個方法跟自己的外掛程序通訊。樣式自己寫,建議按下面範例跟隨應用程式的亮/暗色。

interface PluginHost {
  // Call a tool declared in this plugin's manifest.tools; same call_tool channel the AI uses
  invoke(toolName: string, params: unknown): Promise<
    { content: unknown[]; details?: unknown } | { error: string }
  >;
  // Subscribe to ui_push messages from the plugin process; returns an unsubscribe function
  onPush(callback: (channel: string, payload: unknown) => void): () => void;
  // Current light/dark scheme and subsequent changes
  getColorScheme(): Promise<'light' | 'dark'>;
  onColorSchemeChange(callback: (scheme: 'light' | 'dark') => void): () => void;
}
declare const pluginHost: PluginHost; // exposed as 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 儲存庫」這類依賴執行期狀態的判斷。
// The host calls visibleTool with empty params and shows the icon based on details.visible
process.send({
  type: 'tool_result',
  requestId,
  content: [{ type: 'text', text: isGitRepo ? 'git repo' : 'not a git repo' }],
  details: { visible: isGitRepo },
});

09設定項 configuration

宣告後出現在設定頁的外掛設定裡。使用者改的值由宿主儲存,每次 call_tool 都會以 context.config 傳給你(鍵是 id,沒改過的用 defaultValue),不管這次呼叫來自 AI、頂欄圖示還是你的面板,你不需要自己去問。

type渲染成defaultValue 型別
select單選清單,候選項來自 optionsToolstring
boolean開關boolean
string文字輸入框,失焦提交string
number數字輸入框number
// For type: "select" settings, the host calls optionsTool with empty params to fetch options
process.send({
  type: 'tool_result',
  requestId,
  content: [{ type: 'text', text: '2 options' }],
  details: {
    options: [
      { value: 'fast', label: 'Fast' },
      { value: 'accurate', label: 'Accurate' },
    ],
  },
});
// The host automatically prepends an "Auto" option whose value equals this
// configuration entry's defaultValue; the plugin must not return it again.

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 可呼叫工具暴露出去。

// The toolName declared in documentParsers is called when the user drops a file into the input:
// params = { base64: "<file content as base64>" }
// Return: details must be { success: true, text } or { success: false, reason }
process.on('message', (msg) => {
  if (msg.type !== 'call_tool' || msg.toolName !== 'parse_markdown') return;
  const text = Buffer.from(msg.params.base64, 'base64').toString('utf8');
  process.send({
    type: 'tool_result',
    requestId: msg.requestId,
    content: [{ type: 'text', text: 'parsed' }],
    details: { success: true, text },
  });
});

12反向 RPC host_request

外掛程序沒有 Electron API,需要「真實系統能力」時向宿主發 host_request,宿主回 host_response 或 host_error。這條通道和 call_tool 完全獨立,可以在處理一次工具呼叫的過程中發起,也可以在計時器裡發起。

capabilityparamsresult前提
show_notification{ title, body?, viewId? }{ shown: true } / { shown: false, reason: "disabled" | "unsupported" }宣告 notifications 權限;使用者在設定裡關閉桌面通知時回傳 disabled 而不是報錯。viewId 指向自己的停靠視圖時,點擊通知會把應用程式拉到前景並展開該視圖。
render_pdf{ outputPath, title, content }{}content 是 Markdown,宿主用內建渲染器產生 PDF 到 outputPath。
const crypto = require('crypto');
const pending = new Map();

function requestHost(capability, params) {
  const requestId = crypto.randomUUID();
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => {
      pending.delete(requestId);
      reject(new Error(`host capability "${capability}" timed out`));
    }, 30_000);
    pending.set(requestId, { resolve, reject, timer });
    process.send({ type: 'host_request', requestId, capability, params });
  });
}

process.on('message', (msg) => {
  if (msg.type === 'host_response' || msg.type === 'host_error') {
    const p = pending.get(msg.requestId);
    if (!p) return;
    pending.delete(msg.requestId);
    clearTimeout(p.timer);
    if (msg.type === 'host_error') p.reject(new Error(msg.message));
    else p.resolve(msg.result);
  }
});

// usage
await requestHost('show_notification', {
  title: '3 todos for today',
  body: '• Write weekly report\n• Reply to emails',
  viewId: 'board',      // optional: expand this docked view when the notification is clicked
});
// => { shown: true } or { shown: false, reason: 'disabled' | 'unsupported' }

await requestHost('render_pdf', {
  outputPath: '/abs/path/report.pdf',
  title: 'Report',
  content: '# Title\n\nBody (Markdown)',
});

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 塞進去。

my-plugin.zip
├── manifest.json     # must be at the archive root
├── icon.png          # optional, referenced by manifest.icon
├── index.js
├── view.html
└── node_modules/…    # include dependencies if any (bundling into a single file is recommended)

發佈到市集

AnBoWork → 工具 → 外掛 → 「市集」分頁 → 右上角「發佈外掛」,選擇 zip 上傳。提交後進入待審核狀態,審核會核對 manifest 宣告與程式碼是否一致(尤其是權限)。通過後其他使用者可以在市集裡搜尋安裝,AI 也可以在判斷需要時自動為使用者安裝已認證的外掛。

更新

改 version 後重新發佈。用戶端按已安裝版本和市集版本比對提示更新;重裝會保留使用者的啟用狀態和設定項的值。

15完整範例:四象限待辦

一個真實可用的外掛(精簡版):AI 可以增刪待辦,右側欄有即時重新整理的看板,可以彈系統通知提醒。把三個能力串起來正好涵蓋了工具協定、停靠視圖推送和反向 RPC。

{
  "id": "local.todo-quadrant",
  "name": "Todo Quadrant",
  "version": "1.2.0",
  "description": "Daily todo list organized by the Eisenhower matrix. Install this whenever the user wants to track tasks or asks to be reminded about something…",
  "main": "index.js",
  "permissions": ["filesystem", "notifications"],
  "activationEvents": ["onStartupFinished"],
  "tools": [{
    "name": "manage_todo_quadrant",
    "summary": "Add, list, complete, delete and update todos in an urgent/important matrix",
    "description": "Manage the user's daily todo list… Actions: add / list / complete / delete / update / set_reminder / remind_now. add/update also accept remind_at to schedule a one-time reminder for that specific todo. Call this whenever the user talks about their todos…",
    "parameters": {
      "type": "object",
      "properties": {
        "action": { "type": "string", "enum": ["add", "list", "complete", "delete", "update", "set_reminder", "remind_now"] },
        "id": { "type": "string" },
        "content": { "type": "string" },
        "quadrant": { "type": "string", "enum": ["urgent_important", "important_not_urgent", "urgent_not_important", "not_urgent_not_important"] },
        "due_date": { "type": "string", "description": "YYYY-MM-DD" },
        "remind_at": { "type": "string", "description": "YYYY-MM-DD HH:MM — a one-time reminder for this todo, independent of the daily digest" }
      },
      "required": ["action"]
    }
  }],
  "views": [{ "id": "quadrant", "title": "Todo Quadrant", "icon": "checklist", "entry": "view.html", "width": 380 }]
}

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
查看 API 接入文件