Plugins

ตัวช่วยรันไทม์ของ Plugin

การอ้างอิงสำหรับออบเจ็กต์ api.runtime ที่ถูกฉีดเข้าไปในทุก Plugin ระหว่างการลงทะเบียน ใช้ตัวช่วยเหล่านี้แทนการ import ส่วนภายในของโฮสต์โดยตรง.

register(api) {
  const runtime = api.runtime;
}

การโหลดและเขียน config

ควรใช้ config ที่ถูกส่งเข้ามาในเส้นทางการเรียกที่ทำงานอยู่แล้ว เช่น api.config ระหว่างการลงทะเบียน หรืออาร์กิวเมนต์ cfg บน callback ของช่องทาง/ผู้ให้บริการ วิธีนี้ช่วยให้ snapshot ของ process หนึ่งชุดไหลผ่านงาน แทนการ parse config ซ้ำใน hot path.

ใช้ api.runtime.config.current() เฉพาะเมื่อ handler ที่มีอายุยาวต้องการ snapshot ของ process ปัจจุบันและไม่มี config ถูกส่งให้ฟังก์ชันนั้น ค่าที่คืนมาเป็น readonly; clone หรือใช้ตัวช่วย mutation ก่อนแก้ไข.

factory ของเครื่องมือจะได้รับ ctx.runtimeConfig พร้อมกับ ctx.getRuntimeConfig() ใช้ getter ภายใน callback execute ของเครื่องมือที่มีอายุยาวเมื่อ config อาจเปลี่ยนหลังจากสร้างนิยามเครื่องมือแล้ว.

บันทึกการเปลี่ยนแปลงด้วย api.runtime.config.mutateConfigFile(...) หรือ api.runtime.config.replaceConfigFile(...) การเขียนแต่ละครั้งต้องเลือกนโยบาย afterWrite อย่างชัดเจน:

  • afterWrite: { mode: "auto" } ให้ตัวตัดสินใจ reload ของ gateway จัดการ.
  • afterWrite: { mode: "restart", reason: "..." } บังคับ restart แบบสะอาดเมื่อผู้เขียนรู้ว่า hot reload ไม่ปลอดภัย.
  • afterWrite: { mode: "none", reason: "..." } ระงับ reload/restart อัตโนมัติเฉพาะเมื่อผู้เรียกเป็นเจ้าของการติดตามผล.

ตัวช่วย mutation จะคืน afterWrite พร้อมสรุป followUp ที่มี type เพื่อให้ผู้เรียกสามารถ log หรือทดสอบได้ว่าพวกเขาขอ restart หรือไม่ Gateway ยังคงเป็นเจ้าของว่า restart นั้นจะเกิดขึ้นจริงเมื่อใด.

api.runtime.config.loadConfig() และ api.runtime.config.writeConfigFile(...) เป็นตัวช่วย compatibility ที่เลิกใช้แล้วภายใต้ runtime-config-load-write ตัวช่วยเหล่านี้จะเตือนหนึ่งครั้งใน runtime และยังคงมีให้ใช้สำหรับ Plugin ภายนอกเก่าในช่วง migration Plugin ที่ bundled ต้องไม่ใช้ตัวช่วยเหล่านี้; ตัว guard ของขอบเขต config จะล้มเหลวหากโค้ด Plugin เรียกใช้หรือ import ตัวช่วยเหล่านี้จาก subpath ของ plugin SDK.

สำหรับการ import SDK โดยตรง ให้ใช้ subpath config ที่เฉพาะเจาะจงแทน barrel compatibility แบบกว้าง openclaw/plugin-sdk/config-runtime: config-types สำหรับ type, plugin-config-runtime สำหรับ assertion ของ config ที่โหลดแล้วและการค้นหา entry ของ Plugin, runtime-config-snapshot สำหรับ snapshot ของ process ปัจจุบัน, และ config-mutation สำหรับการเขียน การทดสอบ Plugin ที่ bundled ควร mock subpath เฉพาะเหล่านี้โดยตรง แทนการ mock barrel compatibility แบบกว้าง.

