How to migrate
1
Migrate runtime config load/write helpers
Bundled plugins should stop calling Use
api.runtime.config.loadConfig() and
api.runtime.config.writeConfigFile(...) directly. Prefer config already
passed into the active call path. Long-lived handlers that need the
current process snapshot can use api.runtime.config.current(). Long-lived
agent tools should read ctx.getRuntimeConfig() inside execute so a tool
created before a config write still sees the refreshed config.Config writes go through the transactional helper with an explicit
after-write policy:afterWrite: { mode: "restart", reason: "..." } when the change needs
a clean gateway restart, and afterWrite: { mode: "none", reason: "..." }
only when the caller owns the follow-up and deliberately suppresses the
reload planner. Mutation results include a typed followUp summary for
tests and logging; the gateway remains responsible for applying or
scheduling the restart.loadConfig and writeConfigFile have been removed from the plugin
runtime. Bundled plugins and repo runtime code are guarded by
pnpm check:deprecated-api-usage and
pnpm check:no-runtime-action-load-config: new production plugin usage
fails outright, direct config writes fail, gateway server methods must use
the request runtime snapshot, runtime channel send/action/client helpers
must receive config from their boundary, and long-lived runtime modules
allow zero ambient loadConfig() calls.New plugin code should avoid the broad openclaw/plugin-sdk/config-runtime
barrel. Use the narrow subpath for the job:api.pluginConfig is registration-scoped, not a live getter. Replacing
resolveLivePluginConfigObject(...) requires preserving freshness through
the current config supplied by the runtime boundary. The injected markdown
resolver preserves channel/account precedence and channel defaults;
markdown-table-runtime is a private, JavaScript-only host export.Check named types separately. config-contracts does not export TtsMode,
TtsPersonaConfig, TtsPersonaFallbackPolicy, or SessionResetMode;
session-store-runtime does not export SessionResetMode either. Existing
callers needing those names must keep retained type imports or explicitly
adapt their types. Talk config, cron-store operations, context-visibility
config resolution, and dangerous-name checks also lack a complete modern
typed-public mapping. Missing public contracts require an SDK-owner decision,
not an import of the private focused implementation.Bundled plugins and their tests are scanner-guarded against the broad
barrel so imports and mocks stay local to the behavior they need. The
barrel still exists for external compatibility, but new code should not
depend on it.2
Migrate embedded tool-result extensions to middleware
Bundled plugins must replace embedded-runner-only
Update the plugin manifest at the same time:Installed plugins can also register tool-result middleware when explicitly
enabled and every targeted runtime is declared in
api.registerEmbeddedExtensionFactory(...) tool-result handlers with
runtime-neutral middleware:contracts.agentToolResultMiddleware. Undeclared installed middleware
registrations are rejected.3
Migrate approval-native handlers to capability facts
Approval-capable channel plugins expose native approval behavior through
approvalCapability.nativeRuntime plus the shared runtime-context
registry:- Replace
approvalCapability.handler.loadRuntime(...)withapprovalCapability.nativeRuntime. - Move approval-specific auth/delivery off legacy
plugin.auth/plugin.approvalswiring and ontoapprovalCapability. ChannelPlugin.approvalshas been removed from the public channel-plugin contract; move delivery/native/render fields ontoapprovalCapability.plugin.authremains for channel login/logout flows only; core no longer reads approval auth hooks there.- Register channel-owned runtime objects (clients, tokens, Bolt apps)
through
openclaw/plugin-sdk/channel-runtime-context. - Do not send plugin-owned reroute notices from native approval handlers; core owns routed-elsewhere notices from actual delivery results.
- When passing
channelRuntimeintocreateChannelManager(...), provide a realcreatePluginRuntime().channelsurface - partial stubs are rejected.
4
Audit Windows wrapper fallback behavior
If your plugin uses If your caller does not intentionally rely on shell fallback, do not set
openclaw/plugin-sdk/windows-spawn, unresolved Windows
.cmd/.bat wrappers now fail closed unless you explicitly pass
allowShellFallback: true:allowShellFallback and handle the thrown error instead.5
Find deprecated imports
6
Replace with focused imports
Check the exported name and typed-public contract as well as the import
path. Some functions are renamed; not every retained helper or named type
has a modern public replacement:The explicit alias preserves existing Same pattern for other legacy bridge helpers:
createChannelReplyPipeline(...)
call sites. The modern export is createChannelMessageReplyPipeline;
see Retained channel facade mappings
for the remaining functions and named types.For host-side helpers, use the injected plugin runtime instead of
importing directly:7
Replace broad infra-runtime imports
openclaw/plugin-sdk/infra-runtime still exists for external
compatibility, but new code should use the supported surface it actually
needs:These are symbol-specific mappings, not replacements for the whole barrel.
Private-local entries such as
heartbeat-runtime, delivery-queue-runtime,
fetch-runtime, runtime-fetch, and file-lock are JavaScript-only host
exports, not typed third-party APIs. Heartbeat event/summary/visibility
helpers, pending-delivery drain, transport readiness, concurrency, and file
locking do not have equivalent modern typed-public mappings here. Retain
existing compatibility imports for those operations pending an SDK-owner
decision.fetchWithSsrFGuard is not a drop-in replacement for dispatcher-aware fetch:
it takes an options object and returns { response, finalUrl, release, ... },
not a bare Response; callers must release its resources. The named types
PinnedDispatcherPolicy, GuardedFetchOptions, and GuardedFetchResult
are not exported by ssrf-runtime. Similarly, dedupe-runtime does not
export the legacy DedupeCache or DedupeCacheOptions names. Migrate type
usage explicitly rather than assuming a function move also moves its types.The error mapping does not cover hasErrnoCode, isErrno,
stringifyNonErrorCause, ErrorKind, or detectErrorKind; the last helper
preserves legacy substring classification. The numeric and random mappings
likewise do not cover every timer, expiry, hex, fraction, or integer helper.
Keep unsupported retained imports until their public contract is resolved.System event snapshot inspection and consume helpers remain available only
through the deprecated openclaw/plugin-sdk/infra-runtime compatibility
surface; there is no modern public replacement. Current snapshots carry an
opaque id for one queued occurrence. Preserve it through copies and
serialization when returning a snapshot to consume. Legacy ID-less callers
retain structural matching, which can be ambiguous after queue churn. Do
not treat the ID as persistent or valid across restarts.File-lock nesting is owner-scoped. Pass the same reentrantOwner only for
nested acquisitions in one logical operation; omit it for ordinary locking.
Never use a process-wide constant, because unrelated work would incorrectly
share the critical section.Bundled plugins are scanner-guarded against infra-runtime, so repo code
cannot regress to the broad barrel.8
Migrate channel route helpers
New channel route code uses
openclaw/plugin-sdk/channel-route. The older
route-key names remain as compatibility aliases:The modern route helpers normalize
{ channel, to, accountId, threadId }
consistently across native approvals, reply suppression, inbound dedupe,
cron delivery, and session routing.Channel plugins use messaging.targetResolver.resolveTarget(...) for target-id normalization
and directory-miss fallback,
messaging.inferTargetChatType(...) when core needs an early peer kind,
and messaging.resolveOutboundSessionRoute(...) for provider-native
session and thread identity.9
Build and test