Aller au contenu

Internes de l'architecture des plugins

Pour le modèle de capacité publique, les formes de plugins et les contrats de propriété/exécution, consultez Architecture des plugins. Cette page constitue la référence pour la mécanique interne : pipeline de chargement, registre, hooks d’exécution, routes HTTP Gateway, chemins d’importation et tables de schéma.

Au démarrage, OpenClaw fait approximativement ceci :

  1. découvrir les racines candidates des plugins
  2. lire les manifestes de bundle natifs ou compatibles et les métadonnées des packages
  3. rejeter les candidats non sécurisés
  4. normaliser la configuration du plugin (plugins.enabled, allow, deny, entries, slots, load.paths)
  5. décider de l’activation pour chaque candidat
  6. charger les modules natifs activés : les modules groupés intégrés utilisent un chargeur natif ; le code source TypeScript tiers local utilise le recours de secours Jiti
  7. appeler les hooks natifs register(api) et collecter les inscriptions dans le registre des plugins
  8. exposer le registre aux commandes/surfaces d’exécution

Les verrous de sécurité se produisent avant l’exécution. Les candidats sont bloqués lorsque le point d’entrée sort de la racine du plugin, que le chemin est accessible en écriture par tous, ou que la propriété du chemin semble suspecte pour les plugins non groupés.

Les candidats bloqués restent liés à leur identifiant de plugin pour le diagnostic. Si la configuration référence toujours cet identifiant, la validation signale le plugin comme présent mais bloqué et renvoie à l’avertissement de sécurité du chemin au lieu de traiter l’entrée de configuration comme obsolète.

Le manifeste est la source de vérité du plan de contrôle. OpenClaw l’utilise pour :

  • identifier le plugin
  • découvrir les canaux/compétences/schémas de configuration déclarés ou les capacités groupées
  • valider plugins.entries.<id>.config
  • augmenter les étiquettes/espaces réservés de l’interface utilisateur de contrôle
  • afficher les métadonnées d’installation/de catalogue
  • préserver les descripteurs d’activation et de configuration peu coûteux sans charger le runtime du plugin

Pour les plugins natifs, le module d’exécution est la partie du plan de données. Il enregistre le comportement réel tel que les crochets, les outils, les commandes ou les flux du fournisseur.

Les blocs activation et setup facultatifs du manifeste restent sur le plan de contrôle. Ce sont des descripteurs de métadonnées uniquement pour la planification de l’activation et la découverte de la configuration ; ils ne remplacent pas l’enregistrement du runtime, register(...) ou setupEntry. Les premiers consommateurs d’activation en direct utilisent désormais les indices de commande, de canal et de fournisseur du manifeste pour restreindre le chargement des plugins avant une matérialisation plus large du registre :

  • Le chargement de la CLI se restreint aux plugins qui possèdent la commande principale demandée
  • la configuration du canal/la résolution du plugin se restreint aux plugins qui possèdent l’identifiant de canal demandé
  • la configuration explicite du fournisseur/la résolution du runtime se restreint aux plugins qui possèdent l’identifiant de fournisseur demandé
  • La planification du démarrage du Gateway utilise Gatewayactivation.onStartup pour les importations de démarrage explicites et les refus de démarrage ; les plugins sans métadonnées de démarrage ne sont chargés que par des déclencheurs d’activation plus restreints

Les préchargements d’exécution au moment de la requête qui demandent l’étendue allOpenClaw large dérivent toujours un ensemble explicite d’ID de plugin effectifs à partir de la configuration, de la planification du démarrage, des canaux configurés, emplacements et règles d’activation automatique. Si cet ensemble dérivé est vide, OpenClaw charge un registre d’exécution vide au lieu d’élargir à chaque plugin détectable.

Le planificateur d’activation expose à la fois une API d’ID uniquement pour les appelants existants et une API de plan pour les nouveaux diagnostics. Les entrées de plan signalent pourquoi un plugin a été sélectionné, séparant les indices explicites APIAPIactivation.* du planificateur du repli de propriété du manifeste tel que providers, channels, commandAliases, setup.providers, contracts.tools et les hooks. Cette séparation des raisons est la limite de compatibilité : les métadonnées de plugin existantes continuent de fonctionner, tandis que le nouveau code peut détecter des indices larges ou un comportement de repli sans modifier la sémantique de chargement de l’exécution.

La découverte de la configuration préfère désormais les ID détenus par le descripteur tels que setup.providers et setup.cliBackends pour réduire les plugins candidats avant de revenir à setup-api pour les plugins qui ont encore besoin de hooks d’exécution au moment de la configuration. Les listes de configuration du fournisseur utilisent le providerAuthChoices du manifeste, les choix de configuration dérivés du descripteur et les métadonnées du catalogue d’installation sans charger le runtime du fournisseur. Le setup.requiresRuntime: false explicite est une coupure déscripteur uniquement ; l’requiresRuntime omis conserve le repli de l’API de configuration héritée pour la compatibilité. Si plus d’un plugin découvert revendique le même ID de fournisseur de configuration normalisé ou d’interface backend CLI, la recherche de configuration refuse le propriétaire ambigu au lieu de s’appuyer sur l’ordre de découverte. Lorsque le runtime de configuration s’exécute, les diagnostics du registre signalent une dérive entre setup.providers / setup.cliBackends et les fournisseurs ou les interfaces backend CLI enregistrés par l’API de configuration sans bloquer les plugins hérités.

OpenClaw ne met pas en cache les résultats de découverte de plugins ni les données directes du registre de manifestes derrière des fenêtres d’horloge. Les installations, les modifications de manifestes et les changements de chemin de chargement doivent devenir visibles lors de la prochaine lecture explicite des métadonnées ou de la reconstruction de l’instantané. L’analyseur de fichiers de manifeste peut conserver un cache de signatures de fichiers limité, indexé par le chemin du manifeste ouvert, l’inode, la taille et les horodatages ; ce cache évite uniquement de ré-analyser les octets non modifiés et ne doit pas mettre en cache les réponses de découverte, de registre, de propriétaire ou de stratégie.

Le chemin rapide sécurisé pour les métadonnées est la propriété explicite des objets, et non un cache caché. Les chemins critiques de démarrage du Gateway doivent transmettre le PluginMetadataSnapshot actuel, le PluginLookUpTable dérivé ou un registre de manifestes explicite via la chaîne d’appels. La validation de la configuration, l’activation automatique au démarrage, l’amorçage des plugins et la sélection du provider peuvent réutiliser ces objets tant qu’ils représentent la configuration actuelle et l’inventaire des plugins. La recherche de configuration reconstruit toujours les métadonnées du manifeste à la demande, sauf si le chemin de configuration spécifique reçoit un registre de manifestes explicite ; gardez cela comme solution de repli à chemin froid plutôt que d’ajouter des caches de recherche cachés. Lorsque l’entrée change, reconstruisez et remplacez l’instantané au lieu de le modifier ou de conserver des copies historiques. Les vues sur le registre de plugins actif et les assistants d’amorçage de channel regroupés doivent être recalculées à partir du registre/racine actuel. Les cartes à courte durée de vie sont acceptables dans un seul appel pour dédupliquer le travail ou protéger contre la réentrée ; elles ne doivent pas devenir des caches de métadonnées de processus.

Pour le chargement des plugins, la couche de cache persistante est le chargement d’exécution. Elle peut réutiliser l’état du chargeur lorsque le code ou les artefacts installés sont réellement chargés, tels que :

  • PluginLoaderCacheState et les registres d’exécution actifs compatibles
  • les caches jiti/module et les caches de chargeur de surface publique utilisés pour éviter d’importer répétitivement la même surface d’exécution
  • les caches de système de fichiers pour les artefacts de plugins installés
  • les cartes par appel à courte durée de vie pour la normalisation des chemins ou la résolution des doublons

