diff --git a/docs/plugins/sdk-runtime.md b/docs/plugins/sdk-runtime.md
index b58c8c6c2fe..95e99a69132 100644
--- a/docs/plugins/sdk-runtime.md
+++ b/docs/plugins/sdk-runtime.md
@@ -1,21 +1,23 @@
---
summary: "api.runtime -- the injected runtime helpers available to plugins"
title: "Plugin runtime helpers"
-sidebarTitle: "Runtime Helpers"
+sidebarTitle: "Runtime helpers"
read_when:
- You need to call core helpers from a plugin (TTS, STT, image gen, web search, subagent, nodes)
- You want to understand what api.runtime exposes
- You are accessing config, agent, or media helpers from plugin code
---
-Reference for the `api.runtime` object injected into every plugin during
-registration. Use these helpers instead of importing host internals directly.
+Reference for the `api.runtime` object injected into every plugin during registration. Use these helpers instead of importing host internals directly.
-
- **Looking for a walkthrough?** See [Channel Plugins](/plugins/sdk-channel-plugins)
- or [Provider Plugins](/plugins/sdk-provider-plugins) for step-by-step guides
- that show these helpers in context.
-
+
+
+ Step-by-step guide that uses these helpers in context for channel plugins.
+
+
+ Step-by-step guide that uses these helpers in context for provider plugins.
+
+
```typescript
register(api) {
@@ -25,443 +27,449 @@ register(api) {
## Runtime namespaces
-### `api.runtime.agent`
-
-Agent identity, directories, and session management.
-
-```typescript
-// 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);
-
-// 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(...)` is the neutral helper for starting a normal OpenClaw
-agent turn from plugin code. It uses the same provider/model resolution and
-agent-harness selection as channel-triggered replies.
-
-`runEmbeddedPiAgent(...)` remains as a compatibility alias.
-
-**Session store helpers** are under `api.runtime.agent.session`:
-
-```typescript
-const storePath = api.runtime.agent.session.resolveStorePath(cfg);
-const store = api.runtime.agent.session.loadSessionStore(cfg);
-await api.runtime.agent.session.saveSessionStore(cfg, store);
-const filePath = api.runtime.agent.session.resolveSessionFilePath(cfg, sessionId);
-```
-
-### `api.runtime.agent.defaults`
-
-Default model and provider constants:
-
-```typescript
-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`
-
-Launch and manage background subagent runs.
-
-```typescript
-// 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",
-});
-```
-
-
- Model overrides (`provider`/`model`) require operator opt-in via
- `plugins.entries..subagent.allowModelOverride: true` in config.
- Untrusted plugins can still run subagents, but override requests are rejected.
-
-
-### `api.runtime.nodes`
-
-List connected nodes and invoke a node-host command from Gateway-loaded plugin
-code or from plugin CLI commands. Use this when a plugin owns local work on a
-paired device, for example a browser or audio bridge on another Mac.
-
-```typescript
-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,
-});
-```
-
-Inside the Gateway this runtime is in-process. In plugin CLI commands it calls
-the configured Gateway over RPC, so commands such as `openclaw googlemeet
-recover-tab` can inspect paired nodes from the terminal. Node commands still go
-through normal Gateway node pairing, command allowlists, and node-local command
-handling.
-
-### `api.runtime.taskFlow`
-
-Bind a Task Flow runtime to an existing OpenClaw session key or trusted tool
-context, then create and manage Task Flows without passing an owner on every call.
-
-```typescript
-const taskFlow = api.runtime.taskFlow.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" },
-});
-```
-
-Use `bindSession({ sessionKey, requesterOrigin })` when you already have a
-trusted OpenClaw session key from your own binding layer. Do not bind from raw
-user input.
-
-### `api.runtime.tts`
-
-Text-to-speech synthesis.
-
-```typescript
-// 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,
-});
-```
-
-Uses core `messages.tts` configuration and provider selection. Returns PCM audio
-buffer + sample rate.
-
-### `api.runtime.mediaUnderstanding`
-
-Image, audio, and video analysis.
-
-```typescript
-// 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,
-});
-```
-
-Returns `{ text: undefined }` when no output is produced (e.g. skipped input).
-
-
- `api.runtime.stt.transcribeAudioFile(...)` remains as a compatibility alias
- for `api.runtime.mediaUnderstanding.transcribeAudioFile(...)`.
-
-
-### `api.runtime.imageGeneration`
-
-Image generation.
-
-```typescript
-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`
-
-Web search.
-
-```typescript
-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`
-
-Low-level media utilities.
-
-```typescript
-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`
-
-Config load and write.
-
-```typescript
-const cfg = await api.runtime.config.loadConfig();
-await api.runtime.config.writeConfigFile(cfg);
-```
-
-### `api.runtime.system`
-
-System-level utilities.
-
-```typescript
-await api.runtime.system.enqueueSystemEvent(event);
-api.runtime.system.requestHeartbeatNow();
-const output = await api.runtime.system.runCommandWithTimeout(cmd, args, opts);
-const hint = api.runtime.system.formatNativeDependencyHint(pkg);
-```
-
-### `api.runtime.events`
-
-Event subscriptions.
-
-```typescript
-api.runtime.events.onAgentEvent((event) => {
- /* ... */
-});
-api.runtime.events.onSessionTranscriptUpdate((update) => {
- /* ... */
-});
-```
-
-### `api.runtime.logging`
-
-Logging.
-
-```typescript
-const verbose = api.runtime.logging.shouldLogVerbose();
-const childLogger = api.runtime.logging.getChildLogger({ plugin: "my-plugin" }, { level: "debug" });
-```
-
-### `api.runtime.modelAuth`
-
-Model and provider auth resolution.
-
-```typescript
-const auth = await api.runtime.modelAuth.getApiKeyForModel({ model, cfg });
-const providerAuth = await api.runtime.modelAuth.resolveApiKeyForProvider({
- provider: "openai",
- cfg,
-});
-```
-
-### `api.runtime.state`
-
-State directory resolution.
-
-```typescript
-const stateDir = api.runtime.state.resolveStateDir();
-```
-
-### `api.runtime.tools`
-
-Memory tool factories and CLI.
-
-```typescript
-const getTool = api.runtime.tools.createMemoryGetTool(/* ... */);
-const searchTool = api.runtime.tools.createMemorySearchTool(/* ... */);
-api.runtime.tools.registerMemoryCli(/* ... */);
-```
-
-### `api.runtime.channel`
-
-Channel-specific runtime helpers (available when a channel plugin is loaded).
-
-`api.runtime.channel.mentions` is the shared inbound mention-policy surface for
-bundled channel plugins that use runtime injection:
-
-```typescript
-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,
- },
-});
-```
-
-Available mention helpers:
-
-- `buildMentionRegexes`
-- `matchesMentionPatterns`
-- `matchesMentionWithExplicit`
-- `implicitMentionKindWhen`
-- `resolveInboundMentionDecision`
-
-`api.runtime.channel.mentions` intentionally does not expose the older
-`resolveMentionGating*` compatibility helpers. Prefer the normalized
-`{ facts, policy }` path.
+
+
+ Agent identity, directories, and session management.
+
+ ```typescript
+ // 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);
+
+ // 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(...)` is the neutral helper for starting a normal OpenClaw agent turn from plugin code. It uses the same provider/model resolution and agent-harness selection as channel-triggered replies.
+
+ `runEmbeddedPiAgent(...)` remains as a compatibility alias.
+
+ **Session store helpers** are under `api.runtime.agent.session`:
+
+ ```typescript
+ const storePath = api.runtime.agent.session.resolveStorePath(cfg);
+ const store = api.runtime.agent.session.loadSessionStore(cfg);
+ await api.runtime.agent.session.saveSessionStore(cfg, store);
+ const filePath = api.runtime.agent.session.resolveSessionFilePath(cfg, sessionId);
+ ```
+
+
+
+ Default model and provider constants:
+
+ ```typescript
+ const model = api.runtime.agent.defaults.model; // e.g. "anthropic/claude-sonnet-4-6"
+ const provider = api.runtime.agent.defaults.provider; // e.g. "anthropic"
+ ```
+
+
+
+ Launch and manage background subagent runs.
+
+ ```typescript
+ // 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",
+ });
+ ```
+
+
+ Model overrides (`provider`/`model`) require operator opt-in via `plugins.entries..subagent.allowModelOverride: true` in config. Untrusted plugins can still run subagents, but override requests are rejected.
+
+
+
+
+ List connected nodes and invoke a node-host command from Gateway-loaded plugin code or from plugin CLI commands. Use this when a plugin owns local work on a paired device, for example a browser or audio bridge on another Mac.
+
+ ```typescript
+ 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,
+ });
+ ```
+
+ Inside the Gateway this runtime is in-process. In plugin CLI commands it calls the configured Gateway over RPC, so commands such as `openclaw googlemeet recover-tab` can inspect paired nodes from the terminal. Node commands still go through normal Gateway node pairing, command allowlists, and node-local command handling.
+
+
+
+ Bind a Task Flow runtime to an existing OpenClaw session key or trusted tool context, then create and manage Task Flows without passing an owner on every call.
+
+ ```typescript
+ const taskFlow = api.runtime.taskFlow.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" },
+ });
+ ```
+
+ Use `bindSession({ sessionKey, requesterOrigin })` when you already have a trusted OpenClaw session key from your own binding layer. Do not bind from raw user input.
+
+
+
+ Text-to-speech synthesis.
+
+ ```typescript
+ // 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,
+ });
+ ```
+
+ Uses core `messages.tts` configuration and provider selection. Returns PCM audio buffer + sample rate.
+
+
+
+ Image, audio, and video analysis.
+
+ ```typescript
+ // 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,
+ });
+ ```
+
+ Returns `{ text: undefined }` when no output is produced (e.g. skipped input).
+
+
+ `api.runtime.stt.transcribeAudioFile(...)` remains as a compatibility alias for `api.runtime.mediaUnderstanding.transcribeAudioFile(...)`.
+
+
+
+
+ Image generation.
+
+ ```typescript
+ 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 });
+ ```
+
+
+
+ Web search.
+
+ ```typescript
+ 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 },
+ });
+ ```
+
+
+
+ Low-level media utilities.
+
+ ```typescript
+ 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",
+ });
+ ```
+
+
+
+ Config load and write.
+
+ ```typescript
+ const cfg = await api.runtime.config.loadConfig();
+ await api.runtime.config.writeConfigFile(cfg);
+ ```
+
+
+
+ System-level utilities.
+
+ ```typescript
+ await api.runtime.system.enqueueSystemEvent(event);
+ api.runtime.system.requestHeartbeatNow();
+ const output = await api.runtime.system.runCommandWithTimeout(cmd, args, opts);
+ const hint = api.runtime.system.formatNativeDependencyHint(pkg);
+ ```
+
+
+
+ Event subscriptions.
+
+ ```typescript
+ api.runtime.events.onAgentEvent((event) => {
+ /* ... */
+ });
+ api.runtime.events.onSessionTranscriptUpdate((update) => {
+ /* ... */
+ });
+ ```
+
+
+
+ Logging.
+
+ ```typescript
+ const verbose = api.runtime.logging.shouldLogVerbose();
+ const childLogger = api.runtime.logging.getChildLogger({ plugin: "my-plugin" }, { level: "debug" });
+ ```
+
+
+
+ Model and provider auth resolution.
+
+ ```typescript
+ const auth = await api.runtime.modelAuth.getApiKeyForModel({ model, cfg });
+ const providerAuth = await api.runtime.modelAuth.resolveApiKeyForProvider({
+ provider: "openai",
+ cfg,
+ });
+ ```
+
+
+
+ State directory resolution.
+
+ ```typescript
+ const stateDir = api.runtime.state.resolveStateDir();
+ ```
+
+
+
+ Memory tool factories and CLI.
+
+ ```typescript
+ const getTool = api.runtime.tools.createMemoryGetTool(/* ... */);
+ const searchTool = api.runtime.tools.createMemorySearchTool(/* ... */);
+ api.runtime.tools.registerMemoryCli(/* ... */);
+ ```
+
+
+
+ Channel-specific runtime helpers (available when a channel plugin is loaded).
+
+ `api.runtime.channel.mentions` is the shared inbound mention-policy surface for bundled channel plugins that use runtime injection:
+
+ ```typescript
+ 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,
+ },
+ });
+ ```
+
+ Available mention helpers:
+
+ - `buildMentionRegexes`
+ - `matchesMentionPatterns`
+ - `matchesMentionWithExplicit`
+ - `implicitMentionKindWhen`
+ - `resolveInboundMentionDecision`
+
+ `api.runtime.channel.mentions` intentionally does not expose the older `resolveMentionGating*` compatibility helpers. Prefer the normalized `{ facts, policy }` path.
+
+
+
## Storing runtime references
-Use `createPluginRuntimeStore` to store the runtime reference for use outside
-the `register` callback:
+Use `createPluginRuntimeStore` to store the runtime reference for use outside the `register` callback:
-```typescript
-import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
-import type { PluginRuntime } from "openclaw/plugin-sdk/runtime-store";
+
+
+ ```typescript
+ import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
+ import type { PluginRuntime } from "openclaw/plugin-sdk/runtime-store";
-const store = createPluginRuntimeStore({
- pluginId: "my-plugin",
- errorMessage: "my-plugin runtime not initialized",
-});
+ const store = createPluginRuntimeStore({
+ pluginId: "my-plugin",
+ errorMessage: "my-plugin runtime not initialized",
+ });
+ ```
-// In your entry point
-export default defineChannelPluginEntry({
- id: "my-plugin",
- name: "My Plugin",
- description: "Example",
- plugin: myPlugin,
- setRuntime: store.setRuntime,
-});
+
+
+ ```typescript
+ export default defineChannelPluginEntry({
+ id: "my-plugin",
+ name: "My Plugin",
+ description: "Example",
+ plugin: myPlugin,
+ setRuntime: store.setRuntime,
+ });
+ ```
+
+
+ ```typescript
+ export function getRuntime() {
+ return store.getRuntime(); // throws if not initialized
+ }
-// In other files
-export function getRuntime() {
- return store.getRuntime(); // throws if not initialized
-}
+ export function tryGetRuntime() {
+ return store.tryGetRuntime(); // returns null if not initialized
+ }
+ ```
-export function tryGetRuntime() {
- return store.tryGetRuntime(); // returns null if not initialized
-}
-```
+
+
-Prefer `pluginId` for the runtime-store identity. The lower-level `key` form is
-for uncommon cases where one plugin intentionally needs more than one runtime
-slot.
+
+Prefer `pluginId` for the runtime-store identity. The lower-level `key` form is for uncommon cases where one plugin intentionally needs more than one runtime slot.
+
## Other top-level `api` fields
Beyond `api.runtime`, the API object also provides:
-| Field | Type | Description |
-| ------------------------ | ------------------------- | ------------------------------------------------------------------------------------------- |
-| `api.id` | `string` | Plugin id |
-| `api.name` | `string` | Plugin display name |
-| `api.config` | `OpenClawConfig` | Current config snapshot (active in-memory runtime snapshot when available) |
-| `api.pluginConfig` | `Record` | Plugin-specific config from `plugins.entries..config` |
-| `api.logger` | `PluginLogger` | Scoped logger (`debug`, `info`, `warn`, `error`) |
-| `api.registrationMode` | `PluginRegistrationMode` | Current load mode; `"setup-runtime"` is the lightweight pre-full-entry startup/setup window |
-| `api.resolvePath(input)` | `(string) => string` | Resolve a path relative to the plugin root |
+
+ Plugin id.
+
+
+ Plugin display name.
+
+
+ Current config snapshot (active in-memory runtime snapshot when available).
+
+
+ Plugin-specific config from `plugins.entries..config`.
+
+
+ Scoped logger (`debug`, `info`, `warn`, `error`).
+
+
+ Current load mode; `"setup-runtime"` is the lightweight pre-full-entry startup/setup window.
+
+
+ Resolve a path relative to the plugin root.
+
## Related
-- [SDK overview](/plugins/sdk-overview) — subpath reference
-- [SDK entry points](/plugins/sdk-entrypoints) — `definePluginEntry` options
- [Plugin internals](/plugins/architecture) — capability model and registry
+- [SDK entry points](/plugins/sdk-entrypoints) — `definePluginEntry` options
+- [SDK overview](/plugins/sdk-overview) — subpath reference