插件运行时助手
关于在注册期间注入到每个插件中的 api.runtime 对象的参考。请使用这些助手,而不是直接导入主机内部组件。
在渠道插件的上下文中使用这些助手的分步指南。
在提供商插件的上下文中使用这些助手的分步指南。
register(api) { const runtime = api.runtime;}配置加载与写入
Section titled “配置加载与写入”优先使用已经传入活动调用路径的配置,例如注册期间的 api.config 或渠道/提供商回调上的 cfg 参数。这可以使一个进程快照在工作流中传递,而不是在热路径上重新解析配置。
仅当长生命周期处理程序需要当前进程快照且没有配置传递给该函数时,才使用 api.runtime.config.current()。返回值是只读的;在编辑之前请克隆或使用变异助手。
工具工厂接收 ctx.runtimeConfig 加上 ctx.getRuntimeConfig()。当工具定义创建后配置可能发生变化时,请在长生命周期工具的 execute 回调中使用该获取器。
使用 api.runtime.config.mutateConfigFile(...) 或 api.runtime.config.replaceConfigFile(...) 持久化更改。每次写入必须选择一个明确的 afterWrite 策略:
afterWrite: { mode: "auto" }让网关重载计划器决定。- 当写入者知道热重载不安全时,
afterWrite: { mode: "restart", reason: "..." }强制执行完全重启。 - 仅当调用者拥有后续操作权时,
afterWrite: { mode: "none", reason: "..." }才会抑制自动重载/重启。
变更辅助函数返回 afterWrite 加上一个类型化的 followUp 摘要,以便调用者可以记录或测试他们是否请求了重启。网关仍然拥有实际发生该重启的时机。
api.runtime.config.loadConfig() 和 api.runtime.config.writeConfigFile(...) 是 runtime-config-load-write 下已弃用的兼容性辅助函数。它们在运行时会警告一次,并且在迁移期间保持可用于旧的外部插件。捆绑插件绝不能使用它们;如果插件代码调用它们或从插件 SDK 子路径导入这些辅助函数,配置边界守卫将失败。
对于直接 SDK 导入,请使用专门的配置子路径,而不是广泛的 openclaw/plugin-sdk/config-runtime 兼容性聚合:使用 config-contracts 获取类型,使用 plugin-config-runtime 获取已加载的配置断言和插件入口查找,使用 runtime-config-snapshot 获取当前进程快照,以及使用 config-mutation 进行写入。捆绑插件测试应该直接模拟这些专门的子路径,而不是模拟广泛的兼容性聚合。
内部 OpenClaw 运行时代码遵循相同的方向:在 CLI、网关或进程边界处加载一次配置,然后传递该值。成功的变异写入会刷新进程运行时快照并推进其内部修订;长期缓存的键应基于运行时拥有的缓存键,而不是在本地序列化配置。长期存在的运行时模块对环境 loadConfig() 调用具有零容忍扫描器;在显式进程边界处使用传递的 cfg、请求 context.getRuntimeConfig() 或 getRuntimeConfig()。
提供商和渠道执行路径必须使用活动的运行时配置快照,而不是为配置回读或编辑返回的文件快照。文件快照保留源值(例如用于 UI 和写入的 SecretRef 标记);提供商回调需要已解析的运行时视图。当辅助函数可能使用活动源快照或活动运行时快照调用时,请在读取凭据之前通过 selectApplicableRuntimeConfig() 路由。
可重用的运行时实用程序
Section titled “可重用的运行时实用程序”对机器人创作的入站消息使用渠道轮次 botLoopProtection 事实。核心在会话记录和分发之前应用共享的内存滑动窗口守卫,而不将策略绑定到特定渠道。该守卫跟踪 (scopeId, conversationId, participant pair) 键,将一对的两个方向一起计数,并在超过窗口预算时应用冷却,同时适时修剪不活动的条目。
向操作员公开此行为的渠道插件应为基线预算首选共享的 channels.defaults.botLoopProtection 形状,然后在此基础上叠加特定于渠道/提供商的覆盖。共享配置使用秒,因为它是面向用户的:
type ChannelBotLoopProtectionConfig = { enabled?: boolean; maxEventsPerWindow?: number; windowSeconds?: number; cooldownSeconds?: number;};传递标准化的机器人对事实以及解析的轮次。核心解析默认值、单位转换和 enabled 语义:
return { channel: "example", routeSessionKey, storePath, ctxPayload, recordInboundSession, runDispatch, botLoopProtection: { scopeId: "account-1", conversationId: "channel-1", senderId: "bot-a", receiverId: "bot-b", config: channelConfig.botLoopProtection, defaultsConfig: runtimeConfig.channels?.defaults?.botLoopProtection, defaultEnabled: allowBotsMode !== "off", },};仅在未通过共享渠道轮次内核的自定义
双方事件循环中直接使用 openclaw/plugin-sdk/pair-loop-guard-runtime。
运行时命名空间
Section titled “运行时命名空间”api.runtime.agent
代理身份、目录和会话管理。
// Resolve the agent's working directoryconst agentDir = api.runtime.agent.resolveAgentDir(cfg);
// Resolve agent workspaceconst workspaceDir = api.runtime.agent.resolveAgentWorkspaceDir(cfg);
// Get agent identityconst identity = api.runtime.agent.resolveAgentIdentity(cfg);
// Get default thinking levelconst thinking = api.runtime.agent.resolveThinkingDefault({ cfg, provider, model,});
// Validate a user-provided thinking level against the active provider profileconst 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 timeoutconst timeoutMs = api.runtime.agent.resolveAgentTimeoutMs(cfg);
// Ensure workspace existsawait api.runtime.agent.ensureAgentWorkspace(cfg);
// Run an embedded agent turnconst 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(...)OpenClaw 是用于从插件代码启动普通 OpenClaw 代理轮次的中性辅助函数。它使用与渠道触发回复相同的提供商/模型解析和代理线束选择。
runEmbeddedPiAgent(...) 作为兼容性别名保留。
resolveThinkingPolicy(...) 返回提供商/模型支持的思考级别和可选默认值。提供商插件通过其思考钩子拥有特定于模型的配置文件,因此工具插件应调用此运行时辅助函数,而不是导入或复制提供商列表。
normalizeThinkingLevel(...) 在根据解析的策略检查用户文本(例如 on、x-high 或 extra high)之前,将其转换为规范存储的级别。
会话存储辅助函数 位于 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(...)Gateway(网关)。它们通过 Gateway(网关) 拥有的会话存储写入器进行路由,保留并发更新,并重用热缓存。saveSessionStore(...) 仍可用于兼容性和离线维护式重写。
api.runtime.agent.defaults
默认模型和提供商常量:
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.llm
运行主机拥有的文本补全,而无需导入提供商内部机制或 重复 OpenClaw 模型/认证/基础 URL 准备工作。
const result = await api.runtime.llm.complete({ messages: [{ role: "user", content: "Summarize this transcript." }], purpose: "my-plugin.summary", maxTokens: 512, temperature: 0.2,});```OpenClaw
该助手使用与 OpenClaw 内置运行时和主机拥有的运行时配置快照相同的简单补全准备路径。上下文引擎接收绑定会话的 `llm.complete` 能力,因此模型调用使用活动会话的代理,并且不会静默回退到默认代理。结果包括提供商/模型/代理归属,以及可用的标准化令牌、缓存和预估成本使用情况。api.runtime.subagent
启动和管理后台子代理运行。
// Start a subagent runconst { 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 completionconst result = await api.runtime.subagent.waitForRun({ runId, timeoutMs: 30000 });
// Read session messagesconst { messages } = await api.runtime.subagent.getSessionMessages({ sessionKey: "agent:main:subagent:search-helper", limit: 10,});
// Delete a sessionawait api.runtime.subagent.deleteSession({ sessionKey: "agent:main:subagent:search-helper",});deleteSession(...) 可以通过 api.runtime.subagent.run(...)Gateway(网关) 删除由同一插件创建的会话。删除任意用户或操作员会话仍需要管理员范围的 Gateway 请求。
api.runtime.nodes
列出已连接的节点,并从 Gateway(网关) 加载的插件代码或插件 CLI 命令中调用节点宿主命令。当插件拥有配对设备上的本地工作时,请使用此功能,例如另一台 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(网关)CLIGateway(网关)RPC
在 Gateway(网关) 内部,此运行时是进程内的。在插件 CLI 命令中,它通过 RPC 调用已配置的 Gateway(网关),因此像 `openclaw googlemeet recover-tab`Gateway(网关) 这样的命令可以从终端检查配对节点。节点命令仍然需要经过正常的 Gateway(网关) 节点配对、命令允许列表、插件节点调用策略以及节点本地命令处理。
暴露危险节点宿主命令的插件应使用 `api.registerNodeInvokePolicy(...)`Gateway(网关) 注册节点调用策略。该策略在 Gateway(网关) 中运行,位于命令允许列表检查之后和命令转发到节点之前,因此直接的 `node.invoke` 调用和更高级别的插件工具共享相同的执行路径。api.runtime.tasks.managedFlows
将 Task Flow 运行时绑定到现有的 OpenClaw 会话密钥或受信任的工具上下文,然后创建和管理 Task Flow,而无需在每次调用时传递所有者。
Task Flow 跟踪持久的多步骤工作流状态。它不是调度程序:
请使用 Cron 或 api.session.workflow.scheduleSessionTurn(...) 进行未来的唤醒,然后在需要流程状态、子任务、等待或取消时,从计划的轮次中使用 managedFlows。
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" },});当您已经拥有来自自己的绑定层的受信任 OpenClaw 会话密钥时,请使用 bindSession({ sessionKey, requesterOrigin })OpenClaw。不要从原始用户输入进行绑定。
api.runtime.tts
文本转语音合成。
// Standard TTSconst clip = await api.runtime.tts.textToSpeech({ text: "Hello from OpenClaw", cfg: api.config,});
// Telephony-optimized TTSconst telephonyClip = await api.runtime.tts.textToSpeechTelephony({ text: "Hello from OpenClaw", cfg: api.config,});
// List available voicesconst voices = await api.runtime.tts.listVoices({ provider: "elevenlabs", cfg: api.config,});使用核心 messages.tts 配置和提供商选择。返回 PCM 音频缓冲区 + 采样率。
api.runtime.mediaUnderstanding
图像、音频和视频分析。
// Describe an imageconst image = await api.runtime.mediaUnderstanding.describeImageFile({ filePath: "/tmp/inbound-photo.jpg", cfg: api.config, agentDir: "/tmp/agent",});
// Transcribe audioconst { 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 videoconst video = await api.runtime.mediaUnderstanding.describeVideoFile({ filePath: "/tmp/inbound-video.mp4", cfg: api.config,});
// Generic file analysisconst result = await api.runtime.mediaUnderstanding.runFile({ filePath: "/tmp/inbound-file.pdf", cfg: api.config,});
// Structured image extraction through a specific provider/model.// Include at least one image; text inputs are supplemental context.const evidence = await api.runtime.mediaUnderstanding.extractStructuredWithModel({ provider: "codex", model: "gpt-5.5", input: [ { type: "image", buffer: receiptImageBuffer, fileName: "receipt.png", mime: "image/png", }, { type: "text", text: "Prefer the printed total over handwritten notes." }, ], instructions: "Extract vendor, total, and searchable tags.", schemaName: "receipt.evidence", jsonSchema: { type: "object", properties: { vendor: { type: "string" }, total: { type: "number" }, tags: { type: "array", items: { type: "string" } }, }, required: ["vendor", "total"], }, 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 },该值记录写入者意图,而不从网关剥夺重启控制权。
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
模型和提供商的身份验证解析。
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({ 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();```
键值存储在重启后仍然存在,并通过运行时绑定的插件 ID 进行隔离。使用 `registerIfAbsent(...)` 进行原子性去重声明:当键缺失或已过期并被注册时,它返回 `true`;当活动值已存在且不覆盖其值、创建时间或 TTL 时,它返回 `false`。限制:每个命名空间 `maxEntries`,每个插件 1,000 个活动行,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.渠道
特定于渠道的运行时辅助函数(在加载渠道插件时可用)。
api.runtime.channel.media 是用于渠道媒体下载和存储的首选接口:
const saved = await api.runtime.channel.media.saveRemoteMedia({ url, subdir: "inbound", maxBytes, filePathHint: fileName,});当远程 URL 应变为 OpenClaw 媒体时,请使用 saveRemoteMedia(...)。当插件已获取 Response 并处理了插件自有身份验证、重定向或允许列表时,请使用 saveResponseMedia(...)。仅当插件需要原始字节进行检查、转换、解密或重新上传时,才使用 readRemoteMediaBuffer(...)。fetchRemoteMedia(...) 仍是 readRemoteMediaBuffer(...) 的已弃用兼容别名。
api.runtime.channel.mentions 是使用运行时注入的捆绑渠道插件共享的入站提及策略接口:
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, },});可用的提及辅助函数:
buildMentionRegexesmatchesMentionPatternsmatchesMentionWithExplicitimplicitMentionKindWhenresolveInboundMentionDecision
api.runtime.channel.mentions 故意不公开较旧的 resolveMentionGating* 兼容辅助函数。首选标准化的 { facts, policy } 路径。
存储运行时引用
Section titled “存储运行时引用”使用 createPluginRuntimeStore 存储运行时引用,以便在 register 回调之外使用:
创建存储
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”, }); ```
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 字段
Section titled “其他顶级 api 字段”除了 api.runtime 之外,API 对象还提供了:
- Plugin internals — 能力模型与注册表
- SDK entry points —
definePluginEntry选项 - SDK overview — 子路径参考