Building plugins

قلاب‌های Plugin

نقاط گسترش درون‌پردازشی برای Pluginهای OpenClaw هستند: اجراهای عامل، فراخوانی‌های ابزار، جریان پیام، چرخهٔ عمر نشست، مسیریابی زیرعامل، نصب‌ها یا راه‌اندازی Gateway را بررسی یا تغییر می‌دهند.

در عوض، برای یک اسکریپت کوچک نصب‌شده توسط اپراتور که به رویدادهای فرمان و Gateway مانند /new، /reset، /stop، agent:bootstrap یا gateway:startup واکنش نشان می‌دهد، از هوک‌های داخلی HOOK.md استفاده کنید.

شروع سریع

هوک‌های نوع‌دار را با api.on(...) از ورودی Plugin ثبت کنید:

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 بودجهٔ انتظار برای هر هوک. وقتی منقضی شود، OpenClaw انتظار برای آن کنترل‌گر را متوقف می‌کند و ادامه می‌دهد. این کار کنترل‌گر یا عوارض جانبی آن را لغو نمی‌کند. برای استفاده از مهلت پیش‌فرض اجراکننده برای هر هوک، آن را حذف کنید.

اپراتورها می‌توانند بدون وصله‌کردن کد Plugin، بودجهٔ هوک‌ها را تنظیم کنند:

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

hooks.timeouts.<hookName> بر hooks.timeoutMs اولویت دارد و آن نیز بر مقدار api.on(..., { timeoutMs }) تعریف‌شده توسط نویسندهٔ Plugin اولویت دارد. هر مقدار باید یک عدد صحیح مثبت حداکثر تا 600000 ms باشد. برای هوک‌هایی که کندبودنشان مشخص است، بازنویسی‌های مختص هر هوک را ترجیح دهید تا یک Plugin در همه‌جا بودجهٔ طولانی‌تری نگیرد.

وعدهٔ یک کنترل‌گر که مهلتش تمام شده است، به اجرا ادامه می‌دهد، زیرا فراخوان‌های هوک سیگنال لغو دریافت نمی‌کنند. ارسال هوک می‌تواند مجوز پذیرش Gateway خود را درحالی‌که کار آن Plugin همچنان در حال انجام است، آزاد کند. Pluginهایی که مالک کارهای طولانی‌مدت هستند باید چرخهٔ عمر لغو و خاموش‌سازی خود را فراهم کنند.

هوک‌های تغییردهندهٔ خروجی message_sending و reply_payload_sending برای هر کنترل‌گر به‌طور پیش‌فرض 15 ثانیه مهلت دارند. اگر مهلت یکی تمام شود، OpenClaw خطای Plugin را ثبت می‌کند و با آخرین بار داده ادامه می‌دهد تا مسیر تحویل سریالی بتواند خاتمه یابد. برای Pluginهایی که عمداً پیش از تحویل کار کندتری انجام می‌دهند، بودجهٔ بیشتری برای هر هوک تنظیم کنید.

Pluginهای کانال که از createReplyDispatcher استفاده می‌کنند نیز می‌توانند با beforeDeliverOptions: { timeoutMs } یا هنگام افزودن کار با dispatcher.appendBeforeDeliver(handler, { timeoutMs })، بودجهٔ مثبت بیشتری برای هر مرحله اعلام کنند. بدون بودجهٔ اعلام‌شده توسط مالک، آن فراخوان‌ها از همان مقدار پیش‌فرض 15 ثانیه استفاده می‌کنند تا یک فراخوان هنگ‌کرده نتواند مسیر تحویل سریالی را در اختیار نگه دارد.

هر هوک event.context.pluginConfig، یعنی پیکربندی حل‌شده برای Plugin ثبت‌کنندهٔ آن کنترل‌گر را دریافت می‌کند. OpenClaw آن را برای هر کنترل‌گر تزریق می‌کند، بدون اینکه شیء رویداد مشترکی را که سایر Pluginها می‌بینند تغییر دهد.

فهرست هوک‌ها

هوک‌ها بر اساس سطحی که گسترش می‌دهند گروه‌بندی شده‌اند. نام‌های پررنگ نتیجهٔ تصمیم (مسدودکردن، لغوکردن، بازنویسی یا الزام به تأیید) را می‌پذیرند؛ بقیه صرفاً نظارتی هستند.

نوبت عامل

هوک هدف
before_model_resolve بازنویسی ارائه‌دهنده یا مدل پیش از بارگذاری پیام‌های نشست
agent_turn_prepare مصرف تزریق‌های نوبتِ صف‌شدهٔ Plugin و افزودن زمینهٔ همان نوبت پیش از هوک‌های پرامپت
before_prompt_build افزودن زمینهٔ پویا یا متن پرامپت سیستم پیش از فراخوانی مدل
before_agent_run بررسی پرامپت نهایی و پیام‌های نشست پیش از ارسال به مدل؛ می‌تواند اجرا را مسدود کند
before_agent_reply پایان‌دادن زودهنگام نوبت مدل با پاسخی مصنوعی یا سکوت
before_agent_finalize بررسی پاسخ نهایی طبیعی و درخواست یک گذر دیگر مدل
agent_end مشاهدهٔ پیام‌های نهایی، وضعیت موفقیت و مدت اجرا
heartbeat_prompt_contribution افزودن زمینهٔ مختص Heartbeat برای Pluginهای پایش پس‌زمینه و چرخهٔ عمر

نظارت بر مکالمه

