// Guide du développeur

Documentation du développeur de plugins

Créez des plugins pour l'application de bureau AnBoWork : ajoutez de nouveaux outils pour l'IA, ancrez votre propre panneau à côté d'une session, envoyez des notifications système, analysez de nouveaux formats de fichiers. Un manifest.json plus un script d’entrée suffisent pour commencer.

01Aperçu

Un plugin AnBoWork est un simple programme Node.js. Après l'installation, l'application le transforme en processus enfant autonome (noeud pur, pas d'API Electron) et lui parle via des messages inter-processus. Aucun package SDK n’est requis ; tout ce dont vous avez besoin est process.on('message') et process.send().

Tout ce qu'un plugin peut faire est déclaré statiquement dans manifest.json : la page de consentement de pré-installation et l'examen du marché peuvent indiquer quels outils il ajoute et de quelles autorisations il a besoin sans exécuter de code de plugin. Toute l'interface utilisateur (icônes de la barre d'outils, paramètres, palette de commandes, menus contextuels) est restituée par l'hôte à partir de la déclaration ; le code du plugin ne s'exécute jamais dans la fenêtre principale. Seules les vues ancrées et les panneaux flottants constituent votre propre code HTML et s'exécutent dans des pages isolées en mode bac à sable.

Capacités en un coup d'oeil

Champ manifesteCapacitéOù les utilisateurs le voient
toolsAjouter des outils appelables pour l'IAL'IA les appelle automatiquement en conversation ; les appels d'outils sont affichés dans la session
viewsVue persistante ancrée dans la barre latérale droite de la sessionUne icône supplémentaire dans la barre d'outils de la session ouvre votre page à droite
uiFenêtre flottante autonomeOuvert depuis le menu "⋯" dans la liste des plugins
sessionActionIcône d'action persistante dans la barre d'outils de la sessionUn clic exécute l'outil que vous spécifiez
configurationEntrées sur la page des paramètresParamètres → Configuration du plugin ; les valeurs sont transmises à chaque appel
commandsEntrées de la palette de commandes Ctrl/Cmd+KPalette de commandes
keybindingsRaccourcis clavier globauxUne combinaison de touches déclenche une commande
menusÉléments du menu contextuel de la sessionLe menu "⋯" / clic droit sur une ligne de session
documentParsersAnalyser les fichiers déposés dans la zone de saisieAppelé automatiquement lorsque l'utilisateur dépose un fichier correspondant
activationEventsCommencez avec l'applicationInvisible; pour les plugins qui nécessitent des minuteries d'arrière-plan
permissionsDéclarer les autorisations requisesPage de consentement de préinstallation

02Démarrage rapide

Un plugin minimal n'a besoin que de deux fichiers.

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. Écrivez 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. Écrivez le script d'entrée

// 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. Installez dans l'application

Ouvrez AnBoWork → barre supérieure "Outils" → "Plugins" → "Installer localement" dans le coin supérieur droit, puis choisissez le dossier du plugin (ou un zip de celui-ci). Une page de consentement répertorie les autorisations déclarées et chaque outil avant l'installation ; confirmer pour activer.

4. Essayez-le

Dans n'importe quelle session, dites à l'IA "demandez au plugin de dire bonjour à Alice". Sur la base de la description de l'outil, l'IA décide d'appeler say_hello, et l'appel et son résultat apparaissent dans la session.

CONSEIL
L'IA à l'intérieur de AnBoWork peut également générer des plugins pour vous (dites "écrivez-moi un plugin qui fait X" et elle appelle create_local_plugin). Les plugins générés sont désactivés par défaut jusqu'à ce que vous examiniez le code sur la page du plugin et que vous les activiez manuellement. Ce guide s'applique également à la lecture et à la modification des plugins générés par l'IA.

03Champs du manifeste

