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: "ตรวจสอบเครื่องมือล่วงหน้า", 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 หนึ่งได้รับงบเวลาที่ยาวขึ้นในทุกตำแหน่ง
พรอมิสของตัวจัดการที่หมดเวลาจะยังคงทำงานต่อ เนื่องจากคอลแบ็กของฮุกไม่ได้ รับสัญญาณการยกเลิก การส่งฮุกอาจคืนสิทธิ์การรับงานของ 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 |
เมทาดาทาการเรียกผู้ให้บริการ/โมเดลที่ผ่านการล้างข้อมูลแล้ว ได้แก่ เวลา ผลลัพธ์ และแฮช ID คำขอแบบจำกัด โดยไม่มีเนื้อหาพรอมต์หรือคำตอบ |
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 เสมอ ผู้เรียกจะระบุว่าเซสชันแม่ได้รับ session_end ขั้นสุดท้ายด้วยหรือไม่ผ่าน succeedsParent: true หมายถึงตัวสืบทอด ส่วน false หมายถึงเซสชันลูกแบบขนาน การละค่าไว้จะคงพฤติกรรมเดิมในการสับเปลี่ยนเซสชันแม่ ฮุก command:new และ before_reset ยังคงอธิบายการดำเนินการ /new ที่ร้องขอในทั้งสองกรณี
ซับเอเจนต์
subagent_spawned/subagent_ended- สังเกตการณ์การเริ่มต้นและการเสร็จสิ้นของเอเจนต์ย่อยsubagent_delivery_target- ฮุกความเข้ากันได้สำหรับส่งผลการเสร็จสิ้นเมื่อไม่มีการผูกเซสชันหลักที่สามารถฉายเส้นทางได้subagent_spawning- ฮุกความเข้ากันได้ที่เลิกใช้แล้ว ขณะนี้ส่วนหลักเตรียมการผูกเอเจนต์ย่อยthread: trueผ่านอะแดปเตอร์การผูกเซสชันของช่องทางก่อนที่subagent_spawnedจะทำงาน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; ใช้targetSessionKeyเพื่อเชื่อมโยงกับเหตุการณ์subagent_spawnedที่ตรงกัน
วงจรชีวิต
| ฮุก | วัตถุประสงค์ |
|---|---|
gateway_start / gateway_stop |
เริ่มหรือหยุดบริการที่ Plugin เป็นเจ้าของพร้อมกับ Gateway |
deactivate |
นามแฝงความเข้ากันได้ที่เลิกใช้แล้วสำหรับ gateway_stop; ใช้ gateway_stop ใน Plugin ใหม่ |
cron_reconciled |
กระทบยอดกับสถานะ Cron ทั้งหมดของ Gateway หลังเริ่มต้นหรือโหลดใหม่ |
cron_changed |
สังเกตการณ์การเปลี่ยนแปลงวงจรชีวิต Cron ที่ Gateway เป็นเจ้าของ (เพิ่ม อัปเดต ลบ เริ่ม เสร็จสิ้น กำหนดเวลา) |
before_install |
ตรวจสอบวัสดุติดตั้ง Skills หรือ Plugin ที่จัดเตรียมไว้จากรันไทม์ Plugin ที่โหลดแล้ว |
คำขอจับคู่ช่องทาง
ใช้ channel_pairing_requested เมื่อ Plugin ต้องแจ้งผู้ปฏิบัติงานหรือ
เขียนบันทึกการตรวจสอบหลังจากผู้ส่ง DM ที่ยังไม่ได้จับคู่สร้างคำขอจับคู่ที่รอดำเนินการ
ฮุกจะถูกเรียกเมื่อสร้างคำขอแล้ว การส่งการตอบกลับการจับคู่ผ่านช่องทาง
จะไม่ล่าช้าเพราะตัวจัดการฮุกที่ทำงานช้าหรือล้มเหลว
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แบบดั้งเดิมของโหมดรายงานในเซิร์ฟเวอร์แอป Codex การดำเนินการนี้จะส่งต่อไปยัง คำขออนุมัติของเซิร์ฟเวอร์แอปที่ตรงกัน โปรดดู รันไทม์ชุดควบคุม Codexblock: 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 ของกิลด์หรือช่องทางสามารถให้สิทธิ์
กลุ่มผู้ใช้สำหรับการบำรุงรักษาได้ ช่องทางอื่นสามารถใช้รหัสผู้ส่งที่คงที่ จากนั้นฮุก
จะใช้การตัดสินใจที่ละเอียดกว่าในระดับเครื่องมือกับทุกการเรียกใช้เครื่องมือระหว่างการทำงาน
รวมถึงการเรียก PreToolUse แบบดั้งเดิมของ Codex ฮุกสามารถยับยั้งเครื่องมือที่โมเดลมองเห็นได้ แต่ไม่สามารถ
เพิ่มเครื่องมือที่โฮสต์ละไว้ได้ นโยบายแซนด์บ็อกซ์ การอนุมัติ exec เครื่องมือหลัก
สำหรับเจ้าของเท่านั้น และนโยบายช่องทางที่มีอยู่ยังคงมีผล ฮุกไม่สามารถให้สิทธิ์ข้ามนโยบายเหล่านั้นได้
กำหนดขอบเขตรหัสผู้ส่งและรหัสบทบาทให้ตรงกับคู่ช่องทาง/บัญชีอย่างแม่นยำตามที่แสดง ทั้งสองอย่างเป็น
เนมสเปซภายในผู้ให้บริการ เก็บรายการอนุญาตให้รัดกุม เพิ่มเครื่องมือเขียนหรือ
ดำเนินการเฉพาะเมื่อนโยบายแซนด์บ็อกซ์และการอนุมัติของการปรับใช้
ทำให้ปลอดภัยเท่านั้น สำหรับการทำงานแบบอัตโนมัติหรือของระบบ ให้ตัดสินใจอย่างชัดเจนว่า
ควรอนุญาตเมื่อไม่มี ctx.requester หรือไม่ ตัวอย่างนี้ปฏิเสธกรณีดังกล่าวสำหรับเอเจนต์ที่กำหนดขอบเขตไว้
โปรดดู คำขอสิทธิ์ของ Plugin สำหรับ
การกำหนดเส้นทางการอนุมัติ ลักษณะการตัดสินใจ และกรณีที่ควรใช้ requireApproval แทน
เครื่องมือแบบเลือกได้หรือการอนุมัติ exec
Plugin ที่ต้องการนโยบายระดับโฮสต์สามารถลงทะเบียนนโยบายเครื่องมือที่เชื่อถือได้ด้วย
api.registerTrustedToolPolicy(...) นโยบายเหล่านี้ทำงานก่อนฮุก
before_tool_call ทั่วไปและก่อนการตัดสินใจของฮุกตามปกติ นโยบายที่เชื่อถือได้
ของ Plugin ที่รวมมาในชุดจะทำงานก่อน ตามด้วยนโยบายที่เชื่อถือได้ของ 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 จะถูกตัดออกเสมอ (การแก้ไขพาธคำสั่งและการตรวจสอบไบนารีที่ปลอดภัย
ขึ้นอยู่กับค่านี้) คีย์ที่ไม่ถูกต้องและคีย์อันตรายที่เขียนทับค่าของโฮสต์ เช่น LD_*,
DYLD_*, NODE_OPTIONS, ตัวแปรพร็อกซี (HTTP_PROXY, HTTPS_PROXY,
ALL_PROXY, NO_PROXY) และตัวแปรเขียนทับ TLS (NODE_TLS_REJECT_UNAUTHORIZED,
SSL_CERT_FILE และตัวแปรที่คล้ายกัน) จะถูกตัดออก สภาพแวดล้อมของ Plugin ที่ผ่านการกรองจะรวมอยู่
ในข้อมูลเมตาการอนุมัติ/ตรวจสอบของ Gateway และส่งต่อไปยังคำขอดำเนินการ
บนโฮสต์ Node
การคงผลลัพธ์ของเครื่องมือ
ผลลัพธ์ของเครื่องมือสามารถมี details แบบมีโครงสร้างสำหรับการเรนเดอร์ UI การวินิจฉัย
การกำหนดเส้นทางสื่อ หรือข้อมูลเมตาที่ Plugin เป็นเจ้าของ ให้ถือว่า details เป็นข้อมูลเมตาขณะทำงาน
ไม่ใช่เนื้อหาพรอมต์:
- OpenClaw จะตัด
toolResult.detailsออกก่อนเล่นซ้ำไปยังผู้ให้บริการและก่อนป้อนข้อมูลให้ Compaction เพื่อไม่ให้ข้อมูลเมตากลายเป็นบริบทของโมเดล - รายการเซสชันที่คงไว้จะเก็บเฉพาะ
detailsที่มีขนาดจำกัด รายละเอียดที่ใหญ่เกินไปจะถูก แทนที่ด้วยข้อมูลสรุปแบบย่อและpersistedDetailsTruncated: true tool_result_persistและbefore_message_writeจะทำงานก่อน ขีดจำกัดขั้นสุดท้ายของการคงข้อมูล รักษาdetailsที่ส่งคืนให้มีขนาดเล็ก และหลีกเลี่ยงการใส่ ข้อความที่เกี่ยวข้องกับพรอมต์ไว้เฉพาะในdetails; ให้ใส่เอาต์พุตเครื่องมือที่โมเดลมองเห็นได้ไว้ในcontent
ฮุกพรอมต์และโมเดล
ใช้ฮุกเฉพาะระยะต่อไปนี้สำหรับ Plugin ใหม่:
before_model_resolve: รับเฉพาะพรอมต์ปัจจุบันและข้อมูลเมตาของไฟล์แนบ ส่งคืนproviderOverrideหรือmodelOverrideagent_turn_prepare: รับพรอมต์ปัจจุบัน ข้อความเซสชันที่จัดเตรียมไว้ และการแทรกข้อมูลในคิวแบบครั้งเดียวที่ระบายออกมาสำหรับเซสชันนี้ ส่งคืนprependContextหรือappendContextbefore_prompt_build: รับพรอมต์ปัจจุบันและข้อความเซสชัน ส่งคืนprependContext,appendContext,systemPrompt,prependSystemContextหรือappendSystemContextheartbeat_prompt_contribution: ทำงานเฉพาะในรอบ Heartbeat และส่งคืนprependContextหรือappendContextมีไว้สำหรับตัวตรวจสอบเบื้องหลังที่ ต้องสรุปสถานะปัจจุบันโดยไม่เปลี่ยนแปลงรอบที่ผู้ใช้เป็นผู้เริ่ม
before_agent_run ทำงานหลังสร้างพรอมต์และก่อนอินพุตใด ๆ เข้าสู่โมเดล
รวมถึงการโหลดรูปภาพเฉพาะพรอมต์และการสังเกตการณ์ llm_input โดยรับ
อินพุตผู้ใช้ปัจจุบันเป็น prompt พร้อมประวัติเซสชันที่โหลดแล้วใน messages
และพรอมต์ระบบที่ใช้งานอยู่ ส่งคืน { outcome: "block", reason, message? }
เพื่อหยุดการทำงานก่อนโมเดลอ่านพรอมต์ reason ใช้ภายใน
ส่วน message เป็นข้อความทดแทนที่ผู้ใช้เห็น รองรับเฉพาะผลลัพธ์ pass และ block;
รูปแบบการตัดสินใจที่ไม่รองรับจะปฏิเสธโดยค่าเริ่มต้น
เมื่อการทำงานถูกบล็อก OpenClaw จะจัดเก็บเฉพาะข้อความทดแทนใน
message.content พร้อมข้อมูลเมตาการบล็อกที่ไม่ละเอียดอ่อน เช่น ID ของ
Plugin ที่บล็อกและการประทับเวลา ข้อความต้นฉบับของผู้ใช้จะไม่ถูกเก็บไว้ในทรานสคริปต์
หรือบริบทในอนาคต เหตุผลการบล็อกภายในจะถือว่าเป็นข้อมูลละเอียดอ่อนและ
ไม่รวมอยู่ในเพย์โหลดทรานสคริปต์ ประวัติ การกระจายข้อความ บันทึก และการวินิจฉัย
การสังเกตการณ์ควรใช้ฟิลด์ที่ผ่านการทำให้ปลอดภัย เช่น ID ผู้บล็อก ผลลัพธ์
การประทับเวลา หรือหมวดหมู่ที่ปลอดภัย
ฮุกของรอบเอเจนต์รวมถึง agent_end จะมี event.runId เมื่อ OpenClaw สามารถ
ระบุการทำงานที่ใช้งานอยู่ได้ โดยค่าเดียวกันจะอยู่ใน ctx.runId ด้วย การทำงานที่ขับเคลื่อนด้วย
Cron จะแสดง ctx.jobId (ID ของงาน Cron ต้นทาง) ในบริบทของรอบเอเจนต์ด้วย
เพื่อให้ฮุกจำกัดขอบเขตเมตริก ผลข้างเคียง หรือสถานะไว้กับงานตามกำหนดการงานหนึ่งได้
ctx.jobId ไม่ได้เป็นส่วนหนึ่งของบริบทเครื่องมือ before_tool_call
สำหรับการทำงานที่มาจากช่องทาง ctx.channel และ ctx.messageProvider จะระบุ
พื้นผิวผู้ให้บริการ เช่น discord หรือ telegram ขณะที่ ctx.channelId คือ
ตัวระบุเป้าหมายการสนทนาเมื่อ OpenClaw สามารถอนุมานได้จากคีย์เซสชัน
หรือข้อมูลเมตาการส่งมอบ
เมื่อมีข้อมูลประจำตัวของผู้ส่ง บริบทฮุกเอเจนต์จะมีข้อมูลต่อไปนี้ด้วย:
ctx.senderId- ID ผู้ส่งที่กำหนดขอบเขตตามช่องทาง (เช่น Feishuopen_id, ID ผู้ใช้ Discord) จะมีค่าเมื่อการทำงานมาจากข้อความผู้ใช้ที่มี ข้อมูลเมตาผู้ส่งที่ทราบctx.chatId- ตัวระบุการสนทนาแบบเนทีฟของระบบขนส่ง (เช่น Feishuchat_id, Telegramchat_id) จะมีค่าเมื่อช่องทางต้นทาง มี ID การสนทนาแบบเนทีฟctx.channelContext.sender.id- ID ผู้ส่งเดียวกับctx.senderIdภายใต้ ออบเจ็กต์ที่ช่องทางเป็นเจ้าของ ซึ่ง Plugin สามารถขยายด้วยฟิลด์เฉพาะช่องทางได้ctx.channelContext.chat.id- 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)
ctx.senderExternalId ยังคงอยู่ในฐานะฟิลด์ความเข้ากันได้ระดับซอร์สที่เลิกใช้แล้วสำหรับ
Plugin รุ่นเก่า แกนหลักจะไม่กำหนดค่าให้ฟิลด์นี้ ข้อมูลประจำตัวผู้ส่งเฉพาะช่องทางใหม่
ควรอยู่ภายใต้ ctx.channelContext.sender ผ่านการเสริม
โมดูล
agent_end เป็นฮุกสังเกตการณ์ พาธ Gateway และพาธชุดทดสอบที่คงอยู่จะเรียกใช้
แบบไม่รอผลหลังจบรอบ ขณะที่พาธ CLI แบบครั้งเดียวที่มีอายุสั้นจะรอ
พรอมิสของฮุกก่อนล้างกระบวนการ เพื่อให้ Plugin ที่เชื่อถือได้สามารถส่งข้อมูล
การสังเกตการณ์เทอร์มินัลให้เสร็จหรือบันทึกสถานะได้ ตัวเรียกใช้ฮุกกำหนดเวลาหมดที่ 30 วินาที
เพื่อไม่ให้ Plugin หรือปลายทางการฝังที่ค้างทำให้พรอมิสของฮุก
ค้างอยู่ตลอดไป ระบบจะบันทึกเวลาหมดและ OpenClaw จะทำงานต่อ โดยจะไม่
ยกเลิกงานเครือข่ายที่ Plugin เป็นเจ้าของ เว้นแต่ Plugin จะใช้สัญญาณยกเลิก
ของตนเองด้วย
ใช้ model_call_started และ model_call_ended สำหรับเทเลเมทรีการเรียกผู้ให้บริการ
ที่ไม่ควรได้รับพรอมต์ดิบ ประวัติ การตอบกลับ ส่วนหัว เนื้อหาคำขอ
หรือ ID คำขอของผู้ให้บริการ ฮุกเหล่านี้มีข้อมูลเมตาที่เสถียร เช่น
runId, callId, provider, model, api/transport ที่เป็นทางเลือก, durationMs/outcome ขั้นสุดท้าย และ upstreamRequestIdHash เมื่อ OpenClaw สามารถอนุมาน
แฮช ID คำขอของผู้ให้บริการที่มีขนาดจำกัดได้ เมื่อรันไทม์แก้ไข
ข้อมูลเมตาหน้าต่างบริบทแล้ว เหตุการณ์และบริบทของฮุกจะมี
contextTokenBudget ซึ่งเป็นงบประมาณโทเค็นที่มีผลหลังใช้ขีดจำกัด
ของโมเดล/การกำหนดค่า/เอเจนต์ รวมถึง contextWindowSource และ contextWindowReferenceTokens เมื่อมีการใช้
ขีดจำกัดที่ต่ำกว่า
before_agent_finalize ทำงานเฉพาะเมื่อชุดทดสอบกำลังจะยอมรับคำตอบสุดท้ายตามธรรมชาติ
จากผู้ช่วย ไม่ใช่พาธการยกเลิก /stop และจะไม่
ทำงานเมื่อผู้ใช้ยกเลิกรอบ ส่งคืน { action: "revise", reason } เพื่อขอ
ให้ชุดทดสอบเรียกโมเดลอีกหนึ่งรอบก่อนสรุปผล, { action: "finalize", reason? } เพื่อบังคับสรุปผล หรือไม่ส่งคืนผลลัพธ์เพื่อดำเนินการต่อ
ตัวจัดการมีงบประมาณเริ่มต้น 15 วินาที เมื่อหมดเวลา 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 แทน
ใช้ api.session.workflow.enqueueNextTurnInjection(...) เมื่อ Plugin ต้องการ
ให้บริบทถาวรไปถึงรอบโมเดลถัดไปเพียงครั้งเดียว (ฟิลด์ระดับบนสุด
api.enqueueNextTurnInjection(...) เป็นนามแฝงที่เลิกใช้แล้วและมี
พฤติกรรมเดียวกัน) OpenClaw จะระบายการแทรกข้อมูลที่เข้าคิวไว้ก่อนฮุกพรอมต์ ตัด
การแทรกข้อมูลที่หมดอายุ และขจัดรายการซ้ำตาม idempotencyKey แยกตาม Plugin นี่คือ
จุดเชื่อมต่อที่เหมาะสมสำหรับการดำเนินการต่อหลังอนุมัติ ข้อมูลสรุปนโยบาย ความเปลี่ยนแปลงจากตัวตรวจสอบ
เบื้องหลัง และการดำเนินคำสั่งต่อที่ควรให้โมเดลมองเห็นใน
รอบถัดไป แต่ไม่ควรกลายเป็นข้อความถาวรในพรอมต์ระบบ
ความหมายของการล้างข้อมูลเป็นส่วนหนึ่งของสัญญา การล้างส่วนขยายเซสชันและ
คอลแบ็กการล้างวงจรชีวิตรันไทม์จะได้รับ 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 ซึ่งเป็นสแนปช็อตโมเดล/การใช้งาน/บริบท
แบบสดต่อรอบตามความพยายามที่ดีที่สุด การส่งมอบแบบถาวร การเล่นซ้ำที่กู้คืนแล้ว และ
การตอบกลับที่ไม่มีความสัมพันธ์กับการทำงานอย่างแม่นยำจะไม่มีข้อมูลนี้
บริบทของ message hook จะเปิดเผยฟิลด์สหสัมพันธ์ที่เสถียรเมื่อมี:
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ที่เขียนใหม่จะดำเนินต่อไปยัง hook ที่มีลำดับความสำคัญต่ำกว่า เว้นแต่ hook ในภายหลัง จะยกเลิกการส่งreply_payload_sendingทำงานหลังจากปรับ payload ให้เป็นมาตรฐานและก่อนส่งไปยังช่องทาง รวมถึงการตอบกลับที่กำหนดเส้นทางกลับไปยังช่องทางต้นทาง handler จะทำงานตามลำดับ และแต่ละ handler จะเห็น payload ล่าสุดที่สร้างโดย handler ที่มีลำดับความสำคัญสูงกว่า- payload ของ
reply_payload_sendingจะไม่เปิดเผยเครื่องหมายความน่าเชื่อถือขณะรัน เช่นtrustedLocalMedia; Plugin สามารถแก้ไขรูปแบบ payload ได้ แต่ไม่สามารถมอบความน่าเชื่อถือ ของสื่อภายในเครื่อง message_sendingสามารถส่งคืนcancelReasonและmetadataที่มีขอบเขตจำกัดพร้อมกับ การยกเลิกได้ API วงจรชีวิตข้อความแบบใหม่จะแสดงผลนี้เป็นผลลัพธ์การส่ง ที่ถูกระงับ พร้อมเหตุผลcancelled_by_message_sending_hook; การส่งโดยตรง แบบเดิมยังคงส่งคืนอาร์เรย์ผลลัพธ์ว่างเพื่อความเข้ากันได้message_sentใช้สำหรับการสังเกตการณ์เท่านั้น ความล้มเหลวของ handler จะถูกบันทึกในล็อกและไม่ เปลี่ยนผลลัพธ์การส่ง
ติดตั้ง hook
ใช้ security.installPolicy สำหรับการตัดสินใจอนุญาต/บล็อกที่ผู้ปฏิบัติงานเป็นเจ้าของ
นโยบายดังกล่าวทำงานจากการกำหนดค่า OpenClaw ครอบคลุมเส้นทางการติดตั้งและอัปเดตผ่าน CLI และ
ปฏิเสธโดยอัตโนมัติเมื่อเปิดใช้งานแต่ไม่พร้อมใช้งาน
before_install เป็น hook ของวงจรชีวิตรันไทม์ Plugin โดยจะทำงานหลังจาก
security.installPolicy เฉพาะภายในโปรเซส OpenClaw ที่โหลด hook ของ Plugin
เรียบร้อยแล้ว เช่น ขั้นตอนการติดตั้งที่ทำงานผ่าน Gateway ซึ่งมีประโยชน์สำหรับ
การสังเกตการณ์ คำเตือน และการตรวจสอบความเข้ากันได้ที่ Plugin เป็นเจ้าของ แต่ไม่ใช่
ขอบเขตความปลอดภัยหลักระดับองค์กรหรือโฮสต์สำหรับการติดตั้ง ฟิลด์
builtinScan ยังคงอยู่ใน payload ของเหตุการณ์เพื่อความเข้ากันได้ แต่
OpenClaw จะไม่เรียกใช้การบล็อกโค้ดอันตรายขณะติดตั้งที่มีมาให้ในตัวอีกต่อไป ดังนั้นจึง
เป็นผลลัพธ์ ok ที่ว่างเปล่า ส่งคืนข้อค้นพบเพิ่มเติมหรือ
{ block: true, blockReason } เพื่อหยุดการติดตั้งในโปรเซสนั้น
block: true ถือเป็นสถานะสิ้นสุด block: false ถือว่าไม่มีการตัดสินใจ ความล้มเหลวของ handler
จะบล็อกการติดตั้งตามหลักปฏิเสธโดยอัตโนมัติ
วงจรชีวิต Gateway
ใช้ gateway_start เพื่อเริ่มบริการทั่วไปของ Plugin และ gateway_stop เพื่อ
ล้างทรัพยากรที่ทำงานระยะยาว ตัวจัดกำหนดการ Cron อาจยังอยู่ระหว่างการโหลดเมื่อ
gateway_start ทำงาน ดังนั้นอย่าใช้เป็นสัญญาณฐานสำหรับการฉายภาพ Cron
ภายนอก
อย่าพึ่งพา hook ภายใน gateway:startup สำหรับบริการรันไทม์
ที่ Plugin เป็นเจ้าของ
cron_reconciled จะทำงานหลังจากตัวจัดกำหนดการ Cron ของ Gateway และตัวเฝ้าดูเมื่อออก
กระทบยอดสถานะถาวรเรียบร้อยแล้ว โดยจะทำงานทั้งในการเริ่มต้น
ครั้งแรกและการแทนที่ตัวจัดกำหนดการระหว่างการโหลดการกำหนดค่าใหม่ เหตุการณ์จะรายงาน
reason (startup หรือ reload) และสถานะ enabled ที่มีผล แม้ปิดใช้งาน
Cron ก็ยังปล่อยเหตุการณ์พร้อม enabled: false ทำให้การฉายภาพภายนอกสามารถ
ล้างการปลุกที่ล้าสมัยได้ ใช้ ctx.getCron?.() สำหรับอินสแตนซ์ตัวจัดกำหนดการที่แน่นอนซึ่ง
กระทบยอดเสร็จสิ้น การโหลดใหม่ในภายหลังจะไม่เปลี่ยนเป้าหมายของ callback นั้น
ctx.abortSignal เป็นเจ้าของสแนปช็อตตัวจัดกำหนดการเดียวกัน Gateway จะยกเลิกสัญญาณทันที
ที่ตัวจัดกำหนดการใหม่พร้อมทำงานหรือเมื่อเริ่มปิดระบบ ส่งสัญญาณนี้ผ่านทุก
ผลข้างเคียงแบบถาวร และอย่ายอมรับสแนปช็อตหลังจากสัญญาณถูกยกเลิก
นี่เป็นสัญญาณวงจรชีวิตของตัวจัดกำหนดการ ไม่ใช่สัญญาณการเปิดใช้งาน Plugin:
การโหลดใหม่แบบร้อนเฉพาะ Plugin จะไม่เรียกซ้ำ ผู้ใช้ที่เพิ่งเปิดใช้งานจะได้รับ
ข้อมูลฐานชุดแรกในการแทนที่ตัวจัดกำหนดการครั้งถัดไปหรือเมื่อ Gateway เริ่มทำงาน
เช่นเดียวกับ hook สำหรับการสังเกตการณ์อื่นๆ callback ของ gateway_start และ cron_reconciled
อาจทำงานทับซ้อนกัน หาก handler ทั้งสองใช้การเริ่มต้น Plugin ร่วมกัน ให้ประสานงาน
ด้วย readiness promise ภายใน Plugin แทนการพึ่งพาลำดับ callback
cron_changed จะทำงานสำหรับเหตุการณ์วงจรชีวิต Cron ที่ Gateway เป็นเจ้าของ โดยมี
payload เหตุการณ์ที่มีชนิดข้อมูลกำกับ ครอบคลุมเหตุผล 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 ของอะแดปเตอร์ภายนอกต้องเป็นแบบอะตอมมิกและทำซ้ำได้โดยให้ผลลัพธ์เดิม และต้อง
เสร็จสิ้นหลังจากโฮสต์ยอมรับสแนปช็อตแบบถาวรแล้วเท่านั้น นอกจากนี้ยังต้อง
เคารพสัญญาณยกเลิกที่ให้มา หากสัญญาณถูกยกเลิกก่อนการยอมรับแบบถาวร
อะแดปเตอร์ต้องไม่ยอมรับสแนปช็อตนั้น
รูปแบบนี้ทำให้มี worker สำหรับสถานะล่าสุดเพียงหนึ่งรายการกำลังทำงานอยู่ เฉพาะ cron_reconciled
เท่านั้นที่นำอินสแตนซ์ตัวจัดกำหนดการมาใช้ ส่วน cron_changed เพียงขอให้ worker นั้นอ่าน
อินสแตนซ์ที่เป็นแหล่งข้อมูลจริงอีกครั้ง ดังนั้นคำใบ้ที่มาช้าจึงไม่สามารถกู้คืนตัวจัดกำหนดการรุ่นเก่าได้
รีวิชันที่ใหม่กว่าจะยกเลิกความพยายามของโฮสต์ที่กำลังทำงาน ก่อนที่จะยอมรับสแนปช็อต
ที่ล้าสมัยได้
type ExternalWake = { jobId: string; runAtMs: number }; type ExternalWakeHost = { replaceAll(wakes: readonly ExternalWake[], options: { signal: AbortSignal }): Promise<void>; close(): Promise<void>;}; type CronReader = { list(options: { includeDisabled: true }): Promise< Array<{ id: string; enabled?: boolean; state?: { nextRunAtMs?: number }; }> >;}; export function registerCronProjection(api: OpenClawPluginApi, host: ExternalWakeHost) { const lifecycle = new AbortController(); let cron: CronReader | undefined; let enabled = false; let hasBaseline = false; let reconciliationSignal: AbortSignal | undefined; let requestedRevision = 0; let appliedRevision = 0; let worker = Promise.resolve(); let activeAttempt: AbortController | undefined; const projectLatest = async () => { let retryMs = 1_000; while (!lifecycle.signal.aborted && appliedRevision < requestedRevision) { const ownerSignal = reconciliationSignal; if (!ownerSignal || ownerSignal.aborted) { return; } const targetRevision = requestedRevision; const attempt = new AbortController(); const signal = AbortSignal.any([lifecycle.signal, ownerSignal, attempt.signal]); activeAttempt = attempt; try { const jobs = enabled && cron ? await cron.list({ includeDisabled: true }) : []; if (signal.aborted || targetRevision !== requestedRevision) { continue; } const wakes = jobs .flatMap((job): ExternalWake[] => { const runAtMs = job.enabled === false ? undefined : job.state?.nextRunAtMs; return runAtMs === undefined ? [] : [{ jobId: job.id, runAtMs }]; }) .sort((a, b) => a.runAtMs - b.runAtMs || a.jobId.localeCompare(b.jobId)); await host.replaceAll(wakes, { signal }); if (signal.aborted || targetRevision !== requestedRevision) { continue; } appliedRevision = targetRevision; retryMs = 1_000; } catch { if (lifecycle.signal.aborted || ownerSignal.aborted) { return; } if (attempt.signal.aborted) { continue; } api.logger.warn(`การฉายภาพ Cron ภายนอกล้มเหลว จะลองอีกครั้งใน ${retryMs}ms`); try { await sleep(retryMs, undefined, { signal }); } catch { if (lifecycle.signal.aborted) { return; } if (attempt.signal.aborted) { continue; } } retryMs = Math.min(retryMs * 2, 30_000); } finally { if (activeAttempt === attempt) { activeAttempt = undefined; } } } }; const requestProjection = () => { const targetRevision = ++requestedRevision; activeAttempt?.abort(); worker = worker.then(async () => { if (!lifecycle.signal.aborted && appliedRevision < targetRevision) { await projectLatest(); } }); return worker; }; api.on("cron_reconciled", (event, ctx) => { const reconciledCron = ctx.getCron?.(); if (event.enabled && !reconciledCron) { api.logger.warn("การกระทบยอด Cron ไม่ได้เปิดเผยตัวจัดกำหนดการ"); return; } cron = reconciledCron; enabled = event.enabled; hasBaseline = true; reconciliationSignal = ctx.abortSignal; return requestProjection(); }); api.on("cron_changed", () => { if (hasBaseline) { return requestProjection(); } }); api.on("gateway_stop", async () => { lifecycle.abort(); await worker; await host.close(); });}เมื่อ cron_reconciled รายงาน enabled: false เส้นทางเดียวกันจะเรียก
replaceAll([]) และล้างการปลุกภายนอกที่ล้าสมัย การลองใหม่/การหน่วงเวลาแบบเพิ่มขึ้นในตัวอย่างนี้
อยู่ภายในโปรเซสและถือว่าความล้มเหลวของอะแดปเตอร์รันไทม์เป็นเหตุการณ์ชั่วคราว ให้ตรวจสอบ
การกำหนดค่าที่ไม่ควรลองใหม่ก่อนลงทะเบียน OpenClaw ไม่มี
outbox สำหรับผลกระทบของ hook ของ Plugin หากโปรเซสจบก่อนการยอมรับแบบถาวร
การเริ่ม Gateway ครั้งถัดไปจะปล่อยสแนปช็อต cron_reconciled ใหม่ที่เป็นแหล่งข้อมูลจริง
gateway_stop จะยกเลิกงานของโฮสต์ที่กำลังดำเนินอยู่ รอให้ worker เสร็จสิ้น แล้วจึง
ปิดอะแดปเตอร์
การเลิกใช้งานที่กำลังจะมาถึง
พื้นผิวบางส่วนที่เกี่ยวข้องกับ hook ถูกเลิกใช้แล้วแต่ยังคงรองรับอยู่ ให้ย้าย ก่อนรุ่นหลักถัดไป:
- เอนเวโลปช่องแบบข้อความธรรมดา ในแฮนด์เลอร์
inbound_claimและmessage_receivedให้อ่านBodyForAgentและบล็อกบริบทผู้ใช้แบบมีโครงสร้าง แทนการแยกวิเคราะห์ข้อความเอนเวโลปแบบแบน ดู เอนเวโลปช่องแบบข้อความธรรมดา → BodyForAgent subagent_spawningยังคงมีไว้เพื่อความเข้ากันได้กับ Plugin รุ่นเก่า แต่ Plugin ใหม่ไม่ควรส่งคืนการกำหนดเส้นทางเธรดจากส่วนนี้ Core จะเตรียม การผูก subagent ของthread: trueผ่านอะแดปเตอร์การผูกเซสชันของช่อง ก่อนที่subagent_spawnedจะทำงานdeactivateยังคงเป็นนามแฝงสำหรับการล้างข้อมูลที่เลิกใช้งานแล้วเพื่อความเข้ากันได้ จนถึง หลังวันที่ 2026-08-16 Plugin ใหม่ควรใช้gateway_stoponResolutionในbefore_tool_callตอนนี้ใช้ยูเนียนแบบระบุชนิดPluginApprovalResolution(allow-once/allow-always/deny/timeout/cancelled) แทนstringแบบรูปแบบอิสระapi.registerSessionExtension/api.enqueueNextTurnInjectionยังคง เป็นนามแฝงระดับบนสุดเพื่อความเข้ากันได้ Plugin ใหม่ควรใช้api.session.state.registerSessionExtension(...)และapi.session.workflow.enqueueNextTurnInjection(...)
สำหรับรายการทั้งหมด ได้แก่ การลงทะเบียนความสามารถด้านหน่วยความจำ โปรไฟล์การคิดของผู้ให้บริการ
ผู้ให้บริการการยืนยันตัวตนภายนอก ชนิดการค้นหาผู้ให้บริการ ตัวเข้าถึงรันไทม์ของงาน
และการเปลี่ยนชื่อ command-auth → command-status โปรดดู
การย้าย Plugin SDK → รายการที่เลิกใช้งานอยู่
เนื้อหาที่เกี่ยวข้อง
- การย้าย Plugin SDK - รายการที่เลิกใช้งานอยู่และกำหนดเวลาการนำออก
- การสร้าง Plugin
- ภาพรวม Plugin SDK
- จุดเริ่มต้นของ Plugin
- ฮุกภายใน
- รายละเอียดภายในของสถาปัตยกรรม Plugin