Building plugins
Plugin-Hooks
Plugin-Hooks sind prozessinterne Erweiterungspunkte für OpenClaw-Plugins: Sie können Agent-Ausführungen, Tool-Aufrufe, den Nachrichtenfluss, den Sitzungslebenszyklus, das Subagent-Routing, Installationen oder den Gateway-Start prüfen oder ändern.
Verwenden Sie stattdessen interne Hooks, wenn Sie ein kleines, vom Betreiber installiertes HOOK.md-Skript benötigen, das auf Befehls- und Gateway-Ereignisse wie /new, /reset, /stop, agent:bootstrap oder gateway:startup reagiert.
Schnellstart
Registrieren Sie typisierte Hooks mit api.on(...) im Plugin-Einstiegspunkt:
export default definePluginEntry({ id: "tool-preflight", name: "Tool Preflight", register(api) { api.on( "before_tool_call", async (event) => { if (event.toolName !== "web_search") { return; } return { requireApproval: { title: "Run web search", description: `Allow search query: ${String(event.params.query ?? "")}`, severity: "info", timeoutMs: 60_000, }, }; }, { priority: 50 }, ); },});Handler, die Entscheidungen oder Änderungen zurückgeben können, werden sequenziell in absteigender Reihenfolge ihrer priority ausgeführt; Handler mit gleicher Priorität behalten ihre Registrierungsreihenfolge bei. Reine Beobachtungs-Handler werden parallel ausgeführt, und nach dem Fire-and-Forget-Prinzip ausgelöste Beobachtungen können sich mit späteren Ereignissen überschneiden. Verwenden Sie die Priorität nicht, um Nebenwirkungen von Beobachtungen zu ordnen.
api.on(name, handler, opts?) akzeptiert:
| Option | Wirkung |
|---|---|
priority |
Reihenfolge; höhere Werte werden zuerst ausgeführt. |
timeoutMs |
Wartezeitbudget pro Hook. Nach dessen Ablauf wartet OpenClaw nicht länger auf diesen Handler und fährt fort. Der Handler oder seine Nebenwirkungen werden dadurch nicht abgebrochen. Lassen Sie die Option weg, um das standardmäßige Zeitlimit des Runners pro Hook zu verwenden. |
Betreiber können Hook-Budgets festlegen, ohne den Plugin-Code zu ändern:
{ "plugins": { "entries": { "my-plugin": { "hooks": { "timeoutMs": 30000, "timeouts": { "before_prompt_build": 90000, "agent_end": 60000 } } } } }}hooks.timeouts.<hookName> überschreibt hooks.timeoutMs, das wiederum den vom Plugin festgelegten Wert api.on(..., { timeoutMs }) überschreibt. Jeder Wert muss eine positive Ganzzahl bis 600000 ms sein. Verwenden Sie für bekanntermaßen langsame Hooks vorzugsweise Hook-spezifische Überschreibungen, damit ein Plugin nicht überall ein längeres Budget erhält.
Das Promise eines Handlers, dessen Zeitlimit überschritten wurde, wird weiter ausgeführt, da Hook-Callbacks kein Abbruchsignal erhalten. Die Hook-Ausführung kann ihre Gateway-Zulassung freigeben, während die Arbeit dieses Plugins noch läuft. Plugins, die lang laufende Aufgaben verwalten, müssen einen eigenen Abbruch- und Beendigungslebenszyklus bereitstellen.
Die ausgehenden modifizierenden Hooks message_sending und reply_payload_sending verwenden standardmäßig 15 Sekunden pro Handler. Wird bei einem Handler das Zeitlimit überschritten, protokolliert OpenClaw den Plugin-Fehler und fährt mit der neuesten Nutzlast fort, damit die serialisierte Zustellspur abgeschlossen werden kann. Legen Sie für Plugins, die vor der Zustellung absichtlich langsamere Arbeiten ausführen, ein größeres Hook-spezifisches Budget fest.
Channel-Plugins, die createReplyDispatcher verwenden, können ebenso ein größeres positives Budget pro Stufe mit beforeDeliverOptions: { timeoutMs } angeben oder beim Anhängen von Arbeit dispatcher.appendBeforeDeliver(handler, { timeoutMs }) verwenden. Ohne ein vom zuständigen Eigentümer festgelegtes Budget verwenden diese Callbacks ebenfalls den Standardwert von 15 Sekunden, damit ein hängender Callback die serialisierte Zustellspur nicht blockieren kann.
Jeder Hook erhält event.context.pluginConfig, die aufgelöste Konfiguration für das Plugin, das diesen Handler registriert hat. OpenClaw fügt sie für jeden Handler ein, ohne das gemeinsam genutzte Ereignisobjekt zu verändern, das andere Plugins sehen.
Hook-Katalog
Hooks sind nach dem Bereich gruppiert, den sie erweitern. Fettgedruckte Namen akzeptieren ein Entscheidungsergebnis (blockieren, abbrechen, überschreiben oder Genehmigung anfordern); die übrigen dienen nur der Beobachtung.
Agent-Durchlauf
| Hook | Zweck |
|---|---|
before_model_resolve |
Provider oder Modell überschreiben, bevor Sitzungsnachrichten geladen werden |
agent_turn_prepare |
In die Warteschlange gestellte Plugin-Einspeisungen für den Durchlauf verarbeiten und vor Prompt-Hooks Kontext für denselben Durchlauf hinzufügen |
before_prompt_build |
Vor dem Modellaufruf dynamischen Kontext oder Text für den System-Prompt hinzufügen |
before_agent_start |
Kombinierte Phase ausschließlich zur Kompatibilität; bevorzugen Sie die beiden obigen Hooks |
before_agent_run |
Den endgültigen Prompt und die Sitzungsnachrichten vor der Übermittlung an das Modell prüfen; kann die Ausführung blockieren |
before_agent_reply |
Den Modelldurchlauf mit einer synthetischen Antwort oder ohne Ausgabe vorzeitig beenden |
before_agent_finalize |
Die natürliche endgültige Antwort prüfen und einen weiteren Modelldurchlauf anfordern |
agent_end |
Endgültige Nachrichten, Erfolgsstatus und Ausführungsdauer beobachten |
heartbeat_prompt_contribution |
Nur für den Heartbeat bestimmten Kontext für Hintergrundüberwachungs- und Lebenszyklus-Plugins hinzufügen |
Konversationsbeobachtung
| Hook | Zweck |
|---|---|
model_call_started / model_call_ended |
Bereinigte Metadaten des Provider-/Modellaufrufs: Zeitmessung, Ergebnis und begrenzte Hashes der Anfrage-ID. Keine Prompt- oder Antwortinhalte. |
llm_input |
Provider-Eingabe: System-Prompt, Prompt, Verlauf |
llm_output |
Provider-Ausgabe, Nutzung und das aufgelöste contextTokenBudget, sofern verfügbar |
Tools
| Hook | Zweck |
|---|---|
before_tool_call |
Tool-Parameter umschreiben, Ausführung blockieren oder Genehmigung anfordern |
after_tool_call |
Tool-Ergebnisse, Fehler und Dauer beobachten |
resolve_exec_env |
Plugin-eigene Umgebungsvariablen zu exec beitragen |
tool_result_persist |
Die aus einem Tool-Ergebnis erzeugte Assistentennachricht umschreiben |
before_message_write |
Einen laufenden Schreibvorgang für eine Nachricht prüfen oder blockieren (selten) |
Nachrichten und Zustellung
| Hook | Zweck |
|---|---|
inbound_claim |
Eine eingehende Nachricht vor dem Agent-Routing übernehmen (synthetische Antworten) |
channel_pairing_requested |
Neu erstellte Kopplungsanfragen für Direktnachrichten beobachten |
message_received |
Eingehenden Inhalt, Absender, Thread und Metadaten beobachten |
message_sending |
Ausgehenden Inhalt umschreiben oder die Zustellung abbrechen |
reply_payload_sending |
Normalisierte Antwortnutzlasten vor der Zustellung ändern oder abbrechen |
message_sent |
Erfolg oder Fehlschlag der ausgehenden Zustellung beobachten |
before_dispatch |
Einen ausgehenden Versand vor der Übergabe an den Channel prüfen oder umschreiben |
reply_dispatch |
An der abschließenden Pipeline für den Antwortversand teilnehmen |
Sitzungen und Compaction
| Hook | Zweck |
|---|---|
session_start / session_end |
Grenzen des Sitzungslebenszyklus verfolgen. reason ist einer der Werte new, reset, idle, daily, compaction, deleted, shutdown, restart oder unknown. shutdown/restart werden vom Finalisierer für die Gateway-Beendigung ausgelöst, wenn der Prozess mit aktiven Sitzungen beendet oder neu gestartet wird. Dadurch können Plugins (Speicher, Transkriptspeicher) verwaiste Zeilen abschließen, statt sie über Neustarts hinweg geöffnet zu lassen. Der Finalisierer ist zeitlich begrenzt, damit ein langsames Plugin SIGTERM/SIGINT nicht blockieren kann. |
before_compaction / after_compaction |
Compaction-Zyklen beobachten oder mit Anmerkungen versehen |
before_reset |
Ereignisse zum Zurücksetzen von Sitzungen beobachten (/reset, programmgesteuerte Zurücksetzungen) |
Subagents
subagent_spawned/subagent_ended– Start und Abschluss von Subagents beobachten.subagent_delivery_target– Kompatibilitäts-Hook für die Zustellung nach Abschluss, wenn keine Kernsitzungsbindung eine Route projizieren kann.subagent_spawning– veralteter Kompatibilitäts-Hook. Der Kern bereitet jetztthread: true-Subagent-Bindungen über Adapter für Channel-Sitzungsbindungen vor, bevorsubagent_spawnedausgelöst wird.subagent_spawnedenthältresolvedModelundresolvedProvider, wenn OpenClaw das native Modell der untergeordneten Sitzung vor dem Start aufgelöst hat.subagent_endedübermittelttargetSessionKey(Identität – entsprichtsubagent_spawned.childSessionKey),targetKind("subagent"oder"acp"),reason, optionaloutcome("ok","error","timeout","killed","reset"oder"deleted"), optionalerror,runId,endedAt,accountIdundsendFarewell. Es enthält nichtagentIdoderchildSessionKey; verwenden SietargetSessionKey, um die Zuordnung zum entsprechendensubagent_spawned-Ereignis herzustellen.
Lebenszyklus
| Hook | Zweck |
|---|---|
gateway_start / gateway_stop |
Plugin-eigene Dienste zusammen mit dem Gateway starten oder stoppen |
deactivate |
Veralteter Kompatibilitätsalias für gateway_stop; verwenden Sie in neuen Plugins gateway_stop |
cron_reconciled |
Nach dem Start oder Neuladen mit dem vollständigen Cron-Status des Gateways abgleichen |
cron_changed |
Änderungen am Gateway-eigenen Cron-Lebenszyklus beobachten (hinzugefügt, aktualisiert, entfernt, gestartet, beendet, geplant) |
before_install |
Bereitgestelltes Installationsmaterial für Skills oder Plugins aus einer geladenen Plugin-Laufzeit untersuchen |
Anfragen zur Kanal-Kopplung
Verwenden Sie channel_pairing_requested, wenn ein Plugin einen Operator benachrichtigen oder
einen Audit-Datensatz schreiben muss, nachdem ein nicht gekoppelter DM-Absender eine ausstehende
Kopplungsanfrage erstellt hat. Der Hook wird beim Erstellen der Anfrage ausgelöst; die Kanalzustellung
der Kopplungsantwort wird durch langsame oder fehlschlagende Hook-Handler nicht verzögert.
api.on("channel_pairing_requested", async (event) => { await notifyOperator({ text: `Neue ${event.channel}-Kopplungsanfrage von ${event.senderId}: ${event.code}`, });});Der Hook dient ausschließlich der Beobachtung. Er genehmigt, lehnt, unterdrückt oder ändert
die Kopplungsantwort nicht. Die Nutzlast enthält den Kanal, die optionale accountId,
die kanalbezogene senderId, den Kopplungs-code und Kanalmetadaten. Behandeln Sie den
Kopplungscode als gültige, einmalig verwendbare Genehmigungszugangsdaten und übermitteln Sie ihn nur an eine
vertrauenswürdige Operator-Senke. Behandeln Sie metadata als nicht vertrauenswürdigen, vom Absender bereitgestellten Identitätstext.
Der Hook enthält weder den Text noch die Medien der eingehenden Nachricht.
Hooks zur Laufzeitdiagnose
Verwenden Sie before_model_resolve, um für einen Agent-Durchlauf den Provider oder das Modell zu wechseln – der Hook
wird vor der Modellauflösung ausgeführt. llm_output wird erst ausgeführt, nachdem ein Modellversuch
eine Assistentenausgabe erzeugt hat.
Um das tatsächlich verwendete Sitzungsmodell nachzuweisen, prüfen Sie die Laufzeitregistrierungen und
verwenden Sie anschließend openclaw sessions oder die Sitzungs-/Statusoberflächen des Gateways. Um
Provider-Nutzlasten zu untersuchen, starten Sie den Gateway mit --raw-stream und
--raw-stream-path <path>, damit rohe Modell-Stream-Ereignisse in eine jsonl-Datei geschrieben werden.
Richtlinie für Tool-Aufrufe
before_tool_call empfängt:
event.toolNameevent.params- optional
event.toolKindundevent.toolInputKind, vom Host autoritativ festgelegte Unterscheidungsmerkmale für Tools, die absichtlich dieselben Namen verwenden; beispielsweise verwenden äußereexec-Aufrufe im Code-ModustoolKind: "code_mode_exec"und enthaltentoolInputKind: "javascript" | "typescript", wenn die Eingabesprache bekannt ist - optional
event.derivedPaths, nach bestem Bemühen vom Host abgeleitete Hinweise auf Zielpfade für bekannte Tool-Umschläge wieapply_patch; diese Pfade können unvollständig sein oder mehr umfassen, als das Tool tatsächlich bearbeitet (zum Beispiel bei fehlerhaften oder unvollständigen Eingaben) - optional
event.runId - optional
event.toolCallId - Kontextfelder wie
ctx.agentId,ctx.sessionKey,ctx.sessionId,ctx.runId,ctx.toolKind,ctx.toolInputKindund das diagnostische Feldctx.trace
Der Hook kann Folgendes zurückgeben:
type BeforeToolCallResult = { params?: Record<string, unknown>; block?: boolean; blockReason?: string; requireApproval?: { title: string; description: string; severity?: "info" | "warning" | "critical"; timeoutMs?: number; /** @deprecated Nicht aufgelöste Genehmigungen werden immer abgelehnt. */ timeoutBehavior?: "allow" | "deny"; allowedDecisions?: Array<"allow-once" | "allow-always" | "deny">; pluginId?: string; onResolution?: ( decision: "allow-once" | "allow-always" | "deny" | "timeout" | "cancelled", ) => Promise<void> | void; };};Schutzverhalten für typisierte Lebenszyklus-Hooks:
block: trueist endgültig und überspringt Handler mit niedrigerer Priorität.block: falsewird als keine Entscheidung behandelt.paramsschreibt die Tool-Parameter für die Ausführung neu.requireApprovalpausiert den Agent-Durchlauf und fragt den Benutzer über Plugin- Genehmigungen./approvekann sowohl Ausführungs- als auch Plugin-Genehmigungen erteilen. Bei nativenPreToolUse-Weiterleitungen im Berichtsmodus des Codex-App-Servers wird dies an die entsprechende Genehmigungsanfrage des App-Servers delegiert; siehe Laufzeit des Codex-Harness.- Ein
block: truemit niedrigerer Priorität kann weiterhin blockieren, nachdem ein Hook mit höherer Priorität eine Genehmigung angefordert hat. onResolutionempfängt die aufgelöste Entscheidung:allow-once,allow-always,deny,timeoutodercancelled.
Unter Plugin-Berechtigungsanfragen finden Sie Informationen zur
Weiterleitung von Genehmigungen, zum Entscheidungsverhalten und dazu, wann requireApproval anstelle
optionaler Tools oder Ausführungsgenehmigungen verwendet werden sollte.
Plugins, die Richtlinien auf Host-Ebene benötigen, können mit
api.registerTrustedToolPolicy(...) vertrauenswürdige Tool-Richtlinien registrieren. Diese werden vor gewöhnlichen
before_tool_call-Hooks und vor normalen Hook-Entscheidungen ausgeführt. Gebündelte vertrauenswürdige
Richtlinien werden zuerst ausgeführt; vertrauenswürdige Richtlinien installierter Plugins folgen in der
Plugin-Ladereihenfolge; gewöhnliche before_tool_call-Hooks werden danach ausgeführt. Gebündelte Plugins behalten
den vorhandenen Pfad für vertrauenswürdige Richtlinien bei. Installierte Plugins müssen ausdrücklich aktiviert sein
und jede Richtlinien-ID in contracts.trustedToolPolicies deklarieren; nicht deklarierte IDs
werden vor der Registrierung abgelehnt. Richtlinien-IDs gelten im Gültigkeitsbereich des registrierenden
Plugins, sodass verschiedene Plugins dieselbe lokale ID wiederverwenden können. Verwenden Sie diese Stufe nur
für vom Host als vertrauenswürdig eingestufte Schranken, etwa Arbeitsbereichsrichtlinien, Budgetdurchsetzung oder
die Sicherheit reservierter Arbeitsabläufe.
Hook für die Ausführungsumgebung
Mit resolve_exec_env können Plugins Umgebungsvariablen zu Aufrufen des
exec-Tools beitragen, bevor der Befehl ausgeführt wird. Der Hook empfängt:
event.sessionKeyevent.toolName, derzeit immer"exec"event.host, entweder"gateway","sandbox"oder"node"- Kontextfelder wie
ctx.agentId,ctx.sessionKey,ctx.messageProviderundctx.channelId
Geben Sie einen Record<string, string> zurück, der in die Ausführungsumgebung zusammengeführt wird. Die Handler
werden in Prioritätsreihenfolge ausgeführt; spätere Ergebnisse überschreiben frühere Ergebnisse für denselben
Schlüssel.
Die Hook-Ausgabe wird vor dem Zusammenführen anhand der Richtlinie des Hosts für Schlüssel der Ausführungsumgebung
gefiltert. PATH wird immer verworfen (die Befehlsauflösung und Prüfungen sicherer Binärdateien
hängen davon ab). Ungültige Schlüssel und gefährliche Host-Überschreibungsschlüssel wie LD_*,
DYLD_*, NODE_OPTIONS, Proxy-Variablen (HTTP_PROXY, HTTPS_PROXY,
ALL_PROXY, NO_PROXY) und TLS-Überschreibungsvariablen (NODE_TLS_REJECT_UNAUTHORIZED,
SSL_CERT_FILE und ähnliche) werden verworfen. Die gefilterte Plugin-Umgebung wird
in die Genehmigungs-/Audit-Metadaten des Gateways aufgenommen und an Ausführungsanfragen
des Node-Hosts weitergeleitet.
Persistenz von Tool-Ergebnissen
Tool-Ergebnisse können strukturierte details für die UI-Darstellung, Diagnose,
Medienweiterleitung oder Plugin-eigene Metadaten enthalten. Behandeln Sie details als Laufzeitmetadaten,
nicht als Prompt-Inhalt:
- OpenClaw entfernt
toolResult.detailsvor der erneuten Wiedergabe beim Provider und vor der Compaction- Eingabe, damit Metadaten nicht Teil des Modellkontexts werden. - Persistierte Sitzungseinträge behalten nur begrenzte
details. Übermäßig große Details werden durch eine kompakte Zusammenfassung undpersistedDetailsTruncated: trueersetzt. tool_result_persistundbefore_message_writewerden vor der endgültigen Persistenzbegrenzung ausgeführt. Halten Sie zurückgegebenedetailsklein und vermeiden Sie es, Prompt-relevanten Text ausschließlich indetailsabzulegen; legen Sie für das Modell sichtbare Tool-Ausgaben incontentab.
Prompt- und Modell-Hooks
Verwenden Sie für neue Plugins die phasenspezifischen Hooks:
before_model_resolve: empfängt nur den aktuellen Prompt und die Metadaten der Anhänge. Geben SieproviderOverrideodermodelOverridezurück.agent_turn_prepare: empfängt den aktuellen Prompt, vorbereitete Sitzungsnachrichten und alle für diese Sitzung abgerufenen, genau einmal eingereihten Einfügungen. Geben SieprependContextoderappendContextzurück.before_prompt_build: empfängt den aktuellen Prompt und die Sitzungsnachrichten. Geben SieprependContext,appendContext,systemPrompt,prependSystemContextoderappendSystemContextzurück.heartbeat_prompt_contribution: wird nur bei Heartbeat-Durchläufen ausgeführt und gibtprependContextoderappendContextzurück. Vorgesehen für Hintergrundmonitore, die den aktuellen Status zusammenfassen müssen, ohne vom Benutzer initiierte Durchläufe zu verändern.
before_agent_start bleibt aus Kompatibilitätsgründen erhalten. Bevorzugen Sie die expliziten Hooks
oben, damit das Plugin nicht von einer veralteten kombinierten Phase abhängt.
before_agent_run wird nach der Prompt-Erstellung und vor jeder Modelleingabe ausgeführt,
einschließlich des Ladens Prompt-lokaler Bilder und der llm_input-Beobachtung. Der Hook empfängt
die aktuelle Benutzereingabe als prompt, den geladenen Sitzungsverlauf in messages
und den aktiven System-Prompt. Geben Sie { outcome: "block", reason, message? }
zurück, um den Durchlauf zu stoppen, bevor das Modell den Prompt liest. reason ist intern;
message ist der für den Benutzer sichtbare Ersatz. Es werden nur die Ergebnisse pass und block
unterstützt; nicht unterstützte Entscheidungsformen führen zu einer sicheren Ablehnung.
Wenn ein Durchlauf blockiert wird, speichert OpenClaw nur den Ersatztext in
message.content sowie nicht sensible Blockierungsmetadaten wie die ID des blockierenden
Plugins und den Zeitstempel. Der ursprüngliche Benutzertext wird weder im Transkript
noch im zukünftigen Kontext beibehalten. Interne Blockierungsgründe werden als sensibel behandelt und
aus Transkript-, Verlaufs-, Broadcast-, Protokoll- und Diagnosenutzlasten
ausgeschlossen. Für die Beobachtbarkeit sollten bereinigte Felder wie Blockierer-ID, Ergebnis,
Zeitstempel oder eine sichere Kategorie verwendet werden.
before_agent_start und agent_end enthalten event.runId, wenn OpenClaw
den aktiven Durchlauf identifizieren kann; derselbe Wert ist auch in ctx.runId enthalten. Von Cron
ausgelöste Durchläufe stellen im Agent-Durchlaufkontext außerdem ctx.jobId (die ID des ursprünglichen Cron-Auftrags)
bereit, damit Hooks Metriken, Nebenwirkungen oder Status auf einen bestimmten
geplanten Auftrag beschränken können. ctx.jobId ist nicht Teil des Tool-Kontexts von before_tool_call.
Bei von einem Kanal stammenden Durchläufen identifizieren ctx.channel und ctx.messageProvider
die Provider-Oberfläche wie discord oder telegram, während ctx.channelId
die Kennung des Konversationsziels ist, sofern OpenClaw diese aus dem
Sitzungsschlüssel oder den Zustellungsmetadaten ableiten kann.
Wenn die Absenderidentität verfügbar ist, enthalten Agent-Hook-Kontexte außerdem:
ctx.senderId– kanalbezogene Absender-ID (z. B. Feishu-open_id, Discord- Benutzer-ID). Wird ausgefüllt, wenn der Durchlauf aus einer Benutzernachricht mit bekannten Absendermetadaten stammt.ctx.chatId– transportnative Konversationskennung (z. B. Feishu-chat_id, Telegram-chat_id). Wird ausgefüllt, wenn der ursprüngliche Kanal eine native Konversations-ID bereitstellt.ctx.channelContext.sender.id– dieselbe Absender-ID wiectx.senderId, unter einem kanaleigenen Objekt, das Plugins um kanalspezifische Felder erweitern können.ctx.channelContext.chat.id– dieselbe Konversations-ID wiectx.chatId, unter einem kanaleigenen Objekt, das Plugins um kanalspezifische Felder erweitern können.
Der Kern definiert nur die verschachtelten id-Felder. Kanal-Plugins, die umfangreichere
Absender- oder Chat-Metadaten über die Eingangshilfsfunktion weitergeben, können
PluginHookChannelSenderContext oder PluginHookChannelChatContext aus
openclaw/plugin-sdk/channel-inbound erweitern:
declare module "openclaw/plugin-sdk/channel-inbound" { interface PluginHookChannelSenderContext { unionId?: string; userId?: string; }}Kanal-Plugins geben diese Felder über die SDK-Eingangshilfsfunktion weiter:
buildChannelInboundEventContext({ // ... channelContext: { sender: { id: senderOpenId, unionId, userId }, chat: { id: chatId }, },});Diese Felder sind optional und fehlen bei systemgenerierten Durchläufen (Heartbeat, Cron, Ausführungsereignis).
ctx.senderExternalId bleibt als veraltetes Feld für die Quellkompatibilität mit
älteren Plugins erhalten. Der Kern füllt es nicht aus; neue kanalspezifische
Absenderidentitäten sollten durch Modulerweiterung unter ctx.channelContext.sender
gespeichert werden.
agent_end ist ein Beobachtungs-Hook. Gateway- und persistente Harness-Pfade führen
ihn nach dem Turn nach dem Fire-and-Forget-Prinzip aus, während kurzlebige einmalige CLI-Pfade
vor der Prozessbereinigung auf das Hook-Promise warten, damit vertrauenswürdige Plugins
terminale Beobachtungsdaten schreiben oder Zustand erfassen können. Der Hook-Runner wendet ein
Timeout von 30 Sekunden an, damit ein festgefahrenes Plugin oder ein nicht reagierender Embedding-Endpunkt
das Hook-Promise nicht dauerhaft ausstehend lassen kann. Ein Timeout wird protokolliert und OpenClaw
fährt fort; netzwerkbezogene Arbeit im Besitz des Plugins wird nicht abgebrochen, sofern das Plugin
nicht zusätzlich ein eigenes Abbruchsignal verwendet.
Verwenden Sie model_call_started und model_call_ended für die Telemetrie von Provider-Aufrufen,
die keine Roh-Prompts, Verläufe, Antworten, Header, Anfrage-Bodys oder
Provider-Anfrage-IDs erhalten soll. Diese Hooks enthalten stabile Metadaten wie
runId, callId, provider, model, optional api/transport, terminale Werte für
durationMs/outcome sowie upstreamRequestIdHash, wenn OpenClaw einen
begrenzten Hash der Provider-Anfrage-ID ableiten kann. Wenn die Runtime
Metadaten zum Kontextfenster aufgelöst hat, enthalten das Hook-Ereignis und der Kontext außerdem
contextTokenBudget, das effektive Token-Budget nach Modell-, Konfigurations- und Agent-
Obergrenzen, sowie contextWindowSource und contextWindowReferenceTokens, wenn eine
niedrigere Obergrenze angewendet wurde.
before_agent_finalize wird nur ausgeführt, wenn ein Harness im Begriff ist, eine natürliche
abschließende Assistentenantwort zu akzeptieren. Es ist nicht der Abbruchpfad /stop und wird
nicht ausgeführt, wenn der Benutzer einen Turn abbricht. Geben Sie { action: "revise", reason } zurück, um
das Harness vor der Finalisierung um einen weiteren Modelldurchlauf zu bitten, { action: "finalize", reason? }, um die Finalisierung zu erzwingen, oder lassen Sie ein Ergebnis aus, um fortzufahren.
Handler haben standardmäßig ein Budget von 15s; bei einem Timeout protokolliert OpenClaw den Fehler und
fährt mit der ursprünglichen abschließenden Antwort fort.
Native Codex-Hooks vom Typ Stop werden als OpenClaw-
Entscheidungen für before_agent_finalize an diesen Hook weitergeleitet.
Bei der Rückgabe von action: "revise" können Plugins retry-Metadaten einfügen, um
den zusätzlichen Modelldurchlauf zu begrenzen und wiederholungssicher zu machen:
type BeforeAgentFinalizeRetry = { instruction: string; idempotencyKey?: string; maxAttempts?: number;};instruction wird an den an das Harness gesendeten Überarbeitungsgrund angehängt.
Mit idempotencyKey kann der Host Wiederholungen für dieselbe Plugin-Anfrage
über äquivalente Finalisierungsentscheidungen hinweg zählen, und maxAttempts begrenzt, wie viele zusätzliche
Durchläufe der Host zulässt, bevor er mit der natürlichen abschließenden Antwort fortfährt.
Nicht gebündelte Plugins, die Hooks für rohe Konversationen benötigen (before_model_resolve,
before_agent_reply, llm_input, llm_output, before_agent_finalize,
agent_end oder before_agent_run), müssen Folgendes festlegen:
{ "plugins": { "entries": { "my-plugin": { "hooks": { "allowConversationAccess": true } } } }}Prompt-verändernde Hooks und dauerhafte Einfügungen für den nächsten Turn können pro
Plugin mit plugins.entries.<id>.hooks.allowPromptInjection=false deaktiviert werden.
Sitzungserweiterungen und Einfügungen für den nächsten Turn
Workflow-Plugins können kleinen JSON-kompatiblen Sitzungszustand mit
api.session.state.registerSessionExtension(...) persistieren und ihn über die
Gateway-Methode sessions.pluginPatch aktualisieren. Sitzungszeilen projizieren registrierten
Erweiterungszustand über pluginExtensions, sodass die Control UI und andere
Clients Status im Besitz eines Plugins darstellen können, ohne Plugin-Interna kennen zu müssen.
api.registerSessionExtension(...) funktioniert weiterhin, ist jedoch zugunsten des
Namensraums api.session.state als veraltet markiert.
Verwenden Sie api.session.workflow.enqueueNextTurnInjection(...), wenn ein Plugin
dauerhaften Kontext genau einmal in den nächsten Modell-Turn übertragen muss (das oberste
api.enqueueNextTurnInjection(...) ist ein veralteter Alias mit demselben
Verhalten). OpenClaw entnimmt in die Warteschlange eingestellte Einfügungen vor Prompt-Hooks, verwirft
abgelaufene Einfügungen und dedupliziert pro Plugin anhand von idempotencyKey. Dies ist
die richtige Schnittstelle für die Fortsetzung von Genehmigungsvorgängen, Richtlinienzusammenfassungen, Änderungen
von Hintergrundmonitoren und Befehlsfortsetzungen, die für das Modell im
nächsten Turn sichtbar sein sollen, aber nicht zu dauerhaftem System-Prompt-Text werden sollen.
Die Bereinigungssemantik ist Teil des Vertrags. Bereinigungs-Callbacks für Sitzungserweiterungen und
den Runtime-Lebenszyklus erhalten reset, delete, disable oder
restart. Der Host entfernt den persistenten Sitzungserweiterungszustand des besitzenden Plugins
und ausstehende Einfügungen für den nächsten Turn bei reset/delete/disable; restart
behält dauerhaften Sitzungszustand bei, während Bereinigungs-Callbacks Plugins ermöglichen,
Scheduler-Aufträge, Ausführungskontext und andere außerhalb des regulären Ablaufs liegende Ressourcen der alten
Runtime-Generation freizugeben.
Nachrichten-Hooks
Verwenden Sie Nachrichten-Hooks für Routing- und Zustellrichtlinien auf Kanalebene:
message_received: Beobachtet eingehende Inhalte, Absender,threadId,messageId,senderId, optionale Ausführungs-/Sitzungskorrelation und Metadaten.message_sending: Schreibtcontentum oder gibt{ cancel: true }zurück.reply_payload_sending: Schreibt normalisierteReplyPayload-Objekte um (einschließlichpresentation,delivery, Medienreferenzen und Text) oder gibt{ cancel: true }zurück.message_sent: Beobachtet abschließenden Erfolg oder Fehlschlag.
Bei reinen Audio-TTS-Antworten kann content das verborgene gesprochene
Transkript enthalten, selbst wenn die Kanal-Payload keinen sichtbaren Text/keine sichtbare Beschriftung enthält.
Das Umschreiben dieses content aktualisiert nur das für den Hook sichtbare Transkript; es wird nicht
als Medienbeschriftung dargestellt.
reply_payload_sending-Ereignisse können usageState enthalten, eine nach bestem Bemühen live erstellte
Momentaufnahme des Modells, der Nutzung und des Kontexts pro Turn. Dauerhafte Zustellung, wiederhergestellte Wiederholung und
Antworten ohne exakte Ausführungskorrelation lassen diesen Wert aus.
Kontexte von Nachrichten-Hooks stellen stabile Korrelationsfelder bereit, sofern verfügbar:
ctx.sessionKey, ctx.runId, ctx.messageId, ctx.senderId, ctx.trace,
ctx.traceId, ctx.spanId, ctx.parentSpanId und ctx.callDepth. Eingehende
Kontexte und before_dispatch-Kontexte stellen außerdem Antwortmetadaten bereit, wenn der Kanal
sichtbarkeitsgefilterte Daten zitierter Nachrichten besitzt: replyToId, replyToIdFull,
replyToBody, replyToSender und replyToIsQuote. Bevorzugen Sie diese
erstklassigen Felder, bevor Sie ältere Metadaten lesen.
Bevorzugen Sie typisierte Felder threadId und replyToId, bevor Sie kanalspezifische
Metadaten verwenden.
Entscheidungsregeln:
message_sendingmitcancel: trueist terminal.message_sendingmitcancel: falsewird so behandelt, als läge keine Entscheidung vor.- Umgeschriebener
contentwird an Hooks mit niedrigerer Priorität weitergegeben, sofern nicht ein späterer Hook die Zustellung abbricht. reply_payload_sendingwird nach der Payload-Normalisierung und vor der Kanal- zustellung ausgeführt, einschließlich Antworten, die an den Ursprungskanal zurückgeleitet werden. Handler werden sequenziell ausgeführt, und jeder Handler sieht die neueste Payload, die von Handlern mit höherer Priorität erzeugt wurde.reply_payload_sending-Payloads legen keine Runtime-Vertrauensmarkierungen wietrustedLocalMediaoffen; Plugins können die Payload-Struktur bearbeiten, aber kein Vertrauen für lokale Medien gewähren.message_sendingkann bei einem AbbruchcancelReasonund begrenztemetadatazurückgeben. Neue APIs für den Nachrichtenlebenszyklus stellen dies als unterdrücktes Zustellergebnis mit dem Grundcancelled_by_message_sending_hookbereit; die ältere direkte Zustellung gibt aus Kompatibilitätsgründen weiterhin ein leeres Ergebnisarray zurück.message_sentdient nur der Beobachtung. Fehler von Handlern werden protokolliert und ändern das Zustellergebnis nicht.
Installations-Hooks
Verwenden Sie security.installPolicy für vom Betreiber verwaltete Zulassungs-/Blockierungsentscheidungen. Diese
Richtlinie wird aus der OpenClaw-Konfiguration ausgeführt, deckt CLI-Installations- und Aktualisierungspfade ab und
schlägt im aktivierten, aber nicht verfügbaren Zustand geschlossen fehl.
before_install ist ein Lebenszyklus-Hook der Plugin-Runtime. Er wird nur nach
security.installPolicy in dem OpenClaw-Prozess ausgeführt, in dem Plugin-Hooks bereits
geladen wurden, beispielsweise bei Gateway-gestützten Installationsabläufen. Er eignet sich für
plugin-eigene Beobachtungen, Warnungen und Kompatibilitätsprüfungen, ist aber nicht
die primäre Sicherheitsgrenze für Unternehmen oder Hosts bei Installationen. Das Feld
builtinScan verbleibt aus Kompatibilitätsgründen in der Ereignis-Payload, aber
OpenClaw führt keine integrierte Blockierung gefährlichen Codes zur Installationszeit mehr aus, daher
ist es ein leeres ok-Ergebnis. Geben Sie zusätzliche Befunde oder
{ block: true, blockReason } zurück, um die Installation in diesem Prozess zu stoppen.
block: true ist terminal. block: false wird so behandelt, als läge keine Entscheidung vor. Fehler von Handlern
blockieren die Installation nach dem Fail-Closed-Prinzip.
Gateway-Lebenszyklus
Verwenden Sie gateway_start, um allgemeine Plugin-Dienste zu starten, und gateway_stop, um
langlebige Ressourcen zu bereinigen. Der Cron-Scheduler kann noch geladen werden, wenn
gateway_start ausgeführt wird; verwenden Sie ihn daher nicht als Basissignal für eine externe
Cron-Projektion.
Verlassen Sie sich für Plugin-eigene Runtime-Dienste nicht auf den internen Hook
gateway:startup.
cron_reconciled wird ausgelöst, nachdem der Cron-Scheduler des Gateways und seine beim Beenden
ausgeführten Watcher ihren dauerhaften Zustand abgeglichen haben. Er wird sowohl beim ersten
Start als auch beim Austausch des Schedulers während eines Konfigurations-Neuladens ausgelöst. Das Ereignis meldet
reason (startup oder reload) und den effektiven Zustand enabled. Auch ein deaktiviertes
Cron sendet ein Ereignis mit enabled: false, sodass eine externe Projektion
veraltete Weckzeitpunkte löschen kann. Verwenden Sie ctx.getCron?.() für genau die Scheduler-Instanz, die
den Abgleich abgeschlossen hat; ein späteres Neuladen richtet diesen Callback nicht neu aus.
ctx.abortSignal gehört zu derselben Scheduler-Momentaufnahme. Das Gateway bricht es ab,
sobald ein neuerer Scheduler aktiviert wird oder das Herunterfahren beginnt. Übergeben Sie es an jeden
dauerhaften Nebeneffekt und akzeptieren Sie die Momentaufnahme nach dem Abbruch nicht.
Dies ist ein Scheduler-Lebenszyklussignal, kein Plugin-Aktivierungssignal: Ein
ausschließliches Hot-Reload eines Plugins löst es nicht erneut aus. Ein neu aktivierter Consumer erhält
seine erste Basislinie beim nächsten Austausch des Schedulers oder Gateway-Start.
Wie bei anderen Beobachtungs-Hooks können sich die Callbacks gateway_start und cron_reconciled
überschneiden. Wenn beide Handler dieselbe Plugin-Initialisierung verwenden, koordinieren Sie sie
mit einem plugin-lokalen Bereitschafts-Promise, statt sich auf die Callback-Reihenfolge zu verlassen.
cron_changed wird für Gateway-eigene Cron-Lebenszyklusereignisse mit einer typisierten
Ereignis-Payload ausgelöst, die die Gründe added, updated, removed, started, finished
und scheduled abdeckt. Das Ereignis enthält eine Momentaufnahme von PluginHookGatewayCronJob
(einschließlich state.nextRunAtMs, state.lastRunStatus und
state.lastError, sofern vorhanden) sowie einen PluginHookGatewayCronDeliveryStatus
mit not-requested | delivered | not-delivered | unknown. Removed-Ereignisse
erfolgen nach dem Commit: Sie werden erst ausgelöst, nachdem die dauerhafte Löschung erfolgreich war, und enthalten weiterhin
die Momentaufnahme des gelöschten Auftrags, damit externe Scheduler den Zustand abgleichen können.
Ein scheduled-Ereignis erfolgt nach dem Commit: Es wird nur ausgelöst, nachdem ein erfolgreicher dauerhafter
Schreibvorgang den effektiven Wert nextRunAtMs eines vorhandenen Auftrags ändert, wobei das explizite
Lebenszyklusereignis added, updated oder removed dieses Auftrags ausgeschlossen ist. Der oberste
Wert event.nextRunAtMs ist der übernommene nächste Weckzeitpunkt; fehlt er, hat der Auftrag
keinen nächsten Weckzeitpunkt. Behandeln Sie diese Ereignisse als Abgleichhinweise, nicht als geordnetes Delta-
Protokoll. Verwenden Sie sie als zusammenführbare Hinweise, um den zuletzt von
cron_reconciled erfassten Scheduler erneut zu lesen; übernehmen Sie den Scheduler nicht aus einem
cron_changed-Kontext. Behalten Sie OpenClaw als maßgebliche Quelle für Fälligkeitsprüfungen
und Ausführung bei.
Sichere externe Cron-Projektion
Projizieren Sie eine vollständige Momentaufnahme der Weckzeitpunkte, statt Deltas von Cron-Ereignissen weiterzuleiten. Die
Operation replaceAll des externen Adapters muss atomar und idempotent sein und darf
erst aufgelöst werden, nachdem der Host die Momentaufnahme dauerhaft akzeptiert hat. Sie muss
außerdem das bereitgestellte Abbruchsignal berücksichtigen: Wenn das Signal vor der dauerhaften
Akzeptanz abbricht, darf der Adapter diese Momentaufnahme nicht akzeptieren.
Dieses Muster hält genau einen Worker für den neuesten Zustand aktiv. Nur cron_reconciled
übernimmt eine Scheduler-Instanz; cron_changed fordert diesen Worker lediglich auf, die
maßgebliche Instanz erneut zu lesen, sodass ein verspäteter Hinweis keinen älteren Scheduler wiederherstellen kann.
Eine neuere Revision bricht den aktiven Host-Versuch ab, bevor er eine veraltete
Momentaufnahme akzeptieren kann.
type ExternalWake = { jobId: string; runAtMs: number }; type ExternalWakeHost = { replaceAll(wakes: readonly ExternalWake[], options: { signal: AbortSignal }): Promise<void>; close(): Promise<void>;}; type CronReader = { list(options: { includeDisabled: true }): Promise< Array<{ id: string; enabled?: boolean; state?: { nextRunAtMs?: number }; }> >;}; export function registerCronProjection(api: OpenClawPluginApi, host: ExternalWakeHost) { const lifecycle = new AbortController(); let cron: CronReader | undefined; let enabled = false; let hasBaseline = false; let reconciliationSignal: AbortSignal | undefined; let requestedRevision = 0; let appliedRevision = 0; let worker = Promise.resolve(); let activeAttempt: AbortController | undefined; const projectLatest = async () => { let retryMs = 1_000; while (!lifecycle.signal.aborted && appliedRevision < requestedRevision) { const ownerSignal = reconciliationSignal; if (!ownerSignal || ownerSignal.aborted) { return; } const targetRevision = requestedRevision; const attempt = new AbortController(); const signal = AbortSignal.any([lifecycle.signal, ownerSignal, attempt.signal]); activeAttempt = attempt; try { const jobs = enabled && cron ? await cron.list({ includeDisabled: true }) : []; if (signal.aborted || targetRevision !== requestedRevision) { continue; } const wakes = jobs .flatMap((job): ExternalWake[] => { const runAtMs = job.enabled === false ? undefined : job.state?.nextRunAtMs; return runAtMs === undefined ? [] : [{ jobId: job.id, runAtMs }]; }) .sort((a, b) => a.runAtMs - b.runAtMs || a.jobId.localeCompare(b.jobId)); await host.replaceAll(wakes, { signal }); if (signal.aborted || targetRevision !== requestedRevision) { continue; } appliedRevision = targetRevision; retryMs = 1_000; } catch { if (lifecycle.signal.aborted || ownerSignal.aborted) { return; } if (attempt.signal.aborted) { continue; } api.logger.warn(`external cron projection failed; retrying in ${retryMs}ms`); try { await sleep(retryMs, undefined, { signal }); } catch { if (lifecycle.signal.aborted) { return; } if (attempt.signal.aborted) { continue; } } retryMs = Math.min(retryMs * 2, 30_000); } finally { if (activeAttempt === attempt) { activeAttempt = undefined; } } } }; const requestProjection = () => { const targetRevision = ++requestedRevision; activeAttempt?.abort(); worker = worker.then(async () => { if (!lifecycle.signal.aborted && appliedRevision < targetRevision) { await projectLatest(); } }); return worker; }; api.on("cron_reconciled", (event, ctx) => { const reconciledCron = ctx.getCron?.(); if (event.enabled && !reconciledCron) { api.logger.warn("cron reconciliation did not expose a scheduler"); return; } cron = reconciledCron; enabled = event.enabled; hasBaseline = true; reconciliationSignal = ctx.abortSignal; return requestProjection(); }); api.on("cron_changed", () => { if (hasBaseline) { return requestProjection(); } }); api.on("gateway_stop", async () => { lifecycle.abort(); await worker; await host.close(); });}Wenn cron_reconciled den Wert enabled: false meldet, ruft derselbe Pfad
replaceAll([]) auf und entfernt veraltete externe Aktivierungen. Wiederholungsversuche und Backoff sind in diesem Beispiel
prozesslokal und behandeln Fehler des Laufzeitadapters als vorübergehend; validieren Sie
nicht wiederholbare Konfigurationsfehler vor der Registrierung. OpenClaw stellt keine
Outbox für Auswirkungen von Plugin-Hooks bereit. Wenn der Prozess vor der dauerhaften Annahme beendet wird,
gibt der nächste Start des Gateways einen neuen maßgeblichen cron_reconciled-Snapshot aus.
gateway_stop bricht laufende Host-Arbeiten ab, wartet, bis der Worker abgeschlossen ist, und
schließt anschließend den Adapter.
Bevorstehende Einstellungen
Einige an Hooks angrenzende Oberflächen sind veraltet, werden aber weiterhin unterstützt. Migrieren Sie vor dem nächsten Major-Release:
- Klartext-Channel-Umschläge in
inbound_claim- undmessage_received- Handlern. Lesen SieBodyForAgentund die strukturierten Benutzerkontextblöcke, anstatt flachen Umschlagtext zu parsen. Siehe Klartext-Channel-Umschläge → BodyForAgent. before_agent_startbleibt aus Kompatibilitätsgründen erhalten. Neue Plugins solltenbefore_model_resolveundbefore_prompt_buildanstelle der kombinierten Phase verwenden.subagent_spawningbleibt zur Kompatibilität mit älteren Plugins erhalten, aber neue Plugins sollten daraus kein Thread-Routing zurückgeben. Der Core bereitetthread: true-Subagent-Bindungen über Channel-Sitzungsbindungsadapter vor, bevorsubagent_spawnedausgelöst wird.deactivatebleibt bis nach dem 2026-08-16 als veralteter Kompatibilitätsalias für die Bereinigung erhalten. Neue Plugins solltengateway_stopverwenden.onResolutioninbefore_tool_callverwendet jetzt die typisiertePluginApprovalResolution-Union (allow-once/allow-always/deny/timeout/cancelled) anstelle eines frei formuliertenstring.api.registerSessionExtension/api.enqueueNextTurnInjectionbleiben als Kompatibilitätsaliase auf oberster Ebene erhalten. Neue Plugins solltenapi.session.state.registerSessionExtension(...)undapi.session.workflow.enqueueNextTurnInjection(...)verwenden.
Die vollständige Liste – Registrierung von Memory-Fähigkeiten, Provider-Denkprofil,
externe Authentifizierungs-Provider, Provider-Erkennungstypen, Zugriffsmethoden der Task-Laufzeit
und die Umbenennung von command-auth → command-status – finden Sie unter
Plugin-SDK-Migration → Aktive Einstellungen.
Verwandte Themen
- Plugin-SDK-Migration – aktive Einstellungen und Zeitplan für die Entfernung
- Plugins erstellen
- Plugin-SDK-Übersicht
- Plugin-Einstiegspunkte
- Interne Hooks
- Interna der Plugin-Architektur