โค้ด runtime ภายในของ OpenClaw มีทิศทางเดียวกัน: โหลด config หนึ่งครั้งที่ขอบเขต CLI, gateway, หรือ process แล้วส่งค่านั้นต่อไป การเขียน mutation ที่สำเร็จจะ refresh snapshot runtime ของ process และเพิ่ม revision ภายใน; cache ที่มีอายุยาวควร key จาก cache key ที่ runtime เป็นเจ้าของ แทนการ serialize config เองภายในเครื่อง โมดูล runtime ที่มีอายุยาวมี scanner แบบไม่ยอมรับเลยสำหรับการเรียก loadConfig() แบบ ambient; ใช้ cfg ที่ถูกส่งมา, context.getRuntimeConfig() ของ request, หรือ getRuntimeConfig() ที่ขอบเขต process อย่างชัดเจน.

เส้นทางการทำงานของผู้ให้บริการและช่องทางต้องใช้ snapshot runtime config ที่ทำงานอยู่ ไม่ใช่ snapshot ของไฟล์ที่คืนมาเพื่อ readback หรือแก้ไข config snapshot ของไฟล์จะเก็บค่าต้นทาง เช่น marker SecretRef สำหรับ UI และการเขียน; callback ของผู้ให้บริการต้องการมุมมอง runtime ที่ resolve แล้ว เมื่อตัวช่วยอาจถูกเรียกด้วย snapshot ต้นทางที่ทำงานอยู่หรือ snapshot runtime ที่ทำงานอยู่ ให้ route ผ่าน selectApplicableRuntimeConfig() ก่อนอ่าน credentials.

namespace ของ runtime

api.runtime.agent

ตัวตนของ Agent, ไดเรกทอรี, และการจัดการ session.

// Resolve the agent's working directory
const agentDir = api.runtime.agent.resolveAgentDir(cfg);

// Resolve agent workspace
const workspaceDir = api.runtime.agent.resolveAgentWorkspaceDir(cfg);

// Get agent identity
const identity = api.runtime.agent.resolveAgentIdentity(cfg);

// Get default thinking level
const thinking = api.runtime.agent.resolveThinkingDefault({
  cfg,
  provider,
  model,
});

// Validate a user-provided thinking level against the active provider profile
const policy = api.runtime.agent.resolveThinkingPolicy({ provider, model });
const level = api.runtime.agent.normalizeThinkingLevel("extra high");
if (level && policy.levels.some((entry) => entry.id === level)) {
  // pass level to an embedded run
}

// Get agent timeout
const timeoutMs = api.runtime.agent.resolveAgentTimeoutMs(cfg);

// Ensure workspace exists
await api.runtime.agent.ensureAgentWorkspace(cfg);

// Run an embedded agent turn
const agentDir = api.runtime.agent.resolveAgentDir(cfg);
const result = await api.runtime.agent.runEmbeddedAgent({
  sessionId: "my-plugin:task-1",
  runId: crypto.randomUUID(),
  sessionFile: path.join(agentDir, "sessions", "my-plugin-task-1.jsonl"),
  workspaceDir: api.runtime.agent.resolveAgentWorkspaceDir(cfg),
  prompt: "Summarize the latest changes",
  timeoutMs: api.runtime.agent.resolveAgentTimeoutMs(cfg),
});

runEmbeddedAgent(...) เป็นตัวช่วยแบบเป็นกลางสำหรับเริ่ม turn ของ Agent OpenClaw ปกติจากโค้ด Plugin ตัวช่วยนี้ใช้การ resolve ผู้ให้บริการ/model และการเลือก agent-harness เดียวกับ reply ที่ถูก trigger จากช่องทาง.

runEmbeddedPiAgent(...) ยังคงอยู่ในฐานะ alias สำหรับ compatibility.

resolveThinkingPolicy(...) คืนระดับ thinking ที่ผู้ให้บริการ/model รองรับและค่า default ที่เป็น optional Plugin ผู้ให้บริการเป็นเจ้าของ profile เฉพาะ model ผ่าน hook thinking ของตน ดังนั้น Plugin เครื่องมือควรเรียกตัวช่วย runtime นี้แทนการ import หรือทำรายการผู้ให้บริการซ้ำ.

normalizeThinkingLevel(...) แปลงข้อความผู้ใช้ เช่น on, x-high, หรือ extra high เป็นระดับที่จัดเก็บแบบ canonical ก่อนตรวจสอบกับนโยบายที่ resolve แล้ว.

