Plugins
รายละเอียดภายในของสถาปัตยกรรม Plugin
สำหรับโมเดลความสามารถสาธารณะ รูปแบบ Plugin และสัญญาความเป็นเจ้าของ/การดำเนินการ ให้ดู สถาปัตยกรรม Plugin หน้านี้เป็นเอกสารอ้างอิงสำหรับกลไกภายใน: ลำดับการโหลด, รีจิสทรี, ฮุก runtime, เส้นทาง HTTP ของ Gateway, เส้นทาง import และตาราง schema
ลำดับการโหลด
เมื่อเริ่มต้น OpenClaw จะทำโดยประมาณดังนี้:
- ค้นหารากของ Plugin ที่เป็นตัวเลือก
- อ่าน manifest ของบันเดิลแบบ native หรือแบบเข้ากันได้ และ metadata ของแพ็กเกจ
- ปฏิเสธตัวเลือกที่ไม่ปลอดภัย
- ทำให้ config ของ Plugin เป็นรูปแบบมาตรฐาน (
plugins.enabled,allow,deny,entries,slots,load.paths) - ตัดสินการเปิดใช้งานสำหรับแต่ละตัวเลือก
- โหลดโมดูล native ที่เปิดใช้งาน: โมดูลที่บันเดิลมาและ build แล้วใช้ตัวโหลด native; ซอร์ส TypeScript ในเครื่องจากบุคคลที่สามใช้ fallback ฉุกเฉินของ Jiti
- เรียกฮุก native
register(api)และรวบรวมการลงทะเบียนเข้าไปในรีจิสทรี Plugin - เปิดเผยรีจิสทรีให้คำสั่งและพื้นผิว runtime ใช้งาน
เกตความปลอดภัยจะเกิดขึ้น ก่อน การดำเนินการ runtime ตัวเลือกจะถูกบล็อก เมื่อ entry หลุดออกจากรากของ Plugin, path เขียนได้โดยผู้ใช้ทั้งหมด, หรือความเป็นเจ้าของ path ดูน่าสงสัยสำหรับ Plugin ที่ไม่ได้บันเดิลมา
ตัวเลือกที่ถูกบล็อกยังคงผูกกับ id ของ Plugin เพื่อการวินิจฉัย หาก config ยังอ้างถึง id นั้น การตรวจสอบความถูกต้องจะรายงานว่า Plugin มีอยู่แต่ถูกบล็อก และชี้กลับไปที่คำเตือนความปลอดภัยของ path แทนที่จะถือว่า entry ของ config ล้าสมัย
พฤติกรรมที่ยึด manifest เป็นหลัก
manifest คือแหล่งความจริงของ control plane OpenClaw ใช้สิ่งนี้เพื่อ:
- ระบุ Plugin
- ค้นหา channels/skills/config schema ที่ประกาศไว้ หรือความสามารถของบันเดิล
- ตรวจสอบ
plugins.entries.<id>.config - เพิ่มเติม label/placeholder ของ Control UI
- แสดง metadata สำหรับการติดตั้ง/แคตตาล็อก
- รักษา descriptor สำหรับการเปิดใช้งานและการตั้งค่าที่มีต้นทุนต่ำ โดยไม่ต้องโหลด runtime ของ Plugin
สำหรับ Plugin แบบ native โมดูล runtime คือส่วน data plane โมดูลนี้ลงทะเบียน พฤติกรรมจริง เช่น ฮุก, เครื่องมือ, คำสั่ง หรือ flow ของ provider
บล็อก activation และ setup แบบไม่บังคับใน manifest จะอยู่บน control plane
ต่อไป ทั้งสองเป็น descriptor แบบ metadata-only สำหรับการวางแผนการเปิดใช้งานและการค้นหาการตั้งค่า;
ไม่ได้แทนที่การลงทะเบียน runtime, register(...), หรือ setupEntry
ผู้บริโภคการเปิดใช้งานจริงรายแรกตอนนี้ใช้ hint ของคำสั่ง, channel และ provider จาก manifest
เพื่อจำกัดการโหลด Plugin ก่อนการ materialize รีจิสทรีที่กว้างกว่า:
- การโหลด CLI จำกัดให้เหลือ Plugin ที่เป็นเจ้าของคำสั่งหลักที่ร้องขอ
- การตั้งค่า channel/การ resolve Plugin จำกัดให้เหลือ Plugin ที่เป็นเจ้าของ id ของ channel ที่ร้องขอ
- การตั้งค่า/การ resolve runtime ของ provider แบบ explicit จำกัดให้เหลือ Plugin ที่เป็นเจ้าของ id ของ provider ที่ร้องขอ
- การวางแผนเริ่มต้น Gateway ใช้
activation.onStartupสำหรับ import ตอนเริ่มต้น และการ opt out จากการเริ่มต้นแบบ explicit; Plugin ที่ไม่มี metadata ตอนเริ่มต้นจะโหลดผ่าน trigger การเปิดใช้งานที่แคบกว่าเท่านั้น
การ preload runtime ตอน request ที่ขอ scope กว้าง all ยังคง derive
ชุด id ของ Plugin ที่มีผลแบบ explicit จาก config, การวางแผนเริ่มต้น, channel ที่กำหนดค่าไว้, slot และกฎ auto-enable หากชุดที่ derive ว่างเปล่า OpenClaw
จะโหลดรีจิสทรี runtime ว่างแทนการขยายไปยัง Plugin ทุกตัวที่ค้นพบได้
ตัววางแผนการเปิดใช้งานเปิดเผยทั้ง API แบบมีเฉพาะ ids สำหรับ caller เดิม และ
API แบบ plan สำหรับ diagnostics ใหม่ รายการใน plan รายงานว่าเหตุใด Plugin จึงถูกเลือก
โดยแยก hint ของตัววางแผน activation.* แบบ explicit ออกจาก fallback ความเป็นเจ้าของของ manifest
เช่น providers, channels, commandAliases, setup.providers,
contracts.tools และฮุก การแยกเหตุผลนั้นคือขอบเขตความเข้ากันได้:
metadata ของ Plugin เดิมยังคงทำงาน ขณะที่โค้ดใหม่สามารถตรวจพบ hint แบบกว้าง
หรือพฤติกรรม fallback ได้โดยไม่เปลี่ยนความหมายการโหลด runtime
ตอนนี้การค้นหาการตั้งค่าจะเลือก id ที่ descriptor เป็นเจ้าของ เช่น setup.providers และ
setup.cliBackends เพื่อจำกัด Plugin ที่เป็นตัวเลือก ก่อน fallback ไปที่
setup-api สำหรับ Plugin ที่ยังต้องใช้ฮุก runtime ตอนตั้งค่า รายการตั้งค่า
provider ใช้ providerAuthChoices ใน manifest, ตัวเลือกการตั้งค่าที่ derive จาก descriptor
และ metadata ของ install-catalog โดยไม่โหลด runtime ของ provider
setup.requiresRuntime: false แบบ explicit คือจุดตัดแบบ descriptor-only; การละเว้น
requiresRuntime จะคง fallback setup-api แบบเดิมไว้เพื่อความเข้ากันได้ หากมี Plugin
ที่ค้นพบมากกว่าหนึ่งตัว claim id ของ provider หรือ CLI backend สำหรับการตั้งค่าที่ normalize แล้วเหมือนกัน
การค้นหาการตั้งค่าจะปฏิเสธเจ้าของที่กำกวมแทนการพึ่งพาลำดับการค้นพบ
เมื่อ runtime ของการตั้งค่าถูกเรียกใช้ diagnostics ของรีจิสทรีจะรายงาน drift
ระหว่าง setup.providers / setup.cliBackends กับ provider หรือ CLI
backend ที่ลงทะเบียนโดย setup-api โดยไม่บล็อก Plugin เดิม
ขอบเขต cache ของ Plugin
OpenClaw ไม่ cache ผลการค้นหา Plugin หรือข้อมูลรีจิสทรี manifest โดยตรง ไว้หลังช่วงเวลาตามนาฬิกา การติดตั้ง, การแก้ไข manifest และการเปลี่ยน load-path ต้องมองเห็นได้ในการอ่าน metadata แบบ explicit ครั้งถัดไป หรือการ rebuild snapshot ครั้งถัดไป parser ของไฟล์ manifest อาจเก็บ cache แบบจำกัดตามลายเซ็นไฟล์ โดย keyed จาก path ของ manifest ที่เปิด, inode, size และ timestamp; cache นั้นมีไว้เพียงเพื่อหลีกเลี่ยง การ parse byte ที่ไม่เปลี่ยนแปลงซ้ำ และต้องไม่ cache คำตอบด้านการค้นหา, รีจิสทรี, เจ้าของ หรือ policy
fast path ของ metadata ที่ปลอดภัยคือความเป็นเจ้าของ object แบบ explicit ไม่ใช่ cache ที่ซ่อนอยู่
hot path ตอนเริ่มต้น Gateway ควรส่ง PluginMetadataSnapshot ปัจจุบัน,
PluginLookUpTable ที่ derive แล้ว หรือรีจิสทรี manifest แบบ explicit ผ่าน call
chain การตรวจสอบ config, startup auto-enable, การ bootstrap Plugin และการเลือก provider
สามารถใช้ object เหล่านั้นซ้ำได้ตราบใดที่ object เหล่านั้นแทน config และ
inventory ของ Plugin ปัจจุบัน การค้นหาการตั้งค่ายังคงสร้าง metadata manifest ใหม่ตามต้องการ
เว้นแต่ path การตั้งค่าเฉพาะจะได้รับรีจิสทรี manifest แบบ explicit; ให้คงสิ่งนั้น
เป็น fallback ของ cold path แทนการเพิ่ม cache ค้นหาที่ซ่อนอยู่ เมื่อ input
เปลี่ยน ให้ rebuild และแทนที่ snapshot แทนการ mutate snapshot หรือเก็บ
สำเนาประวัติไว้
view เหนือรีจิสทรี Plugin ที่ active และ helper การ bootstrap channel ที่บันเดิลมา
ควรถูกคำนวณใหม่จากรีจิสทรี/root ปัจจุบัน map อายุสั้นใช้ได้
ภายในหนึ่ง call เพื่อ dedupe งานหรือป้องกัน reentry; ต้องไม่กลายเป็น cache
metadata ของ process
สำหรับการโหลด Plugin ชั้น cache แบบ persistent คือการโหลด runtime ชั้นนี้อาจใช้ สถานะของ loader ซ้ำเมื่อโค้ดหรือ artifact ที่ติดตั้งถูกโหลดจริง เช่น:
PluginLoaderCacheStateและรีจิสทรี runtime ที่ active และเข้ากันได้- cache ของ jiti/module และ cache ของตัวโหลด public-surface ที่ใช้เพื่อหลีกเลี่ยงการ import พื้นผิว runtime เดิมซ้ำๆ
- cache ของ filesystem สำหรับ artifact ของ Plugin ที่ติดตั้งแล้ว
- map อายุสั้นต่อ call สำหรับการ normalize path หรือการ resolve รายการซ้ำ
cache เหล่านั้นเป็นรายละเอียด implementation ของ data plane ต้องไม่ตอบ คำถามของ control plane เช่น "Plugin ใดเป็นเจ้าของ provider นี้?" เว้นแต่ caller จะขอการโหลด runtime โดยเจตนา
อย่าเพิ่ม cache แบบ persistent หรือตามนาฬิกาสำหรับ:
- ผลการค้นหา
- รีจิสทรี manifest โดยตรง
- รีจิสทรี manifest ที่สร้างขึ้นใหม่จาก index ของ Plugin ที่ติดตั้งแล้ว
- การค้นหาเจ้าของ provider, การ suppress model, policy ของ provider หรือ metadata public-artifact
- คำตอบอื่นใดที่ derive จาก manifest ซึ่ง manifest, index ที่ติดตั้งแล้ว หรือ load path ที่เปลี่ยนไปควรมองเห็นได้ในการอ่าน metadata ครั้งถัดไป
caller ที่ rebuild metadata ของ manifest จาก index ของ Plugin ที่ติดตั้งแล้วซึ่ง persist อยู่ จะสร้างรีจิสทรีนั้นใหม่ตามต้องการ index ที่ติดตั้งแล้วคือสถานะ source-plane ที่ durable; ไม่ใช่ cache metadata ใน process ที่ซ่อนอยู่
โมเดลรีจิสทรี
Plugin ที่โหลดแล้วไม่ได้ mutate global แบบสุ่มของ core โดยตรง แต่ลงทะเบียนเข้าไปใน รีจิสทรี Plugin ส่วนกลาง
รีจิสทรีติดตาม:
- record ของ Plugin (identity, source, origin, status, diagnostics)
- เครื่องมือ
- ฮุกเดิมและฮุกแบบ typed
- channel
- provider
- handler ของ Gateway RPC
- เส้นทาง HTTP
- registrar ของ CLI
- service เบื้องหลัง
- คำสั่งที่ Plugin เป็นเจ้าของ
จากนั้นฟีเจอร์ core จะอ่านจากรีจิสทรีนั้นแทนการคุยกับโมดูล Plugin โดยตรง วิธีนี้ทำให้การโหลดเป็นแบบทางเดียว:
- โมดูล Plugin -> การลงทะเบียนรีจิสทรี
- runtime ของ core -> การบริโภครีจิสทรี
การแยกนี้สำคัญต่อการบำรุงรักษา หมายความว่าพื้นผิว core ส่วนใหญ่ต้องการ จุด integration เพียงจุดเดียว: "อ่านรีจิสทรี" ไม่ใช่ "ทำ special-case ให้ทุกโมดูล Plugin"
callback การผูก conversation
Plugin ที่ผูก conversation สามารถตอบสนองเมื่อ approval ถูก resolve ได้
ใช้ api.onConversationBindingResolved(...) เพื่อรับ callback หลังจาก request การผูก
ได้รับการอนุมัติหรือปฏิเสธ:
export default {
id: "my-plugin",
register(api) {
api.onConversationBindingResolved(async (event) => {
if (event.status === "approved") {
// A binding now exists for this plugin + conversation.
console.log(event.binding?.conversationId);
return;
}
// The request was denied; clear any local pending state.
console.log(event.request.conversation.conversationId);
});
},
};
ฟิลด์ของ payload callback:
status:"approved"หรือ"denied"decision:"allow-once","allow-always"หรือ"deny"binding: binding ที่ resolve แล้วสำหรับ request ที่อนุมัติrequest: สรุป request เดิม, detach hint, id ของ sender และ metadata ของ conversation
callback นี้เป็นการแจ้งเตือนเท่านั้น ไม่ได้เปลี่ยนว่าใครได้รับอนุญาตให้ผูก conversation และจะทำงานหลังจาก core จัดการ approval เสร็จแล้ว
ฮุก runtime ของ provider
Plugin ของ provider มีสามชั้น:
- metadata ของ manifest สำหรับการค้นหาก่อน runtime ที่มีต้นทุนต่ำ:
setup.providers[].envVars, ความเข้ากันได้แบบ deprecatedproviderAuthEnvVars,providerAuthAliases,providerAuthChoicesและchannelEnvVars - ฮุกตอน config:
catalog(discoveryเดิม) รวมถึงapplyConfigDefaults - ฮุก runtime: ฮุกแบบ optional มากกว่า 40 รายการ ครอบคลุม auth, การ resolve model, การ wrap stream, ระดับ thinking, policy การ replay และ endpoint การใช้งาน ดู รายการเต็มใต้ ลำดับและการใช้งานฮุก
OpenClaw ยังคงเป็นเจ้าของ loop ของ agent แบบทั่วไป, failover, การจัดการ transcript และ policy ของเครื่องมือ ฮุกเหล่านี้คือพื้นผิว extension สำหรับพฤติกรรมเฉพาะ provider โดยไม่จำเป็นต้องมี inference transport แบบกำหนดเองทั้งชุด
ใช้ setup.providers[].envVars ใน manifest เมื่อ provider มี credential ที่อิง env
ซึ่ง path auth/status/model-picker แบบทั่วไปควรมองเห็นโดยไม่ต้องโหลด runtime ของ Plugin
providerAuthEnvVars ที่ deprecated แล้วยังคงถูกอ่านโดย compatibility adapter
ในช่วง deprecation window และ Plugin ที่ไม่ได้บันเดิลมาซึ่งใช้สิ่งนี้จะได้รับ
diagnostic ของ manifest ใช้ providerAuthAliases ใน manifest เมื่อ id ของ provider
หนึ่งควร reuse env var, auth profile, auth ที่ backing ด้วย config และตัวเลือก
onboarding สำหรับ API key ของ id provider อีกตัว ใช้ providerAuthChoices ใน manifest
เมื่อพื้นผิว onboarding/auth-choice ของ CLI ควรรู้ id ของตัวเลือก provider, label ของ group
และการต่อ wiring auth แบบ one-flag ง่ายๆ โดยไม่โหลด runtime ของ provider เก็บ
envVars ของ runtime provider ไว้สำหรับ hint ที่ operator เห็น เช่น label ของ onboarding
หรือ setup var ของ OAuth client-id/client-secret
ใช้ channelEnvVars ใน manifest เมื่อ channel มี auth หรือการตั้งค่าที่ขับเคลื่อนด้วย env
ซึ่ง shell-env fallback แบบทั่วไป, การตรวจสอบ config/status หรือ prompt การตั้งค่าควรมองเห็น
โดยไม่โหลด runtime ของ channel
ลำดับและการใช้งานฮุก
สำหรับ Plugin ของ model/provider OpenClaw จะเรียกฮุกในลำดับคร่าวๆ นี้
คอลัมน์ "ควรใช้เมื่อใด" คือคู่มือตัดสินใจแบบเร็ว
ฟิลด์ provider แบบ compatibility-only ที่ OpenClaw ไม่เรียกใช้อีกแล้ว เช่น
ProviderPlugin.capabilities และ suppressBuiltInModel ตั้งใจไม่ถูก
แสดงไว้ที่นี่
| # | ฮุก | สิ่งที่ทำ | ควรใช้เมื่อใด |
|---|---|---|---|
| 1 | catalog |
เผยแพร่การกำหนดค่าผู้ให้บริการลงใน models.providers ระหว่างการสร้าง models.json |
ผู้ให้บริการเป็นเจ้าของแค็ตตาล็อกหรือค่าเริ่มต้นของ URL ฐาน |
| 2 | applyConfigDefaults |
ใช้ค่าเริ่มต้นการกำหนดค่าส่วนกลางที่ผู้ให้บริการเป็นเจ้าของระหว่างการทำให้การกำหนดค่าเป็นรูปธรรม | ค่าเริ่มต้นขึ้นกับโหมด auth, env หรือ semantics ของตระกูลโมเดลของผู้ให้บริการ |
| -- | (การค้นหาโมเดลในตัว) | OpenClaw ลองใช้เส้นทาง registry/catalog ปกติก่อน | (ไม่ใช่ฮุกของ Plugin) |
| 3 | normalizeModelId |
ทำให้นามแฝง model-id แบบ legacy หรือ preview เป็นมาตรฐานก่อนค้นหา | ผู้ให้บริการเป็นเจ้าของการล้างนามแฝงก่อนการ resolve โมเดล canonical |
| 4 | normalizeTransport |
ทำให้ api / baseUrl ของตระกูลผู้ให้บริการเป็นมาตรฐานก่อนประกอบโมเดลแบบ generic |
ผู้ให้บริการเป็นเจ้าของการล้าง transport สำหรับรหัสผู้ให้บริการแบบกำหนดเองในตระกูล transport เดียวกัน |
| 5 | normalizeConfig |
ทำให้ models.providers.<id> เป็นมาตรฐานก่อนการ resolve runtime/provider |
ผู้ให้บริการต้องล้าง config ที่ควรอยู่กับ Plugin; ตัวช่วยตระกูล Google ที่บันเดิลมายังเป็น backstop ให้รายการ config ของ Google ที่รองรับด้วย |
| 6 | applyNativeStreamingUsageCompat |
ใช้การเขียน compat ของ native streaming-usage ใหม่กับผู้ให้บริการ config | ผู้ให้บริการต้องแก้ metadata การใช้ native streaming ที่ขับเคลื่อนโดย endpoint |
| 7 | resolveConfigApiKey |
Resolve auth แบบ env-marker สำหรับผู้ให้บริการ config ก่อนโหลด runtime auth | ผู้ให้บริการมีการ resolve API-key แบบ env-marker ที่ผู้ให้บริการเป็นเจ้าของ; amazon-bedrock ยังมีตัว resolve AWS env-marker ในตัวที่นี่ |
| 8 | resolveSyntheticAuth |
แสดง auth แบบ local/self-hosted หรือ config-backed โดยไม่ persist plaintext | ผู้ให้บริการสามารถทำงานด้วย marker credential แบบ synthetic/local |
| 9 | resolveExternalAuthProfiles |
Overlay โปรไฟล์ auth ภายนอกที่ผู้ให้บริการเป็นเจ้าของ; ค่าเริ่มต้นของ persistence คือ runtime-only สำหรับ credential ที่ CLI/app เป็นเจ้าของ |
ผู้ให้บริการนำ credential auth ภายนอกมาใช้ซ้ำโดยไม่ persist refresh token ที่คัดลอกมา; ประกาศ contracts.externalAuthProviders ใน manifest |
| 10 | shouldDeferSyntheticProfileAuth |
ลดลำดับความสำคัญของ placeholder โปรไฟล์ synthetic ที่จัดเก็บไว้ไว้หลัง auth แบบ env/config-backed | ผู้ให้บริการจัดเก็บโปรไฟล์ placeholder แบบ synthetic ที่ไม่ควรชนะลำดับความสำคัญ |
| 11 | resolveDynamicModel |
Fallback แบบ sync สำหรับรหัสโมเดลที่ผู้ให้บริการเป็นเจ้าของซึ่งยังไม่อยู่ใน registry ภายในเครื่อง | ผู้ให้บริการยอมรับรหัสโมเดล upstream ใดก็ได้ |
| 12 | prepareDynamicModel |
Warm-up แบบ async แล้ว resolveDynamicModel จะทำงานอีกครั้ง |
ผู้ให้บริการต้องใช้ metadata จากเครือข่ายก่อน resolve รหัสที่ไม่รู้จัก |
| 13 | normalizeResolvedModel |
เขียนใหม่ครั้งสุดท้ายก่อน embedded runner ใช้โมเดลที่ resolve แล้ว | ผู้ให้บริการต้องเขียน transport ใหม่แต่ยังใช้ transport หลัก |
| 14 | contributeResolvedModelCompat |
เพิ่มธง compat สำหรับโมเดลของผู้ขายที่อยู่หลัง transport อื่นที่เข้ากันได้ | ผู้ให้บริการรู้จักโมเดลของตนเองบน proxy transport โดยไม่เข้าควบคุมผู้ให้บริการ |
| 15 | normalizeToolSchemas |
ทำให้ schema ของเครื่องมือเป็นมาตรฐานก่อนที่ embedded runner จะเห็น | ผู้ให้บริการต้องล้าง schema ของตระกูล transport |
| 16 | inspectToolSchemas |
แสดง diagnostics ของ schema ที่ผู้ให้บริการเป็นเจ้าของหลัง normalization | ผู้ให้บริการต้องการคำเตือน keyword โดยไม่สอนกฎเฉพาะผู้ให้บริการให้ core |
| 17 | resolveReasoningOutputMode |
เลือก contract ของ reasoning-output แบบ native หรือ tagged | ผู้ให้บริการต้องใช้ reasoning/final output แบบ tagged แทนฟิลด์ native |
| 18 | prepareExtraParams |
ทำให้ request-param เป็นมาตรฐานก่อน wrapper ตัวเลือก stream แบบ generic | ผู้ให้บริการต้องใช้ request params เริ่มต้นหรือการล้าง param เฉพาะผู้ให้บริการ |
| 19 | createStreamFn |
แทนที่เส้นทาง stream ปกติทั้งหมดด้วย transport แบบกำหนดเอง | ผู้ให้บริการต้องใช้ wire protocol แบบกำหนดเอง ไม่ใช่แค่ wrapper |
| 20 | wrapStreamFn |
Wrapper ของ stream หลังใช้ wrapper แบบ generic แล้ว | ผู้ให้บริการต้องใช้ wrapper ของ request headers/body/model compat โดยไม่ใช้ transport แบบกำหนดเอง |
| 21 | resolveTransportTurnState |
แนบ header หรือ metadata ของ transport ต่อ turn แบบ native | ผู้ให้บริการต้องการให้ transport แบบ generic ส่ง identity ของ turn แบบ native ของผู้ให้บริการ |
| 22 | resolveWebSocketSessionPolicy |
แนบ header ของ WebSocket แบบ native หรือนโยบาย cool-down ของ session | ผู้ให้บริการต้องการให้ transport WS แบบ generic ปรับ header ของ session หรือนโยบาย fallback |
| 23 | formatApiKey |
ตัวจัดรูปแบบ auth-profile: โปรไฟล์ที่จัดเก็บไว้กลายเป็นสตริง apiKey ของ runtime |
ผู้ให้บริการจัดเก็บ metadata auth เพิ่มเติมและต้องการรูปทรง token ของ runtime แบบกำหนดเอง |
| 24 | refreshOAuth |
Override การ refresh OAuth สำหรับ endpoint refresh แบบกำหนดเองหรือนโยบาย refresh-failure | ผู้ให้บริการไม่เข้ากับ refresher pi-ai ที่ใช้ร่วมกัน |
| 25 | buildAuthDoctorHint |
คำแนะนำการซ่อมแซมที่ต่อท้ายเมื่อการ refresh OAuth ล้มเหลว | ผู้ให้บริการต้องการคำแนะนำการซ่อม auth ที่ผู้ให้บริการเป็นเจ้าของหลัง refresh ล้มเหลว |
| 26 | matchesContextOverflowError |
Matcher context-window overflow ที่ผู้ให้บริการเป็นเจ้าของ | ผู้ให้บริการมีข้อผิดพลาด overflow ดิบที่ heuristic แบบ generic จะพลาด |
| 27 | classifyFailoverReason |
การจัดประเภทเหตุผล failover ที่ผู้ให้บริการเป็นเจ้าของ | ผู้ให้บริการสามารถ map ข้อผิดพลาด API/transport ดิบเป็น rate-limit/overload/etc |
| 28 | isCacheTtlEligible |
นโยบาย prompt-cache สำหรับผู้ให้บริการ proxy/backhaul | ผู้ให้บริการต้อง gating TTL ของ cache เฉพาะ proxy |
| 29 | buildMissingAuthMessage |
ข้อความแทนที่สำหรับข้อความ recovery missing-auth แบบ generic | ผู้ให้บริการต้องการคำแนะนำ recovery missing-auth เฉพาะผู้ให้บริการ |
| 30 | augmentModelCatalog |
แถวแค็ตตาล็อก synthetic/final ที่ต่อท้ายหลัง discovery | ผู้ให้บริการต้องการแถว forward-compat แบบ synthetic ใน models list และตัวเลือก |
| 31 | resolveThinkingProfile |
ชุดระดับ /think เฉพาะโมเดล ป้ายกำกับแสดงผล และค่าเริ่มต้น |
ผู้ให้บริการเปิดเผยลำดับ thinking แบบกำหนดเองหรือป้ายกำกับแบบไบนารีสำหรับโมเดลที่เลือก |
| 32 | isBinaryThinking |
ฮุกความเข้ากันได้ของ toggle reasoning แบบเปิด/ปิด | ผู้ให้บริการเปิดเผยเพียง thinking แบบไบนารีเปิด/ปิด |
| 33 | supportsXHighThinking |
ฮุกความเข้ากันได้ของการรองรับ reasoning xhigh |
ผู้ให้บริการต้องการ xhigh เฉพาะบางโมเดล |
| 34 | resolveDefaultThinkingLevel |
ฮุกความเข้ากันได้ของระดับ /think เริ่มต้น |
ผู้ให้บริการเป็นเจ้าของนโยบาย /think เริ่มต้นสำหรับตระกูลโมเดล |
| 35 | isModernModelRef |
Matcher โมเดลสมัยใหม่สำหรับตัวกรองโปรไฟล์ live และการเลือก smoke | ผู้ให้บริการเป็นเจ้าของการจับคู่ preferred-model สำหรับ live/smoke |
| 36 | prepareRuntimeAuth |
แลกเปลี่ยน credential ที่กำหนดค่าไว้เป็น token/key ของ runtime จริงก่อน inference | ผู้ให้บริการต้องใช้การแลกเปลี่ยน token หรือ credential คำขออายุสั้น |
| 37 | resolveUsageAuth |
แก้ไขข้อมูลรับรองการใช้งาน/การเรียกเก็บเงินสำหรับ /usage และพื้นผิวสถานะที่เกี่ยวข้อง |
ผู้ให้บริการต้องการการแยกวิเคราะห์โทเค็นการใช้งาน/โควตาแบบกำหนดเอง หรือข้อมูลรับรองการใช้งานที่แตกต่างกัน |
| 38 | fetchUsageSnapshot |
ดึงและปรับสแนปช็อตการใช้งาน/โควตาเฉพาะผู้ให้บริการให้เป็นรูปแบบมาตรฐานหลังจากแก้ไข auth แล้ว | ผู้ให้บริการต้องการ endpoint การใช้งานหรือ parser payload เฉพาะผู้ให้บริการ |
| 39 | createEmbeddingProvider |
สร้าง adapter embedding ที่ผู้ให้บริการเป็นเจ้าของสำหรับหน่วยความจำ/การค้นหา | พฤติกรรม embedding ของหน่วยความจำเป็นของ Plugin ผู้ให้บริการ |
| 40 | buildReplayPolicy |
ส่งคืนนโยบาย replay ที่ควบคุมการจัดการ transcript สำหรับผู้ให้บริการ | ผู้ให้บริการต้องการนโยบาย transcript แบบกำหนดเอง (เช่น การตัด thinking-block ออก) |
| 41 | sanitizeReplayHistory |
เขียนประวัติ replay ใหม่หลังจากการล้าง transcript แบบทั่วไป | ผู้ให้บริการต้องการการเขียน replay ใหม่เฉพาะผู้ให้บริการ นอกเหนือจากตัวช่วย Compaction ที่ใช้ร่วมกัน |
| 42 | validateReplayTurns |
ตรวจสอบความถูกต้องหรือปรับรูปแบบ replay-turn ขั้นสุดท้ายก่อนตัวรันแบบฝัง | ทรานสปอร์ตของผู้ให้บริการต้องการการตรวจสอบ turn ที่เข้มงวดยิ่งขึ้นหลังการทำความสะอาดแบบทั่วไป |
| 43 | onModelSelected |
เรียกใช้ side effect หลังการเลือกที่ผู้ให้บริการเป็นเจ้าของ | ผู้ให้บริการต้องการ telemetry หรือสถานะที่ผู้ให้บริการเป็นเจ้าของเมื่อโมเดลกลายเป็นรายการที่ใช้งานอยู่ |
normalizeModelId, normalizeTransport และ normalizeConfig จะตรวจสอบ
Plugin ของผู้ให้บริการที่ตรงกันก่อน จากนั้นจึงไล่ไปยัง Plugin ผู้ให้บริการอื่นที่รองรับ hook
จนกว่าจะมีตัวใดเปลี่ยนรหัสโมเดลหรือ transport/config จริง วิธีนี้ทำให้
shim ของผู้ให้บริการสำหรับ alias/compat ยังทำงานได้โดยผู้เรียกไม่ต้องรู้ว่า
Plugin ที่รวมมาด้วยตัวใดเป็นเจ้าของการเขียนใหม่นั้น หากไม่มี hook ของผู้ให้บริการใดเขียนรายการ config
ตระกูล Google ที่รองรับใหม่ ตัวปรับ Google config normalizer ที่รวมมาด้วยจะยังคงใช้
การล้างข้อมูลเพื่อความเข้ากันได้นั้น
หากผู้ให้บริการต้องการ wire protocol แบบกำหนดเองทั้งหมดหรือ request executor แบบกำหนดเอง นั่นเป็นส่วนขยายอีกประเภทหนึ่ง hook เหล่านี้มีไว้สำหรับพฤติกรรมของผู้ให้บริการ ที่ยังคงทำงานบน inference loop ปกติของ OpenClaw
ตัวอย่างผู้ให้บริการ
api.registerProvider({
id: "example-proxy",
label: "Example Proxy",
auth: [],
catalog: {
order: "simple",
run: async (ctx) => {
const apiKey = ctx.resolveProviderApiKey("example-proxy").apiKey;
if (!apiKey) {
return null;
}
return {
provider: {
baseUrl: "https://proxy.example.com/v1",
apiKey,
api: "openai-completions",
models: [{ id: "auto", name: "Auto" }],
},
};
},
},
resolveDynamicModel: (ctx) => ({
id: ctx.modelId,
name: ctx.modelId,
provider: "example-proxy",
api: "openai-completions",
baseUrl: "https://proxy.example.com/v1",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 8192,
}),
prepareRuntimeAuth: async (ctx) => {
const exchanged = await exchangeToken(ctx.apiKey);
return {
apiKey: exchanged.token,
baseUrl: exchanged.baseUrl,
expiresAt: exchanged.expiresAt,
};
},
resolveUsageAuth: async (ctx) => {
const auth = await ctx.resolveOAuthToken();
return auth ? { token: auth.token } : null;
},
fetchUsageSnapshot: async (ctx) => {
return await fetchExampleProxyUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn);
},
});
ตัวอย่างที่มีในตัว
Plugin ผู้ให้บริการที่รวมมาด้วยผสาน hook ข้างต้นเพื่อให้เหมาะกับ catalog,
auth, thinking, replay และความต้องการด้าน usage ของผู้ขายแต่ละราย ชุด hook ที่เป็นแหล่งอ้างอิงจริงอยู่กับ
แต่ละ Plugin ภายใต้ extensions/; หน้านี้แสดงรูปแบบแทนการ
ทำสำเนารายการทั้งหมด
Pass-through catalog providers
OpenRouter, Kilocode, Z.AI, xAI ลงทะเบียน catalog ร่วมกับ
resolveDynamicModel / prepareDynamicModel เพื่อให้แสดงรหัสโมเดลจาก upstream
ก่อน catalog แบบคงที่ของ OpenClaw ได้
OAuth and usage endpoint providers
GitHub Copilot, Gemini CLI, ChatGPT Codex, MiniMax, Xiaomi, z.ai จับคู่
prepareRuntimeAuth หรือ formatApiKey กับ resolveUsageAuth +
fetchUsageSnapshot เพื่อเป็นเจ้าของการแลก token และการผสาน /usage
Replay and transcript cleanup families
กลุ่มที่มีชื่อร่วมกัน (google-gemini, passthrough-gemini,
anthropic-by-model, hybrid-anthropic-openai) ช่วยให้ผู้ให้บริการเลือกใช้
นโยบาย transcript ผ่าน buildReplayPolicy แทนที่แต่ละ Plugin
จะต้องสร้างการล้างข้อมูลซ้ำเอง
Catalog-only providers
byteplus, cloudflare-ai-gateway, huggingface, kimi-coding, nvidia,
qianfan, synthetic, together, venice, vercel-ai-gateway และ
volcengine ลงทะเบียนเพียง catalog และใช้ inference loop ร่วม
Anthropic-specific stream helpers
Beta headers, /fast / serviceTier และ context1m อยู่ภายใน seam สาธารณะ
api.ts / contract-api.ts ของ Anthropic Plugin
(wrapAnthropicProviderStream, resolveAnthropicBetas,
resolveAnthropicFastMode, resolveAnthropicServiceTier) ไม่ใช่ใน
SDK ทั่วไป
ตัวช่วย runtime
Plugin สามารถเข้าถึงตัวช่วย core ที่เลือกไว้ผ่าน api.runtime สำหรับ TTS:
const clip = await api.runtime.tts.textToSpeech({
text: "Hello from OpenClaw",
cfg: api.config,
});
const result = await api.runtime.tts.textToSpeechTelephony({
text: "Hello from OpenClaw",
cfg: api.config,
});
const voices = await api.runtime.tts.listVoices({
provider: "elevenlabs",
cfg: api.config,
});
หมายเหตุ:
textToSpeechส่งคืน payload เอาต์พุต TTS ของ core ปกติสำหรับพื้นผิวไฟล์/voice-note- ใช้ config
messages.ttsของ core และการเลือกผู้ให้บริการ - ส่งคืนบัฟเฟอร์เสียง PCM + sample rate Plugin ต้อง resample/encode สำหรับผู้ให้บริการเอง
listVoicesเป็นตัวเลือกตามผู้ให้บริการ ใช้สำหรับตัวเลือกเสียงหรือขั้นตอนตั้งค่าที่ผู้ขายเป็นเจ้าของ- รายการเสียงอาจรวม metadata ที่ละเอียดขึ้น เช่น locale, gender และแท็ก personality สำหรับตัวเลือกที่รู้จักผู้ให้บริการ
- OpenAI และ ElevenLabs รองรับ telephony ในปัจจุบัน Microsoft ไม่รองรับ
Plugin ยังสามารถลงทะเบียนผู้ให้บริการ speech ผ่าน api.registerSpeechProvider(...) ได้ด้วย
api.registerSpeechProvider({
id: "acme-speech",
label: "Acme Speech",
isConfigured: ({ config }) => Boolean(config.messages?.tts),
synthesize: async (req) => {
return {
audioBuffer: Buffer.from([]),
outputFormat: "mp3",
fileExtension: ".mp3",
voiceCompatible: false,
};
},
});
หมายเหตุ:
- เก็บนโยบาย TTS, fallback และการส่ง reply ไว้ใน core
- ใช้ผู้ให้บริการ speech สำหรับพฤติกรรม synthesis ที่ผู้ขายเป็นเจ้าของ
- อินพุต Microsoft
edgeแบบเดิมจะถูกปรับให้เป็นรหัสผู้ให้บริการmicrosoft - โมเดลความเป็นเจ้าของที่แนะนำคือแบบอิงบริษัท: Plugin ผู้ขายหนึ่งตัวสามารถเป็นเจ้าของ ผู้ให้บริการ text, speech, image และสื่อในอนาคตได้เมื่อ OpenClaw เพิ่ม สัญญาความสามารถเหล่านั้น
สำหรับการทำความเข้าใจ image/audio/video นั้น Plugin จะลงทะเบียนผู้ให้บริการ media-understanding แบบ typed หนึ่งตัวแทน key/value bag ทั่วไป:
api.registerMediaUnderstandingProvider({
id: "google",
capabilities: ["image", "audio", "video"],
describeImage: async (req) => ({ text: "..." }),
transcribeAudio: async (req) => ({ text: "..." }),
describeVideo: async (req) => ({ text: "..." }),
});
หมายเหตุ:
- เก็บ orchestration, fallback, config และการเชื่อม channel ไว้ใน core
- เก็บพฤติกรรมของผู้ขายไว้ใน Plugin ผู้ให้บริการ
- การขยายแบบเพิ่มเติมควรยังเป็น typed: เมธอด optional ใหม่ ฟิลด์ผลลัพธ์ optional ใหม่ ความสามารถ optional ใหม่
- การสร้างวิดีโอใช้รูปแบบเดียวกันอยู่แล้ว:
- core เป็นเจ้าของสัญญาความสามารถและ runtime helper
- Plugin ผู้ขายลงทะเบียน
api.registerVideoGenerationProvider(...) - Plugin ฟีเจอร์/channel ใช้
api.runtime.videoGeneration.*
สำหรับ runtime helper ของ media-understanding นั้น Plugin สามารถเรียก:
const image = await api.runtime.mediaUnderstanding.describeImageFile({
filePath: "/tmp/inbound-photo.jpg",
cfg: api.config,
agentDir: "/tmp/agent",
});
const video = await api.runtime.mediaUnderstanding.describeVideoFile({
filePath: "/tmp/inbound-video.mp4",
cfg: api.config,
});
สำหรับการถอดเสียง audio นั้น Plugin สามารถใช้ได้ทั้ง runtime ของ media-understanding หรือ alias STT เดิม:
const { text } = await api.runtime.mediaUnderstanding.transcribeAudioFile({
filePath: "/tmp/inbound-audio.ogg",
cfg: api.config,
// Optional when MIME cannot be inferred reliably:
mime: "audio/ogg",
});
หมายเหตุ:
api.runtime.mediaUnderstanding.*คือพื้นผิวร่วมที่แนะนำสำหรับ การทำความเข้าใจ image/audio/video- ใช้ config audio ของ media-understanding ใน core (
tools.media.audio) และลำดับ fallback ของผู้ให้บริการ - ส่งคืน
{ text: undefined }เมื่อไม่มีเอาต์พุตการถอดเสียงเกิดขึ้น (เช่น อินพุตถูกข้าม/ไม่รองรับ) api.runtime.stt.transcribeAudioFile(...)ยังคงอยู่เป็น alias เพื่อความเข้ากันได้
Plugin ยังสามารถเริ่มการรัน subagent เบื้องหลังผ่าน api.runtime.subagent ได้ด้วย:
const result = await api.runtime.subagent.run({
sessionKey: "agent:main:subagent:search-helper",
message: "Expand this query into focused follow-up searches.",
provider: "openai",
model: "gpt-4.1-mini",
deliver: false,
});
หมายเหตุ:
providerและmodelเป็น override ต่อการรันแบบ optional ไม่ใช่การเปลี่ยน session แบบถาวร- OpenClaw จะยอมรับฟิลด์ override เหล่านั้นเฉพาะสำหรับผู้เรียกที่เชื่อถือได้
- สำหรับการรัน fallback ที่ Plugin เป็นเจ้าของ ผู้ปฏิบัติงานต้องเลือกใช้ด้วย
plugins.entries.<id>.subagent.allowModelOverride: true - ใช้
plugins.entries.<id>.subagent.allowedModelsเพื่อจำกัด Plugin ที่เชื่อถือได้ให้อยู่ที่เป้าหมายprovider/modelแบบ canonical ที่เฉพาะเจาะจง หรือ"*"เพื่ออนุญาตเป้าหมายใด ๆ อย่างชัดเจน - การรัน subagent ของ Plugin ที่ไม่เชื่อถือได้ยังทำงานได้ แต่คำขอ override จะถูกปฏิเสธแทนการ fallback แบบเงียบ
- session ของ subagent ที่ Plugin สร้างจะถูกแท็กด้วยรหัส Plugin ที่สร้างขึ้น Fallback
api.runtime.subagent.deleteSession(...)อาจลบได้เฉพาะ session ที่เป็นเจ้าของเหล่านั้นเท่านั้น; การลบ session ใด ๆ โดยพลการยังต้องใช้คำขอ Gateway ที่มี scope แบบ admin
สำหรับการค้นหาเว็บนั้น Plugin สามารถใช้ runtime helper ร่วมแทน การเข้าถึง wiring ของ agent tool โดยตรง:
const providers = api.runtime.webSearch.listProviders({
config: api.config,
});
const result = await api.runtime.webSearch.search({
config: api.config,
args: {
query: "OpenClaw plugin runtime helpers",
count: 5,
},
});
Plugin ยังสามารถลงทะเบียนผู้ให้บริการ web-search ผ่าน
api.registerWebSearchProvider(...) ได้ด้วย
หมายเหตุ:
- เก็บการเลือกผู้ให้บริการ การแก้ไข credential และ semantics ของคำขอร่วมไว้ใน core
- ใช้ผู้ให้บริการ web-search สำหรับ search transport เฉพาะผู้ขาย
api.runtime.webSearch.*คือพื้นผิวร่วมที่แนะนำสำหรับ Plugin ฟีเจอร์/channel ที่ต้องการพฤติกรรมการค้นหาโดยไม่พึ่งพา wrapper ของ agent tool
api.runtime.imageGeneration
const result = await api.runtime.imageGeneration.generate({
config: api.config,
args: { prompt: "A friendly lobster mascot", size: "1024x1024" },
});
const providers = api.runtime.imageGeneration.listProviders({
config: api.config,
});
generate(...): สร้างรูปภาพโดยใช้ chain ของผู้ให้บริการ image-generation ที่ตั้งค่าไว้listProviders(...): แสดงรายการผู้ให้บริการ image-generation ที่พร้อมใช้งานและความสามารถของพวกเขา
เส้นทาง HTTP ของ Gateway
Plugin สามารถเปิดเผย endpoint HTTP ด้วย api.registerHttpRoute(...)
api.registerHttpRoute({
path: "/acme/webhook",
auth: "plugin",
match: "exact",
handler: async (_req, res) => {
res.statusCode = 200;
res.end("ok");
return true;
},
});
ฟิลด์ของ route:
path: เส้นทาง route ใต้เซิร์ฟเวอร์ HTTP ของ gatewayauth: จำเป็น ใช้"gateway"เพื่อกำหนดให้ต้องใช้ auth ปกติของ gateway หรือ"plugin"สำหรับการยืนยัน auth/webhook ที่ Plugin จัดการเองmatch: optional"exact"(ค่าเริ่มต้น) หรือ"prefix"replaceExisting: optional อนุญาตให้ Plugin เดียวกันแทนที่การลงทะเบียน route เดิมของตัวเองhandler: ส่งคืนtrueเมื่อ route จัดการคำขอแล้ว
หมายเหตุ:
api.registerHttpHandler(...)ถูกนำออกแล้วและจะทำให้เกิดข้อผิดพลาดในการโหลด Plugin ให้ใช้api.registerHttpRoute(...)แทน- เส้นทางของ Plugin ต้องประกาศ
authอย่างชัดเจน - ความขัดแย้งของ
path + matchแบบตรงกันทุกประการจะถูกปฏิเสธ เว้นแต่จะตั้งค่าreplaceExisting: trueและ Plugin หนึ่งไม่สามารถแทนที่เส้นทางของ Plugin อื่นได้ - เส้นทางที่ทับซ้อนกันซึ่งมีระดับ
authต่างกันจะถูกปฏิเสธ ให้ใช้สายโซ่ fallthrough ของexact/prefixเฉพาะในระดับ auth เดียวกันเท่านั้น - เส้นทาง
auth: "plugin"จะ ไม่ได้ รับสโคปรันไทม์ของผู้ปฏิบัติการโดยอัตโนมัติ เส้นทางเหล่านี้มีไว้สำหรับ Webhook/การตรวจสอบลายเซ็นที่ Plugin จัดการ ไม่ใช่การเรียกตัวช่วย Gateway ที่มีสิทธิ์สูง - เส้นทาง
auth: "gateway"ทำงานภายในสโคปรันไทม์ของคำขอ Gateway แต่สโคปนั้นตั้งใจให้มีข้อจำกัดอย่างรอบคอบ:- auth แบบ bearer ด้วยความลับร่วม (
gateway.auth.mode = "token"/"password") จะตรึงสโคปรันไทม์ของเส้นทาง Plugin ไว้ที่operator.writeแม้ว่าผู้เรียกจะส่งx-openclaw-scopesมาก็ตาม - โหมด HTTP ที่มีตัวตนที่เชื่อถือได้ (เช่น
trusted-proxyหรือgateway.auth.mode = "none"บน ingress ส่วนตัว) จะเคารพx-openclaw-scopesเฉพาะเมื่อมีส่วนหัวนี้อย่างชัดเจนเท่านั้น - หากไม่มี
x-openclaw-scopesในคำขอเส้นทาง Plugin ที่มีตัวตนเหล่านั้น สโคปรันไทม์จะย้อนกลับไปใช้operator.write
- auth แบบ bearer ด้วยความลับร่วม (
- กฎเชิงปฏิบัติ: อย่าถือว่าเส้นทาง Plugin ที่ใช้ gateway-auth เป็นพื้นผิวผู้ดูแลโดยปริยาย หากเส้นทางของคุณต้องการพฤติกรรมเฉพาะผู้ดูแล ให้กำหนดให้ใช้โหมด auth ที่มีตัวตน และจัดทำเอกสารสัญญาส่วนหัว
x-openclaw-scopesที่ชัดเจน
เส้นทางนำเข้าของ Plugin SDK
ใช้ subpath ของ SDK ที่แคบแทน barrel รากแบบรวมศูนย์ openclaw/plugin-sdk
เมื่อสร้าง Plugin ใหม่ subpath หลัก:
| Subpath | วัตถุประสงค์ |
|---|---|
openclaw/plugin-sdk/plugin-entry |
primitive สำหรับการลงทะเบียน Plugin |
openclaw/plugin-sdk/channel-core |
ตัวช่วยสำหรับ entry/build ของช่องทาง |
openclaw/plugin-sdk/core |
ตัวช่วยร่วมทั่วไปและสัญญาครอบคลุม |
openclaw/plugin-sdk/config-schema |
สกีมา Zod ของ openclaw.json ราก (OpenClawSchema) |
Plugin ช่องทางเลือกใช้จากชุด seam ที่แคบ ได้แก่ channel-setup,
setup-runtime, setup-adapter-runtime, setup-tools, channel-pairing,
channel-contract, channel-feedback, channel-inbound, channel-lifecycle,
channel-reply-pipeline, command-auth, secret-input, webhook-ingress,
channel-targets และ channel-actions พฤติกรรมการอนุมัติควรรวมศูนย์
ไว้ที่สัญญา approvalCapability เดียว แทนการผสมข้ามฟิลด์ Plugin
ที่ไม่เกี่ยวข้องกัน ดู Plugin ช่องทาง
ตัวช่วยรันไทม์และ config อยู่ภายใต้ subpath *-runtime ที่เจาะจงตรงกัน
(approval-runtime, agent-runtime, lazy-runtime, directory-runtime,
text-runtime, runtime-store, system-event-runtime, heartbeat-runtime,
channel-activity-runtime ฯลฯ) ควรใช้ config-types,
plugin-config-runtime, runtime-config-snapshot และ config-mutation
แทน barrel ความเข้ากันได้แบบกว้าง config-runtime
entry point ภายใน repo (ต่อรากแพ็กเกจ Plugin ที่บันเดิลมา):
index.js— entry ของ Plugin ที่บันเดิลมาapi.js— barrel ตัวช่วย/ชนิดข้อมูลruntime-api.js— barrel เฉพาะรันไทม์setup-entry.js— entry ของ Plugin ตั้งค่า
Plugin ภายนอกควรนำเข้าเฉพาะ subpath openclaw/plugin-sdk/* เท่านั้น ห้าม
นำเข้า src/* ของแพ็กเกจ Plugin อื่นจาก core หรือจาก Plugin อื่น
entry point ที่โหลดผ่าน facade จะเลือก snapshot config รันไทม์ที่ใช้งานอยู่เมื่อมีอยู่
จากนั้นจึง fallback ไปยังไฟล์ config ที่ resolve แล้วบนดิสก์
subpath เฉพาะ capability เช่น image-generation, media-understanding
และ speech มีอยู่เพราะ Plugin ที่บันเดิลมาใช้งานในปัจจุบัน สิ่งเหล่านี้ไม่ได้เป็น
สัญญาภายนอกที่ถูกตรึงระยะยาวโดยอัตโนมัติ ให้ตรวจสอบหน้าอ้างอิง SDK
ที่เกี่ยวข้องเมื่อพึ่งพาใช้งาน
สกีมาของเครื่องมือข้อความ
Plugin ควรเป็นเจ้าของการส่งเสริมสกีมา describeMessageTool(...)
เฉพาะช่องทางสำหรับ primitive ที่ไม่ใช่ข้อความ เช่น reactions, reads และ polls
การนำเสนอการส่งแบบร่วมควรใช้สัญญา MessagePresentation ทั่วไป
แทนฟิลด์ button, component, block หรือ card แบบ native ของผู้ให้บริการ
ดู การนำเสนอข้อความ สำหรับสัญญา
กฎ fallback การแมปผู้ให้บริการ และ checklist สำหรับผู้เขียน Plugin
Plugin ที่ส่งข้อความได้ประกาศสิ่งที่สามารถเรนเดอร์ผ่าน capability ของข้อความ:
presentationสำหรับบล็อกการนำเสนอเชิงความหมาย (text,context,divider,buttons,select)delivery-pinสำหรับคำขอปักหมุดการส่ง
Core ตัดสินใจว่าจะเรนเดอร์การนำเสนอแบบ native หรือ degrade เป็นข้อความ อย่าเปิดเผยช่องทางลัด UI แบบ native ของผู้ให้บริการจากเครื่องมือข้อความทั่วไป ตัวช่วย SDK ที่เลิกแนะนำแล้วสำหรับสกีมา native แบบเก่ายังคง export ไว้สำหรับ Plugin ภายนอกที่มีอยู่ แต่ Plugin ใหม่ไม่ควรใช้
การ resolve เป้าหมายช่องทาง
Plugin ช่องทางควรเป็นเจ้าของความหมายของเป้าหมายเฉพาะช่องทาง เก็บ host outbound แบบร่วมให้เป็นแบบทั่วไป และใช้พื้นผิว adapter การส่งข้อความสำหรับกฎของผู้ให้บริการ:
messaging.inferTargetChatType({ to })ตัดสินว่าเป้าหมายที่ normalize แล้ว ควรถูกมองเป็นdirect,groupหรือchannelก่อนค้นหาในไดเรกทอรีmessaging.targetResolver.looksLikeId(raw, normalized)บอก core ว่าอินพุต ควรข้ามตรงไปยังการ resolve แบบคล้าย id แทนการค้นหาไดเรกทอรีหรือไม่messaging.targetResolver.resolveTarget(...)เป็น fallback ของ Plugin เมื่อ core ต้องการการ resolve ขั้นสุดท้ายที่ผู้ให้บริการเป็นเจ้าของหลังการ normalize หรือหลังจาก ไม่พบในไดเรกทอรีmessaging.resolveOutboundSessionRoute(...)เป็นเจ้าของการสร้างเส้นทางเซสชัน เฉพาะผู้ให้บริการเมื่อ resolve เป้าหมายแล้ว
การแยกส่วนที่แนะนำ:
- ใช้
inferTargetChatTypeสำหรับการตัดสินหมวดหมู่ที่ควรเกิดขึ้นก่อน การค้นหา peers/groups - ใช้
looksLikeIdสำหรับการตรวจว่า "ให้ปฏิบัติต่อสิ่งนี้เป็น id เป้าหมายแบบชัดเจน/native" - ใช้
resolveTargetสำหรับ fallback การ normalize เฉพาะผู้ให้บริการ ไม่ใช่สำหรับ การค้นหาไดเรกทอรีแบบกว้าง - เก็บ id แบบ native ของผู้ให้บริการ เช่น chat ids, thread ids, JIDs, handles และ room
ids ไว้ภายในค่า
targetหรือพารามิเตอร์เฉพาะผู้ให้บริการ ไม่ใช่ในฟิลด์ SDK ทั่วไป
ไดเรกทอรีที่อิง config
Plugin ที่ derive รายการไดเรกทอรีจาก config ควรเก็บตรรกะนั้นไว้ใน
Plugin และใช้ตัวช่วยร่วมจาก
openclaw/plugin-sdk/directory-runtime
ใช้สิ่งนี้เมื่อช่องทางต้องการ peers/groups ที่อิง config เช่น:
- DM peers ที่ขับเคลื่อนด้วย allowlist
- แผนที่ช่องทาง/กลุ่มที่กำหนดค่าไว้
- fallback ไดเรกทอรีแบบ static ตามสโคปบัญชี
ตัวช่วยร่วมใน directory-runtime จัดการเฉพาะการดำเนินการทั่วไป:
- การกรอง query
- การใช้ limit
- ตัวช่วย dedupe/normalization
- การสร้าง
ChannelDirectoryEntry[]
การตรวจสอบบัญชีและการ normalize id เฉพาะช่องทางควรอยู่ใน การใช้งานของ Plugin
แค็ตตาล็อกผู้ให้บริการ
Plugin ผู้ให้บริการสามารถกำหนดแค็ตตาล็อกโมเดลสำหรับ inference ด้วย
registerProvider({ catalog: { run(...) { ... } } })
catalog.run(...) คืนค่ารูปทรงเดียวกับที่ OpenClaw เขียนลงใน
models.providers:
{ provider }สำหรับรายการผู้ให้บริการหนึ่งรายการ{ providers }สำหรับรายการผู้ให้บริการหลายรายการ
ใช้ catalog เมื่อ Plugin เป็นเจ้าของ id โมเดลเฉพาะผู้ให้บริการ ค่าเริ่มต้น base URL
หรือ metadata โมเดลที่ถูก gate ด้วย auth
catalog.order ควบคุมเวลาที่แค็ตตาล็อกของ Plugin merge เทียบกับผู้ให้บริการ implicit
ในตัวของ OpenClaw:
simple: ผู้ให้บริการที่ขับเคลื่อนด้วย API key หรือ env แบบธรรมดาprofile: ผู้ให้บริการที่ปรากฏเมื่อมีโปรไฟล์ authpaired: ผู้ให้บริการที่สังเคราะห์รายการผู้ให้บริการที่เกี่ยวข้องหลายรายการlate: pass สุดท้าย หลังผู้ให้บริการ implicit อื่น
ผู้ให้บริการภายหลังชนะเมื่อ key collision ดังนั้น Plugin สามารถ override รายการผู้ให้บริการในตัวที่มี provider id เดียวกันได้โดยตั้งใจ
ความเข้ากันได้:
discoveryยังทำงานเป็น alias รุ่นเก่า- หากลงทะเบียนทั้ง
catalogและdiscoveryแล้ว OpenClaw จะใช้catalog
การตรวจสอบช่องทางแบบอ่านอย่างเดียว
หาก Plugin ของคุณลงทะเบียนช่องทาง ควร implement
plugin.config.inspectAccount(cfg, accountId) ควบคู่กับ resolveAccount(...)
เหตุผล:
resolveAccount(...)เป็นเส้นทางรันไทม์ อนุญาตให้ถือว่า credential ถูก materialize ครบถ้วนแล้ว และสามารถล้มเหลวเร็วเมื่อขาด secret ที่จำเป็น- เส้นทางคำสั่งแบบอ่านอย่างเดียว เช่น
openclaw status,openclaw status --all,openclaw channels status,openclaw channels resolveและโฟลว์ doctor/config repair ไม่ควรต้อง materialize credential รันไทม์เพียงเพื่อ อธิบายการกำหนดค่า
พฤติกรรม inspectAccount(...) ที่แนะนำ:
- คืนเฉพาะสถานะบัญชีแบบบรรยาย
- คงค่า
enabledและconfigured - รวมฟิลด์แหล่งที่มา/สถานะ credential เมื่อเกี่ยวข้อง เช่น:
tokenSource,tokenStatusbotTokenSource,botTokenStatusappTokenSource,appTokenStatussigningSecretSource,signingSecretStatus
- คุณไม่จำเป็นต้องคืนค่า token ดิบเพียงเพื่อรายงานความพร้อมใช้งาน
แบบอ่านอย่างเดียว การคืน
tokenStatus: "available"(และฟิลด์แหล่งที่มาตรงกัน) ก็เพียงพอสำหรับคำสั่งแนว status - ใช้
configured_unavailableเมื่อ credential ถูกกำหนดค่าผ่าน SecretRef แต่ ไม่พร้อมใช้งานในเส้นทางคำสั่งปัจจุบัน
สิ่งนี้ทำให้คำสั่งแบบอ่านอย่างเดียวสามารถรายงานว่า "กำหนดค่าแล้วแต่ไม่พร้อมใช้งานในเส้นทางคำสั่งนี้" แทนการ crash หรือรายงานผิดว่าบัญชียังไม่ได้กำหนดค่า
แพ็กแพ็กเกจ
ไดเรกทอรี Plugin อาจมี package.json พร้อม openclaw.extensions:
{
"name": "my-pack",
"openclaw": {
"extensions": ["./src/safety.ts", "./src/tools.ts"],
"setupEntry": "./src/setup-entry.ts"
}
}
แต่ละ entry จะกลายเป็น Plugin หากแพ็กระบุ extension หลายรายการ id ของ Plugin
จะกลายเป็น name/<fileBase>
หาก Plugin ของคุณนำเข้า npm deps ให้ติดตั้งในไดเรกทอรีนั้นเพื่อให้
node_modules พร้อมใช้งาน (npm install / pnpm install)
แนวป้องกันด้านความปลอดภัย: entry ทุกตัวใน openclaw.extensions ต้องอยู่ภายในไดเรกทอรี Plugin
หลัง resolve symlink แล้ว entry ที่หลุดออกจากไดเรกทอรีแพ็กเกจจะถูก
ปฏิเสธ
หมายเหตุด้านความปลอดภัย: openclaw plugins install ติดตั้ง dependency ของ Plugin ด้วย
npm install --omit=dev --ignore-scripts แบบ local ต่อโปรเจกต์ (ไม่มี lifecycle scripts,
ไม่มี dev dependencies ตอนรันไทม์) โดยไม่สนใจการตั้งค่า npm install ส่วนกลางที่สืบทอดมา
รักษา dependency tree ของ Plugin ให้เป็น "JS/TS ล้วน" และหลีกเลี่ยงแพ็กเกจที่ต้องใช้
build แบบ postinstall
ไม่บังคับ: openclaw.setupEntry สามารถชี้ไปยังโมดูล setup-only ที่เบาได้
เมื่อ OpenClaw ต้องการพื้นผิวการตั้งค่าสำหรับ Plugin ช่องทางที่ปิดใช้งานอยู่ หรือ
เมื่อ Plugin ช่องทางเปิดใช้งานแล้วแต่ยังไม่ได้กำหนดค่า จะโหลด setupEntry
แทน entry ของ Plugin แบบเต็ม สิ่งนี้ทำให้การเริ่มต้นและการตั้งค่าเบาลง
เมื่อ entry หลักของ Plugin ของคุณยังต่อสาย tools, hooks หรือโค้ดอื่น
ที่ใช้เฉพาะรันไทม์ด้วย
ไม่บังคับ: openclaw.startup.deferConfiguredChannelFullLoadUntilAfterListen
สามารถเลือกให้ Plugin ช่องทางใช้เส้นทาง setupEntry เดียวกันระหว่างเฟสเริ่มต้น
ก่อน listen ของ gateway ได้ แม้ว่าช่องทางนั้นจะกำหนดค่าแล้วก็ตาม
ใช้สิ่งนี้เฉพาะเมื่อ setupEntry ครอบคลุมพื้นผิวเริ่มต้นที่ต้องมีอยู่
ก่อนที่ gateway จะเริ่ม listen อย่างครบถ้วน ในทางปฏิบัติ หมายความว่า setup entry
ต้องลงทะเบียน capability ทุกอย่างที่ช่องทางเป็นเจ้าของและการเริ่มต้นต้องพึ่งพา เช่น:
- การลงทะเบียนช่องทางเอง
- เส้นทาง HTTP ใดๆ ที่ต้องพร้อมใช้งานก่อนที่ gateway จะเริ่ม listen
- gateway methods, tools หรือ services ใดๆ ที่ต้องมีอยู่ระหว่างช่วงเวลาเดียวกันนั้น
หาก entry แบบเต็มของคุณยังเป็นเจ้าของ capability การเริ่มต้นที่จำเป็นใดๆ อยู่ อย่าเปิดใช้ flag นี้ ให้ Plugin ใช้พฤติกรรมเริ่มต้นและให้ OpenClaw โหลด entry แบบเต็มระหว่างการเริ่มต้น
ช่องทางที่บันเดิลมายังสามารถเผยแพร่ตัวช่วยพื้นผิวสัญญาแบบ setup-only ที่ core สามารถ consult ก่อนโหลดรันไทม์ช่องทางแบบเต็มได้ พื้นผิวการโปรโมต setup ปัจจุบันคือ:
singleAccountKeysToMovenamedAccountPromotionKeysresolveSingleAccountPromotionTarget(...)
Core ใช้พื้นผิวนั้นเมื่อต้องโปรโมตการกำหนดค่าช่องทางแบบบัญชีเดียวรุ่นเก่า
ไปเป็น channels.<id>.accounts.* โดยไม่โหลดรายการ Plugin เต็มรูปแบบ
Matrix คือตัวอย่างที่บันเดิลอยู่ในปัจจุบัน: โดยจะย้ายเฉพาะคีย์ auth/bootstrap ไปยัง
บัญชีที่ถูกโปรโมตแบบมีชื่อเมื่อมีบัญชีแบบมีชื่ออยู่แล้ว และสามารถรักษาคีย์บัญชีเริ่มต้น
ที่กำหนดค่าไว้แต่ไม่ใช่แบบ canonical แทนที่จะสร้าง accounts.default เสมอ
อะแดปเตอร์แพตช์การตั้งค่าเหล่านั้นช่วยให้การค้นพบพื้นผิวสัญญาที่บันเดิลอยู่ยังคงเป็นแบบ lazy เวลา import จึงยังเบาอยู่; พื้นผิวการโปรโมตจะถูกโหลดเฉพาะเมื่อใช้งานครั้งแรก แทนที่จะ กลับเข้าไปสู่การเริ่มต้นช่องทางที่บันเดิลไว้ระหว่างการ import โมดูล
เมื่อพื้นผิวเริ่มต้นเหล่านั้นรวมเมธอด RPC ของ Gateway ไว้ ให้คงไว้บน prefix
เฉพาะ Plugin เนมสเปซผู้ดูแลของ Core (config.*,
exec.approvals.*, wizard.*, update.*) ยังคงสงวนไว้และจะ resolve
เป็น operator.admin เสมอ แม้ว่า Plugin จะขอ scope ที่แคบกว่าก็ตาม
ตัวอย่าง:
{
"name": "@scope/my-channel",
"openclaw": {
"extensions": ["./index.ts"],
"setupEntry": "./setup-entry.ts",
"startup": {
"deferConfiguredChannelFullLoadUntilAfterListen": true
}
}
}
เมทาดาทาแคตตาล็อกช่องทาง
Plugin ช่องทางสามารถประกาศเมทาดาทาการตั้งค่า/การค้นพบผ่าน openclaw.channel และ
คำใบ้การติดตั้งผ่าน openclaw.install ได้ วิธีนี้ทำให้แคตตาล็อกของ Core ไม่มีข้อมูลผูกติดอยู่
ตัวอย่าง:
{
"name": "@openclaw/nextcloud-talk",
"openclaw": {
"extensions": ["./index.ts"],
"channel": {
"id": "nextcloud-talk",
"label": "Nextcloud Talk",
"selectionLabel": "Nextcloud Talk (self-hosted)",
"docsPath": "/channels/nextcloud-talk",
"docsLabel": "nextcloud-talk",
"blurb": "Self-hosted chat via Nextcloud Talk webhook bots.",
"order": 65,
"aliases": ["nc-talk", "nc"]
},
"install": {
"npmSpec": "@openclaw/nextcloud-talk",
"localPath": "<bundled-plugin-local-path>",
"defaultChoice": "npm"
}
}
}
ฟิลด์ openclaw.channel ที่มีประโยชน์นอกเหนือจากตัวอย่างขั้นต่ำ:
detailLabel: ป้ายกำกับรองสำหรับพื้นผิวแคตตาล็อก/สถานะที่สมบูรณ์ขึ้นdocsLabel: แทนที่ข้อความลิงก์สำหรับลิงก์เอกสารpreferOver: id ของ Plugin/ช่องทางที่มีลำดับความสำคัญต่ำกว่าซึ่งรายการแคตตาล็อกนี้ควรอยู่เหนือกว่าselectionDocsPrefix,selectionDocsOmitLabel,selectionExtras: ตัวควบคุมข้อความบนพื้นผิวการเลือกmarkdownCapable: ทำเครื่องหมายช่องทางว่าใช้ markdown ได้สำหรับการตัดสินใจจัดรูปแบบขาออกexposure.configured: ซ่อนช่องทางจากพื้นผิวรายการช่องทางที่กำหนดค่าแล้วเมื่อตั้งเป็นfalseexposure.setup: ซ่อนช่องทางจากตัวเลือกการตั้งค่า/กำหนดค่าแบบโต้ตอบเมื่อตั้งเป็นfalseexposure.docs: ทำเครื่องหมายช่องทางว่าเป็นภายใน/ส่วนตัวสำหรับพื้นผิวนำทางเอกสารshowConfigured/showInSetup: alias รุ่นเก่าที่ยังยอมรับเพื่อความเข้ากันได้; แนะนำให้ใช้exposurequickstartAllowFrom: เลือกให้ช่องทางเข้าร่วม flowallowFromของ quickstart มาตรฐานforceAccountBinding: บังคับให้ผูกบัญชีอย่างชัดเจนแม้มีเพียงบัญชีเดียวอยู่preferSessionLookupForAnnounceTarget: ใช้การค้นหา session เป็นหลักเมื่อ resolve เป้าหมายประกาศ
OpenClaw ยังสามารถรวม แคตตาล็อกช่องทางภายนอก ได้ด้วย (เช่น export จาก registry ของ MPM) วางไฟล์ JSON ไว้ที่หนึ่งในตำแหน่งต่อไปนี้:
~/.openclaw/mpm/plugins.json~/.openclaw/mpm/catalog.json~/.openclaw/plugins/catalog.json
หรือชี้ OPENCLAW_PLUGIN_CATALOG_PATHS (หรือ OPENCLAW_MPM_CATALOG_PATHS) ไปยัง
ไฟล์ JSON หนึ่งไฟล์หรือมากกว่า (คั่นด้วยจุลภาค/อัฒภาค/PATH) แต่ละไฟล์ควร
มี { "entries": [ { "name": "@scope/pkg", "openclaw": { "channel": {...}, "install": {...} } } ] } parser ยังยอมรับ "packages" หรือ "plugins" เป็น alias รุ่นเก่าสำหรับคีย์ "entries" ด้วย
รายการแคตตาล็อกช่องทางที่สร้างขึ้นและรายการแคตตาล็อกการติดตั้ง provider จะแสดง
ข้อเท็จจริงแหล่งติดตั้งที่ normalize แล้วถัดจากบล็อก openclaw.install ดิบ
ข้อเท็จจริงที่ normalize แล้วจะระบุว่า npm spec เป็นเวอร์ชัน exact หรือ selector แบบลอยตัว
มีเมทาดาทา integrity ที่คาดไว้หรือไม่ และมี path แหล่งที่มาในเครื่องพร้อมใช้งานด้วยหรือไม่
เมื่อทราบตัวตนของแคตตาล็อก/แพ็กเกจ ข้อเท็จจริงที่ normalize แล้วจะเตือนหากชื่อแพ็กเกจ npm
ที่ parse ได้เบี่ยงเบนจากตัวตนนั้น และยังเตือนเมื่อ defaultChoice ไม่ถูกต้องหรือชี้ไปยัง
แหล่งที่มาไม่พร้อมใช้งาน และเมื่อมีเมทาดาทา integrity ของ npm โดยไม่มีแหล่ง npm
ที่ถูกต้อง ผู้บริโภคควรมอง installSource เป็นฟิลด์เสริมแบบเติมเพิ่มได้ เพื่อให้
รายการที่สร้างด้วยมือและ shim แคตตาล็อกไม่จำเป็นต้องสังเคราะห์มันขึ้นมา
วิธีนี้ช่วยให้ onboarding และ diagnostics อธิบายสถานะ source-plane ได้โดยไม่ต้อง
รายการ npm ภายนอกอย่างเป็นทางการควรใช้ npmSpec แบบ exact พร้อม
expectedIntegrity เป็นหลัก ชื่อแพ็กเกจเปล่าและ dist-tag ยังคงใช้งานได้เพื่อ
ความเข้ากันได้ แต่จะแสดงคำเตือน source-plane เพื่อให้แคตตาล็อกขยับไปสู่
การติดตั้งแบบปักหมุดและตรวจ integrity โดยไม่ทำให้ Plugin ที่มีอยู่เสียหาย
เมื่อ onboarding ติดตั้งจาก path แคตตาล็อกในเครื่อง จะบันทึกรายการดัชนี Plugin
ที่จัดการแล้วด้วย source: "path" และ sourcePath แบบสัมพันธ์กับ workspace
เมื่อเป็นไปได้ path โหลดเชิงปฏิบัติการแบบ absolute จะยังอยู่ใน
plugins.load.paths; ระเบียนการติดตั้งหลีกเลี่ยงการทำซ้ำ path workstation
ในเครื่องเข้าไปในการกำหนดค่าระยะยาว วิธีนี้ทำให้การติดตั้งสำหรับพัฒนาในเครื่อง
มองเห็นได้ต่อ diagnostics ของ source-plane โดยไม่เพิ่มพื้นผิวเปิดเผย raw filesystem-path
ชุดที่สอง ดัชนี Plugin plugins/installs.json ที่ persist ไว้คือแหล่งความจริงของ
แหล่งติดตั้งและสามารถ refresh ได้โดยไม่โหลดโมดูล runtime ของ Plugin
map installRecords ของมันคงทนแม้ manifest ของ Plugin จะหายไปหรือไม่ถูกต้อง;
array plugins ของมันเป็นมุมมอง manifest ที่สร้างใหม่ได้
Plugin เครื่องมือบริบท
Plugin เครื่องมือบริบทเป็นเจ้าของการจัดการบริบท session สำหรับ ingest, assembly,
และ Compaction ลงทะเบียนจาก Plugin ของคุณด้วย
api.registerContextEngine(id, factory) แล้วเลือก engine ที่ใช้งานอยู่ด้วย
plugins.slots.contextEngine
ใช้สิ่งนี้เมื่อ Plugin ของคุณต้องแทนที่หรือขยาย pipeline บริบทเริ่มต้น แทนที่จะเพียงเพิ่มการค้นหาหน่วยความจำหรือ hooks
export default function (api) {
api.registerContextEngine("lossless-claw", (ctx) => ({
info: { id: "lossless-claw", name: "Lossless Claw", ownsCompaction: true },
async ingest() {
return { ingested: true };
},
async assemble({ messages, availableTools, citationsMode }) {
return {
messages,
estimatedTokens: 0,
systemPromptAddition: buildMemorySystemPromptAddition({
availableTools: availableTools ?? new Set(),
citationsMode,
}),
};
},
async compact() {
return { ok: true, compacted: false };
},
}));
}
factory ctx แสดงค่า config, agentDir, และ workspaceDir แบบไม่บังคับ
สำหรับการเริ่มต้นตอนสร้าง
หาก engine ของคุณ ไม่ได้ เป็นเจ้าของอัลกอริทึม Compaction ให้คง compact()
ไว้และ delegate อย่างชัดเจน:
buildMemorySystemPromptAddition,
delegateCompactionToRuntime,
} from "openclaw/plugin-sdk/core";
export default function (api) {
api.registerContextEngine("my-memory-engine", (ctx) => ({
info: {
id: "my-memory-engine",
name: "My Memory Engine",
ownsCompaction: false,
},
async ingest() {
return { ingested: true };
},
async assemble({ messages, availableTools, citationsMode }) {
return {
messages,
estimatedTokens: 0,
systemPromptAddition: buildMemorySystemPromptAddition({
availableTools: availableTools ?? new Set(),
citationsMode,
}),
};
},
async compact(params) {
return await delegateCompactionToRuntime(params);
},
}));
}
การเพิ่ม capability ใหม่
เมื่อ Plugin ต้องการพฤติกรรมที่ไม่เข้ากับ API ปัจจุบัน อย่าเลี่ยง ระบบ Plugin ด้วยการ reach-in แบบส่วนตัว ให้เพิ่ม capability ที่ขาดไป
ลำดับที่แนะนำ:
- กำหนดสัญญา Core ตัดสินใจว่า Core ควรเป็นเจ้าของพฤติกรรมร่วมใด: policy, fallback, การ merge config, lifecycle, semantics ที่หันหน้าเข้าหาช่องทาง และรูปทรง runtime helper
- เพิ่มพื้นผิวการลงทะเบียน/runtime ของ Plugin แบบ typed
ขยาย
OpenClawPluginApiและ/หรือapi.runtimeด้วยพื้นผิว capability แบบ typed ที่เล็กที่สุดแต่มีประโยชน์ - เชื่อม Core + ผู้บริโภคช่องทาง/ฟีเจอร์ ช่องทางและ Plugin ฟีเจอร์ควรบริโภค capability ใหม่ผ่าน Core ไม่ใช่โดย import implementation ของ vendor โดยตรง
- ลงทะเบียน implementation ของ vendor จากนั้น Plugin vendor จึงลงทะเบียน backend ของตนกับ capability
- เพิ่ม coverage ของสัญญา เพิ่ม tests เพื่อให้ ownership และรูปทรงการลงทะเบียนยังชัดเจนเมื่อเวลาผ่านไป
นี่คือวิธีที่ OpenClaw ยังคงมีจุดยืนโดยไม่ถูก hardcode กับมุมมองของ provider รายเดียว ดู Capability Cookbook สำหรับ checklist ไฟล์ที่เป็นรูปธรรมและตัวอย่างที่ลงมือทำแล้ว
Checklist ของ capability
เมื่อคุณเพิ่ม capability ใหม่ implementation มักควรแตะพื้นผิวเหล่านี้พร้อมกัน:
- ชนิดสัญญา Core ใน
src/<capability>/types.ts - runner/runtime helper ของ Core ใน
src/<capability>/runtime.ts - พื้นผิวการลงทะเบียน API ของ Plugin ใน
src/plugins/types.ts - การเชื่อม registry ของ Plugin ใน
src/plugins/registry.ts - การเปิดเผย runtime ของ Plugin ใน
src/plugins/runtime/*เมื่อ Plugin ฟีเจอร์/ช่องทาง ต้องบริโภคมัน - capture/test helpers ใน
src/test-utils/plugin-registration.ts - assertion เรื่อง ownership/contract ใน
src/plugins/contracts/registry.ts - เอกสาร operator/Plugin ใน
docs/
หากพื้นผิวใดพื้นผิวหนึ่งหายไป โดยปกตินั่นเป็นสัญญาณว่า capability ยังไม่ได้ integrate อย่างสมบูรณ์
Template ของ capability
รูปแบบขั้นต่ำ:
// core contract
export type VideoGenerationProviderPlugin = {
id: string;
label: string;
generateVideo: (req: VideoGenerationRequest) => Promise<VideoGenerationResult>;
};
// plugin API
api.registerVideoGenerationProvider({
id: "openai",
label: "OpenAI",
async generateVideo(req) {
return await generateOpenAiVideo(req);
},
});
// shared runtime helper for feature/channel plugins
const clip = await api.runtime.videoGeneration.generate({
prompt: "Show the robot walking through the lab.",
cfg,
});
รูปแบบ contract test:
expect(findVideoGenerationProviderIdsForPlugin("openai")).toEqual(["openai"]);
นั่นทำให้กฎเรียบง่าย:
- Core เป็นเจ้าของสัญญา capability + orchestration
- Plugin vendor เป็นเจ้าของ implementation ของ vendor
- Plugin ฟีเจอร์/ช่องทางบริโภค runtime helpers
- contract tests ทำให้ ownership ชัดเจน
ที่เกี่ยวข้อง
- สถาปัตยกรรม Plugin — โมเดลและรูปทรง capability สาธารณะ
- Subpaths ของ Plugin SDK
- การตั้งค่า Plugin SDK
- การสร้าง Plugin