Building plugins
قلابهای Plugin
نقاط گسترش درونپردازشی برای Pluginهای OpenClaw هستند: اجراهای عامل، فراخوانیهای ابزار، جریان پیام، چرخهٔ عمر نشست، مسیریابی زیرعامل، نصبها یا راهاندازی Gateway را بررسی یا تغییر میدهند.
در عوض، برای یک اسکریپت کوچک نصبشده توسط اپراتور که به رویدادهای فرمان و Gateway مانند /new،
/reset، /stop، agent:bootstrap یا gateway:startup واکنش نشان میدهد، از هوکهای داخلی HOOK.md استفاده کنید.
شروع سریع
هوکهای نوعدار را با api.on(...) از ورودی Plugin ثبت کنید:
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، بودجهٔ هوکها را تنظیم کنند:
{ "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.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 |
سرویسهای تحت مالکیت 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 استفاده کنید.
این قلاب هنگام ایجاد درخواست فراخوانی میشود؛ تحویل پاسخ جفتسازی در کانال
بهدلیل کندی یا خرابی مدیریتکنندههای قلاب به تأخیر نمیافتد.
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.toolNameevent.paramsevent.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بومی ارائهدهنده باشد. فیلدهای مفقود اثباتنشدهاند، نه تضمینهای منفی؛ هرگاه خطمشی آنها را الزامی میداند، با رویکرد بسته و امن عمل کنید.
این قلاب میتواند موارد زیر را بازگرداند:
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 را در اختیار فرستندگانی قرار میدهد که از قبل توسط پیکربندی کانال مجاز شدهاند:
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 را دوباره راهاندازی کنید:
{ 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.sessionKeyevent.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 گسترش دهند:
declare module "openclaw/plugin-sdk/channel-inbound" { interface PluginHookChannelSenderContext { unionId?: string; userId?: string; }}Pluginهای کانال این فیلدها را از طریق کمکتابع SDK ورودی عبور میدهند:
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 را برای محدود و ایمنکردن گذر اضافی مدل در برابر بازپخش
اضافه کنند:
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) نیاز دارند،
باید این گزینه را تنظیم کنند:
{ "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 صرفاً از آن عامل اجرایی میخواهد
نمونه معتبر را دوباره بخواند، بنابراین یک سرنخ دیرهنگام نمیتواند زمانبندی قدیمیتر را بازیابی کند.
بازبینی جدیدتر تلاش فعال میزبان را پیش از پذیرش یک تصویر لحظهای منسوخ لغو میکند.
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-auth ← command-status — ببینید:
مهاجرت SDK مربوط به Plugin ← موارد منسوخشدهٔ فعال.
مرتبط
- مهاجرت SDK مربوط به Plugin — موارد منسوخشدهٔ فعال و جدول زمانی حذف
- ساخت Pluginها
- نمای کلی SDK مربوط به Plugin
- نقاط ورود Plugin
- قلابهای داخلی
- جزئیات داخلی معماری Plugin