Ces caches sont des détails d’implémentation du plan de données. Ils ne doivent pas répondre aux questions du plan de contrôle telles que « quel plugin possède ce provider ? » à moins que l’appelant n’ait délibérément demandé le chargement d’exécution.

N’ajoutez pas de caches persistants ou d’horloge pour :

  • les résultats de découverte
  • les registres de manifestes directs
  • registres de manifestes reconstruits à partir de l’index des plugins installés
  • recherche du propriétaire du provider, suppression du modèle, stratégie du provider, ou métadonnées d’artefact public
  • toute autre réponse dérivée du manifeste où un manifeste modifié, un index installé ou un chemin de chargement doit être visible lors de la prochaine lecture des métadonnées

Les appelants qui reconstruisent les métadonnées de manifeste à partir de l’index persistant des plugins installés reconstruisent ce registre à la demande. L’index installé est un état durable du plan source ; ce n’est pas un cache de métadonnées caché dans le processus.

Les plugins chargés ne modifient pas directement les globales principales aléatoires. Ils s’enregistrent dans un registre central de plugins.

Le registre suit :

  • enregistrements de plugins (identité, source, origine, statut, diagnostics)
  • outils
  • hooks hérités et hooks typés
  • canaux
  • providers
  • gestionnaires RPC de passerelle
  • routes HTTP
  • registrars CLI
  • services d’arrière-plan
  • commandes détenues par le plugin

Les fonctionnalités principales lisent ensuite ce registre au lieu de communiquer directement avec les modules de plugins. Cela maintient le chargement unidirectionnel :

  • module de plugin -> enregistrement du registre
  • runtime principal -> consommation du registre

Cette séparation est importante pour la maintenabilité. Cela signifie que la plupart des surfaces principales n’ont besoin que d’un point d’intégration : “lire le registre”, et non “cas particulier pour chaque module de plugin”.

Les plugins qui lient une conversation peuvent réagir lorsqu’une approbation est résolue.

Utilisez api.onConversationBindingResolved(...) pour recevoir un rappel après qu’une demande de liaison est approuvée ou refusée :

export default {
id: "my-plugin",
register(api) {
api.onConversationBindingResolved(async (event) => {
if (event.status === "approved") {
// A binding now exists for this plugin + conversation.
console.log(event.binding?.conversationId);
return;
}
// The request was denied; clear any local pending state.
console.log(event.request.conversation.conversationId);
});
},
};

Champs de la charge utile du rappel :

  • status : "approved" ou "denied"
  • decision : "allow-once", "allow-always" ou "deny"
  • binding : la liaison résolue pour les demandes approuvées
  • request : le résumé de la demande d’origine, l’indice de détachement, l’identifiant de l’expéditeur et les métadonnées de la conversation

Ce rappel est uniquement une notification. Il ne modifie pas qui est autorisé à lier une conversation et s’exécute après la fin du traitement de l’approbation principale.

Les plugins de provider comportent trois couches :

  • Métadonnées de manifeste pour une recherche pré-exécution économique : setup.providers[].envVars, compatibilité obsolète providerAuthEnvVars, providerAuthAliases, providerAuthChoices et channelEnvVars.
  • Hooks de configuration : catalog (discovery hérité) plus applyConfigDefaults.
  • Hooks d’exécution : plus de 40 hooks facultatifs couvrant l’authentification, la résolution de modèle, l’encapsulation de flux, les niveaux de réflexion, la stratégie de relecture et les points de terminaison d’utilisation. Consultez la liste complète sous Ordre et utilisation des hooks.

OpenClaw conserve toujours la boucle d’agent générique, le basculement (failover), la gestion des transcriptions et la stratégie d’outil. Ces hooks constituent la surface d’extension pour les comportements spécifiques au fournisseur sans nécessiter un transport d’inférence entièrement personnalisé.

Utilisez le manifeste setup.providers[].envVars lorsque le fournisseur dispose d’informations d’identification basées sur des variables d’environnement que les chemins d’authentification/génération d’état/sélection de modèle génériques devraient voir sans charger le runtime du plugin. L’élément obsolète providerAuthEnvVars est toujours lu par l’adaptateur de compatibilité pendant la fenêtre de dépréciation, et les plugins non regroupés qui l’utilisent reçoivent un diagnostic de manifeste. Utilisez le manifeste providerAuthAliases lorsqu’un identifiant de fournisseur doit réutiliser les variables d’environnement, les profils d’authentification, l’authentification basée sur la configuration et le choix d’intégration de clé API d’un autre identifiant de fournisseur. Utilisez le manifeste providerAuthChoices lorsque les surfaces CLI d’intégration/de choix d’authentification doivent connaître l’identifiant de choix du fournisseur, les étiquettes de groupe et le câblage d’authentification simple à un seul indicateur sans charger le runtime du fournisseur. Gardez le envVars du runtime du fournisseur pour les indications destinées aux opérateurs, telles que les étiquettes d’intégration ou les variables de configuration client-id/client-secret OAuth.

Utilisez le manifeste channelEnvVars lorsqu’un canal dispose d’une authentification ou d’une configuration pilotée par l’environnement que le repli générique d’environnement de shell, les vérifications de configuration/d’état ou les invites de configuration devraient voir sans charger le runtime du canal.

Pour les plugins de modèle/fournisseur, OpenClaw appelle les hooks dans cet ordre approximatif. La colonne “Quand utiliser” sert de guide de décision rapide. Les champs de fournisseur uniquement pour la compatibilité que OpenClaw n’appelle plus, tels que ProviderPlugin.capabilities et suppressBuiltInModel%, ne sont pas intentionnellement listés ici.