ChampTaperRequisDescription
idstringOuiStyle de domaine inversé, par ex. com.example.my-plugin. Lettres, chiffres, points et traits d'union uniquement. Immuable une fois créé ; l'installation à nouveau du même identifiant est traitée comme une réinstallation.
namestringOuiNom du plugin. Avec description, il est transmis au modèle comme contexte, l'anglais est donc recommandé.
versionstringOuiVersion sémantique ; le marché l'utilise pour détecter les mises à jour.
descriptionstringOuiUne phrase décrivant ce que fait le plugin (anglais).
authorstringNonAuteur.
readmestringNonLa description détaillée affichée sur la page de détails du marché prend en charge Markdown. Les images à l'intérieur doivent être des URL https — les chemins relatifs vers les fichiers regroupés dans le zip ne sont pas pris en charge.
iconstringNonIcône affichée dans la liste du marché et la page de détails : un chemin relatif vers un fichier fourni dans le zip (par exemple icon.png). Doit être PNG, pas SVG, au moins 128×128.
mainstringOuiScript d'entrée, relatif au dossier du plugin. .. l'échappement du dossier n'est pas autorisé.
permissionsstring[]Ouinetwork / filesystem / notifications ; peut être un tableau vide. Voir « Modèle d'autorisation ».
toolsobject[]OuiDéclarations d'outils ; peut être un tableau vide. Voir « Outils et protocole d'appel ».
documentParsersobject[]NonCapacité d'analyse de fichiers.
uiobjectNonPanneau flottant :{ entry, title }
viewsobject[]NonVues ancrées :{ id, title, icon, entry, width? }
sessionActionobjectNonIcône de la barre d'outils, au maximum une par plugin.
configurationobject[]NonListe des entrées de paramètres.
commandsobject[]NonEntrées de la palette de commandes.
keybindingsobject[]NonRaccourcis. command doit faire référence au tool d'une entrée dans commands[], sinon l'installation est rejetée.
menusobjectNonSeul l'emplacement session/context est disponible pour l'instant.
activationEventsstring[]NonSeulement onStartupFinished pour l'instant.

Conventions linguistiques pour le texte

Le texte du manifeste a deux publics. Pour le modèle : name, description, tools[].description, tools[].parameters.*.description. Écrivez-les en anglais ; les modèles comprennent les descriptions des outils en anglais et remplissent les paramètres de manière plus fiable. Pour les personnes : tools[].summary (résumé d'une ligne sur la page de consentement), ui.title, views[].title, sessionAction.tooltip, configuration[].label, commands[].title et title à l'intérieur de menus. Écrivez-les dans la langue de vos utilisateurs cibles.

{
  "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" }
    ]
  }
}

04Modèle d'autorisation

permissions doit refléter honnêtement ce que fait réellement le code. La page de consentement de pré-installation répertorie chacun d'entre eux afin que les utilisateurs puissent décider d'installer ou non ; Marketplace Review vérifie également la déclaration par rapport au code.

AutorisationQuand il faut le déclarerComment l'hôte le traite
filesystemLecture ou écriture de fichiers utilisateur (lecture du code du projet, génération de documents, stockage des données dans le dossier personnel)Consentement éclairé, montré à l'utilisateur.
networkFaire toute requête réseau (API tierces, téléchargements)Consentement éclairé, montré à l'utilisateur.
notificationsAffichage des notifications système via le RPC inversé show_notificationEnforced : l'appeler sans la déclaration renvoie host_error et aucune notification n'est affichée.
NOTE
Le processus enfant du plugin est un environnement Node.js complet. filesystem et network sont actuellement des déclarations au niveau du consentement, pas un bac à sable d'exécution : l'hôte n'intercepte pas fs ou fetch. C'est exactement pourquoi les déclarations doivent être honnêtes. Les plugins qui sous-déclarent sont rejetés lors de l'examen du marché et perdent la confiance des utilisateurs lorsqu'ils sont découverts. Ne pas non plus trop déclarer (les autorisations inutilisées font hésiter les utilisateurs).

Confidentialité et données

context.cwd est le dossier du projet de l'utilisateur et peut contenir du code source et une configuration sensible ; les arguments de l'outil peuvent contenir la conversation de l'utilisateur. N'envoyez pas ces données sur le réseau à moins que la fonctionnalité ne l'exige, et lorsque vous le faites, indiquez en description et summary ce qui est envoyé et à qui. Conservez les données persistantes du plugin dans un fichier sous le dossier personnel de l'utilisateur nommé d'après l'identifiant du plugin, afin qu'il soit facile à trouver lors de la désinstallation.

05Outils et protocole d'appel

Déclaration

