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 für ein kleines, vom Betreiber installiertes HOOK.md-Skript, 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 nach priority ausgeführt; Handler mit gleicher Priorität behalten ihre Registrierungsreihenfolge bei. Reine Beobachtungs-Handler werden parallel ausgeführt, und asynchron ausgelöste Beobachtungen ohne Warten auf das Ergebnis können sich mit späteren Ereignissen überschneiden. Verwenden Sie die Priorität nicht, um Nebeneffekte 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 Nebeneffekte 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 definierten Wert api.on(..., { timeoutMs }) überschreibt. Jeder Wert muss eine positive Ganzzahl bis höchstens 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, läuft weiter, 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 Arbeit verwalten, müssen einen eigenen Abbruch- und Beendigungslebenszyklus bereitstellen.
Die ausgehenden, verändernden Hooks message_sending und reply_payload_sending verwenden standardmäßig 15 Sekunden pro Handler. Wenn bei einem Handler das Zeitlimit überschritten wird, protokolliert OpenClaw den Plugin-Fehler und fährt mit dem neuesten Payload fort, damit die serialisierte Zustellungsspur abgeschlossen werden kann. Legen Sie für Plugins, die vor der Zustellung absichtlich langsamere Arbeit ausführen, ein größeres Hook-spezifisches Budget fest.
Kanal-Plugins, die createReplyDispatcher verwenden, können ebenfalls mit beforeDeliverOptions: { timeoutMs } oder beim Anhängen von Arbeit mit dispatcher.appendBeforeDeliver(handler, { timeoutMs }) ein größeres positives Budget pro Phase angeben. 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 Zustellungsspur 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 der Oberfläche gruppiert, die sie erweitern. Fettgedruckte Namen akzeptieren ein Entscheidungsergebnis (blockieren, abbrechen, überschreiben oder eine Genehmigung verlangen); 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 den 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_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 Antwort 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 |
Ausschließlich für Heartbeat bestimmten Kontext für Hintergrundüberwachungs- und Lebenszyklus-Plugins hinzufügen |
Konversationsbeobachtung
| Hook | Zweck |
|---|---|
model_call_started / model_call_ended |
Bereinigte Metadaten zu Provider-/Modellaufrufen: Zeitmessung, Ergebnis und begrenzte Anfrage-ID-Hashes. Keine Prompt- oder Antwortinhalte. |
llm_input |
Provider-Eingabe: System-Prompt, Prompt, Verlauf |
llm_output |
Provider-Ausgabe, Nutzung und die aufgelöste contextTokenBudget, sofern verfügbar |
Tools
| Hook | Zweck |
|---|---|
before_tool_call |
Tool-Parameter umschreiben, Ausführung blockieren oder Genehmigung verlangen |
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 DM-Kopplungsanfragen beobachten |
message_received |
Eingehende Inhalte, Absender, Thread und Metadaten beobachten |
message_sending |
Ausgehende Inhalte umschreiben oder Zustellung abbrechen |
reply_payload_sending |
Normalisierte Antwort-Payloads vor der Zustellung verändern oder abbrechen |
message_sent |
Erfolg oder Fehlschlag der ausgehenden Zustellung beobachten |
before_dispatch |
Eine ausgehende Weiterleitung vor der Übergabe an den Kanal prüfen oder umschreiben |
reply_dispatch |
An der abschließenden Pipeline zur Antwortzustellung teilnehmen |
Sitzungen und Compaction
| Hook | Zweck |
|---|---|
session_start / session_end |
Grenzen des Sitzungslebenszyklus verfolgen. reason ist entweder new, reset, idle, daily, compaction, deleted, shutdown, restart oder unknown. shutdown/restart werden vom Finalizer für die Gateway-Beendigung ausgelöst, wenn der Prozess mit aktiven Sitzungen beendet oder neu gestartet wird, sodass Plugins (Speicher, Transkriptspeicher) verwaiste Zeilen abschließen können, statt sie über Neustarts hinweg geöffnet zu lassen. Der Finalizer 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) |
Bei sessions.create-Aufrufen mit parentSessionKey und emitCommandHooks: true erhält eine eigenständige untergeordnete Sitzung immer session_start. Aufrufer geben mit succeedsParent an, ob auch die übergeordnete Sitzung das abschließende session_end erhält: true bedeutet Nachfolger, false bedeutet parallele untergeordnete Sitzung. Wird die Angabe weggelassen, bleibt das bisherige Verhalten beim Wechsel der übergeordneten Sitzung erhalten. Die Hooks command:new und before_reset beschreiben in beiden Fällen weiterhin die angeforderte Aktion /new.
Subagents
subagent_spawned/subagent_ended– Start und Abschluss von Subagenten beobachten.subagent_delivery_target– Kompatibilitäts-Hook für die Zustellung des Abschlusses, wenn keine Kern-Sitzungsbindung eine Route projizieren kann.subagent_spawning– veralteter Kompatibilitäts-Hook. Der Kern bereitet jetztthread: true-Subagentenbindungen über Adapter für Kanal-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 wederagentIdnochchildSessionKey; verwenden SietargetSessionKey, um es mit dem entsprechendensubagent_spawned-Ereignis zu korrelieren.
Lebenszyklus
| Hook | Zweck |
|---|---|
gateway_start / gateway_stop |
Plugin-eigene Dienste gemeinsam mit dem Gateway starten oder beenden |
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-Zustand des Gateways abgleichen |
cron_changed |
Änderungen am Gateway-eigenen Cron-Lebenszyklus beobachten (hinzugefügt, aktualisiert, entfernt, gestartet, abgeschlossen, geplant) |
before_install |
Bereitgestelltes Installationsmaterial für Skills oder Plugins aus einer geladenen Plugin-Laufzeit untersuchen |
Kanalkopplungsanfragen
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 verändert
die Kopplungsantwort nicht. Die Nutzlast enthält den Kanal, optional accountId,
die kanalbezogene senderId, die Kopplungs-code und Kanalmetadaten. Behandeln Sie den
Kopplungscode als aktive, einmalig verwendbare Genehmigungszugangsdaten und übermitteln Sie ihn nur an ein
vertrauenswürdiges Operator-Ziel. Behandeln Sie metadata als nicht vertrauenswürdigen, vom Absender bereitgestellten Identitätstext.
Der Hook enthält weder den Text noch Medien der eingehenden Nachricht.
Hooks zur Laufzeit-Diagnose
Verwenden Sie before_model_resolve, um den Provider oder das Modell für einen Agentendurchlauf 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 diagnostizieren, starten Sie das Gateway mit --raw-stream und
--raw-stream-path <path>, damit Rohereignisse des Modellstreams in eine jsonl-Datei geschrieben werden.
Richtlinie für Tool-Aufrufe
before_tool_call empfängt:
event.toolNameevent.params- optional
event.toolKindundevent.toolInputKind, hostseitig maßgebliche Unterscheidungsmerkmale für Tools, die absichtlich dieselben Namen verwenden; beispielsweise verwenden äußereexec-Aufrufe im CodemodustoolKind: "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 verändert (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 die Diagnoseangabectx.trace - optional
ctx.requester, der vom Host abgeleitete Anforderer, der den aktuellen Nachrichtendurchlauf ausgelöst hat. Die Angabe kannchannel,accountId,senderId,senderIsOwnerund die Provider-nativeroleIdsenthalten. Fehlende Felder sind nicht nachgewiesen und keine fälschlichen Zusicherungen; wenden Sie ein Fail-Closed-Verhalten an, wenn die Richtlinie sie verlangt.
Es 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 um.requireApprovalhält den Agentendurchlauf an und fragt den Benutzer über Plugin- Genehmigungen./approvekann sowohl Ausführungs- als auch Plugin-Genehmigungen erteilen. Bei nativenPreToolUse-Weiterleitungen des Codex-App-Servers im Berichtsmodus wird dies an die entsprechende Genehmigungsanfrage des App-Servers delegiert; siehe Codex-Harness-Laufzeit.- Ein
block: truemit niedrigerer Priorität kann den Aufruf 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.
Absenderbezogene Richtlinie in einer Datei
Eine eigenständige Plugin-Datei kann bereitstellungsspezifische Richtlinien im Code verwalten,
anstatt ein weiteres Konfigurationsschema hinzuzufügen. Dieses Beispiel gewährt Eigentümern Zugriff auf jedes Tool,
erlaubt konfigurierten Maintainern die Verwendung einer konservativen Auswahl an Tools und Nachrichtenaktionen
und stellt /fix für Absender bereit, die bereits durch die Kanalkonfiguration autorisiert sind:
const AGENT_ID = "maintenance-agent";const MAINTAINER_SCOPES = [ { channel: "discord", accountId: "operations", senderIds: new Set(["maintainer-user-id"]), roleIds: new Set(["maintainer-role-id"]), },];const MAINTAINER_TOOLS = new Set(["read", "web_fetch", "web_search", "session_status", "message"]);const MAINTAINER_MESSAGE_ACTIONS = new Set(["react", "reply", "thread-create", "thread-reply"]); export default definePluginEntry({ id: "maintenance-access", name: "Wartungszugriff", description: "Wendet eine absenderbezogene Tool-Richtlinie auf den Wartungsagenten an.", register(api) { api.on("before_tool_call", (event, ctx) => { if (ctx.agentId !== AGENT_ID) { return; } const requester = ctx.requester; if (requester?.senderIsOwner === true) { return; } const maintainerScope = requester ? MAINTAINER_SCOPES.find( (scope) => scope.channel === requester.channel && scope.accountId === requester.accountId, ) : undefined; const isMaintainer = maintainerScope !== undefined && ((requester?.senderId !== undefined && maintainerScope.senderIds.has(requester.senderId)) || requester?.roleIds?.some((roleId) => maintainerScope.roleIds.has(roleId)) === true); if (!isMaintainer) { return { block: true, blockReason: "Maintainer-Zugriff erforderlich." }; } if (event.toolName === "message") { const action = typeof event.params.action === "string" ? event.params.action : ""; if (MAINTAINER_MESSAGE_ACTIONS.has(action)) { return; } return { block: true, blockReason: `Für message.${action || "unknown"} ist ein Eigentümer erforderlich.` }; } if (MAINTAINER_TOOLS.has(event.toolName)) { return; } return { block: true, blockReason: `Für ${event.toolName} ist ein Eigentümer erforderlich.` }; }); api.registerCommand({ name: "fix", description: "Fordert den Wartungsagenten auf, ein Problem zu untersuchen und zu beheben.", acceptsArgs: true, requireAuth: true, handler: async (ctx) => ctx.agentId === AGENT_ID ? { continueAgent: true } : { text: "Dieser Befehl ist nur in der Wartungsunterhaltung verfügbar." }, }); },});Laden Sie die Datei direkt und starten Sie das Gateway neu:
{ agents: { list: [ { id: "maintenance-agent", workspace: "~/.openclaw/workspace-maintenance", }, ], }, bindings: [ { agentId: "maintenance-agent", match: { channel: "discord", accountId: "operations", peer: { kind: "channel", id: "maintenance-channel-id" }, }, }, ], plugins: { load: { paths: ["~/.openclaw/policies/maintenance-access.ts"] }, },}AGENT_ID muss den Agenten benennen, der an die Wartungsunterhaltung gebunden ist. Die
Bindung wählt diesen Agenten für normale Nachrichten und /fix aus; die eigenständige Datei
bleibt der alleinige Eigentümer der Tool-Richtlinie für Eigentümer und Maintainer.
requireAuth: true verwendet die bestehende Absenderzulassung des jeweiligen Kanals erneut. Bei
Discord kann eine users-/roles-Positivliste einer Gilde oder eines Kanals die
Wartungszielgruppe autorisieren. Andere Kanäle können stabile Absender-IDs verwenden. Der Hook
wendet anschließend bei jedem Tool-Aufruf im Durchlauf die feinere Entscheidung pro Tool an, einschließlich
nativer PreToolUse-Aufrufe von Codex. Er kann ein für das Modell sichtbares Tool ablehnen, aber
kein vom Host ausgelassenes Tool hinzufügen. Bestehende Sandbox-, Ausführungsgenehmigungs-, nur Eigentümern vorbehaltene
Kern-Tool- und Kanalrichtlinien gelten weiterhin; der Hook kann sie nicht umgehen.
Beschränken Sie Absender- und Rollen-IDs wie dargestellt auf ein exaktes Kanal-/Kontopaar; beide gehören zu
Provider-lokalen Namensräumen. Halten Sie die Positivlisten restriktiv. Fügen Sie Schreib- oder
Ausführungstools nur hinzu, wenn die Sandbox- und Genehmigungsrichtlinie der Bereitstellung
dies sicher zulässt. Entscheiden Sie bei automatisierten oder Systemdurchläufen ausdrücklich, ob eine fehlende
ctx.requester passieren darf; das Beispiel lehnt sie für den betreffenden Agenten ab.
Siehe Plugin-Genehmigungsanfragen für
Genehmigungsrouting, Entscheidungsverhalten und Hinweise dazu, wann requireApproval anstelle
optionaler Tools oder Ausführungsgenehmigungen verwendet werden sollte.
Plugins, die Richtlinien auf Hostebene benötigen, können vertrauenswürdige Tool-Richtlinien mit
api.registerTrustedToolPolicy(...) 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 Reihenfolge
des Ladens der Plugins; gewöhnliche before_tool_call-Hooks werden danach ausgeführt. Gebündelte Plugins behalten
den bestehenden Pfad für vertrauenswürdige Richtlinien. 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 sind auf das registrierende
Plugin beschränkt, sodass verschiedene Plugins dieselbe lokale ID verwenden können. Verwenden Sie diese Ebene nur
für vom Host als vertrauenswürdig eingestufte Schutzmechanismen wie Arbeitsbereichsrichtlinien, Budgetdurchsetzung oder
die Sicherheit reservierter Arbeitsabläufe.
Hook für die Exec-Umgebung
resolve_exec_env ermöglicht es Plugins, Umgebungsvariablen zu exec-
Tool-Aufrufen beizutragen, 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 ein Record<string, string> zurück, das mit der Exec-Umgebung zusammengeführt wird. Handler
werden nach Priorität 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 für Schlüssel der
Host-Exec-Umgebung gefiltert. PATH wird immer verworfen (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) sowie 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
Node-Host-Ausführungsanfragen weitergeleitet.
Persistenz von Tool-Ergebnissen
Tool-Ergebnisse können strukturierte details für UI-Rendering, Diagnose,
Medien-Routing oder Plugin-eigene Metadaten enthalten. Behandeln Sie details als
Laufzeitmetadaten, nicht als Prompt-Inhalt:
- OpenClaw entfernt
toolResult.detailsvor der erneuten Provider-Wiedergabe und der Eingabe für Compaction, damit Metadaten nicht Teil des Modellkontexts werden. - Persistierte Sitzungseinträge behalten nur begrenzte
details. Zu umfangreiche 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 legen Sie Prompt-relevanten Text nicht ausschließlich indetailsab; für das Modell sichtbare Tool-Ausgaben gehören incontent.
Prompt- und Modell-Hooks
Verwenden Sie für neue Plugins die phasenspezifischen Hooks:
before_model_resolve: empfängt nur den aktuellen Prompt und die Anhangsmetadaten. Geben SieproviderOverrideodermodelOverridezurück.agent_turn_prepare: empfängt den aktuellen Prompt, vorbereitete Sitzungsnachrichten und alle für diese Sitzung geleerten, 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 für Heartbeat-Durchläufe ausgeführt und gibtprependContextoderappendContextzurück. Vorgesehen für Hintergrundmonitore, die den aktuellen Zustand zusammenfassen müssen, ohne benutzerinitiierte Durchläufe zu verändern.
before_agent_run wird nach der Prompt-Erstellung und vor jeder Modelleingabe ausgeführt,
einschließlich des Ladens Prompt-lokaler Bilder und der Beobachtung durch
llm_input. 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 benutzerseitige Ersatz. Es werden
nur die Ergebnisse pass und block unterstützt; nicht unterstützte
Entscheidungsformen werden sicher abgelehnt.
Wenn ein Durchlauf blockiert wird, speichert OpenClaw nur den Ersatztext in
message.content sowie nicht vertrauliche Blockierungsmetadaten wie die ID des
blockierenden Plugins und den Zeitstempel. Der ursprüngliche Benutzertext wird weder im
Transkript noch im zukünftigen Kontext aufbewahrt. Interne Blockierungsgründe gelten als
vertraulich und werden aus Transkript-, Verlaufs-, Broadcast-, Protokoll- und
Diagnosenutzdaten ausgeschlossen. Für die Beobachtbarkeit sollten bereinigte Felder wie
Blockierer-ID, Ergebnis, Zeitstempel oder eine sichere Kategorie verwendet werden.
Hooks für Agent-Durchläufe einschließlich agent_end enthalten
event.runId, wenn OpenClaw den aktiven Durchlauf identifizieren kann; derselbe Wert
befindet sich auch in ctx.runId. Cron-gesteuerte Durchläufe stellen außerdem
ctx.jobId (die ID des auslösenden Cron-Jobs) im Kontext des Agent-Durchlaufs bereit,
damit Hooks Metriken, Nebeneffekte oder Zustände auf einen bestimmten geplanten Job
beschränken können. ctx.jobId ist nicht Teil des Tool-Kontexts
before_tool_call.
Bei kanalbasierten Durchläufen identifizieren ctx.channel und
ctx.messageProvider die Provider-Oberfläche, beispielsweise discord oder
telegram, während ctx.channelId der Bezeichner des Konversationsziels ist,
wenn OpenClaw diesen aus dem Sitzungsschlüssel oder den Zustellungsmetadaten ableiten kann.
Wenn die Identität des Absenders verfügbar ist, enthalten Agent-Hook-Kontexte außerdem:
ctx.senderId– kanalbezogene Absender-ID (z. B. Feishuopen_id, Discord-Benutzer-ID). Wird gesetzt, wenn der Durchlauf aus einer Benutzernachricht mit bekannten Absendermetadaten stammt.ctx.chatId– nativer Konversationsbezeichner des Transports (z. B. Feishuchat_id, Telegramchat_id). Wird gesetzt, wenn der Ursprungskanal 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 Felder id. Kanal-Plugins, die
umfangreichere Absender- oder Chat-Metadaten über den Eingangshilfsmechanismus übergeben,
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 übergeben diese Felder über den eingehenden SDK-Hilfsmechanismus:
buildChannelInboundEventContext({ // ... channelContext: { sender: { id: senderOpenId, unionId, userId }, chat: { id: chatId }, },});Diese Felder sind optional und fehlen bei systeminitiierten Durchläufen (Heartbeat, Cron, Exec-Ereignis).
ctx.senderExternalId bleibt als veraltetes Feld zur Quellkompatibilität für ältere Plugins
erhalten. Der Kern füllt es nicht aus; neue kanalspezifische Absenderidentitäten sollten
durch Modulerweiterung unter ctx.channelContext.sender abgelegt werden.
agent_end ist ein Beobachtungs-Hook. Gateway- und persistente Harness-Pfade
führen ihn nach dem Durchlauf ohne Warten auf das Ergebnis aus, während kurzlebige
einmalige CLI-Pfade vor der Prozessbereinigung auf das Hook-Promise warten, damit
vertrauenswürdige Plugins abschließende Beobachtbarkeitsdaten schreiben oder Zustände
erfassen können. Der Hook-Runner erzwingt ein Zeitlimit von 30 Sekunden, damit ein
hängendes Plugin oder ein hängender Embedding-Endpunkt das Hook-Promise nicht dauerhaft
offen hält. Eine Zeitüberschreitung wird protokolliert und OpenClaw fährt fort; Plugin-
eigene Netzwerkarbeit 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 unverarbeiteten Prompts, Verläufe, Antworten, Header,
Anfragekörper oder Provider-Anfrage-IDs erhalten soll. Diese Hooks enthalten stabile
Metadaten wie runId, callId, provider,
model, optional api/transport, abschließende
durationMs/outcome und upstreamRequestIdHash, wenn OpenClaw einen
begrenzten Hash der Provider-Anfrage-ID ableiten kann. Wenn die Laufzeit 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-Begrenzungen, sowie contextWindowSource und contextWindowReferenceTokens, wenn eine
niedrigere Begrenzung angewendet wurde.
before_agent_finalize wird nur ausgeführt, wenn ein Harness im Begriff ist, eine natürliche
endgültige Assistentenantwort zu akzeptieren. Dies ist nicht der Abbruchpfad
/stop und der Hook wird nicht ausgeführt, wenn der Benutzer einen Durchlauf
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 kein Ergebnis, um fortzufahren. Handler haben standardmäßig ein Budget
von 15s; bei einer Zeitüberschreitung protokolliert OpenClaw den Fehler und fährt mit der
ursprünglichen endgültigen Antwort fort.
Native Codex-Hooks Stop werden als OpenClaw-Entscheidungen
before_agent_finalize an diesen Hook weitergeleitet.
Bei der Rückgabe von action: "revise" können Plugins Metadaten vom Typ
retry 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.
idempotencyKey ermöglicht es dem Host, Wiederholungen für dieselbe Plugin-Anfrage
über gleichwertige Finalisierungsentscheidungen hinweg zu zählen, und
maxAttempts begrenzt, wie viele zusätzliche Durchläufe der Host zulässt, bevor er
mit der natürlichen endgültigen Antwort fortfährt.
Nicht gebündelte Plugins, die Hooks für unverarbeitete Konversationen
(before_model_resolve, before_agent_reply, llm_input,
llm_output, before_agent_finalize, agent_end oder
before_agent_run) benötigen, müssen Folgendes festlegen:
{ "plugins": { "entries": { "my-plugin": { "hooks": { "allowConversationAccess": true } } } }}Prompt-verändernde Hooks und dauerhafte Einfügungen für den nächsten Durchlauf können
pro Plugin mit plugins.entries.<id>.hooks.allowPromptInjection=false deaktiviert werden.
Sitzungserweiterungen und Einfügungen für den nächsten Durchlauf
Workflow-Plugins können kleine JSON-kompatible Sitzungszustände mit
api.session.state.registerSessionExtension(...) persistieren und über die Gateway-Methode
sessions.pluginPatch aktualisieren. Sitzungszeilen projizieren registrierte
Erweiterungszustände über pluginExtensions, sodass Control UI und andere Clients
Plugin-eigene Status darstellen können, ohne Plugin-Interna kennen zu müssen.
api.registerSessionExtension(...) funktioniert weiterhin, ist jedoch zugunsten des Namensraums
api.session.state veraltet.
Verwenden Sie api.session.workflow.enqueueNextTurnInjection(...), wenn ein Plugin dauerhaften Kontext benötigt, der
genau einmal den nächsten Modelldurchlauf erreichen soll (das api.enqueueNextTurnInjection(...) auf
oberster Ebene ist ein veralteter Alias mit demselben Verhalten). OpenClaw leert
eingereihte 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 nach Genehmigungen, Richtlinienzusammenfassungen,
Änderungen von Hintergrundmonitoren und Befehlsfortsetzungen, die für das Modell beim
nächsten Durchlauf sichtbar sein sollen, aber nicht zu dauerhaftem System-Prompt-Text
werden dürfen.
Bereinigungssemantiken sind Teil des Vertrags. Bereinigungs-Callbacks für
Sitzungserweiterungen und den Laufzeitlebenszyklus empfangen reset,
delete, disable oder restart. Der Host entfernt bei
Zurücksetzen/Löschen/Deaktivieren den persistenten Sitzungserweiterungszustand und
ausstehende Einfügungen für den nächsten Durchlauf des zuständigen Plugins; bei einem
Neustart bleibt der dauerhafte Sitzungszustand erhalten, während Bereinigungs-Callbacks
Plugins ermöglichen, Scheduler-Jobs, Ausführungskontext und andere externe Ressourcen
der alten Laufzeitgeneration freizugeben.
Nachrichten-Hooks
Verwenden Sie Nachrichten-Hooks für Routing- und Zustellungsrichtlinien auf Kanalebene:
message_received: beobachtet eingehende Inhalte, Absender,threadId,messageId,senderId, optionale Durchlauf-/Sitzungskorrelation, geordnetemediaund Metadaten.message_sending: schreibtcontentum oder gibt{ cancel: true }zurück.reply_payload_sending: schreibt normalisierteReplyPayload-Objekte (einschließlichpresentation,delivery, Medienreferenzen und Text) um oder gibt{ cancel: true }zurück.message_sent: beobachtet den endgültigen Erfolg oder Fehler.
Bei reinen Audio-TTS-Antworten kann content das verborgene gesprochene
Transkript enthalten, selbst wenn die Kanal-Nutzdaten keinen sichtbaren Text/Untertitel
enthalten. Das Umschreiben dieses content aktualisiert nur das für den Hook
sichtbare Transkript; es wird nicht als Medienuntertitel dargestellt.
reply_payload_sending-Ereignisse können usageState enthalten, eine nach bestem
Bemühen erstellte Live-Momentaufnahme von Modell, Nutzung und Kontext pro Durchlauf.
Dauerhafte Zustellungen, wiederhergestellte Wiedergaben und Antworten ohne genaue
Durchlaufkorrelation enthalten sie nicht.
Message-Hook-Kontexte stellen, sofern verfügbar, stabile Korrelationsfelder bereit:
ctx.sessionKey, ctx.runId, ctx.messageId, ctx.senderId, ctx.trace,
ctx.traceId, ctx.spanId, ctx.parentSpanId und ctx.callDepth. Eingehende
und before_dispatch-Kontexte stellen außerdem Antwortmetadaten bereit, wenn der Kanal
über sichtbarkeitsgefilterte Daten zitierter Nachrichten verfügt: replyToId, replyToIdFull,
replyToBody, replyToSender und replyToIsQuote. Bevorzugen Sie diese
erstklassigen Felder gegenüber dem Lesen veralteter Metadaten.
Bevorzugen Sie typisierte threadId- und replyToId-Felder gegenüber der Verwendung kanalspezifischer
Metadaten.
Eingehende Anspruchs- und Nachrichtenempfangsereignisse stellen media?: PluginHookMediaFact[] als kanonische Anhangs-API bereit. Jeder Fakt kann
path, url, contentType, kind, transcribed, messageId und
workspaceDir enthalten; die Array-Position ist die Anhangsidentität. Wenn ein Remote-Anhang
noch nicht lokal bereitgestellt wurde, fehlt media,
mediaStagingPending: true, und originalMedia enthält die
Provider-seitigen Fakten. Behandeln Sie originalMedia.path erst dann als lokal lesbar, wenn ein späteres
Bereitstellungsereignis media liefert.
Die Singular-/Plural-Eigenschaften mediaPath, mediaUrl, mediaType, mediaPaths,
mediaUrls, mediaTypes und die entsprechenden originalMedia*-Metadateneigenschaften sind
veraltete Kompatibilitätsaliase. Neue Hooks sollten die typisierten Arrays auf oberster Ebene
verwenden.
Entscheidungsregeln:
message_sendingmitcancel: trueist endgültig.message_sendingmitcancel: falsewird als keine Entscheidung behandelt.- Ein umgeschriebenes
contentwird an Hooks mit niedrigerer Priorität weitergegeben, sofern kein späterer Hook die Zustellung abbricht. reply_payload_sendingwird nach der Nutzlastnormalisierung und vor der Kanalzustellung ausgeführt, einschließlich Antworten, die an den ursprünglichen Kanal zurückgeleitet werden. Handler werden sequenziell ausgeführt, und jeder Handler sieht die neueste Nutzlast, die von Handlern mit höherer Priorität erzeugt wurde.reply_payload_sending-Nutzlasten stellen keine Laufzeit-Vertrauensmarkierungen wietrustedLocalMediabereit; Plugins können die Nutzlaststruktur bearbeiten, aber kein lokales Medienvertrauen gewähren.message_sendingkann bei einem AbbruchcancelReasonund ein begrenztesmetadatazurückgeben. Neue Nachrichtenlebenszyklus-APIs stellen dies als unterdrücktes Zustellungsergebnis mit dem Grundcancelled_by_message_sending_hookbereit; die veraltete direkte Zustellung gibt aus Kompatibilitätsgründen weiterhin ein leeres Ergebnisarray zurück.message_sentdient nur der Beobachtung. Handlerfehler werden protokolliert und ändern das Zustellungsergebnis nicht.
Installations-Hooks
Verwenden Sie security.installPolicy für vom Betreiber festgelegte Zulassungs-/Sperrentscheidungen. Diese
Richtlinie wird aus der OpenClaw-Konfiguration ausgeführt, deckt CLI-Installations- und Aktualisierungspfade ab und
schlägt bei Aktivierung, aber Nichtverfügbarkeit geschlossen fehl.
before_install ist ein Lebenszyklus-Hook der Plugin-Laufzeit. Er wird nur in dem
OpenClaw-Prozess nach security.installPolicy ausgeführt, in dem Plugin-Hooks bereits
geladen wurden, etwa bei Gateway-gestützten Installationsabläufen. Er eignet sich für
Plugin-eigene Beobachtungen, Warnungen und Kompatibilitätsprüfungen, ist jedoch nicht
die primäre Sicherheitsgrenze für Unternehmen oder Hosts bei Installationen. Das Feld
builtinScan bleibt aus Kompatibilitätsgründen in der Ereignisnutzlast, 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 endgültig. block: false wird als keine Entscheidung behandelt. Handlerfehler
blockieren die Installation nach dem Fail-Closed-Prinzip.
Gateway-Lebenszyklus
Verwenden Sie gateway_start, um allgemeine Plugin-Dienste zu starten, und gateway_stop, um
lang laufende Ressourcen zu bereinigen. Der Cron-Scheduler kann noch geladen werden, wenn
gateway_start ausgeführt wird. Verwenden Sie dies daher nicht als Basissignal für eine externe
Cron-Projektion.
Verlassen Sie sich für Plugin-eigene Laufzeitdienste nicht auf den internen
gateway:startup-Hook.
cron_reconciled wird ausgelöst, nachdem der Cron-Scheduler des Gateway und seine Beim-Beenden-
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 enabled-Zustand. Deaktiviertes
Cron löst weiterhin ein Ereignis mit enabled: false aus, 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 besitzt denselben Scheduler-Snapshot. Das Gateway bricht ihn ab,
sobald ein neuerer Scheduler aktiviert wird oder das Herunterfahren beginnt. Leiten Sie ihn an jeden
dauerhaften Nebeneffekt weiter und akzeptieren Sie den Snapshot nach seinem Abbruch nicht.
Dies ist ein Scheduler-Lebenszyklussignal und kein Plugin-Aktivierungssignal: Ein
reines Plugin-Hot-Reload löst es nicht erneut aus. Ein neu aktivierter Consumer erhält
seine erste Basislinie beim nächsten Scheduler-Austausch oder Gateway-Start.
Wie bei anderen Beobachtungs-Hooks können sich gateway_start- und cron_reconciled-Callbacks
ü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
Ereignisnutzlast ausgelöst, die die Gründe added, updated, removed, started, finished
und scheduled abdeckt. Das Ereignis enthält einen PluginHookGatewayCronJob-
Snapshot (einschließlich state.nextRunAtMs, state.lastRunStatus und
state.lastError, sofern vorhanden) sowie einen PluginHookGatewayCronDeliveryStatus
vom Typ not-requested | delivered | not-delivered | unknown. Entfernungsereignisse
erfolgen nach dem Commit: Sie werden erst ausgelöst, nachdem die dauerhafte Löschung erfolgreich war, und enthalten weiterhin
den Snapshot des gelöschten Jobs, 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 das effektive nextRunAtMs eines bestehenden Jobs geändert hat, wobei das explizite
added-, updated- oder removed-Lebenszyklusereignis dieses Jobs ausgeschlossen ist. Das
event.nextRunAtMs auf oberster Ebene ist der festgeschriebene nächste Weckzeitpunkt; wenn es fehlt, hat der Job
keinen nächsten Weckzeitpunkt. Behandeln Sie diese Ereignisse als Abgleichshinweise und nicht als geordnetes Delta-
Protokoll. Verwenden Sie sie als zusammenfassbare 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 einen vollständigen Weck-Snapshot, statt Cron-Ereignis-Deltas weiterzuleiten. Die
replaceAll-Operation des externen Adapters muss atomar und idempotent sein und darf
erst abgeschlossen werden, nachdem der Host den Snapshot dauerhaft akzeptiert hat. Sie muss außerdem
das bereitgestellte Abbruchsignal berücksichtigen: Wenn das Signal vor der dauerhaften
Akzeptanz abbricht, darf der Adapter diesen Snapshot 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 dieser einen veralteten
Snapshot 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(`externe Cron-Projektion fehlgeschlagen; erneuter Versuch 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-Abgleich hat keinen Scheduler bereitgestellt"); 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 enabled: false meldet, ruft derselbe Pfad
replaceAll([]) auf und löscht veraltete externe Weckzeitpunkte. Wiederholungsversuche/Backoff sind in diesem Beispiel
prozesslokal und behandeln Laufzeitfehler des Adapters als vorübergehend; validieren Sie
nicht wiederholbare Konfigurationsfehler vor der Registrierung. OpenClaw stellt keine
Outbox für Effekte von Plugin-Hooks bereit. Wenn der Prozess vor der dauerhaften Akzeptanz beendet wird,
gibt der nächste Gateway-Start einen neuen maßgeblichen cron_reconciled-Snapshot aus.
gateway_stop bricht laufende Host-Arbeiten ab, wartet auf den Abschluss des Workers und
schließt anschließend den Adapter.
Bevorstehende Einstellungen
Einige Hook-nahe Oberflächen sind veraltet, werden aber weiterhin unterstützt. Migrieren Sie vor dem nächsten Hauptrelease:
- Klartext-Kanalumschläge in den Handlern
inbound_claimundmessage_received. Lesen SieBodyForAgentund die strukturierten Benutzerkontextblöcke, anstatt flachen Umschlagtext zu parsen. Siehe Klartext-Kanalumschläge → BodyForAgent. subagent_spawningbleibt zur Kompatibilität mit älteren Plugins bestehen, aber neue Plugins sollten darüber kein Thread-Routing zurückgeben. Der Core bereitetthread: true-Subagent-Bindungen über Adapter für Kanalsitzungsbindungen vor, bevorsubagent_spawnedausgelöst wird.deactivatebleibt bis nach dem 2026-08-16 als veralteter Kompatibilitätsalias für die Bereinigung bestehen. 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 bestehen. Neue Plugins solltenapi.session.state.registerSessionExtension(...)undapi.session.workflow.enqueueNextTurnInjection(...)verwenden.
Die vollständige Liste – Registrierung von Speicherfähigkeiten, Denkprofil
des Providers, externe Authentifizierungs-Provider, Typen für die Provider-Erkennung, Zugriffsfunktionen
der Task-Laufzeit und die Umbenennung von command-auth → command-status – finden Sie unter
Plugin-SDK-Migration → Aktive veraltete Funktionen.
Verwandte Themen
- Plugin-SDK-Migration – aktive veraltete Funktionen und Zeitplan für deren Entfernung
- Plugins erstellen
- Plugin-SDK-Übersicht
- Plugin-Einstiegspunkte
- Interne Hooks
- Interna der Plugin-Architektur