#HookCe qu’il faitQuand utiliser
1catalogPublier la config du fournisseur dans models.providers lors de la génération models.jsonLe fournisseur possède un catalogue ou des valeurs par défaut d’URL de base
2applyConfigDefaultsAppliquer les valeurs par défaut de config globales possédées par le fournisseur lors de la matérialisation de la configLes valeurs par défaut dépendent du mode d’auth, de l’env, ou de la sémantique de la famille de modèles du fournisseur
(recherche de modèle intégrée)OpenClaw essaie d’abord le chemin normal du registre/catalogue(pas un hook de plugin)
3normalizeModelIdNormaliser les alias d’ID de modèle hérités ou preview avant la rechercheLe fournisseur possède le nettoyage des alias avant la résolution du modèle canonique
4normalizeTransportNormaliser api / baseUrl de la famille du fournisseur avant l’assemblage du modèle génériqueLe fournisseur possède le nettoyage du transport pour les IDs de fournisseur personnalisés dans la même famille de transport
5normalizeConfigNormaliser models.providers.<id> avant la résolution runtime/fournisseurLe fournisseur a besoin d’un nettoyage de config qui doit résider avec le plugin ; les aides Google groupées servent également de filet de sécurité pour les entrées de config Google prises en charge
6applyNativeStreamingUsageCompatAppliquer les réécritures de compatibilité d’utilisation du streaming natif aux fournisseurs de configLe fournisseur a besoin de correctifs de métadonnées d’utilisation du streaming natif pilotés par le point de terminaison
7resolveConfigApiKeyRésoudre l’auth par marqueur d’env pour les fournisseurs de config avant le chargement de l’auth runtimeLe fournisseur a une résolution de clé API par marqueur d’env possédée par le fournisseur ; amazon-bedrock dispose également ici d’un résolveur AWS par marqueur d’env intégré
8resolveSyntheticAuthExposer l’auth locale/auto-hébergée ou basée sur la config sans persister le texte en clairLe fournisseur peut fonctionner avec un marqueur d’identification synthétique/local
9resolveExternalAuthProfilesSuperposer les profils d’authentification externe appartenant au provider ; la valeur par défaut persistence est runtime-only pour les identifiants appartenant à la CLI/l’applicationLe provider réutilise les identifiants d’authentification externe sans conserver les jetons d’actualisation copiés ; déclarer contracts.externalAuthProviders dans le manifeste
10shouldDeferSyntheticProfileAuthAbaisser les espaces réservés de profil synthétiques stockés derrière une authentification soutenue par env/configLe provider stocke des profils synthétiques espaces réservés qui ne doivent pas primer
11resolveDynamicModelSynchronisation de secours pour les ids de modèle appartenant au provider qui ne sont pas encore dans le registre localLe provider accepte des ids de modèle en amont arbitraires
12prepareDynamicModelPréchauffage asynchrone, puis resolveDynamicModel s’exécute à nouveauLe provider a besoin de métadonnées réseau avant de résoudre les ids inconnus
13normalizeResolvedModelRéécriture finale avant que l’exécuteur intégré n’utilise le modèle résoluLe provider a besoin de réécritures de transport mais utilise toujours un transport principal
14contributeResolvedModelCompatContribuer aux indicateurs de compatibilité pour les modèles de fournisseur derrière un autre transport compatibleLe provider reconnaît ses propres modèles sur les transports proxy sans prendre le pas sur le provider
15normalizeToolSchemasNormaliser les schémas d’outils avant que l’exécuteur intégré ne les voieLe provider a besoin d’un nettoyage de schéma de famille de transport
16inspectToolSchemasAfficher les diagnostics de schéma appartenant au provider après normalisationLe provider souhaite des avertissements de mots-clés sans enseigner de règles spécifiques au provider principal
17resolveReasoningOutputModeSélectionner le contrat de sortie de raisonnement natif ou étiquetéLe provider a besoin d’une sortie de raisonnement/finale étiquetée au lieu de champs natifs
18prepareExtraParamsNormalisation des paramètres de requête avant les enveloppes d’options de flux génériquesLe provider a besoin de paramètres de requête par défaut ou d’un nettoyage des paramètres par provider
19createStreamFnRemplacer entièrement le chemin de flux normal par un transport personnaliséLe provider a besoin d’un protocole filaire personnalisé, pas seulement d’un enveloppeur
20wrapStreamFnEnveloppeur de flux après l’application des enveloppeurs génériquesLe provider a besoin d’enveloppeurs de compatibilité pour les en-têtes/corps/modèles de requête sans transport personnalisé
21resolveTransportTurnStateAttacher des en-têtes ou métadonnées de transport natifs par tourLe provider souhaite que les transports génériques envoient l’identité de tour native du provider
22resolveWebSocketSessionPolicyAttacher des en-têtes WebSocket natifs ou une politique de refroidissement de sessionLe provider souhaite que les transports WS génériques ajustent les en-têtes de session ou la politique de repli
23formatApiKeyFormateur de profil d’auth : le profil stocké devient la chaîne apiKey d’exécutionLe provider stocke des métadonnées d’auth supplémentaires et a besoin d’une forme de token d’exécution personnalisée
24refreshOAuthRemplacement du rafraîchissement OAuth pour les points de terminaison de rafraîchissement personnalisés ou la politique d’échec de rafraîchissementLe provider ne correspond pas aux rafraîchisseurs pi-ai partagés
25buildAuthDoctorHintIndice de réparation ajouté lorsque le rafraîchissement OAuth échoueLe provider a besoin de conseils de réparation d’auth appartenant au provider après un échec de rafraîchissement
26matchesContextOverflowErrorCorrespondance de dépassement de fenêtre de contexte appartenant au providerLe provider a des erreurs brutes de dépassement que les heuristiques génériques manqueraient
27classifyFailoverReasonClassification des motifs de basculement appartenant au providerLe provider peut mapper les erreurs brutes API/transport aux limites de taux/surcharges/etc
28isCacheTtlEligiblePolitique de cache de prompt pour les providers de proxy/backhaulLe provider a besoin d’une porte de TTL de cache spécifique au proxy
29buildMissingAuthMessageRemplacement du message générique de récupération d’auth manquanteLe provider a besoin d’un indice de récupération d’auth manquante spécifique au provider
30augmentModelCatalogLignes de catalogue synthétiques/finales ajoutées après la découverteLe provider a besoin de lignes synthétiques de compatibilité ascendante dans models list et les sélecteurs
31resolveThinkingProfileDéfinition de niveau /think spécifique au modèle, étiquettes d’affichage et valeur par défautLe fournisseur expose une échelle de réflexion personnalisée ou une étiquette binaire pour certains modèles
32isBinaryThinkingHook de compatibilité pour le basculement du raisonnement on/offLe fournisseur expose uniquement une réflexion binaire on/off
33supportsXHighThinkingHook de compatibilité du support de raisonnement xhighLe provider souhaite xhigh uniquement sur un sous-ensemble de modèles
34resolveDefaultThinkingLevelHook de compatibilité du niveau /think par défautLe fournisseur possède la stratégie par défaut /think pour une famille de modèles
35isModernModelRefCorrespondance de modèle moderne pour les filtres de profil en direct et la sélection de smokeLe fournisseur possède la correspondance de modèle préférée en direct/smoke
36prepareRuntimeAuthÉchanger une information d’identification configurée contre le jeton/clé de runtime réel juste avant l’inférenceLe fournisseur a besoin d’un échange de jeton ou d’une information d’identification de demande à courte durée de vie
37resolveUsageAuthRésoudre les identifiants d’utilisation/facturation pour /usage et les surfaces d’état associéesLe fournisseur a besoin d’une analyse personnalisée des jetons d’utilisation/quota ou d’informations d’identification d’utilisation différentes
38fetchUsageSnapshotRécupérer et normaliser les instantanés d’utilisation/quota spécifiques au fournisseur après résolution de l’authentificationLe fournisseur a besoin d’un point de terminaison d’utilisation ou d’un analyseur de charge utile spécifique au fournisseur
39createEmbeddingProviderConstruire un adaptateur d’intégration appartenant au fournisseur pour la mémoire/rechercheLe comportement d’intégration de la mémoire appartient au plugin du fournisseur
40buildReplayPolicyRenvoyer une stratégie de relecture contrôlant la gestion des transcriptions pour le fournisseurLe fournisseur a besoin d’une stratégie de transcription personnalisée (par exemple, suppression des blocs de réflexion)
41sanitizeReplayHistoryRéécrire l’historique de relecture après le nettoyage générique de la transcriptionLe fournisseur a besoin de réécritures de relecture spécifiques au fournisseur au-delà des assistants de compactage partagés
42validateReplayTurnsValidation finale ou restructuration du tour de relecture avant le runner intégréLe transport du provider a besoin d’une validation de tour plus stricte après la désinfection générique
43onModelSelectedExécuter les effets secondaires post-sélection appartenant au providerLe provider a besoin de télémétrie ou d’un état appartenant au provider lorsqu’un modèle devient actif

normalizeModelId, normalizeTransport, et normalizeConfig vérifient d’abord le plugin fournisseur correspondant, puis passent aux autres plugins fournisseurs capables d’accroches jusqu’à ce que l’un modifie réellement l’identifiant du modèle ou le transport/la configuration. Cela permet de maintenir le fonctionnement des shims d’alias/compatibilité des fournisseurs sans que l’appelant ait besoin de savoir quel plugin groupé possède la réécriture. Si aucune accroche de fournisseur ne réécrit une entrée de configuration prise en charge de la famille Google, le normaliseur de configuration groupé Google applique toujours ce nettoyage de compatibilité.