ตัวช่วย session store อยู่ภายใต้ api.runtime.agent.session:

const storePath = api.runtime.agent.session.resolveStorePath(cfg);
const store = api.runtime.agent.session.loadSessionStore(storePath);
await api.runtime.agent.session.updateSessionStore(storePath, (nextStore) => {
  // Patch one entry without replacing the whole file from stale state.
  nextStore[sessionKey] = { ...nextStore[sessionKey], thinkingLevel: "high" };
});
const filePath = api.runtime.agent.session.resolveSessionFilePath(cfg, sessionId);

ควรใช้ updateSessionStore(...) หรือ updateSessionStoreEntry(...) สำหรับการเขียน runtime ตัวช่วยเหล่านี้ route ผ่าน writer ของ session-store ที่ Gateway เป็นเจ้าของ, รักษาการอัปเดตที่เกิดพร้อมกัน, และใช้ hot cache ซ้ำ saveSessionStore(...) ยังคงมีให้ใช้สำหรับ compatibility และการ rewrite แบบ maintenance-style ออฟไลน์.

api.runtime.agent.defaults

ค่าคงที่ของ model และผู้ให้บริการ default:

const model = api.runtime.agent.defaults.model; // e.g. "anthropic/claude-sonnet-4-6"
const provider = api.runtime.agent.defaults.provider; // e.g. "anthropic"
api.runtime.subagent

เปิดและจัดการการรัน subagent เบื้องหลัง.

// Start a subagent run
const { runId } = await api.runtime.subagent.run({
  sessionKey: "agent:main:subagent:search-helper",
  message: "Expand this query into focused follow-up searches.",
  provider: "openai", // optional override
  model: "gpt-4.1-mini", // optional override
  deliver: false,
});

// Wait for completion
const result = await api.runtime.subagent.waitForRun({ runId, timeoutMs: 30000 });

// Read session messages
const { messages } = await api.runtime.subagent.getSessionMessages({
  sessionKey: "agent:main:subagent:search-helper",
  limit: 10,
});

// Delete a session
await api.runtime.subagent.deleteSession({
  sessionKey: "agent:main:subagent:search-helper",
});

deleteSession(...) สามารถลบ session ที่สร้างโดย Plugin เดียวกันผ่าน api.runtime.subagent.run(...) การลบ session ของผู้ใช้หรือ operator ใด ๆ ยังคงต้องใช้ request ของ Gateway ที่มี scope เป็น admin.

api.runtime.nodes

แสดงรายการ node ที่เชื่อมต่อและ invoke คำสั่ง node-host จากโค้ด Plugin ที่ Gateway โหลด หรือจากคำสั่ง CLI ของ Plugin ใช้สิ่งนี้เมื่อ Plugin เป็นเจ้าของงาน local บนอุปกรณ์ที่ pair แล้ว เช่น browser หรือ audio bridge บน Mac เครื่องอื่น.

const { nodes } = await api.runtime.nodes.list({ connected: true });

const result = await api.runtime.nodes.invoke({
  nodeId: "mac-studio",
  command: "my-plugin.command",
  params: { action: "start" },
  timeoutMs: 30000,
});

ภายใน Gateway runtime นี้อยู่ใน process ในคำสั่ง CLI ของ Plugin runtime นี้จะเรียก Gateway ที่กำหนดค่าไว้ผ่าน RPC ดังนั้นคำสั่งเช่น openclaw googlemeet recover-tab สามารถตรวจสอบ node ที่ pair แล้วจาก terminal ได้ คำสั่ง Node ยังคงผ่านการจับคู่ node ของ Gateway ตามปกติ, allowlist ของคำสั่ง, นโยบาย node-invoke ของ Plugin, และการจัดการคำสั่งแบบ node-local.

Plugin ที่เปิดเผยคำสั่ง node-host ที่อันตรายควรลงทะเบียนนโยบาย node-invoke ด้วย api.registerNodeInvokePolicy(...) นโยบายจะรันใน Gateway หลังจากตรวจสอบ allowlist ของคำสั่งและก่อนส่งต่อคำสั่งไปยัง node ดังนั้นการเรียก node.invoke โดยตรงและเครื่องมือ Plugin ระดับสูงกว่าจะใช้เส้นทางการบังคับใช้นโยบายเดียวกัน.

