Building plugins

Plugin हुक्स

Plugin hooks, OpenClaw plugins के लिए इन-प्रोसेस एक्सटेंशन पॉइंट हैं: एजेंट रन, टूल कॉल, संदेश प्रवाह, सत्र जीवनचक्र, सबएजेंट रूटिंग, इंस्टॉलेशन या Gateway स्टार्टअप का निरीक्षण करें या उन्हें बदलें।

इसके बजाय, कमांड और Gateway इवेंट्स जैसे /new, /reset, /stop, agent:bootstrap, या gateway:startup पर प्रतिक्रिया देने वाली, ऑपरेटर द्वारा इंस्टॉल की गई छोटी HOOK.md स्क्रिप्ट के लिए आंतरिक hooks का उपयोग करें।

त्वरित शुरुआत

Plugin एंट्री से api.on(...) के साथ टाइप किए गए hooks पंजीकृत करें:

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

निर्णय या संशोधन लौटा सकने वाले हैंडलर, घटते हुए priority क्रम में क्रमिक रूप से चलते हैं; समान प्राथमिकता वाले हैंडलर पंजीकरण क्रम बनाए रखते हैं। केवल-अवलोकन हैंडलर समानांतर रूप से चलते हैं, और फायर-एंड-फॉरगेट अवलोकन डिस्पैच बाद के इवेंट्स के साथ ओवरलैप हो सकते हैं। अवलोकन के साइड इफ़ेक्ट्स को क्रमबद्ध करने के लिए प्राथमिकता का उपयोग न करें।

api.on(name, handler, opts?) यह स्वीकार करता है:

विकल्प प्रभाव
priority क्रम निर्धारण; अधिक मान वाला पहले चलता है।
timeoutMs प्रति-hook प्रतीक्षा बजट। इसकी अवधि समाप्त होने पर OpenClaw उस हैंडलर की प्रतीक्षा बंद करके आगे बढ़ जाता है। यह हैंडलर या उसके साइड इफ़ेक्ट्स को रद्द नहीं करता। रनर की डिफ़ॉल्ट प्रति-hook टाइमआउट अवधि का उपयोग करने के लिए इसे छोड़ दें।

ऑपरेटर Plugin कोड पैच किए बिना hook बजट सेट कर सकते हैं:

json
{  "plugins": {    "entries": {      "my-plugin": {        "hooks": {          "timeoutMs": 30000,          "timeouts": {            "before_prompt_build": 90000,            "agent_end": 60000          }        }      }    }  }}

hooks.timeouts.<hookName>, hooks.timeoutMs को ओवरराइड करता है, जो बदले में Plugin द्वारा लिखे गए api.on(..., { timeoutMs }) मान को ओवरराइड करता है। प्रत्येक मान 600000 ms तक का धनात्मक पूर्णांक होना चाहिए। ज्ञात रूप से धीमे hooks के लिए प्रति-hook ओवरराइड को प्राथमिकता दें, ताकि किसी एक Plugin को हर जगह लंबा बजट न मिले।

टाइमआउट हो चुका हैंडलर प्रॉमिस चलता रहता है, क्योंकि hook कॉलबैक को रद्दीकरण सिग्नल नहीं मिलता। जब उस Plugin का कार्य अभी भी जारी हो, तब hook डिस्पैच अपना Gateway एडमिशन मुक्त कर सकता है। लंबे समय तक चलने वाले कार्य के स्वामी Plugins को अपना रद्दीकरण और शटडाउन जीवनचक्र उपलब्ध कराना होगा।

आउटबाउंड संशोधन hooks message_sending और reply_payload_sending प्रत्येक हैंडलर के लिए 15-सेकंड की डिफ़ॉल्ट अवधि का उपयोग करते हैं। यदि किसी का टाइमआउट होता है, तो OpenClaw Plugin त्रुटि लॉग करता है और नवीनतम पेलोड के साथ जारी रहता है, ताकि क्रमबद्ध डिलीवरी लेन स्थिर हो सके। डिलीवरी से पहले जानबूझकर धीमा कार्य करने वाले Plugins के लिए अधिक बड़ा प्रति-hook बजट सेट करें।

createReplyDispatcher का उपयोग करने वाले चैनल Plugins भी beforeDeliverOptions: { timeoutMs } के साथ, या dispatcher.appendBeforeDeliver(handler, { timeoutMs }) के ज़रिए कार्य जोड़ते समय, प्रत्येक चरण के लिए अधिक बड़ा धनात्मक बजट घोषित कर सकते हैं। स्वामी द्वारा घोषित बजट के बिना, वे कॉलबैक भी उसी 15-सेकंड डिफ़ॉल्ट अवधि का उपयोग करते हैं, ताकि अटका हुआ कॉलबैक क्रमबद्ध डिलीवरी लेन को रोके न रख सके।

प्रत्येक hook को event.context.pluginConfig मिलता है, जो उस Plugin का रिज़ॉल्व किया गया कॉन्फ़िगरेशन है जिसने उस हैंडलर को पंजीकृत किया था। OpenClaw इसे साझा इवेंट ऑब्जेक्ट को बदले बिना प्रत्येक हैंडलर में इंजेक्ट करता है, जिसे अन्य Plugins देखते हैं।

Hook सूची

Hooks को उनके द्वारा विस्तारित सतह के अनुसार समूहीकृत किया गया है। मोटे अक्षरों वाले नाम निर्णय परिणाम स्वीकार करते हैं (ब्लॉक करना, रद्द करना, ओवरराइड करना या अनुमोदन आवश्यक करना); शेष केवल-अवलोकन के लिए हैं।

एजेंट टर्न

Hook उद्देश्य
before_model_resolve सत्र संदेश लोड होने से पहले प्रदाता या मॉडल को ओवरराइड करना
agent_turn_prepare कतारबद्ध Plugin टर्न इंजेक्शन का उपयोग करना और प्रॉम्प्ट hooks से पहले उसी टर्न का संदर्भ जोड़ना
before_prompt_build मॉडल कॉल से पहले डायनेमिक संदर्भ या सिस्टम-प्रॉम्प्ट टेक्स्ट जोड़ना
before_agent_run मॉडल सबमिशन से पहले अंतिम प्रॉम्प्ट और सत्र संदेशों का निरीक्षण करना; रन को ब्लॉक कर सकता है
before_agent_reply सिंथेटिक उत्तर या मौन के साथ मॉडल टर्न को शॉर्ट-सर्किट करना
before_agent_finalize स्वाभाविक अंतिम उत्तर का निरीक्षण करना और मॉडल के एक और पास का अनुरोध करना
agent_end अंतिम संदेशों, सफलता स्थिति और रन अवधि का अवलोकन करना
heartbeat_prompt_contribution बैकग्राउंड मॉनिटर और जीवनचक्र Plugins के लिए केवल-Heartbeat संदर्भ जोड़ना

वार्तालाप अवलोकन