Si le provider a besoin d’un protocole filaire entièrement personnalisé ou d’un exécuteur de requêtes personnalisé, il s’agit d’une classe d’extension différente. Ces hooks sont destinés au comportement du provider qui s’exécute toujours sur la boucle d’inférence normale d’OpenClaw.

api.registerProvider({
id: "example-proxy",
label: "Example Proxy",
auth: [],
catalog: {
order: "simple",
run: async (ctx) => {
const apiKey = ctx.resolveProviderApiKey("example-proxy").apiKey;
if (!apiKey) {
return null;
}
return {
provider: {
baseUrl: "https://proxy.example.com/v1",
apiKey,
api: "openai-completions",
models: [{ id: "auto", name: "Auto" }],
},
};
},
},
resolveDynamicModel: (ctx) => ({
id: ctx.modelId,
name: ctx.modelId,
provider: "example-proxy",
api: "openai-completions",
baseUrl: "https://proxy.example.com/v1",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 8192,
}),
prepareRuntimeAuth: async (ctx) => {
const exchanged = await exchangeToken(ctx.apiKey);
return {
apiKey: exchanged.token,
baseUrl: exchanged.baseUrl,
expiresAt: exchanged.expiresAt,
};
},
resolveUsageAuth: async (ctx) => {
const auth = await ctx.resolveOAuthToken();
return auth ? { token: auth.token } : null;
},
fetchUsageSnapshot: async (ctx) => {
return await fetchExampleProxyUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn);
},
});

Les plugins fournisseurs groupés combinent les accroches ci-dessus pour répondre aux besoins de catalogue, d’authentification, de réflexion, de relecture et d’utilisation de chaque fournisseur. L’ensemble autorisé d’accroches se trouve avec chaque plugin sous extensions/; cette page illustre les formes plutôt que de refléter la liste.

Fournisseurs de catalogue en mode passe-through

OpenRouter, Kilocode, Z.AI, xAI enregistrent catalog plus resolveDynamicModel / prepareDynamicModelOpenClaw afin qu’ils puissent afficher les identifiants de modèles en amont avant le catalogue statique d’OpenClaw.

OAuthFournisseurs de points de terminaison OAuth et d'utilisation

GitHub Copilot, Gemini CLI, ChatGPT Codex, MiniMax, Xiaomi, z.ai pair prepareRuntimeAuth ou formatApiKey avec resolveUsageAuth + fetchUsageSnapshot pour posséder l’échange de jetons et l’intégration /usage.

Familles de relecture et de nettoyage de transcriptions

Des familles nommées partagées (google-gemini, passthrough-gemini, anthropic-by-model, hybrid-anthropic-openai) permettent aux fournisseurs d’opter pour une stratégie de transcription via buildReplayPolicy au lieu que chaque plugin réimplémente le nettoyage.

Fournisseurs de catalogue uniquement

byteplus, cloudflare-ai-gateway, huggingface, kimi-coding, nvidia, qianfan, synthetic, together, venice, vercel-ai-gateway et volcengine n’enregistrent que catalog et utilisent la boucle d’inférence partagée.

Assistants de flux spécifiques à Anthropic

Les en-têtes bêta, /fast / serviceTier et context1m se trouvent dans la jointure publique api.ts / contract-api.ts du plugin Anthropic (wrapAnthropicProviderStream, resolveAnthropicBetas, resolveAnthropicFastMode, resolveAnthropicServiceTier) plutôt que dans le SDK générique.

Les plugins peuvent accéder à des assistants principaux sélectionnés via api.runtime. Pour le TTS :

const clip = await api.runtime.tts.textToSpeech({
text: "Hello from OpenClaw",
cfg: api.config,
});
const result = await api.runtime.tts.textToSpeechTelephony({
text: "Hello from OpenClaw",
cfg: api.config,
});
const voices = await api.runtime.tts.listVoices({
provider: "elevenlabs",
cfg: api.config,
});

Notes :

  • textToSpeech renvoie la charge utile de sortie TTS principale normale pour les surfaces de fichier/note vocale.
  • Utilise la configuration messages.tts principale et la sélection du provider.
  • Returns PCM audio buffer + sample rate. Plugins must resample/encode for providers.
  • listVoices est optionnel par provider. Utilisez-le pour les sélecteurs de voix ou les flux de configuration détenus par le fournisseur.
  • Voice listings can include richer metadata such as locale, gender, and personality tags for provider-aware pickers.
  • OpenAI and ElevenLabs support telephony today. Microsoft does not.

Les plugins peuvent également enregistrer des providers de synthèse vocale via api.registerSpeechProvider(...).

api.registerSpeechProvider({
id: "acme-speech",
label: "Acme Speech",
isConfigured: ({ config }) => Boolean(config.messages?.tts),
synthesize: async (req) => {
return {
audioBuffer: Buffer.from([]),
outputFormat: "mp3",
fileExtension: ".mp3",
voiceCompatible: false,
};
},
});

Notes:

  • Keep TTS policy, fallback, and reply delivery in core.
  • Use speech providers for vendor-owned synthesis behavior.
  • L’entrée edge Microsoft héritée est normalisée vers l’identifiant de provider microsoft.
  • The preferred ownership model is company-oriented: one vendor plugin can own text, speech, image, and future media providers as OpenClaw adds those capability contracts.

Pour la compréhension d’image/audio/vidéo, les plugins enregistrent un provider de compréhension de média typé au lieu d’un sac générique de paires clé/valeur :

api.registerMediaUnderstandingProvider({
id: "google",
capabilities: ["image", "audio", "video"],
describeImage: async (req) => ({ text: "..." }),
transcribeAudio: async (req) => ({ text: "..." }),
describeVideo: async (req) => ({ text: "..." }),
});

Notes :

  • Garder l’orchestration, le secours (fallback), la configuration et le câblage des canaux dans le cœur (core).
  • Garder le comportement du fournisseur dans le plugin du provider.
  • L’expansion additive doit rester typée : nouvelles méthodes optionnelles, nouveaux champs de résultat optionnels, nouvelles capacités optionnelles.
  • La génération vidéo suit déjà le même modèle :
    • le cœur (core) possède le contrat de capacité et l’assistant d’exécution (runtime helper)
    • les plugins fournisseur enregistrent api.registerVideoGenerationProvider(...)
    • les plugins de fonctionnalité/channel consomment api.runtime.videoGeneration.*

Pour les assistants d’exécution de compréhension de média, les plugins peuvent appeler :

const image = await api.runtime.mediaUnderstanding.describeImageFile({
filePath: "/tmp/inbound-photo.jpg",
cfg: api.config,
agentDir: "/tmp/agent",
});
const video = await api.runtime.mediaUnderstanding.describeVideoFile({
filePath: "/tmp/inbound-video.mp4",
cfg: api.config,
});
const extraction = await api.runtime.mediaUnderstanding.extractStructuredWithModel({
provider: "codex",
model: "gpt-5.5",
input: [
{
type: "image",
buffer: receiptImageBuffer,
fileName: "receipt.png",
mime: "image/png",
},
{ type: "text", text: "Use the printed fields as the source of truth." },
],
instructions: "Return entities and searchable tags.",
schemaName: "example.evidence",
jsonSchema: {
type: "object",
properties: {
entities: { type: "array", items: { type: "string" } },
tags: { type: "array", items: { type: "string" } },
},
},
cfg: api.config,
});

Pour la transcription audio, les plugins peuvent utiliser soit l’exécution de compréhension de média, soit l’alias STT plus ancien :

const { text } = await api.runtime.mediaUnderstanding.transcribeAudioFile({
filePath: "/tmp/inbound-audio.ogg",
cfg: api.config,
// Optional when MIME cannot be inferred reliably:
mime: "audio/ogg",
});