api.runtime.tasks.managedFlows

ผูก runtime ของ Task Flow กับ session key ของ OpenClaw ที่มีอยู่หรือ context ของเครื่องมือที่เชื่อถือได้ แล้วสร้างและจัดการ Task Flows โดยไม่ต้องส่ง owner ในทุกการเรียก.

const taskFlow = api.runtime.tasks.managedFlows.fromToolContext(ctx);

const created = taskFlow.createManaged({
  controllerId: "my-plugin/review-batch",
  goal: "Review new pull requests",
});

const child = taskFlow.runTask({
  flowId: created.flowId,
  runtime: "acp",
  childSessionKey: "agent:main:subagent:reviewer",
  task: "Review PR #123",
  status: "running",
  startedAt: Date.now(),
});

const waiting = taskFlow.setWaiting({
  flowId: created.flowId,
  expectedRevision: created.revision,
  currentStep: "await-human-reply",
  waitJson: { kind: "reply", channel: "telegram" },
});

ใช้ bindSession({ sessionKey, requesterOrigin }) เมื่อคุณมี session key ของ OpenClaw ที่เชื่อถือได้แล้วจาก binding layer ของคุณเอง อย่า bind จาก input ผู้ใช้แบบ raw.

api.runtime.tts

การสังเคราะห์ข้อความเป็นเสียงพูด.

// Standard TTS
const clip = await api.runtime.tts.textToSpeech({
  text: "Hello from OpenClaw",
  cfg: api.config,
});

// Telephony-optimized TTS
const telephonyClip = await api.runtime.tts.textToSpeechTelephony({
  text: "Hello from OpenClaw",
  cfg: api.config,
});

// List available voices
const voices = await api.runtime.tts.listVoices({
  provider: "elevenlabs",
  cfg: api.config,
});

ใช้การกำหนดค่า messages.tts ของ core และการเลือกผู้ให้บริการ คืน audio buffer PCM + sample rate.

api.runtime.mediaUnderstanding

การวิเคราะห์รูปภาพ, เสียง, และวิดีโอ.

// Describe an image
const image = await api.runtime.mediaUnderstanding.describeImageFile({
  filePath: "/tmp/inbound-photo.jpg",
  cfg: api.config,
  agentDir: "/tmp/agent",
});

// Transcribe audio
const { text } = await api.runtime.mediaUnderstanding.transcribeAudioFile({
  filePath: "/tmp/inbound-audio.ogg",
  cfg: api.config,
  mime: "audio/ogg", // optional, for when MIME cannot be inferred
});

// Describe a video
const video = await api.runtime.mediaUnderstanding.describeVideoFile({
  filePath: "/tmp/inbound-video.mp4",
  cfg: api.config,
});

// Generic file analysis
const result = await api.runtime.mediaUnderstanding.runFile({
  filePath: "/tmp/inbound-file.pdf",
  cfg: api.config,
});

คืนค่า { text: undefined } เมื่อไม่มีเอาต์พุตถูกสร้างขึ้น (เช่น อินพุตที่ถูกข้าม)

api.runtime.imageGeneration

การสร้างรูปภาพ

const result = await api.runtime.imageGeneration.generate({
  prompt: "A robot painting a sunset",
  cfg: api.config,
});

const providers = api.runtime.imageGeneration.listProviders({ cfg: api.config });
api.runtime.webSearch

การค้นหาเว็บ

const providers = api.runtime.webSearch.listProviders({ config: api.config });

const result = await api.runtime.webSearch.search({
  config: api.config,
  args: { query: "OpenClaw plugin SDK", count: 5 },
});
api.runtime.media

ยูทิลิตีสื่อระดับต่ำ