ChampDescription
nameNom de l'outil en lettres minuscules et traits de soulignement, unique dans toute l'application (il est enregistré avec le modèle aux côtés des outils intégrés et des outils d'autres plugins).
descriptionDescription du modèle (anglais). Énoncez trois choses : ce que fait l'outil, ce que dit l'utilisateur quand il doit être appelé et quand il ne doit pas être appelé. Il s’agit du champ le plus important pour savoir si l’IA utilise votre plugin.
summaryRésumé lisible en une ligne affiché sur la page de consentement. Facultatif mais fortement recommandé ; sinon, une description tronquée est affichée à la place.
parametersObjet de schéma JSON standard avec un description sur chaque propriété. Le modèle en remplit les arguments et l'hôte les transmet sans validation supplémentaire, alors vérifiez les champs obligatoires dans votre code.
renderAsFacultatif; actuellement seulement checklist : l'hôte restitue le tableau todos à partir des arguments d'appel sous forme de carte de liste de contrôle, voir ci-dessous.
Note
Le modèle ne peut appeler que les noms d'outils déclarés ici en tools[] — sessionAction.tool, commands[].tool et le champ tool à l'intérieur de menus ne sont vérifiés comme étant des chaînes non vides au moment de l'installation ; l'hôte ne vérifie pas pas que le nom est déclaré en tools[] et envoie toujours un appel sous ce nom lorsque le bouton, le raccourci ou l'élément de menu est déclenché. Ainsi, une action purement déclenchée par l'interface utilisateur peut être gérée entièrement dans votre propre index.js sans jamais apparaître dans tools[] — mais l'IA ne peut alors jamais la voir ou l'appeler. Si vous souhaitez que la même action puisse être déclenchée à la fois en cliquant et par l'IA agissant sur une demande de chat, déclarez également ce même nom dans tools[], afin que les deux points d'entrée partagent un seul gestionnaire ; s'il est censé rester uniquement sur l'interface utilisateur, laissez-le non déclaré : il n'est pas nécessaire d'ajouter une entrée juste pour avoir l'air complet.

Protocole de message

L'hôte envoie call_tool ; le plugin doit répondre par un tool_result ou un tool_error portant le même requestId. Un appel sans réponse dans les 30 secondes expire et le modèle reçoit une erreur.

// 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 n'accepte que les blocs text et image (image en base64 data plus mimeType) ; les autres types sont abandonnés. details correspond à des données structurées arbitraires qui ne sont jamais transmises au modèle ; c'est pour le rendu de l'interface utilisateur de l'hôte et vos propres vues ancrées.

Résultats du fichier : fiches de fichiers

// 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 },
});

Rendu de la liste de contrôle : liste de contrôle renderAs

// 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" }
  ]
}

Rédiger une bonne description

Décrivez le déclencheur à l'impératif (« Appelez ceci lorsque l'utilisateur demande à… ») et indiquez les exclusions (« Ne pas appeler ceci pour… »). Si le résultat est un fichier, demandez au modèle de référencer le lien du fichier renvoyé plutôt que de prétendre qu'il a été généré. Si les arguments sont transmis à une API de génération externe, indiquez dans quelle langue ils doivent être. Évitez les phrases simples comme « gérer les tâches » ; le modèle ne saura pas quand utiliser l’outil.

06Vues ancrées (vues)

Une vue ancrée est une page persistante dans la barre latérale droite de la session : une icône supplémentaire apparaît dans la barre d'outils de la session et votre code HTML s'affiche à droite lorsqu'il est ouvert. L'état de la page (position de défilement, brouillons) survit au changement de session et à la réduction/développement, la même sémantique qu'une vue de la barre latérale VS Code.

ChampDescription
idUnique au sein du plugin.
titleTexte d'info-bulle pour l'icône (lisible par l'homme).
iconChoisissez dans la liste d'autorisation des hôtes : git-branch, checklist. Maintient la cohérence des icônes dans toute l’application ; les images personnalisées ne sont pas acceptées.
entryFichier HTML, relatif au dossier du plugin.
widthLargeur du panneau par défaut en option en pixels, 240 à 640, 360 par défaut. Les utilisateurs peuvent faire glisser pour redimensionner et la taille est mémorisée.

Durée d'exécution des pages

La page s'exécute dans un bac à sable (sandbox + contextIsolation, pas de nœud, pas d'accès aux données de l'application hôte) et ne peut communiquer qu'avec son propre processus de plugin via les quelques méthodes sur window.pluginHost. Apportez vos propres styles ; Il est recommandé de suivre le schéma clair/sombre de l'application, comme dans l'exemple ci-dessous.

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

Modèle recommandé : lors du premier chargement, appelez l'un de vos propres outils de requête avec invoke() et demandez-lui de renvoyer des données structurées dans details ; Ensuite, poussez chaque modification du processus du plugin avec ui_push au lieu de l'interrogation. Lorsque l'IA modifie les données de la conversation, le panneau s'actualise immédiatement.

07Panneau flottant (ui)

ui: { entry, title } déclare une fenêtre flottante autonome (900 × 700) que les utilisateurs ouvrent à partir du menu "⋯" dans la liste des plugins ; il est détruit à la fermeture. L'environnement de la page et l'API window.pluginHost sont identiques aux vues ancrées, et ui_push est fourni à la fois au panneau flottant et aux vues ancrées. Adapté aux grands panneaux ouverts occasionnellement ; utilisez views pour le contenu qui doit rester à côté de la session.

08Icône de la barre d'outils de session (sessionAction)

Au plus une icône persistante par plugin. En cliquant dessus, vous appelez tool avec des arguments vides, de sorte que cet outil doit faire quelque chose de significatif sans paramètres.

ChampDescription
toolOutil pour appeler au clic.
iconNom de l’icône sur liste autorisée.
tooltipSurvolez le texte.
visibleToolFacultatif. Lorsqu'il est défini, il est appelé avec des arguments vides avant le rendu et le booléen details.visible dans le résultat décide si l'icône s'affiche. Utile pour les vérifications d'exécution telles que "le dossier actuel est-il un référentiel 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 },
});