Hook उद्देश्य
model_call_started / model_call_ended स्वच्छ किए गए प्रदाता/मॉडल कॉल मेटाडेटा: समय, परिणाम, सीमित अनुरोध-ID हैश। कोई प्रॉम्प्ट या प्रतिक्रिया सामग्री नहीं।
llm_input प्रदाता इनपुट: सिस्टम प्रॉम्प्ट, प्रॉम्प्ट, इतिहास
llm_output प्रदाता आउटपुट, उपयोग और उपलब्ध होने पर रिज़ॉल्व किया गया contextTokenBudget

टूल्स

Hook उद्देश्य
before_tool_call टूल पैरामीटर फिर से लिखना, निष्पादन ब्लॉक करना या अनुमोदन आवश्यक करना
after_tool_call टूल परिणामों, त्रुटियों और अवधि का अवलोकन करना
resolve_exec_env exec में Plugin के स्वामित्व वाले पर्यावरण चर प्रदान करना
tool_result_persist टूल परिणाम से बने सहायक संदेश को फिर से लिखना
before_message_write जारी संदेश लेखन का निरीक्षण करना या उसे ब्लॉक करना (दुर्लभ)

संदेश और डिलीवरी

Hook उद्देश्य
inbound_claim एजेंट रूटिंग से पहले इनबाउंड संदेश का दावा करना (सिंथेटिक उत्तर)
channel_pairing_requested नए बनाए गए DM पेयरिंग अनुरोधों का अवलोकन करना
message_received इनबाउंड सामग्री, प्रेषक, थ्रेड और मेटाडेटा का अवलोकन करना
message_sending आउटबाउंड सामग्री को फिर से लिखना या डिलीवरी रद्द करना
reply_payload_sending डिलीवरी से पहले सामान्यीकृत उत्तर पेलोड को बदलना या रद्द करना
message_sent आउटबाउंड डिलीवरी की सफलता या विफलता का अवलोकन करना
before_dispatch चैनल हैंडऑफ़ से पहले आउटबाउंड डिस्पैच का निरीक्षण करना या उसे फिर से लिखना
reply_dispatch अंतिम उत्तर-डिस्पैच पाइपलाइन में भाग लेना

सत्र और Compaction

Hook उद्देश्य
session_start / session_end सत्र जीवनचक्र सीमाओं को ट्रैक करना। reason, new, reset, idle, daily, compaction, deleted, shutdown, restart, या unknown में से एक है। सक्रिय सत्रों के साथ प्रक्रिया के रुकने या पुनः प्रारंभ होने पर shutdown/restart, Gateway शटडाउन फ़ाइनलाइज़र से सक्रिय होते हैं, ताकि Plugins (मेमोरी, ट्रांसक्रिप्ट स्टोर) घोस्ट पंक्तियों को पुनः प्रारंभों के बीच खुला छोड़ने के बजाय अंतिम रूप दे सकें। फ़ाइनलाइज़र सीमित अवधि वाला है, ताकि धीमा Plugin SIGTERM/SIGINT को ब्लॉक न कर सके।
before_compaction / after_compaction Compaction चक्रों का अवलोकन करना या उन पर टिप्पणी जोड़ना
before_reset सत्र-रीसेट इवेंट्स (/reset, प्रोग्रामेटिक रीसेट) का अवलोकन करना

parentSessionKey और emitCommandHooks: true वाली sessions.create कॉल्स के लिए, एक अलग चाइल्ड को हमेशा session_start मिलता है। कॉलर succeedsParent के साथ यह घोषित करते हैं कि पैरेंट को भी टर्मिनल session_end मिलता है या नहीं: true का अर्थ उत्तराधिकारी है, false का अर्थ समानांतर चाइल्ड है। इसे छोड़ने पर लेगेसी पैरेंट-रोलओवर व्यवहार बना रहता है। command:new और before_reset hooks दोनों मामलों में अनुरोधित /new कार्रवाई का वर्णन करते रहते हैं।

सबएजेंट्स

  • subagent_spawned / subagent_ended - सबएजेंट के प्रारंभ और पूर्ण होने का अवलोकन करें।
  • subagent_delivery_target - जब कोई कोर सत्र बाइंडिंग किसी रूट को प्रक्षेपित नहीं कर सकती, तब पूर्णता डिलीवरी के लिए संगतता हुक।
  • subagent_spawning - बहिष्कृत संगतता हुक। अब कोर, subagent_spawned के सक्रिय होने से पहले चैनल सत्र-बाइंडिंग अडैप्टर के माध्यम से thread: true सबएजेंट बाइंडिंग तैयार करता है।
  • जब OpenClaw ने प्रारंभ से पहले चाइल्ड सत्र का नेटिव मॉडल निर्धारित कर लिया हो, तब subagent_spawned में resolvedModel और resolvedProvider शामिल होते हैं।
  • subagent_ended में targetSessionKey (पहचान - subagent_spawned.childSessionKey से मेल खाती है), targetKind ("subagent" या "acp"), reason, वैकल्पिक outcome ("ok", "error", "timeout", "killed", "reset", या "deleted"), वैकल्पिक error, runId, endedAt, accountId, और sendFarewell होते हैं। इसमें agentId या childSessionKey शामिल नहीं होते; संबंधित subagent_spawned इवेंट से सहसंबंध स्थापित करने के लिए targetSessionKey का उपयोग करें।

जीवनचक्र

हुक उद्देश्य
gateway_start / gateway_stop Gateway के साथ Plugin के स्वामित्व वाली सेवाएँ प्रारंभ या बंद करना
deactivate gateway_stop के लिए बहिष्कृत संगतता उपनाम; नए Plugins में gateway_stop का उपयोग करें
cron_reconciled स्टार्टअप या रीलोड के बाद संपूर्ण Gateway Cron स्थिति से मिलान करना
cron_changed Gateway के स्वामित्व वाले Cron जीवनचक्र परिवर्तनों (जोड़ा गया, अपडेट किया गया, हटाया गया, प्रारंभ हुआ, समाप्त हुआ, शेड्यूल किया गया) का अवलोकन करना
before_install लोड किए गए Plugin रनटाइम से चरणबद्ध Skills या Plugin इंस्टॉलेशन सामग्री का निरीक्षण करना

चैनल पेयरिंग अनुरोध

जब किसी Plugin को किसी अनपेयर किए गए DM प्रेषक द्वारा लंबित पेयरिंग अनुरोध बनाने के बाद ऑपरेटर को सूचित करने या ऑडिट रिकॉर्ड लिखने की आवश्यकता हो, तब channel_pairing_requested का उपयोग करें। अनुरोध बनते ही हुक डिस्पैच होता है; धीमे या विफल हुक हैंडलर के कारण पेयरिंग उत्तर की चैनल डिलीवरी में विलंब नहीं होता।

typescript
api.on("channel_pairing_requested", async (event) => {  await notifyOperator({    text: `${event.senderId} से नया ${event.channel} पेयरिंग अनुरोध: ${event.code}`,  });});