هوک هدف
model_call_started / model_call_ended فرادادهٔ پاک‌سازی‌شدهٔ فراخوانی ارائه‌دهنده/مدل: زمان‌بندی، نتیجه و هش‌های محدودشدهٔ شناسهٔ درخواست. بدون محتوای پرامپت یا پاسخ.
llm_input ورودی ارائه‌دهنده: پرامپت سیستم، پرامپت، تاریخچه
llm_output خروجی ارائه‌دهنده، میزان استفاده و contextTokenBudget حل‌شده در صورت وجود

ابزارها

هوک هدف
before_tool_call بازنویسی پارامترهای ابزار، مسدودکردن اجرا یا الزام به تأیید
after_tool_call مشاهدهٔ نتایج ابزار، خطاها و مدت‌زمان
resolve_exec_env افزودن متغیرهای محیطی تحت مالکیت Plugin به exec
tool_result_persist بازنویسی پیام دستیار تولیدشده از نتیجهٔ ابزار
before_message_write بررسی یا مسدودکردن نوشتن پیام در حال انجام (به‌ندرت)

پیام‌ها و تحویل

هوک هدف
inbound_claim در اختیار گرفتن پیام ورودی پیش از مسیریابی عامل (پاسخ‌های مصنوعی)
channel_pairing_requested مشاهدهٔ درخواست‌های جفت‌سازی DM که تازه ایجاد شده‌اند
message_received مشاهدهٔ محتوای ورودی، فرستنده، رشته و فراداده
message_sending بازنویسی محتوای خروجی یا لغو تحویل
reply_payload_sending تغییر یا لغو بارهای دادهٔ پاسخ نرمال‌شده پیش از تحویل
message_sent مشاهدهٔ موفقیت یا شکست تحویل خروجی
before_dispatch بررسی یا بازنویسی ارسال خروجی پیش از واگذاری به کانال
reply_dispatch مشارکت در پایپ‌لاین نهایی ارسال پاسخ

نشست‌ها و Compaction

هوک هدف
session_start / session_end پیگیری مرزهای چرخهٔ عمر نشست. reason یکی از new، reset، idle، daily، compaction، deleted، shutdown، restart یا unknown است. هنگامی‌که فرایند با نشست‌های فعال متوقف یا راه‌اندازی مجدد می‌شود، shutdown/restart از نهایی‌ساز خاموش‌سازی Gateway اجرا می‌شوند تا Pluginها (حافظه، مخازن رونوشت) بتوانند ردیف‌های سرگردان را نهایی کنند، به‌جای اینکه آن‌ها را در فاصلهٔ راه‌اندازی‌های مجدد باز باقی بگذارند. نهایی‌ساز محدودیت زمانی دارد تا یک Plugin کند نتواند SIGTERM/SIGINT را مسدود کند.
before_compaction / after_compaction مشاهده یا حاشیه‌نویسی چرخه‌های Compaction
before_reset مشاهدهٔ رویدادهای بازنشانی نشست (/reset، بازنشانی‌های برنامه‌ای)

برای فراخوانی‌های sessions.create با parentSessionKey و emitCommandHooks: true، یک فرزند متمایز همیشه session_start را دریافت می‌کند. فراخواننده‌ها با succeedsParent مشخص می‌کنند که آیا والد نیز session_end نهایی را دریافت می‌کند: true به‌معنای جانشین و false به‌معنای فرزند موازی است. حذف آن، رفتار قدیمی جابه‌جایی والد را حفظ می‌کند. هوک‌های command:new و before_reset همچنان در هر دو حالت، کنش درخواستی /new را توصیف می‌کنند.