09Paramètres (configuration)

Les entrées déclarées apparaissent sous la configuration du plugin sur la page des paramètres. L'hôte stocke les valeurs modifiées par l'utilisateur et les transmet sous la forme context.config à chaque call_tool (saisi par id, retombant à defaultValue), que l'appel provienne de l'IA, de l'icône de la barre d'outils ou de votre panneau. Vous n’avez jamais besoin de les demander.

typeRendu commeType de valeur par défaut
selectListe à choix unique ; les options viennent de optionsToolstring
booleanInterrupteur à basculeboolean
stringSaisie de texte, engagée sur le floustring
numberSaisie du numéronumber
// 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.

10Palette de commandes / raccourcis / menu contextuel

Tous les trois sont des points d'entrée différents pour « appeler un outil avec des arguments vides » ; un plugin peut en déclarer plusieurs.

ChampFormeDescription
commands{ tool, icon, title }[]Affiché dans la palette de commandes Ctrl/Cmd+K et déclenché après que l'utilisateur l'a recherché.
keybindings{ command, key }[]command doit être égal au tool d'une entrée dans commands[]. key utilise la notation mod+shift+g, où mod est Cmd sur macOS et Ctrl ailleurs. Les conflits avec d'autres raccourcis ne sont pas vérifiés de manière statique, choisissez donc des combinaisons inhabituelles.
menus{ "session/context": { tool, icon, title }[] }Ajouté au menu sur une ligne de session. Seul cet emplacement est disponible pour l'instant.

11Analyseurs de documents (documentParsers)

Lorsque l'utilisateur dépose un fichier dans la zone de saisie, l'hôte recherche les analyseurs déclarés par les plugins activés par type MIME et transmet le contenu du fichier en base64 au toolName correspondant. Ce n'est pas un outil que l'IA décide d'appeler, alors pas répertoriez également l'outil d'analyse sous tools, sinon il sera exposé comme un outil appelable par l'IA.

// 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 },
  });
});

12RPC inversé (host_request)

Le processus du plugin n'a pas d'API Electron. Lorsqu'il a besoin d'une réelle capacité du système, il envoie host_request à l'hôte, qui répond par host_response ou host_error. Ce canal est totalement indépendant de call_tool ; vous pouvez l'utiliser lors du traitement d'un appel d'outil ou à partir d'un minuteur.

capabilityparamsresultCondition préalable
show_notification{ title, body?, viewId? }{ shown: true } / { shown: false, reason: "disabled" | "unsupported" }Nécessite l'autorisation notifications. Renvoie désactivé au lieu d'une erreur lorsque l'utilisateur a désactivé les notifications du bureau dans les paramètres. Lorsque viewId pointe vers l'une de vos vues ancrées, cliquer sur la notification amène l'application au premier plan et développe cette vue.
render_pdf{ outputPath, title, content }{}le contenu est Markdown ; l'hôte restitue un PDF sur OutputPath avec son moteur de rendu intégré.
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)',
});

13Cycle de vie