const webMedia = await api.runtime.media.loadWebMedia(url);
const mime = await api.runtime.media.detectMime(buffer);
const kind = api.runtime.media.mediaKindFromMime("image/jpeg"); // "image"
const isVoice = api.runtime.media.isVoiceCompatibleAudio(filePath);
const metadata = await api.runtime.media.getImageMetadata(filePath);
const resized = await api.runtime.media.resizeToJpeg(buffer, { maxWidth: 800 });
const terminalQr = await api.runtime.media.renderQrTerminal("https://openclaw.ai");
const pngQr = await api.runtime.media.renderQrPngBase64("https://openclaw.ai", {
  scale: 6, // 1-12
  marginModules: 4, // 0-16
});
const pngQrDataUrl = await api.runtime.media.renderQrPngDataUrl("https://openclaw.ai");
const tmpRoot = resolvePreferredOpenClawTmpDir();
const pngQrFile = await api.runtime.media.writeQrPngTempFile("https://openclaw.ai", {
  tmpRoot,
  dirPrefix: "my-plugin-qr-",
  fileName: "qr.png",
});
api.runtime.config

สแนปชอตการกำหนดค่ารันไทม์ปัจจุบันและการเขียนการกำหนดค่าแบบทรานแซกชัน ควรใช้ การกำหนดค่าที่ถูกส่งเข้าไปในเส้นทางการเรียกใช้งานที่กำลังทำงานอยู่แล้ว ใช้ current() เฉพาะเมื่อแฮนเดลอร์ต้องการสแนปชอตของโปรเซสโดยตรงเท่านั้น

const cfg = api.runtime.config.current();
await api.runtime.config.mutateConfigFile({
  afterWrite: { mode: "auto" },
  mutate(draft) {
    draft.plugins ??= {};
  },
});

mutateConfigFile(...) และ replaceConfigFile(...) คืนค่า followUp ตัวอย่างเช่น { mode: "restart", requiresRestart: true, reason } ซึ่งบันทึกเจตนาของตัวเขียนโดยไม่ยึดการควบคุมการรีสตาร์ตไปจาก Gateway

api.runtime.system

ยูทิลิตีระดับระบบ

await api.runtime.system.enqueueSystemEvent(event);
api.runtime.system.requestHeartbeat({
  source: "other",
  intent: "event",
  reason: "plugin-event",
});
api.runtime.system.requestHeartbeatNow({ reason: "plugin-event" }); // Deprecated compatibility alias.
const output = await api.runtime.system.runCommandWithTimeout(cmd, args, opts);
const hint = api.runtime.system.formatNativeDependencyHint(pkg);
api.runtime.events

การสมัครรับเหตุการณ์

api.runtime.events.onAgentEvent((event) => {
  /* ... */
});
api.runtime.events.onSessionTranscriptUpdate((update) => {
  /* ... */
});
api.runtime.logging

การบันทึกล็อก

const verbose = api.runtime.logging.shouldLogVerbose();
const childLogger = api.runtime.logging.getChildLogger({ plugin: "my-plugin" }, { level: "debug" });
api.runtime.modelAuth

การแปลงค่า auth ของโมเดลและผู้ให้บริการ

const auth = await api.runtime.modelAuth.getApiKeyForModel({ model, cfg });
const providerAuth = await api.runtime.modelAuth.resolveApiKeyForProvider({
  provider: "openai",
  cfg,
});
api.runtime.state

การแปลงค่าไดเรกทอรีสถานะและพื้นที่จัดเก็บแบบคีย์ที่รองรับด้วย SQLite

const stateDir = api.runtime.state.resolveStateDir(process.env);
const store = api.runtime.state.openKeyedStore<MyRecord>({
  namespace: "my-feature",
  maxEntries: 200,
  defaultTtlMs: 15 * 60_000,
});

await store.register("key-1", { value: "hello" });
const claimed = await store.registerIfAbsent("dedupe-key", { value: "first" });
const value = await store.lookup("key-1");
await store.consume("key-1");
await store.clear();

พื้นที่จัดเก็บแบบคีย์จะอยู่รอดหลังรีสตาร์ตและถูกแยกตามรหัส Plugin ที่ผูกกับรันไทม์ ใช้ registerIfAbsent(...) สำหรับการอ้างสิทธิ์เพื่อขจัดรายการซ้ำแบบอะตอมิก: คืนค่า true เมื่อคีย์หายไปหรือหมดอายุแล้วและถูกลงทะเบียน หรือ false เมื่อมีค่าที่ยังใช้งานอยู่แล้วโดยไม่เขียนทับค่า เวลาเริ่มสร้าง หรือ TTL ของค่านั้น ขีดจำกัด: maxEntries ต่อ namespace, แถวที่ยังใช้งานอยู่ 1,000 แถวต่อ Plugin, ค่า JSON ต่ำกว่า 64KB และการหมดอายุ TTL แบบเลือกได้

