Building plugins
قلابهای Plugin
قلابهای Plugin نقاط توسعهٔ درونپردازهای برای Pluginهای OpenClaw هستند: اجرای عاملها، فراخوانی ابزارها، جریان پیام، چرخهٔ عمر نشست، مسیریابی زیرعامل، نصبها یا راهاندازی Gateway را بررسی یا تغییر میدهند.
برای واکنش یک اسکریپت کوچک HOOK.md نصبشده توسط اپراتور به رویدادهای فرمان و Gateway مانند /new،
/reset، /stop، agent:bootstrap یا gateway:startup، در عوض از قلابهای داخلی استفاده کنید.
شروع سریع
قلابهای دارای نوع را با 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: "اجرای جستوجوی وب", description: `اجازه به عبارت جستوجو: ${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 در همهجا بودجهٔ طولانیتری دریافت نکند.
Promise رسیدگیکنندهای که مهلتش تمام شده است همچنان به اجرا ادامه میدهد، زیرا فراخوانهای قلاب سیگنال لغو دریافت نمیکنند. ارسال قلاب میتواند پذیرش 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_start |
مرحلهٔ ترکیبی صرفاً برای سازگاری؛ دو قلاب بالا را ترجیح دهید |
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 |
مشاهدهٔ درخواستهای جفتسازی پیام خصوصی تازه ایجادشده |
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، بازنشانیهای برنامهنویسیشده) |
زیرعاملها
subagent_spawned/subagent_ended- راهاندازی و تکمیل زیرعامل را مشاهده میکند.subagent_delivery_target- قلاب سازگاری برای تحویل تکمیل، هنگامی که هیچ اتصال نشست هستهای نمیتواند مسیری را تصویر کند.subagent_spawning- قلاب سازگاری منسوخشده. اکنون هسته، اتصالهای زیرعاملthread: trueرا پیش از فعالشدنsubagent_spawned، از طریق سازگارکنندههای اتصال نشست کانال آماده میکند.subagent_spawnedهنگامی که OpenClaw مدل بومی نشست فرزند را پیش از راهاندازی تعیین کرده باشد، شامل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 |
آغاز یا توقف سرویسهای تحت مالکیت 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تشخیصی
میتواند مقدار زیر را بازگرداند:
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.
برای مسیریابی تأیید، رفتار تصمیم و زمان استفاده از requireApproval بهجای
ابزارهای اختیاری یا تأییدهای exec، به درخواستهای مجوز Plugin
مراجعه کنید.
Pluginهایی که به سیاست در سطح میزبان نیاز دارند، میتوانند سیاستهای ابزار مورداعتماد را با
api.registerTrustedToolPolicy(...) ثبت کنند. این سیاستها پیش از قلابهای معمولی
before_tool_call و پیش از تصمیمهای عادی قلاب اجرا میشوند. سیاستهای مورداعتماد
همراه نخست اجرا میشوند؛ سیاستهای مورداعتماد Pluginهای نصبشده، سپس بهترتیب بارگذاری
Plugin اجرا میشوند؛ قلابهای معمولی before_tool_call پس از آنها اجرا میشوند. Pluginهای همراه،
مسیر موجود سیاست مورداعتماد را حفظ میکنند. Pluginهای نصبشده باید بهصراحت فعال شوند
و هر شناسه سیاست را در contracts.trustedToolPolicies اعلام کنند؛ شناسههای اعلامنشده
پیش از ثبت رد میشوند. شناسههای سیاست به Plugin ثبتکننده محدودند،
بنابراین Pluginهای مختلف میتوانند از شناسه محلی یکسانی دوباره استفاده کنند. از این سطح فقط
برای دروازههای مورداعتماد میزبان، مانند سیاست فضای کاری، اعمال بودجه یا
ایمنی گردشکارهای رزروشده استفاده کنید.
قلاب محیط exec
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> برای ادغام در محیط exec بازگردانید. کنترلکنندهها
بهترتیب اولویت اجرا میشوند؛ نتایج بعدی برای کلید یکسان، نتایج قبلی را بازنویسی میکنند.
خروجی قلاب پیش از ادغام، با سیاست کلید محیط 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
ارسال میشود.
ماندگاری نتیجه ابزار
نتایج ابزار میتوانند شامل 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_start برای سازگاری باقی مانده است. قلابهای صریح
بالا را ترجیح دهید تا Plugin به یک مرحله ترکیبی قدیمی وابسته نباشد.
before_agent_run پس از ساخت اعلان و پیش از هرگونه ورودی مدل،
از جمله بارگذاری تصویر محلی اعلان و مشاهده llm_input اجرا میشود. این قلاب
ورودی فعلی کاربر را بهصورت prompt، همراه با تاریخچه نشست بارگذاریشده در messages
و اعلان سیستمی فعال دریافت میکند. برای توقف اجرا پیش از خواندن اعلان توسط مدل،
{ outcome: "block", reason, message? } را بازگردانید. reason داخلی است؛
message جایگزین قابلمشاهده برای کاربر است. فقط نتایج pass و block
پشتیبانی میشوند؛ شکلهای تصمیم پشتیبانینشده بهصورت بسته و ایمن شکست میخورند.
هنگامی که اجرایی مسدود میشود، OpenClaw فقط متن جایگزین را در
message.content همراه با فراداده غیرحساس مسدودسازی، مانند شناسه Plugin مسدودکننده
و مُهر زمانی، ذخیره میکند. متن اصلی کاربر در رونوشت یا زمینه آینده
نگهداری نمیشود. دلایل داخلی مسدودسازی حساس در نظر گرفته میشوند و
از بارهای داده رونوشت، تاریخچه، پخش، گزارش و تشخیص حذف میشوند.
مشاهدهپذیری باید از فیلدهای پالایششده مانند شناسه مسدودکننده، نتیجه،
مُهر زمانی یا یک دستهبندی امن استفاده کند.
before_agent_start و 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- شناسه فرستنده در محدوده کانال (برای مثال، Feishuopen_id، شناسه کاربر Discord). زمانی مقداردهی میشود که اجرا از پیام کاربری با فراداده شناختهشده فرستنده آغاز شده باشد.ctx.chatId- شناسه مکالمه بومیِ انتقال (برای مثال، Feishuchat_id، Telegramchat_id). زمانی مقداردهی میشود که کانال مبدأ یک شناسه مکالمه بومی ارائه کند.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، exec-event) وجود ندارند.
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 و upstreamRequestIdHash هستند، زمانی که OpenClaw بتواند یک هش کراندار از شناسه درخواست ارائهدهنده استخراج کند. وقتی زمان اجرا فراداده پنجره زمینه را تفکیک کرده باشد، رویداد و زمینه هوک همچنین شامل 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، همبستگی اختیاری اجرا/نشست و فراداده را مشاهده میکند.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را ارائه نمیکنند؛ Pluginها میتوانند شکل بار را ویرایش کنند، اما نمیتوانند به رسانه محلی اعتماد اعطا کنند. message_sendingمیتواند همراه با لغو،cancelReasonوmetadataکراندار را بازگرداند. APIهای جدید چرخه عمر پیام، این وضعیت را بهصورت نتیجه تحویل سرکوبشده با دلیلcancelled_by_message_sending_hookارائه میکنند؛ تحویل مستقیم قدیمی برای سازگاری همچنان آرایه نتیجه خالی بازمیگرداند.message_sentفقط برای مشاهده است. شکست کنترلکنندهها ثبت میشود و نتیجه تحویل را تغییر نمیدهد.
هوکهای نصب
از security.installPolicy برای تصمیمهای مجاز/مسدودسازی متعلق به اپراتور استفاده کنید. آن خطمشی از پیکربندی OpenClaw اجرا میشود، مسیرهای نصب و بهروزرسانی CLI را پوشش میدهد و در صورت فعالبودن اما دردسترسنبودن، بهصورت بسته شکست میخورد.
before_install یک هوک چرخه عمر زمان اجرای Plugin است. این هوک فقط پس از security.installPolicy در همان فرایند OpenClaw اجرا میشود که هوکهای Plugin از قبل در آن بارگذاری شدهاند، مانند جریانهای نصب مبتنی بر Gateway. این هوک برای مشاهدات، هشدارها و بررسیهای سازگاری متعلق به Plugin مفید است، اما مرز امنیتی اصلی سازمان یا میزبان برای نصبها نیست. فیلد builtinScan برای سازگاری در بار رویداد باقی میماند، اما OpenClaw دیگر مسدودسازی داخلی کد خطرناک هنگام نصب را اجرا نمیکند؛ بنابراین این فیلد یک نتیجه خالی ok است. برای توقف نصب در آن فرایند، یافتههای اضافی یا { block: true, blockReason } را بازگردانید.
block: true پایانی است. block: false بهعنوان بدون تصمیم در نظر گرفته میشود. شکست کنترلکنندهها نصب را بهصورت بسته مسدود میکند.
چرخه عمر Gateway
از gateway_start برای شروع سرویسهای عمومی Plugin و از gateway_stop برای پاکسازی منابع بلندمدت استفاده کنید. ممکن است زمانبند Cron هنگام اجرای gateway_start همچنان در حال بارگذاری باشد؛ بنابراین از آن بهعنوان سیگنال مبنا برای یک تصویرسازی خارجی 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 در آداپتور خارجی باید اتمی و تکرارپذیر باشد و
تنها پس از آن پایان یابد که میزبان عکس فوری را بهصورت ماندگار پذیرفته باشد. همچنین باید
سیگنال لغو ارائهشده را رعایت کند: اگر سیگنال پیش از پذیرش ماندگار
لغو شود، آداپتور نباید آن عکس فوری را بپذیرد.
این الگو تنها یک کارگر با آخرین وضعیت را در حال اجرا نگه میدارد. فقط 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. before_agent_startبرای سازگاری باقی میماند. Pluginهای جدید باید بهجای مرحله ترکیبی ازbefore_model_resolveوbefore_prompt_buildاستفاده کنند.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 افزونه ← منسوخسازیهای فعال.
مرتبط
- مهاجرت SDK افزونه — منسوخسازیهای فعال و جدول زمانی حذف
- ساخت افزونهها
- نمای کلی SDK افزونه
- نقاط ورود افزونه
- هوکهای داخلی
- جزئیات داخلی معماری افزونه