ScèneComportement
CommencerParesseux par défaut : non démarré avec l'application, forké au premier appel à l'un de vos outils (IA, icône de la barre d'outils, panneau, commande, analyse de document). Les plugins déclarant activationEvents: ["onStartupFinished"] démarrent juste après la fin du lancement de l'application ; utilisez-le uniquement pour les plugins qui nécessitent des minuteries en arrière-plan (rappels, sondages), pas pour les plugins d'outils ordinaires.
RésidentUne fois démarré, le processus reste actif et est réutilisé lors des appels d'outils. Vous pouvez mettre l'état en cache en mémoire, mais supposez qu'il peut être redémarré à tout moment.
AccidentSi le processus se termine de manière inattendue, l'hôte le redémarre : immédiatement, puis après 5 et 15 secondes. Après 3 échecs consécutifs, il est marqué « crashé » et n'est plus réessayé ; les utilisateurs voient le statut sur la page du plugin. Les appels en vol renvoient tous des erreurs.
Désactiver/désinstaller/réinstallerLe processus est terminé et les vues ancrées et les panneaux flottants sont détruits ; une réinstallation remplace tout le dossier.
JournauxVos stdout/stderr sont écrits dans le fichier journal de l'application préfixé par l'identifiant du plugin. Un simple console.log suffit pour le débogage ; voir "Débogage et FAQ".
ENV
Le processus enfant commence par ELECTRON_RUN_AS_NODE=1, cwd est le dossier d'installation du plugin et la version Node correspond à l'Electron fourni avec l'application. Ne dépendez d'aucun package installé globalement et ne présumez pas que npx existe.

14Emballage et édition

Conditionnement

Zippez le dossier du plugin avec manifest.json à la racine de l'archive. Si vous avez des dépendances tierces, regroupez-les d'abord dans un seul fichier avec esbuild/webpack au lieu d'expédier l'intégralité du 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)

Publication sur le marché

AnBoWork → Outils → Plugins → onglet « Marketplace » → « Publier le plugin » dans le coin supérieur droit, puis téléchargez le zip. Les soumissions entrent en révision, qui vérifie que la déclaration du manifeste correspond au code (en particulier les autorisations). Une fois approuvé, d’autres utilisateurs peuvent le trouver et l’installer sur le marché, et l’IA peut installer automatiquement des plugins vérifiés pour les utilisateurs lorsqu’elle le juge nécessaire.

Mises à jour

Cliquez sur version et publiez à nouveau. L'application compare la version installée avec la version du marché et vous invite à effectuer la mise à jour ; la réinstallation conserve l'état activé et les valeurs des paramètres de l'utilisateur.

15Exemple complet : Todo Quadrant

Un vrai plugin fonctionnel (tronqué) : l'IA peut ajouter et supprimer des tâches, la barre latérale droite affiche un tableau qui s'actualise en temps réel et elle peut envoyer des rappels de notification au système. Le chaînage de ces trois éléments couvre le protocole de l'outil, les poussées de vue ancrée et le RPC inversé.

{
  "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 }]
}

16Débogage et FAQ

SymptômeCauser et réparer
La validation du manifeste échoue lors de l'installationVérifiez chaque champ par rapport au tableau « Champs du manifeste » comme l'indique le message : format d'identifiant, main/entry ne doit pas contenir .., l'icône doit être sur la liste autorisée, la commande keybindings doit pointer vers un outil existant dans les commandes, largeur comprise entre 240 et 640.
L'IA n'appelle jamais mon outilPresque toujours un problème description : le déclencheur n'est pas clair ou il n'est pas écrit en anglais. Voir « Rédiger une bonne description ». Vérifiez également que le nom de l'outil n'entre pas en collision avec un autre outil.
Expiration du délai d'appel (30 s)Le script d'entrée n'a pas répondu par tool_result. Habituellement, requestId n'a pas été renvoyé, le message type est mal orthographié ou le script est lancé au démarrage et s'arrête. Recherchez les lignes préfixées par [plugin:your-id] dans le journal de l'application.
L'état du plugin indique "planté"3 démarrages ratés consécutifs. Généralement un require d'un paquet manquant ; l'exécution de node index.js dans le dossier du plugin le reproduit. Après la correction, désactivez et réactivez le plugin sur la page du plugin.
Le panneau s'ouvre videMauvais chemin entry ou erreur JS sur la page. La page s'exécute dans un bac à sable sans le require de Node ; seul window.pluginHost est disponible.
La notification n'apparaît pasVérifiez dans l'ordre : le manifeste déclare notifications ; le résultat n'est pas shown:false (l'utilisateur a désactivé les notifications sur le bureau) ; le système d'exploitation autorise les notifications de AnBoWork.
Les valeurs des paramètres ne sont pas lisiblesLes valeurs sont en message.context.config[id], pas en params.

Emplacement du fichier journal

Système d'exploitationChemin
macOS~/Library/Logs/AnBoWork/main.log
Windows%USERPROFILE%\AppData\Roaming\AnBoWork\logs\main.log
Voir la documentation sur l'intégration de l'API