api.runtime.tools

แฟกทอรีเครื่องมือหน่วยความจำและ CLI

const getTool = api.runtime.tools.createMemoryGetTool(/* ... */);
const searchTool = api.runtime.tools.createMemorySearchTool(/* ... */);
api.runtime.tools.registerMemoryCli(/* ... */);
api.runtime.channel

ตัวช่วยรันไทม์เฉพาะช่องทาง (พร้อมใช้งานเมื่อโหลด Plugin ช่องทางแล้ว)

api.runtime.channel.mentions คือพื้นผิวนโยบายการกล่าวถึงขาเข้าที่ใช้ร่วมกันสำหรับ Plugin ช่องทางที่รวมมากับระบบและใช้การฉีดรันไทม์:

const mentionMatch = api.runtime.channel.mentions.matchesMentionWithExplicit(text, {
  mentionRegexes,
  mentionPatterns,
});

const decision = api.runtime.channel.mentions.resolveInboundMentionDecision({
  facts: {
    canDetectMention: true,
    wasMentioned: mentionMatch.matched,
    implicitMentionKinds: api.runtime.channel.mentions.implicitMentionKindWhen(
      "reply_to_bot",
      isReplyToBot,
    ),
  },
  policy: {
    isGroup,
    requireMention,
    allowTextCommands,
    hasControlCommand,
    commandAuthorized,
  },
});

ตัวช่วยการกล่าวถึงที่พร้อมใช้งาน:

  • buildMentionRegexes
  • matchesMentionPatterns
  • matchesMentionWithExplicit
  • implicitMentionKindWhen
  • resolveInboundMentionDecision

api.runtime.channel.mentions ตั้งใจไม่เปิดเผยตัวช่วยความเข้ากันได้ resolveMentionGating* รุ่นเก่า ควรใช้เส้นทาง { facts, policy } ที่ปรับให้เป็นมาตรฐานแล้ว

การจัดเก็บการอ้างอิงรันไทม์

ใช้ createPluginRuntimeStore เพื่อจัดเก็บการอ้างอิงรันไทม์สำหรับใช้นอกคอลแบ็ก register:

  • Create the store

    import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
    import type { PluginRuntime } from "openclaw/plugin-sdk/runtime-store";
    
    const store = createPluginRuntimeStore<PluginRuntime>({
      pluginId: "my-plugin",
      errorMessage: "my-plugin runtime not initialized",
    });
    
  • Wire into the entry point

    export default defineChannelPluginEntry({
      id: "my-plugin",
      name: "My Plugin",
      description: "Example",
      plugin: myPlugin,
      setRuntime: store.setRuntime,
    });
    
  • Access from other files

    export function getRuntime() {
      return store.getRuntime(); // throws if not initialized
    }
    
    export function tryGetRuntime() {
      return store.tryGetRuntime(); // returns null if not initialized
    }
    
  • ฟิลด์ api ระดับบนสุดอื่นๆ

    นอกเหนือจาก api.runtime แล้ว ออบเจ็กต์ API ยังมี:

    api.idstring

    รหัส Plugin

    api.namestring

    ชื่อที่แสดงของ Plugin

    api.configOpenClawConfig

    สแนปชอตการกำหนดค่าปัจจุบัน (สแนปชอตรันไทม์ในหน่วยความจำที่ใช้งานอยู่เมื่อพร้อมใช้งาน)

    OPENCLAW_DOCS_MARKER:paramOpen:IHBhdGg9ImFwaS5wbHVnaW5Db25maWciIHR5cGU9IlJlY29yZDxzdHJpbmcsIHVua25vd24 "> การกำหนดค่าเฉพาะ Plugin จาก plugins.entries.<id>.config

    api.loggerPluginLogger

    Logger แบบมีขอบเขต (debug, info, warn, error)

    api.registrationModePluginRegistrationMode

    โหมดการโหลดปัจจุบัน; "setup-runtime" คือหน้าต่างเริ่มต้น/ตั้งค่าแบบเบาก่อนเข้าสู่ entry แบบเต็ม

    api.resolvePath(input)(string) =,������� @b6]K�o���

    ที่เกี่ยวข้อง