Notes :

  • api.runtime.mediaUnderstanding.* est la surface partagée préférée pour la compréhension d’image/audio/vidéo.
  • extractStructuredWithModel(...) est la jonction orientée plugin pour l’extraction d’images prioritaire, bornée et détenue par le provider. Incluez au moins une entrée d’image ; les entrées texte sont un contexte supplémentaire. les plugins produit possèdent leurs propres routes et schémas tandis que OpenClaw possède la limite provider/runtime.
  • Utilise la configuration audio principale de compréhension des médias (tools.media.audio) et l’ordre de secours des providers.
  • Renvoie { text: undefined } lorsqu’aucune sortie de transcription n’est produite (par exemple entrée ignorée/non prise en charge).
  • api.runtime.stt.transcribeAudioFile(...) reste un alias de compatibilité.

Les plugins peuvent également lancer des exécutions de sous-agents en arrière-plan via api.runtime.subagent :

const result = await api.runtime.subagent.run({
sessionKey: "agent:main:subagent:search-helper",
message: "Expand this query into focused follow-up searches.",
provider: "openai",
model: "gpt-4.1-mini",
deliver: false,
});

Notes :

  • provider et model sont des remplacements facultatifs par exécution, et non des modifications persistantes de session.
  • OpenClaw ne respecte ces champs de remplacement que pour les appelants de confiance.
  • Pour les exécutions de secours détenues par le plugin, les opérateurs doivent opter pour plugins.entries.<id>.subagent.allowModelOverride: true.
  • Utilisez plugins.entries.<id>.subagent.allowedModels pour restreindre les plugins de confiance à des cibles provider/model canoniques spécifiques, ou "*" pour autoriser explicitement n’importe quelle cible.
  • Les exécutions de sous-agents de plugins non approuvés fonctionnent toujours, mais les demandes de remplacement sont rejetées au lieu de retomber silencieusement.
  • Les sessions de sous-agents créées par des plugins sont étiquetées avec l’identifiant du plugin créateur. Le api.runtime.subagent.deleteSession(...) de secours ne peut supprimer que ces sessions détenues ; la suppression arbitraire de sessions nécessite toujours une requête Gateway avec portée d’administrateur.

Pour la recherche Web, les plugins peuvent utiliser l’assistant d’exécution partagé au lieu d’accéder au câblage des outils de l’agent :

const providers = api.runtime.webSearch.listProviders({
config: api.config,
});
const result = await api.runtime.webSearch.search({
config: api.config,
args: {
query: "OpenClaw plugin runtime helpers",
count: 5,
},
});

Les plugins peuvent également enregistrer des providers de recherche Web via api.registerWebSearchProvider(...).

Notes :

  • Conservez la sélection du provider, la résolution des identifiants et la sémantique des requêtes partagées dans le core.
  • Utilisez les providers de recherche Web pour les transports de recherche spécifiques aux fournisseurs.
  • api.runtime.webSearch.* est l’interface partagée préférée pour les plugins de fonctionnalité/canal qui ont besoin d’un comportement de recherche sans dépendre du wrapper d’outils de l’agent.
const result = await api.runtime.imageGeneration.generate({
config: api.config,
args: { prompt: "A friendly lobster mascot", size: "1024x1024" },
});
const providers = api.runtime.imageGeneration.listProviders({
config: api.config,
});
  • generate(...) : générer une image en utilisant la chaîne de providers de génération d’images configurée.
  • listProviders(...) : liste les fournisseurs de génération d’images disponibles et leurs capacités.

Les plugins peuvent exposer des points de terminaison HTTP avec api.registerHttpRoute(...).

api.registerHttpRoute({
path: "/acme/webhook",
auth: "plugin",
match: "exact",
handler: async (_req, res) => {
res.statusCode = 200;
res.end("ok");
return true;
},
});

Champs de route :

  • path : chemin de la route sous le serveur HTTP de la passerelle.
  • auth : obligatoire. Utilisez "gateway" pour exiger l’authentification normale de la passerelle, ou "plugin" pour une authentification gérée par le plugin / une vérification de webhook.
  • match : facultatif. "exact" (par défaut) ou "prefix".
  • replaceExisting : facultatif. Permet au même plugin de remplacer son propre enregistrement de route existant.
  • handler : renvoie true lorsque la route a traité la demande.

Notes :

  • api.registerHttpHandler(...) a été supprimé et provoquera une erreur de chargement de plugin. Utilisez plutôt api.registerHttpRoute(...).
  • Les routes des plugins doivent déclarer auth explicitement.
  • Les conflits exacts de path + match sont rejetés, sauf si replaceExisting: true, et un plugin ne peut pas remplacer la route d’un autre plugin.
  • Les routes qui se chevauchent avec des niveaux auth différents sont rejetées. Gardez les chaînes de rebond exact/prefix uniquement sur le même niveau d’authentification.
  • Les routes auth: "plugin" ne reçoivent pas automatiquement les scopes d’exécution de l’opérateur. Elles sont destinées à la vérification des webhooks/signatures gérée par le plugin, et non aux appels aux assistants privilégiés du Gateway.
  • Les routes auth: "gateway" s’exécutent dans un scope d’exécution de requête du Gateway, mais ce scope est intentionnellement conservateur :
    • l’authentification Bearer par secret partagé (gateway.auth.mode = "token" / "password") maintient les scopes d’exécution des routes de plugins épinglés à operator.write, même si l’appelant envoie x-openclaw-scopes
    • les modes HTTP de confiance porteurs d’identité (par exemple trusted-proxy ou gateway.auth.mode = "none" sur une entrée privée) honorent x-openclaw-scopes uniquement lorsque l’en-tête est explicitement présent
    • si x-openclaw-scopes est absent sur ces demandes de route de plugin porteuses d’identité, la portée d’exécution revient à operator.write
  • Règle pratique : ne supposez pas qu’une route de plugin d’authentification de passerelle est une surface d’administration implicite. Si votre route nécessite un comportement réservé aux administrateurs, exigez un mode d’authentification porteur d’identité et documentez le contrat d’en-tête x-openclaw-scopes explicite.

Utilisez des sous-chemins SDK étroits au lieu du module racine monolithique openclaw/plugin-sdk lors de la création de nouveaux plugins. Sous-chemins principaux :

Sous-cheminObjectif
openclaw/plugin-sdk/plugin-entryPrimitives d’enregistrement de plugins
openclaw/plugin-sdk/channel-coreAssistants d’entrée/de construction de canal
openclaw/plugin-sdk/coreAssistants partagés génériques et contrat parapluie
openclaw/plugin-sdk/config-schemaSchéma Zod racine openclaw.json (OpenClawSchema)

Les plugins de channel choisissent parmi une famille de coutures étroites — channel-setup, setup-runtime, setup-tools, channel-pairing, channel-contract, channel-feedback, channel-inbound, channel-lifecycle, channel-reply-pipeline, command-auth, secret-input, webhook-ingress, channel-targets et channel-actions. Le comportement d’approbation doit se consolider sur un seul contrat approvalCapability plutôt que de mélanger des champs de plugins sans rapport. Consultez Plugins de channel.

Les helpers d’exécution et de configuration résident sous des sous-chemins focalisés *-runtime correspondants (approval-runtime, agent-runtime, lazy-runtime, directory-runtime, text-runtime, runtime-store, system-event-runtime, heartbeat-runtime, channel-activity-runtime, etc.). Privilégiez config-contracts, plugin-config-runtime, runtime-config-snapshot et config-mutation plutôt que le baril de compatibilité config-runtime plus large.