यह हुक केवल अवलोकन के लिए है। यह पेयरिंग उत्तर को स्वीकृत, अस्वीकृत, दबाता या पुनर्लिखित नहीं करता। पेलोड में चैनल, वैकल्पिक accountId, चैनल-स्कोप वाला senderId, पेयरिंग code, और चैनल मेटाडेटा शामिल हैं। पेयरिंग कोड को सक्रिय, एकल-उपयोग स्वीकृति क्रेडेंशियल मानें और इसे केवल किसी विश्वसनीय ऑपरेटर सिंक तक पहुँचाएँ। metadata को प्रेषक द्वारा दिया गया अविश्वसनीय पहचान टेक्स्ट मानें। हुक में आने वाले संदेश का मुख्य भाग या मीडिया शामिल नहीं होता।

डीबग रनटाइम हुक

एजेंट टर्न के लिए प्रदाता या मॉडल बदलने हेतु before_model_resolve का उपयोग करें - यह मॉडल रिज़ॉल्यूशन से पहले चलता है। llm_output केवल तब चलता है, जब मॉडल प्रयास सहायक आउटपुट उत्पन्न करता है।

प्रभावी सत्र मॉडल के प्रमाण के लिए, रनटाइम पंजीकरणों का निरीक्षण करें, फिर openclaw sessions या Gateway सत्र/स्थिति सतहों का उपयोग करें। प्रदाता पेलोड डीबग करने के लिए, कच्चे मॉडल स्ट्रीम इवेंट को jsonl फ़ाइल में लिखने हेतु Gateway को --raw-stream और --raw-stream-path <path> के साथ प्रारंभ करें।

टूल कॉल नीति

before_tool_call को ये प्राप्त होते हैं:

  • event.toolName
  • event.params
  • वैकल्पिक event.toolKind और event.toolInputKind, ऐसे टूल के लिए होस्ट-प्रामाणिक विभेदक जो जानबूझकर समान नाम साझा करते हैं; उदाहरण के लिए, बाहरी कोड-मोड exec कॉल toolKind: "code_mode_exec" का उपयोग करते हैं और इनपुट भाषा ज्ञात होने पर toolInputKind: "javascript" | "typescript" शामिल करते हैं
  • वैकल्पिक event.derivedPaths, apply_patch जैसे प्रसिद्ध टूल एनवेलप के लिए होस्ट से प्राप्त सर्वोत्तम-प्रयास लक्ष्य पथ संकेत; ये पथ अधूरे हो सकते हैं या टूल वास्तव में जिन चीज़ों को प्रभावित करेगा उनका अधिक व्यापक अनुमान लगा सकते हैं (उदाहरण के लिए, विकृत या आंशिक इनपुट के साथ)
  • वैकल्पिक event.runId
  • वैकल्पिक event.toolCallId
  • संदर्भ फ़ील्ड, जैसे ctx.agentId, ctx.sessionKey, ctx.sessionId, ctx.runId, ctx.toolKind, ctx.toolInputKind, और डायग्नोस्टिक ctx.trace
  • वैकल्पिक ctx.requester, होस्ट से प्राप्त वह अनुरोधकर्ता जिसने वर्तमान संदेश रन प्रारंभ किया। इसमें channel, accountId, senderId, senderIsOwner, और प्रदाता-नेटिव roleIds शामिल हो सकते हैं। अनुपस्थित फ़ील्ड अप्रमाणित हैं, झूठे आश्वासन नहीं; जहाँ नीति को उनकी आवश्यकता हो, वहाँ सुरक्षित रूप से अस्वीकार करें।

यह इन्हें लौटा सकता है:

typescript
type BeforeToolCallResult = {  params?: Record<string, unknown>;  block?: boolean;  blockReason?: string;  requireApproval?: {    title: string;    description: string;    severity?: "info" | "warning" | "critical";    timeoutMs?: number;    /** @deprecated अनसुलझी स्वीकृतियाँ हमेशा अस्वीकार होती हैं। */    timeoutBehavior?: "allow" | "deny";    allowedDecisions?: Array<"allow-once" | "allow-always" | "deny">;    pluginId?: string;    onResolution?: (      decision: "allow-once" | "allow-always" | "deny" | "timeout" | "cancelled",    ) => Promise<void> | void;  };};

टाइप किए गए जीवनचक्र हुक के लिए गार्ड व्यवहार:

  • block: true अंतिम है और निम्न-प्राथमिकता हैंडलर को छोड़ देता है।
  • block: false को कोई निर्णय नहीं माना जाता।
  • params निष्पादन के लिए टूल पैरामीटर पुनर्लिखित करता है।
  • requireApproval एजेंट रन को रोकता है और Plugin स्वीकृतियों के माध्यम से उपयोगकर्ता से पूछता है। /approve exec और Plugin, दोनों स्वीकृतियों को मंज़ूरी दे सकता है। Codex app-server रिपोर्ट-मोड नेटिव PreToolUse रिले में, यह संबंधित app-server स्वीकृति अनुरोध को सौंप देता है; देखें Codex हार्नेस रनटाइम
  • उच्च-प्राथमिकता हुक द्वारा स्वीकृति का अनुरोध किए जाने के बाद भी निम्न-प्राथमिकता वाला block: true ब्लॉक कर सकता है।
  • onResolution को निर्धारित निर्णय प्राप्त होता है: allow-once, allow-always, deny, timeout, या cancelled

एक फ़ाइल में प्रेषक-जागरूक नीति

एक स्वतंत्र Plugin फ़ाइल एक और कॉन्फ़िगरेशन स्कीमा जोड़ने के बजाय परिनियोजन-विशिष्ट नीति को कोड में रख सकती है। यह उदाहरण स्वामियों को हर टूल देता है, कॉन्फ़िगर किए गए मेंटेनर को एक रूढ़िवादी टूल और संदेश-क्रिया सेट का उपयोग करने देता है, और चैनल कॉन्फ़िगरेशन द्वारा पहले से अधिकृत प्रेषकों को /fix उपलब्ध कराता है:

typescript
 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: "रखरखाव पहुँच",  description: "रखरखाव एजेंट पर प्रेषक-जागरूक टूल नीति लागू करें।",  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: "मेंटेनर पहुँच आवश्यक है।" };      }       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: `message.${action || "unknown"} के लिए स्वामी आवश्यक है।` };      }       if (MAINTAINER_TOOLS.has(event.toolName)) {        return;      }      return { block: true, blockReason: `${event.toolName} के लिए स्वामी आवश्यक है।` };    });     api.registerCommand({      name: "fix",      description: "रखरखाव एजेंट से किसी समस्या की जाँच और समाधान करने को कहें।",      acceptsArgs: true,      requireAuth: true,      handler: async (ctx) =>        ctx.agentId === AGENT_ID          ? { continueAgent: true }          : { text: "यह कमांड केवल रखरखाव वार्तालाप में उपलब्ध है।" },    });  },});

फ़ाइल को सीधे लोड करें और Gateway पुनः प्रारंभ करें:

json5
{  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 में रखरखाव वार्तालाप से बाइंड किए गए एजेंट का नाम होना आवश्यक है। बाइंडिंग सामान्य संदेशों और /fix के लिए उस एजेंट को चुनती है; स्वतंत्र फ़ाइल स्वामी-बनाम-मेंटेनर टूल नीति की एकमात्र स्वामी बनी रहती है।

requireAuth: true प्रत्येक चैनल के मौजूदा प्रेषक प्रवेश का पुनः उपयोग करता है। Discord के लिए, गिल्ड या चैनल users/roles अनुमतिसूची रखरखाव दर्शकों को अधिकृत कर सकती है। अन्य चैनल स्थिर प्रेषक आईडी का उपयोग कर सकते हैं। इसके बाद हुक रन में हर टूल कॉल पर अधिक सूक्ष्म प्रति-टूल निर्णय लागू करता है, जिसमें Codex नेटिव PreToolUse कॉल भी शामिल हैं। यह मॉडल को दिखाई देने वाले टूल को वीटो कर सकता है, लेकिन होस्ट द्वारा छोड़े गए टूल को जोड़ नहीं सकता। मौजूदा सैंडबॉक्स, exec स्वीकृति, केवल-स्वामी कोर-टूल, और चैनल नीतियाँ फिर भी लागू होती हैं; हुक उन्हें पार करके अनुमति नहीं दे सकता।

जैसा दिखाया गया है, प्रेषक और भूमिका आईडी को सटीक चैनल/अकाउंट जोड़ी तक सीमित रखें; दोनों प्रदाता-स्थानीय नेमस्पेस हैं। अनुमतिसूचियों को रूढ़िवादी रखें। लेखन या निष्पादन टूल केवल तभी जोड़ें, जब परिनियोजन की सैंडबॉक्स और स्वीकृति नीति उसे सुरक्षित बनाती हो। स्वचालित या सिस्टम रन के लिए स्पष्ट रूप से तय करें कि अनुपस्थित ctx.requester को पास होना चाहिए या नहीं; उदाहरण स्कोप किए गए एजेंट के लिए इसे अस्वीकार करता है।

स्वीकृति रूटिंग, निर्णय व्यवहार, और वैकल्पिक टूल या exec स्वीकृतियों के बजाय requireApproval का उपयोग कब करना है, इसके लिए Plugin अनुमति अनुरोध देखें।

जिन Plugins को होस्ट-स्तरीय नीति की आवश्यकता है, वे api.registerTrustedToolPolicy(...) के साथ विश्वसनीय टूल नीतियाँ पंजीकृत कर सकते हैं। ये सामान्य before_tool_call हुक और सामान्य हुक निर्णयों से पहले चलते हैं। बंडल की गई विश्वसनीय नीतियाँ पहले चलती हैं; इंस्टॉल किए गए Plugin की विश्वसनीय नीतियाँ Plugin-लोड क्रम में अगली चलती हैं; सामान्य before_tool_call हुक उनके बाद चलते हैं। बंडल किए गए Plugins मौजूदा विश्वसनीय-नीति पथ बनाए रखते हैं। इंस्टॉल किए गए Plugins स्पष्ट रूप से सक्षम होने चाहिए और उन्हें contracts.trustedToolPolicies में प्रत्येक नीति आईडी घोषित करनी होगी; अघोषित आईडी पंजीकरण से पहले अस्वीकार कर दी जाती हैं। नीति आईडी पंजीकरण करने वाले Plugin के स्कोप में होती हैं, इसलिए अलग-अलग Plugins समान स्थानीय आईडी का पुनः उपयोग कर सकते हैं। इस स्तर का उपयोग केवल होस्ट-विश्वसनीय गेट, जैसे कार्यस्थान नीति, बजट प्रवर्तन, या आरक्षित वर्कफ़्लो सुरक्षा के लिए करें।

Exec परिवेश हुक

resolve_exec_env कमांड चलने से पहले plugins को exec टूल आह्वानों में परिवेश चर देने देता है। इसे ये प्राप्त होते हैं:

  • event.sessionKey
  • event.toolName, वर्तमान में हमेशा "exec"
  • event.host, जो "gateway", "sandbox", या "node" में से एक है
  • संदर्भ फ़ील्ड, जैसे ctx.agentId, ctx.sessionKey, ctx.messageProvider, और ctx.channelId

Exec परिवेश में मर्ज करने के लिए एक Record<string, string> लौटाएँ। हैंडलर प्राथमिकता क्रम में चलते हैं; समान कुंजी के लिए बाद के परिणाम पहले के परिणामों को ओवरराइड करते हैं।

मर्ज करने से पहले हुक आउटपुट को होस्ट की exec परिवेश कुंजी नीति से फ़िल्टर किया जाता है। PATH हमेशा हटा दिया जाता है (कमांड रिज़ॉल्यूशन और safe-bin जाँच इस पर निर्भर करती हैं)। अमान्य कुंजियाँ और खतरनाक होस्ट ओवरराइड कुंजियाँ, जैसे LD_*, DYLD_*, NODE_OPTIONS, प्रॉक्सी चर (HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, NO_PROXY), और TLS ओवरराइड चर (NODE_TLS_REJECT_UNAUTHORIZED, SSL_CERT_FILE, और इसी प्रकार के अन्य) हटा दिए जाते हैं। फ़िल्टर किया गया plugin परिवेश Gateway अनुमोदन/ऑडिट मेटाडेटा में शामिल किया जाता है और node-host निष्पादन अनुरोधों में अग्रेषित किया जाता है।

टूल परिणाम स्थायित्व

टूल परिणामों में UI रेंडरिंग, निदान, मीडिया रूटिंग या plugin-स्वामित्व वाले मेटाडेटा के लिए संरचित details शामिल हो सकता है। details को रनटाइम मेटाडेटा मानें, प्रॉम्प्ट सामग्री नहीं:

  • OpenClaw प्रदाता रीप्ले और Compaction इनपुट से पहले toolResult.details हटा देता है, ताकि मेटाडेटा मॉडल संदर्भ न बने।
  • स्थायी सत्र प्रविष्टियाँ केवल सीमित details रखती हैं। बहुत बड़े विवरणों को एक संक्षिप्त सारांश और persistedDetailsTruncated: true से बदल दिया जाता है।
  • tool_result_persist और before_message_write अंतिम स्थायित्व सीमा से पहले चलते हैं। लौटाया गया details छोटा रखें और प्रॉम्प्ट-संबंधित टेक्स्ट केवल details में रखने से बचें; मॉडल को दिखाई देने वाला टूल आउटपुट content में रखें।

प्रॉम्प्ट और मॉडल हुक

नए plugins के लिए चरण-विशिष्ट हुक का उपयोग करें:

  • before_model_resolve: केवल वर्तमान प्रॉम्प्ट और अटैचमेंट मेटाडेटा प्राप्त करता है। providerOverride या modelOverride लौटाएँ।
  • agent_turn_prepare: वर्तमान प्रॉम्प्ट, तैयार सत्र संदेश और इस सत्र के लिए निकाले गए exactly-once कतारबद्ध इंजेक्शन प्राप्त करता है। prependContext या appendContext लौटाएँ।
  • before_prompt_build: वर्तमान प्रॉम्प्ट और सत्र संदेश प्राप्त करता है। prependContext, appendContext, systemPrompt, prependSystemContext, या appendSystemContext लौटाएँ।
  • heartbeat_prompt_contribution: केवल Heartbeat टर्न के लिए चलता है और prependContext या appendContext लौटाता है। यह ऐसे पृष्ठभूमि मॉनिटरों के लिए है जिन्हें उपयोगकर्ता द्वारा आरंभ किए गए टर्न बदले बिना वर्तमान स्थिति का सारांश देना होता है।

before_agent_run प्रॉम्प्ट निर्माण के बाद और किसी भी मॉडल इनपुट से पहले चलता है, जिसमें प्रॉम्प्ट-स्थानीय छवि लोडिंग और llm_input अवलोकन शामिल हैं। इसे वर्तमान उपयोगकर्ता इनपुट prompt के रूप में, साथ ही messages में लोड किया गया सत्र इतिहास और सक्रिय सिस्टम प्रॉम्प्ट प्राप्त होते हैं। मॉडल द्वारा प्रॉम्प्ट पढ़े जाने से पहले रन रोकने के लिए { outcome: "block", reason, message? } लौटाएँ। reason आंतरिक है; message उपयोगकर्ता को दिखाई देने वाला प्रतिस्थापन है। केवल pass और block परिणाम समर्थित हैं; असमर्थित निर्णय संरचनाएँ fail closed होती हैं।

जब कोई रन अवरुद्ध होता है, तो OpenClaw केवल प्रतिस्थापन टेक्स्ट को message.content में तथा गैर-संवेदनशील ब्लॉक मेटाडेटा, जैसे अवरोधक plugin id और टाइमस्टैम्प, संग्रहीत करता है। मूल उपयोगकर्ता टेक्स्ट ट्रांसक्रिप्ट या भविष्य के संदर्भ में नहीं रखा जाता। आंतरिक ब्लॉक कारणों को संवेदनशील माना जाता है और ट्रांसक्रिप्ट, इतिहास, प्रसारण, लॉग तथा निदान पेलोड से बाहर रखा जाता है। अवलोकनीयता के लिए अवरोधक id, परिणाम, टाइमस्टैम्प या सुरक्षित श्रेणी जैसे स्वच्छ किए गए फ़ील्ड का उपयोग करना चाहिए।

agent_end सहित एजेंट-टर्न हुक में event.runId शामिल होता है, जब OpenClaw सक्रिय रन की पहचान कर सकता है; वही मान ctx.runId पर भी होता है। Cron-संचालित रन एजेंट-टर्न संदर्भ पर ctx.jobId (मूल Cron जॉब id) भी उपलब्ध कराते हैं, ताकि हुक मेट्रिक्स, साइड इफ़ेक्ट या स्थिति को किसी विशिष्ट निर्धारित जॉब तक सीमित कर सकें। ctx.jobId, before_tool_call टूल संदर्भ का भाग नहीं है।

चैनल से आरंभ हुए रन के लिए, ctx.channel और ctx.messageProvider discord या telegram जैसी प्रदाता सतह की पहचान करते हैं, जबकि ctx.channelId वार्तालाप लक्ष्य पहचानकर्ता है, जब OpenClaw सत्र कुंजी या डिलीवरी मेटाडेटा से इसे प्राप्त कर सकता है।

जब प्रेषक की पहचान उपलब्ध होती है, तो एजेंट हुक संदर्भों में ये भी शामिल होते हैं:

  • ctx.senderId - चैनल-सीमित प्रेषक ID (जैसे Feishu open_id, Discord उपयोगकर्ता ID)। जब रन ज्ञात प्रेषक मेटाडेटा वाले उपयोगकर्ता संदेश से आरंभ होता है, तब भरा जाता है।
  • ctx.chatId - परिवहन-मूल वार्तालाप पहचानकर्ता (जैसे Feishu chat_id, Telegram chat_id)। जब मूल चैनल मूल वार्तालाप ID प्रदान करता है, तब भरा जाता है।
  • ctx.channelContext.sender.id - ctx.senderId वाला वही प्रेषक ID, चैनल-स्वामित्व वाली उस ऑब्जेक्ट के अंतर्गत जिसे plugins चैनल-विशिष्ट फ़ील्ड से विस्तारित कर सकते हैं।
  • ctx.channelContext.chat.id - ctx.chatId वाली वही वार्तालाप ID, चैनल-स्वामित्व वाली उस ऑब्जेक्ट के अंतर्गत जिसे plugins चैनल-विशिष्ट फ़ील्ड से विस्तारित कर सकते हैं।

कोर केवल नेस्टेड id फ़ील्ड परिभाषित करता है। इनबाउंड सहायक के माध्यम से अधिक समृद्ध प्रेषक या चैट मेटाडेटा भेजने वाले चैनल plugins openclaw/plugin-sdk/channel-inbound से PluginHookChannelSenderContext या PluginHookChannelChatContext को विस्तारित कर सकते हैं:

ts
declare module "openclaw/plugin-sdk/channel-inbound" {  interface PluginHookChannelSenderContext {    unionId?: string;    userId?: string;  }}

चैनल plugins इनबाउंड SDK सहायक के माध्यम से वे फ़ील्ड भेजते हैं:

ts
buildChannelInboundEventContext({  // ...  channelContext: {    sender: { id: senderOpenId, unionId, userId },    chat: { id: chatId },  },});

ये फ़ील्ड वैकल्पिक हैं और सिस्टम से आरंभ हुए रन (Heartbeat, Cron, exec-event) में अनुपस्थित रहते हैं।

ctx.senderExternalId पुराने plugins के लिए बहिष्कृत स्रोत-संगतता फ़ील्ड के रूप में बना हुआ है। कोर इसे नहीं भरता; नए चैनल-विशिष्ट प्रेषक पहचान मान मॉड्यूल विस्तार के माध्यम से ctx.channelContext.sender के अंतर्गत रहने चाहिए।

agent_end एक अवलोकन हुक है। Gateway और स्थायी हार्नेस पथ टर्न के बाद इसे fire-and-forget रूप में चलाते हैं, जबकि अल्पकालिक one-shot CLI पथ प्रोसेस क्लीनअप से पहले हुक प्रॉमिस की प्रतीक्षा करते हैं, ताकि विश्वसनीय plugins टर्मिनल अवलोकनीयता फ़्लश कर सकें या स्थिति कैप्चर कर सकें। हुक रनर 30 सेकंड का टाइमआउट लागू करता है, ताकि अटका हुआ plugin या एम्बेडिंग एंडपॉइंट हुक प्रॉमिस को हमेशा के लिए लंबित न छोड़ सके। टाइमआउट लॉग किया जाता है और OpenClaw जारी रहता है; यह plugin-स्वामित्व वाले नेटवर्क कार्य को रद्द नहीं करता, जब तक plugin अपने स्वयं के abort सिग्नल का भी उपयोग न करे।

ऐसी प्रदाता-कॉल टेलीमेट्री के लिए model_call_started और model_call_ended का उपयोग करें, जिसे रॉ प्रॉम्प्ट, इतिहास, प्रतिक्रियाएँ, हेडर, अनुरोध बॉडी या प्रदाता अनुरोध ID प्राप्त नहीं होने चाहिए। इन हुक में स्थिर मेटाडेटा, जैसे runId, callId, provider, model, वैकल्पिक api/transport, टर्मिनल durationMs/outcome, और upstreamRequestIdHash शामिल होते हैं, जब OpenClaw एक सीमित प्रदाता request-id हैश प्राप्त कर सकता है। जब रनटाइम ने context-window मेटाडेटा रिज़ॉल्व कर लिया हो, तो हुक इवेंट और संदर्भ में contextTokenBudget, यानी मॉडल/config/एजेंट सीमाओं के बाद प्रभावी टोकन बजट, और कम सीमा लागू होने पर contextWindowSource तथा contextWindowReferenceTokens भी शामिल होते हैं।

before_agent_finalize केवल तब चलता है, जब कोई हार्नेस स्वाभाविक अंतिम सहायक उत्तर स्वीकार करने वाला हो। यह /stop रद्दीकरण पथ नहीं है और उपयोगकर्ता द्वारा टर्न रद्द करने पर नहीं चलता। अंतिम रूप देने से पहले हार्नेस से एक और मॉडल पास माँगने के लिए { action: "revise", reason }, अंतिम रूप देना बाध्य करने के लिए { action: "finalize", reason? } लौटाएँ, या जारी रखने के लिए परिणाम छोड़ दें। हैंडलरों का डिफ़ॉल्ट बजट 15s है; टाइमआउट होने पर OpenClaw विफलता लॉग करता है और मूल अंतिम उत्तर के साथ जारी रहता है। Codex के मूल Stop हुक इस हुक में OpenClaw before_agent_finalize निर्णयों के रूप में रिले किए जाते हैं।

action: "revise" लौटाते समय, plugins अतिरिक्त मॉडल पास को सीमित और रीप्ले-सुरक्षित बनाने के लिए retry मेटाडेटा शामिल कर सकते हैं:

typescript
type BeforeAgentFinalizeRetry = {  instruction: string;  idempotencyKey?: string;  maxAttempts?: number;};

instruction को हार्नेस को भेजे गए संशोधन कारण में जोड़ा जाता है। idempotencyKey होस्ट को समतुल्य अंतिमकरण निर्णयों में समान plugin अनुरोध के लिए पुनः प्रयास गिनने देता है, और maxAttempts यह सीमा तय करता है कि स्वाभाविक अंतिम उत्तर के साथ जारी रहने से पहले होस्ट कितने अतिरिक्त पास की अनुमति देगा।

जिन गैर-बंडल plugins को रॉ वार्तालाप हुक (before_model_resolve, before_agent_reply, llm_input, llm_output, before_agent_finalize, agent_end, या before_agent_run) चाहिए, उन्हें यह सेट करना होगा:

json
{  "plugins": {    "entries": {      "my-plugin": {        "hooks": {          "allowConversationAccess": true        }      }    }  }}

प्रॉम्प्ट बदलने वाले हुक और स्थायी अगले-टर्न इंजेक्शन को प्रत्येक plugin के लिए plugins.entries.<id>.hooks.allowPromptInjection=false से अक्षम किया जा सकता है।

सत्र विस्तार और अगले-टर्न इंजेक्शन

वर्कफ़्लो plugins api.session.state.registerSessionExtension(...) के साथ छोटी JSON-संगत सत्र स्थिति को स्थायी कर सकते हैं और Gateway sessions.pluginPatch विधि के माध्यम से उसे अपडेट कर सकते हैं। सत्र पंक्तियाँ पंजीकृत विस्तार स्थिति को pluginExtensions के माध्यम से प्रोजेक्ट करती हैं, जिससे Control UI और अन्य क्लाइंट plugin की आंतरिक जानकारी जाने बिना plugin-स्वामित्व वाली स्थिति रेंडर कर सकते हैं। api.registerSessionExtension(...) अब भी काम करता है, लेकिन api.session.state नेमस्पेस के पक्ष में बहिष्कृत है।

जब किसी plugin को अगले मॉडल टर्न तक स्थायी संदर्भ ठीक एक बार पहुँचाना हो, तब api.session.workflow.enqueueNextTurnInjection(...) का उपयोग करें (शीर्ष-स्तरीय api.enqueueNextTurnInjection(...) समान व्यवहार वाला एक बहिष्कृत उपनाम है)। OpenClaw प्रॉम्प्ट हुक से पहले कतारबद्ध इंजेक्शन निकालता है, समाप्त हो चुके इंजेक्शन हटाता है और प्रत्येक plugin के लिए idempotencyKey के अनुसार डुप्लिकेट हटाता है। यह अनुमोदन पुनरारंभ, नीति सारांश, पृष्ठभूमि मॉनिटर अंतर और कमांड निरंतरताओं के लिए सही सीम है, जिन्हें अगले टर्न पर मॉडल को दिखाई देना चाहिए, लेकिन स्थायी सिस्टम प्रॉम्प्ट टेक्स्ट नहीं बनना चाहिए।

क्लीनअप अर्थविज्ञान अनुबंध का भाग हैं। सत्र विस्तार क्लीनअप और रनटाइम जीवनचक्र क्लीनअप कॉलबैक को reset, delete, disable, या restart प्राप्त होते हैं। होस्ट reset/delete/disable के लिए स्वामी plugin की स्थायी सत्र विस्तार स्थिति और लंबित अगले-टर्न इंजेक्शन हटा देता है; restart स्थायी सत्र स्थिति बनाए रखता है, जबकि क्लीनअप कॉलबैक plugins को पुरानी रनटाइम पीढ़ी के शेड्यूलर जॉब, रन संदर्भ और अन्य आउट-ऑफ़-बैंड संसाधन रिलीज़ करने देते हैं।

संदेश हुक

चैनल-स्तरीय रूटिंग और डिलीवरी नीति के लिए संदेश हुक का उपयोग करें:

  • message_received: इनबाउंड सामग्री, प्रेषक, threadId, messageId, senderId, वैकल्पिक रन/सत्र सहसंबंध और मेटाडेटा का अवलोकन करता है।
  • message_sending: content को फिर से लिखें या { cancel: true } लौटाएँ।
  • reply_payload_sending: सामान्यीकृत ReplyPayload ऑब्जेक्ट (presentation, delivery, मीडिया संदर्भ और टेक्स्ट सहित) को फिर से लिखें या { cancel: true } लौटाएँ।
  • message_sent: अंतिम सफलता या विफलता का अवलोकन करता है।

केवल-ऑडियो TTS उत्तरों के लिए, content में छिपी हुई बोली गई ट्रांसक्रिप्ट शामिल हो सकती है, भले ही चैनल पेलोड में कोई दृश्यमान टेक्स्ट/कैप्शन न हो। उस content को फिर से लिखने से केवल हुक को दिखाई देने वाली ट्रांसक्रिप्ट अपडेट होती है; इसे मीडिया कैप्शन के रूप में रेंडर नहीं किया जाता।

reply_payload_sending इवेंट में usageState, यानी सर्वोत्तम-प्रयास वाला लाइव प्रति-टर्न मॉडल/उपयोग/संदर्भ स्नैपशॉट शामिल हो सकता है। स्थायी डिलीवरी, पुनर्प्राप्त रीप्ले और सटीक रन सहसंबंध के बिना उत्तरों में यह शामिल नहीं होता।

उपलब्ध होने पर संदेश हुक संदर्भ स्थिर सहसंबंध फ़ील्ड उजागर करते हैं: ctx.sessionKey, ctx.runId, ctx.messageId, ctx.senderId, ctx.trace, ctx.traceId, ctx.spanId, ctx.parentSpanId, और ctx.callDepth। इनबाउंड और before_dispatch संदर्भ उत्तर मेटाडेटा भी उजागर करते हैं, जब चैनल के पास दृश्यता-फ़िल्टर किया हुआ उद्धृत संदेश डेटा हो: replyToId, replyToIdFull, replyToBody, replyToSender, और replyToIsQuote। विरासती मेटाडेटा पढ़ने से पहले इन प्रथम-श्रेणी फ़ील्ड को प्राथमिकता दें।

चैनल-विशिष्ट मेटाडेटा का उपयोग करने से पहले टाइप किए गए threadId और replyToId फ़ील्ड को प्राथमिकता दें।

निर्णय नियम:

  • message_sending के साथ cancel: true अंतिम है।
  • message_sending के साथ cancel: false को कोई निर्णय नहीं माना जाता है।
  • पुनर्लिखित content निम्न-प्राथमिकता वाले हुक तक जारी रहता है, जब तक कि कोई बाद का हुक डिलीवरी रद्द न कर दे।
  • reply_payload_sending पेलोड सामान्यीकरण के बाद और चैनल डिलीवरी से पहले चलता है, जिसमें मूल चैनल पर वापस रूट किए गए उत्तर भी शामिल हैं। हैंडलर क्रमिक रूप से चलते हैं और प्रत्येक हैंडलर उच्च-प्राथमिकता वाले हैंडलरों द्वारा निर्मित नवीनतम पेलोड देखता है।
  • reply_payload_sending पेलोड trustedLocalMedia जैसे रनटाइम विश्वास चिह्न उजागर नहीं करते; प्लगइन पेलोड का आकार संपादित कर सकते हैं, लेकिन स्थानीय मीडिया को विश्वास प्रदान नहीं कर सकते।
  • message_sending रद्दीकरण के साथ cancelReason और सीमित metadata लौटा सकता है। नए संदेश जीवनचक्र API इसे कारण cancelled_by_message_sending_hook वाले दबाए गए डिलीवरी परिणाम के रूप में उजागर करते हैं; विरासती प्रत्यक्ष डिलीवरी संगतता के लिए खाली परिणाम सरणी लौटाती रहती है।
  • message_sent केवल अवलोकन के लिए है। हैंडलर विफलताएँ लॉग की जाती हैं और डिलीवरी परिणाम नहीं बदलतीं।

इंस्टॉल हुक

ऑपरेटर-स्वामित्व वाले अनुमति/अवरोध निर्णयों के लिए security.installPolicy का उपयोग करें। वह नीति OpenClaw कॉन्फ़िगरेशन से चलती है, CLI इंस्टॉल और अपडेट पथों को कवर करती है, और सक्षम लेकिन अनुपलब्ध होने पर सुरक्षित रूप से अवरोधित करती है।

before_install एक प्लगइन-रनटाइम जीवनचक्र हुक है। यह केवल उस OpenClaw प्रक्रिया में security.installPolicy के बाद चलता है जहाँ प्लगइन हुक पहले ही लोड किए जा चुके हों, जैसे Gateway-समर्थित इंस्टॉल प्रवाह। यह प्लगइन-स्वामित्व वाले अवलोकनों, चेतावनियों और संगतता जाँचों के लिए उपयोगी है, लेकिन इंस्टॉल के लिए प्राथमिक एंटरप्राइज़ या होस्ट सुरक्षा सीमा नहीं है। संगतता के लिए builtinScan फ़ील्ड इवेंट पेलोड में बना रहता है, लेकिन OpenClaw अब अंतर्निहित इंस्टॉल-समय खतरनाक-कोड अवरोधन नहीं चलाता, इसलिए यह एक खाली ok परिणाम है। उस प्रक्रिया में इंस्टॉल रोकने के लिए अतिरिक्त निष्कर्ष या { block: true, blockReason } लौटाएँ।

block: true अंतिम है। block: false को कोई निर्णय नहीं माना जाता है। हैंडलर विफलताएँ सुरक्षित रूप से इंस्टॉल को अवरोधित करती हैं।

Gateway जीवनचक्र

सामान्य प्लगइन सेवाएँ शुरू करने के लिए gateway_start और लंबे समय तक चलने वाले संसाधनों की सफ़ाई के लिए gateway_stop का उपयोग करें। जब gateway_start चलता है, तब भी Cron शेड्यूलर लोड हो रहा हो सकता है, इसलिए किसी बाहरी Cron प्रक्षेपण के आधारभूत संकेत के रूप में इसका उपयोग न करें।

प्लगइन-स्वामित्व वाली रनटाइम सेवाओं के लिए आंतरिक gateway:startup हुक पर निर्भर न रहें।

cron_reconciled Gateway Cron शेड्यूलर और उसके निकास-समय निगरानीकर्ताओं द्वारा अपनी टिकाऊ स्थिति का सामंजस्य करने के बाद सक्रिय होता है। यह प्रारंभिक स्टार्टअप और कॉन्फ़िगरेशन पुनः लोड के दौरान शेड्यूलर प्रतिस्थापन, दोनों के लिए सक्रिय होता है। इवेंट reason (startup या reload) और प्रभावी enabled स्थिति की रिपोर्ट करता है। अक्षम Cron भी enabled: false के साथ उत्सर्जित होता है, जिससे कोई बाहरी प्रक्षेपण पुराने वेक साफ़ कर सकता है। सामंजस्य पूरा करने वाले सटीक शेड्यूलर इंस्टेंस के लिए ctx.getCron?.() का उपयोग करें; बाद का पुनः लोड उस कॉलबैक को पुनर्लक्षित नहीं करता। ctx.abortSignal उसी शेड्यूलर स्नैपशॉट का स्वामी है। जैसे ही कोई नया शेड्यूलर सक्रिय होता है या शटडाउन शुरू होता है, Gateway इसे निरस्त कर देता है। इसे प्रत्येक टिकाऊ दुष्प्रभाव तक पहुँचाएँ और इसके निरस्त होने के बाद स्नैपशॉट स्वीकार न करें। यह शेड्यूलर जीवनचक्र संकेत है, प्लगइन-सक्रियण संकेत नहीं: केवल-प्लगइन हॉट रीलोड इसे दोबारा नहीं चलाता। नया सक्षम हुआ उपभोक्ता अपना पहला आधारभूत मान अगले शेड्यूलर प्रतिस्थापन या Gateway प्रारंभ पर प्राप्त करता है।

अन्य अवलोकन हुक की तरह, gateway_start और cron_reconciled कॉलबैक एक-दूसरे से ओवरलैप हो सकते हैं। यदि दोनों हैंडलर प्लगइन आरंभीकरण साझा करते हैं, तो कॉलबैक क्रम पर निर्भर रहने के बजाय प्लगइन-स्थानीय तत्परता प्रॉमिस से उनका समन्वय करें।

cron_changed टाइप किए गए इवेंट पेलोड के साथ Gateway-स्वामित्व वाले Cron जीवनचक्र इवेंट के लिए सक्रिय होता है, जिसमें added, updated, removed, started, finished, और scheduled कारण शामिल हैं। इवेंट एक PluginHookGatewayCronJob स्नैपशॉट (उपस्थित होने पर state.nextRunAtMs, state.lastRunStatus, और state.lastError सहित) तथा not-requested | delivered | not-delivered | unknown का एक PluginHookGatewayCronDeliveryStatus वहन करता है। हटाए गए इवेंट कमिट-पश्चात होते हैं: वे केवल टिकाऊ विलोपन सफल होने के बाद सक्रिय होते हैं और फिर भी हटाए गए जॉब का स्नैपशॉट रखते हैं, ताकि बाहरी शेड्यूलर स्थिति का सामंजस्य कर सकें।

scheduled इवेंट कमिट-पश्चात है: यह केवल तब सक्रिय होता है जब कोई सफल टिकाऊ लेखन मौजूदा जॉब के प्रभावी nextRunAtMs को बदलता है, और उस जॉब के स्पष्ट added, updated, या removed जीवनचक्र इवेंट को छोड़ देता है। शीर्ष-स्तरीय event.nextRunAtMs कमिट किया गया अगला वेक है; इसके अनुपस्थित होने पर जॉब का कोई अगला वेक नहीं है। इन इवेंट को क्रमबद्ध डेल्टा लॉग नहीं, बल्कि सामंजस्य संकेत मानें। इन्हें एकत्र किए जा सकने वाले संकेतों की तरह उपयोग करके cron_reconciled द्वारा अंतिम बार कैप्चर किए गए शेड्यूलर को दोबारा पढ़ें; cron_changed संदर्भ से शेड्यूलर न अपनाएँ। देयता जाँच और निष्पादन के लिए OpenClaw को सत्य का स्रोत बनाए रखें।

सुरक्षित बाहरी Cron प्रक्षेपण

Cron इवेंट डेल्टा अग्रेषित करने के बजाय एक पूर्ण वेक स्नैपशॉट प्रक्षेपित करें। बाहरी अडैप्टर का replaceAll ऑपरेशन परमाण्विक और समशक्तिशाली होना चाहिए, और इसे केवल होस्ट द्वारा स्नैपशॉट को टिकाऊ रूप से स्वीकार करने के बाद ही पूर्ण होना चाहिए। इसे प्रदान किए गए निरस्तीकरण संकेत का भी पालन करना चाहिए: यदि टिकाऊ स्वीकृति से पहले संकेत निरस्त हो जाता है, तो अडैप्टर को वह स्नैपशॉट स्वीकार नहीं करना चाहिए।

यह पैटर्न नवीनतम स्थिति वाला केवल एक वर्कर सक्रिय रखता है। केवल cron_reconciled शेड्यूलर इंस्टेंस अपनाता है; cron_changed केवल उस वर्कर से प्रामाणिक इंस्टेंस को दोबारा पढ़ने का अनुरोध करता है, इसलिए देर से आया संकेत किसी पुराने शेड्यूलर को पुनर्स्थापित नहीं कर सकता। कोई नया संशोधन सक्रिय होस्ट प्रयास को पुराना स्नैपशॉट स्वीकार करने से पहले निरस्त कर देता है।

typescript
  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(`बाहरी Cron प्रक्षेपण विफल हुआ; ${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 सामंजस्य ने कोई शेड्यूलर उजागर नहीं किया");      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();  });}

जब cron_reconciled, enabled: false की रिपोर्ट करता है, तो वही पथ replaceAll([]) को कॉल करता है और पुराने बाहरी वेक साफ़ करता है। इस उदाहरण में पुनः प्रयास/बैकऑफ़ प्रक्रिया-स्थानीय है और रनटाइम अडैप्टर विफलताओं को अस्थायी मानता है; पंजीकरण से पहले ऐसे कॉन्फ़िगरेशन को सत्यापित करें जिन पर पुनः प्रयास नहीं किया जा सकता। OpenClaw प्लगइन हुक प्रभावों के लिए आउटबॉक्स प्रदान नहीं करता। यदि टिकाऊ स्वीकृति से पहले प्रक्रिया समाप्त हो जाती है, तो अगला Gateway प्रारंभ एक नया प्रामाणिक cron_reconciled स्नैपशॉट उत्सर्जित करता है। gateway_stop सक्रिय होस्ट कार्य को निरस्त करता है, वर्कर के स्थिर होने की प्रतीक्षा करता है, फिर अडैप्टर बंद करता है।

आगामी अप्रचलन

हुक से सटे कुछ सतह अप्रचलित हैं, लेकिन अभी भी समर्थित हैं। अगले प्रमुख रिलीज़ से पहले माइग्रेट करें:

  • प्लेनटेक्स्ट चैनल एनवेलप inbound_claim और message_received हैंडलर में। समतल एनवेलप टेक्स्ट को पार्स करने के बजाय BodyForAgent और संरचित उपयोगकर्ता-संदर्भ ब्लॉक पढ़ें। देखें प्लेनटेक्स्ट चैनल एनवेलप → BodyForAgent
  • subagent_spawning पुराने plugins के साथ संगतता के लिए बना हुआ है, लेकिन नए plugins को इससे थ्रेड रूटिंग वापस नहीं करनी चाहिए। Core, subagent_spawned के सक्रिय होने से पहले चैनल सेशन-बाइंडिंग अडैप्टर के माध्यम से thread: true सबएजेंट बाइंडिंग तैयार करता है।
  • deactivate 2026-08-16 के बाद तक अप्रचलित क्लीनअप संगतता उपनाम के रूप में बना हुआ है। नए plugins को gateway_stop का उपयोग करना चाहिए।
  • before_tool_call में onResolution अब मुक्त-रूप string के बजाय टाइप किए गए PluginApprovalResolution यूनियन (allow-once / allow-always / deny / timeout / cancelled) का उपयोग करता है।
  • api.registerSessionExtension / api.enqueueNextTurnInjection शीर्ष-स्तरीय संगतता उपनामों के रूप में बने हुए हैं। नए plugins को api.session.state.registerSessionExtension(...) और api.session.workflow.enqueueNextTurnInjection(...) का उपयोग करना चाहिए।

पूरी सूची—मेमोरी क्षमता पंजीकरण, प्रदाता थिंकिंग प्रोफ़ाइल, बाहरी प्रमाणीकरण प्रदाता, प्रदाता खोज प्रकार, टास्क रनटाइम एक्सेसर और command-authcommand-status नाम-परिवर्तन—के लिए Plugin SDK माइग्रेशन → सक्रिय अप्रचलन देखें।

संबंधित

Was this useful?
On this page

On this page