زیرعامل‌ها

  • subagent_spawned / subagent_ended - راه‌اندازی و تکمیل زیرعامل را مشاهده می‌کند.
  • subagent_delivery_target - قلاب سازگاری برای تحویل تکمیل، هنگامی که هیچ اتصال نشست هسته‌ای نتواند مسیری را نگاشت کند.
  • subagent_spawning - قلاب سازگاری منسوخ‌شده. اکنون هسته پیش از فعال‌شدن subagent_spawned، اتصال‌های زیرعامل thread: true را از طریق آداپتورهای اتصال نشست کانال آماده می‌کند.
  • subagent_spawned زمانی شامل resolvedModel و resolvedProvider است که OpenClaw مدل بومی نشست فرزند را پیش از راه‌اندازی تعیین کرده باشد.
  • subagent_ended شامل targetSessionKey (هویت - مطابق با subagent_spawned.childSessionKeytargetKind ("subagent" یا "acp"reason، outcome اختیاری ("ok"، "error"، "timeout"، "killed"، "reset" یا "deleted"error اختیاری، runId، endedAt، accountId و sendFarewell است. این داده شامل agentId یا childSessionKey نیست؛ برای هم‌بسته‌سازی با رویداد متناظر subagent_spawned از targetSessionKey استفاده کنید.

چرخهٔ حیات

قلاب هدف
gateway_start / gateway_stop سرویس‌های تحت مالکیت Plugin را همراه با Gateway راه‌اندازی یا متوقف می‌کند
deactivate نام مستعار سازگاری منسوخ‌شده برای gateway_stop؛ در Pluginهای جدید از gateway_stop استفاده کنید
cron_reconciled پس از راه‌اندازی یا بارگذاری مجدد، وضعیت را با کل وضعیت Cron در Gateway تطبیق می‌دهد
cron_changed تغییرات چرخهٔ حیات Cron تحت مالکیت Gateway را مشاهده می‌کند (افزوده‌شده، به‌روزشده، حذف‌شده، آغازشده، پایان‌یافته، زمان‌بندی‌شده)
before_install مواد نصب مرحله‌بندی‌شدهٔ skill یا Plugin را از یک زمان‌اجرای Plugin بارگذاری‌شده بازرسی می‌کند

درخواست‌های جفت‌سازی کانال

وقتی Plugin باید پس از ایجاد یک درخواست جفت‌سازی در انتظار توسط فرستندهٔ پیام خصوصی جفت‌نشده، به اپراتور اطلاع دهد یا یک رکورد ممیزی بنویسد، از channel_pairing_requested استفاده کنید. این قلاب هنگام ایجاد درخواست فراخوانی می‌شود؛ تحویل پاسخ جفت‌سازی در کانال به‌دلیل کندی یا خرابی مدیریت‌کننده‌های قلاب به تأخیر نمی‌افتد.

typescript
api.on("channel_pairing_requested", async (event) => {  await notifyOperator({    text: `درخواست جفت‌سازی جدید ${event.channel} از ${event.senderId}: ${event.code}`,  });});

این قلاب فقط برای مشاهده است. پاسخ جفت‌سازی را تأیید، رد، سرکوب یا بازنویسی نمی‌کند. بار داده شامل کانال، accountId اختیاری، senderId در محدودهٔ کانال، code جفت‌سازی و فرادادهٔ کانال است. کد جفت‌سازی را یک اعتبارنامهٔ تأیید زنده و یک‌بارمصرف در نظر بگیرید و آن را فقط به یک مقصد اپراتور مورداعتماد تحویل دهید. metadata را متن هویت نامطمئنِ ارائه‌شده توسط فرستنده در نظر بگیرید. این قلاب شامل بدنه یا رسانهٔ پیام ورودی نیست.

قلاب‌های زمان‌اجرای اشکال‌زدایی

برای تغییر ارائه‌دهنده یا مدل در یک نوبت عامل از before_model_resolve استفاده کنید؛ این قلاب پیش از تعیین مدل اجرا می‌شود. llm_output تنها پس از آن اجرا می‌شود که یک تلاش مدل خروجی دستیار تولید کند.

برای اثبات مدل مؤثر نشست، ثبت‌های زمان‌اجرا را بررسی کنید، سپس از openclaw sessions یا سطوح نشست/وضعیت Gateway استفاده کنید. برای اشکال‌زدایی بارهای دادهٔ ارائه‌دهنده، Gateway را با --raw-stream و --raw-stream-path <path> راه‌اندازی کنید تا رویدادهای خام جریان مدل در یک فایل jsonl نوشته شوند.

خط‌مشی فراخوانی ابزار

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 را تأیید کند. در رله‌های بومی PreToolUse در حالت گزارش app-server مربوط به Codex، این کار به درخواست تأیید متناظر 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 یک guild یا کانال می‌تواند مخاطبان نگه‌داری را مجاز کند. کانال‌های دیگر می‌توانند از شناسه‌های پایدار فرستنده استفاده کنند. سپس قلاب تصمیم دقیق‌تر برای هر ابزار را در هر فراخوانی ابزار در اجرا اعمال می‌کند، از جمله فراخوانی‌های بومی PreToolUse در Codex. این قلاب می‌تواند ابزاری را که مدل می‌بیند رد کند، اما نمی‌تواند ابزاری را که میزبان حذف کرده است اضافه کند. خط‌مشی‌های موجودِ sandbox، تأیید exec، ابزارهای هسته‌ای مختص مالک و کانال همچنان اعمال می‌شوند؛ قلاب نمی‌تواند از آن‌ها فراتر مجوز اعطا کند.

همان‌گونه که نشان داده شده است، شناسه‌های فرستنده و نقش را به یک جفت دقیق کانال/حساب محدود کنید؛ هر دو فضای نام محلی ارائه‌دهنده هستند. فهرست‌های مجاز را محافظه‌کارانه نگه دارید. ابزارهای نوشتن یا اجرا را تنها زمانی اضافه کنید که sandbox و خط‌مشی تأیید استقرار، این کار را ایمن می‌کنند. برای اجراهای خودکار یا سیستمی، صریحاً تصمیم بگیرید که آیا نبود ctx.requester باید پذیرفته شود؛ نمونه آن را برای عامل محدودشده رد می‌کند.

برای مسیریابی تأیید، رفتار تصمیم و زمان استفاده از requireApproval به‌جای ابزارهای اختیاری یا تأییدهای exec، درخواست‌های مجوز Plugin را ببینید.

Pluginهایی که به خط‌مشی در سطح میزبان نیاز دارند می‌توانند خط‌مشی‌های ابزار مورداعتماد را با api.registerTrustedToolPolicy(...) ثبت کنند. این موارد پیش از قلاب‌های عادی before_tool_call و پیش از تصمیم‌های عادی قلاب اجرا می‌شوند. خط‌مشی‌های مورداعتماد همراه‌شده ابتدا اجرا می‌شوند؛ خط‌مشی‌های مورداعتماد Pluginهای نصب‌شده سپس به‌ترتیب بارگذاری Plugin اجرا می‌شوند؛ قلاب‌های عادی before_tool_call پس از آن‌ها اجرا می‌شوند. Pluginهای همراه‌شده مسیر خط‌مشی مورداعتماد موجود را حفظ می‌کنند. Pluginهای نصب‌شده باید صریحاً فعال شوند و هر شناسهٔ خط‌مشی را در contracts.trustedToolPolicies اعلام کنند؛ شناسه‌های اعلام‌نشده پیش از ثبت رد می‌شوند. شناسه‌های خط‌مشی به Plugin ثبت‌کننده محدودند، بنابراین Pluginهای مختلف می‌توانند از یک شناسهٔ محلی یکسان استفاده کنند. از این سطح فقط برای دروازه‌های مورداعتماد میزبان مانند خط‌مشی فضای کاری، اعمال بودجه یا ایمنی گردش‌کارهای رزروشده استفاده کنید.

هوک محیط اجرا

resolve_exec_env به Pluginها اجازه می‌دهد پیش از اجرای فرمان، متغیرهای محیطی را به فراخوانی‌های ابزار exec اضافه کنند. این هوک موارد زیر را دریافت می‌کند:

  • event.sessionKey
  • event.toolName، که در حال حاضر همیشه "exec" است
  • event.host، یکی از "gateway"، "sandbox" یا "node"
  • فیلدهای زمینه مانند ctx.agentId، ctx.sessionKey، ctx.messageProvider و ctx.channelId

برای ادغام در محیط اجرا، یک Record<string, string> برگردانید. کنترل‌گرها به‌ترتیب اولویت اجرا می‌شوند؛ برای کلید یکسان، نتایج بعدی نتایج قبلی را بازنویسی می‌کنند.

خروجی هوک پیش از ادغام، با سیاست کلیدهای محیط اجرای میزبان پالایش می‌شود. PATH همیشه حذف می‌شود (تفکیک فرمان و بررسی‌های باینری امن به آن وابسته‌اند). کلیدهای نامعتبر و کلیدهای خطرناک بازنویسی میزبان مانند LD_*، DYLD_*، NODE_OPTIONS، متغیرهای پراکسی (HTTP_PROXY، HTTPS_PROXY، ALL_PROXY، NO_PROXY) و متغیرهای بازنویسی TLS (NODE_TLS_REJECT_UNAUTHORIZED، SSL_CERT_FILE و موارد مشابه) حذف می‌شوند. محیط پالایش‌شدهٔ Plugin در فرادادهٔ تأیید/ممیزی Gateway گنجانده و به درخواست‌های اجرا در میزبان Node ارسال می‌شود.

ماندگاری نتایج ابزار

نتایج ابزار می‌توانند شامل details ساخت‌یافته برای رندر رابط کاربری، عیب‌یابی، مسیریابی رسانه یا فرادادهٔ تحت مالکیت Plugin باشند. با details به‌عنوان فرادادهٔ زمان اجرا رفتار کنید، نه محتوای پرامپت:

  • OpenClaw پیش از بازپخش برای ارائه‌دهنده و ورودی Compaction، toolResult.details را حذف می‌کند تا فراداده به زمینهٔ مدل تبدیل نشود.
  • ورودی‌های نشستِ ماندگار فقط details محدودشده را نگه می‌دارند. جزئیات بیش‌ازحد بزرگ با خلاصه‌ای فشرده و persistedDetailsTruncated: true جایگزین می‌شوند.
  • tool_result_persist و before_message_write پیش از سقف نهایی ماندگاری اجرا می‌شوند. details بازگشتی را کوچک نگه دارید و متن مرتبط با پرامپت را فقط در details قرار ندهید؛ خروجی ابزارِ قابل مشاهده برای مدل را در content قرار دهید.

هوک‌های پرامپت و مدل

برای Pluginهای جدید از هوک‌های مختص هر مرحله استفاده کنید:

  • before_model_resolve: فقط پرامپت فعلی و فرادادهٔ پیوست را دریافت می‌کند. providerOverride یا modelOverride را برگردانید.
  • agent_turn_prepare: پرامپت فعلی، پیام‌های آماده‌شدهٔ نشست و هر تزریق صف‌شدهٔ دقیقاً یک‌باره‌ای را که برای این نشست تخلیه شده است دریافت می‌کند. 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 پشتیبانی می‌شوند؛ شکل‌های تصمیم پشتیبانی‌نشده به‌صورت امن بسته شکست می‌خورند.

وقتی اجرایی مسدود می‌شود، OpenClaw فقط متن جایگزین را در message.content، به‌همراه فرادادهٔ غیرحساس مسدودسازی مانند شناسهٔ Plugin مسدودکننده و برچسب زمانی، ذخیره می‌کند. متن اصلی کاربر در رونوشت یا زمینهٔ آینده نگهداری نمی‌شود. دلایل داخلی مسدودسازی حساس تلقی می‌شوند و از محموله‌های رونوشت، تاریخچه، پخش، گزارش و عیب‌یابی کنار گذاشته می‌شوند. مشاهده‌پذیری باید از فیلدهای پاک‌سازی‌شده مانند شناسهٔ مسدودکننده، نتیجه، برچسب زمانی یا یک دسته‌بندی امن استفاده کند.

هوک‌های نوبت عامل، از جمله agent_end، زمانی که OpenClaw بتواند اجرای فعال را شناسایی کند شامل event.runId هستند؛ همین مقدار روی ctx.runId نیز قرار دارد. اجراهای هدایت‌شده با Cron همچنین ctx.jobId (شناسهٔ کار Cron مبدأ) را در زمینهٔ نوبت عامل ارائه می‌کنند تا هوک‌ها بتوانند سنجه‌ها، اثرات جانبی یا وضعیت را به یک کار زمان‌بندی‌شدهٔ مشخص محدود کنند. ctx.jobId بخشی از زمینهٔ ابزار before_tool_call نیست.

برای اجراهایی که از کانال منشأ می‌گیرند، ctx.channel و ctx.messageProvider سطح ارائه‌دهنده مانند discord یا telegram را مشخص می‌کنند، درحالی‌که ctx.channelId شناسهٔ مقصد گفت‌وگو است، اگر OpenClaw بتواند آن را از کلید نشست یا فرادادهٔ تحویل استخراج کند.

وقتی هویت فرستنده در دسترس باشد، زمینه‌های هوک عامل شامل موارد زیر نیز می‌شوند:

  • ctx.senderId - شناسهٔ فرستنده در محدودهٔ کانال (برای مثال open_id در Feishu، شناسهٔ کاربر Discord). زمانی مقداردهی می‌شود که اجرا از پیام کاربری با فرادادهٔ فرستندهٔ شناخته‌شده منشأ بگیرد.
  • ctx.chatId - شناسهٔ بومی گفت‌وگوی انتقال (برای مثال chat_id در Feishu، chat_id در Telegram). زمانی مقداردهی می‌شود که کانال مبدأ یک شناسهٔ بومی گفت‌وگو ارائه کند.
  • ctx.channelContext.sender.id - همان شناسهٔ فرستندهٔ ctx.senderId، درون یک شیء تحت مالکیت کانال که Pluginها می‌توانند آن را با فیلدهای مختص کانال گسترش دهند.
  • ctx.channelContext.chat.id - همان شناسهٔ گفت‌وگوی ctx.chatId، درون یک شیء تحت مالکیت کانال که Pluginها می‌توانند آن را با فیلدهای مختص کانال گسترش دهند.

هسته فقط فیلدهای تو‌در‌توی id را تعریف می‌کند. Pluginهای کانالی که فرادادهٔ غنی‌تر فرستنده یا گفت‌وگو را از طریق کمک‌تابع ورودی عبور می‌دهند، می‌توانند PluginHookChannelSenderContext یا PluginHookChannelChatContext را از openclaw/plugin-sdk/channel-inbound گسترش دهند:

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

Pluginهای کانال این فیلدها را از طریق کمک‌تابع SDK ورودی عبور می‌دهند:

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

این فیلدها اختیاری‌اند و برای اجراهایی با منشأ سیستم (Heartbeat، Cron، رویداد اجرا) وجود ندارند.

ctx.senderExternalId برای Pluginهای قدیمی‌تر به‌عنوان فیلد منسوخ‌شدهٔ سازگاری منبع باقی می‌ماند. هسته آن را مقداردهی نمی‌کند؛ هویت‌های جدید فرستندهٔ مختص کانال باید از طریق گسترش ماژول زیر ctx.channelContext.sender قرار گیرند.

agent_end یک هوک مشاهده است. مسیرهای Gateway و هارنس ماندگار آن را پس از نوبت به‌صورت اجرا و فراموش اجرا می‌کنند، درحالی‌که مسیرهای کوتاه‌عمر و تک‌اجرای CLI پیش از پاک‌سازی فرایند منتظر Promise هوک می‌مانند تا Pluginهای مورد اعتماد بتوانند مشاهده‌پذیری پایانی را تخلیه یا وضعیت را ثبت کنند. اجراکنندهٔ هوک یک مهلت زمانی 30 ثانیه‌ای اعمال می‌کند تا یک Plugin گیرکرده یا نقطهٔ پایانی تعبیه‌سازی نتواند Promise هوک را برای همیشه در حالت انتظار نگه دارد. پایان مهلت ثبت می‌شود و OpenClaw ادامه می‌دهد؛ کار شبکه‌ای تحت مالکیت Plugin لغو نمی‌شود، مگر اینکه Plugin از سیگنال لغو خودش نیز استفاده کند.

از model_call_started و model_call_ended برای تله‌متری فراخوانی ارائه‌دهنده استفاده کنید که نباید پرامپت‌ها، تاریخچه، پاسخ‌ها، سرآیندها، بدنهٔ درخواست یا شناسه‌های درخواست ارائه‌دهندهٔ خام را دریافت کند. این هوک‌ها شامل فرادادهٔ پایدار مانند runId، callId، provider، model، api/transport اختیاری، مقادیر پایانی durationMs/outcome و، زمانی که OpenClaw بتواند هش محدودشدهٔ شناسهٔ درخواست ارائه‌دهنده را استخراج کند، upstreamRequestIdHash هستند. وقتی زمان اجرا فرادادهٔ پنجرهٔ زمینه را تفکیک کرده باشد، رویداد و زمینهٔ هوک همچنین شامل contextTokenBudget، یعنی بودجهٔ مؤثر توکن پس از سقف‌های مدل/پیکربندی/عامل، به‌علاوهٔ contextWindowSource و contextWindowReferenceTokens، زمانی که سقف پایین‌تری اعمال شده باشد، هستند.

before_agent_finalize فقط زمانی اجرا می‌شود که هارنس در آستانهٔ پذیرش یک پاسخ نهایی طبیعی از دستیار باشد. این مسیر لغو /stop نیست و هنگام لغو یک نوبت توسط کاربر اجرا نمی‌شود. برای درخواست یک گذر دیگر مدل از هارنس پیش از نهایی‌سازی، { action: "revise", reason }، برای اجبار نهایی‌سازی { action: "finalize", reason? } را برگردانید، یا برای ادامه نتیجه‌ای برنگردانید. کنترل‌گرها به‌طور پیش‌فرض بودجهٔ 15s دارند؛ در پایان مهلت، OpenClaw شکست را ثبت می‌کند و با پاسخ نهایی اصلی ادامه می‌دهد. هوک‌های بومی Stop در Codex به این هوک به‌صورت تصمیم‌های before_agent_finalize در OpenClaw رله می‌شوند.

هنگام برگرداندن action: "revise"، Pluginها می‌توانند فرادادهٔ retry را برای محدود و ایمن‌کردن گذر اضافی مدل در برابر بازپخش اضافه کنند:

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

instruction به دلیل بازبینی ارسال‌شده به هارنس افزوده می‌شود. idempotencyKey به میزبان اجازه می‌دهد تلاش‌های مجدد برای درخواست یکسان Plugin را در میان تصمیم‌های نهایی‌سازی معادل بشمارد و maxAttempts تعداد گذرهای اضافی مجاز میزبان را پیش از ادامه با پاسخ نهایی طبیعی محدود می‌کند.

Pluginهای غیرباندل‌شده‌ای که به هوک‌های خام گفت‌وگو (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 غیرفعال کرد.

افزونه‌های نشست و تزریق‌های نوبت بعدی

Pluginهای گردش‌کار می‌توانند وضعیت کوچک و سازگار با JSON نشست را با api.session.state.registerSessionExtension(...) ماندگار کنند و آن را از طریق روش sessions.pluginPatch در Gateway به‌روزرسانی کنند. ردیف‌های نشست، وضعیت افزونهٔ ثبت‌شده را از طریق pluginExtensions نمایش می‌دهند و به Control UI و دیگر کلاینت‌ها اجازه می‌دهند وضعیت تحت مالکیت Plugin را بدون آگاهی از جزئیات داخلی Plugin رندر کنند. api.registerSessionExtension(...) همچنان کار می‌کند، اما به‌نفع فضای نام api.session.state منسوخ شده است.

وقتی یک Plugin به زمینه‌ای ماندگار نیاز دارد که دقیقاً یک‌بار به نوبت بعدی مدل برسد، از api.session.workflow.enqueueNextTurnInjection(...) استفاده کنید (api.enqueueNextTurnInjection(...) سطح‌بالا یک نام مستعار منسوخ‌شده با همان رفتار است). OpenClaw تزریق‌های صف‌شده را پیش از هوک‌های پرامپت تخلیه می‌کند، تزریق‌های منقضی‌شده را کنار می‌گذارد و برای هر Plugin بر اساس idempotencyKey موارد تکراری را حذف می‌کند. این درگاه مناسبی برای ازسرگیری پس از تأیید، خلاصه‌های سیاست، تغییرات پایشگر پس‌زمینه و ادامهٔ فرمان‌هایی است که باید در نوبت بعدی برای مدل قابل مشاهده باشند، اما نباید به متن دائمی پرامپت سیستم تبدیل شوند.

معنای پاک‌سازی بخشی از قرارداد است. فراخوان‌های بازگشتی پاک‌سازی افزونهٔ نشست و چرخهٔ عمر زمان اجرا، reset، delete، disable یا restart را دریافت می‌کنند. میزبان برای بازنشانی/حذف/غیرفعال‌سازی، وضعیت افزونهٔ نشست ماندگار و تزریق‌های در انتظار نوبت بعدیِ Plugin مالک را حذف می‌کند؛ راه‌اندازی مجدد وضعیت ماندگار نشست را حفظ می‌کند، درحالی‌که فراخوان‌های بازگشتی پاک‌سازی به Pluginها اجازه می‌دهند کارهای زمان‌بند، زمینهٔ اجرا و دیگر منابع خارج از باند را برای نسل قدیمی زمان اجرا آزاد کنند.

هوک‌های پیام

از هوک‌های پیام برای سیاست مسیریابی و تحویل در سطح کانال استفاده کنید:

  • message_received: محتوای ورودی، فرستنده، threadId، messageId، senderId، هم‌بستگی اختیاری اجرا/نشست، media مرتب‌شده و فراداده را مشاهده می‌کند.
  • 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 را ترجیح دهید.

رویدادهای ادعای ورودی و دریافت پیام، media?: PluginHookMediaFact[] را به‌عنوان API متعارف پیوست ارائه می‌کنند. هر واقعیت می‌تواند شامل path، url، contentType، kind، transcribed، messageId و workspaceDir باشد؛ موقعیت در آرایه، هویت پیوست است. هنگامی که یک پیوست راه‌دور هنوز به‌صورت محلی آماده‌سازی نشده است، media حذف می‌شود، mediaStagingPending: true و originalMedia شامل واقعیت‌های سمت ارائه‌دهنده است. تا زمانی که رویداد آماده‌سازی‌شده بعدی media را ارائه نکرده است، originalMedia.path را به‌عنوان داده‌ای قابل خواندن در محل در نظر نگیرید.

ویژگی‌های فراداده مفرد/جمع mediaPath، mediaUrl، mediaType، mediaPaths، mediaUrls، mediaTypes و originalMedia* متناظر، نام‌های مستعار سازگاری منسوخ‌شده‌اند. هوک‌های جدید باید از آرایه‌های سطح بالای نوع‌دار استفاده کنند.

قواعد تصمیم‌گیری:

  • message_sending همراه با cancel: true نهایی است.
  • message_sending همراه با cancel: false به‌عنوان نبود تصمیم در نظر گرفته می‌شود.
  • content بازنویسی‌شده به هوک‌های با اولویت پایین‌تر ادامه می‌یابد، مگر اینکه هوکی بعدی تحویل را لغو کند.
  • reply_payload_sending پس از عادی‌سازی بار داده و پیش از تحویل به کانال اجرا می‌شود، از جمله پاسخ‌هایی که به کانال مبدأ بازگردانده می‌شوند. گرداننده‌ها به‌ترتیب اجرا می‌شوند و هر گرداننده آخرین بار داده تولیدشده توسط گرداننده‌های با اولویت بالاتر را می‌بیند.
  • بارهای داده reply_payload_sending نشانگرهای اعتماد زمان اجرا مانند trustedLocalMedia را ارائه نمی‌کنند؛ Pluginها می‌توانند شکل بار داده را ویرایش کنند، اما نمی‌توانند به رسانه محلی اعتماد اعطا کنند.
  • message_sending می‌تواند همراه با یک لغو، cancelReason و metadata محدودشده را برگرداند. APIهای جدید چرخه عمر پیام، این وضعیت را به‌صورت یک نتیجه تحویل سرکوب‌شده با دلیل cancelled_by_message_sending_hook ارائه می‌کنند؛ تحویل مستقیم قدیمی برای حفظ سازگاری همچنان یک آرایه نتیجه خالی برمی‌گرداند.
  • message_sent فقط برای مشاهده است. خطاهای گرداننده ثبت می‌شوند و نتیجه تحویل را تغییر نمی‌دهند.

نصب هوک‌ها

برای تصمیم‌های مجازسازی/مسدودسازی تحت مالکیت اپراتور از security.installPolicy استفاده کنید. این سیاست از پیکربندی OpenClaw اجرا می‌شود، مسیرهای نصب و به‌روزرسانی CLI را پوشش می‌دهد و هنگامی که فعال اما در دسترس نباشد، به‌صورت بسته و امن شکست می‌خورد.

before_install یک هوک چرخه عمر زمان اجرای Plugin است. این هوک تنها در فرایند OpenClaw که هوک‌های Plugin از پیش در آن بارگذاری شده‌اند، مانند جریان‌های نصب مبتنی بر Gateway، پس از security.installPolicy اجرا می‌شود. این هوک برای مشاهدات، هشدارها و بررسی‌های سازگاری تحت مالکیت Plugin مفید است، اما مرز امنیتی اصلی سازمانی یا میزبان برای نصب‌ها نیست. فیلد builtinScan برای سازگاری در بار داده رویداد باقی می‌ماند، اما OpenClaw دیگر مسدودسازی داخلی کد خطرناک هنگام نصب را اجرا نمی‌کند؛ بنابراین این فیلد یک نتیجه خالی ok است. برای توقف نصب در آن فرایند، یافته‌های اضافی یا { block: true, blockReason } را برگردانید.

block: true نهایی است. block: false به‌عنوان نبود تصمیم در نظر گرفته می‌شود. خطاهای گرداننده، نصب را به‌صورت بسته و امن مسدود می‌کنند.

چرخه عمر Gateway

برای راه‌اندازی سرویس‌های عمومی Plugin از gateway_start و برای پاک‌سازی منابع بلندمدت از gateway_stop استفاده کنید. زمان اجرای gateway_start ممکن است زمان‌بند cron همچنان در حال بارگذاری باشد؛ بنابراین از آن به‌عنوان سیگنال مبنا برای یک تصویرسازی خارجی cron استفاده نکنید.

برای سرویس‌های زمان اجرای تحت مالکیت Plugin به هوک داخلی gateway:startup متکی نباشید.

cron_reconciled پس از آن فعال می‌شود که زمان‌بند cron متعلق به Gateway و ناظرهای هنگام خروج آن، وضعیت ماندگار خود را تطبیق داده باشند. این هوک هم برای راه‌اندازی اولیه و هم برای جایگزینی زمان‌بند هنگام بارگذاری مجدد پیکربندی فعال می‌شود. رویداد، reason (startup یا reload) و وضعیت مؤثر enabled را گزارش می‌کند. cron غیرفعال همچنان با enabled: false رویداد منتشر می‌کند و به یک تصویرسازی خارجی اجازه می‌دهد بیدارسازی‌های منسوخ را پاک کند. برای نمونه دقیق زمان‌بندی که تطبیق را تکمیل کرده است از ctx.getCron?.() استفاده کنید؛ بارگذاری مجدد بعدی، آن فراخوانی را به نمونه دیگری هدایت نمی‌کند. ctx.abortSignal مالک همان تصویر لحظه‌ای زمان‌بند است. Gateway به‌محض آماده‌شدن زمان‌بندی جدیدتر یا آغاز خاموش‌سازی، آن را لغو می‌کند. آن را از تمام اثرات جانبی ماندگار عبور دهید و پس از لغوشدن آن، تصویر لحظه‌ای را نپذیرید. این یک سیگنال چرخه عمر زمان‌بند است، نه سیگنال فعال‌سازی Plugin: بارگذاری مجددی که فقط مربوط به Plugin باشد، آن را دوباره اجرا نمی‌کند. مصرف‌کننده‌ای که به‌تازگی فعال شده است، نخستین مبنای خود را هنگام جایگزینی بعدی زمان‌بند یا شروع Gateway دریافت می‌کند.

مانند دیگر هوک‌های مشاهده، فراخوانی‌های gateway_start و cron_reconciled می‌توانند هم‌پوشانی داشته باشند. اگر هر دو گرداننده مقداردهی اولیه Plugin مشترکی دارند، آن‌ها را به‌جای اتکا به ترتیب فراخوانی، با یک promise آمادگی محلی Plugin هماهنگ کنید.

cron_changed برای رویدادهای چرخه عمر cron متعلق به Gateway با یک بار داده رویداد نوع‌دار شامل دلایل added، updated، removed، started، finished و scheduled فعال می‌شود. رویداد یک تصویر لحظه‌ای PluginHookGatewayCronJob (شامل state.nextRunAtMs، state.lastRunStatus و state.lastError در صورت وجود) به‌همراه یک PluginHookGatewayCronDeliveryStatus از not-requested | delivered | not-delivered | unknown حمل می‌کند. رویدادهای حذف‌شده پس از ثبت هستند: تنها پس از موفقیت حذف ماندگار فعال می‌شوند و همچنان تصویر لحظه‌ای کار حذف‌شده را حمل می‌کنند تا زمان‌بندهای خارجی بتوانند وضعیت را تطبیق دهند.

رویداد scheduled پس از ثبت است: تنها پس از آن فعال می‌شود که یک نوشتن ماندگار موفق، nextRunAtMs مؤثر یک کار موجود را تغییر دهد، به‌استثنای رویداد صریح چرخه عمر added، updated یا removed همان کار. event.nextRunAtMs سطح بالا بیدارسازی بعدی ثبت‌شده است؛ در صورت نبود آن، کار بیدارسازی بعدی ندارد. این رویدادها را سرنخ‌های تطبیق در نظر بگیرید، نه یک گزارش ترتیبی از تغییرات. از آن‌ها به‌عنوان سرنخ‌های قابل ادغام برای بازخوانی آخرین زمان‌بندی استفاده کنید که توسط cron_reconciled ثبت شده است؛ زمان‌بند را از یک زمینه cron_changed نپذیرید. OpenClaw را به‌عنوان منبع حقیقت برای بررسی‌های موعد و اجرا نگه دارید.

تصویرسازی امن خارجی cron

به‌جای ارسال تغییرات رویداد cron، یک تصویر لحظه‌ای کامل از بیدارسازی‌ها را تصویرسازی کنید. عملیات replaceAll آداپتور خارجی باید اتمی و idempotent باشد و تنها پس از پذیرش ماندگار تصویر لحظه‌ای توسط میزبان تکمیل شود. همچنین باید سیگنال لغو ارائه‌شده را رعایت کند: اگر سیگنال پیش از پذیرش ماندگار لغو شود، آداپتور نباید آن تصویر لحظه‌ای را بپذیرد.

این الگو تنها یک عامل اجرایی آخرین‌وضعیت را در حال اجرا نگه می‌دارد. فقط 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(`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();  });}

هنگامی که cron_reconciled مقدار enabled: false را گزارش می‌کند، همان مسیر replaceAll([]) را فراخوانی کرده و بیدارسازی‌های خارجی منسوخ را پاک می‌کند. تلاش مجدد/عقب‌نشینی در این مثال محلیِ فرایند است و خرابی‌های آداپتور زمان اجرا را گذرا در نظر می‌گیرد؛ پیکربندی غیرقابل تلاش مجدد را پیش از ثبت اعتبارسنجی کنید. OpenClaw برای اثرات هوک Plugin صندوق خروجی ارائه نمی‌کند. اگر فرایند پیش از پذیرش ماندگار خاتمه یابد، شروع بعدی Gateway یک تصویر لحظه‌ای معتبر جدید cron_reconciled منتشر می‌کند. gateway_stop کار در حال اجرای میزبان را لغو می‌کند، منتظر می‌ماند عامل اجرایی به پایان برسد و سپس آداپتور را می‌بندد.

منسوخ‌سازی‌های پیش رو

چند سطح مرتبط با هوک منسوخ شده‌اند، اما همچنان پشتیبانی می‌شوند. پیش از نسخه اصلی بعدی مهاجرت کنید:

  • پوشش‌های متنی سادهٔ کانال در گرداننده‌های inbound_claim و message_received. به‌جای تجزیهٔ متن مسطح پوشش، BodyForAgent و بلوک‌های ساخت‌یافتهٔ زمینهٔ کاربر را بخوانید. ببینید: پوشش‌های متنی سادهٔ کانال ← BodyForAgent.
  • subagent_spawning برای سازگاری با Pluginهای قدیمی‌تر باقی می‌ماند، اما Pluginهای جدید نباید مسیریابی رشته را از آن برگردانند. هسته، اتصال‌های زیرعامل thread: true را پیش از فعال‌شدن subagent_spawned، از طریق آداپتورهای اتصال نشست کانال آماده می‌کند.
  • deactivate تا پس از 2026-08-16 به‌عنوان نام مستعار منسوخ‌شدهٔ سازگاری برای پاک‌سازی باقی می‌ماند. Pluginهای جدید باید از gateway_stop استفاده کنند.
  • onResolution در before_tool_call اکنون به‌جای یک string آزاد، از اجتماع نوع‌دار PluginApprovalResolution (allow-once / allow-always / deny / timeout / cancelled) استفاده می‌کند.
  • api.registerSessionExtension / api.enqueueNextTurnInjection به‌عنوان نام‌های مستعار سازگاری سطح‌بالا باقی می‌مانند. Pluginهای جدید باید از api.session.state.registerSessionExtension(...) و api.session.workflow.enqueueNextTurnInjection(...) استفاده کنند.

برای فهرست کامل — ثبت قابلیت حافظه، پروفایل تفکر ارائه‌دهنده، ارائه‌دهندگان احراز هویت خارجی، انواع کشف ارائه‌دهنده، دسترسی‌دهنده‌های زمان اجرای وظیفه و تغییر نام command-authcommand-status — ببینید: مهاجرت SDK مربوط به Plugin ← موارد منسوخ‌شدهٔ فعال.

مرتبط

Was this useful?
On this page

On this page