Points d’entrée internes au dépôt (par racine de package de plugin groupé) :

  • index.js — point d’entrée du plugin groupé
  • api.js — baril d’aides/types
  • runtime-api.js — baril uniquement pour l’exécution
  • setup-entry.js — point d’entrée du plugin de configuration

Les plugins externes ne doivent importer que des sous-chemins openclaw/plugin-sdk/*. N’importez jamais le src/* d’un autre package de plugin depuis le core ou depuis un autre plugin. Les points d’entrée chargés par la façade préfèrent l’instantané de la configuration d’exécution active lorsqu’il existe, puis reviennent au fichier de configuration résolu sur le disque.

Les sous-chemins spécifiques aux capacités tels que image-generation, media-understanding et speech existent car les plugins groupés les utilisent aujourd’hui. Ils ne sont pas automatiquement des contrats externes figés à long terme — consultez la page de référence du SDK pertinente lorsque vous vous y fiez.

Les plugins doivent posséder les contributions de schéma describeMessageTool(...) spécifiques au channel pour les primitives non-message telles que les réactions, les lectures et les sondages. La présentation d’envoi partagée doit utiliser le contrat générique MessagePresentation au lieu des champs de bouton, de composant, de bloc ou de carte natifs du provider. Consultez Présentation des messages pour le contrat, les règles de repli, le mappage de provider et la liste de contrôle pour les auteurs de plugins.

Les plugins capables d’envoyer déclarent ce qu’ils peuvent restituer via les capacités de message :

  • presentation pour les blocs de présentation sémantique (text, context, divider, buttons, select)
  • delivery-pin pour les demandes de livraison épinglée (pinned-delivery)

Core décide s’il faut restituer la présentation nativement ou la dégrader en texte. N’exposez pas d’échappatoires d’interface utilisateur natives du provider depuis l’outil de message générique. Les helpers SDK dépréciés pour les schémas natifs hérités restent exportés pour les plugins tiers existants, mais les nouveaux plugins ne doivent pas les utiliser.

Les plugins de canal doivent posséder la sémantique de cible spécifique au canal. Gardez l’hôte sortant partagé générique et utilisez la surface de l’adaptateur de messagerie pour les règles du provider :

  • messaging.inferTargetChatType({ to }) décide si une cible normalisée doit être traitée comme direct, group ou channel avant la recherche dans l’annuaire.
  • messaging.targetResolver.looksLikeId(raw, normalized) indique à core si une entrée doit passer directement à une résolution de type identifiant au lieu d’une recherche dans l’annuaire.
  • messaging.targetResolver.resolveTarget(...) est le repli du plugin lorsque core a besoin d’une résolution finale possédée par le provider après normalisation ou après un échec de l’annuaire.
  • messaging.resolveOutboundSessionRoute(...) possède la construction de route de session spécifique au provider une fois la cible résolue.

Répartition recommandée :

  • Utilisez inferTargetChatType pour les décisions de catégorie qui doivent se produire avant la recherche de pairs/groupes.
  • Utilisez looksLikeId pour les vérifications « traiter ceci comme un identifiant de cible explicite/native ».
  • Utilisez resolveTarget pour le repli de normalisation spécifique au fournisseur, et non pour une recherche étendue dans l’annuaire.
  • Conservez les identifiants natifs du fournisseur tels que les identifiants de chat, les identifiants de fil de discussion, les JIDs, les handles et les identifiants de salle à l’intérieur des valeurs target ou des paramètres spécifiques au fournisseur, et non dans les champs génériques du SDK.

Les plugins qui dérivent des entrées d’annuaire à partir de la configuration doivent conserver cette logique dans le plugin et réutiliser les assistants partagés de openclaw/plugin-sdk/directory-runtime.

Utilisez ceci lorsqu’un canal a besoin de pairs/groupes basés sur la configuration, tels que :

  • pairs en DM pilotés par liste d’autorisation
  • cartes de canal/groupe configurées
  • replis d’annuaire statique délimités au compte

Les assistants partagés dans directory-runtime ne gèrent que les opérations génériques :

  • filtrage des requêtes
  • application des limites
  • assistants de déduplication/normalisation
  • construction de ChannelDirectoryEntry[]

L’inspection de compte spécifique au canal et la normalisation des identifiants doivent rester dans l’ implémentation du plugin.

Les plugins de fournisseur peuvent définir des catalogues de modèles pour l’inférence avec registerProvider({ catalog: { run(...) { ... } } }).

catalog.run(...) renvoie la même forme que OpenClaw écrit dans models.providers :

  • { provider } pour une entrée de fournisseur
  • { providers } pour plusieurs entrées de fournisseur

Utilisez catalog lorsque le plugin possède des identifiants de modèle spécifiques au fournisseur, des valeurs par défaut d’URL de base ou des métadonnées de modèle protégées par authentification.

catalog.order contrôle quand le catalogue d’un plugin fusionne par rapport aux fournisseurs implicites intégrés de OpenClaw :

  • simple : fournisseurs simples avec clé API ou pilotés par env
  • profile : fournisseurs qui apparaissent lorsque des profils d’authentification existent
  • paired : fournisseurs qui synthétisent plusieurs entrées de fournisseur connexes
  • late : dernière passe, après les autres fournisseurs implicites

Les fournisseurs ultérieurs l’emportent en cas de collision de clé, les plugins peuvent donc intentionnellement remplacer une entrée de fournisseur intégrée avec le même identifiant de fournisseur.

Les plugins peuvent également publier des lignes de modèle en lecture seule via api.registerModelCatalogProvider({ provider, kinds, staticCatalog, liveCatalog }). Il s’agit du chemin direct pour les surfaces de liste/aide/sélecteur et prend en charge les lignes text, image_generation, video_generation et music_generation. Les plugins de provider sont toujours propriétaires des appels aux endpoints en direct, de l’échange de jetons et du mappage des réponses des fournisseurs ; le cœur (core) possède la forme de ligne commune, les étiquettes de source et le formatage de l’aide des outils médias. Les enregistrements de providers de génération de médias synthétisent automatiquement les lignes de catalogue statique à partir de defaultModel, models et capabilities.

Compatibilité :

  • discovery fonctionne toujours comme un alias hérité, mais émet un avertissement de dépréciation
  • si catalog et discovery sont tous deux enregistrés, OpenClaw utilise catalog
  • augmentModelCatalog est déprécié ; les providers groupés doivent publier des lignes supplémentaires via registerModelCatalogProvider

Si votre plugin enregistre un canal, privilégiez l’implémentation de plugin.config.inspectAccount(cfg, accountId) parallèlement à resolveAccount(...).

Pourquoi :

  • resolveAccount(...) est le chemin d’exécution. Il est autorisé à supposer que les informations d’identification sont entièrement matérialisées et peut échouer rapidement lorsque les secrets requis sont manquants.
  • Les chemins de commande en lecture seule tels que openclaw status, openclaw status --all, openclaw channels status, openclaw channels resolve et les flux de réparation doctor/config ne devraient pas avoir besoin de matérialiser les informations d’identification d’exécution juste pour décrire la configuration.

Comportement recommandé pour inspectAccount(...) :

  • Ne renvoyez que l’état descriptif du compte.
  • Préservez enabled et configured.
  • Incluez les champs de source/statut des informations d’identification lorsque cela est pertinent, tels que :
    • tokenSource, tokenStatus
    • botTokenSource, botTokenStatus
    • appTokenSource, appTokenStatus
    • signingSecretSource, signingSecretStatus
  • Vous n’avez pas besoin de renvoyer les valeurs brutes des jetons simplement pour signaler une disponibilité en lecture seule. Renvoyer tokenStatus: "available" (et le champ source correspondant) suffit pour les commandes de type statut.
  • Utilisez configured_unavailable lorsqu’une information d’identification est configurée via SecretRef mais n’est pas disponible dans le chemin de commande actuel.

Cela permet aux commandes en lecture seule de signaler « configuré mais indisponible dans ce chemin de commande » au lieu de planter ou de signaler incorrectement que le compte n’est pas configuré.

Un répertoire de plugin peut inclure un package.json avec openclaw.extensions :

{
"name": "my-pack",
"openclaw": {
"extensions": ["./src/safety.ts", "./src/tools.ts"],
"setupEntry": "./src/setup-entry.ts"
}
}

Chaque entrée devient un plugin. Si le pack liste plusieurs extensions, l’identifiant du plugin devient name/<fileBase>.

Si votre plugin importe des dépendances npm, installez-les dans ce répertoire afin que node_modules soit disponible (npm install / pnpm install).

Garantie de sécurité : chaque entrée openclaw.extensions doit rester à l’intérieur du répertoire du plugin après la résolution des liens symboliques. Les entrées qui s’échappent du répertoire du package sont rejetées.

Note de sécurité : openclaw plugins install installe les dépendances du plugin avec un npm install --omit=dev --ignore-scripts local au projet (pas de scripts de cycle de vie, pas de dépendances de développement à l’exécution), en ignorant les paramètres d’installation globaux hérités de npm. Gardez les arbres de dépendances des plugins « pur JS/TS » et évitez les packages qui nécessitent des builds postinstall.

Optionnel : openclaw.setupEntry peut pointer vers un module léger dédié uniquement au configuration. Lorsque OpenClaw a besoin de surfaces de configuration pour un plugin de canal désactivé, ou lorsqu’un plugin de canal est activé mais toujours non configuré, il charge setupEntry à la place de l’entrée complète du plugin. Cela allège le démarrage et la configuration lorsque votre entrée principale du plugin connecte également des outils, des hooks ou d’autres codes exclusifs à l’exécution.

Optionnel : openclaw.startup.deferConfiguredChannelFullLoadUntilAfterListen peut permettre à un plugin de canal d’opter pour le même chemin setupEntry pendant la phase de démarrage pré-écoute de la passerelle, même lorsque le canal est déjà configuré.

Utilisez ceci uniquement lorsque setupEntry couvre entièrement la surface de démarrage qui doit exister avant que la passerie ne commence à écouter. En pratique, cela signifie que l’entrée de configuration doit enregistrer chaque capacité appartenant au channel dont dépend le démarrage, telle que :

  • l’enregistrement du channel lui-même
  • toutes les routes HTTP qui doivent être disponibles avant que la passerie ne commence à écouter
  • toutes les méthodes, outils ou services de la passerie qui doivent exister durant cette même fenêtre

Si votre entrée complète possède toujours des capacités de démarrage requises, n’activez pas ce indicateur. Conservez le comportement par défaut du plugin et laissez OpenClaw charger l’entrée complète pendant le démarrage.

Les canaux regroupés peuvent également publier des helpers de surface de contrat uniquement pour la configuration que le cœur peut consulter avant le chargement complet de l’exécution du canal. La surface actuelle de promotion de configuration est :

  • singleAccountKeysToMove
  • namedAccountPromotionKeys
  • resolveSingleAccountPromotionTarget(...)

Le cœur utilise cette surface lorsqu’il doit promouvoir une configuration de canal à compte unique héritée en channels.<id>.accounts.* sans charger l’entrée complète du plugin. Matrix est l’exemple regroupé actuel : il ne déplace que les clés d’authentification/amorçage vers un compte promu nommé lorsque des comptes nommés existent déjà, et il peut préserver une clé de compte par défaut configurée non canonique au lieu de toujours créer accounts.default.

Ces adaptateurs de correctifs de configuration maintiennent la découverte de la surface de contrat regroupée paresseuse. Le temps d’importation reste léger ; la surface de promotion est chargée uniquement lors de la première utilisation au lieu de réentrer dans le démarrage du canal regroupé lors de l’importation du module.

Lorsque ces surfaces de démarrage incluent des méthodes RPC de la passerie, gardez-les sur un préfixe spécifique au plugin. Les espaces de noms d’administration du cœur (RPCconfig.*, exec.approvals.*, wizard.*, update.*) restent réservés et résolvent toujours vers operator.admin, même si un plugin demande une portée plus étroite.

Exemple :

{
"name": "@scope/my-channel",
"openclaw": {
"extensions": ["./index.ts"],
"setupEntry": "./setup-entry.ts",
"startup": {
"deferConfiguredChannelFullLoadUntilAfterListen": true
}
}
}

Les plugins de canal peuvent annoncer des métadonnées de configuration/découverte via openclaw.channel et des indices d’installation via openclaw.install. Cela maintient le catalogue principal exempt de données.

Exemple :

{
"name": "@openclaw/nextcloud-talk",
"openclaw": {
"extensions": ["./index.ts"],
"channel": {
"id": "nextcloud-talk",
"label": "Nextcloud Talk",
"selectionLabel": "Nextcloud Talk (self-hosted)",
"docsPath": "/channels/nextcloud-talk",
"docsLabel": "nextcloud-talk",
"blurb": "Self-hosted chat via Nextcloud Talk webhook bots.",
"order": 65,
"aliases": ["nc-talk", "nc"]
},
"install": {
"npmSpec": "@openclaw/nextcloud-talk",
"localPath": "<bundled-plugin-local-path>",
"defaultChoice": "npm"
}
}
}

Champs openclaw.channel utiles au-delà de l’exemple minimal :

  • detailLabel : étiquette secondaire pour des surfaces de catalogue/statut plus riches
  • docsLabel : remplacer le texte du lien pour le lien vers la documentation
  • preferOver : identifiants de plugin/channel de priorité inférieure que cette entrée de catalogue doit surpasser
  • selectionDocsPrefix, selectionDocsOmitLabel, selectionExtras : contrôles de copie pour la surface de sélection
  • markdownCapable : marque le channel comme compatible markdown pour les décisions de formatage sortant
  • exposure.configured : masquer le channel des surfaces de listing des channels configurés s’il est défini sur false
  • exposure.setup : masquer le channel des sélecteurs de configuration/installation interactifs s’il est défini sur false
  • exposure.docs : marquer le channel comme interne/privé pour les surfaces de navigation de la documentation
  • showConfigured / showInSetup : alias hérités toujours acceptés pour compatibilité ; préférer exposure
  • quickstartAllowFrom : activer le channel dans le flux de démarrage rapide standard allowFrom
  • forceAccountBinding : exiger une liaison de compte explicite même lorsqu’un seul compte existe
  • preferSessionLookupForAnnounceTarget : préférer la recherche de session lors de la résolution des cibles d’annonce

OpenClaw peut également fusionner des catalogues de channels externes (par exemple, un export de registre MPM). Déposez un fichier JSON à l’un des emplacements suivants :

  • ~/.openclaw/mpm/plugins.json
  • ~/.openclaw/mpm/catalog.json
  • ~/.openclaw/plugins/catalog.json

Ou pointez OPENCLAW_PLUGIN_CATALOG_PATHS (ou OPENCLAW_MPM_CATALOG_PATHS) vers un ou plusieurs fichiers JSON (délimités par des virgules/points-virgules/PATH). Chaque fichier doit contenir { "entries": [ { "name": "@scope/pkg", "openclaw": { "channel": {...}, "install": {...} } } ] }. L’analyseur accepte également "packages" ou "plugins" comme alias hérités pour la clé "entries".

Les entrées de catalogue de channel générées et les entrées de catalogue d’installation de provider exposent des faits normalisés sur la source d’installation à côté du bloc brut openclaw.install. Les faits normalisés identifient si la spécification npm est une version exacte ou un sélecteur flottant, si les métadonnées d’intégrité attendues sont présentes, et si un chemin source local est également disponible. Lorsque l’identité du catalogue/du paquet est connue, les faits normalisés avertissent si le nom du paquet npm analysé dérive de cette identité. Ils avertissent également lorsque defaultChoice est invalide ou pointe vers une source non disponible, et lorsque des métadonnées d’intégrité npm sont présentes sans une source npm valide. Les consommateurs doivent traiter installSource comme un champ optionnel additif afin que les entrées construites à la main et les shims de catalogue n’aient pas à le synthétiser. Cela permet à l’onboarding et aux diagnostics d’expliquer l’état du plan source sans importer le runtime du plugin.

Les entrées npm externes officielles devraient préférer un npmSpec exact plus expectedIntegrity. Les noms de paquets nus et les balises de distribution (dist-tags) fonctionnent toujours pour la compatibilité, mais ils affichent des avertissements du plan source afin que le catalogue puisse passer à des installations épinglées et vérifiées par intégrité sans casser les plugins existants. Lors de l’onboarding des installations à partir d’un chemin de catalogue local, il enregistre une entrée d’index de plugin gérée avec source: "path" et un sourcePath relatif à l’espace de travail si possible. Le chemin de chargement opérationnel absolu reste dans plugins.load.paths ; l’enregistrement d’installation évite de dupliquer les chemins de la station de travail locale dans la configuration de longue durée. Cela maintient les installations de développement locales visibles pour les diagnostics du plan source sans ajouter une seconde surface de divulgation de chemin de système de fichiers brut. L’index de plugin plugins/installs.json persisté est la source de vérité de l’installation et peut être actualisé sans charger les modules runtime du plugin. Sa carte installRecords est durable même lorsqu’un manifeste de plugin est manquant ou invalide ; son tableau plugins est une vue de manifeste reconstructible.

Les plugins de moteur de contexte gèrent l’orchestration du contexte de session pour l’ingestion, l’assemblage et la compactage. Enregistrez-les depuis votre plugin avec api.registerContextEngine(id, factory), puis sélectionnez le moteur actif avec plugins.slots.contextEngine.

Utilisez ceci lorsque votre plugin doit remplacer ou étendre le pipeline de contexte par défaut plutôt que de simplement ajouter une recherche mémoire ou des hooks.

import { buildMemorySystemPromptAddition } from "openclaw/plugin-sdk/core";
export default function (api) {
api.registerContextEngine("lossless-claw", (ctx) => ({
info: { id: "lossless-claw", name: "Lossless Claw", ownsCompaction: true },
async ingest() {
return { ingested: true };
},
async assemble({ messages, availableTools, citationsMode }) {
return {
messages,
estimatedTokens: 0,
systemPromptAddition: buildMemorySystemPromptAddition({
availableTools: availableTools ?? new Set(),
citationsMode,
}),
};
},
async compact() {
return { ok: true, compacted: false };
},
}));
}

La fabrique ctx expose des valeurs config, agentDir et workspaceDir facultatives pour l’initialisation au moment de la construction.

assemble() peut renvoyer contextProjection lorsque le harnais actif dispose d’un thread backend persistant. Omettez-le pour une projection par tour héritée. Renvoyez { mode: "thread_bootstrap", epoch } lorsque le contexte assemblé doit être injecté une fois dans un thread backend et réutilisé jusqu’à ce que l’époque change. Modifiez l’époque après que le contexte sémantique du moteur a changé, par exemple après une passe de compactage appartenant au moteur. Les hôtes peuvent conserver les métadonnées d’appel d’outil, la forme des entrées, et les résultats d’outil expurgés dans une projection d’amorçage de thread afin que les nouveaux threads backend conservent la continuité de l’outil sans copier des charges utiles brutes contenant des secrets.

Si votre moteur ne possède pas l’algorithme de compactage, gardez compact() implémenté et déléguez-le explicitement :

import { buildMemorySystemPromptAddition, delegateCompactionToRuntime } from "openclaw/plugin-sdk/core";
export default function (api) {
api.registerContextEngine("my-memory-engine", (ctx) => ({
info: {
id: "my-memory-engine",
name: "My Memory Engine",
ownsCompaction: false,
},
async ingest() {
return { ingested: true };
},
async assemble({ messages, availableTools, citationsMode }) {
return {
messages,
estimatedTokens: 0,
systemPromptAddition: buildMemorySystemPromptAddition({
availableTools: availableTools ?? new Set(),
citationsMode,
}),
};
},
async compact(params) {
return await delegateCompactionToRuntime(params);
},
}));
}

Lorsqu’un plugin a besoin d’un comportement qui ne correspond pas à l’API actuel, ne contournez pas le système de plugin avec un accès privé. Ajoutez la capacité manquante.

Séquence recommandée :

  1. définir le contrat principal Décidez du comportement partagé que le cœur doit posséder : stratégie, repli, fusion de configuration, cycle de vie, sémantique orientée canal, et forme de l’assistant d’exécution.
  2. ajouter des surfaces d’inscription/d’exécution de plugin typées Étendez OpenClawPluginApi et/ou api.runtime avec la plus petite surface de capacité typée utile.
  3. connecter le cœur + les consommateurs de canal/fonctionnalité Les canaux et les plugins de fonctionnalité devraient consommer la nouvelle capacité via le cœur, et non en important une implémentation fournisseur directement.
  4. inscrire les implémentations fournisseurs Les plugins fournisseurs inscrivent ensuite leurs backends auprès de la capacité.
  5. ajouter une couverture de contrat Ajoutez des tests pour que la propriété et la forme de l’inscription restent explicites dans le temps.

C’est ainsi que OpenClaw reste opinionné sans devenir codé en dur pour la vision du monde d’un seul provider. Consultez le Capability Cookbook pour une liste de fichiers concrète et un exemple travaillé.

Lorsque vous ajoutez une nouvelle capacité, l’implémentation doit généralement toucher ces surfaces ensemble :

  • types de contrat principal dans src/<capability>/types.ts
  • assistant d’exécution/runner principal dans src/<capability>/runtime.ts
  • surface d’inscription de API de plugin dans src/plugins/types.ts
  • câblage du registre de plugins dans src/plugins/registry.ts
  • exposition du runtime du plugin dans src/plugins/runtime/* lorsque les plugins de fonctionnalité/channel doivent le consommer
  • helpers de capture/test dans src/test-utils/plugin-registration.ts
  • assertions de propriété/contrat dans src/plugins/contracts/registry.ts
  • docs opérateur/plugin dans docs/

Si l’une de ces surfaces est manquante, cela indique généralement que la capacité n’est pas encore entièrement intégrée.

Modèle minimal :

// core contract
export type VideoGenerationProviderPlugin = {
id: string;
label: string;
generateVideo: (req: VideoGenerationRequest) => Promise<VideoGenerationResult>;
};
// plugin API
api.registerVideoGenerationProvider({
id: "openai",
label: "OpenAI",
async generateVideo(req) {
return await generateOpenAiVideo(req);
},
});
// shared runtime helper for feature/channel plugins
const clip = await api.runtime.videoGeneration.generate({
prompt: "Show the robot walking through the lab.",
cfg,
});

Modèle de test de contrat :

expect(findVideoGenerationProviderIdsForPlugin("openai")).toEqual(["openai"]);

Cela permet de garder la règle simple :

  • le cœur possède le contrat de capacité + l’orchestration
  • les plugins fournisseurs possèdent les implémentations fournisseurs
  • les plugins de fonctionnalité/channel consomment les helpers d’exécution
  • les tests de contrat gardent la propriété explicite